1
1
"""This module is a CRUD interface between resource managers and the sqlalchemy ORM"""
2
2
import logging
3
- from typing import TYPE_CHECKING , Any , Iterable , List , Optional , Tuple , Type
3
+ from typing import TYPE_CHECKING , Any , Iterable , List , Literal , Optional , Tuple , Type , Union
4
4
5
5
from sqlalchemy import delete , func , select
6
6
from sqlalchemy .exc import DBAPIError , IntegrityError , MissingGreenlet , NoResultFound
44
44
45
45
log = logging .getLogger (__name__ )
46
46
47
+ ModelTypeOneOrMany = Union [TypeModel , list [TypeModel ]]
48
+ ActionTrigger = Literal ["create" , "update" ]
49
+
47
50
48
51
class SqlalchemyDataLayer (BaseDataLayer ):
49
52
"""Sqlalchemy data layer"""
@@ -134,12 +137,88 @@ def prepare_id_value(self, col: InstrumentedAttribute, value: Any) -> Any:
134
137
135
138
return value
136
139
137
- async def apply_relationships (self , obj : TypeModel , data_create : BaseJSONAPIItemInSchema ) -> None :
140
+ async def link_relationship_object (
141
+ self ,
142
+ obj : TypeModel ,
143
+ relation_name : str ,
144
+ related_data : Optional [ModelTypeOneOrMany ],
145
+ action_trigger : ActionTrigger ,
146
+ ):
147
+ """
148
+ Links target object with relationship object or objects
149
+
150
+ :param obj:
151
+ :param relation_name:
152
+ :param related_data:
153
+ :param action_trigger: indicates which one operation triggered relationships applying
154
+ """
155
+ # todo: relation name may be different?
156
+ setattr (obj , relation_name , related_data )
157
+
158
+ async def check_object_has_relationship_or_raise (self , obj : TypeModel , relation_name : str ):
138
159
"""
139
- TODO: move generic code to another method
160
+ Checks that there is relationship with relation_name in obj
161
+
162
+ :param obj:
163
+ :param relation_name:
164
+ """
165
+ try :
166
+ hasattr (obj , relation_name )
167
+ except MissingGreenlet :
168
+ raise InternalServerError (
169
+ detail = (
170
+ f"Error of loading the { relation_name !r} relationship. "
171
+ f"Please add this relationship to include query parameter explicitly."
172
+ ),
173
+ parameter = "include" ,
174
+ )
175
+
176
+ async def get_related_data_to_link (
177
+ self ,
178
+ related_model : TypeModel ,
179
+ relationship_info : RelationshipInfo ,
180
+ relationship_in : Union [
181
+ BaseJSONAPIRelationshipDataToOneSchema ,
182
+ BaseJSONAPIRelationshipDataToManySchema ,
183
+ ],
184
+ ) -> Optional [ModelTypeOneOrMany ]:
185
+ """
186
+ Retrieves object or objects to link from database
187
+
188
+ :param related_model:
189
+ :param relationship_info:
190
+ :param relationship_in:
191
+ """
192
+ if not relationship_in .data :
193
+ return [] if relationship_info .many else None
194
+
195
+ if relationship_info .many :
196
+ assert isinstance (relationship_in , BaseJSONAPIRelationshipDataToManySchema )
197
+ return await self .get_related_objects_list (
198
+ related_model = related_model ,
199
+ related_id_field = relationship_info .id_field_name ,
200
+ ids = [r .id for r in relationship_in .data ],
201
+ )
202
+
203
+ assert isinstance (relationship_in , BaseJSONAPIRelationshipDataToOneSchema )
204
+ return await self .get_related_object (
205
+ related_model = related_model ,
206
+ related_id_field = relationship_info .id_field_name ,
207
+ id_value = relationship_in .data .id ,
208
+ )
209
+
210
+ async def apply_relationships (
211
+ self ,
212
+ obj : TypeModel ,
213
+ data_create : BaseJSONAPIItemInSchema ,
214
+ action_trigger : ActionTrigger ,
215
+ ) -> None :
216
+ """
217
+ Handles relationships passed in request
140
218
141
219
:param obj:
142
220
:param data_create:
221
+ :param action_trigger: indicates which one operation triggered relationships applying
143
222
:return:
144
223
"""
145
224
relationships : "PydanticBaseModel" = data_create .relationships
@@ -167,45 +246,15 @@ async def apply_relationships(self, obj: TypeModel, data_create: BaseJSONAPIItem
167
246
continue
168
247
169
248
relationship_info : RelationshipInfo = field .field_info .extra ["relationship" ]
170
-
171
- # ...
172
249
related_model = get_related_model_cls (type (obj ), relation_name )
250
+ related_data = await self .get_related_data_to_link (
251
+ related_model = related_model ,
252
+ relationship_info = relationship_info ,
253
+ relationship_in = relationship_in ,
254
+ )
173
255
174
- if relationship_info .many :
175
- assert isinstance (relationship_in , BaseJSONAPIRelationshipDataToManySchema )
176
-
177
- related_data = []
178
- if relationship_in .data :
179
- related_data = await self .get_related_objects_list (
180
- related_model = related_model ,
181
- related_id_field = relationship_info .id_field_name ,
182
- ids = [r .id for r in relationship_in .data ],
183
- )
184
- else :
185
- assert isinstance (relationship_in , BaseJSONAPIRelationshipDataToOneSchema )
186
-
187
- if relationship_in .data :
188
- related_data = await self .get_related_object (
189
- related_model = related_model ,
190
- related_id_field = relationship_info .id_field_name ,
191
- id_value = relationship_in .data .id ,
192
- )
193
- else :
194
- setattr (obj , relation_name , None )
195
- continue
196
- try :
197
- hasattr (obj , relation_name )
198
- except MissingGreenlet :
199
- raise InternalServerError (
200
- detail = (
201
- f"Error of loading the { relation_name !r} relationship. "
202
- f"Please add this relationship to include query parameter explicitly."
203
- ),
204
- parameter = "include" ,
205
- )
206
-
207
- # todo: relation name may be different?
208
- setattr (obj , relation_name , related_data )
256
+ await self .check_object_has_relationship_or_raise (obj , relation_name )
257
+ await self .link_relationship_object (obj , relation_name , related_data , action_trigger )
209
258
210
259
async def create_object (self , data_create : BaseJSONAPIItemInSchema , view_kwargs : dict ) -> TypeModel :
211
260
"""
@@ -222,7 +271,7 @@ async def create_object(self, data_create: BaseJSONAPIItemInSchema, view_kwargs:
222
271
await self .before_create_object (model_kwargs = model_kwargs , view_kwargs = view_kwargs )
223
272
224
273
obj = self .model (** model_kwargs )
225
- await self .apply_relationships (obj , data_create )
274
+ await self .apply_relationships (obj , data_create , action_trigger = "create" )
226
275
227
276
self .session .add (obj )
228
277
try :
@@ -348,7 +397,7 @@ async def update_object(
348
397
"""
349
398
new_data = data_update .attributes .dict (exclude_unset = True )
350
399
351
- await self .apply_relationships (obj , data_update )
400
+ await self .apply_relationships (obj , data_update , action_trigger = "update" )
352
401
353
402
await self .before_update_object (obj , model_kwargs = new_data , view_kwargs = view_kwargs )
354
403
0 commit comments