Skip to content

Commit bb270ae

Browse files
authored
Merge pull request #83 from mts-ai/sqla-data-layer-refactor
Sqla dl refactor
2 parents a5dfc0d + edcec1e commit bb270ae

File tree

1 file changed

+91
-42
lines changed

1 file changed

+91
-42
lines changed

fastapi_jsonapi/data_layers/sqla_orm.py

+91-42
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""This module is a CRUD interface between resource managers and the sqlalchemy ORM"""
22
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
44

55
from sqlalchemy import delete, func, select
66
from sqlalchemy.exc import DBAPIError, IntegrityError, MissingGreenlet, NoResultFound
@@ -44,6 +44,9 @@
4444

4545
log = logging.getLogger(__name__)
4646

47+
ModelTypeOneOrMany = Union[TypeModel, list[TypeModel]]
48+
ActionTrigger = Literal["create", "update"]
49+
4750

4851
class 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

Comments
 (0)