Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
aec3105
fix some typos
Frosty2500 Dec 29, 2024
4872506
fix README.md
Frosty2500 Dec 29, 2024
188508e
tutorial_serialization_deserialization: Fix missing import (#363)
Frosty2500 Jan 6, 2025
de94f65
README.md: Fix typo in Markdown Link
s-heppner Jan 2, 2025
fc99d9c
compliance-tool: Fix compliance tool imports and unitests (#356)
Frosty2500 Jan 15, 2025
886dcc0
aasx.adapter: Fix `semantic_id` type deserialization of `ModelReferen…
JGrothoff Jan 15, 2025
f858e75
sdk: Update version of pyecma dependency in pyproject.toml
s-heppner Jan 20, 2025
dad15ac
sdk: Move testing depencies to dev section in pyproject.toml (#369)
JGrothoff Jan 20, 2025
da1d5b6
basyx.provider: Add SetObjectStore to provider (#340)
zrgt Jan 20, 2025
6dc956d
scripts.set_copyright_year: Add a CI check to ensure the copyright is…
Frosty2500 Feb 17, 2025
e8fc37b
sdk/.readthedocs.yaml: Install dependencies from pyproject.toml (#359)
Frosty2500 Feb 21, 2025
8bd2c54
compliance_tool: add pyproject.toml (#361)
Frosty2500 Feb 21, 2025
9c98a7b
http.py: Fix redirects, bugs, and SDK installation (#362)
Frosty2500 Apr 15, 2025
1aac797
CONTRIBUTING.md: Improve codestyle and testing section (#376)
s-heppner Apr 17, 2025
079e128
compliance_tool: Remove setup.py (#377)
s-heppner Apr 23, 2025
288ea49
Add CI job to release compliance-tool to PyPI (#382)
s-heppner Apr 23, 2025
a6a904a
sdk: Update lxml and mypy dependency in pyproject.toml (#392)
moritzsommer Jun 8, 2025
ac8f7f5
Merge branch 'main' into develop
s-heppner Jul 30, 2025
0f044f8
Added find_registered_referable_type_in_key_types_classes()
zrgt Aug 28, 2025
354c6e0
Add `get_identifiable_root()` and `get_id_short_path()`
zrgt Aug 28, 2025
df33289
Fix annotations
zrgt Aug 29, 2025
c3442bc
Adapt `referable.repr()` and fix tests
zrgt Aug 29, 2025
9a9672c
Fix tests
zrgt Aug 29, 2025
b17ca90
Refactor test
zrgt Aug 29, 2025
f1e21f9
Fix code style
zrgt Aug 29, 2025
5ef960c
Add util methods for id_short_path
zrgt Sep 5, 2025
3aaf274
Minor fix
zrgt Sep 5, 2025
97a6505
Refactor `UniqueIdShortNamespace.get_referable`
zrgt Sep 5, 2025
a7e9f60
Fix docstring
zrgt Sep 5, 2025
974e112
Merge branch 'eclipse-basyx:develop' into develop
Frosty2500 Sep 23, 2025
b33fa52
Refactor func and add docs
zrgt Oct 9, 2025
3593819
Remove unused import
zrgt Oct 13, 2025
284f3a9
Define MAX_RECURSION_DEPTH
zrgt Oct 13, 2025
ff499b2
Refactor resolve_referable_class_in_key_types
zrgt Oct 13, 2025
7e07e11
Merge remote-tracking branch 'rwth-iat/develop' into aas_manager/v301
zrgt Oct 13, 2025
9903426
Resolve conflicts with `develop` branch
zrgt Oct 13, 2025
a6d0b5d
Adjust tests for references
zrgt Oct 13, 2025
4a102bf
Fix codestyle and docs
zrgt Oct 13, 2025
fe11fe4
Fix docs
zrgt Oct 13, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion sdk/basyx/aas/adapter/json/json_serialization.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ def _abstract_classes_to_json(cls, obj: object) -> Dict[str, object]:
if obj.description:
data['description'] = obj.description
try:
ref_type = next(iter(t for t in inspect.getmro(type(obj)) if t in model.KEY_TYPES_CLASSES))
ref_type = model.find_registered_referable_type_in_key_types_classes(obj)
except StopIteration as e:
raise TypeError("Object of type {} is Referable but does not inherit from a known AAS type"
.format(obj.__class__.__name__)) from e
Expand Down
5 changes: 5 additions & 0 deletions sdk/basyx/aas/model/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,8 @@
RelationshipElement: KeyTypes.RELATIONSHIP_ELEMENT,
SubmodelElement: KeyTypes.SUBMODEL_ELEMENT, # type: ignore
}


def find_registered_referable_type_in_key_types_classes(referable: Referable) -> type:
ref_type = next(iter(t for t in inspect.getmro(type(referable)) if t in KEY_TYPES_CLASSES))
return ref_type
84 changes: 61 additions & 23 deletions sdk/basyx/aas/model/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -454,25 +454,35 @@ def from_referable(referable: "Referable") -> "Key":
"""
# Get the `type` by finding the first class from the base classes list (via inspect.getmro), that is contained
# in KEY_ELEMENTS_CLASSES
from . import KEY_TYPES_CLASSES, SubmodelElementList
from . import SubmodelElementList
key_type = Key._get_key_type_for_referable(referable)
key_value = Key._get_key_value_for_referable(referable)
return Key(key_type, key_value)

@staticmethod
def _get_key_type_for_referable(referable: "Referable") -> KeyTypes:
from . import KEY_TYPES_CLASSES, find_registered_referable_type_in_key_types_classes
try:
key_type = next(iter(KEY_TYPES_CLASSES[t]
for t in inspect.getmro(type(referable))
if t in KEY_TYPES_CLASSES))
ref_type = find_registered_referable_type_in_key_types_classes(referable)
key_type = KEY_TYPES_CLASSES[ref_type]
except StopIteration:
key_type = KeyTypes.PROPERTY
return key_type

@staticmethod
def _get_key_value_for_referable(referable: "Referable") -> str:
from . import SubmodelElementList
if isinstance(referable, Identifiable):
return Key(key_type, referable.id)
return referable.id
elif isinstance(referable.parent, SubmodelElementList):
try:
return Key(key_type, str(referable.parent.value.index(referable))) # type: ignore
return str(referable.parent.value.index(referable)) # type: ignore
except ValueError as e:
raise ValueError(f"Object {referable!r} is not contained within its parent {referable.parent!r}") from e
else:
if referable.id_short is None:
raise ValueError(f"Can't create Key for {referable!r} without an id_short!")
return Key(key_type, referable.id_short)
raise ValueError(f"Can't create Key value for {referable!r} without an id_short!")
return referable.id_short


_NSO = TypeVar('_NSO', bound=Union["Referable", "Qualifier", "HasSemantics", "Extension"])
Expand Down Expand Up @@ -620,26 +630,55 @@ def __init__(self):
self.source: str = ""

def __repr__(self) -> str:
reversed_path = []
root = self.get_identifiable_root()
id_short_path = self.get_id_short_path()
item_cls_name = self.__class__.__name__

if root is None:
item_path = f"[{id_short_path}]" if id_short_path else ""
else:
item_path = f"[{root.id} / {id_short_path}]" if id_short_path else f"[{root.id}]"

return f"{item_cls_name}{item_path}"

def get_identifiable_root(self) -> Optional["Identifiable"]:
"""
Get the root :class:`~.Identifiable` of this referable, if it exists.

:return: The root :class:`~.Identifiable` or None if no such root exists
"""
item = self # type: Any
while item is not None:
if isinstance(item, Identifiable):
return item
elif isinstance(item, Referable):
item = item.parent
else:
raise AttributeError('Referable must have an identifiable as root object and only parents that are '
'referable')
return None

def get_id_short_path(self) -> str:
"""
Get the id_short path of this referable, i.e. the id_short of this referable and all its parents.

:return: The id_short path as a string, e.g. "MySubmodelElementCollection.MySubProperty1"
"""
path: List[str] = []
item = self # type: Any
if item.id_short is not None:
from .submodel import SubmodelElementList
while item is not None:
if isinstance(item, Identifiable):
reversed_path.append(item.id)
break
elif isinstance(item, Referable):
while item is not None and not isinstance(item, Identifiable):
if isinstance(item, Referable):
if isinstance(item.parent, SubmodelElementList):
reversed_path.append(f"{item.parent.id_short}[{item.parent.value.index(item)}]")
item = item.parent
path.insert(0, f"[{item.parent.value.index(item)}]")
else:
reversed_path.append(item.id_short)
path.insert(0, item.id_short)
item = item.parent
else:
raise AttributeError('Referable must have an identifiable as root object and only parents that are '
'referable')

return self.__class__.__name__ + ("[{}]".format(" / ".join(reversed(reversed_path))) if reversed_path else "")
return ".".join(path).replace(".[", "[") if path else ""

def _get_id_short(self) -> Optional[NameType]:
return self._id_short
Expand Down Expand Up @@ -1113,18 +1152,17 @@ def from_referable(referable: Referable) -> "ModelReference":
object's ancestors
"""
# Get the first class from the base classes list (via inspect.getmro), that is contained in KEY_ELEMENTS_CLASSES
from . import KEY_TYPES_CLASSES
from . import find_registered_referable_type_in_key_types_classes
try:
ref_type = next(iter(t for t in inspect.getmro(type(referable)) if t in KEY_TYPES_CLASSES))
ref_type = find_registered_referable_type_in_key_types_classes(referable)
except StopIteration:
ref_type = Referable

ref: Referable = referable
keys: List[Key] = []
while True:
keys.append(Key.from_referable(ref))
keys.insert(0, Key.from_referable(ref))
if isinstance(ref, Identifiable):
keys.reverse()
return ModelReference(tuple(keys), ref_type)
if ref.parent is None or not isinstance(ref.parent, Referable):
raise ValueError("The given Referable object is not embedded within an Identifiable object")
Expand Down
4 changes: 2 additions & 2 deletions sdk/test/examples/test_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ def test_submodel_element_collection_checker(self):
self.assertEqual("FAIL: Attribute value of SubmodelElementCollection[Collection] must contain 2 "
"SubmodelElements (count=1)",
repr(next(checker_iterator)))
self.assertEqual("FAIL: Submodel Element Property[Collection / Prop1] must exist ()",
self.assertEqual("FAIL: Submodel Element Property[Collection.Prop1] must exist ()",
repr(next(checker_iterator)))

collection.add_referable(property)
Expand Down Expand Up @@ -291,7 +291,7 @@ def test_annotated_relationship_element(self):
self.assertEqual("FAIL: Attribute annotation of AnnotatedRelationshipElement[test] must contain 1 DataElements "
"(count=0)",
repr(next(checker_iterator)))
self.assertEqual("FAIL: Annotation Property[test / ExampleAnnotatedProperty] must exist ()",
self.assertEqual("FAIL: Annotation Property[test.ExampleAnnotatedProperty] must exist ()",
repr(next(checker_iterator)))

def test_submodel_checker(self):
Expand Down
102 changes: 96 additions & 6 deletions sdk/test/model/test_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ def test_from_referable(self):
self.assertEqual(model.Key(model.KeyTypes.MULTI_LANGUAGE_PROPERTY, "0"), model.Key.from_referable(mlp2))
with self.assertRaises(ValueError) as cm:
model.Key.from_referable(mlp1)
self.assertEqual("Can't create Key for MultiLanguageProperty without an id_short!", str(cm.exception))
self.assertEqual("Can't create Key value for MultiLanguageProperty without an id_short!", str(cm.exception))
mlp1.id_short = "mlp1"
self.assertEqual(model.Key(model.KeyTypes.MULTI_LANGUAGE_PROPERTY, "mlp1"), model.Key.from_referable(mlp1))

Expand All @@ -52,7 +52,7 @@ def __init__(self):
super().__init__()


class ExampleRefereableWithNamespace(model.Referable, model.UniqueIdShortNamespace):
class ExampleReferableWithNamespace(model.Referable, model.UniqueIdShortNamespace):
def __init__(self):
super().__init__()

Expand Down Expand Up @@ -98,7 +98,7 @@ def generate_example_referable_with_namespace(id_short: model.NameType,
:param child: Child to be added to the namespace sets of the Referable
:return: The generated Referable
"""
referable = ExampleRefereableWithNamespace()
referable = ExampleReferableWithNamespace()
referable.id_short = id_short
if child:
namespace_set = model.NamespaceSet(parent=referable, attribute_names=[("id_short", True)],
Expand Down Expand Up @@ -157,6 +157,96 @@ def __init__(self, value: model.Referable):
self.assertEqual('Referable must have an identifiable as root object and only parents that are referable',
str(cm.exception))

def test_get_identifiable_root(self):
ref_with_no_parent = ExampleReferableWithNamespace()
ref_with_no_parent.id_short = "NotNone"

identifiable = ExampleIdentifiable()

ref_child = ExampleReferable()
ref_child.id_short = "Child"
ref_child.parent = identifiable

list1 = model.SubmodelElementList("List1", model.SubmodelElementList)
list2 = model.SubmodelElementList(None, model.Property, value_type_list_element=model.datatypes.Int)
prop1 = model.Property(None, model.datatypes.Int)

list1.parent = ref_child
list1.add_referable(list2)
list2.add_referable(prop1)

self.assertIs(ref_with_no_parent.get_identifiable_root(), None)
self.assertIs(identifiable.get_identifiable_root(), identifiable)
self.assertIs(ref_child.get_identifiable_root(), identifiable)
self.assertIs(list1.get_identifiable_root(), identifiable)
self.assertIs(list2.get_identifiable_root(), identifiable)
self.assertIs(prop1.get_identifiable_root(), identifiable)

def test_get_id_short_path(self):
"""
Tests the get_id_short_path() method of Referable objects.

Example structure:
- SMC: MySubmodelElementCollection
- Property: MySubProperty1
- Property: MySubProperty2
- SMC: MySubSubmodelElementCollection
- Property: MySubSubProperty1
- Property: MySubSubProperty2
- SML: MySubSubmodelElementList1
- Property: "MySubTestValue1"
- Property: "MySubTestValue2"
- SML: MySubSubmodelElementList2
- SML: MySubSubmodelElementList3
- SMC: MySubmodelElementCollectionInSML3
- Property: "MySubTestValue3"
"""
MySubmodelElementCollection = model.SubmodelElementCollection("MySubmodelElementCollection")
MySubProperty1 = model.Property("MySubProperty1", model.datatypes.String)
MySubProperty2 = model.Property("MySubProperty2", model.datatypes.String)
MySubSubmodelElementCollection = model.SubmodelElementCollection("MySubSubmodelElementCollection")
MySubSubProperty1 = model.Property("MySubSubProperty1", model.datatypes.String)
MySubSubProperty2 = model.Property("MySubSubProperty2", model.datatypes.String)
MySubSubmodelElementList1 = model.SubmodelElementList("MySubSubmodelElementList1", model.Property,
value_type_list_element=model.datatypes.String)
MySubTestValue1 = model.Property(None, model.datatypes.String)
MySubTestValue2 = model.Property(None, model.datatypes.String)
MySubSubmodelElementList2 = model.SubmodelElementList("MySubSubmodelElementList2", model.SubmodelElementList)
MySubSubmodelElementList3 = model.SubmodelElementList(None, model.SubmodelElementCollection)
MySubmodelElementCollectionInSML3 = model.SubmodelElementCollection(None)
MySubTestValue3 = model.Property("MySubTestValue3", model.datatypes.String)

MySubmodelElementCollection.add_referable(MySubProperty1)
MySubmodelElementCollection.add_referable(MySubProperty2)
MySubmodelElementCollection.add_referable(MySubSubmodelElementCollection)
MySubSubmodelElementCollection.add_referable(MySubSubProperty1)
MySubSubmodelElementCollection.add_referable(MySubSubProperty2)
MySubmodelElementCollection.add_referable(MySubSubmodelElementList1)
MySubSubmodelElementList1.add_referable(MySubTestValue1)
MySubSubmodelElementList1.add_referable(MySubTestValue2)
MySubmodelElementCollection.add_referable(MySubSubmodelElementList2)
MySubSubmodelElementList2.add_referable(MySubSubmodelElementList3)
MySubSubmodelElementList3.add_referable(MySubmodelElementCollectionInSML3)
MySubmodelElementCollectionInSML3.add_referable(MySubTestValue3)

expected_id_short_paths = {
MySubmodelElementCollection: "MySubmodelElementCollection",
MySubProperty1: "MySubmodelElementCollection.MySubProperty1",
MySubProperty2: "MySubmodelElementCollection.MySubProperty2",
MySubSubmodelElementCollection: "MySubmodelElementCollection.MySubSubmodelElementCollection",
MySubSubProperty1: "MySubmodelElementCollection.MySubSubmodelElementCollection.MySubSubProperty1",
MySubSubProperty2: "MySubmodelElementCollection.MySubSubmodelElementCollection.MySubSubProperty2",
MySubSubmodelElementList1: "MySubmodelElementCollection.MySubSubmodelElementList1",
MySubTestValue1: "MySubmodelElementCollection.MySubSubmodelElementList1[0]",
MySubTestValue2: "MySubmodelElementCollection.MySubSubmodelElementList1[1]",
MySubSubmodelElementList2: "MySubmodelElementCollection.MySubSubmodelElementList2",
MySubSubmodelElementList3: "MySubmodelElementCollection.MySubSubmodelElementList2[0]",
MySubmodelElementCollectionInSML3: "MySubmodelElementCollection.MySubSubmodelElementList2[0][0]",
MySubTestValue3: "MySubmodelElementCollection.MySubSubmodelElementList2[0][0].MySubTestValue3",
}
for referable, expected_path in expected_id_short_paths.items():
self.assertEqual(referable.get_id_short_path(), expected_path)

def test_update(self):
backends.register_backend("mockScheme", MockBackend)
example_referable = generate_example_referable_tree()
Expand Down Expand Up @@ -609,7 +699,7 @@ def test_id_short_path_resolution(self) -> None:
with self.assertRaises(TypeError) as cm_3:
self.namespace.get_referable(["List1", "0", "Prop1", "Test"])
self.assertEqual("Cannot resolve id_short or index 'Test' at "
f"Property[{self.namespace.id} / List1[0] / Prop1], "
f"Property[{self.namespace.id} / List1[0].Prop1], "
"because it is not a UniqueIdShortNamespace!", str(cm_3.exception))

self.namespace.get_referable(["List1", "0", "Prop1"])
Expand Down Expand Up @@ -696,7 +786,7 @@ def test_aasd_117(self) -> None:
se_collection.add_referable(property)
with self.assertRaises(model.AASConstraintViolation) as cm:
property.id_short = None
self.assertEqual("id_short of Property[foo / property] cannot be unset, since it is already contained in "
self.assertEqual("id_short of Property[foo.property] cannot be unset, since it is already contained in "
"SubmodelElementCollection[foo] (Constraint AASd-117)", str(cm.exception))
property.id_short = "bar"

Expand Down Expand Up @@ -947,7 +1037,7 @@ def get_identifiable(self, identifier: Identifier) -> Identifiable:
model.Property)
with self.assertRaises(TypeError) as cm_3:
ref4.resolve(DummyObjectProvider())
self.assertEqual("Cannot resolve id_short or index 'prop' at Property[urn:x-test:submodel / list[0] / prop], "
self.assertEqual("Cannot resolve id_short or index 'prop' at Property[urn:x-test:submodel / list[0].prop], "
"because it is not a UniqueIdShortNamespace!", str(cm_3.exception))

with self.assertRaises(AttributeError) as cm_4:
Expand Down