44from enum import Enum
55from functools import partial
66from operator import is_
7- from typing import Any , Callable , Dict , Optional , Tuple , TypeVar , Union
7+ from typing import Any , Callable , Dict , List , Optional , Tuple , TypeVar , Union
88
99import jsondiff
1010import pydantic_core .core_schema as core_schema
@@ -287,6 +287,75 @@ def loader(url, options=None):
287287 return loader
288288
289289
290+ def _interate_annotation_args (field_annotation_class ):
291+ """Get the annotation class from a field annotation.
292+ e.g. <SomeClass> or List[SomeClass] or Optional[SomeClass]"""
293+ annotation_class = None
294+ if hasattr (field_annotation_class , "__origin__" ):
295+ # if _origin__ is List or list or Union interate over __args__
296+ if field_annotation_class .__origin__ in [list , List , Union ]:
297+ for arg in field_annotation_class .__args__ :
298+ # recursive call if arg is List or Union
299+ # Todo: handle multiple types in Union
300+ if hasattr (arg , "__origin__" ):
301+ annotation_class = _interate_annotation_args (arg )
302+ if annotation_class is not None :
303+ break
304+ if arg is not type (None ):
305+ annotation_class = arg
306+ break
307+ return annotation_class
308+
309+
310+ def build_context (model_cls , model_type , visited = None ) -> Dict :
311+ """Takes the base context from the model_type.
312+ Iterate over the model_type fields.
313+ If the field is another LinkedBaseModel, a nested context is built recursively."""
314+
315+ if visited is None :
316+ visited = set ()
317+ if model_cls in visited :
318+ return None
319+ visited .add (model_cls )
320+ context = {}
321+
322+ if model_type == BaseModel :
323+ # get the context from self.ConfigDict.json_schema_extra["@context"]
324+ context = model_cls .model_config .get ("json_schema_extra" , {}).get (
325+ "@context" , {}
326+ )
327+ for field_name , field_value in model_cls .model_fields .items ():
328+ annotation_class = _interate_annotation_args (field_value .annotation )
329+ if annotation_class is not None and issubclass (annotation_class , BaseModel ):
330+ nested_context = build_context (annotation_class , model_type , visited )
331+ target_context = context
332+ # if target context is a list,
333+ # find the dict that contains the field_name
334+ if isinstance (target_context , list ):
335+ for ctx in target_context :
336+ if isinstance (ctx , dict ) and field_name in ctx :
337+ target_context = ctx
338+ break
339+ if nested_context is not None :
340+ if target_context [field_name ] is None :
341+ continue
342+ if isinstance (target_context [field_name ], str ):
343+ target_context [field_name ] = {
344+ "@id" : target_context [field_name ],
345+ "@context" : nested_context ,
346+ }
347+ elif isinstance (target_context [field_name ], dict ):
348+ target_context [field_name ] = {
349+ ** target_context [field_name ],
350+ ** {"@context" : nested_context },
351+ }
352+
353+ if model_type == BaseModel_v1 :
354+ context = model_cls .__config__ .schema_extra .get ("@context" , {})
355+
356+ return context
357+
358+
290359def export_jsonld (model_instance , model_type ) -> Dict :
291360 """Return the RDF representation of the object as JSON-LD."""
292361
@@ -297,6 +366,7 @@ def export_jsonld(model_instance, model_type) -> Dict:
297366 context = model_instance .model_config .get ("json_schema_extra" , {}).get (
298367 "@context" , {}
299368 )
369+ context = build_context (model_instance .__class__ , model_type )
300370 data = json .loads (model_instance .model_dump_json (exclude_none = True ))
301371 if model_type == BaseModel_v1 :
302372 context = model_instance .__class__ .__config__ .schema_extra .get ("@context" , {})
@@ -336,9 +406,7 @@ def import_jsonld(model_type, jsonld_dict: Dict, _types: Dict[str, type]):
336406 return None
337407 if model_type == BaseModel :
338408 # get the context from self.ConfigDict.json_schema_extra["@context"]
339- context = model_cls .model_config .get ("json_schema_extra" , {}).get (
340- "@context" , {}
341- )
409+ context = build_context (model_cls , model_type )
342410 if model_type == BaseModel_v1 :
343411 context = model_cls .__config__ .schema_extra .get ("@context" , {})
344412 jsonld .set_document_loader (get_jsonld_context_loader (model_cls , model_type ))
0 commit comments