Skip to content

Commit 98e1770

Browse files
s-heppnermoritzsommerZANDx1Frosty2500
authored
Update/metamodel v3.1.2 (#484)
This implements the changes of the metamodel for version 3.1.2 of the spec. Fixes #437 --------- Co-authored-by: Moritz Sommer <m.sommer@iat.rwth-aachen.de> Co-authored-by: Leon Huang <hleon04@icloud.com> Co-authored-by: Sercan Sahin <s.sahin@iat.rwth-aachen.de>
1 parent 3ae2eae commit 98e1770

15 files changed

Lines changed: 140 additions & 150 deletions

File tree

.github/workflows/ci.yml

Lines changed: 30 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -203,36 +203,37 @@ jobs:
203203
chmod +x ./etc/scripts/set_copyright_year.sh
204204
./etc/scripts/set_copyright_year.sh --check
205205
206+
# Todo: reset when the compliance-tool was updated (#485)
206207

207-
compliance-tool-test:
208-
# This job runs the unittests on the python versions specified down at the matrix
209-
runs-on: ubuntu-latest
210-
strategy:
211-
matrix:
212-
python-version: ["3.10", "3.12"]
213-
defaults:
214-
run:
215-
working-directory: ./compliance_tool
216-
217-
steps:
218-
- uses: actions/checkout@v4
219-
- name: Set up Python ${{ matrix.python-version }}
220-
uses: actions/setup-python@v5
221-
with:
222-
python-version: ${{ matrix.python-version }}
223-
- name: Install Python dependencies
224-
# install the local sdk in editable mode so it does not get overwritten
225-
run: |
226-
python -m pip install --upgrade pip
227-
python -m pip install ../sdk
228-
python -m pip install .[dev]
229-
- name: Test with coverage + unittest
230-
run: |
231-
python -m coverage run --source=aas_compliance_tool -m unittest
232-
- name: Report test coverage
233-
if: ${{ always() }}
234-
run: |
235-
python -m coverage report -m
208+
# compliance-tool-test:
209+
# # This job runs the unittests on the python versions specified down at the matrix
210+
# runs-on: ubuntu-latest
211+
# strategy:
212+
# matrix:
213+
# python-version: ["3.10", "3.12"]
214+
# defaults:
215+
# run:
216+
# working-directory: ./compliance_tool
217+
#
218+
# steps:
219+
# - uses: actions/checkout@v4
220+
# - name: Set up Python ${{ matrix.python-version }}
221+
# uses: actions/setup-python@v5
222+
# with:
223+
# python-version: ${{ matrix.python-version }}
224+
# - name: Install Python dependencies
225+
# # install the local sdk in editable mode so it does not get overwritten
226+
# run: |
227+
# python -m pip install --upgrade pip
228+
# python -m pip install ../sdk
229+
# python -m pip install .[dev]
230+
# - name: Test with coverage + unittest
231+
# run: |
232+
# python -m coverage run --source=aas_compliance_tool -m unittest
233+
# - name: Report test coverage
234+
# if: ${{ always() }}
235+
# run: |
236+
# python -m coverage report -m
236237

237238
compliance-tool-static-analysis:
238239
# This job runs static code analysis, namely pycodestyle and mypy

README.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,13 @@ for Industry 4.0 Systems.
77

88
These are the implemented AAS specifications of the [current SDK release](https://github.com/eclipse-basyx/basyx-python-sdk/releases/latest), which can be also found on [PyPI](https://pypi.org/project/basyx-python-sdk/):
99

10-
| Specification | Version |
11-
|---------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
12-
| Part 1: Metamodel | [v3.0.1 (01001)](https://industrialdigitaltwin.org/wp-content/uploads/2024/06/IDTA-01001-3-0-1_SpecificationAssetAdministrationShell_Part1_Metamodel.pdf) |
13-
| Schemata (JSONSchema, XSD) | [v3.0.8 (IDTA-01001-3-0-1_schemasV3.0.8)](https://github.com/admin-shell-io/aas-specs/releases/tag/IDTA-01001-3-0-1_schemasV3.0.8) |
14-
| Part 2: API | [v3.1.1 (01002)](https://industrialdigitaltwin.org/en/wp-content/uploads/sites/2/2025/08/IDTA-01002-3-1-1_AAS-Specification_Part2_API.pdf) |
10+
| Specification | Version |
11+
|---------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------|
12+
| Part 1: Metamodel | [v3.1.2 (01001-3-1-2)](https://industrialdigitaltwin.io/aas-specifications/IDTA-01001/v3.1.2/index.html) |
13+
| Schemata (JSONSchema, XSD) | [v3.1.2 (IDTA-01001-3-1-2)](https://github.com/admin-shell-io/aas-specs-metamodel/releases/tag/v3.1.2) |
14+
| Part 2: API | [v3.1.1 (01002)](https://industrialdigitaltwin.org/en/wp-content/uploads/sites/2/2025/08/IDTA-01002-3-1-1_AAS-Specification_Part2_API.pdf) |
1515
| Part 3a: Data Specification IEC 61360 | [v3.1.1 (01003-a)](https://industrialdigitaltwin.org/wp-content/uploads/2025/08/IDTA-01003-a-3-1-1_AAS-Specification_Part3a_DataSpecification.pdf) |
16-
| Part 5: Package File Format (AASX) | [v3.1 (01005)](https://industrialdigitaltwin.org/wp-content/uploads/2025/06/IDTA_01005-25-01_AAS-Specification_Part5_AASXPackageFileFormat.pdf) |
16+
| Part 5: Package File Format (AASX) | [v3.1 (01005)](https://industrialdigitaltwin.org/wp-content/uploads/2025/06/IDTA_01005-25-01_AAS-Specification_Part5_AASXPackageFileFormat.pdf) |
1717

1818

1919
If you need support to an older version of the specifications, please refer to our [prior releases](https://github.com/eclipse-basyx/basyx-python-sdk/releases).

sdk/basyx/aas/adapter/_generic.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@
3737
ASSET_KIND: Dict[model.AssetKind, str] = {
3838
model.AssetKind.TYPE: 'Type',
3939
model.AssetKind.INSTANCE: 'Instance',
40-
model.AssetKind.NOT_APPLICABLE: 'NotApplicable'}
40+
model.AssetKind.NOT_APPLICABLE: 'NotApplicable',
41+
model.AssetKind.ROLE: 'Role'}
4142

4243
QUALIFIER_KIND: Dict[model.QualifierKind, str] = {
4344
model.QualifierKind.CONCEPT_QUALIFIER: 'ConceptQualifier',

sdk/basyx/aas/adapter/json/json_deserialization.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -526,9 +526,12 @@ def _construct_entity(cls, dct: Dict[str, object], object_class=model.Entity) ->
526526
if 'specificAssetIds' in dct:
527527
for desc_data in _get_ts(dct, "specificAssetIds", list):
528528
specific_asset_id.add(cls._construct_specific_asset_id(desc_data, model.SpecificAssetId))
529-
529+
if 'entityType' in dct:
530+
entity_type = ENTITY_TYPES_INVERSE[_get_ts(dct, 'entityType', str)]
531+
else:
532+
entity_type = None
530533
ret = object_class(id_short=None,
531-
entity_type=ENTITY_TYPES_INVERSE[_get_ts(dct, "entityType", str)],
534+
entity_type=entity_type,
532535
global_asset_id=global_asset_id,
533536
specific_asset_id=specific_asset_id)
534537
cls._amend_abstract_attributes(ret, dct)

sdk/basyx/aas/adapter/json/json_serialization.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -476,7 +476,8 @@ def _blob_to_json(cls, obj: model.Blob) -> Dict[str, object]:
476476
:return: dict with the serialized attributes of this object
477477
"""
478478
data = cls._abstract_classes_to_json(obj)
479-
data['contentType'] = obj.content_type
479+
if obj.content_type is not None:
480+
data['contentType'] = obj.content_type
480481
if obj.value is not None:
481482
data['value'] = base64.b64encode(obj.value).decode()
482483
return data
@@ -490,7 +491,8 @@ def _file_to_json(cls, obj: model.File) -> Dict[str, object]:
490491
:return: dict with the serialized attributes of this object
491492
"""
492493
data = cls._abstract_classes_to_json(obj)
493-
data['contentType'] = obj.content_type
494+
if obj.content_type is not None:
495+
data['contentType'] = obj.content_type
494496
if obj.value is not None:
495497
data['value'] = obj.value
496498
return data
@@ -576,8 +578,7 @@ def _annotated_relationship_element_to_json(cls, obj: model.AnnotatedRelationshi
576578
:param obj: object of class AnnotatedRelationshipElement
577579
:return: dict with the serialized attributes of this object
578580
"""
579-
data = cls._abstract_classes_to_json(obj)
580-
data.update({'first': obj.first, 'second': obj.second})
581+
data = cls._relationship_element_to_json(obj)
581582
if not cls.stripped and obj.annotation:
582583
data['annotations'] = list(obj.annotation)
583584
return data
@@ -635,10 +636,11 @@ def _entity_to_json(cls, obj: model.Entity) -> Dict[str, object]:
635636
data = cls._abstract_classes_to_json(obj)
636637
if not cls.stripped and obj.statement:
637638
data['statements'] = list(obj.statement)
638-
data['entityType'] = _generic.ENTITY_TYPES[obj.entity_type]
639-
if obj.global_asset_id:
639+
if obj.entity_type is not None:
640+
data['entityType'] = _generic.ENTITY_TYPES[obj.entity_type]
641+
if obj.global_asset_id is not None:
640642
data['globalAssetId'] = obj.global_asset_id
641-
if obj.specific_asset_id:
643+
if obj.specific_asset_id is not None:
642644
data['specificAssetIds'] = list(obj.specific_asset_id)
643645
return data
644646

sdk/basyx/aas/adapter/xml/xml_deserialization.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -827,10 +827,14 @@ def construct_entity(cls, element: etree._Element, object_class=model.Entity, **
827827
for id in _child_construct_multiple(specific_asset_ids, NS_AAS + "specificAssetId",
828828
cls.construct_specific_asset_id, cls.failsafe):
829829
specific_asset_id.add(id)
830-
830+
entity_type_text = _get_text_or_none(element.find(NS_AAS + "entityType"))
831+
if entity_type_text is not None:
832+
entity_type = ENTITY_TYPES_INVERSE[entity_type_text]
833+
else:
834+
entity_type = None
831835
entity = object_class(
832836
id_short=None,
833-
entity_type=_child_text_mandatory_mapped(element, NS_AAS + "entityType", ENTITY_TYPES_INVERSE),
837+
entity_type=entity_type,
834838
global_asset_id=_get_text_or_none(element.find(NS_AAS + "globalAssetId")),
835839
specific_asset_id=specific_asset_id)
836840

sdk/basyx/aas/adapter/xml/xml_serialization.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -628,7 +628,8 @@ def blob_to_xml(obj: model.Blob,
628628
if obj.value is not None:
629629
et_value.text = base64.b64encode(obj.value).decode()
630630
et_blob.append(et_value)
631-
et_blob.append(_generate_element(NS_AAS + "contentType", text=obj.content_type))
631+
if obj.content_type is not None:
632+
et_blob.append(_generate_element(NS_AAS + "contentType", text=obj.content_type))
632633
return et_blob
633634

634635

@@ -644,7 +645,8 @@ def file_to_xml(obj: model.File,
644645
et_file = abstract_classes_to_xml(tag, obj)
645646
if obj.value:
646647
et_file.append(_generate_element(NS_AAS + "value", text=obj.value))
647-
et_file.append(_generate_element(NS_AAS + "contentType", text=obj.content_type))
648+
if obj.content_type is not None:
649+
et_file.append(_generate_element(NS_AAS + "contentType", text=obj.content_type))
648650
return et_file
649651

650652

@@ -734,8 +736,10 @@ def relationship_element_to_xml(obj: model.RelationshipElement,
734736
:return: Serialized :class:`~lxml.etree._Element` object
735737
"""
736738
et_relationship_element = abstract_classes_to_xml(tag, obj)
737-
et_relationship_element.append(reference_to_xml(obj.first, NS_AAS+"first"))
738-
et_relationship_element.append(reference_to_xml(obj.second, NS_AAS+"second"))
739+
if obj.first is not None:
740+
et_relationship_element.append(reference_to_xml(obj.first, NS_AAS+"first"))
741+
if obj.second is not None:
742+
et_relationship_element.append(reference_to_xml(obj.second, NS_AAS+"second"))
739743
return et_relationship_element
740744

741745

@@ -823,7 +827,8 @@ def entity_to_xml(obj: model.Entity,
823827
for statement in obj.statement:
824828
et_statements.append(submodel_element_to_xml(statement))
825829
et_entity.append(et_statements)
826-
et_entity.append(_generate_element(NS_AAS + "entityType", text=_generic.ENTITY_TYPES[obj.entity_type]))
830+
if obj.entity_type:
831+
et_entity.append(_generate_element(NS_AAS + "entityType", text=_generic.ENTITY_TYPES[obj.entity_type]))
827832
if obj.global_asset_id:
828833
et_entity.append(_generate_element(NS_AAS + "globalAssetId", text=obj.global_asset_id))
829834
if obj.specific_asset_id:

sdk/basyx/aas/examples/data/example_aas.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
_embedded_data_specification_iec61360 = model.EmbeddedDataSpecification(
2424
data_specification=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE,
2525
value='https://admin-shell.io/DataSpecificationTemplates/'
26-
'DataSpecificationIEC61360/3'),)),
26+
'DataSpecificationIEC61360/3/1'),)),
2727
data_specification_content=model.DataSpecificationIEC61360(preferred_name=model.PreferredNameTypeIEC61360({
2828
'de': 'Test Specification',
2929
'en-US': 'TestSpecification'

sdk/basyx/aas/model/_string_constraints.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,11 +64,11 @@ def check(value: str, type_name: str, min_length: int = 0, max_length: Optional[
6464

6565

6666
def check_content_type(value: str, type_name: str = "ContentType") -> None:
67-
return check(value, type_name, 1, 100)
67+
return check(value, type_name, 1, 128)
6868

6969

7070
def check_identifier(value: str, type_name: str = "Identifier") -> None:
71-
return check(value, type_name, 1, 2000)
71+
return check(value, type_name, 1, 2048)
7272

7373

7474
def check_label_type(value: str, type_name: str = "LabelType") -> None:
@@ -84,7 +84,7 @@ def check_name_type(value: str, type_name: str = "NameType") -> None:
8484

8585

8686
def check_path_type(value: str, type_name: str = "PathType") -> None:
87-
return check(value, type_name, 1, 2000)
87+
return check(value, type_name, 1, 2048)
8888

8989

9090
def check_qualifier_type(value: str, type_name: str = "QualifierType") -> None:

sdk/basyx/aas/model/base.py

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,7 @@ class ModellingKind(Enum):
223223
@unique
224224
class AssetKind(Enum):
225225
"""
226-
Enumeration for denoting whether an asset is a type asset or an instance asset or whether this kind of
226+
Enumeration for denoting whether an asset is a type asset or an instance asset or role asset or whether this kind of
227227
classification is not applicable.
228228
229229
.. note::
@@ -235,12 +235,14 @@ class AssetKind(Enum):
235235
236236
:cvar TYPE: Type asset
237237
:cvar INSTANCE: Instance asset
238-
:cvar NOT_APPLICABLE: Neither a type asset nor an instance asset
238+
:cvar ROLE: Role asset
239+
:cvar NOT_APPLICABLE: Neither a type asset nor an instance asset nor a role asset
239240
"""
240241

241242
TYPE = 0
242243
INSTANCE = 1
243244
NOT_APPLICABLE = 2
245+
ROLE = 3
244246

245247

246248
class QualifierKind(Enum):
@@ -574,7 +576,7 @@ class HasExtension(Namespace, metaclass=abc.ABCMeta):
574576
575577
<<abstract>>
576578
577-
**Constraint AASd-077:** The name of an Extension within HasExtensions needs to be unique.
579+
**Constraint AASd-077:** The name of an Extension within HasExtensions shall be unique.
578580
579581
:ivar namespace_element_sets: List of :class:`NamespaceSets <basyx.aas.model.base.NamespaceSet>`
580582
:ivar extension: A :class:`~.NamespaceSet` of :class:`Extensions <.Extension>` of the element.
@@ -622,8 +624,9 @@ class Referable(HasExtension, metaclass=abc.ABCMeta):
622624
**Constraint AASd-001:** In case of a referable element not being an identifiable element the
623625
idShort is mandatory and used for referring to the element in its name space.
624626
625-
**Constraint AASd-002:** idShort shall only feature letters, digits, underscore (``_``); starting
626-
mandatory with a letter.
627+
**Constraint AASd-002:** idShort shall only feature letters, digits, underscore (``_``), hyphen (``-``);
628+
starting mandatory with a letter and not ending with a hyphen.
629+
I.e. ``^[a-zA-Z]|[a-zA-Z][a-zA-Z0-9_-]*[a-zA-Z0-9_]$``
627630
628631
**Constraint AASd-004:** Add parent in case of non-identifiable elements.
629632
@@ -784,8 +787,9 @@ def validate_id_short(cls, id_short: NameType) -> None:
784787
"""
785788
Validates an id_short against Constraint AASd-002 and :class:`NameType` restrictions.
786789
787-
**Constraint AASd-002:** idShort of Referables shall only feature letters, digits, underscore (``_``); starting
788-
mandatory with a letter. I.e. ``[a-zA-Z][a-zA-Z0-9_]+``
790+
**Constraint AASd-002:** idShort shall only feature letters, digits, underscore (``_``), hyphen (``-``);
791+
starting mandatory with a letter and not ending with a hyphen.
792+
I.e. ``^[a-zA-Z]|[a-zA-Z][a-zA-Z0-9_-]*[a-zA-Z0-9_]$``
789793
790794
:param id_short: The id_short to validate
791795
:raises ValueError: If the id_short doesn't comply to the constraints imposed by :class:`NameType`
@@ -794,25 +798,31 @@ def validate_id_short(cls, id_short: NameType) -> None:
794798
"""
795799
_string_constraints.check_name_type(id_short)
796800
test_id_short: NameType = str(id_short)
797-
if not re.fullmatch("[a-zA-Z0-9_]*", test_id_short):
801+
if not re.fullmatch("[A-Za-z0-9_-]*", test_id_short):
798802
raise AASConstraintViolation(
799803
2,
800-
"The id_short must contain only letters, digits and underscore"
804+
"The id_short must contain only letters, digits underscore and hyphen"
801805
)
802806
if not test_id_short[0].isalpha():
803807
raise AASConstraintViolation(
804808
2,
805809
"The id_short must start with a letter"
806810
)
811+
if test_id_short.endswith("-"):
812+
raise AASConstraintViolation(
813+
2,
814+
"The id_short must not end with a hyphen"
815+
)
807816

808817
category = property(_get_category, _set_category)
809818

810819
def _set_id_short(self, id_short: Optional[NameType]):
811820
"""
812821
Check the input string
813822
814-
**Constraint AASd-002:** idShort of Referables shall only feature letters, digits, underscore (``_``); starting
815-
mandatory with a letter. I.e. ``[a-zA-Z][a-zA-Z0-9_]+``
823+
**Constraint AASd-002:** idShort shall only feature letters, digits, underscore (``_``), hyphen (``-``);
824+
starting mandatory with a letter and not ending with a hyphen.
825+
I.e. ``^[a-zA-Z]|[a-zA-Z][a-zA-Z0-9_-]*[a-zA-Z0-9_]$``
816826
817827
**Constraint AASd-022:** idShort of non-identifiable referables shall be unique in its namespace
818828
(case-sensitive)
@@ -834,9 +844,6 @@ def _set_id_short(self, id_short: Optional[NameType]):
834844
raise AASConstraintViolation(117, f"id_short of {self!r} cannot be unset, since it is already "
835845
f"contained in {self.parent!r}")
836846
from .submodel import SubmodelElementList
837-
if isinstance(self.parent, SubmodelElementList):
838-
raise AASConstraintViolation(120, f"id_short of {self!r} cannot be set, because it is "
839-
f"contained in a {self.parent!r}")
840847
for set_ in self.parent.namespace_element_sets:
841848
if set_.contains_id("id_short", id_short):
842849
raise AASConstraintViolation(22, "Object with id_short '{}' is already present in the parent "
@@ -1197,7 +1204,7 @@ class DataSpecificationContent:
11971204
**Constraint AASc-3a-050:** If the ``Data_specification_IEC_61360`` is used
11981205
for an element, the value of ``HasDataSpecification.embedded_data_specifications``
11991206
shall contain the external reference to the IRI of the corresponding data specification
1200-
template ``https://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3``
1207+
template ``https://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/1``
12011208
"""
12021209
@abc.abstractmethod
12031210
def __init__(self):
@@ -1656,8 +1663,8 @@ class Qualifier(HasSemantics):
16561663
"""
16571664
A qualifier is a type-value pair that makes additional statements w.r.t. the value of the element.
16581665
1659-
**Constraint AASd-006:** If both, the value and the valueId of a Qualifier are present, the value needs
1660-
to be identical to the value of the referenced coded value in Qualifier/valueId.
1666+
**Constraint AASd-006:** If both, the value and the valueId of a Qualifier are present, the value shall
1667+
be identical to the value of the referenced coded value in Qualifier/valueId.
16611668
16621669
**Constraint AASd-020:** The value of Qualifier/value shall be consistent with the
16631670
data type as defined in Qualifier/valueType.

0 commit comments

Comments
 (0)