Skip to content

Commit cd57703

Browse files
committed
feat: add context building for LinkedBaseModel in JSON-LD export
1 parent 6a3d455 commit cd57703

1 file changed

Lines changed: 72 additions & 4 deletions

File tree

‎src/oold/model/static.py‎

Lines changed: 72 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from enum import Enum
55
from functools import partial
66
from 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

99
import jsondiff
1010
import 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+
290359
def 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

Comments
 (0)