11import inspect
22import logging
3+ from collections import defaultdict
34from contextvars import ContextVar
45from functools import partial
56from typing import (
67 Any ,
78 Callable ,
9+ ClassVar ,
810 Dict ,
911 Iterable ,
1012 List ,
4749included_object_schema_ctx_var : ContextVar [Type [TypeSchema ]] = ContextVar ("included_object_schema_ctx_var" )
4850relationship_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
5156class 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
@@ -241,12 +246,12 @@ def prepare_data_for_relationship(
241246 def update_related_object (
242247 cls ,
243248 relationship_data : Union [Dict [str , str ], List [Dict [str , str ]]],
244- included_objects : Dict [Tuple [str , str ], TypeSchema ],
245249 cache_key : Tuple [str , str ],
246250 related_field_name : str ,
247251 ):
248252 relationships_schema : Type [BaseModel ] = relationships_schema_ctx_var .get ()
249253 object_schema : Type [JSONAPIObjectSchema ] = object_schema_ctx_var .get ()
254+ included_objects : Dict [Tuple [str , str ], TypeSchema ] = included_objects_ctx_var .get ()
250255
251256 relationship_data_schema = get_related_schema (relationships_schema , related_field_name )
252257 parent_included_object = included_objects .get (cache_key )
@@ -257,12 +262,10 @@ def update_related_object(
257262 existing = existing .dict ()
258263 new_relationships .update (existing )
259264 new_relationships .update (
260- {
261- ** {
262- related_field_name : relationship_data_schema (
263- data = relationship_data ,
264- ),
265- },
265+ ** {
266+ related_field_name : relationship_data_schema (
267+ data = relationship_data ,
268+ ),
266269 },
267270 )
268271 included_objects [cache_key ] = object_schema .parse_obj (
@@ -274,17 +277,19 @@ def update_related_object(
274277 @classmethod
275278 def update_known_included (
276279 cls ,
277- included_objects : Dict [Tuple [str , str ], TypeSchema ],
278280 new_included : List [TypeSchema ],
279281 ):
282+ included_objects : Dict [Tuple [str , str ], TypeSchema ] = included_objects_ctx_var .get ()
283+
280284 for included in new_included :
281- included_objects [(included .id , included .type )] = included
285+ key = (included .id , included .type )
286+ if key not in included_objects :
287+ included_objects [key ] = included
282288
283289 @classmethod
284290 def process_single_db_item_and_prepare_includes (
285291 cls ,
286292 parent_db_item : TypeModel ,
287- included_objects : Dict [Tuple [str , str ], TypeSchema ],
288293 ):
289294 previous_resource_type : str = previous_resource_type_ctx_var .get ()
290295 related_field_name : str = related_field_name_ctx_var .get ()
@@ -306,7 +311,6 @@ def process_single_db_item_and_prepare_includes(
306311 )
307312
308313 cls .update_known_included (
309- included_objects = included_objects ,
310314 new_included = new_included ,
311315 )
312316 relationship_data_items .append (data_for_relationship )
@@ -318,7 +322,6 @@ def process_single_db_item_and_prepare_includes(
318322
319323 cls .update_related_object (
320324 relationship_data = relationship_data_items ,
321- included_objects = included_objects ,
322325 cache_key = cache_key ,
323326 related_field_name = related_field_name ,
324327 )
@@ -329,14 +332,12 @@ def process_single_db_item_and_prepare_includes(
329332 def process_db_items_and_prepare_includes (
330333 cls ,
331334 parent_db_items : List [TypeModel ],
332- included_objects : Dict [Tuple [str , str ], TypeSchema ],
333335 ):
334336 next_current_db_item = []
335337
336338 for parent_db_item in parent_db_items :
337339 new_next_items = cls .process_single_db_item_and_prepare_includes (
338340 parent_db_item = parent_db_item ,
339- included_objects = included_objects ,
340341 )
341342 next_current_db_item .extend (new_next_items )
342343 return next_current_db_item
@@ -347,18 +348,21 @@ def process_include_with_nested(
347348 current_db_item : Union [List [TypeModel ], TypeModel ],
348349 item_as_schema : TypeSchema ,
349350 current_relation_schema : Type [TypeSchema ],
351+ included_objects : Dict [Tuple [str , str ], TypeSchema ],
352+ requested_includes : Dict [str , Iterable [str ]],
350353 ) -> Tuple [Dict [str , TypeSchema ], List [JSONAPIObjectSchema ]]:
351354 root_item_key = (item_as_schema .id , item_as_schema .type )
352- included_objects : Dict [ Tuple [ str , str ], TypeSchema ] = {
353- root_item_key : item_as_schema ,
354- }
355+
356+ if root_item_key not in included_objects :
357+ included_objects [ root_item_key ] = item_as_schema
355358 previous_resource_type = item_as_schema .type
356359
360+ previous_related_field_name = previous_resource_type
357361 for related_field_name in include .split (SPLIT_REL ):
358362 object_schemas = self .jsonapi .schema_builder .create_jsonapi_object_schemas (
359363 schema = current_relation_schema ,
360- includes = [ related_field_name ],
361- compute_included_schemas = bool ([ related_field_name ]) ,
364+ includes = requested_includes [ previous_related_field_name ],
365+ compute_included_schemas = True ,
362366 )
363367 relationships_schema = object_schemas .relationships_schema
364368 schemas_include = object_schemas .can_be_included_schemas
@@ -380,16 +384,28 @@ def process_include_with_nested(
380384 related_field_name_ctx_var .set (related_field_name )
381385 relationship_info_ctx_var .set (relationship_info )
382386 included_object_schema_ctx_var .set (included_object_schema )
387+ included_objects_ctx_var .set (included_objects )
383388
384389 current_db_item = self .process_db_items_and_prepare_includes (
385390 parent_db_items = current_db_item ,
386- included_objects = included_objects ,
387391 )
388392
389393 previous_resource_type = relationship_info .resource_type
394+ previous_related_field_name = related_field_name
390395
391396 return included_objects .pop (root_item_key ), list (included_objects .values ())
392397
398+ def prep_requested_includes (self , includes : Iterable [str ]):
399+ requested_includes : Dict [str , set [str ]] = defaultdict (set )
400+ default : str = self .jsonapi .type_
401+ for include in includes :
402+ prev = default
403+ for related_field_name in include .split (SPLIT_REL ):
404+ requested_includes [prev ].add (related_field_name )
405+ prev = related_field_name
406+
407+ return requested_includes
408+
393409 def process_db_object (
394410 self ,
395411 includes : List [str ],
@@ -404,12 +420,17 @@ def process_db_object(
404420 attributes = object_schemas .attributes_schema .from_orm (item ),
405421 )
406422
423+ cache_included_objects : Dict [Tuple [str , str ], TypeSchema ] = {}
424+ requested_includes = self .prep_requested_includes (includes )
425+
407426 for include in includes :
408427 item_as_schema , new_included_objects = self .process_include_with_nested (
409428 include = include ,
410429 current_db_item = item ,
411430 item_as_schema = item_as_schema ,
412431 current_relation_schema = item_schema ,
432+ included_objects = cache_included_objects ,
433+ requested_includes = requested_includes ,
413434 )
414435
415436 included_objects .extend (new_included_objects )
0 commit comments