Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

implement constraint AASd-120 #133

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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 basyx/aas/adapter/json/json_serialization.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ def _abstract_classes_to_json(cls, obj: object) -> Dict[str, object]:
]

if isinstance(obj, model.Referable):
if obj.id_short:
if obj.id_short and not isinstance(obj.parent, model.SubmodelElementList):
data['idShort'] = obj.id_short
if obj.display_name:
data['displayName'] = obj.display_name
Expand Down
2 changes: 1 addition & 1 deletion basyx/aas/adapter/xml/xml_serialization.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ def abstract_classes_to_xml(tag: str, obj: object) -> etree.Element:
if isinstance(obj, model.Referable):
if obj.category:
elm.append(_generate_element(name=NS_AAS + "category", text=obj.category))
if obj.id_short:
if obj.id_short and not isinstance(obj.parent, model.SubmodelElementList):
elm.append(_generate_element(name=NS_AAS + "idShort", text=obj.id_short))
if obj.display_name:
elm.append(lang_string_set_to_xml(obj.display_name, tag=NS_AAS + "displayName"))
Expand Down
20 changes: 12 additions & 8 deletions basyx/aas/examples/data/_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,10 @@ def _check_referable_equal(self, object_: model.Referable, expected_object: mode
:param expected_object: The expected referable object
:return: The value of expression to be used in control statements
"""
self.check_attribute_equal(object_, "id_short", expected_object.id_short)
# For SubmodelElementLists, the id_shorts of children are randomly generated.
# Thus, this check would always fail if enabled.
if not isinstance(object_.parent, model.SubmodelElementList):
self.check_attribute_equal(object_, "id_short", expected_object.id_short)
self.check_attribute_equal(object_, "category", expected_object.category)
self.check_attribute_equal(object_, "description", expected_object.description)
self.check_attribute_equal(object_, "display_name", expected_object.display_name)
Expand Down Expand Up @@ -383,13 +386,14 @@ def check_submodel_element_list_equal(self, object_: model.SubmodelElementList,
self.check_attribute_equal(object_, 'type_value_list_element', expected_value.type_value_list_element)
self.check_contained_element_length(object_, 'value', object_.type_value_list_element,
len(expected_value.value))
if object_.order_relevant:
# compare ordered
for se1, se2 in zip(object_.value, expected_value.value):
self._check_submodel_element(se1, se2)
else:
# compare unordered
self._check_submodel_elements_equal_unordered(object_, expected_value)
if not object_.order_relevant or not expected_value.order_relevant:
# It is impossible to compare SubmodelElementLists with order_relevant=False, since it is impossible
# to know which element should be compared against which other element.
raise NotImplementedError("A SubmodelElementList with order_relevant=False cannot be compared!")

# compare ordered
for se1, se2 in zip(object_.value, expected_value.value):
self._check_submodel_element(se1, se2)

def check_relationship_element_equal(self, object_: model.RelationshipElement,
expected_value: model.RelationshipElement):
Expand Down
4 changes: 2 additions & 2 deletions basyx/aas/examples/data/example_aas.py
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,7 @@ def create_example_submodel() -> model.Submodel:
"""

submodel_element_property = model.Property(
id_short='ExampleProperty',
id_short=None,
value_type=model.datatypes.String,
value='exampleValue',
value_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE,
Expand All @@ -361,7 +361,7 @@ def create_example_submodel() -> model.Submodel:
embedded_data_specifications=(_embedded_data_specification_iec61360,))

submodel_element_property_2 = model.Property(
id_short='ExampleProperty2',
id_short=None,
value_type=model.datatypes.String,
value='exampleValue',
value_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE,
Expand Down
4 changes: 2 additions & 2 deletions basyx/aas/examples/data/example_aas_mandatory_attributes.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ def create_example_submodel() -> model.Submodel:
state=model.StateOfEvent.OFF)

submodel_element_submodel_element_collection = model.SubmodelElementCollection(
id_short='ExampleSubmodelCollection',
id_short=None,
value=(submodel_element_blob,
submodel_element_file,
submodel_element_multi_language_property,
Expand All @@ -123,7 +123,7 @@ def create_example_submodel() -> model.Submodel:
submodel_element_reference_element))

submodel_element_submodel_element_collection_2 = model.SubmodelElementCollection(
id_short='ExampleSubmodelCollection2',
id_short=None,
value=())

submodel_element_submodel_element_list = model.SubmodelElementList(
Expand Down
8 changes: 4 additions & 4 deletions basyx/aas/examples/data/example_submodel_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ def create_example_submodel_template() -> model.Submodel:
qualifier=())

submodel_element_submodel_element_collection = model.SubmodelElementCollection(
id_short='ExampleSubmodelCollection',
id_short=None,
value=(
submodel_element_property,
submodel_element_multi_language_property,
Expand All @@ -232,7 +232,7 @@ def create_example_submodel_template() -> model.Submodel:
qualifier=())

submodel_element_submodel_element_collection_2 = model.SubmodelElementCollection(
id_short='ExampleSubmodelCollection2',
id_short=None,
value=(),
category='PARAMETER',
description=model.MultiLanguageTextType({'en-US': 'Example SubmodelElementCollection object',
Expand All @@ -250,7 +250,7 @@ def create_example_submodel_template() -> model.Submodel:
semantic_id_list_element=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE,
value='http://acplt.org/SubmodelElementCollections/'
'ExampleSubmodelElementCollection'),)),
order_relevant=False,
order_relevant=True,
category='PARAMETER',
description=model.MultiLanguageTextType({'en-US': 'Example SubmodelElementList object',
'de': 'Beispiel SubmodelElementList Element'}),
Expand All @@ -267,7 +267,7 @@ def create_example_submodel_template() -> model.Submodel:
semantic_id_list_element=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE,
value='http://acplt.org/SubmodelElementCollections/'
'ExampleSubmodelElementCollection'),)),
order_relevant=False,
order_relevant=True,
category='PARAMETER',
description=model.MultiLanguageTextType({'en-US': 'Example SubmodelElementList object',
'de': 'Beispiel SubmodelElementList Element'}),
Expand Down
4 changes: 4 additions & 0 deletions basyx/aas/model/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -684,6 +684,10 @@ def _set_id_short(self, id_short: Optional[NameType]):
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}")
from .submodel import SubmodelElementList
if isinstance(self.parent, SubmodelElementList):
raise AASConstraintViolation(120, f"id_short of {self!r} cannot be set, because it is "
f"contained in a {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 "
Expand Down
30 changes: 28 additions & 2 deletions basyx/aas/model/submodel.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"""

import abc
import datetime
import uuid
from typing import Optional, Set, Iterable, TYPE_CHECKING, List, Type, TypeVar, Generic, Union

from . import base, datatypes, _string_constraints
Expand Down Expand Up @@ -703,6 +703,9 @@ def __init__(self,
embedded_data_specifications: Iterable[base.EmbeddedDataSpecification] = ()):
super().__init__(id_short, display_name, category, description, parent, semantic_id, qualifier, extension,
supplemental_semantic_id, embedded_data_specifications)
# Counter to generate a unique idShort whenever a SubmodelElement is added
self._uuid_seq: int = 0

# It doesn't really make sense to change any of these properties. thus they are immutable here.
self._type_value_list_element: Type[_SE] = type_value_list_element
self._order_relevant: bool = order_relevant
Expand All @@ -716,7 +719,9 @@ 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)], (),
self._check_constraints)
item_add_hook=self._check_constraints,
item_id_set_hook=self._generate_id_short,
item_id_del_hook=self._unset_id_short)
# 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
Expand All @@ -729,7 +734,25 @@ def __init__(self,
self._value.clear()
raise

def _generate_id_short(self, new: _SE) -> None:
if new.id_short is not None:
raise base.AASConstraintViolation(120, "Objects with an id_short may not be added to a "
f"SubmodelElementList, got {new!r} with id_short={new.id_short}")
# Generate a unique id_short when a SubmodelElement is added, because children of a SubmodelElementList may not
# have an id_short. The alternative would be making SubmodelElementList a special kind of base.Namespace without
# a unique attribute for child-elements (which contradicts the definition of a Namespace).
new.id_short = "generated_submodel_list_hack_" + uuid.uuid1(clock_seq=self._uuid_seq).hex
self._uuid_seq += 1

def _unset_id_short(self, old: _SE) -> None:
old.id_short = None

def _check_constraints(self, new: _SE, existing: Iterable[_SE]) -> None:
# Since the id_short contains randomness, unset it temporarily for pretty and predictable error messages.
# This also prevents the random id_short from remaining set in case a constraint violation is encountered.
saved_id_short = new.id_short
new.id_short = None

# We can't use isinstance(new, self.type_value_list_element) here, because each subclass of
# self.type_value_list_element wouldn't raise a ConstraintViolation, when it should.
# Example: AnnotatedRelationshipElement is a subclass of RelationshipElement
Expand Down Expand Up @@ -768,6 +791,9 @@ def _check_constraints(self, new: _SE, existing: Iterable[_SE]) -> None:
f"{item!r} has semantic_id {item.semantic_id!r}, which "
"aren't equal.")

# Re-assign id_short
new.id_short = saved_id_short

@property
def value(self) -> base.OrderedNamespaceSet[_SE]:
return self._value
Expand Down
10 changes: 2 additions & 8 deletions test/compliance_tool/files/test_demo_full_example.json
Original file line number Diff line number Diff line change
Expand Up @@ -1424,7 +1424,6 @@
},
"value": [
{
"idShort": "ExampleProperty",
"displayName": [
{
"language": "en-US",
Expand Down Expand Up @@ -1595,7 +1594,6 @@
]
},
{
"idShort": "ExampleProperty2",
"displayName": [
{
"language": "en-US",
Expand Down Expand Up @@ -1754,7 +1752,6 @@
"modelType": "SubmodelElementList",
"value": [
{
"idShort": "ExampleSubmodelCollection",
"modelType": "SubmodelElementCollection",
"value": [
{
Expand Down Expand Up @@ -1796,7 +1793,6 @@
]
},
{
"idShort": "ExampleSubmodelCollection2",
"modelType": "SubmodelElementCollection"
}
]
Expand Down Expand Up @@ -2757,7 +2753,7 @@
}
]
},
"orderRelevant": false,
"orderRelevant": true,
"category": "PARAMETER",
"description": [
{
Expand All @@ -2782,7 +2778,6 @@
"kind": "Template",
"value": [
{
"idShort": "ExampleSubmodelCollection",
"category": "PARAMETER",
"description": [
{
Expand Down Expand Up @@ -2995,7 +2990,6 @@
]
},
{
"idShort": "ExampleSubmodelCollection2",
"category": "PARAMETER",
"description": [
{
Expand Down Expand Up @@ -3033,7 +3027,7 @@
}
]
},
"orderRelevant": false,
"orderRelevant": true,
"category": "PARAMETER",
"description": [
{
Expand Down
10 changes: 2 additions & 8 deletions test/compliance_tool/files/test_demo_full_example.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1180,7 +1180,6 @@
<aas:value>
<aas:property>
<aas:category>CONSTANT</aas:category>
<aas:idShort>ExampleProperty</aas:idShort>
<aas:displayName>
<aas:langStringNameType>
<aas:language>en-US</aas:language>
Expand Down Expand Up @@ -1341,7 +1340,6 @@
</aas:property>
<aas:property>
<aas:category>CONSTANT</aas:category>
<aas:idShort>ExampleProperty2</aas:idShort>
<aas:displayName>
<aas:langStringNameType>
<aas:language>en-US</aas:language>
Expand Down Expand Up @@ -1624,7 +1622,6 @@
<aas:idShort>ExampleSubmodelList</aas:idShort>
<aas:value>
<aas:submodelElementCollection>
<aas:idShort>ExampleSubmodelCollection</aas:idShort>
<aas:kind>Instance</aas:kind>
<aas:value>
<aas:blob>
Expand Down Expand Up @@ -1663,7 +1660,6 @@
</aas:value>
</aas:submodelElementCollection>
<aas:submodelElementCollection>
<aas:idShort>ExampleSubmodelCollection2</aas:idShort>
<aas:kind>Instance</aas:kind>
</aas:submodelElementCollection>
</aas:value>
Expand Down Expand Up @@ -2632,11 +2628,10 @@
</aas:key>
</aas:keys>
</aas:semanticId>
<aas:orderRelevant>false</aas:orderRelevant>
<aas:orderRelevant>true</aas:orderRelevant>
<aas:value>
<aas:submodelElementCollection>
<aas:category>PARAMETER</aas:category>
<aas:idShort>ExampleSubmodelCollection</aas:idShort>
<aas:description>
<aas:langStringTextType>
<aas:language>en-US</aas:language>
Expand Down Expand Up @@ -2838,7 +2833,6 @@
</aas:submodelElementCollection>
<aas:submodelElementCollection>
<aas:category>PARAMETER</aas:category>
<aas:idShort>ExampleSubmodelCollection2</aas:idShort>
<aas:description>
<aas:langStringTextType>
<aas:language>en-US</aas:language>
Expand Down Expand Up @@ -2895,7 +2889,7 @@
</aas:key>
</aas:keys>
</aas:semanticId>
<aas:orderRelevant>false</aas:orderRelevant>
<aas:orderRelevant>true</aas:orderRelevant>
<aas:semanticIdListElement>
<aas:type>ExternalReference</aas:type>
<aas:keys>
Expand Down
Binary file modified test/compliance_tool/files/test_demo_full_example_json.aasx
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -1424,7 +1424,6 @@
},
"value": [
{
"idShort": "ExampleProperty",
"displayName": [
{
"language": "en-US",
Expand Down Expand Up @@ -1595,7 +1594,6 @@
]
},
{
"idShort": "ExampleProperty2",
"displayName": [
{
"language": "en-US",
Expand Down Expand Up @@ -1754,7 +1752,6 @@
"modelType": "SubmodelElementList",
"value": [
{
"idShort": "ExampleSubmodelCollection",
"modelType": "SubmodelElementCollection",
"value": [
{
Expand Down Expand Up @@ -1796,7 +1793,6 @@
]
},
{
"idShort": "ExampleSubmodelCollection2",
"modelType": "SubmodelElementCollection"
}
]
Expand Down Expand Up @@ -2757,7 +2753,7 @@
}
]
},
"orderRelevant": false,
"orderRelevant": true,
"category": "PARAMETER",
"description": [
{
Expand All @@ -2782,7 +2778,6 @@
"kind": "Template",
"value": [
{
"idShort": "ExampleSubmodelCollection",
"category": "PARAMETER",
"description": [
{
Expand Down Expand Up @@ -2995,7 +2990,6 @@
]
},
{
"idShort": "ExampleSubmodelCollection2",
"category": "PARAMETER",
"description": [
{
Expand Down Expand Up @@ -3033,7 +3027,7 @@
}
]
},
"orderRelevant": false,
"orderRelevant": true,
"category": "PARAMETER",
"description": [
{
Expand Down
Loading
Loading