Skip to content

Commit 1eec4bd

Browse files
committed
feat: implement IRI access via index operator in LinkedBaseModel
1 parent cd57703 commit 1eec4bd

File tree

3 files changed

+80
-21
lines changed

3 files changed

+80
-21
lines changed

src/oold/model/__init__.py

Lines changed: 37 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import json
22
from abc import abstractmethod
3-
from typing import TYPE_CHECKING, Any, Dict, List, Literal, Optional, Union
3+
from typing import TYPE_CHECKING, Any, Dict, List, Literal, Optional, Union, overload
44

55
import pydantic
66
from pydantic import BaseModel
7-
from typing_extensions import Self # to support python < 3.11
7+
from typing_extensions import Self
88

99
from oold.model.static import GenericLinkedBaseModel, export_jsonld, import_jsonld
1010

@@ -60,18 +60,30 @@ def get_resolver(param: GetResolverParam) -> GetResolverResult:
6060
class LinkedBaseModelMetaClass(pydantic.main._model_construction.ModelMetaclass):
6161
def __new__(mcs, name, bases, namespace):
6262
cls = super().__new__(mcs, name, bases, namespace)
63-
schema = {}
64-
65-
# pydantic v2
66-
if "model_config" in namespace:
67-
if "json_schema_extra" in namespace["model_config"]:
68-
schema = namespace["model_config"]["json_schema_extra"]
6963

70-
if "iri" in schema:
71-
iri = schema["iri"]
72-
_types[iri] = cls
64+
if hasattr(cls, "get_cls_iri"):
65+
iri = cls.get_cls_iri()
66+
if iri is not None:
67+
_types[iri] = cls
7368
return cls
7469

70+
# override operators, see https://docs.python.org/3/library/operator.html
71+
72+
@overload
73+
def __getitem__(cls: "LinkedBaseModel", item: str) -> Self:
74+
...
75+
76+
@overload
77+
def __getitem__(cls: "LinkedBaseModel", item: List[str]) -> List[Self]:
78+
...
79+
80+
def __getitem__(
81+
cls: "LinkedBaseModel", item: Union[str, List[str]]
82+
) -> Union[Self, List[Self]]:
83+
"""Allow access to the class by its IRI."""
84+
result = cls._resolve(item if isinstance(item, list) else [item])
85+
return result[item] if isinstance(item, str) else [result[i] for i in item]
86+
7587

7688
# the following switch ensures that autocomplete works in IDEs like VSCode
7789
if TYPE_CHECKING:
@@ -92,6 +104,20 @@ class LinkedBaseModel(_LinkedBaseModel):
92104

93105
__iris__: Optional[Dict[str, Union[str, List[str]]]] = {}
94106

107+
@classmethod
108+
def get_cls_iri(cls) -> str:
109+
"""Return the unique IRI of the class.
110+
Overwrite this method in the subclass."""
111+
schema = {}
112+
if hasattr(cls, "__config__"):
113+
if hasattr(cls.__config__, "schema_extra"):
114+
schema = cls.__config__.schema_extra
115+
116+
if "iri" in schema:
117+
return schema["iri"]
118+
else:
119+
return None
120+
95121
def get_iri(self) -> str:
96122
"""Return the unique IRI of the object.
97123
Overwrite this method in the subclass."""

src/oold/model/v1/__init__.py

Lines changed: 37 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import json
22
from abc import abstractmethod
3-
from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Union
3+
from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Union, overload
44

55
import pydantic
66
from pydantic.v1 import BaseModel, PrivateAttr
7+
from typing_extensions import Self
78

89
from oold.model.static import GenericLinkedBaseModel, export_jsonld, import_jsonld
910

@@ -62,18 +63,30 @@ def get_resolver(param: GetResolverParam) -> GetResolverResult:
6263
class LinkedBaseModelMetaClass(pydantic.v1.main.ModelMetaclass):
6364
def __new__(mcs, name, bases, namespace):
6465
cls = super().__new__(mcs, name, bases, namespace)
65-
schema = {}
66-
67-
# pydantic v1
68-
if "Config" in namespace:
69-
if "schema_extra" in namespace["Config"].__dict__:
70-
schema = namespace["Config"].schema_extra
7166

72-
if "iri" in schema:
73-
iri = schema["iri"]
74-
_types[iri] = cls
67+
if hasattr(cls, "get_cls_iri"):
68+
iri = cls.get_cls_iri()
69+
if iri is not None:
70+
_types[iri] = cls
7571
return cls
7672

73+
# override operators, see https://docs.python.org/3/library/operator.html
74+
75+
@overload
76+
def __getitem__(cls: "LinkedBaseModel", item: str) -> Self:
77+
...
78+
79+
@overload
80+
def __getitem__(cls: "LinkedBaseModel", item: List[str]) -> List[Self]:
81+
...
82+
83+
def __getitem__(
84+
cls: "LinkedBaseModel", item: Union[str, List[str]]
85+
) -> Union[Self, List[Self]]:
86+
"""Allow access to the class by its IRI."""
87+
result = cls._resolve(item if isinstance(item, list) else [item])
88+
return result[item] if isinstance(item, str) else [result[i] for i in item]
89+
7790

7891
# the following switch ensures that autocomplete works in IDEs like VSCode
7992
if TYPE_CHECKING:
@@ -94,6 +107,20 @@ class LinkedBaseModel(_LinkedBaseModel):
94107

95108
__iris__: Optional[Dict[str, Union[str, List[str]]]] = PrivateAttr()
96109

110+
@classmethod
111+
def get_cls_iri(cls) -> str:
112+
"""Return the unique IRI of the class.
113+
Overwrite this method in the subclass."""
114+
schema = {}
115+
if hasattr(cls, "__config__"):
116+
if hasattr(cls.__config__, "schema_extra"):
117+
schema = cls.__config__.schema_extra
118+
119+
if "iri" in schema:
120+
return schema["iri"]
121+
else:
122+
return None
123+
97124
def get_iri(self) -> str:
98125
"""Return the unique IRI of the object.
99126
Overwrite this method in the subclass."""

tests/test_oold.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,12 @@ def export_json(obj):
217217
else:
218218
assert False, "ValueError not raised"
219219

220+
# test index operator for getting objects by IRI
221+
f = model.Foo["ex:f"]
222+
assert f.id == "ex:f"
223+
[b1, b2] = model.Bar[["ex:b1", "ex:b2"]]
224+
assert b1.id == "ex:b1" and b2.id == "ex:b2"
225+
220226

221227
def test_core():
222228
_run(pydantic_version="v1")

0 commit comments

Comments
 (0)