Skip to content

Commit 3845368

Browse files
committed
fix multi include
1 parent d39673b commit 3845368

1 file changed

Lines changed: 42 additions & 21 deletions

File tree

fastapi_jsonapi/views/view_base.py

Lines changed: 42 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import inspect
22
import logging
3+
from collections import defaultdict
34
from contextvars import ContextVar
45
from functools import partial
56
from typing import (
67
Any,
78
Callable,
9+
ClassVar,
810
Dict,
911
Iterable,
1012
List,
@@ -47,14 +49,17 @@
4749
included_object_schema_ctx_var: ContextVar[Type[TypeSchema]] = ContextVar("included_object_schema_ctx_var")
4850
relationship_info_ctx_var: ContextVar[RelationshipInfo] = ContextVar("relationship_info_ctx_var")
4951

52+
# TODO: just change state on `self`!! (refactor)
53+
included_objects_ctx_var: ContextVar[Dict[Tuple[str, str], TypeSchema]] = ContextVar("included_objects_ctx_var")
54+
5055

5156
class ViewBase:
5257
"""
5358
Views are inited for each request
5459
"""
5560

5661
data_layer_cls = BaseDataLayer
57-
method_dependencies: Dict[HTTPMethod, HTTPMethodConfig] = {}
62+
method_dependencies: ClassVar[Dict[HTTPMethod, HTTPMethodConfig]] = {}
5863

5964
def __init__(self, *, request: Request, jsonapi: RoutersJSONAPI, **options):
6065
self.request: Request = request
@@ -240,12 +245,12 @@ def prepare_data_for_relationship(
240245
def update_related_object(
241246
cls,
242247
relationship_data: Union[Dict[str, str], List[Dict[str, str]]],
243-
included_objects: Dict[Tuple[str, str], TypeSchema],
244248
cache_key: Tuple[str, str],
245249
related_field_name: str,
246250
):
247251
relationships_schema: Type[BaseModel] = relationships_schema_ctx_var.get()
248252
object_schema: Type[JSONAPIObjectSchema] = object_schema_ctx_var.get()
253+
included_objects: Dict[Tuple[str, str], TypeSchema] = included_objects_ctx_var.get()
249254

250255
relationship_data_schema = get_related_schema(relationships_schema, related_field_name)
251256
parent_included_object = included_objects.get(cache_key)
@@ -256,12 +261,10 @@ def update_related_object(
256261
existing = existing.dict()
257262
new_relationships.update(existing)
258263
new_relationships.update(
259-
{
260-
**{
261-
related_field_name: relationship_data_schema(
262-
data=relationship_data,
263-
),
264-
},
264+
**{
265+
related_field_name: relationship_data_schema(
266+
data=relationship_data,
267+
),
265268
},
266269
)
267270
included_objects[cache_key] = object_schema.parse_obj(
@@ -273,17 +276,19 @@ def update_related_object(
273276
@classmethod
274277
def update_known_included(
275278
cls,
276-
included_objects: Dict[Tuple[str, str], TypeSchema],
277279
new_included: List[TypeSchema],
278280
):
281+
included_objects: Dict[Tuple[str, str], TypeSchema] = included_objects_ctx_var.get()
282+
279283
for included in new_included:
280-
included_objects[(included.id, included.type)] = included
284+
key = (included.id, included.type)
285+
if key not in included_objects:
286+
included_objects[key] = included
281287

282288
@classmethod
283289
def process_single_db_item_and_prepare_includes(
284290
cls,
285291
parent_db_item: TypeModel,
286-
included_objects: Dict[Tuple[str, str], TypeSchema],
287292
):
288293
previous_resource_type: str = previous_resource_type_ctx_var.get()
289294
related_field_name: str = related_field_name_ctx_var.get()
@@ -305,7 +310,6 @@ def process_single_db_item_and_prepare_includes(
305310
)
306311

307312
cls.update_known_included(
308-
included_objects=included_objects,
309313
new_included=new_included,
310314
)
311315
relationship_data_items.append(data_for_relationship)
@@ -317,7 +321,6 @@ def process_single_db_item_and_prepare_includes(
317321

318322
cls.update_related_object(
319323
relationship_data=relationship_data_items,
320-
included_objects=included_objects,
321324
cache_key=cache_key,
322325
related_field_name=related_field_name,
323326
)
@@ -328,14 +331,12 @@ def process_single_db_item_and_prepare_includes(
328331
def process_db_items_and_prepare_includes(
329332
cls,
330333
parent_db_items: List[TypeModel],
331-
included_objects: Dict[Tuple[str, str], TypeSchema],
332334
):
333335
next_current_db_item = []
334336

335337
for parent_db_item in parent_db_items:
336338
new_next_items = cls.process_single_db_item_and_prepare_includes(
337339
parent_db_item=parent_db_item,
338-
included_objects=included_objects,
339340
)
340341
next_current_db_item.extend(new_next_items)
341342
return next_current_db_item
@@ -346,18 +347,21 @@ def process_include_with_nested(
346347
current_db_item: Union[List[TypeModel], TypeModel],
347348
item_as_schema: TypeSchema,
348349
current_relation_schema: Type[TypeSchema],
350+
included_objects: Dict[Tuple[str, str], TypeSchema],
351+
requested_includes: Dict[str, Iterable[str]],
349352
) -> Tuple[Dict[str, TypeSchema], List[JSONAPIObjectSchema]]:
350353
root_item_key = (item_as_schema.id, item_as_schema.type)
351-
included_objects: Dict[Tuple[str, str], TypeSchema] = {
352-
root_item_key: item_as_schema,
353-
}
354+
355+
if root_item_key not in included_objects:
356+
included_objects[root_item_key] = item_as_schema
354357
previous_resource_type = item_as_schema.type
355358

359+
previous_related_field_name = previous_resource_type
356360
for related_field_name in include.split(SPLIT_REL):
357361
object_schemas = self.jsonapi.schema_builder.create_jsonapi_object_schemas(
358362
schema=current_relation_schema,
359-
includes=[related_field_name],
360-
compute_included_schemas=bool([related_field_name]),
363+
includes=requested_includes[previous_related_field_name],
364+
compute_included_schemas=True,
361365
)
362366
relationships_schema = object_schemas.relationships_schema
363367
schemas_include = object_schemas.can_be_included_schemas
@@ -379,16 +383,28 @@ def process_include_with_nested(
379383
related_field_name_ctx_var.set(related_field_name)
380384
relationship_info_ctx_var.set(relationship_info)
381385
included_object_schema_ctx_var.set(included_object_schema)
386+
included_objects_ctx_var.set(included_objects)
382387

383388
current_db_item = self.process_db_items_and_prepare_includes(
384389
parent_db_items=current_db_item,
385-
included_objects=included_objects,
386390
)
387391

388392
previous_resource_type = relationship_info.resource_type
393+
previous_related_field_name = related_field_name
389394

390395
return included_objects.pop(root_item_key), list(included_objects.values())
391396

397+
def prep_requested_includes(self, includes: Iterable[str]):
398+
requested_includes: Dict[str, set[str]] = defaultdict(set)
399+
default: str = self.jsonapi.type_
400+
for include in includes:
401+
prev = default
402+
for related_field_name in include.split(SPLIT_REL):
403+
requested_includes[prev].add(related_field_name)
404+
prev = related_field_name
405+
406+
return requested_includes
407+
392408
def process_db_object(
393409
self,
394410
includes: List[str],
@@ -403,12 +419,17 @@ def process_db_object(
403419
attributes=object_schemas.attributes_schema.from_orm(item),
404420
)
405421

422+
cache_included_objects: Dict[Tuple[str, str], TypeSchema] = {}
423+
requested_includes = self.prep_requested_includes(includes)
424+
406425
for include in includes:
407426
item_as_schema, new_included_objects = self.process_include_with_nested(
408427
include=include,
409428
current_db_item=item,
410429
item_as_schema=item_as_schema,
411430
current_relation_schema=item_schema,
431+
included_objects=cache_included_objects,
432+
requested_includes=requested_includes,
412433
)
413434

414435
included_objects.extend(new_included_objects)

0 commit comments

Comments
 (0)