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
@@ -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