From ee4d6acb5c8acf1583955f9807ccb528426fcd42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leon=20M=C3=B6ller?= Date: Mon, 2 Oct 2023 19:29:08 +0200 Subject: [PATCH] model.base: implement constraint `AASd-117` --- basyx/aas/model/base.py | 12 ++++++++++-- test/model/test_base.py | 35 +++++++++++++++++++++++++---------- 2 files changed, 35 insertions(+), 12 deletions(-) diff --git a/basyx/aas/model/base.py b/basyx/aas/model/base.py index 1c840df6b..db2d35327 100644 --- a/basyx/aas/model/base.py +++ b/basyx/aas/model/base.py @@ -681,6 +681,9 @@ def _set_id_short(self, id_short: Optional[NameType]): ) if self.parent is not None: + if id_short is None: + raise AASConstraintViolation(117, f"id_short of {self!r} cannot be unset, since it is already " + f"contained in {self.parent!r}") for set_ in self.parent.namespace_element_sets: if set_.contains_id("id_short", id_short): raise AASConstraintViolation(22, "Object with id_short '{}' is already present in the parent " @@ -1813,7 +1816,8 @@ def __init__(self, parent: Union[UniqueIdShortNamespace, UniqueSemanticIdNamespa :param item_add_hook: A function that is called for each item that is added to this NamespaceSet, even when it is initialized. The first parameter is the item that is added while the second is an iterator over all currently contained items. Useful for constraint checking. - :raises AASConstraintViolation: When `items` contains multiple objects with same unique attribute + :raises AASConstraintViolation: When `items` contains multiple objects with same unique attribute or when an + item doesn't has an identifying attribute """ self.parent = parent parent.namespace_element_sets.append(self) @@ -1869,6 +1873,9 @@ def add(self, value: _NSO): for attr_name, (backend, case_sensitive) in set_._backend.items(): if hasattr(value, attr_name): attr_value = self._get_attribute(value, attr_name, case_sensitive) + if attr_value is None: + raise AASConstraintViolation(117, f"{value!r} has attribute {attr_name}=None, which is not " + f"allowed within a {self.parent.__class__.__name__}!") if attr_value in backend: raise AASConstraintViolation(22, "Object with attribute (name='{}', value='{}') is already " "present in {}" @@ -2006,7 +2013,8 @@ def __init__(self, parent: Union[UniqueIdShortNamespace, UniqueSemanticIdNamespa :param item_add_hook: A function that is called for each item that is added to this NamespaceSet, even when it is initialized. The first parameter is the item that is added while the second is an iterator over all currently contained items. Useful for constraint checking. - :raises AASConstraintViolation: When `items` contains multiple objects with same id_short + :raises AASConstraintViolation: When `items` contains multiple objects with same unique attribute or when an + item doesn't has an identifying attribute """ self._order: List[_NSO] = [] super().__init__(parent, attribute_names, items, item_add_hook) diff --git a/test/model/test_base.py b/test/model/test_base.py index d41e712ef..65b8e02f6 100644 --- a/test/model/test_base.py +++ b/test/model/test_base.py @@ -358,12 +358,12 @@ def setUp(self): self.prop6 = model.Property("Prop4", model.datatypes.Int, semantic_id=self.propSemanticID2) self.prop7 = model.Property("Prop2", model.datatypes.Int, semantic_id=self.propSemanticID3) self.prop8 = model.Property("ProP2", model.datatypes.Int, semantic_id=self.propSemanticID3) - self.prop1alt = model.Property("Prop1", model.datatypes.Int) - self.qualifier1 = model.Qualifier("type1", model.datatypes.Int, 1) - self.qualifier2 = model.Qualifier("type2", model.datatypes.Int, 1) - self.qualifier1alt = model.Qualifier("type1", model.datatypes.Int, 1) - self.extension1 = model.Extension("Ext1", model.datatypes.Int, 1) - self.extension2 = model.Extension("Ext2", model.datatypes.Int, 1) + self.prop1alt = model.Property("Prop1", model.datatypes.Int, semantic_id=self.propSemanticID) + self.qualifier1 = model.Qualifier("type1", model.datatypes.Int, 1, semantic_id=self.propSemanticID) + self.qualifier2 = model.Qualifier("type2", model.datatypes.Int, 1, semantic_id=self.propSemanticID2) + self.qualifier1alt = model.Qualifier("type1", model.datatypes.Int, 1, semantic_id=self.propSemanticID) + self.extension1 = model.Extension("Ext1", model.datatypes.Int, 1, semantic_id=self.propSemanticID) + self.extension2 = model.Extension("Ext2", model.datatypes.Int, 1, semantic_id=self.propSemanticID2) self.namespace = self._namespace_class() self.namespace3 = self._namespace_class_qualifier() @@ -565,13 +565,13 @@ def test_Namespaceset_update_from(self) -> None: # Prop2 is getting deleted since it does not exist in namespace2.set1 # Prop3 is getting added, since it does not exist in namespace1.set1 yet namespace1 = self._namespace_class() - prop1 = model.Property("Prop1", model.datatypes.Int, 1) - prop2 = model.Property("Prop2", model.datatypes.Int, 0) + prop1 = model.Property("Prop1", model.datatypes.Int, 1, semantic_id=self.propSemanticID) + prop2 = model.Property("Prop2", model.datatypes.Int, 0, semantic_id=self.propSemanticID2) namespace1.set2.add(prop1) namespace1.set2.add(prop2) namespace2 = self._namespace_class() - namespace2.set2.add(model.Property("Prop1", model.datatypes.Int, 0)) - namespace2.set2.add(model.Property("Prop3", model.datatypes.Int, 2)) + namespace2.set2.add(model.Property("Prop1", model.datatypes.Int, 0, semantic_id=self.propSemanticID)) + namespace2.set2.add(model.Property("Prop3", model.datatypes.Int, 2, semantic_id=self.propSemanticID2)) namespace1.set2.update_nss_from(namespace2.set2) # Check that Prop1 got updated correctly self.assertIs(namespace1.get_referable("Prop1"), prop1) @@ -596,6 +596,21 @@ def test_qualifiable_id_short_namespace(self) -> None: self.assertIs(submodel_element_collection.get_referable("Prop1"), prop1) self.assertIs(submodel_element_collection.get_qualifier_by_type("Qualifier1"), qualifier1) + def test_aasd_117(self) -> None: + property = model.Property(None, model.datatypes.Int, semantic_id=self.propSemanticID) + se_collection = model.SubmodelElementCollection("foo") + with self.assertRaises(model.AASConstraintViolation) as cm: + se_collection.add_referable(property) + self.assertEqual("Property has attribute id_short=None, which is not allowed within a " + "SubmodelElementCollection! (Constraint AASd-117)", str(cm.exception)) + property.id_short = "property" + 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 " + "SubmodelElementCollection[foo] (Constraint AASd-117)", str(cm.exception)) + property.id_short = "bar" + class ExampleOrderedNamespace(model.UniqueIdShortNamespace, model.UniqueSemanticIdNamespace): def __init__(self, values=()):