Coverage for bc/kwai-bc-club/src/kwai_bc_club/import_members.py: 86%
71 statements
« prev ^ index » next coverage.py v7.11.0, created at 2024-01-01 00:00 +0000
« prev ^ index » next coverage.py v7.11.0, created at 2024-01-01 00:00 +0000
1"""Module that implements a use case for importing members."""
3from abc import ABC
4from dataclasses import dataclass, replace
5from typing import AsyncGenerator
7from kwai_core.domain.presenter import AsyncPresenter, IterableResult
8from kwai_core.domain.use_case import UseCaseResult
10from kwai_bc_club.domain.file_upload import FileUploadEntity
11from kwai_bc_club.domain.member import MemberEntity
12from kwai_bc_club.repositories import member_importer
13from kwai_bc_club.repositories.file_upload_repository import FileUploadRepository
14from kwai_bc_club.repositories.member_repository import (
15 MemberNotFoundException,
16 MemberRepository,
17)
20@dataclass(kw_only=True, slots=True, frozen=True)
21class MemberImportResult(UseCaseResult, ABC):
22 """The result of the use case ImportMembers."""
24 file_upload: FileUploadEntity
25 row: int
28@dataclass(kw_only=True, slots=True, frozen=True)
29class OkMemberImportResult(MemberImportResult):
30 """A successful import of a member."""
32 member: MemberEntity
34 def to_message(self) -> str:
35 return f"Member {self.member.id}(row={self.row}) imported successfully."
38@dataclass(kw_only=True, slots=True, frozen=True)
39class FailureMemberImportResult(MemberImportResult):
40 """An import of a member failed."""
42 message: str
44 def to_message(self) -> str:
45 return self.message
48@dataclass(kw_only=True, slots=True, frozen=True)
49class ImportMembersCommand:
50 """Input for the use case "ImportMembers"."""
52 preview: bool = True
55class ImportMembers:
56 """Use case for importing members.
58 Note: The presenter will need to process all elements from this generator
59 to save all the members into the database.
60 """
62 def __init__(
63 self,
64 importer: member_importer.MemberImporter,
65 file_upload_repo: FileUploadRepository,
66 member_repo: MemberRepository,
67 presenter: AsyncPresenter[IterableResult[MemberImportResult]],
68 ):
69 """Initialize the use case.
71 Args:
72 importer: A class that is responsible for importing members from a resource.
73 file_upload_repo: A repository for storing the file upload information.
74 member_repo: A repository for managing members.
75 presenter: A presenter
76 """
77 self._importer = importer
78 self._file_upload_repo = file_upload_repo
79 self._member_repo = member_repo
80 self._presenter = presenter
82 async def execute(self, command: ImportMembersCommand):
83 """Execute the use case."""
84 file_upload_entity = await self._file_upload_repo.create(
85 self._importer.create_file_upload_entity(command.preview)
86 )
88 await self._presenter.present(
89 IterableResult(
90 count=0,
91 iterator=self.process_member(file_upload_entity, command.preview),
92 )
93 )
95 if not command.preview:
96 await self._activate_members(file_upload_entity)
98 async def process_member(
99 self, file_upload_entity: FileUploadEntity, preview: bool
100 ) -> AsyncGenerator[MemberImportResult, None]:
101 """Get the next imported member."""
102 async for import_result in self._importer.import_():
103 match import_result:
104 case member_importer.OkResult():
105 member = await self._save_member(
106 file_upload_entity, import_result.member, preview
107 )
108 yield OkMemberImportResult(
109 file_upload=file_upload_entity,
110 row=import_result.row,
111 member=member,
112 )
113 case member_importer.FailureResult():
114 yield FailureMemberImportResult(
115 file_upload=file_upload_entity,
116 row=import_result.row,
117 message=import_result.message,
118 )
120 async def _save_member(
121 self, file_upload: FileUploadEntity, member: MemberEntity, preview: bool
122 ) -> MemberEntity:
123 """Create or update the member."""
124 existing_member = await self._get_member(member)
125 if existing_member is not None:
126 updated_member = self._update_member(existing_member, member)
127 await self._file_upload_repo.save_member(file_upload, updated_member)
128 if not preview:
129 await self._member_repo.update(updated_member)
130 return updated_member
132 if not preview:
133 member = await self._member_repo.create(member)
134 await self._file_upload_repo.save_member(file_upload, member)
136 return member
138 @classmethod
139 def _update_member(
140 cls, old_member: MemberEntity, new_member: MemberEntity
141 ) -> MemberEntity:
142 """Update an existing member with the new imported data."""
143 updated_contact = replace(
144 old_member.person.contact,
145 traceable_time=old_member.person.contact.traceable_time.mark_for_update(),
146 )
147 updated_person = replace(
148 old_member.person,
149 id=old_member.person.id,
150 contact=updated_contact,
151 traceable_time=old_member.person.traceable_time.mark_for_update(),
152 )
153 updated_member = replace(
154 new_member,
155 id=old_member.id,
156 uuid=old_member.uuid,
157 remark=old_member.remark,
158 competition=old_member.competition,
159 active=old_member.active,
160 person=updated_person,
161 traceable_time=old_member.traceable_time.mark_for_update(),
162 )
163 return updated_member
165 async def _get_member(self, member: MemberEntity) -> MemberEntity | None:
166 """Return the member.
168 Returns:
169 If found the member is returned, otherwise None is returned.
170 """
171 member_query = self._member_repo.create_query()
172 member_query.filter_by_license(member.license.number)
174 try:
175 member = await self._member_repo.get(member_query)
176 except MemberNotFoundException:
177 return None
179 return member
181 async def _activate_members(self, upload_entity: FileUploadEntity):
182 """Activate members.
184 Members that are part of the upload will be activated.
185 Members not part of the upload will be deactivated.
186 """
187 await self._member_repo.activate_members(upload_entity)
188 await self._member_repo.deactivate_members(upload_entity)