Skip to content

Commit 4f746c4

Browse files
committed
refactored sqla dl
1 parent a5dfc0d commit 4f746c4

File tree

1 file changed

+79
-39
lines changed

1 file changed

+79
-39
lines changed

fastapi_jsonapi/data_layers/sqla_orm.py

Lines changed: 79 additions & 39 deletions
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, Optional, Tuple, Type, Union
44

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

4545
log = logging.getLogger(__name__)
4646

47+
ModelTypeOneOrMany = Union[TypeModel, list[TypeModel]]
48+
4749

4850
class SqlalchemyDataLayer(BaseDataLayer):
4951
"""Sqlalchemy data layer"""
@@ -134,9 +136,77 @@ def prepare_id_value(self, col: InstrumentedAttribute, value: Any) -> Any:
134136

135137
return value
136138

139+
async def link_relationship_object(
140+
self,
141+
obj: TypeModel,
142+
relation_name: str,
143+
related_data: Optional[ModelTypeOneOrMany],
144+
):
145+
"""
146+
Links target object with relationship object or objects
147+
148+
:param obj:
149+
:param relation_name:
150+
:param related_data:
151+
"""
152+
# todo: relation name may be different?
153+
setattr(obj, relation_name, related_data)
154+
155+
async def check_object_has_relationship_or_raise(self, obj: TypeModel, relation_name: str):
156+
"""
157+
Checks that there is relationship with relation_name in obj
158+
159+
:param obj:
160+
:param relation_name:
161+
"""
162+
try:
163+
hasattr(obj, relation_name)
164+
except MissingGreenlet:
165+
raise InternalServerError(
166+
detail=(
167+
f"Error of loading the {relation_name!r} relationship. "
168+
f"Please add this relationship to include query parameter explicitly."
169+
),
170+
parameter="include",
171+
)
172+
173+
async def get_related_data_to_link(
174+
self,
175+
related_model: TypeModel,
176+
relationship_info: RelationshipInfo,
177+
relationship_in: Union[
178+
BaseJSONAPIRelationshipDataToOneSchema,
179+
BaseJSONAPIRelationshipDataToManySchema,
180+
],
181+
) -> Optional[ModelTypeOneOrMany]:
182+
"""
183+
Retrieves object or objects to link from database
184+
185+
:param related_model:
186+
:param relationship_info:
187+
:param relationship_in:
188+
"""
189+
if not relationship_in.data:
190+
return {True: [], False: None}[relationship_info.many]
191+
192+
if relationship_info.many:
193+
assert isinstance(relationship_in, BaseJSONAPIRelationshipDataToManySchema)
194+
return await self.get_related_objects_list(
195+
related_model=related_model,
196+
related_id_field=relationship_info.id_field_name,
197+
ids=[r.id for r in relationship_in.data],
198+
)
199+
200+
assert isinstance(relationship_in, BaseJSONAPIRelationshipDataToOneSchema)
201+
return await self.get_related_object(
202+
related_model=related_model,
203+
related_id_field=relationship_info.id_field_name,
204+
id_value=relationship_in.data.id,
205+
)
206+
137207
async def apply_relationships(self, obj: TypeModel, data_create: BaseJSONAPIItemInSchema) -> None:
138208
"""
139-
TODO: move generic code to another method
209+
Handles relationships passed in request
140210
141211
:param obj:
142212
:param data_create:
@@ -167,45 +237,15 @@ async def apply_relationships(self, obj: TypeModel, data_create: BaseJSONAPIItem
167237
continue
168238

169239
relationship_info: RelationshipInfo = field.field_info.extra["relationship"]
170-
171-
# ...
172240
related_model = get_related_model_cls(type(obj), relation_name)
241+
related_data = await self.get_related_data_to_link(
242+
related_model=related_model,
243+
relationship_info=relationship_info,
244+
relationship_in=relationship_in,
245+
)
173246

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)
247+
await self.check_object_has_relationship_or_raise(obj, relation_name)
248+
await self.link_relationship_object(obj, relation_name, related_data)
209249

210250
async def create_object(self, data_create: BaseJSONAPIItemInSchema, view_kwargs: dict) -> TypeModel:
211251
"""

0 commit comments

Comments
 (0)