Skip to content

Commit

Permalink
model.base: implement AASd-022 correctly
Browse files Browse the repository at this point in the history
Type is changed from `KeyError` to `AASConstraintViolation` and the
tests are adjusted accordingly.
Furthermore, the NamespaceSet.add() identifier uniqueness check is moved
after the simple parent check for performance reasons, solely because
the parent check is a lot faster.
  • Loading branch information
jkhsjdhjs committed Oct 2, 2023
1 parent ebe628d commit caec4aa
Show file tree
Hide file tree
Showing 2 changed files with 56 additions and 53 deletions.
24 changes: 13 additions & 11 deletions basyx/aas/model/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -686,8 +686,8 @@ def _set_id_short(self, id_short: Optional[NameType]):
f"contained in {self.parent!r}")
for set_ in self.parent.namespace_element_sets:
if set_.contains_id("id_short", id_short):
raise KeyError("Object with id_short '{}' is already present in the parent Namespace"
.format(id_short))
raise AASConstraintViolation(22, "Object with id_short '{}' is already present in the parent "
"Namespace".format(id_short))

set_add_list: List[NamespaceSet] = []
for set_ in self.parent.namespace_element_sets:
Expand Down Expand Up @@ -1816,7 +1816,7 @@ 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 KeyError: When `items` contains multiple objects with same unique attribute
:raises AASConstraintViolation: When `items` contains multiple objects with same unique attribute
"""
self.parent = parent
parent.namespace_element_sets.append(self)
Expand Down Expand Up @@ -1865,17 +1865,19 @@ def __iter__(self) -> Iterator[_NSO]:
return iter(next(iter(self._backend.values()))[0].values())

def add(self, value: _NSO):
for set_ in self.parent.namespace_element_sets:
for attr_name, (backend, case_sensitive) in set_._backend.items():
if hasattr(value, attr_name):
if self._get_attribute(value, attr_name, case_sensitive) in backend:
raise KeyError("Object with attribute (name='{}', value='{}') is already present in {}"
.format(attr_name, str(getattr(value, attr_name)),
"this set of objects"
if set_ is self else "another set in the same namespace"))
if value.parent is not None and value.parent is not self.parent:
raise ValueError("Object has already a parent, but it must not be part of two namespaces.")
# TODO remove from current parent instead (allow moving)?
for set_ in self.parent.namespace_element_sets:
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 in backend:
raise AASConstraintViolation(22, "Object with attribute (name='{}', value='{}') is already "
"present in {}"
.format(attr_name, str(getattr(value, attr_name)),
"this set of objects"
if set_ is self else "another set in the same namespace"))
if self._item_add_hook is not None:
self._item_add_hook(value, self.__iter__())
value.parent = self.parent
Expand Down
85 changes: 43 additions & 42 deletions test/model/test_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -370,26 +370,27 @@ def setUp(self):
def test_NamespaceSet(self) -> None:
self.namespace.set1.add(self.prop1)
self.assertEqual(1, len(self.namespace.set1))
with self.assertRaises(KeyError) as cm:
with self.assertRaises(model.AASConstraintViolation) as cm:
self.namespace.set1.add(self.prop2)
self.assertEqual(
'"Object with attribute (name=\'semantic_id\', value=\'ExternalReference(key=(Key('
'type=GLOBAL_REFERENCE, value=http://acplt.org/Test1),))\') is already present in this set of objects"',
"Object with attribute (name='semantic_id', value='ExternalReference(key=(Key("
"type=GLOBAL_REFERENCE, value=http://acplt.org/Test1),))') is already present in this set of objects "
"(Constraint AASd-022)",
str(cm.exception))
self.namespace.set2.add(self.prop5)
self.namespace.set2.add(self.prop6)
self.assertEqual(2, len(self.namespace.set2))
with self.assertRaises(KeyError) as cm:
with self.assertRaises(model.AASConstraintViolation) as cm:
self.namespace.set2.add(self.prop1)
self.assertEqual('"Object with attribute (name=\'id_short\', value=\'Prop1\') is already present in another '
'set in the same namespace"',
self.assertEqual("Object with attribute (name='id_short', value='Prop1') is already present in another "
"set in the same namespace (Constraint AASd-022)",
str(cm.exception))
with self.assertRaises(KeyError) as cm:
with self.assertRaises(model.AASConstraintViolation) as cm:
self.namespace.set2.add(self.prop4)
self.assertEqual(
'"Object with attribute (name=\'semantic_id\', value=\''
'ExternalReference(key=(Key(type=GLOBAL_REFERENCE, value=http://acplt.org/Test1),))\')'
' is already present in another set in the same namespace"',
"Object with attribute (name='semantic_id', value='"
"ExternalReference(key=(Key(type=GLOBAL_REFERENCE, value=http://acplt.org/Test1),))')"
" is already present in another set in the same namespace (Constraint AASd-022)",
str(cm.exception))

self.assertIs(self.prop1, self.namespace.set1.get("id_short", "Prop1"))
Expand All @@ -399,22 +400,22 @@ def test_NamespaceSet(self) -> None:

self.assertIs(self.prop5, self.namespace.set2.get("id_short", "Prop3"))

with self.assertRaises(KeyError) as cm:
with self.assertRaises(model.AASConstraintViolation) as cm:
self.namespace.set1.add(self.prop1alt)
self.assertEqual('"Object with attribute (name=\'id_short\', value=\'Prop1\') is already present in this set of'
' objects"',
self.assertEqual("Object with attribute (name='id_short', value='Prop1') is already present in this set of"
" objects (Constraint AASd-022)",
str(cm.exception))

self.namespace.set1.add(self.prop3)
with self.assertRaises(KeyError) as cm:
with self.assertRaises(model.AASConstraintViolation) as cm:
self.namespace.set1.add(self.prop7)
self.assertEqual('"Object with attribute (name=\'id_short\', value=\'Prop2\') is already present in this set '
'of objects"',
self.assertEqual("Object with attribute (name='id_short', value='Prop2') is already present in this set "
"of objects (Constraint AASd-022)",
str(cm.exception))
with self.assertRaises(KeyError) as cm:
with self.assertRaises(model.AASConstraintViolation) as cm:
self.namespace.set1.add(self.prop8)
self.assertEqual('"Object with attribute (name=\'id_short\', value=\'ProP2\') is already present in this set '
'of objects"',
self.assertEqual("Object with attribute (name='id_short', value='ProP2') is already present in this set "
"of objects (Constraint AASd-022)",
str(cm.exception))

namespace2 = self._namespace_class()
Expand Down Expand Up @@ -453,10 +454,10 @@ def test_NamespaceSet(self) -> None:
self.assertEqual(1, len(self.namespace3.set1))
self.namespace3.set1.add(self.qualifier2)
self.assertEqual(2, len(self.namespace3.set1))
with self.assertRaises(KeyError) as cm:
with self.assertRaises(model.AASConstraintViolation) as cm:
self.namespace3.set1.add(self.qualifier1alt)
self.assertEqual('"Object with attribute (name=\'type\', value=\'type1\') is already present in this set '
'of objects"',
self.assertEqual("Object with attribute (name='type', value='type1') is already present in this set "
"of objects (Constraint AASd-022)",
str(cm.exception))

def test_namespaceset_item_add_hook(self) -> None:
Expand Down Expand Up @@ -501,28 +502,28 @@ def dummy_hook(new, existing):
self.assertIn(prop, existing_items)

def test_Namespace(self) -> None:
with self.assertRaises(KeyError) as cm:
with self.assertRaises(model.AASConstraintViolation) as cm:
namespace_test = ExampleNamespaceReferable([self.prop1, self.prop2, self.prop1alt])
self.assertEqual('"Object with attribute (name=\'id_short\', value=\'Prop1\') is already present in this set '
'of objects"',
self.assertEqual("Object with attribute (name='id_short', value='Prop1') is already present in this set "
"of objects (Constraint AASd-022)",
str(cm.exception))
self.assertIsNone(self.prop1.parent)

namespace = self._namespace_class([self.prop1, self.prop2])
self.assertIs(self.prop2, namespace.get_referable("Prop2"))
with self.assertRaises(KeyError) as cm:
with self.assertRaises(KeyError) as cm2:
namespace.get_referable("Prop3")
self.assertEqual("'Referable with id_short Prop3 not found in this namespace'",
str(cm.exception))
str(cm2.exception))

namespace.remove_referable("Prop2")
with self.assertRaises(KeyError) as cm2:
with self.assertRaises(KeyError) as cm3:
namespace.get_referable("Prop2")
self.assertEqual("'Referable with id_short Prop2 not found in this namespace'", str(cm2.exception))
self.assertEqual("'Referable with id_short Prop2 not found in this namespace'", str(cm3.exception))

with self.assertRaises(KeyError) as cm3:
with self.assertRaises(KeyError) as cm4:
namespace.remove_referable("Prop2")
self.assertEqual("'Referable with id_short Prop2 not found in this namespace'", str(cm3.exception))
self.assertEqual("'Referable with id_short Prop2 not found in this namespace'", str(cm4.exception))

def test_renaming(self) -> None:
self.namespace.set2.add(self.prop1)
Expand All @@ -539,9 +540,9 @@ def test_renaming(self) -> None:
self.assertEqual("'Referable with id_short Prop1 not found in this namespace'",
str(cm.exception))
self.assertIs(self.prop2, self.namespace.get_referable("Prop2"))
with self.assertRaises(KeyError) as cm:
with self.assertRaises(model.AASConstraintViolation) as cm2:
self.prop1.id_short = "Prop2"
self.assertIn("already present", str(cm.exception))
self.assertIn("already present", str(cm2.exception))

self.namespace.set3.add(self.extension1)
self.namespace.set3.add(self.extension2)
Expand Down Expand Up @@ -615,20 +616,20 @@ def test_OrderedNamespace(self) -> None:
self.assertEqual(1, len(self.namespace.set2))
self.namespace.set2.insert(0, self.prop2)
self.assertEqual(2, len(self.namespace.set2))
with self.assertRaises(KeyError) as cm:
with self.assertRaises(model.AASConstraintViolation) as cm:
self.namespace.set1.insert(0, self.prop1alt)
self.assertEqual('"Object with attribute (name=\'id_short\', value=\'Prop1\') is already present in another '
'set in the same namespace"',
self.assertEqual('Object with attribute (name=\'id_short\', value=\'Prop1\') is already present in another '
'set in the same namespace (Constraint AASd-022)',
str(cm.exception))
self.assertEqual((self.prop2, self.prop1), tuple(self.namespace.set2))
self.assertEqual(self.prop1, self.namespace.set2[1])

with self.assertRaises(KeyError) as cm:
with self.assertRaises(model.AASConstraintViolation) as cm:
self.namespace.set2[1] = self.prop2
self.assertEqual('"Object with attribute (name=\'id_short\', value=\'Prop2\') is already present in this '
'set of objects"',
self.assertEqual('Object with attribute (name=\'id_short\', value=\'Prop2\') is already present in this '
'set of objects (Constraint AASd-022)',
str(cm.exception))
prop3 = model.Property("Prop3", model.datatypes.Int)
prop3 = model.Property("Prop3", model.datatypes.Int, semantic_id=self.propSemanticID3)
self.assertEqual(2, len(self.namespace.set2))
self.namespace.set2[1] = prop3
self.assertEqual(2, len(self.namespace.set2))
Expand All @@ -647,10 +648,10 @@ def test_OrderedNamespace(self) -> None:
self.assertIs(self.prop1, namespace2.set2.get("id_short", "Prop1"))
namespace2.set2.remove(("id_short", "Prop1"))
self.assertEqual(1, len(namespace2.set2))
with self.assertRaises(KeyError) as cm:
with self.assertRaises(KeyError) as cm2:
namespace2.get_referable("Prop1")
self.assertEqual("'Referable with id_short Prop1 not found in this namespace'",
str(cm.exception))
str(cm2.exception))


class ExternalReferenceTest(unittest.TestCase):
Expand Down

0 comments on commit caec4aa

Please sign in to comment.