Skip to content

Commit

Permalink
Merge remote-tracking branch 'acplt/feature/SubmodelElementList_gener…
Browse files Browse the repository at this point in the history
…ated_id_shorts_aasd_120' into fix/id_short
  • Loading branch information
zrgt committed Oct 16, 2023
2 parents 1545e47 + a0a2416 commit 250517d
Show file tree
Hide file tree
Showing 18 changed files with 108 additions and 94 deletions.
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

0 comments on commit 250517d

Please sign in to comment.