11"""This module is a CRUD interface between resource managers and the sqlalchemy ORM"""
22import 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
44
55from sqlalchemy import delete , func , select
66from sqlalchemy .exc import DBAPIError , IntegrityError , MissingGreenlet , NoResultFound
4444
4545log = logging .getLogger (__name__ )
4646
47+ ModelTypeOneOrMany = Union [TypeModel , list [TypeModel ]]
48+ ActionTrigger = Literal ["create" , "update" ]
49+
4750
4851class SqlalchemyDataLayer (BaseDataLayer ):
4952 """Sqlalchemy data layer"""
@@ -134,12 +137,88 @@ def prepare_id_value(self, col: InstrumentedAttribute, value: Any) -> Any:
134137
135138 return value
136139
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 ):
138159 """
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
140218
141219 :param obj:
142220 :param data_create:
221+ :param action_trigger: indicates which one operation triggered relationships applying
143222 :return:
144223 """
145224 relationships : "PydanticBaseModel" = data_create .relationships
@@ -167,45 +246,15 @@ async def apply_relationships(self, obj: TypeModel, data_create: BaseJSONAPIItem
167246 continue
168247
169248 relationship_info : RelationshipInfo = field .field_info .extra ["relationship" ]
170-
171- # ...
172249 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+ )
173255
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 )
209258
210259 async def create_object (self , data_create : BaseJSONAPIItemInSchema , view_kwargs : dict ) -> TypeModel :
211260 """
@@ -222,7 +271,7 @@ async def create_object(self, data_create: BaseJSONAPIItemInSchema, view_kwargs:
222271 await self .before_create_object (model_kwargs = model_kwargs , view_kwargs = view_kwargs )
223272
224273 obj = self .model (** model_kwargs )
225- await self .apply_relationships (obj , data_create )
274+ await self .apply_relationships (obj , data_create , action_trigger = "create" )
226275
227276 self .session .add (obj )
228277 try :
@@ -348,7 +397,7 @@ async def update_object(
348397 """
349398 new_data = data_update .attributes .dict (exclude_unset = True )
350399
351- await self .apply_relationships (obj , data_update )
400+ await self .apply_relationships (obj , data_update , action_trigger = "update" )
352401
353402 await self .before_update_object (obj , model_kwargs = new_data , view_kwargs = view_kwargs )
354403
0 commit comments