Coverage for apps/kwai-api/src/kwai_api/v1/club/members/presenters.py: 97%

64 statements  

« prev     ^ index     » next       coverage.py v7.11.0, created at 2024-01-01 00:00 +0000

1"""Module that defines presenters for the club api.""" 

2 

3from typing import Self 

4 

5from kwai_bc_club.domain.contact import ContactEntity 

6from kwai_bc_club.domain.member import MemberEntity 

7from kwai_bc_club.domain.person import PersonEntity 

8from kwai_bc_club.import_members import ( 

9 FailureMemberImportResult, 

10 MemberImportResult, 

11 OkMemberImportResult, 

12) 

13from kwai_core.domain.presenter import AsyncPresenter, IterableResult, Presenter 

14from kwai_core.json_api import ( 

15 JsonApiPresenter, 

16 Meta, 

17 Relationship, 

18 RelationshipList, 

19 ResourceMeta, 

20) 

21 

22from kwai_api.schemas.resources import CountryResourceIdentifier 

23from kwai_api.v1.club.schemas.contact import ( 

24 ContactAttributes, 

25 ContactDocument, 

26 ContactRelationships, 

27 ContactResource, 

28) 

29from kwai_api.v1.club.schemas.member import ( 

30 MemberAttributes, 

31 MemberDocument, 

32 MemberRelationships, 

33 MemberResource, 

34 MembersDocument, 

35) 

36from kwai_api.v1.club.schemas.person import ( 

37 PersonAttributes, 

38 PersonDocument, 

39 PersonRelationships, 

40 PersonResource, 

41) 

42from kwai_api.v1.club.schemas.resources import ( 

43 ContactResourceIdentifier, 

44 MemberResourceIdentifier, 

45 PersonResourceIdentifier, 

46) 

47from kwai_api.v1.club.schemas.upload import ( 

48 MemberUploadAttributes, 

49 MemberUploadDocument, 

50 MemberUploadError, 

51 MemberUploadRelationships, 

52 MemberUploadResource, 

53 UploadedMemberMeta, 

54 UploadedMemberResource, 

55) 

56from kwai_api.v1.presenters import JsonApiCountryPresenter 

57 

58 

59class JsonApiContactPresenter( 

60 JsonApiPresenter[ContactDocument], Presenter[ContactEntity] 

61): 

62 """A presenter that transforms a contact entity into a JSON:API document.""" 

63 

64 def present(self, contact: ContactEntity) -> Self: 

65 country_document = ( 

66 JsonApiCountryPresenter().present(contact.address.country).get_document() 

67 ) 

68 self._document = ContactDocument( 

69 data=ContactResource( 

70 id=str(contact.id), 

71 attributes=ContactAttributes( 

72 emails=[str(email) for email in contact.emails], 

73 tel=contact.tel, 

74 mobile=contact.mobile, 

75 address=contact.address.address, 

76 postal_code=contact.address.postal_code, 

77 city=contact.address.city, 

78 county=contact.address.county, 

79 remark=contact.remark, 

80 ), 

81 relationships=ContactRelationships( 

82 country=Relationship[CountryResourceIdentifier]( 

83 data=CountryResourceIdentifier(id=country_document.data.id) 

84 ) 

85 ), 

86 meta=ResourceMeta( 

87 created_at=str(contact.traceable_time.created_at), 

88 updated_at=str(contact.traceable_time.updated_at), 

89 ), 

90 ), 

91 included={country_document.data}, 

92 ) 

93 return self 

94 

95 

96class JsonApiPersonPresenter(JsonApiPresenter, Presenter[PersonEntity]): 

97 """A presenter that transforms a person entity into a JSON:API document.""" 

98 

99 def present(self, person: PersonEntity) -> Self: 

100 country_document = ( 

101 JsonApiCountryPresenter().present(person.nationality).get_document() 

102 ) 

103 contact_document = ( 

104 JsonApiContactPresenter().present(person.contact).get_document() 

105 ) 

106 

107 self._document = PersonDocument( 

108 data=PersonResource( 

109 id=str(person.id), 

110 attributes=PersonAttributes( 

111 first_name=person.name.first_name, 

112 last_name=person.name.last_name, 

113 gender=person.gender.value, 

114 birthdate=str(person.birthdate), 

115 remark=person.remark, 

116 ), 

117 relationships=PersonRelationships( 

118 nationality=Relationship[CountryResourceIdentifier]( 

119 data=CountryResourceIdentifier(id=country_document.data.id) 

120 ), 

121 contact=Relationship[ContactResourceIdentifier]( 

122 data=ContactResourceIdentifier(id=contact_document.data.id) 

123 ), 

124 ), 

125 meta=ResourceMeta( 

126 created_at=str(person.traceable_time.created_at), 

127 updated_at=str(person.traceable_time.updated_at), 

128 ), 

129 ), 

130 included={country_document.data, contact_document.data} 

131 | contact_document.included, 

132 ) 

133 return self 

134 

135 

136class JsonApiMemberPresenter(JsonApiPresenter[MemberDocument], Presenter[MemberEntity]): 

137 """A presenter that transform a member entity into a JSON:API document.""" 

138 

139 def present(self, member: MemberEntity) -> Self: 

140 person_document = JsonApiPersonPresenter().present(member.person).get_document() 

141 

142 self._document = MemberDocument( 

143 data=MemberResource( 

144 id=str(member.uuid), 

145 attributes=MemberAttributes( 

146 license_number=member.license.number, 

147 license_end_date=str(member.license.end_date), 

148 remark=member.remark, 

149 active=member.active, 

150 competition=member.competition, 

151 ), 

152 meta=ResourceMeta( 

153 created_at=str(member.traceable_time.created_at), 

154 updated_at=str(member.traceable_time.updated_at), 

155 ), 

156 relationships=MemberRelationships( 

157 person=Relationship[PersonResourceIdentifier]( 

158 data=PersonResourceIdentifier(id=person_document.data.id) 

159 ) 

160 ), 

161 ), 

162 included={person_document.data} | person_document.included, 

163 ) 

164 

165 return self 

166 

167 

168class JsonApiMembersPresenter( 

169 JsonApiPresenter[MembersDocument], AsyncPresenter[IterableResult[MemberEntity]] 

170): 

171 """A presenter that transform an iterator for members into a JSON:API document.""" 

172 

173 async def present(self, result: IterableResult[MemberEntity]) -> Self: 

174 self._document = MembersDocument( 

175 meta=Meta(count=result.count, offset=result.offset, limit=result.limit), 

176 ) 

177 async for member in result.iterator: 

178 member_document = JsonApiMemberPresenter().present(member).get_document() 

179 self._document.data.append(member_document.data) 

180 self._document.included |= member_document.included 

181 return self 

182 

183 

184class JsonApiUploadMemberPresenter( 

185 JsonApiPresenter[MemberUploadDocument], 

186 AsyncPresenter[IterableResult[MemberImportResult]], 

187): 

188 """A presenter that transform a file upload of a member into a JSON:API document.""" 

189 

190 def __init__(self, preview: bool = False): 

191 super().__init__() 

192 self._preview = preview 

193 

194 async def present(self, result: IterableResult[MemberImportResult]) -> Self: 

195 async for import_result in result.iterator: 

196 if self._document is None: 

197 self._document = MemberUploadDocument( 

198 data=MemberUploadResource( 

199 id=str(import_result.file_upload.uuid), 

200 attributes=MemberUploadAttributes( 

201 filename=import_result.file_upload.filename, 

202 preview=self._preview, 

203 remark=import_result.file_upload.remark, 

204 errors=[], 

205 ), 

206 relationships=MemberUploadRelationships( 

207 members=RelationshipList[MemberResourceIdentifier]() 

208 ), 

209 ) 

210 ) 

211 

212 match import_result: 

213 case OkMemberImportResult(): 

214 member_document = ( 

215 JsonApiMemberPresenter() 

216 .present(import_result.member) 

217 .get_document() 

218 ) 

219 self._document.data.relationships.members.data.append( 

220 MemberResourceIdentifier(id=member_document.data.id) 

221 ) 

222 

223 upload_meta = UploadedMemberMeta( 

224 **member_document.data.meta.model_dump(), 

225 row=import_result.row, 

226 new=import_result.member.id.is_empty(), 

227 ) 

228 # A new member has related resources that are not saved yet, 

229 # so give them temporarily the same id as the member. 

230 if ( 

231 upload_meta.new 

232 and member_document.data.relationships.person.data 

233 ): 

234 member_document.data.relationships.person.data.id = ( 

235 member_document.data.id 

236 ) 

237 for included in member_document.included: 

238 if ( 

239 included.type == "persons" 

240 and included.relationships.contact.data 

241 ): 

242 included.relationships.contact.data.id = ( 

243 member_document.data.id 

244 ) 

245 if included.id == "0": 

246 included.id = member_document.data.id 

247 self._document.included |= member_document.included 

248 uploaded_member = UploadedMemberResource( 

249 id=member_document.data.id, 

250 meta=upload_meta, 

251 attributes=member_document.data.attributes, 

252 relationships=member_document.data.relationships, 

253 ) 

254 self._document.included.add(uploaded_member) 

255 case FailureMemberImportResult(): 

256 self._document.data.attributes.errors.append( 

257 MemberUploadError( 

258 row=import_result.row, 

259 message=import_result.to_message(), 

260 ) 

261 ) 

262 return self