From 1aa0e828d4711e3cfa339cc10fc1973af3bb5bc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leon=20M=C3=B6ller?= Date: Fri, 29 Sep 2023 18:11:20 +0200 Subject: [PATCH 1/2] model.base: fix `Referable.__repr__()` for `SubmodelElementList`-children Since AASd-120 prohibits specifying id_shorts of direct children of `SubmodelElementLists`, this commit adjusts `Referable.__repr__()` such that the index of the element in the corresponding list is returned instead. Furthermore, the tests are adjusted accordingly. --- basyx/aas/model/base.py | 9 +++++++-- test/examples/test_helpers.py | 8 ++++---- test/model/test_submodel.py | 2 +- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/basyx/aas/model/base.py b/basyx/aas/model/base.py index bacd8b005..e948a255b 100644 --- a/basyx/aas/model/base.py +++ b/basyx/aas/model/base.py @@ -610,12 +610,17 @@ def __init__(self): def __repr__(self) -> str: reversed_path = [] item = self # type: Any + from .submodel import SubmodelElementList while item is not None: if isinstance(item, Identifiable): - reversed_path.append(str(item.id)) + reversed_path.append(item.id) break elif isinstance(item, Referable): - reversed_path.append(item.id_short) + if isinstance(item.parent, SubmodelElementList): + reversed_path.append(f"{item.parent.id_short}[{item.parent.value.index(item)}]") + item = item.parent + else: + reversed_path.append(item.id_short) item = item.parent else: raise AttributeError('Referable must have an identifiable as root object and only parents that are ' diff --git a/test/examples/test_helpers.py b/test/examples/test_helpers.py index 5eea373f8..d898d1db0 100644 --- a/test/examples/test_helpers.py +++ b/test/examples/test_helpers.py @@ -94,13 +94,13 @@ def test_submodel_element_list_checker(self): checker.check_submodel_element_list_equal(list_, list_expected) self.assertEqual(4, sum(1 for _ in checker.failed_checks)) checker_iterator = checker.failed_checks - self.assertEqual("FAIL: Attribute id_short of Range[test_list / range1] must be == range2 (value='range1')", + self.assertEqual("FAIL: Attribute id_short of Range[test_list[0]] must be == range2 (value='range1')", repr(next(checker_iterator))) - self.assertEqual("FAIL: Attribute max of Range[test_list / range1] must be == 1337 (value=142857)", + self.assertEqual("FAIL: Attribute max of Range[test_list[0]] must be == 1337 (value=142857)", repr(next(checker_iterator))) - self.assertEqual("FAIL: Attribute id_short of Range[test_list / range2] must be == range1 (value='range2')", + self.assertEqual("FAIL: Attribute id_short of Range[test_list[1]] must be == range1 (value='range2')", repr(next(checker_iterator))) - self.assertEqual("FAIL: Attribute max of Range[test_list / range2] must be == 142857 (value=1337)", + self.assertEqual("FAIL: Attribute max of Range[test_list[1]] must be == 142857 (value=1337)", repr(next(checker_iterator))) # order_relevant diff --git a/test/model/test_submodel.py b/test/model/test_submodel.py index ea523ebc1..87e60d7bb 100644 --- a/test/model/test_submodel.py +++ b/test/model/test_submodel.py @@ -127,7 +127,7 @@ def test_constraints(self): model.SubmodelElementList("test_list", model.MultiLanguageProperty, [mlp1, mlp2]) self.assertEqual("Element to be added MultiLanguageProperty[mlp2] has semantic_id " "ExternalReference(key=(Key(type=GLOBAL_REFERENCE, value=urn:x-test:different),)), " - "while already contained element MultiLanguageProperty[test_list / mlp1] has semantic_id " + "while already contained element MultiLanguageProperty[test_list[0]] has semantic_id " "ExternalReference(key=(Key(type=GLOBAL_REFERENCE, value=urn:x-test:test),)), " "which aren't equal. (Constraint AASd-114)", str(cm.exception)) mlp2.semantic_id = semantic_id1 From dab29a44f840e2484303e96ee58ff123ee752f9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leon=20M=C3=B6ller?= Date: Fri, 29 Sep 2023 18:14:33 +0200 Subject: [PATCH 2/2] model.submodel: add elements to `SubmodelElementList` after the `_value` attribute has been assigned This fixes an `AttributeError` previously raised during initialization of a `SubmodelElementList`, since the `check_constraints` function, which is called for every added item, may call `Referable.__repr__()` when the `value` attribute isn't set yet. The `AttributeError` occured because `Referable.__repr__()` accesses the `value` attribute for direct children of a `SubmodelElementList` (1aa0e828d4711e3cfa339cc10fc1973af3bb5bc5). This is worked around by first assigning the `OrderedNamespaceSet` to the `value`-attribute and then adding each item separately. --- basyx/aas/model/submodel.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/basyx/aas/model/submodel.py b/basyx/aas/model/submodel.py index 5b05e9ff3..104eff995 100644 --- a/basyx/aas/model/submodel.py +++ b/basyx/aas/model/submodel.py @@ -715,8 +715,19 @@ def __init__(self, # Items must be added after the above contraint has been checked. Otherwise, it can lead to errors, since the # constraints in _check_constraints() assume that this constraint has been checked. - self._value: base.OrderedNamespaceSet[_SE] = base.OrderedNamespaceSet(self, [("id_short", True)], value, + self._value: base.OrderedNamespaceSet[_SE] = base.OrderedNamespaceSet(self, [("id_short", True)], (), self._check_constraints) + # SubmodelElements need to be added after the assignment of the ordered NamespaceSet, otherwise, if a constraint + # check fails, Referable.__repr__ may be called for an already-contained item during the AASd-114 check, which + # in turn tries to access the SubmodelElementLists value / _value attribute, which wouldn't be set yet if all + # elements are passed to the OrderedNamespaceSet initializer. + try: + for i in value: + self._value.add(i) + except Exception: + # Remove all SubmodelElements if an exception occurs during initialization of the SubmodelElementList + self._value.clear() + raise def _check_constraints(self, new: _SE, existing: Iterable[_SE]) -> None: # We can't use isinstance(new, self.type_value_list_element) here, because each subclass of