diff --git a/basyx/aas/adapter/json/aasJSONSchema.json b/basyx/aas/adapter/json/aasJSONSchema.json
index feb41a2af..9aacb9098 100644
--- a/basyx/aas/adapter/json/aasJSONSchema.json
+++ b/basyx/aas/adapter/json/aasJSONSchema.json
@@ -444,8 +444,12 @@
"maxLength": 2000,
"pattern": "^([\\t\\n\\r -\ud7ff\ue000-\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$"
},
- "specificAssetId": {
- "$ref": "#/definitions/SpecificAssetId"
+ "specificAssetIds": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/SpecificAssetId"
+ },
+ "minItems": 1
}
},
"required": [
diff --git a/basyx/aas/adapter/json/json_deserialization.py b/basyx/aas/adapter/json/json_deserialization.py
index 636531564..d2618735e 100644
--- a/basyx/aas/adapter/json/json_deserialization.py
+++ b/basyx/aas/adapter/json/json_deserialization.py
@@ -417,13 +417,19 @@ def _construct_value_reference_pair(cls, dct: Dict[str, object],
@classmethod
def _construct_asset_information(cls, dct: Dict[str, object], object_class=model.AssetInformation)\
-> model.AssetInformation:
- ret = object_class(asset_kind=ASSET_KIND_INVERSE[_get_ts(dct, 'assetKind', str)])
- cls._amend_abstract_attributes(ret, dct)
+ global_asset_id = None
if 'globalAssetId' in dct:
- ret.global_asset_id = _get_ts(dct, 'globalAssetId', str)
+ global_asset_id = _get_ts(dct, 'globalAssetId', str)
+ specific_asset_id = set()
if 'specificAssetIds' in dct:
for desc_data in _get_ts(dct, "specificAssetIds", list):
- ret.specific_asset_id.add(cls._construct_specific_asset_id(desc_data, model.SpecificAssetId))
+ specific_asset_id.add(cls._construct_specific_asset_id(desc_data, model.SpecificAssetId))
+
+ ret = object_class(asset_kind=ASSET_KIND_INVERSE[_get_ts(dct, 'assetKind', str)],
+ global_asset_id=global_asset_id,
+ specific_asset_id=specific_asset_id)
+ cls._amend_abstract_attributes(ret, dct)
+
if 'assetType' in dct:
ret.asset_type = _get_ts(dct, 'assetType', str)
if 'defaultThumbnail' in dct:
@@ -497,9 +503,10 @@ def _construct_entity(cls, dct: Dict[str, object], object_class=model.Entity) ->
global_asset_id = None
if 'globalAssetId' in dct:
global_asset_id = _get_ts(dct, 'globalAssetId', str)
- specific_asset_id = None
+ specific_asset_id = set()
if 'specificAssetIds' in dct:
- specific_asset_id = cls._construct_specific_asset_id(_get_ts(dct, 'specificAssetIds', dict))
+ for desc_data in _get_ts(dct, "specificAssetIds", list):
+ specific_asset_id.add(cls._construct_specific_asset_id(desc_data, model.SpecificAssetId))
ret = object_class(id_short=None,
entity_type=ENTITY_TYPES_INVERSE[_get_ts(dct, "entityType", str)],
diff --git a/basyx/aas/adapter/json/json_serialization.py b/basyx/aas/adapter/json/json_serialization.py
index c2f6e11cd..0ab6cddcb 100644
--- a/basyx/aas/adapter/json/json_serialization.py
+++ b/basyx/aas/adapter/json/json_serialization.py
@@ -631,7 +631,7 @@ def _entity_to_json(cls, obj: model.Entity) -> Dict[str, object]:
if obj.global_asset_id:
data['globalAssetId'] = obj.global_asset_id
if obj.specific_asset_id:
- data['specificAssetIds'] = obj.specific_asset_id
+ data['specificAssetIds'] = list(obj.specific_asset_id)
return data
@classmethod
diff --git a/basyx/aas/adapter/xml/AAS.xsd b/basyx/aas/adapter/xml/AAS.xsd
index 1c9a6e209..76c1fd544 100644
--- a/basyx/aas/adapter/xml/AAS.xsd
+++ b/basyx/aas/adapter/xml/AAS.xsd
@@ -284,7 +284,13 @@
-
+
+
+
+
+
+
+
diff --git a/basyx/aas/adapter/xml/xml_deserialization.py b/basyx/aas/adapter/xml/xml_deserialization.py
index fdf346af5..98f4a9fa3 100644
--- a/basyx/aas/adapter/xml/xml_deserialization.py
+++ b/basyx/aas/adapter/xml/xml_deserialization.py
@@ -797,13 +797,17 @@ def construct_capability(cls, element: etree.Element, object_class=model.Capabil
@classmethod
def construct_entity(cls, element: etree.Element, object_class=model.Entity, **_kwargs: Any) -> model.Entity:
- global_asset_id = _get_text_or_none(element.find(NS_AAS + "globalAssetId"))
- specific_asset_id = _failsafe_construct(element.find(NS_AAS + "specificAssetId"),
- cls.construct_specific_asset_id, cls.failsafe)
+ specific_asset_id = set()
+ specific_assset_ids = element.find(NS_AAS + "specificAssetIds")
+ if specific_assset_ids is not None:
+ for id in _child_construct_multiple(specific_assset_ids, NS_AAS + "specificAssetId",
+ cls.construct_specific_asset_id, cls.failsafe):
+ specific_asset_id.add(id)
+
entity = object_class(
id_short=None,
entity_type=_child_text_mandatory_mapped(element, NS_AAS + "entityType", ENTITY_TYPES_INVERSE),
- global_asset_id=global_asset_id,
+ global_asset_id=_get_text_or_none(element.find(NS_AAS + "globalAssetId")),
specific_asset_id=specific_asset_id)
if not cls.stripped:
@@ -994,17 +998,19 @@ def construct_specific_asset_id(cls, element: etree.Element, object_class=model.
@classmethod
def construct_asset_information(cls, element: etree.Element, object_class=model.AssetInformation, **_kwargs: Any) \
-> model.AssetInformation:
- asset_information = object_class(
- _child_text_mandatory_mapped(element, NS_AAS + "assetKind", ASSET_KIND_INVERSE),
- )
- global_asset_id = _get_text_or_none(element.find(NS_AAS + "globalAssetId"))
- if global_asset_id is not None:
- asset_information.global_asset_id = global_asset_id
+ specific_asset_id = set()
specific_assset_ids = element.find(NS_AAS + "specificAssetIds")
if specific_assset_ids is not None:
for id in _child_construct_multiple(specific_assset_ids, NS_AAS + "specificAssetId",
cls.construct_specific_asset_id, cls.failsafe):
- asset_information.specific_asset_id.add(id)
+ specific_asset_id.add(id)
+
+ asset_information = object_class(
+ _child_text_mandatory_mapped(element, NS_AAS + "assetKind", ASSET_KIND_INVERSE),
+ global_asset_id=_get_text_or_none(element.find(NS_AAS + "globalAssetId")),
+ specific_asset_id=specific_asset_id,
+ )
+
asset_type = _get_text_or_none(element.find(NS_AAS + "assetType"))
if asset_type is not None:
asset_information.asset_type = asset_type
diff --git a/basyx/aas/adapter/xml/xml_serialization.py b/basyx/aas/adapter/xml/xml_serialization.py
index 55446dac9..0ba5c8629 100644
--- a/basyx/aas/adapter/xml/xml_serialization.py
+++ b/basyx/aas/adapter/xml/xml_serialization.py
@@ -801,7 +801,10 @@ def entity_to_xml(obj: model.Entity,
if obj.global_asset_id:
et_entity.append(_generate_element(NS_AAS + "globalAssetId", text=obj.global_asset_id))
if obj.specific_asset_id:
- et_entity.append(specific_asset_id_to_xml(obj.specific_asset_id, NS_AAS + "specificAssetId"))
+ et_specific_asset_id = _generate_element(name=NS_AAS + "specificAssetIds")
+ for specific_asset_id in obj.specific_asset_id:
+ et_specific_asset_id.append(specific_asset_id_to_xml(specific_asset_id, NS_AAS + "specificAssetId"))
+ et_entity.append(et_specific_asset_id)
return et_entity
diff --git a/basyx/aas/examples/data/_helper.py b/basyx/aas/examples/data/_helper.py
index 9bf53e8aa..01e8a6d66 100644
--- a/basyx/aas/examples/data/_helper.py
+++ b/basyx/aas/examples/data/_helper.py
@@ -454,7 +454,7 @@ def _find_reference(self, object_: model.Reference, search_list: Iterable) -> Un
return element
return None
- def _find_specific_asset_id(self, object_: model.SpecificAssetId, search_list: Union[Set, List]) \
+ def _find_specific_asset_id(self, object_: model.SpecificAssetId, search_list: Iterable) \
-> Union[model.SpecificAssetId, None]:
"""
Find a SpecificAssetId in an list
@@ -604,25 +604,26 @@ def check_entity_equal(self, object_: model.Entity, expected_value: model.Entity
self._check_abstract_attributes_submodel_element_equal(object_, expected_value)
self.check_attribute_equal(object_, 'entity_type', expected_value.entity_type)
self.check_attribute_equal(object_, 'global_asset_id', expected_value.global_asset_id)
- if object_.specific_asset_id and expected_value.specific_asset_id:
- self.check_specific_asset_id(object_.specific_asset_id, expected_value.specific_asset_id)
- else:
- if expected_value.specific_asset_id:
- self.check(expected_value.specific_asset_id is not None,
- 'SpecificAssetId {} must exist'.format(repr(expected_value.specific_asset_id)),
- value=object_.specific_asset_id)
- else:
- if object_.specific_asset_id:
- self.check(expected_value.specific_asset_id is None, 'Enity {} must not have a '
- 'specificAssetId'.format(repr(object_)),
- value=expected_value.specific_asset_id)
+ self._check_specific_asset_ids_equal(object_.specific_asset_id, expected_value.specific_asset_id, object_)
self.check_contained_element_length(object_, 'statement', model.SubmodelElement, len(expected_value.statement))
for expected_element in expected_value.statement:
element = object_.get_referable(expected_element.id_short)
- self.check(element is not None, 'Entity {} must exist'.format(repr(expected_element)))
+ self.check(element is not None, f'Entity {repr(expected_element)} must exist')
found_elements = self._find_extra_elements_by_id_short(object_.statement, expected_value.statement)
- self.check(found_elements == set(), 'Enity {} must not have extra statements'.format(repr(object_)),
+ self.check(found_elements == set(), f'Enity {repr(object_)} must not have extra statements',
+ value=found_elements)
+
+ def _check_specific_asset_ids_equal(self, object_: Iterable[model.SpecificAssetId],
+ expected_value: Iterable[model.SpecificAssetId],
+ object_parent):
+ for expected_pair in expected_value:
+ pair = self._find_specific_asset_id(expected_pair, object_)
+ if self.check(pair is not None, f'SpecificAssetId {repr(expected_pair)} must exist'):
+ self.check_specific_asset_id(pair, expected_pair) # type: ignore
+
+ found_elements = self._find_extra_object(object_, expected_value, model.SpecificAssetId)
+ self.check(found_elements == set(), f'{repr(object_parent)} must not have extra specificAssetIds',
value=found_elements)
def _check_event_element_equal(self, object_: model.EventElement, expected_value: model.EventElement):
@@ -722,16 +723,7 @@ def check_asset_information_equal(self, object_: model.AssetInformation, expecte
self.check_attribute_equal(object_, 'global_asset_id', expected_value.global_asset_id)
self.check_contained_element_length(object_, 'specific_asset_id', model.SpecificAssetId,
len(expected_value.specific_asset_id))
- for expected_pair in expected_value.specific_asset_id:
- pair = self._find_specific_asset_id(expected_pair, object_.specific_asset_id)
- if self.check(pair is not None, 'SpecificAssetId {} must exist'.format(repr(expected_pair))):
- self.check_specific_asset_id(pair, expected_pair) # type: ignore
-
- found_elements = self._find_extra_object(object_.specific_asset_id, expected_value.specific_asset_id,
- model.SpecificAssetId)
- self.check(found_elements == set(), '{} must not have extra '
- 'specificAssetIds'.format(repr(object_)),
- value=found_elements)
+ self._check_specific_asset_ids_equal(object_.specific_asset_id, expected_value.specific_asset_id, object_)
self.check_attribute_equal(object_, 'asset_type', object_.asset_type)
if object_.default_thumbnail and expected_value.default_thumbnail:
self.check_resource_equal(object_.default_thumbnail, expected_value.default_thumbnail)
diff --git a/basyx/aas/examples/data/example_aas.py b/basyx/aas/examples/data/example_aas.py
index 7b0a98541..6d2a39326 100644
--- a/basyx/aas/examples/data/example_aas.py
+++ b/basyx/aas/examples/data/example_aas.py
@@ -244,11 +244,12 @@ def create_example_bill_of_material_submodel() -> model.Submodel:
entity_type=model.EntityType.SELF_MANAGED_ENTITY,
statement={submodel_element_property, submodel_element_property2},
global_asset_id='http://acplt.org/TestAsset/',
- specific_asset_id=model.SpecificAssetId(name="TestKey",
- value="TestValue",
- external_subject_id=model.ExternalReference(
- (model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE,
- value='http://acplt.org/SpecificAssetId/'),))),
+ specific_asset_id={
+ model.SpecificAssetId(name="TestKey", value="TestValue",
+ external_subject_id=model.ExternalReference(
+ (model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE,
+ value='http://acplt.org/SpecificAssetId/'),))
+ )},
category="PARAMETER",
description=model.MultiLanguageTextType({
'en-US': 'Legally valid designation of the natural or judicial person which '
@@ -276,7 +277,7 @@ def create_example_bill_of_material_submodel() -> model.Submodel:
entity_type=model.EntityType.CO_MANAGED_ENTITY,
statement=(),
global_asset_id=None,
- specific_asset_id=None,
+ specific_asset_id=(),
category="PARAMETER",
description=model.MultiLanguageTextType({
'en-US': 'Legally valid designation of the natural or judicial person which '
diff --git a/basyx/aas/examples/data/example_aas_mandatory_attributes.py b/basyx/aas/examples/data/example_aas_mandatory_attributes.py
index ab41b4aef..aa83d3f93 100644
--- a/basyx/aas/examples/data/example_aas_mandatory_attributes.py
+++ b/basyx/aas/examples/data/example_aas_mandatory_attributes.py
@@ -201,7 +201,8 @@ def create_example_empty_asset_administration_shell() -> model.AssetAdministrati
:return: example asset administration shell
"""
asset_administration_shell = model.AssetAdministrationShell(
- asset_information=model.AssetInformation(),
+ asset_information=model.AssetInformation(
+ global_asset_id='http://acplt.org/TestAsset2_Mandatory/'),
id_='https://acplt.org/Test_AssetAdministrationShell2_Mandatory')
return asset_administration_shell
diff --git a/basyx/aas/examples/tutorial_serialization_deserialization.py b/basyx/aas/examples/tutorial_serialization_deserialization.py
index dd11043b3..8f7b36949 100755
--- a/basyx/aas/examples/tutorial_serialization_deserialization.py
+++ b/basyx/aas/examples/tutorial_serialization_deserialization.py
@@ -47,7 +47,7 @@
)
aashell = model.AssetAdministrationShell(
id_='https://acplt.org/Simple_AAS',
- asset_information=model.AssetInformation(),
+ asset_information=model.AssetInformation(global_asset_id="test"),
submodel={model.ModelReference.from_referable(submodel)}
)
diff --git a/basyx/aas/model/aas.py b/basyx/aas/model/aas.py
index c99efc594..9edf891ea 100644
--- a/basyx/aas/model/aas.py
+++ b/basyx/aas/model/aas.py
@@ -29,6 +29,8 @@ class AssetInformation:
identifiers. However, to support the corner case of very first phase of lifecycle where a stabilised/constant
global asset identifier does not already exist, the corresponding attribute “globalAssetId” is optional.
+ *Constraint AASd-131*: The globalAssetId or at least one specificAssetId shall be defined for AssetInformation.
+
:ivar asset_kind: Denotes whether the Asset is of :class:`~aas.model.base.AssetKind` "TYPE" or "INSTANCE".
Default is "INSTANCE".
:ivar global_asset_id: :class:`~aas.model.base.Identifier` modelling the identifier of the asset the AAS is
@@ -52,30 +54,65 @@ class AssetInformation:
def __init__(self,
asset_kind: base.AssetKind = base.AssetKind.INSTANCE,
global_asset_id: Optional[base.Identifier] = None,
- specific_asset_id: Optional[Set[base.SpecificAssetId]] = None,
+ specific_asset_id: Iterable[base.SpecificAssetId] = (),
asset_type: Optional[base.Identifier] = None,
default_thumbnail: Optional[base.Resource] = None):
super().__init__()
self.asset_kind: base.AssetKind = asset_kind
- self._global_asset_id: Optional[base.Identifier] = global_asset_id
- self.specific_asset_id: Set[base.SpecificAssetId] = set() if specific_asset_id is None \
- else specific_asset_id
self.asset_type: Optional[base.Identifier] = asset_type
self.default_thumbnail: Optional[base.Resource] = default_thumbnail
+ # assign private attributes, bypassing setters, as constraints will be checked below
+ self._specific_asset_id: base.ConstrainedList[base.SpecificAssetId] = base.ConstrainedList(
+ specific_asset_id,
+ item_set_hook=self._check_constraint_set_spec_asset_id,
+ item_del_hook=self._check_constraint_del_spec_asset_id
+ )
+ self._global_asset_id: Optional[base.Identifier] = global_asset_id
+ self._validate_global_asset_id(global_asset_id)
+ self._validate_aasd_131(global_asset_id, bool(specific_asset_id))
- def _get_global_asset_id(self):
+ @property
+ def global_asset_id(self) -> Optional[base.Identifier]:
return self._global_asset_id
- def _set_global_asset_id(self, global_asset_id: Optional[base.Identifier]):
- if global_asset_id is None:
- if self.specific_asset_id is None or not self.specific_asset_id:
- raise ValueError("either global or specific asset id must be set")
- else:
- _string_constraints.check_identifier(global_asset_id)
+ @global_asset_id.setter
+ def global_asset_id(self, global_asset_id: Optional[base.Identifier]) -> None:
+ self._validate_global_asset_id(global_asset_id)
+ self._validate_aasd_131(global_asset_id, bool(self.specific_asset_id))
self._global_asset_id = global_asset_id
- global_asset_id = property(_get_global_asset_id, _set_global_asset_id)
+ @property
+ def specific_asset_id(self) -> base.ConstrainedList[base.SpecificAssetId]:
+ return self._specific_asset_id
+
+ @specific_asset_id.setter
+ def specific_asset_id(self, specific_asset_id: Iterable[base.SpecificAssetId]) -> None:
+ # constraints are checked via _check_constraint_set_spec_asset_id() in this case
+ self._specific_asset_id[:] = specific_asset_id
+
+ def _check_constraint_set_spec_asset_id(self, items_to_replace: List[base.SpecificAssetId],
+ new_items: List[base.SpecificAssetId],
+ old_list: List[base.SpecificAssetId]) -> None:
+ self._validate_aasd_131(self.global_asset_id,
+ len(old_list) - len(items_to_replace) + len(new_items) > 0)
+
+ def _check_constraint_del_spec_asset_id(self, _item_to_del: base.SpecificAssetId,
+ old_list: List[base.SpecificAssetId]) -> None:
+ self._validate_aasd_131(self.global_asset_id, len(old_list) > 1)
+
+ @staticmethod
+ def _validate_global_asset_id(global_asset_id: Optional[base.Identifier]) -> None:
+ if global_asset_id is not None:
+ _string_constraints.check_identifier(global_asset_id)
+
+ @staticmethod
+ def _validate_aasd_131(global_asset_id: Optional[base.Identifier], specific_asset_id_nonempty: bool) -> None:
+ if global_asset_id is None and not specific_asset_id_nonempty:
+ raise base.AASConstraintViolation(131,
+ "An AssetInformation has to have a globalAssetId or a specificAssetId")
+ if global_asset_id is not None:
+ _string_constraints.check_identifier(global_asset_id)
def __repr__(self) -> str:
return "AssetInformation(assetKind={}, globalAssetId={}, specificAssetId={}, assetType={}, " \
diff --git a/basyx/aas/model/base.py b/basyx/aas/model/base.py
index a39f48fe9..2994a91b9 100644
--- a/basyx/aas/model/base.py
+++ b/basyx/aas/model/base.py
@@ -1258,7 +1258,7 @@ class Identifiable(Referable, metaclass=abc.ABCMeta):
:ivar ~.id: The globally unique id of the element.
"""
@abc.abstractmethod
- def __init__(self):
+ def __init__(self) -> None:
super().__init__()
self.administration: Optional[AdministrativeInformation] = None
# The id attribute is set by all inheriting classes __init__ functions.
@@ -1311,6 +1311,10 @@ def extend(self, values: Iterable[_T]) -> None:
self._item_add_hook(v, self._list + v_list[:idx])
self._list = self._list + v_list
+ def clear(self) -> None:
+ # clear() repeatedly deletes the last item by default, making it not atomic
+ del self[:]
+
@overload
def __getitem__(self, index: int) -> _T: ...
@@ -1516,7 +1520,7 @@ class HasKind(metaclass=abc.ABCMeta):
:ivar _kind: Kind of the element: either type or instance. Default = :attr:`~ModellingKind.INSTANCE`.
"""
@abc.abstractmethod
- def __init__(self):
+ def __init__(self) -> None:
super().__init__()
self._kind: ModellingKind = ModellingKind.INSTANCE
@@ -1537,7 +1541,7 @@ class Qualifiable(Namespace, metaclass=abc.ABCMeta):
qualifiable element.
"""
@abc.abstractmethod
- def __init__(self):
+ def __init__(self) -> None:
super().__init__()
self.namespace_element_sets: List[NamespaceSet] = []
self.qualifier: NamespaceSet[Qualifier]
@@ -1765,6 +1769,14 @@ def remove_object_by_semantic_id(self, semantic_id: Reference) -> None:
ATTRIBUTE_TYPES = Union[NameType, Reference, QualifierType]
+# TODO: Find a better solution for providing constraint ids
+ATTRIBUTES_CONSTRAINT_IDS = {
+ "id_short": 22, # Referable,
+ "type": 21, # Qualifier,
+ "name": 77, # Extension,
+ # "id_short": 134, # model.OperationVariable
+}
+
class NamespaceSet(MutableSet[_NSO], Generic[_NSO]):
"""
@@ -1864,35 +1876,65 @@ def __len__(self) -> int:
def __iter__(self) -> Iterator[_NSO]:
return iter(next(iter(self._backend.values()))[0].values())
- def add(self, value: _NSO):
- 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.")
+ def add(self, element: _NSO):
+ if element.parent is not None and element.parent is not self.parent:
+ raise ValueError("Object has already a parent; it cannot belong to two namespaces.")
# TODO remove from current parent instead (allow moving)?
- if self._item_id_set_hook is not None:
- self._item_id_set_hook(value)
+
+ self._execute_item_id_set_hook(element)
+ self._validate_namespace_constraints(element)
+ self._execute_item_add_hook(element)
+
+ element.parent = self.parent
+ for key_attr_name, (backend, case_sensitive) in self._backend.items():
+ backend[self._get_attribute(element, key_attr_name, case_sensitive)] = element
+
+ def _validate_namespace_constraints(self, element: _NSO):
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 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 {}"
- .format(attr_name, str(getattr(value, attr_name)),
- "this set of objects"
- if set_ is self else "another set in the same namespace"))
+ for key_attr_name, (backend_dict, case_sensitive) in set_._backend.items():
+ if hasattr(element, key_attr_name):
+ key_attr_value = self._get_attribute(element, key_attr_name, case_sensitive)
+ self._check_attr_is_not_none(element, key_attr_name, key_attr_value)
+ self._check_value_is_not_in_backend(element, key_attr_name, key_attr_value, backend_dict, set_)
+
+ def _check_attr_is_not_none(self, element: _NSO, attr_name: str, attr):
+ if attr is None:
+ if attr_name == "id_short":
+ raise AASConstraintViolation(117, f"{element!r} has attribute {attr_name}=None, "
+ f"which is not allowed within a {self.parent.__class__.__name__}!")
+ else:
+ raise ValueError(f"{element!r} has attribute {attr_name}=None, which is not allowed!")
+
+ def _check_value_is_not_in_backend(self, element: _NSO, attr_name: str, attr,
+ backend_dict: Dict[ATTRIBUTE_TYPES, _NSO], set_: "NamespaceSet"):
+ if attr in backend_dict:
+ if set_ is self:
+ text = f"Object with attribute (name='{attr_name}', value='{getattr(element, attr_name)}') " \
+ f"is already present in this set of objects"
+ else:
+ text = f"Object with attribute (name='{attr_name}', value='{getattr(element, attr_name)}') " \
+ f"is already present in another set in the same namespace"
+ raise AASConstraintViolation(ATTRIBUTES_CONSTRAINT_IDS.get(attr_name, 0), text)
+
+ def _execute_item_id_set_hook(self, element: _NSO):
+ if self._item_id_set_hook is not None:
+ self._item_id_set_hook(element)
+
+ def _execute_item_add_hook(self, element: _NSO):
if self._item_add_hook is not None:
try:
- self._item_add_hook(value, self.__iter__())
- except Exception:
- if self._item_id_del_hook is not None:
- self._item_id_del_hook(value)
+ self._item_add_hook(element, self.__iter__())
+ except Exception as e:
+ self._execute_item_del_hook(element)
raise
- value.parent = self.parent
- for attr_name, (backend, case_sensitive) in self._backend.items():
- backend[self._get_attribute(value, attr_name, case_sensitive)] = value
+
+ def _execute_item_del_hook(self, element: _NSO):
+ # parent needs to be unset first, otherwise generated id_shorts cannot be unset
+ # see SubmodelElementList
+ if hasattr(element, "parent"):
+ element.parent = None
+ if self._item_id_del_hook is not None:
+ self._item_id_del_hook(element)
def remove_by_id(self, attribute_name: str, identifier: ATTRIBUTE_TYPES) -> None:
item = self.get_object_by_attribute(attribute_name, identifier)
@@ -1900,22 +1942,16 @@ def remove_by_id(self, attribute_name: str, identifier: ATTRIBUTE_TYPES) -> None
def remove(self, item: _NSO) -> None:
item_found = False
- for attr_name, (backend, case_sensitive) in self._backend.items():
- item_in_dict = backend[self._get_attribute(item, attr_name, case_sensitive)]
- if item_in_dict is item:
+ for key_attr_name, (backend_dict, case_sensitive) in self._backend.items():
+ key_attr_value = self._get_attribute(item, key_attr_name, case_sensitive)
+ if backend_dict[key_attr_value] is item:
+ # item has to be removed from backend before _item_del_hook() is called,
+ # as the hook may unset the id_short, as in SubmodelElementLists
+ del backend_dict[key_attr_value]
item_found = True
- break
if not item_found:
raise KeyError("Object not found in NamespaceDict")
- # parent needs to be unset first, otherwise generated id_shorts cannot be unset
- # see SubmodelElementList
- item.parent = None
- # item has to be removed from backend before _item_del_hook() is called, as the hook may unset the id_short,
- # as in SubmodelElementLists
- for attr_name, (backend, case_sensitive) in self._backend.items():
- del backend[self._get_attribute(item, attr_name, case_sensitive)]
- if self._item_id_del_hook is not None:
- self._item_id_del_hook(item)
+ self._execute_item_del_hook(item)
def discard(self, x: _NSO) -> None:
if x not in self:
@@ -1924,19 +1960,14 @@ def discard(self, x: _NSO) -> None:
def pop(self) -> _NSO:
_, value = next(iter(self._backend.values()))[0].popitem()
- if self._item_id_del_hook is not None:
- self._item_id_del_hook(value)
+ self._execute_item_del_hook(value)
value.parent = None
return value
def clear(self) -> None:
for attr_name, (backend, case_sensitive) in self._backend.items():
for value in backend.values():
- # parent needs to be unset first, otherwise generated id_shorts cannot be unset
- # see SubmodelElementList
- value.parent = None
- if self._item_id_del_hook is not None:
- self._item_id_del_hook(value)
+ self._execute_item_del_hook(value)
for attr_name, (backend, case_sensitive) in self._backend.items():
backend.clear()
@@ -2047,9 +2078,9 @@ def __init__(self, parent: Union[UniqueIdShortNamespace, UniqueSemanticIdNamespa
def __iter__(self) -> Iterator[_NSO]:
return iter(self._order)
- def add(self, value: _NSO):
- super().add(value)
- self._order.append(value)
+ def add(self, element: _NSO):
+ super().add(element)
+ self._order.append(element)
def remove(self, item: Union[Tuple[str, ATTRIBUTE_TYPES], _NSO]):
if isinstance(item, tuple):
diff --git a/basyx/aas/model/submodel.py b/basyx/aas/model/submodel.py
index 7ca7d70f4..af9c074fe 100644
--- a/basyx/aas/model/submodel.py
+++ b/basyx/aas/model/submodel.py
@@ -1048,7 +1048,6 @@ def __init__(self,
supplemental_semantic_id, embedded_data_specifications)
-@_string_constraints.constrain_identifier("global_asset_id")
class Entity(SubmodelElement, base.UniqueIdShortNamespace):
"""
An entity is a :class:`~.SubmodelElement` that is used to model entities
@@ -1091,7 +1090,7 @@ def __init__(self,
entity_type: base.EntityType,
statement: Iterable[SubmodelElement] = (),
global_asset_id: Optional[base.Identifier] = None,
- specific_asset_id: Optional[base.SpecificAssetId] = None,
+ specific_asset_id: Iterable[base.SpecificAssetId] = (),
display_name: Optional[base.MultiLanguageNameType] = None,
category: Optional[base.NameType] = None,
description: Optional[base.MultiLanguageTextType] = None,
@@ -1107,28 +1106,77 @@ def __init__(self,
super().__init__(id_short, display_name, category, description, parent, semantic_id, qualifier, extension,
supplemental_semantic_id, embedded_data_specifications)
self.statement = base.NamespaceSet(self, [("id_short", True)], statement)
- self.specific_asset_id: Optional[base.SpecificAssetId] = specific_asset_id
- self.global_asset_id: Optional[base.Identifier] = global_asset_id
- self._entity_type: base.EntityType
- self.entity_type = entity_type
+ # assign private attributes, bypassing setters, as constraints will be checked below
+ self._entity_type: base.EntityType = entity_type
+ self._global_asset_id: Optional[base.Identifier] = global_asset_id
+ self._specific_asset_id: base.ConstrainedList[base.SpecificAssetId] = base.ConstrainedList(
+ specific_asset_id,
+ item_add_hook=self._check_constraint_add_spec_asset_id,
+ item_set_hook=self._check_constraint_set_spec_asset_id,
+ item_del_hook=self._check_constraint_del_spec_asset_id
+ )
+ self._validate_global_asset_id(global_asset_id)
+ self._validate_aasd_014(entity_type, global_asset_id, bool(specific_asset_id))
- def _get_entity_type(self) -> base.EntityType:
+ @property
+ def entity_type(self) -> base.EntityType:
return self._entity_type
- def _set_entity_type(self, entity_type: base.EntityType) -> None:
- if self.global_asset_id is None and self.specific_asset_id is None \
- and entity_type == base.EntityType.SELF_MANAGED_ENTITY:
- raise base.AASConstraintViolation(
- 14,
- "A self-managed entity has to have a globalAssetId or a specificAssetId"
- )
- if (self.global_asset_id or self.specific_asset_id) and entity_type == base.EntityType.CO_MANAGED_ENTITY:
- raise base.AASConstraintViolation(
- 14,
- "A co-managed entity has to have neither a globalAssetId nor a specificAssetId")
+ @entity_type.setter
+ def entity_type(self, entity_type: base.EntityType) -> None:
+ self._validate_aasd_014(entity_type, self.global_asset_id, bool(self.specific_asset_id))
self._entity_type = entity_type
- entity_type = property(_get_entity_type, _set_entity_type)
+ @property
+ def global_asset_id(self) -> Optional[base.Identifier]:
+ return self._global_asset_id
+
+ @global_asset_id.setter
+ def global_asset_id(self, global_asset_id: Optional[base.Identifier]) -> None:
+ self._validate_global_asset_id(global_asset_id)
+ self._validate_aasd_014(self.entity_type, global_asset_id, bool(self.specific_asset_id))
+ self._global_asset_id = global_asset_id
+
+ @property
+ def specific_asset_id(self) -> base.ConstrainedList[base.SpecificAssetId]:
+ return self._specific_asset_id
+
+ @specific_asset_id.setter
+ def specific_asset_id(self, specific_asset_id: Iterable[base.SpecificAssetId]) -> None:
+ # constraints are checked via _check_constraint_set_spec_asset_id() in this case
+ self._specific_asset_id[:] = specific_asset_id
+
+ def _check_constraint_add_spec_asset_id(self, _new_item: base.SpecificAssetId,
+ _old_list: List[base.SpecificAssetId]) -> None:
+ self._validate_aasd_014(self.entity_type, self.global_asset_id, True)
+
+ def _check_constraint_set_spec_asset_id(self, items_to_replace: List[base.SpecificAssetId],
+ new_items: List[base.SpecificAssetId],
+ old_list: List[base.SpecificAssetId]) -> None:
+ self._validate_aasd_014(self.entity_type, self.global_asset_id,
+ len(old_list) - len(items_to_replace) + len(new_items) > 0)
+
+ def _check_constraint_del_spec_asset_id(self, _item_to_del: base.SpecificAssetId,
+ old_list: List[base.SpecificAssetId]) -> None:
+ self._validate_aasd_014(self.entity_type, self.global_asset_id, len(old_list) > 1)
+
+ @staticmethod
+ def _validate_global_asset_id(global_asset_id: Optional[base.Identifier]) -> None:
+ if global_asset_id is not None:
+ _string_constraints.check_identifier(global_asset_id)
+
+ @staticmethod
+ def _validate_aasd_014(entity_type: base.EntityType,
+ global_asset_id: Optional[base.Identifier],
+ specific_asset_id_nonempty: bool) -> None:
+ if entity_type == base.EntityType.SELF_MANAGED_ENTITY and global_asset_id is None \
+ and not specific_asset_id_nonempty:
+ raise base.AASConstraintViolation(
+ 14, "A self-managed entity has to have a globalAssetId or a specificAssetId")
+ elif entity_type == base.EntityType.CO_MANAGED_ENTITY and (global_asset_id is not None
+ or specific_asset_id_nonempty):
+ raise base.AASConstraintViolation(
+ 14, "A co-managed entity has to have neither a globalAssetId nor a specificAssetId")
class EventElement(SubmodelElement, metaclass=abc.ABCMeta):
diff --git a/test/adapter/json/test_json_deserialization.py b/test/adapter/json/test_json_deserialization.py
index 3ee11cb0f..7f127be93 100644
--- a/test/adapter/json/test_json_deserialization.py
+++ b/test/adapter/json/test_json_deserialization.py
@@ -31,7 +31,8 @@ def test_file_format_wrong_list(self) -> None:
"modelType": "AssetAdministrationShell",
"id": "https://acplt.org/Test_Asset",
"assetInformation": {
- "assetKind": "Instance"
+ "assetKind": "Instance",
+ "globalAssetId": "https://acplt.org/Test_AssetId"
}
}
]
@@ -142,7 +143,8 @@ def test_duplicate_identifier(self) -> None:
"modelType": "AssetAdministrationShell",
"id": "http://acplt.org/test_aas",
"assetInformation": {
- "assetKind": "Instance"
+ "assetKind": "Instance",
+ "globalAssetId": "https://acplt.org/Test_AssetId"
}
}],
"submodels": [{
diff --git a/test/adapter/xml/test_xml_deserialization.py b/test/adapter/xml/test_xml_deserialization.py
index 4a473d2d4..f562e02a6 100644
--- a/test/adapter/xml/test_xml_deserialization.py
+++ b/test/adapter/xml/test_xml_deserialization.py
@@ -81,6 +81,7 @@ def test_missing_asset_kind(self) -> None:
http://acplt.org/test_aas
+ http://acplt.org/TestAsset/
@@ -94,6 +95,7 @@ def test_missing_asset_kind_text(self) -> None:
http://acplt.org/test_aas
+ http://acplt.org/TestAsset/
@@ -107,6 +109,7 @@ def test_invalid_asset_kind_text(self) -> None:
http://acplt.org/test_aas
invalidKind
+ http://acplt.org/TestAsset/
@@ -153,6 +156,7 @@ def test_reference_kind_mismatch(self) -> None:
http://acplt.org/test_aas
Instance
+ http://acplt.org/TestAsset/
ModelReference
@@ -260,6 +264,7 @@ def test_duplicate_identifier(self) -> None:
http://acplt.org/test_aas
Instance
+ http://acplt.org/TestAsset/
@@ -375,6 +380,7 @@ def test_stripped_asset_administration_shell(self) -> None:
http://acplt.org/test_aas
Instance
+ http://acplt.org/TestAsset/
diff --git a/test/compliance_tool/files/test_demo_full_example.json b/test/compliance_tool/files/test_demo_full_example.json
index 6dbe924ea..05371e694 100644
--- a/test/compliance_tool/files/test_demo_full_example.json
+++ b/test/compliance_tool/files/test_demo_full_example.json
@@ -254,7 +254,8 @@
"modelType": "AssetAdministrationShell",
"id": "https://acplt.org/Test_AssetAdministrationShell2_Mandatory",
"assetInformation": {
- "assetKind": "Instance"
+ "assetKind": "Instance",
+ "globalAssetId": "http://acplt.org/TestAsset2_Mandatory/"
}
},
{
@@ -720,19 +721,21 @@
],
"entityType": "SelfManagedEntity",
"globalAssetId": "http://acplt.org/TestAsset/",
- "specificAssetId": {
- "name": "TestKey",
- "value": "TestValue",
- "externalSubjectId": {
- "type": "ExternalReference",
- "keys": [
- {
- "type": "GlobalReference",
- "value": "http://acplt.org/SpecificAssetId/"
- }
- ]
+ "specificAssetIds": [
+ {
+ "name": "TestKey",
+ "value": "TestValue",
+ "externalSubjectId": {
+ "type": "ExternalReference",
+ "keys": [
+ {
+ "type": "GlobalReference",
+ "value": "http://acplt.org/SpecificAssetId/"
+ }
+ ]
+ }
}
- }
+ ]
},
{
"idShort": "ExampleEntity2",
diff --git a/test/compliance_tool/files/test_demo_full_example.xml b/test/compliance_tool/files/test_demo_full_example.xml
index 3c1c4eafa..dccbd55b0 100644
--- a/test/compliance_tool/files/test_demo_full_example.xml
+++ b/test/compliance_tool/files/test_demo_full_example.xml
@@ -245,6 +245,7 @@
https://acplt.org/Test_AssetAdministrationShell2_Mandatory
Instance
+ http://acplt.org/TestAsset2_Mandatory/
@@ -601,19 +602,21 @@
SelfManagedEntity
http://acplt.org/TestAsset/
-
- TestKey
- TestValue
-
- ExternalReference
-
-
- GlobalReference
- http://acplt.org/SpecificAssetId/
-
-
-
-
+
+
+ TestKey
+ TestValue
+
+ ExternalReference
+
+
+ GlobalReference
+ http://acplt.org/SpecificAssetId/
+
+
+
+
+
PARAMETER
diff --git a/test/compliance_tool/files/test_demo_full_example_json_aasx/aasx/data.json b/test/compliance_tool/files/test_demo_full_example_json_aasx/aasx/data.json
index d7669dd99..93d0e3eda 100644
--- a/test/compliance_tool/files/test_demo_full_example_json_aasx/aasx/data.json
+++ b/test/compliance_tool/files/test_demo_full_example_json_aasx/aasx/data.json
@@ -262,7 +262,8 @@
"modelType": "AssetAdministrationShell",
"id": "https://acplt.org/Test_AssetAdministrationShell2_Mandatory",
"assetInformation": {
- "assetKind": "Instance"
+ "assetKind": "Instance",
+ "globalAssetId": "http://acplt.org/TestAsset2_Mandatory/"
}
},
{
@@ -728,19 +729,21 @@
],
"entityType": "SelfManagedEntity",
"globalAssetId": "http://acplt.org/TestAsset/",
- "specificAssetId": {
- "name": "TestKey",
- "value": "TestValue",
- "externalSubjectId": {
- "type": "ExternalReference",
- "keys": [
- {
- "type": "GlobalReference",
- "value": "http://acplt.org/SpecificAssetId/"
- }
- ]
+ "specificAssetIds": [
+ {
+ "name": "TestKey",
+ "value": "TestValue",
+ "externalSubjectId": {
+ "type": "ExternalReference",
+ "keys": [
+ {
+ "type": "GlobalReference",
+ "value": "http://acplt.org/SpecificAssetId/"
+ }
+ ]
+ }
}
- }
+ ]
},
{
"idShort": "ExampleEntity2",
diff --git a/test/compliance_tool/files/test_demo_full_example_wrong_attribute.json b/test/compliance_tool/files/test_demo_full_example_wrong_attribute.json
index d10beb9a8..4d05b2f55 100644
--- a/test/compliance_tool/files/test_demo_full_example_wrong_attribute.json
+++ b/test/compliance_tool/files/test_demo_full_example_wrong_attribute.json
@@ -254,7 +254,8 @@
"modelType": "AssetAdministrationShell",
"id": "https://acplt.org/Test_AssetAdministrationShell2_Mandatory",
"assetInformation": {
- "assetKind": "Instance"
+ "assetKind": "Instance",
+ "globalAssetId": "http://acplt.org/TestAsset2_Mandatory/"
}
},
{
@@ -718,19 +719,21 @@
],
"entityType": "SelfManagedEntity",
"globalAssetId": "http://acplt.org/TestAsset/",
- "specificAssetId": {
- "name": "TestKey",
- "value": "TestValue",
- "externalSubjectId": {
- "type": "ExternalReference",
- "keys": [
- {
- "type": "GlobalReference",
- "value": "http://acplt.org/SpecificAssetId/"
- }
- ]
+ "specificAssetIds": [
+ {
+ "name": "TestKey",
+ "value": "TestValue",
+ "externalSubjectId": {
+ "type": "ExternalReference",
+ "keys": [
+ {
+ "type": "GlobalReference",
+ "value": "http://acplt.org/SpecificAssetId/"
+ }
+ ]
+ }
}
- }
+ ]
},
{
"idShort": "ExampleEntity2",
diff --git a/test/compliance_tool/files/test_demo_full_example_wrong_attribute.xml b/test/compliance_tool/files/test_demo_full_example_wrong_attribute.xml
index 52d39d0a6..4d3d5a561 100644
--- a/test/compliance_tool/files/test_demo_full_example_wrong_attribute.xml
+++ b/test/compliance_tool/files/test_demo_full_example_wrong_attribute.xml
@@ -245,6 +245,7 @@
https://acplt.org/Test_AssetAdministrationShell2_Mandatory
Instance
+ http://acplt.org/TestAsset2_Mandatory/
@@ -601,19 +602,21 @@
SelfManagedEntity
http://acplt.org/TestAsset/
-
- TestKey
- TestValue
-
- ExternalReference
-
-
- GlobalReference
- http://acplt.org/SpecificAssetId/
-
-
-
-
+
+
+ TestKey
+ TestValue
+
+ ExternalReference
+
+
+ GlobalReference
+ http://acplt.org/SpecificAssetId/
+
+
+
+
+
PARAMETER
diff --git a/test/compliance_tool/files/test_demo_full_example_xml_aasx/aasx/data.xml b/test/compliance_tool/files/test_demo_full_example_xml_aasx/aasx/data.xml
index 6922d53d3..6ba3cc9c5 100644
--- a/test/compliance_tool/files/test_demo_full_example_xml_aasx/aasx/data.xml
+++ b/test/compliance_tool/files/test_demo_full_example_xml_aasx/aasx/data.xml
@@ -253,6 +253,7 @@
https://acplt.org/Test_AssetAdministrationShell2_Mandatory
Instance
+ http://acplt.org/TestAsset2_Mandatory/
@@ -609,19 +610,21 @@
SelfManagedEntity
http://acplt.org/TestAsset/
-
- TestKey
- TestValue
-
- ExternalReference
-
-
- GlobalReference
- http://acplt.org/SpecificAssetId/
-
-
-
-
+
+
+ TestKey
+ TestValue
+
+ ExternalReference
+
+
+ GlobalReference
+ http://acplt.org/SpecificAssetId/
+
+
+
+
+
PARAMETER
diff --git a/test/compliance_tool/files/test_demo_full_example_xml_wrong_attribute_aasx/aasx/data.xml b/test/compliance_tool/files/test_demo_full_example_xml_wrong_attribute_aasx/aasx/data.xml
index 6fda1e2c5..1ce64e565 100644
--- a/test/compliance_tool/files/test_demo_full_example_xml_wrong_attribute_aasx/aasx/data.xml
+++ b/test/compliance_tool/files/test_demo_full_example_xml_wrong_attribute_aasx/aasx/data.xml
@@ -253,6 +253,7 @@
https://acplt.org/Test_AssetAdministrationShell2_Mandatory
Instance
+ http://acplt.org/TestAsset2_Mandatory/
@@ -609,19 +610,21 @@
SelfManagedEntity
http://acplt.org/TestAsset/
-
- TestKey
- TestValue
-
- ExternalReference
-
-
- GlobalReference
- http://acplt.org/SpecificAssetId/
-
-
-
-
+
+
+ TestKey
+ TestValue
+
+ ExternalReference
+
+
+ GlobalReference
+ http://acplt.org/SpecificAssetId/
+
+
+
+
+
PARAMETER
diff --git a/test/compliance_tool/files/test_deserializable_aas_warning.json b/test/compliance_tool/files/test_deserializable_aas_warning.json
index 14d358d4e..35da52c24 100644
--- a/test/compliance_tool/files/test_deserializable_aas_warning.json
+++ b/test/compliance_tool/files/test_deserializable_aas_warning.json
@@ -8,7 +8,8 @@
},
"modelType": "AssetAdministrationShell",
"assetInformation": {
- "assetKind": "Instance"
+ "assetKind": "Instance",
+ "globalAssetId": "http://acplt.org/TestAsset/"
}
}
]
diff --git a/test/compliance_tool/files/test_deserializable_aas_warning.xml b/test/compliance_tool/files/test_deserializable_aas_warning.xml
index 8fdda7354..53f72ab71 100644
--- a/test/compliance_tool/files/test_deserializable_aas_warning.xml
+++ b/test/compliance_tool/files/test_deserializable_aas_warning.xml
@@ -9,6 +9,7 @@
https://acplt.org/Test_AssetAdministrationShell
Instance
+ http://acplt.org/TestAsset/
diff --git a/test/model/test_aas.py b/test/model/test_aas.py
new file mode 100644
index 000000000..27ce13b4d
--- /dev/null
+++ b/test/model/test_aas.py
@@ -0,0 +1,86 @@
+# Copyright (c) 2023 the Eclipse BaSyx Authors
+#
+# This program and the accompanying materials are made available under the terms of the MIT License, available in
+# the LICENSE file of this project.
+#
+# SPDX-License-Identifier: MIT
+
+import unittest
+
+from basyx.aas import model
+
+
+class AssetInformationTest(unittest.TestCase):
+ def test_aasd_131_init(self) -> None:
+ with self.assertRaises(model.AASConstraintViolation) as cm:
+ model.AssetInformation(model.AssetKind.INSTANCE)
+ self.assertEqual("An AssetInformation has to have a globalAssetId or a specificAssetId (Constraint AASd-131)",
+ str(cm.exception))
+ model.AssetInformation(model.AssetKind.INSTANCE, global_asset_id="https://acplt.org/TestAsset")
+ model.AssetInformation(model.AssetKind.INSTANCE, specific_asset_id=(model.SpecificAssetId("test", "test"),))
+ model.AssetInformation(model.AssetKind.INSTANCE, global_asset_id="https://acplt.org/TestAsset",
+ specific_asset_id=(model.SpecificAssetId("test", "test"),))
+
+ def test_aasd_131_set(self) -> None:
+ asset_information = model.AssetInformation(model.AssetKind.INSTANCE,
+ global_asset_id="https://acplt.org/TestAsset",
+ specific_asset_id=(model.SpecificAssetId("test", "test"),))
+ asset_information.global_asset_id = None
+ with self.assertRaises(model.AASConstraintViolation) as cm:
+ asset_information.specific_asset_id = model.ConstrainedList(())
+ self.assertEqual("An AssetInformation has to have a globalAssetId or a specificAssetId (Constraint AASd-131)",
+ str(cm.exception))
+
+ asset_information = model.AssetInformation(model.AssetKind.INSTANCE,
+ global_asset_id="https://acplt.org/TestAsset",
+ specific_asset_id=(model.SpecificAssetId("test", "test"),))
+ asset_information.specific_asset_id = model.ConstrainedList(())
+ with self.assertRaises(model.AASConstraintViolation) as cm:
+ asset_information.global_asset_id = None
+ self.assertEqual("An AssetInformation has to have a globalAssetId or a specificAssetId (Constraint AASd-131)",
+ str(cm.exception))
+
+ def test_aasd_131_specific_asset_id_add(self) -> None:
+ asset_information = model.AssetInformation(model.AssetKind.INSTANCE,
+ global_asset_id="https://acplt.org/TestAsset")
+ specific_asset_id1 = model.SpecificAssetId("test", "test")
+ specific_asset_id2 = model.SpecificAssetId("test", "test")
+ asset_information.specific_asset_id.append(specific_asset_id1)
+ asset_information.specific_asset_id.extend((specific_asset_id2,))
+ self.assertIs(asset_information.specific_asset_id[0], specific_asset_id1)
+ self.assertIs(asset_information.specific_asset_id[1], specific_asset_id2)
+
+ def test_aasd_131_specific_asset_id_set(self) -> None:
+ asset_information = model.AssetInformation(model.AssetKind.INSTANCE,
+ specific_asset_id=(model.SpecificAssetId("test", "test"),))
+ with self.assertRaises(model.AASConstraintViolation) as cm:
+ asset_information.specific_asset_id[:] = ()
+ self.assertEqual("An AssetInformation has to have a globalAssetId or a specificAssetId (Constraint AASd-131)",
+ str(cm.exception))
+ specific_asset_id = model.SpecificAssetId("test", "test")
+ self.assertIsNot(asset_information.specific_asset_id[0], specific_asset_id)
+ asset_information.specific_asset_id[:] = (specific_asset_id,)
+ self.assertIs(asset_information.specific_asset_id[0], specific_asset_id)
+ asset_information.specific_asset_id[0] = model.SpecificAssetId("test", "test")
+ self.assertIsNot(asset_information.specific_asset_id[0], specific_asset_id)
+
+ def test_aasd_131_specific_asset_id_del(self) -> None:
+ specific_asset_id = model.SpecificAssetId("test", "test")
+ asset_information = model.AssetInformation(model.AssetKind.INSTANCE,
+ specific_asset_id=(model.SpecificAssetId("test1", "test1"),
+ specific_asset_id))
+ with self.assertRaises(model.AASConstraintViolation) as cm:
+ del asset_information.specific_asset_id[:]
+ self.assertEqual("An AssetInformation has to have a globalAssetId or a specificAssetId (Constraint AASd-131)",
+ str(cm.exception))
+ with self.assertRaises(model.AASConstraintViolation) as cm:
+ asset_information.specific_asset_id.clear()
+ self.assertEqual("An AssetInformation has to have a globalAssetId or a specificAssetId (Constraint AASd-131)",
+ str(cm.exception))
+ self.assertIsNot(asset_information.specific_asset_id[0], specific_asset_id)
+ del asset_information.specific_asset_id[0]
+ self.assertIs(asset_information.specific_asset_id[0], specific_asset_id)
+ with self.assertRaises(model.AASConstraintViolation) as cm:
+ del asset_information.specific_asset_id[0]
+ self.assertEqual("An AssetInformation has to have a globalAssetId or a specificAssetId (Constraint AASd-131)",
+ str(cm.exception))
diff --git a/test/model/test_base.py b/test/model/test_base.py
index ef894de50..3fa8a64a3 100644
--- a/test/model/test_base.py
+++ b/test/model/test_base.py
@@ -374,7 +374,7 @@ def test_NamespaceSet(self) -> None:
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 "
- "(Constraint AASd-022)",
+ "(Constraint AASd-000)",
str(cm.exception))
self.namespace.set2.add(self.prop5)
self.namespace.set2.add(self.prop6)
@@ -389,7 +389,7 @@ def test_NamespaceSet(self) -> None:
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 (Constraint AASd-022)",
+ " is already present in another set in the same namespace (Constraint AASd-000)",
str(cm.exception))
self.assertIs(self.prop1, self.namespace.set1.get("id_short", "Prop1"))
@@ -456,7 +456,7 @@ def test_NamespaceSet(self) -> None:
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 (Constraint AASd-022)",
+ "of objects (Constraint AASd-021)",
str(cm.exception))
def test_namespaceset_hooks(self) -> None:
@@ -1213,7 +1213,15 @@ def hook(itm: int, _list: List[int]) -> None:
self.assertEqual(c_list, [1, 2, 3])
with self.assertRaises(ValueError):
c_list.clear()
- self.assertEqual(c_list, [1, 2, 3])
+ # the default clear() implementation seems to repeatedly delete the last item until the list is empty
+ # in this case, the last item is 3, which cannot be deleted because it is > 2, thus leaving it unclear whether
+ # clear() really is atomic. to work around this, the list is reversed, making 1 the last item, and attempting
+ # to clear again.
+ c_list.reverse()
+ with self.assertRaises(ValueError):
+ c_list.clear()
+ self.assertEqual(c_list, [3, 2, 1])
+ c_list.reverse()
del c_list[0:2]
self.assertEqual(c_list, [3])
diff --git a/test/model/test_provider.py b/test/model/test_provider.py
index 5a0f5ada4..55586ffc8 100644
--- a/test/model/test_provider.py
+++ b/test/model/test_provider.py
@@ -12,8 +12,10 @@
class ProvidersTest(unittest.TestCase):
def setUp(self) -> None:
- self.aas1 = model.AssetAdministrationShell(model.AssetInformation(), "urn:x-test:aas1")
- self.aas2 = model.AssetAdministrationShell(model.AssetInformation(), "urn:x-test:aas2")
+ self.aas1 = model.AssetAdministrationShell(
+ model.AssetInformation(global_asset_id="http://acplt.org/TestAsset1/"), "urn:x-test:aas1")
+ self.aas2 = model.AssetAdministrationShell(
+ model.AssetInformation(global_asset_id="http://acplt.org/TestAsset2/"), "urn:x-test:aas2")
self.submodel1 = model.Submodel("urn:x-test:submodel1")
self.submodel2 = model.Submodel("urn:x-test:submodel2")
@@ -24,7 +26,8 @@ def test_store_retrieve(self) -> None:
self.assertIn(self.aas1, object_store)
property = model.Property('test', model.datatypes.String)
self.assertFalse(property in object_store)
- aas3 = model.AssetAdministrationShell(model.AssetInformation(), "urn:x-test:aas1")
+ aas3 = model.AssetAdministrationShell(model.AssetInformation(global_asset_id="http://acplt.org/TestAsset/"),
+ "urn:x-test:aas1")
with self.assertRaises(KeyError) as cm:
object_store.add(aas3)
self.assertEqual("'Identifiable object with same id urn:x-test:aas1 is already "
diff --git a/test/model/test_submodel.py b/test/model/test_submodel.py
index cdc539978..74c12328d 100644
--- a/test/model/test_submodel.py
+++ b/test/model/test_submodel.py
@@ -12,34 +12,133 @@
class EntityTest(unittest.TestCase):
+ def test_aasd_014_init_self_managed(self) -> None:
+ with self.assertRaises(model.AASConstraintViolation) as cm:
+ model.Entity("TestEntity", model.EntityType.SELF_MANAGED_ENTITY)
+ self.assertEqual("A self-managed entity has to have a globalAssetId or a specificAssetId (Constraint AASd-014)",
+ str(cm.exception))
+ model.Entity("TestEntity", model.EntityType.SELF_MANAGED_ENTITY, global_asset_id="https://acplt.org/TestAsset")
+ model.Entity("TestEntity", model.EntityType.SELF_MANAGED_ENTITY,
+ specific_asset_id=(model.SpecificAssetId("test", "test"),))
+ model.Entity("TestEntity", model.EntityType.SELF_MANAGED_ENTITY, global_asset_id="https://acplt.org/TestAsset",
+ specific_asset_id=(model.SpecificAssetId("test", "test"),))
- def test_set_entity(self):
+ def test_aasd_014_init_co_managed(self) -> None:
+ model.Entity("TestEntity", model.EntityType.CO_MANAGED_ENTITY)
with self.assertRaises(model.AASConstraintViolation) as cm:
- obj = model.Entity(id_short='Test', entity_type=model.EntityType.SELF_MANAGED_ENTITY, statement=())
- self.assertIn(
- 'A self-managed entity has to have a globalAssetId or a specificAssetId',
- str(cm.exception)
- )
+ model.Entity("TestEntity", model.EntityType.CO_MANAGED_ENTITY,
+ global_asset_id="https://acplt.org/TestAsset")
+ self.assertEqual("A co-managed entity has to have neither a globalAssetId nor a specificAssetId "
+ "(Constraint AASd-014)", str(cm.exception))
with self.assertRaises(model.AASConstraintViolation) as cm:
- obj2 = model.Entity(id_short='Test', entity_type=model.EntityType.CO_MANAGED_ENTITY,
- global_asset_id='http://acplt.org/TestAsset/',
- statement=())
- self.assertIn(
- 'A co-managed entity has to have neither a globalAssetId nor a specificAssetId',
- str(cm.exception)
- )
+ model.Entity("TestEntity", model.EntityType.CO_MANAGED_ENTITY,
+ specific_asset_id=(model.SpecificAssetId("test", "test"),))
+ self.assertEqual("A co-managed entity has to have neither a globalAssetId nor a specificAssetId "
+ "(Constraint AASd-014)", str(cm.exception))
+ with self.assertRaises(model.AASConstraintViolation) as cm:
+ model.Entity("TestEntity", model.EntityType.CO_MANAGED_ENTITY,
+ global_asset_id="https://acplt.org/TestAsset",
+ specific_asset_id=(model.SpecificAssetId("test", "test"),))
+ self.assertEqual("A co-managed entity has to have neither a globalAssetId nor a specificAssetId "
+ "(Constraint AASd-014)", str(cm.exception))
+
+ def test_aasd_014_set_self_managed(self) -> None:
+ entity = model.Entity("TestEntity", model.EntityType.SELF_MANAGED_ENTITY,
+ global_asset_id="https://acplt.org/TestAsset",
+ specific_asset_id=(model.SpecificAssetId("test", "test"),))
+ entity.global_asset_id = None
+ with self.assertRaises(model.AASConstraintViolation) as cm:
+ entity.specific_asset_id = model.ConstrainedList(())
+ self.assertEqual("A self-managed entity has to have a globalAssetId or a specificAssetId (Constraint AASd-014)",
+ str(cm.exception))
+
+ entity = model.Entity("TestEntity", model.EntityType.SELF_MANAGED_ENTITY,
+ global_asset_id="https://acplt.org/TestAsset",
+ specific_asset_id=(model.SpecificAssetId("test", "test"),))
+ entity.specific_asset_id = model.ConstrainedList(())
+ with self.assertRaises(model.AASConstraintViolation) as cm:
+ entity.global_asset_id = None
+ self.assertEqual("A self-managed entity has to have a globalAssetId or a specificAssetId (Constraint AASd-014)",
+ str(cm.exception))
+
+ def test_aasd_014_set_co_managed(self) -> None:
+ entity = model.Entity("TestEntity", model.EntityType.CO_MANAGED_ENTITY)
+ with self.assertRaises(model.AASConstraintViolation) as cm:
+ entity.global_asset_id = "https://acplt.org/TestAsset"
+ self.assertEqual("A co-managed entity has to have neither a globalAssetId nor a specificAssetId "
+ "(Constraint AASd-014)", str(cm.exception))
+ with self.assertRaises(model.AASConstraintViolation) as cm:
+ entity.specific_asset_id = model.ConstrainedList((model.SpecificAssetId("test", "test"),))
+ self.assertEqual("A co-managed entity has to have neither a globalAssetId nor a specificAssetId "
+ "(Constraint AASd-014)", str(cm.exception))
+
+ def test_aasd_014_specific_asset_id_add_self_managed(self) -> None:
+ entity = model.Entity("TestEntity", model.EntityType.SELF_MANAGED_ENTITY,
+ global_asset_id="https://acplt.org/TestAsset")
+ specific_asset_id1 = model.SpecificAssetId("test", "test")
+ specific_asset_id2 = model.SpecificAssetId("test", "test")
+ entity.specific_asset_id.append(specific_asset_id1)
+ entity.specific_asset_id.extend((specific_asset_id2,))
+ self.assertIs(entity.specific_asset_id[0], specific_asset_id1)
+ self.assertIs(entity.specific_asset_id[1], specific_asset_id2)
+
+ def test_aasd_014_specific_asset_id_add_co_managed(self) -> None:
+ entity = model.Entity("TestEntity", model.EntityType.CO_MANAGED_ENTITY)
+ with self.assertRaises(model.AASConstraintViolation) as cm:
+ entity.specific_asset_id.append(model.SpecificAssetId("test", "test"))
+ self.assertEqual("A co-managed entity has to have neither a globalAssetId nor a specificAssetId "
+ "(Constraint AASd-014)", str(cm.exception))
+ with self.assertRaises(model.AASConstraintViolation) as cm:
+ entity.specific_asset_id.extend((model.SpecificAssetId("test", "test"),))
+ self.assertEqual("A co-managed entity has to have neither a globalAssetId nor a specificAssetId "
+ "(Constraint AASd-014)", str(cm.exception))
+
+ def test_assd_014_specific_asset_id_set_self_managed(self) -> None:
+ entity = model.Entity("TestEntity", model.EntityType.SELF_MANAGED_ENTITY,
+ specific_asset_id=(model.SpecificAssetId("test", "test"),))
+ with self.assertRaises(model.AASConstraintViolation) as cm:
+ entity.specific_asset_id[:] = ()
+ self.assertEqual("A self-managed entity has to have a globalAssetId or a specificAssetId (Constraint AASd-014)",
+ str(cm.exception))
+ specific_asset_id = model.SpecificAssetId("test", "test")
+ self.assertIsNot(entity.specific_asset_id[0], specific_asset_id)
+ entity.specific_asset_id[:] = (specific_asset_id,)
+ self.assertIs(entity.specific_asset_id[0], specific_asset_id)
+ entity.specific_asset_id[0] = model.SpecificAssetId("test", "test")
+ self.assertIsNot(entity.specific_asset_id[0], specific_asset_id)
+
+ def test_assd_014_specific_asset_id_set_co_managed(self) -> None:
+ entity = model.Entity("TestEntity", model.EntityType.CO_MANAGED_ENTITY)
+ with self.assertRaises(model.AASConstraintViolation) as cm:
+ entity.specific_asset_id[:] = (model.SpecificAssetId("test", "test"),)
+ self.assertEqual("A co-managed entity has to have neither a globalAssetId nor a specificAssetId "
+ "(Constraint AASd-014)", str(cm.exception))
+ entity.specific_asset_id[:] = ()
+
+ def test_aasd_014_specific_asset_id_del_self_managed(self) -> None:
+ specific_asset_id = model.SpecificAssetId("test", "test")
+ entity = model.Entity("TestEntity", model.EntityType.SELF_MANAGED_ENTITY,
+ specific_asset_id=(model.SpecificAssetId("test", "test"),
+ specific_asset_id))
+ with self.assertRaises(model.AASConstraintViolation) as cm:
+ del entity.specific_asset_id[:]
+ self.assertEqual("A self-managed entity has to have a globalAssetId or a specificAssetId (Constraint AASd-014)",
+ str(cm.exception))
+ with self.assertRaises(model.AASConstraintViolation) as cm:
+ entity.specific_asset_id.clear()
+ self.assertEqual("A self-managed entity has to have a globalAssetId or a specificAssetId (Constraint AASd-014)",
+ str(cm.exception))
+ self.assertIsNot(entity.specific_asset_id[0], specific_asset_id)
+ del entity.specific_asset_id[0]
+ self.assertIs(entity.specific_asset_id[0], specific_asset_id)
+ with self.assertRaises(model.AASConstraintViolation) as cm:
+ del entity.specific_asset_id[0]
+ self.assertEqual("A self-managed entity has to have a globalAssetId or a specificAssetId (Constraint AASd-014)",
+ str(cm.exception))
- specific_asset_id = model.SpecificAssetId(name="TestKey",
- value="TestValue",
- external_subject_id=model.ExternalReference((model.Key(
- type_=model.KeyTypes.GLOBAL_REFERENCE,
- value='http://acplt.org/SpecificAssetId/'),)))
- with self.assertRaises(model.AASConstraintViolation) as cm:
- obj3 = model.Entity(id_short='Test', entity_type=model.EntityType.CO_MANAGED_ENTITY,
- specific_asset_id=specific_asset_id, statement=())
- self.assertIn(
- 'A co-managed entity has to have neither a globalAssetId nor a specificAssetId',
- str(cm.exception))
+ def test_aasd_014_specific_asset_id_del_co_managed(self) -> None:
+ entity = model.Entity("TestEntity", model.EntityType.CO_MANAGED_ENTITY)
+ del entity.specific_asset_id[:]
class PropertyTest(unittest.TestCase):