From 603ff70da890b5172bd0304ba48902835092d871 Mon Sep 17 00:00:00 2001 From: Joshua Benning Date: Fri, 17 Jul 2026 14:47:16 +0200 Subject: [PATCH 01/17] Run ruff format --- sdk/basyx/aas/adapter/__init__.py | 8 +- sdk/basyx/aas/adapter/_generic.py | 173 +- sdk/basyx/aas/adapter/aasx.py | 414 +++-- sdk/basyx/aas/adapter/json/__init__.py | 17 +- .../aas/adapter/json/json_deserialization.py | 876 +++++---- .../aas/adapter/json/json_serialization.py | 345 ++-- sdk/basyx/aas/adapter/xml/__init__.py | 20 +- .../aas/adapter/xml/xml_deserialization.py | 1116 ++++++++---- .../aas/adapter/xml/xml_serialization.py | 524 ++++-- sdk/basyx/aas/backend/couchdb.py | 266 ++- sdk/basyx/aas/backend/local_file.py | 71 +- sdk/basyx/aas/examples/data/__init__.py | 35 +- sdk/basyx/aas/examples/data/_helper.py | 1030 +++++++---- sdk/basyx/aas/examples/data/example_aas.py | 1584 +++++++++++------ .../data/example_aas_mandatory_attributes.py | 255 ++- .../data/example_aas_missing_attributes.py | 784 +++++--- .../data/example_submodel_template.py | 673 ++++--- sdk/basyx/aas/examples/tutorial_aasx.py | 59 +- .../aas/examples/tutorial_backend_couchdb.py | 32 +- .../examples/tutorial_create_simple_aas.py | 56 +- .../aas/examples/tutorial_navigate_aas.py | 93 +- .../tutorial_serialization_deserialization.py | 58 +- sdk/basyx/aas/examples/tutorial_storage.py | 53 +- sdk/basyx/aas/model/__init__.py | 8 +- sdk/basyx/aas/model/_string_constraints.py | 51 +- sdk/basyx/aas/model/aas.py | 117 +- sdk/basyx/aas/model/base.py | 760 +++++--- sdk/basyx/aas/model/concept.py | 40 +- sdk/basyx/aas/model/datatypes.py | 381 ++-- sdk/basyx/aas/model/provider.py | 45 +- sdk/basyx/aas/model/submodel.py | 1021 +++++++---- sdk/basyx/aas/util/identification.py | 56 +- sdk/basyx/aas/util/traversal.py | 19 +- sdk/docs/source/conf.py | 45 +- sdk/test/_helper/setup_testdb.py | 112 +- sdk/test/_helper/test_helpers.py | 30 +- sdk/test/adapter/aasx/test_aasx.py | 408 +++-- .../adapter/json/test_json_deserialization.py | 34 +- .../adapter/json/test_json_serialization.py | 148 +- ...test_json_serialization_deserialization.py | 55 +- sdk/test/adapter/test_load_directory.py | 25 +- .../adapter/xml/test_xml_deserialization.py | 125 +- .../adapter/xml/test_xml_serialization.py | 79 +- .../test_xml_serialization_deserialization.py | 49 +- sdk/test/backend/test_couchdb.py | 81 +- sdk/test/backend/test_local_file.py | 72 +- sdk/test/examples/test__init__.py | 13 +- sdk/test/examples/test_examples.py | 94 +- sdk/test/examples/test_helpers.py | 376 ++-- sdk/test/examples/test_tutorials.py | 20 +- sdk/test/model/test_aas.py | 95 +- sdk/test/model/test_base.py | 948 +++++++--- sdk/test/model/test_datatypes.py | 571 ++++-- sdk/test/model/test_provider.py | 70 +- sdk/test/model/test_string_constraints.py | 65 +- sdk/test/model/test_submodel.py | 435 +++-- sdk/test/util/test_identification.py | 14 +- sdk/test/util/test_traversal.py | 40 +- 58 files changed, 10182 insertions(+), 4862 deletions(-) diff --git a/sdk/basyx/aas/adapter/__init__.py b/sdk/basyx/aas/adapter/__init__.py index 166b6b90f..80fd05df2 100644 --- a/sdk/basyx/aas/adapter/__init__.py +++ b/sdk/basyx/aas/adapter/__init__.py @@ -16,7 +16,9 @@ from typing import Union -def load_directory(directory: Union[Path, str]) -> tuple[DictIdentifiableStore, DictSupplementaryFileContainer]: +def load_directory( + directory: Union[Path, str], +) -> tuple[DictIdentifiableStore, DictSupplementaryFileContainer]: """ Create a new :class:`~basyx.aas.model.provider.DictIdentifiableStore` and use it to load Asset Administration Shell and Submodel files in ``AASX``, ``JSON`` and ``XML`` format from a given directory into memory. Additionally, load @@ -46,6 +48,8 @@ def load_directory(directory: Union[Path, str]) -> tuple[DictIdentifiableStore, read_aas_xml_file_into(dict_identifiable_store, f) elif suffix == ".aasx": with AASXReader(file) as reader: - reader.read_into(object_store=dict_identifiable_store, file_store=file_container) + reader.read_into( + object_store=dict_identifiable_store, file_store=file_container + ) return dict_identifiable_store, file_container diff --git a/sdk/basyx/aas/adapter/_generic.py b/sdk/basyx/aas/adapter/_generic.py index aa4f9d692..c2f333a7c 100644 --- a/sdk/basyx/aas/adapter/_generic.py +++ b/sdk/basyx/aas/adapter/_generic.py @@ -8,6 +8,7 @@ The dicts defined in this module are used in the json and xml modules to translate enum members of our implementation to the respective string and vice versa. """ + import os from typing import BinaryIO, Dict, IO, Type, Union @@ -21,9 +22,9 @@ # JSON top-level keys and their corresponding model classes JSON_AAS_TOP_LEVEL_KEYS_TO_TYPES = ( - ('assetAdministrationShells', model.AssetAdministrationShell), - ('submodels', model.Submodel), - ('conceptDescriptions', model.ConceptDescription), + ("assetAdministrationShells", model.AssetAdministrationShell), + ("submodels", model.Submodel), + ("conceptDescriptions", model.ConceptDescription), ) # XML Namespace definition @@ -31,100 +32,122 @@ XML_NS_AAS = "{" + XML_NS_MAP["aas"] + "}" MODELLING_KIND: Dict[model.ModellingKind, str] = { - model.ModellingKind.TEMPLATE: 'Template', - model.ModellingKind.INSTANCE: 'Instance'} + model.ModellingKind.TEMPLATE: "Template", + model.ModellingKind.INSTANCE: "Instance", +} ASSET_KIND: Dict[model.AssetKind, str] = { - model.AssetKind.TYPE: 'Type', - model.AssetKind.INSTANCE: 'Instance', - model.AssetKind.NOT_APPLICABLE: 'NotApplicable', - model.AssetKind.ROLE: 'Role'} + model.AssetKind.TYPE: "Type", + model.AssetKind.INSTANCE: "Instance", + model.AssetKind.NOT_APPLICABLE: "NotApplicable", + model.AssetKind.ROLE: "Role", +} QUALIFIER_KIND: Dict[model.QualifierKind, str] = { - model.QualifierKind.CONCEPT_QUALIFIER: 'ConceptQualifier', - model.QualifierKind.TEMPLATE_QUALIFIER: 'TemplateQualifier', - model.QualifierKind.VALUE_QUALIFIER: 'ValueQualifier'} + model.QualifierKind.CONCEPT_QUALIFIER: "ConceptQualifier", + model.QualifierKind.TEMPLATE_QUALIFIER: "TemplateQualifier", + model.QualifierKind.VALUE_QUALIFIER: "ValueQualifier", +} DIRECTION: Dict[model.Direction, str] = { - model.Direction.INPUT: 'input', - model.Direction.OUTPUT: 'output'} + model.Direction.INPUT: "input", + model.Direction.OUTPUT: "output", +} STATE_OF_EVENT: Dict[model.StateOfEvent, str] = { - model.StateOfEvent.ON: 'on', - model.StateOfEvent.OFF: 'off'} + model.StateOfEvent.ON: "on", + model.StateOfEvent.OFF: "off", +} REFERENCE_TYPES: Dict[Type[model.Reference], str] = { - model.ExternalReference: 'ExternalReference', - model.ModelReference: 'ModelReference'} + model.ExternalReference: "ExternalReference", + model.ModelReference: "ModelReference", +} KEY_TYPES: Dict[model.KeyTypes, str] = { - model.KeyTypes.ASSET_ADMINISTRATION_SHELL: 'AssetAdministrationShell', - model.KeyTypes.CONCEPT_DESCRIPTION: 'ConceptDescription', - model.KeyTypes.SUBMODEL: 'Submodel', - model.KeyTypes.ANNOTATED_RELATIONSHIP_ELEMENT: 'AnnotatedRelationshipElement', - model.KeyTypes.BASIC_EVENT_ELEMENT: 'BasicEventElement', - model.KeyTypes.BLOB: 'Blob', - model.KeyTypes.CAPABILITY: 'Capability', - model.KeyTypes.DATA_ELEMENT: 'DataElement', - model.KeyTypes.ENTITY: 'Entity', - model.KeyTypes.EVENT_ELEMENT: 'EventElement', - model.KeyTypes.FILE: 'File', - model.KeyTypes.MULTI_LANGUAGE_PROPERTY: 'MultiLanguageProperty', - model.KeyTypes.OPERATION: 'Operation', - model.KeyTypes.PROPERTY: 'Property', - model.KeyTypes.RANGE: 'Range', - model.KeyTypes.REFERENCE_ELEMENT: 'ReferenceElement', - model.KeyTypes.RELATIONSHIP_ELEMENT: 'RelationshipElement', - model.KeyTypes.SUBMODEL_ELEMENT: 'SubmodelElement', - model.KeyTypes.SUBMODEL_ELEMENT_COLLECTION: 'SubmodelElementCollection', - model.KeyTypes.SUBMODEL_ELEMENT_LIST: 'SubmodelElementList', - model.KeyTypes.GLOBAL_REFERENCE: 'GlobalReference', - model.KeyTypes.FRAGMENT_REFERENCE: 'FragmentReference'} + model.KeyTypes.ASSET_ADMINISTRATION_SHELL: "AssetAdministrationShell", + model.KeyTypes.CONCEPT_DESCRIPTION: "ConceptDescription", + model.KeyTypes.SUBMODEL: "Submodel", + model.KeyTypes.ANNOTATED_RELATIONSHIP_ELEMENT: "AnnotatedRelationshipElement", + model.KeyTypes.BASIC_EVENT_ELEMENT: "BasicEventElement", + model.KeyTypes.BLOB: "Blob", + model.KeyTypes.CAPABILITY: "Capability", + model.KeyTypes.DATA_ELEMENT: "DataElement", + model.KeyTypes.ENTITY: "Entity", + model.KeyTypes.EVENT_ELEMENT: "EventElement", + model.KeyTypes.FILE: "File", + model.KeyTypes.MULTI_LANGUAGE_PROPERTY: "MultiLanguageProperty", + model.KeyTypes.OPERATION: "Operation", + model.KeyTypes.PROPERTY: "Property", + model.KeyTypes.RANGE: "Range", + model.KeyTypes.REFERENCE_ELEMENT: "ReferenceElement", + model.KeyTypes.RELATIONSHIP_ELEMENT: "RelationshipElement", + model.KeyTypes.SUBMODEL_ELEMENT: "SubmodelElement", + model.KeyTypes.SUBMODEL_ELEMENT_COLLECTION: "SubmodelElementCollection", + model.KeyTypes.SUBMODEL_ELEMENT_LIST: "SubmodelElementList", + model.KeyTypes.GLOBAL_REFERENCE: "GlobalReference", + model.KeyTypes.FRAGMENT_REFERENCE: "FragmentReference", +} ENTITY_TYPES: Dict[model.EntityType, str] = { - model.EntityType.CO_MANAGED_ENTITY: 'CoManagedEntity', - model.EntityType.SELF_MANAGED_ENTITY: 'SelfManagedEntity'} + model.EntityType.CO_MANAGED_ENTITY: "CoManagedEntity", + model.EntityType.SELF_MANAGED_ENTITY: "SelfManagedEntity", +} IEC61360_DATA_TYPES: Dict[model.base.DataTypeIEC61360, str] = { - model.base.DataTypeIEC61360.DATE: 'DATE', - model.base.DataTypeIEC61360.STRING: 'STRING', - model.base.DataTypeIEC61360.STRING_TRANSLATABLE: 'STRING_TRANSLATABLE', - model.base.DataTypeIEC61360.INTEGER_MEASURE: 'INTEGER_MEASURE', - model.base.DataTypeIEC61360.INTEGER_COUNT: 'INTEGER_COUNT', - model.base.DataTypeIEC61360.INTEGER_CURRENCY: 'INTEGER_CURRENCY', - model.base.DataTypeIEC61360.REAL_MEASURE: 'REAL_MEASURE', - model.base.DataTypeIEC61360.REAL_COUNT: 'REAL_COUNT', - model.base.DataTypeIEC61360.REAL_CURRENCY: 'REAL_CURRENCY', - model.base.DataTypeIEC61360.BOOLEAN: 'BOOLEAN', - model.base.DataTypeIEC61360.IRI: 'IRI', - model.base.DataTypeIEC61360.IRDI: 'IRDI', - model.base.DataTypeIEC61360.RATIONAL: 'RATIONAL', - model.base.DataTypeIEC61360.RATIONAL_MEASURE: 'RATIONAL_MEASURE', - model.base.DataTypeIEC61360.TIME: 'TIME', - model.base.DataTypeIEC61360.TIMESTAMP: 'TIMESTAMP', - model.base.DataTypeIEC61360.HTML: 'HTML', - model.base.DataTypeIEC61360.BLOB: 'BLOB', - model.base.DataTypeIEC61360.FILE: 'FILE', + model.base.DataTypeIEC61360.DATE: "DATE", + model.base.DataTypeIEC61360.STRING: "STRING", + model.base.DataTypeIEC61360.STRING_TRANSLATABLE: "STRING_TRANSLATABLE", + model.base.DataTypeIEC61360.INTEGER_MEASURE: "INTEGER_MEASURE", + model.base.DataTypeIEC61360.INTEGER_COUNT: "INTEGER_COUNT", + model.base.DataTypeIEC61360.INTEGER_CURRENCY: "INTEGER_CURRENCY", + model.base.DataTypeIEC61360.REAL_MEASURE: "REAL_MEASURE", + model.base.DataTypeIEC61360.REAL_COUNT: "REAL_COUNT", + model.base.DataTypeIEC61360.REAL_CURRENCY: "REAL_CURRENCY", + model.base.DataTypeIEC61360.BOOLEAN: "BOOLEAN", + model.base.DataTypeIEC61360.IRI: "IRI", + model.base.DataTypeIEC61360.IRDI: "IRDI", + model.base.DataTypeIEC61360.RATIONAL: "RATIONAL", + model.base.DataTypeIEC61360.RATIONAL_MEASURE: "RATIONAL_MEASURE", + model.base.DataTypeIEC61360.TIME: "TIME", + model.base.DataTypeIEC61360.TIMESTAMP: "TIMESTAMP", + model.base.DataTypeIEC61360.HTML: "HTML", + model.base.DataTypeIEC61360.BLOB: "BLOB", + model.base.DataTypeIEC61360.FILE: "FILE", } IEC61360_LEVEL_TYPES: Dict[model.base.IEC61360LevelType, str] = { - model.base.IEC61360LevelType.MIN: 'min', - model.base.IEC61360LevelType.NOM: 'nom', - model.base.IEC61360LevelType.TYP: 'typ', - model.base.IEC61360LevelType.MAX: 'max', + model.base.IEC61360LevelType.MIN: "min", + model.base.IEC61360LevelType.NOM: "nom", + model.base.IEC61360LevelType.TYP: "typ", + model.base.IEC61360LevelType.MAX: "max", } -MODELLING_KIND_INVERSE: Dict[str, model.ModellingKind] = {v: k for k, v in MODELLING_KIND.items()} +MODELLING_KIND_INVERSE: Dict[str, model.ModellingKind] = { + v: k for k, v in MODELLING_KIND.items() +} ASSET_KIND_INVERSE: Dict[str, model.AssetKind] = {v: k for k, v in ASSET_KIND.items()} -QUALIFIER_KIND_INVERSE: Dict[str, model.QualifierKind] = {v: k for k, v in QUALIFIER_KIND.items()} +QUALIFIER_KIND_INVERSE: Dict[str, model.QualifierKind] = { + v: k for k, v in QUALIFIER_KIND.items() +} DIRECTION_INVERSE: Dict[str, model.Direction] = {v: k for k, v in DIRECTION.items()} -STATE_OF_EVENT_INVERSE: Dict[str, model.StateOfEvent] = {v: k for k, v in STATE_OF_EVENT.items()} -REFERENCE_TYPES_INVERSE: Dict[str, Type[model.Reference]] = {v: k for k, v in REFERENCE_TYPES.items()} +STATE_OF_EVENT_INVERSE: Dict[str, model.StateOfEvent] = { + v: k for k, v in STATE_OF_EVENT.items() +} +REFERENCE_TYPES_INVERSE: Dict[str, Type[model.Reference]] = { + v: k for k, v in REFERENCE_TYPES.items() +} KEY_TYPES_INVERSE: Dict[str, model.KeyTypes] = {v: k for k, v in KEY_TYPES.items()} -ENTITY_TYPES_INVERSE: Dict[str, model.EntityType] = {v: k for k, v in ENTITY_TYPES.items()} -IEC61360_DATA_TYPES_INVERSE: Dict[str, model.base.DataTypeIEC61360] = {v: k for k, v in IEC61360_DATA_TYPES.items()} -IEC61360_LEVEL_TYPES_INVERSE: Dict[str, model.base.IEC61360LevelType] = \ - {v: k for k, v in IEC61360_LEVEL_TYPES.items()} +ENTITY_TYPES_INVERSE: Dict[str, model.EntityType] = { + v: k for k, v in ENTITY_TYPES.items() +} +IEC61360_DATA_TYPES_INVERSE: Dict[str, model.base.DataTypeIEC61360] = { + v: k for k, v in IEC61360_DATA_TYPES.items() +} +IEC61360_LEVEL_TYPES_INVERSE: Dict[str, model.base.IEC61360LevelType] = { + v: k for k, v in IEC61360_LEVEL_TYPES.items() +} -KEY_TYPES_CLASSES_INVERSE: Dict[model.KeyTypes, Type[model.Referable]] = \ - {v: k for k, v in model.KEY_TYPES_CLASSES.items()} +KEY_TYPES_CLASSES_INVERSE: Dict[model.KeyTypes, Type[model.Referable]] = { + v: k for k, v in model.KEY_TYPES_CLASSES.items() +} diff --git a/sdk/basyx/aas/adapter/aasx.py b/sdk/basyx/aas/adapter/aasx.py index 82fe4b76f..67084f96b 100644 --- a/sdk/basyx/aas/adapter/aasx.py +++ b/sdk/basyx/aas/adapter/aasx.py @@ -40,7 +40,9 @@ RELATIONSHIP_TYPE_AASX_ORIGIN = "http://admin-shell.io/aasx/relationships/aasx-origin" RELATIONSHIP_TYPE_AAS_SPEC = "http://admin-shell.io/aasx/relationships/aas-spec" -RELATIONSHIP_TYPE_AAS_SPEC_SPLIT = "http://admin-shell.io/aasx/relationships/aas-spec-split" +RELATIONSHIP_TYPE_AAS_SPEC_SPLIT = ( + "http://admin-shell.io/aasx/relationships/aas-spec-split" +) RELATIONSHIP_TYPE_AAS_SUPL = "http://admin-shell.io/aasx/relationships/aas-suppl" @@ -59,6 +61,7 @@ class AASXReader: reader.read_into(objects, files) """ + def __init__(self, file: Union[os.PathLike, str, IO], failsafe: bool = True): """ Open an AASX reader for the given filename or file handle @@ -108,16 +111,22 @@ def get_thumbnail(self) -> Optional[bytes]: :return: The AASX package thumbnail's file contents or None if no thumbnail is provided """ try: - thumbnail_part = self.reader.get_related_parts_by_type()[pyecma376_2.RELATIONSHIP_TYPE_THUMBNAIL][0] + thumbnail_part = self.reader.get_related_parts_by_type()[ + pyecma376_2.RELATIONSHIP_TYPE_THUMBNAIL + ][0] except IndexError: return None with self.reader.open_part(thumbnail_part) as p: return p.read() - def read_into(self, object_store: model.AbstractObjectStore, - file_store: "AbstractSupplementaryFileContainer", - override_existing: bool = False, **kwargs) -> Set[model.Identifier]: + def read_into( + self, + object_store: model.AbstractObjectStore, + file_store: "AbstractSupplementaryFileContainer", + override_existing: bool = False, + **kwargs, + ) -> Set[model.Identifier]: """ Read the contents of the AASX package and add them into a given :class:`ObjectStore ` @@ -144,7 +153,9 @@ def read_into(self, object_store: model.AbstractObjectStore, AASX_REL_BASE = "http://admin-shell.io/aasx/relationships" AASX_REL_BASE_DEPRECATED = "http://www.admin-shell.io/aasx/relationships" RELATIONSHIP_TYPE_AASX_ORIGIN = f"{AASX_REL_BASE}/aasx-origin" - RELATIONSHIP_TYPE_AASX_ORIGIN_DEPRECATED = f"{AASX_REL_BASE_DEPRECATED}/aasx-origin" + RELATIONSHIP_TYPE_AASX_ORIGIN_DEPRECATED = ( + f"{AASX_REL_BASE_DEPRECATED}/aasx-origin" + ) # Find AASX-Origin part core_rels = self.reader.get_related_parts_by_type() @@ -164,23 +175,43 @@ def read_into(self, object_store: model.AbstractObjectStore, RELATIONSHIP_TYPE_AASX_ORIGIN_DEPRECATED, RELATIONSHIP_TYPE_AASX_ORIGIN, ) - aasx_origin_part = core_rels[RELATIONSHIP_TYPE_AASX_ORIGIN_DEPRECATED][0] + aasx_origin_part = core_rels[RELATIONSHIP_TYPE_AASX_ORIGIN_DEPRECATED][ + 0 + ] else: - raise ValueError("Not a valid AASX file: aasx-origin Relationship is missing.") from e + raise ValueError( + "Not a valid AASX file: aasx-origin Relationship is missing." + ) from e read_identifiables: Set[model.Identifier] = set() no_aas_files_found = True # Iterate AAS files - for aas_part in self.reader.get_related_parts_by_type(aasx_origin_part)[RELATIONSHIP_TYPE_AAS_SPEC]: + for aas_part in self.reader.get_related_parts_by_type(aasx_origin_part)[ + RELATIONSHIP_TYPE_AAS_SPEC + ]: no_aas_files_found = False - self._read_aas_part_into(aas_part, object_store, file_store, - read_identifiables, override_existing, **kwargs) + self._read_aas_part_into( + aas_part, + object_store, + file_store, + read_identifiables, + override_existing, + **kwargs, + ) # Iterate split parts of AAS file - for split_part in self.reader.get_related_parts_by_type(aas_part)[RELATIONSHIP_TYPE_AAS_SPEC_SPLIT]: - self._read_aas_part_into(split_part, object_store, file_store, - read_identifiables, override_existing, **kwargs) + for split_part in self.reader.get_related_parts_by_type(aas_part)[ + RELATIONSHIP_TYPE_AAS_SPEC_SPLIT + ]: + self._read_aas_part_into( + split_part, + object_store, + file_store, + read_identifiables, + override_existing, + **kwargs, + ) if no_aas_files_found: if self.failsafe: logger.warning("No AAS files found in AASX package") @@ -201,11 +232,15 @@ def __enter__(self) -> "AASXReader": def __exit__(self, exc_type, exc_val, exc_tb) -> None: self.close() - def _read_aas_part_into(self, part_name: str, - object_store: model.AbstractObjectStore, - file_store: "AbstractSupplementaryFileContainer", - read_identifiables: Set[model.Identifier], - override_existing: bool, **kwargs) -> None: + def _read_aas_part_into( + self, + part_name: str, + object_store: model.AbstractObjectStore, + file_store: "AbstractSupplementaryFileContainer", + read_identifiables: Set[model.Identifier], + override_existing: bool, + **kwargs, + ) -> None: """ Helper function for :meth:`read_into()` to read and process the contents of an AAS-spec part of the AASX file. @@ -226,15 +261,21 @@ def _read_aas_part_into(self, part_name: str, continue if obj.id in object_store: if override_existing: - logger.info(f"Overriding existing object in ObjectStore with {obj} ...") + logger.info( + f"Overriding existing object in ObjectStore with {obj} ..." + ) object_store.discard(obj) else: if self.failsafe: - logger.warning(f"Skipping {obj}, since an object with the same id is already contained in the " - "ObjectStore") + logger.warning( + f"Skipping {obj}, since an object with the same id is already contained in the " + "ObjectStore" + ) continue else: - raise ValueError(f"Object with id {obj} is already contained in the ObjectStore") + raise ValueError( + f"Object with id {obj} is already contained in the ObjectStore" + ) object_store.add(obj) read_identifiables.add(obj.id) if isinstance(obj, model.Submodel): @@ -253,27 +294,47 @@ def _parse_aas_part(self, part_name: str, **kwargs) -> model.DictIdentifiableSto """ content_type = self.reader.get_content_type(part_name) extension = part_name.split("/")[-1].split(".")[-1] - if content_type.split(";")[0] in ("text/xml", "application/xml") or content_type == "" and extension == "xml": - logger.debug(f"Parsing AAS objects from XML stream in OPC part {part_name} ...") + if ( + content_type.split(";")[0] in ("text/xml", "application/xml") + or content_type == "" + and extension == "xml" + ): + logger.debug( + f"Parsing AAS objects from XML stream in OPC part {part_name} ..." + ) with self.reader.open_part(part_name) as p: return read_aas_xml_file(p, failsafe=self.failsafe, **kwargs) - elif content_type.split(";")[0] in ("text/json", "application/json") \ - or content_type == "" and extension == "json": - logger.debug(f"Parsing AAS objects from JSON stream in OPC part {part_name} ...") + elif ( + content_type.split(";")[0] in ("text/json", "application/json") + or content_type == "" + and extension == "json" + ): + logger.debug( + f"Parsing AAS objects from JSON stream in OPC part {part_name} ..." + ) with self.reader.open_part(part_name) as p: - return read_aas_json_file(io.TextIOWrapper(p, encoding='utf-8-sig'), failsafe=self.failsafe, **kwargs) + return read_aas_json_file( + io.TextIOWrapper(p, encoding="utf-8-sig"), + failsafe=self.failsafe, + **kwargs, + ) else: - error_message = (f"Could not determine part format of AASX part {part_name} (Content Type: {content_type}," - f" extension: {extension}") + error_message = ( + f"Could not determine part format of AASX part {part_name} (Content Type: {content_type}," + f" extension: {extension}" + ) if self.failsafe: logger.error(error_message) else: raise ValueError(error_message) return model.DictIdentifiableStore() - def _collect_supplementary_files(self, part_name: str, - root_element: Union[model.AssetAdministrationShell, model.Submodel], - file_store: "AbstractSupplementaryFileContainer") -> None: + def _collect_supplementary_files( + self, + part_name: str, + root_element: Union[model.AssetAdministrationShell, model.Submodel], + file_store: "AbstractSupplementaryFileContainer", + ) -> None: """ Helper function to search File objects within a single parsed AssetAdministrationShell or Submodel. Resolve their absolute paths, and update the corresponding File/Thumbnail objects with the absolute path. @@ -284,11 +345,15 @@ def _collect_supplementary_files(self, part_name: str, :param file_store: The SupplementaryFileContainer to add the extracted supplementary files to """ if isinstance(root_element, model.AssetAdministrationShell): - if (root_element.asset_information.default_thumbnail and - root_element.asset_information.default_thumbnail.path): - file_name = self._add_supplementary_file(part_name, - root_element.asset_information.default_thumbnail.path, - file_store) + if ( + root_element.asset_information.default_thumbnail + and root_element.asset_information.default_thumbnail.path + ): + file_name = self._add_supplementary_file( + part_name, + root_element.asset_information.default_thumbnail.path, + file_store, + ) if file_name: root_element.asset_information.default_thumbnail.path = file_name if isinstance(root_element, model.Submodel): @@ -296,12 +361,18 @@ def _collect_supplementary_files(self, part_name: str, if isinstance(element, model.File): if element.value is None: continue - final_name = self._add_supplementary_file(part_name, element.value, file_store) + final_name = self._add_supplementary_file( + part_name, element.value, file_store + ) if final_name: element.value = final_name - def _add_supplementary_file(self, part_name: str, file_path: str, - file_store: "AbstractSupplementaryFileContainer") -> Optional[str]: + def _add_supplementary_file( + self, + part_name: str, + file_path: str, + file_store: "AbstractSupplementaryFileContainer", + ) -> Optional[str]: """ Helper function to extract a single referenced supplementary file and return the absolute path within the AASX package. @@ -315,14 +386,20 @@ def _add_supplementary_file(self, part_name: str, file_path: str, # Only absolute-path references and relative-path URI references (see RFC 3986, sec. 4.2) are considered # to refer to files within the AASX package. Thus, we must skip all other types of URIs (esp. absolute # URIs and network-path references) - if file_path.startswith('//') or ':' in file_path.split('/')[0]: - logger.info(f"Skipping supplementary file {file_path}, since it seems to be an absolute URI or " - f"network-path URI reference") + if file_path.startswith("//") or ":" in file_path.split("/")[0]: + logger.info( + f"Skipping supplementary file {file_path}, since it seems to be an absolute URI or " + f"network-path URI reference" + ) return None absolute_name = pyecma376_2.package_model.part_realpath(file_path, part_name) - logger.debug(f"Reading supplementary file {absolute_name} from AASX package ...") + logger.debug( + f"Reading supplementary file {absolute_name} from AASX package ..." + ) with self.reader.open_part(absolute_name) as p: - final_name = file_store.add_file(absolute_name, p, self.reader.get_content_type(absolute_name)) + final_name = file_store.add_file( + absolute_name, p, self.reader.get_content_type(absolute_name) + ) return final_name @@ -354,6 +431,7 @@ class AASXWriter: functionality (as shown above). Otherwise, the resulting AASX file will lack important data structures and will not be readable. """ + AASX_ORIGIN_PART_NAME = "/aasx/aasx-origin" def __init__(self, file: Union[os.PathLike, str, IO], failsafe: bool = True): @@ -386,11 +464,13 @@ def __init__(self, file: Union[os.PathLike, str, IO], failsafe: bool = True): p = self.writer.open_part(self.AASX_ORIGIN_PART_NAME, "text/plain") p.close() - def write_aas(self, - aas_ids: Union[model.Identifier, Iterable[model.Identifier]], - object_store: model.AbstractObjectStore[model.Identifier, model.Identifiable], - file_store: "AbstractSupplementaryFileContainer", - write_json: bool = False) -> None: + def write_aas( + self, + aas_ids: Union[model.Identifier, Iterable[model.Identifier]], + object_store: model.AbstractObjectStore[model.Identifier, model.Identifiable], + file_store: "AbstractSupplementaryFileContainer", + write_json: bool = False, + ) -> None: """ Convenience method to write one or more :class:`AssetAdministrationShells ` with all included @@ -438,13 +518,17 @@ def write_aas(self, if isinstance(aas_ids, model.Identifier): aas_ids = (aas_ids,) - objects_to_be_written: model.DictIdentifiableStore[model.Identifiable] = model.DictIdentifiableStore() + objects_to_be_written: model.DictIdentifiableStore[model.Identifiable] = ( + model.DictIdentifiableStore() + ) for aas_id in aas_ids: try: aas = object_store.get_item(aas_id) if not isinstance(aas, model.AssetAdministrationShell): - raise TypeError(f"Identifier {aas_id} does not belong to an AssetAdministrationShell object but to " - f"{aas!r}") + raise TypeError( + f"Identifier {aas_id} does not belong to an AssetAdministrationShell object but to " + f"{aas!r}" + ) except (KeyError, TypeError) as e: if self.failsafe: logger.error(f"Skipping AAS {aas_id}: {e}") @@ -461,7 +545,9 @@ def write_aas(self, submodel = submodel_ref.resolve(object_store) except KeyError: if self.failsafe: - logger.warning(f"Could not find Submodel {submodel_ref}. Skipping it.") + logger.warning( + f"Could not find Submodel {submodel_ref}. Skipping it." + ) continue else: raise KeyError(f"Could not find Submodel {submodel_ref!r}") @@ -474,43 +560,59 @@ def write_aas(self, for semantic_id in traversal.walk_semantic_ids_recursive(identifiable): if isinstance(semantic_id, model.ExternalReference): continue - if not isinstance(semantic_id, model.ModelReference) \ - or semantic_id.type is not model.ConceptDescription: + if ( + not isinstance(semantic_id, model.ModelReference) + or semantic_id.type is not model.ConceptDescription + ): continue try: cd = semantic_id.resolve(object_store) except KeyError: if self.failsafe: - logger.warning(f"ConceptDescription for semanticId {semantic_id} not found in ObjectStore. " - f"Skipping it.") + logger.warning( + f"ConceptDescription for semanticId {semantic_id} not found in ObjectStore. " + f"Skipping it." + ) continue else: - raise KeyError(f"ConceptDescription for semanticId {semantic_id!r} not found in ObjectStore.") + raise KeyError( + f"ConceptDescription for semanticId {semantic_id!r} not found in ObjectStore." + ) except model.UnexpectedTypeError as e: if self.failsafe: - logger.error(f"semanticId {semantic_id} resolves to {e.value}, " - f"which is not a ConceptDescription. Skipping it.") + logger.error( + f"semanticId {semantic_id} resolves to {e.value}, " + f"which is not a ConceptDescription. Skipping it." + ) continue else: - raise TypeError(f"semanticId {semantic_id!r} resolves to {e.value!r}, which is not a" - f" ConceptDescription.") from e + raise TypeError( + f"semanticId {semantic_id!r} resolves to {e.value!r}, which is not a" + f" ConceptDescription." + ) from e concept_descriptions.append(cd) objects_to_be_written.update(concept_descriptions) # Write AAS data part - self.write_all_aas_objects("/aasx/data.{}".format("json" if write_json else "xml"), - objects_to_be_written, file_store, write_json) + self.write_all_aas_objects( + "/aasx/data.{}".format("json" if write_json else "xml"), + objects_to_be_written, + file_store, + write_json, + ) # TODO remove `method` parameter in future version. # Not actually required since you can always create a local dict - def write_aas_objects(self, - part_name: str, - object_ids: Iterable[model.Identifier], - object_store: model.AbstractObjectStore, - file_store: "AbstractSupplementaryFileContainer", - write_json: bool = False, - split_part: bool = False, - additional_relationships: Iterable[pyecma376_2.OPCRelationship] = ()) -> None: + def write_aas_objects( + self, + part_name: str, + object_ids: Iterable[model.Identifier], + object_store: model.AbstractObjectStore, + file_store: "AbstractSupplementaryFileContainer", + write_json: bool = False, + split_part: bool = False, + additional_relationships: Iterable[pyecma376_2.OPCRelationship] = (), + ) -> None: """ A thin wrapper around :meth:`write_all_aas_objects` to ensure backward compatibility @@ -546,7 +648,9 @@ def write_aas_objects(self, """ logger.debug(f"Writing AASX part {part_name} with AAS identifiables ...") - identifiables: model.DictIdentifiableStore[model.Identifiable] = model.DictIdentifiableStore() + identifiables: model.DictIdentifiableStore[model.Identifiable] = ( + model.DictIdentifiableStore() + ) # Retrieve identifiables and scan for referenced supplementary files for identifier in object_ids: @@ -554,25 +658,36 @@ def write_aas_objects(self, the_identifiable = object_store.get_item(identifier) except KeyError: if self.failsafe: - logger.error(f"Could not find identifiable {identifier} in IdentifiableStore") + logger.error( + f"Could not find identifiable {identifier} in IdentifiableStore" + ) continue else: - raise KeyError(f"Could not find identifiable {identifier!r} in IdentifiableStore") + raise KeyError( + f"Could not find identifiable {identifier!r} in IdentifiableStore" + ) identifiables.add(the_identifiable) self.write_all_aas_objects( - part_name, identifiables, file_store, write_json, split_part, additional_relationships + part_name, + identifiables, + file_store, + write_json, + split_part, + additional_relationships, ) # TODO remove `split_part` parameter in future version. # Not required anymore since changes from DotAAS version 2.0.1 to 3.0RC01 - def write_all_aas_objects(self, - part_name: str, - objects: model.AbstractObjectStore[model.Identifier, model.Identifiable], - file_store: "AbstractSupplementaryFileContainer", - write_json: bool = False, - split_part: bool = False, - additional_relationships: Iterable[pyecma376_2.OPCRelationship] = ()) -> None: + def write_all_aas_objects( + self, + part_name: str, + objects: model.AbstractObjectStore[model.Identifier, model.Identifiable], + file_store: "AbstractSupplementaryFileContainer", + write_json: bool = False, + split_part: bool = False, + additional_relationships: Iterable[pyecma376_2.OPCRelationship] = (), + ) -> None: """ Write all AAS objects in a given :class:`ObjectStore ` to an XML or JSON part in the AASX package and add the referenced supplementary files to the package. @@ -607,16 +722,24 @@ def write_all_aas_objects(self, def _collect_supplementary_file(file_name: str) -> None: # Skip File objects with empty value URI references that are considered to be no local file # (absolute URIs or network-path URI references) - if file_name is None or file_name.startswith('//') or ':' in file_name.split('/')[0]: + if ( + file_name is None + or file_name.startswith("//") + or ":" in file_name.split("/")[0] + ): return supplementary_files.append(file_name) # Retrieve objects and scan for referenced supplementary files for the_object in objects: if isinstance(the_object, model.AssetAdministrationShell): - if (the_object.asset_information.default_thumbnail and - the_object.asset_information.default_thumbnail.path): - _collect_supplementary_file(the_object.asset_information.default_thumbnail.path) + if ( + the_object.asset_information.default_thumbnail + and the_object.asset_information.default_thumbnail.path + ): + _collect_supplementary_file( + the_object.asset_information.default_thumbnail.path + ) if isinstance(the_object, model.Submodel): for element in traversal.walk_submodel(the_object): if isinstance(element, model.File): @@ -629,9 +752,11 @@ def _collect_supplementary_file(file_name: str) -> None: # Write part # TODO allow writing xml *and* JSON part - with self.writer.open_part(part_name, "application/json" if write_json else "application/xml") as p: + with self.writer.open_part( + part_name, "application/json" if write_json else "application/xml" + ) as p: if write_json: - write_aas_json_file(io.TextIOWrapper(p, encoding='utf-8'), objects) + write_aas_json_file(io.TextIOWrapper(p, encoding="utf-8"), objects) else: write_aas_xml_file(p, objects) @@ -652,28 +777,42 @@ def _collect_supplementary_file(file_name: str) -> None: continue elif file_name in self._supplementary_part_names: if self.failsafe: - logger.error(f"Trying to write supplementary file {file_name} to AASX " - f"twice with different contents") + logger.error( + f"Trying to write supplementary file {file_name} to AASX " + f"twice with different contents" + ) else: - raise ValueError(f"Trying to write supplementary file {file_name} to AASX twice with" - f" different contents") + raise ValueError( + f"Trying to write supplementary file {file_name} to AASX twice with" + f" different contents" + ) logger.debug(f"Writing supplementary file {file_name} to AASX package ...") with self.writer.open_part(file_name, content_type) as p: file_store.write_file(file_name, p) - supplementary_file_names.append(pyecma376_2.package_model.normalize_part_name(file_name)) + supplementary_file_names.append( + pyecma376_2.package_model.normalize_part_name(file_name) + ) self._supplementary_part_names[file_name] = hash # Add relationships from Submodel to supplementary parts - logger.debug(f"Writing aas-suppl relationships for AAS object part {part_name} to AASX package ...") + logger.debug( + f"Writing aas-suppl relationships for AAS object part {part_name} to AASX package ..." + ) self.writer.write_relationships( itertools.chain( - (pyecma376_2.OPCRelationship("r{}".format(i), - RELATIONSHIP_TYPE_AAS_SUPL, - submodel_file_name, - pyecma376_2.OPCTargetMode.INTERNAL) - for i, submodel_file_name in enumerate(supplementary_file_names)), - additional_relationships), - part_name) + ( + pyecma376_2.OPCRelationship( + "r{}".format(i), + RELATIONSHIP_TYPE_AAS_SUPL, + submodel_file_name, + pyecma376_2.OPCTargetMode.INTERNAL, + ) + for i, submodel_file_name in enumerate(supplementary_file_names) + ), + additional_relationships, + ), + part_name, + ) def write_core_properties(self, core_properties: pyecma376_2.OPCCoreProperties): """ @@ -687,7 +826,9 @@ def write_core_properties(self, core_properties: pyecma376_2.OPCCoreProperties): if self._properties_part is not None: raise RuntimeError("Core Properties have already been written.") logger.debug("Writing core properties to AASX package ...") - with self.writer.open_part(pyecma376_2.DEFAULT_CORE_PROPERTIES_NAME, "application/xml") as p: + with self.writer.open_part( + pyecma376_2.DEFAULT_CORE_PROPERTIES_NAME, "application/xml" + ) as p: core_properties.write_xml(p) self._properties_part = pyecma376_2.DEFAULT_CORE_PROPERTIES_NAME @@ -703,7 +844,9 @@ def write_thumbnail(self, name: str, data: bytearray, content_type: str): :param content_type: OPC content type (MIME type) of the image file """ if self._thumbnail_part is not None: - raise RuntimeError(f"package thumbnail has already been written to {self._thumbnail_part}.") + raise RuntimeError( + f"package thumbnail has already been written to {self._thumbnail_part}." + ) with self.writer.open_part(name, content_type) as p: p.write(data) self._thumbnail_part = name @@ -732,11 +875,17 @@ def _write_aasx_origin_relationships(self): # Add relationships from AASX-origin part to AAS parts logger.debug("Writing aas-spec relationships to AASX package ...") self.writer.write_relationships( - (pyecma376_2.OPCRelationship("r{}".format(i), RELATIONSHIP_TYPE_AAS_SPEC, - aas_part_name, - pyecma376_2.OPCTargetMode.INTERNAL) - for i, aas_part_name in enumerate(self._aas_part_names)), - self.AASX_ORIGIN_PART_NAME) + ( + pyecma376_2.OPCRelationship( + "r{}".format(i), + RELATIONSHIP_TYPE_AAS_SPEC, + aas_part_name, + pyecma376_2.OPCTargetMode.INTERNAL, + ) + for i, aas_part_name in enumerate(self._aas_part_names) + ), + self.AASX_ORIGIN_PART_NAME, + ) def _write_package_relationships(self): """ @@ -750,18 +899,31 @@ def _write_package_relationships(self): """ logger.debug("Writing package relationships to AASX package ...") package_relationships: List[pyecma376_2.OPCRelationship] = [ - pyecma376_2.OPCRelationship("r1", RELATIONSHIP_TYPE_AASX_ORIGIN, - self.AASX_ORIGIN_PART_NAME, - pyecma376_2.OPCTargetMode.INTERNAL), + pyecma376_2.OPCRelationship( + "r1", + RELATIONSHIP_TYPE_AASX_ORIGIN, + self.AASX_ORIGIN_PART_NAME, + pyecma376_2.OPCTargetMode.INTERNAL, + ), ] if self._properties_part is not None: - package_relationships.append(pyecma376_2.OPCRelationship( - "r2", pyecma376_2.RELATIONSHIP_TYPE_CORE_PROPERTIES, self._properties_part, - pyecma376_2.OPCTargetMode.INTERNAL)) + package_relationships.append( + pyecma376_2.OPCRelationship( + "r2", + pyecma376_2.RELATIONSHIP_TYPE_CORE_PROPERTIES, + self._properties_part, + pyecma376_2.OPCTargetMode.INTERNAL, + ) + ) if self._thumbnail_part is not None: - package_relationships.append(pyecma376_2.OPCRelationship( - "r3", pyecma376_2.RELATIONSHIP_TYPE_THUMBNAIL, self._thumbnail_part, - pyecma376_2.OPCTargetMode.INTERNAL)) + package_relationships.append( + pyecma376_2.OPCRelationship( + "r3", + pyecma376_2.RELATIONSHIP_TYPE_THUMBNAIL, + self._thumbnail_part, + pyecma376_2.OPCTargetMode.INTERNAL, + ) + ) self.writer.write_relationships(package_relationships) @@ -778,6 +940,7 @@ class AbstractSupplementaryFileContainer(metaclass=abc.ABCMeta): new file. It also provides each files sha256 hash sum to allow name conflict checking in other classes (e.g. when writing AASX files). """ + @abc.abstractmethod def add_file(self, name: str, file: IO[bytes], content_type: str) -> str: """ @@ -857,6 +1020,7 @@ class DictSupplementaryFileContainer(AbstractSupplementaryFileContainer): """ SupplementaryFileContainer implementation using a dict to store the file contents in-memory. """ + def __init__(self): # Stores the files' contents, identified by their sha256 hash self._store: Dict[bytes, bytes] = {} @@ -876,7 +1040,9 @@ def add_file(self, name: str, file: IO[bytes], content_type: str) -> str: def rename_file(self, old_name: str, new_name: str) -> str: if old_name not in self._name_map: - raise KeyError(f"File with name {old_name} not found in SupplementaryFileContainer.") + raise KeyError( + f"File with name {old_name} not found in SupplementaryFileContainer." + ) if new_name == old_name: return new_name file_hash, file_content_type = self._name_map[old_name] @@ -899,8 +1065,8 @@ def _assign_unique_name(self, name: str, sha: bytes, content_type: str) -> str: @staticmethod def _append_counter(name: str, i: int) -> str: - split1 = name.split('/') - split2 = split1[-1].split('.') + split1 = name.split("/") + split2 = split1[-1].split(".") index = -2 if len(split2) > 1 else -1 new_basename = "{}_{:04d}".format(split2[index], i) split2[index] = new_basename diff --git a/sdk/basyx/aas/adapter/json/__init__.py b/sdk/basyx/aas/adapter/json/__init__.py index 04b780566..3d2236431 100644 --- a/sdk/basyx/aas/adapter/json/__init__.py +++ b/sdk/basyx/aas/adapter/json/__init__.py @@ -17,6 +17,17 @@ :class:`ObjectStore `. """ -from .json_serialization import AASToJsonEncoder, StrippedAASToJsonEncoder, write_aas_json_file, object_store_to_json -from .json_deserialization import AASFromJsonDecoder, StrictAASFromJsonDecoder, StrippedAASFromJsonDecoder, \ - StrictStrippedAASFromJsonDecoder, read_aas_json_file, read_aas_json_file_into +from .json_serialization import ( + AASToJsonEncoder, + StrippedAASToJsonEncoder, + write_aas_json_file, + object_store_to_json, +) +from .json_deserialization import ( + AASFromJsonDecoder, + StrictAASFromJsonDecoder, + StrippedAASFromJsonDecoder, + StrictStrippedAASFromJsonDecoder, + read_aas_json_file, + read_aas_json_file_into, +) diff --git a/sdk/basyx/aas/adapter/json/json_deserialization.py b/sdk/basyx/aas/adapter/json/json_deserialization.py index e6fc9b448..ee83ad7b7 100644 --- a/sdk/basyx/aas/adapter/json/json_deserialization.py +++ b/sdk/basyx/aas/adapter/json/json_deserialization.py @@ -29,18 +29,45 @@ Embedded objects that should have a ``modelType`` themselves are expected to be converted already. Other embedded objects are converted using a number of helper constructor methods. """ + import base64 import contextlib import json import logging import pprint -from typing import (Dict, Callable, ContextManager, TypeVar, Type, - List, IO, Optional, Set, get_args, Tuple, Iterable, Any) +from typing import ( + Dict, + Callable, + ContextManager, + TypeVar, + Type, + List, + IO, + Optional, + Set, + get_args, + Tuple, + Iterable, + Any, +) from basyx.aas import model -from .._generic import MODELLING_KIND_INVERSE, ASSET_KIND_INVERSE, KEY_TYPES_INVERSE, ENTITY_TYPES_INVERSE, \ - IEC61360_DATA_TYPES_INVERSE, IEC61360_LEVEL_TYPES_INVERSE, KEY_TYPES_CLASSES_INVERSE, REFERENCE_TYPES_INVERSE, \ - DIRECTION_INVERSE, STATE_OF_EVENT_INVERSE, QUALIFIER_KIND_INVERSE, PathOrIO, Path, JSON_AAS_TOP_LEVEL_KEYS_TO_TYPES +from .._generic import ( + MODELLING_KIND_INVERSE, + ASSET_KIND_INVERSE, + KEY_TYPES_INVERSE, + ENTITY_TYPES_INVERSE, + IEC61360_DATA_TYPES_INVERSE, + IEC61360_LEVEL_TYPES_INVERSE, + KEY_TYPES_CLASSES_INVERSE, + REFERENCE_TYPES_INVERSE, + DIRECTION_INVERSE, + STATE_OF_EVENT_INVERSE, + QUALIFIER_KIND_INVERSE, + PathOrIO, + Path, + JSON_AAS_TOP_LEVEL_KEYS_TO_TYPES, +) logger = logging.getLogger(__name__) @@ -49,8 +76,8 @@ # Helper functions (for simplifying implementation of constructor functions) # ############################################################################# -T = TypeVar('T') -LSS = TypeVar('LSS', bound=model.LangStringSet) +T = TypeVar("T") +LSS = TypeVar("LSS", bound=model.LangStringSet) def _get_ts(dct: Dict[str, object], key: str, type_: Type[T]) -> T: @@ -68,7 +95,9 @@ def _get_ts(dct: Dict[str, object], key: str, type_: Type[T]) -> T: """ val = dct[key] if not isinstance(val, type_): - raise TypeError("Dict entry '{}' has unexpected type {}".format(key, type(val).__name__)) + raise TypeError( + "Dict entry '{}' has unexpected type {}".format(key, type(val).__name__) + ) return val @@ -95,9 +124,14 @@ def _expect_type(object_: object, type_: Type, context: str, failsafe: bool) -> if isinstance(object_, type_): return True if failsafe: - logger.error("Expected a %s in %s, but found %s", type_.__name__, context, repr(object_)) + logger.error( + "Expected a %s in %s, but found %s", type_.__name__, context, repr(object_) + ) else: - raise TypeError("Expected a %s in %s, but found %s" % (type_.__name__, context, repr(object_))) + raise TypeError( + "Expected a %s in %s, but found %s" + % (type_.__name__, context, repr(object_)) + ) return False @@ -148,6 +182,7 @@ def _construct_submodel(cls, dct, object_class=EnhancedSubmodel): Defaults to ``False``. See https://git.rwth-aachen.de/acplt/pyi40aas/-/issues/91 """ + failsafe = True stripped = False @@ -169,27 +204,27 @@ def _get_aas_class_parsers(cls) -> Dict[str, Callable[[Dict[str, object]], objec :return: The dictionary of AAS class parsers """ aas_class_parsers: Dict[str, Callable[[Dict[str, object]], object]] = { - 'AssetAdministrationShell': cls._construct_asset_administration_shell, - 'AssetInformation': cls._construct_asset_information, - 'SpecificAssetId': cls._construct_specific_asset_id, - 'ConceptDescription': cls._construct_concept_description, - 'Extension': cls._construct_extension, - 'Submodel': cls._construct_submodel, - 'Capability': cls._construct_capability, - 'Entity': cls._construct_entity, - 'BasicEventElement': cls._construct_basic_event_element, - 'Operation': cls._construct_operation, - 'RelationshipElement': cls._construct_relationship_element, - 'AnnotatedRelationshipElement': cls._construct_annotated_relationship_element, - 'SubmodelElementCollection': cls._construct_submodel_element_collection, - 'SubmodelElementList': cls._construct_submodel_element_list, - 'Blob': cls._construct_blob, - 'File': cls._construct_file, - 'MultiLanguageProperty': cls._construct_multi_language_property, - 'Property': cls._construct_property, - 'Range': cls._construct_range, - 'ReferenceElement': cls._construct_reference_element, - 'DataSpecificationIec61360': cls._construct_data_specification_iec61360, + "AssetAdministrationShell": cls._construct_asset_administration_shell, + "AssetInformation": cls._construct_asset_information, + "SpecificAssetId": cls._construct_specific_asset_id, + "ConceptDescription": cls._construct_concept_description, + "Extension": cls._construct_extension, + "Submodel": cls._construct_submodel, + "Capability": cls._construct_capability, + "Entity": cls._construct_entity, + "BasicEventElement": cls._construct_basic_event_element, + "Operation": cls._construct_operation, + "RelationshipElement": cls._construct_relationship_element, + "AnnotatedRelationshipElement": cls._construct_annotated_relationship_element, + "SubmodelElementCollection": cls._construct_submodel_element_collection, + "SubmodelElementList": cls._construct_submodel_element_list, + "Blob": cls._construct_blob, + "File": cls._construct_file, + "MultiLanguageProperty": cls._construct_multi_language_property, + "Property": cls._construct_property, + "Range": cls._construct_range, + "ReferenceElement": cls._construct_reference_element, + "DataSpecificationIec61360": cls._construct_data_specification_iec61360, } return aas_class_parsers @@ -197,31 +232,44 @@ def _get_aas_class_parsers(cls) -> Dict[str, Callable[[Dict[str, object]], objec def object_hook(cls, dct: Dict[str, object]) -> object: # Check if JSON object seems to be a deserializable AAS object (i.e. it has a modelType). Otherwise, the JSON # object is returned as is, so it's possible to mix AAS objects with other data within a JSON structure. - if 'modelType' not in dct: + if "modelType" not in dct: return dct AAS_CLASS_PARSERS = cls._get_aas_class_parsers() # Get modelType and constructor function - if not isinstance(dct['modelType'], str): - logger.warning("JSON object has unexpected format of modelType: %s", dct['modelType']) + if not isinstance(dct["modelType"], str): + logger.warning( + "JSON object has unexpected format of modelType: %s", dct["modelType"] + ) # Even in strict mode, we consider 'modelType' attributes of wrong type as non-AAS objects instead of # raising an exception. However, the object's type will probably checked later by read_json_aas_file() or # _expect_type() return dct - model_type = dct['modelType'] + model_type = dct["modelType"] if model_type not in AAS_CLASS_PARSERS: if not cls.failsafe: - raise TypeError("Found JSON object with modelType=\"%s\", which is not a known AAS class" % model_type) - logger.error("Found JSON object with modelType=\"%s\", which is not a known AAS class", model_type) + raise TypeError( + 'Found JSON object with modelType="%s", which is not a known AAS class' + % model_type + ) + logger.error( + 'Found JSON object with modelType="%s", which is not a known AAS class', + model_type, + ) return dct # Use constructor function to transform JSON representation into BaSyx Python SDK model object try: return AAS_CLASS_PARSERS[model_type](dct) except (KeyError, TypeError, model.AASConstraintViolation) as e: - error_message = "Error while trying to convert JSON object into {}: {} >>> {}".format( - model_type, e, pprint.pformat(dct, depth=2, width=2**14, compact=True)) + error_message = ( + "Error while trying to convert JSON object into {}: {} >>> {}".format( + model_type, + e, + pprint.pformat(dct, depth=2, width=2**14, compact=True), + ) + ) if cls.failsafe: logger.error(error_message, exc_info=e) # In failsafe mode, we return the raw JSON object dict, if there were errors while parsing an object, so @@ -229,7 +277,9 @@ def object_hook(cls, dct: Dict[str, object]) -> object: # constructors for complex objects will skip those items by using _expect_type(). return dct else: - raise (type(e) if isinstance(e, (KeyError, TypeError)) else TypeError)(error_message) from e + raise (type(e) if isinstance(e, (KeyError, TypeError)) else TypeError)( + error_message + ) from e # ################################################################################################## # Utility Methods used in constructor methods to add general attributes (from abstract base classes) @@ -245,48 +295,58 @@ def _amend_abstract_attributes(cls, obj: object, dct: Dict[str, object]) -> None :param dct: The object's dict representation from JSON """ if isinstance(obj, model.Referable): - if 'idShort' in dct: - obj.id_short = _get_ts(dct, 'idShort', str) - if 'category' in dct: - obj.category = _get_ts(dct, 'category', str) - if 'displayName' in dct: - obj.display_name = cls._construct_lang_string_set(_get_ts(dct, 'displayName', list), - model.MultiLanguageNameType) - if 'description' in dct: - obj.description = cls._construct_lang_string_set(_get_ts(dct, 'description', list), - model.MultiLanguageTextType) + if "idShort" in dct: + obj.id_short = _get_ts(dct, "idShort", str) + if "category" in dct: + obj.category = _get_ts(dct, "category", str) + if "displayName" in dct: + obj.display_name = cls._construct_lang_string_set( + _get_ts(dct, "displayName", list), model.MultiLanguageNameType + ) + if "description" in dct: + obj.description = cls._construct_lang_string_set( + _get_ts(dct, "description", list), model.MultiLanguageTextType + ) if isinstance(obj, model.Identifiable): - if 'administration' in dct: - obj.administration = cls._construct_administrative_information(_get_ts(dct, 'administration', dict)) + if "administration" in dct: + obj.administration = cls._construct_administrative_information( + _get_ts(dct, "administration", dict) + ) if isinstance(obj, model.HasSemantics): - if 'semanticId' in dct: - obj.semantic_id = cls._construct_reference(_get_ts(dct, 'semanticId', dict)) - if 'supplementalSemanticIds' in dct: - for ref in _get_ts(dct, 'supplementalSemanticIds', list): + if "semanticId" in dct: + obj.semantic_id = cls._construct_reference( + _get_ts(dct, "semanticId", dict) + ) + if "supplementalSemanticIds" in dct: + for ref in _get_ts(dct, "supplementalSemanticIds", list): obj.supplemental_semantic_id.append(cls._construct_reference(ref)) # `HasKind` provides only mandatory, immutable attributes; so we cannot do anything here, after object creation. # However, the `cls._get_kind()` function may assist by retrieving them from the JSON object if isinstance(obj, model.Qualifiable) and not cls.stripped: - if 'qualifiers' in dct: - for constraint_dct in _get_ts(dct, 'qualifiers', list): + if "qualifiers" in dct: + for constraint_dct in _get_ts(dct, "qualifiers", list): constraint = cls._construct_qualifier(constraint_dct) obj.qualifier.add(constraint) if isinstance(obj, model.HasDataSpecification) and not cls.stripped: - if 'embeddedDataSpecifications' in dct: - for dspec in _get_ts(dct, 'embeddedDataSpecifications', list): + if "embeddedDataSpecifications" in dct: + for dspec in _get_ts(dct, "embeddedDataSpecifications", list): obj.embedded_data_specifications.append( # TODO: remove the following type: ignore comment when mypy supports abstract types for Type[T] # see https://github.com/python/mypy/issues/5374 model.EmbeddedDataSpecification( data_specification=cls._construct_external_reference( - _get_ts(dspec, 'dataSpecification', dict)), - data_specification_content=_get_ts(dspec, 'dataSpecificationContent', - model.DataSpecificationContent) # type: ignore + _get_ts(dspec, "dataSpecification", dict) + ), + data_specification_content=_get_ts( + dspec, + "dataSpecificationContent", + model.DataSpecificationContent, + ), # type: ignore ) ) if isinstance(obj, model.HasExtension) and not cls.stripped: - if 'extensions' in dct: - for extension in _get_ts(dct, 'extensions', list): + if "extensions" in dct: + for extension in _get_ts(dct, "extensions", list): obj.extension.add(cls._construct_extension(extension)) @classmethod @@ -297,7 +357,11 @@ def _get_kind(cls, dct: Dict[str, object]) -> model.ModellingKind: :param dct: The object's dict representation from JSON :return: The object's ``kind`` value """ - return MODELLING_KIND_INVERSE[_get_ts(dct, "kind", str)] if 'kind' in dct else model.ModellingKind.INSTANCE + return ( + MODELLING_KIND_INVERSE[_get_ts(dct, "kind", str)] + if "kind" in dct + else model.ModellingKind.INSTANCE + ) # ############################################################################# # Helper Constructor Methods starting from here @@ -308,28 +372,43 @@ def _get_kind(cls, dct: Dict[str, object]) -> model.ModellingKind: # embedded JSON data into the expected type at their location in the outer JSON object. @classmethod - def _construct_key(cls, dct: Dict[str, object], object_class=model.Key) -> model.Key: - return object_class(type_=KEY_TYPES_INVERSE[_get_ts(dct, 'type', str)], - value=_get_ts(dct, 'value', str)) + def _construct_key( + cls, dct: Dict[str, object], object_class=model.Key + ) -> model.Key: + return object_class( + type_=KEY_TYPES_INVERSE[_get_ts(dct, "type", str)], + value=_get_ts(dct, "value", str), + ) @classmethod - def _construct_specific_asset_id(cls, dct: Dict[str, object], object_class=model.SpecificAssetId) \ - -> model.SpecificAssetId: + def _construct_specific_asset_id( + cls, dct: Dict[str, object], object_class=model.SpecificAssetId + ) -> model.SpecificAssetId: # semantic_id can't be applied by _amend_abstract_attributes because specificAssetId is immutable - return object_class(name=_get_ts(dct, 'name', str), - value=_get_ts(dct, 'value', str), - external_subject_id=cls._construct_external_reference( - _get_ts(dct, 'externalSubjectId', dict)) if 'externalSubjectId' in dct else None, - semantic_id=cls._construct_reference(_get_ts(dct, 'semanticId', dict)) - if 'semanticId' in dct else None, - supplemental_semantic_id=[ - cls._construct_reference(ref) for ref in - _get_ts(dct, 'supplementalSemanticIds', list)] - if 'supplementalSemanticIds' in dct else ()) + return object_class( + name=_get_ts(dct, "name", str), + value=_get_ts(dct, "value", str), + external_subject_id=cls._construct_external_reference( + _get_ts(dct, "externalSubjectId", dict) + ) + if "externalSubjectId" in dct + else None, + semantic_id=cls._construct_reference(_get_ts(dct, "semanticId", dict)) + if "semanticId" in dct + else None, + supplemental_semantic_id=[ + cls._construct_reference(ref) + for ref in _get_ts(dct, "supplementalSemanticIds", list) + ] + if "supplementalSemanticIds" in dct + else (), + ) @classmethod def _construct_reference(cls, dct: Dict[str, object]) -> model.Reference: - reference_type: Type[model.Reference] = REFERENCE_TYPES_INVERSE[_get_ts(dct, 'type', str)] + reference_type: Type[model.Reference] = REFERENCE_TYPES_INVERSE[ + _get_ts(dct, "type", str) + ] if reference_type is model.ModelReference: return cls._construct_model_reference(dct, model.Referable) # type: ignore elif reference_type is model.ExternalReference: @@ -337,71 +416,102 @@ def _construct_reference(cls, dct: Dict[str, object]) -> model.Reference: raise ValueError(f"Unsupported reference type {reference_type}!") @classmethod - def _construct_external_reference(cls, dct: Dict[str, object], object_class=model.ExternalReference)\ - -> model.ExternalReference: - reference_type: Type[model.Reference] = REFERENCE_TYPES_INVERSE[_get_ts(dct, 'type', str)] + def _construct_external_reference( + cls, dct: Dict[str, object], object_class=model.ExternalReference + ) -> model.ExternalReference: + reference_type: Type[model.Reference] = REFERENCE_TYPES_INVERSE[ + _get_ts(dct, "type", str) + ] if reference_type is not model.ExternalReference: - raise ValueError(f"Expected a reference of type {model.ExternalReference}, got {reference_type}!") + raise ValueError( + f"Expected a reference of type {model.ExternalReference}, got {reference_type}!" + ) keys = [cls._construct_key(key_data) for key_data in _get_ts(dct, "keys", list)] - return object_class(tuple(keys), cls._construct_reference(_get_ts(dct, 'referredSemanticId', dict)) - if 'referredSemanticId' in dct else None) + return object_class( + tuple(keys), + cls._construct_reference(_get_ts(dct, "referredSemanticId", dict)) + if "referredSemanticId" in dct + else None, + ) @classmethod - def _construct_model_reference(cls, dct: Dict[str, object], type_: Type[T], object_class=model.ModelReference)\ - -> model.ModelReference: - reference_type: Type[model.Reference] = REFERENCE_TYPES_INVERSE[_get_ts(dct, 'type', str)] + def _construct_model_reference( + cls, dct: Dict[str, object], type_: Type[T], object_class=model.ModelReference + ) -> model.ModelReference: + reference_type: Type[model.Reference] = REFERENCE_TYPES_INVERSE[ + _get_ts(dct, "type", str) + ] if reference_type is not model.ModelReference: - raise ValueError(f"Expected a reference of type {model.ModelReference}, got {reference_type}!") + raise ValueError( + f"Expected a reference of type {model.ModelReference}, got {reference_type}!" + ) keys = [cls._construct_key(key_data) for key_data in _get_ts(dct, "keys", list)] last_key_type = KEY_TYPES_CLASSES_INVERSE.get(keys[-1].type, type(None)) if keys and not issubclass(last_key_type, type_): - logger.warning("type %s of last key of reference to %s does not match reference type %s", - keys[-1].type.name, " / ".join(str(k) for k in keys), type_.__name__) + logger.warning( + "type %s of last key of reference to %s does not match reference type %s", + keys[-1].type.name, + " / ".join(str(k) for k in keys), + type_.__name__, + ) # Infer type the model refence points to using `last_key_type` instead of `type_`. # `type_` is often a `model.Referable`, which is more abstract than e.g. a `model.ConceptDescription`, # leading to information loss while deserializing. # TODO Remove this fix, when this function is called with correct `type_` - return object_class(tuple(keys), last_key_type, - cls._construct_reference(_get_ts(dct, 'referredSemanticId', dict)) - if 'referredSemanticId' in dct else None) + return object_class( + tuple(keys), + last_key_type, + cls._construct_reference(_get_ts(dct, "referredSemanticId", dict)) + if "referredSemanticId" in dct + else None, + ) @classmethod def _construct_administrative_information( - cls, dct: Dict[str, object], object_class=model.AdministrativeInformation)\ - -> model.AdministrativeInformation: + cls, dct: Dict[str, object], object_class=model.AdministrativeInformation + ) -> model.AdministrativeInformation: ret = object_class() cls._amend_abstract_attributes(ret, dct) - if 'version' in dct: - ret.version = _get_ts(dct, 'version', str) - if 'revision' in dct: - ret.revision = _get_ts(dct, 'revision', str) - elif 'revision' in dct: - logger.warning("Ignoring 'revision' attribute of AdministrativeInformation object due to missing 'version'") - if 'creator' in dct: - ret.creator = cls._construct_reference(_get_ts(dct, 'creator', dict)) - if 'templateId' in dct: - ret.template_id = _get_ts(dct, 'templateId', str) + if "version" in dct: + ret.version = _get_ts(dct, "version", str) + if "revision" in dct: + ret.revision = _get_ts(dct, "revision", str) + elif "revision" in dct: + logger.warning( + "Ignoring 'revision' attribute of AdministrativeInformation object due to missing 'version'" + ) + if "creator" in dct: + ret.creator = cls._construct_reference(_get_ts(dct, "creator", dict)) + if "templateId" in dct: + ret.template_id = _get_ts(dct, "templateId", str) return ret @classmethod - def _construct_operation_variable(cls, dct: Dict[str, object]) -> model.SubmodelElement: + def _construct_operation_variable( + cls, dct: Dict[str, object] + ) -> model.SubmodelElement: """ Since we don't implement ``OperationVariable``, this constructor discards the wrapping ``OperationVariable`` object and just returns the contained :class:`~basyx.aas.model.submodel.SubmodelElement`. """ # TODO: remove the following type: ignore comments when mypy supports abstract types for Type[T] # see https://github.com/python/mypy/issues/5374 - return _get_ts(dct, 'value', model.SubmodelElement) # type: ignore + return _get_ts(dct, "value", model.SubmodelElement) # type: ignore @classmethod - def _construct_lang_string_set(cls, lst: List[Dict[str, object]], object_class: Type[LSS]) -> LSS: + def _construct_lang_string_set( + cls, lst: List[Dict[str, object]], object_class: Type[LSS] + ) -> LSS: ret = {} for desc in lst: try: - ret[_get_ts(desc, 'language', str)] = _get_ts(desc, 'text', str) + ret[_get_ts(desc, "language", str)] = _get_ts(desc, "text", str) except (KeyError, TypeError) as e: error_message = "Error while trying to convert JSON object into {}: {} >>> {}".format( - object_class.__name__, e, pprint.pformat(desc, depth=2, width=2 ** 14, compact=True)) + object_class.__name__, + e, + pprint.pformat(desc, depth=2, width=2**14, compact=True), + ) if cls.failsafe: logger.error(error_message, exc_info=e) else: @@ -411,12 +521,13 @@ def _construct_lang_string_set(cls, lst: List[Dict[str, object]], object_class: @classmethod def _construct_value_list(cls, dct: Dict[str, object]) -> model.ValueList: ret: model.ValueList = set() - for element in _get_ts(dct, 'valueReferencePairs', list): + for element in _get_ts(dct, "valueReferencePairs", list): try: ret.add(cls._construct_value_reference_pair(element)) except (KeyError, TypeError) as e: error_message = "Error while trying to convert JSON object into ValueReferencePair: {} >>> {}".format( - e, pprint.pformat(element, depth=2, width=2 ** 14, compact=True)) + e, pprint.pformat(element, depth=2, width=2**14, compact=True) + ) if cls.failsafe: logger.error(error_message, exc_info=e) else: @@ -424,11 +535,15 @@ def _construct_value_list(cls, dct: Dict[str, object]) -> model.ValueList: return ret @classmethod - def _construct_value_reference_pair(cls, dct: Dict[str, object], - object_class=model.ValueReferencePair) -> model.ValueReferencePair: - return object_class(value=_get_ts(dct, 'value', str), - value_id=cls._construct_reference(_get_ts(dct, 'valueId', dict)) - if 'valueId' in dct else None) + def _construct_value_reference_pair( + cls, dct: Dict[str, object], object_class=model.ValueReferencePair + ) -> model.ValueReferencePair: + return object_class( + value=_get_ts(dct, "value", str), + value_id=cls._construct_reference(_get_ts(dct, "valueId", dict)) + if "valueId" in dct + else None, + ) # ############################################################################# # Direct Constructor Methods (for classes with `modelType`) starting from here @@ -438,193 +553,256 @@ def _construct_value_reference_pair(cls, dct: Dict[str, object], # be called from the object_hook() method directly. @classmethod - def _construct_asset_information(cls, dct: Dict[str, object], object_class=model.AssetInformation)\ - -> model.AssetInformation: + def _construct_asset_information( + cls, dct: Dict[str, object], object_class=model.AssetInformation + ) -> model.AssetInformation: global_asset_id = None - if 'globalAssetId' in dct: - global_asset_id = _get_ts(dct, 'globalAssetId', str) + if "globalAssetId" in dct: + global_asset_id = _get_ts(dct, "globalAssetId", str) specific_asset_id = set() - if 'specificAssetIds' in dct: + if "specificAssetIds" in dct: for desc_data in _get_ts(dct, "specificAssetIds", list): - 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) + 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: - ret.default_thumbnail = cls._construct_resource(_get_ts(dct, 'defaultThumbnail', dict)) + if "assetType" in dct: + ret.asset_type = _get_ts(dct, "assetType", str) + if "defaultThumbnail" in dct: + ret.default_thumbnail = cls._construct_resource( + _get_ts(dct, "defaultThumbnail", dict) + ) return ret @classmethod def _construct_asset_administration_shell( - cls, dct: Dict[str, object], object_class=model.AssetAdministrationShell) -> model.AssetAdministrationShell: + cls, dct: Dict[str, object], object_class=model.AssetAdministrationShell + ) -> model.AssetAdministrationShell: ret = object_class( - asset_information=cls._construct_asset_information(_get_ts(dct, 'assetInformation', dict), - model.AssetInformation), - id_=_get_ts(dct, 'id', str)) + asset_information=cls._construct_asset_information( + _get_ts(dct, "assetInformation", dict), model.AssetInformation + ), + id_=_get_ts(dct, "id", str), + ) cls._amend_abstract_attributes(ret, dct) - if not cls.stripped and 'submodels' in dct: - for sm_data in _get_ts(dct, 'submodels', list): - ret.submodel.add(cls._construct_model_reference(sm_data, model.Submodel)) - if 'derivedFrom' in dct: - ret.derived_from = cls._construct_model_reference(_get_ts(dct, 'derivedFrom', dict), - model.AssetAdministrationShell) + if not cls.stripped and "submodels" in dct: + for sm_data in _get_ts(dct, "submodels", list): + ret.submodel.add( + cls._construct_model_reference(sm_data, model.Submodel) + ) + if "derivedFrom" in dct: + ret.derived_from = cls._construct_model_reference( + _get_ts(dct, "derivedFrom", dict), model.AssetAdministrationShell + ) return ret @classmethod - def _construct_concept_description(cls, dct: Dict[str, object], object_class=model.ConceptDescription)\ - -> model.ConceptDescription: - ret = object_class(id_=_get_ts(dct, 'id', str)) + def _construct_concept_description( + cls, dct: Dict[str, object], object_class=model.ConceptDescription + ) -> model.ConceptDescription: + ret = object_class(id_=_get_ts(dct, "id", str)) cls._amend_abstract_attributes(ret, dct) - if 'isCaseOf' in dct: + if "isCaseOf" in dct: for case_data in _get_ts(dct, "isCaseOf", list): ret.is_case_of.add(cls._construct_reference(case_data)) return ret @classmethod - def _construct_data_specification_iec61360(cls, dct: Dict[str, object], - object_class=model.base.DataSpecificationIEC61360)\ - -> model.base.DataSpecificationIEC61360: - ret = object_class(preferred_name=cls._construct_lang_string_set(_get_ts(dct, 'preferredName', list), - model.PreferredNameTypeIEC61360)) - if 'dataType' in dct: - ret.data_type = IEC61360_DATA_TYPES_INVERSE[_get_ts(dct, 'dataType', str)] - if 'definition' in dct: - ret.definition = cls._construct_lang_string_set(_get_ts(dct, 'definition', list), - model.DefinitionTypeIEC61360) - if 'shortName' in dct: - ret.short_name = cls._construct_lang_string_set(_get_ts(dct, 'shortName', list), - model.ShortNameTypeIEC61360) - if 'unit' in dct: - ret.unit = _get_ts(dct, 'unit', str) - if 'unitId' in dct: - ret.unit_id = cls._construct_reference(_get_ts(dct, 'unitId', dict)) - if 'sourceOfDefinition' in dct: - ret.source_of_definition = _get_ts(dct, 'sourceOfDefinition', str) - if 'symbol' in dct: - ret.symbol = _get_ts(dct, 'symbol', str) - if 'valueFormat' in dct: - ret.value_format = _get_ts(dct, 'valueFormat', str) - if 'valueList' in dct: - ret.value_list = cls._construct_value_list(_get_ts(dct, 'valueList', dict)) - if 'value' in dct: - ret.value = _get_ts(dct, 'value', str) - if 'levelType' in dct: - for k, v in _get_ts(dct, 'levelType', dict).items(): + def _construct_data_specification_iec61360( + cls, dct: Dict[str, object], object_class=model.base.DataSpecificationIEC61360 + ) -> model.base.DataSpecificationIEC61360: + ret = object_class( + preferred_name=cls._construct_lang_string_set( + _get_ts(dct, "preferredName", list), model.PreferredNameTypeIEC61360 + ) + ) + if "dataType" in dct: + ret.data_type = IEC61360_DATA_TYPES_INVERSE[_get_ts(dct, "dataType", str)] + if "definition" in dct: + ret.definition = cls._construct_lang_string_set( + _get_ts(dct, "definition", list), model.DefinitionTypeIEC61360 + ) + if "shortName" in dct: + ret.short_name = cls._construct_lang_string_set( + _get_ts(dct, "shortName", list), model.ShortNameTypeIEC61360 + ) + if "unit" in dct: + ret.unit = _get_ts(dct, "unit", str) + if "unitId" in dct: + ret.unit_id = cls._construct_reference(_get_ts(dct, "unitId", dict)) + if "sourceOfDefinition" in dct: + ret.source_of_definition = _get_ts(dct, "sourceOfDefinition", str) + if "symbol" in dct: + ret.symbol = _get_ts(dct, "symbol", str) + if "valueFormat" in dct: + ret.value_format = _get_ts(dct, "valueFormat", str) + if "valueList" in dct: + ret.value_list = cls._construct_value_list(_get_ts(dct, "valueList", dict)) + if "value" in dct: + ret.value = _get_ts(dct, "value", str) + if "levelType" in dct: + for k, v in _get_ts(dct, "levelType", dict).items(): if v: ret.level_types.add(IEC61360_LEVEL_TYPES_INVERSE[k]) return ret @classmethod - def _construct_entity(cls, dct: Dict[str, object], object_class=model.Entity) -> model.Entity: + def _construct_entity( + cls, dct: Dict[str, object], object_class=model.Entity + ) -> model.Entity: global_asset_id = None - if 'globalAssetId' in dct: - global_asset_id = _get_ts(dct, 'globalAssetId', str) + if "globalAssetId" in dct: + global_asset_id = _get_ts(dct, "globalAssetId", str) specific_asset_id = set() - if 'specificAssetIds' in dct: + if "specificAssetIds" in dct: for desc_data in _get_ts(dct, "specificAssetIds", list): - specific_asset_id.add(cls._construct_specific_asset_id(desc_data, model.SpecificAssetId)) - if 'entityType' in dct: - entity_type = ENTITY_TYPES_INVERSE[_get_ts(dct, 'entityType', str)] + specific_asset_id.add( + cls._construct_specific_asset_id(desc_data, model.SpecificAssetId) + ) + if "entityType" in dct: + entity_type = ENTITY_TYPES_INVERSE[_get_ts(dct, "entityType", str)] else: entity_type = None - ret = object_class(id_short=None, - entity_type=entity_type, - global_asset_id=global_asset_id, - specific_asset_id=specific_asset_id) + ret = object_class( + id_short=None, + entity_type=entity_type, + global_asset_id=global_asset_id, + specific_asset_id=specific_asset_id, + ) cls._amend_abstract_attributes(ret, dct) - if not cls.stripped and 'statements' in dct: + if not cls.stripped and "statements" in dct: for element in _get_ts(dct, "statements", list): if _expect_type(element, model.SubmodelElement, str(ret), cls.failsafe): ret.statement.add(element) return ret @classmethod - def _construct_qualifier(cls, dct: Dict[str, object], object_class=model.Qualifier) -> model.Qualifier: - ret = object_class(type_=_get_ts(dct, 'type', str), - value_type=model.datatypes.XSD_TYPE_CLASSES[_get_ts(dct, 'valueType', str)]) + def _construct_qualifier( + cls, dct: Dict[str, object], object_class=model.Qualifier + ) -> model.Qualifier: + ret = object_class( + type_=_get_ts(dct, "type", str), + value_type=model.datatypes.XSD_TYPE_CLASSES[_get_ts(dct, "valueType", str)], + ) cls._amend_abstract_attributes(ret, dct) - if 'value' in dct: - ret.value = model.datatypes.from_xsd(_get_ts(dct, 'value', str), ret.value_type) - if 'valueId' in dct: - ret.value_id = cls._construct_reference(_get_ts(dct, 'valueId', dict)) - if 'kind' in dct: - ret.kind = QUALIFIER_KIND_INVERSE[_get_ts(dct, 'kind', str)] + if "value" in dct: + ret.value = model.datatypes.from_xsd( + _get_ts(dct, "value", str), ret.value_type + ) + if "valueId" in dct: + ret.value_id = cls._construct_reference(_get_ts(dct, "valueId", dict)) + if "kind" in dct: + ret.kind = QUALIFIER_KIND_INVERSE[_get_ts(dct, "kind", str)] return ret @classmethod - def _construct_extension(cls, dct: Dict[str, object], object_class=model.Extension) -> model.Extension: - ret = object_class(name=_get_ts(dct, 'name', str)) + def _construct_extension( + cls, dct: Dict[str, object], object_class=model.Extension + ) -> model.Extension: + ret = object_class(name=_get_ts(dct, "name", str)) cls._amend_abstract_attributes(ret, dct) - if 'valueType' in dct: - ret.value_type = model.datatypes.XSD_TYPE_CLASSES[_get_ts(dct, 'valueType', str)] - if 'value' in dct: - ret.value = model.datatypes.from_xsd(_get_ts(dct, 'value', str), ret.value_type) - if 'refersTo' in dct: - ret.refers_to = {cls._construct_model_reference(refers_to, model.Referable) # type: ignore - for refers_to in _get_ts(dct, 'refersTo', list)} + if "valueType" in dct: + ret.value_type = model.datatypes.XSD_TYPE_CLASSES[ + _get_ts(dct, "valueType", str) + ] + if "value" in dct: + ret.value = model.datatypes.from_xsd( + _get_ts(dct, "value", str), ret.value_type + ) + if "refersTo" in dct: + ret.refers_to = { + cls._construct_model_reference(refers_to, model.Referable) # type: ignore + for refers_to in _get_ts(dct, "refersTo", list) + } return ret @classmethod - def _construct_submodel(cls, dct: Dict[str, object], object_class=model.Submodel) -> model.Submodel: - ret = object_class(id_=_get_ts(dct, 'id', str), - kind=cls._get_kind(dct)) + def _construct_submodel( + cls, dct: Dict[str, object], object_class=model.Submodel + ) -> model.Submodel: + ret = object_class(id_=_get_ts(dct, "id", str), kind=cls._get_kind(dct)) cls._amend_abstract_attributes(ret, dct) - if not cls.stripped and 'submodelElements' in dct: + if not cls.stripped and "submodelElements" in dct: for element in _get_ts(dct, "submodelElements", list): if _expect_type(element, model.SubmodelElement, str(ret), cls.failsafe): ret.submodel_element.add(element) return ret @classmethod - def _construct_capability(cls, dct: Dict[str, object], object_class=model.Capability) -> model.Capability: + def _construct_capability( + cls, dct: Dict[str, object], object_class=model.Capability + ) -> model.Capability: ret = object_class(id_short=None) cls._amend_abstract_attributes(ret, dct) return ret @classmethod - def _construct_basic_event_element(cls, dct: Dict[str, object], object_class=model.BasicEventElement) \ - -> model.BasicEventElement: + def _construct_basic_event_element( + cls, dct: Dict[str, object], object_class=model.BasicEventElement + ) -> model.BasicEventElement: # TODO: remove the following type: ignore comments when mypy supports abstract types for Type[T] # see https://github.com/python/mypy/issues/5374 - ret = object_class(id_short=None, - observed=cls._construct_model_reference(_get_ts(dct, 'observed', dict), - model.Referable), # type: ignore - direction=DIRECTION_INVERSE[_get_ts(dct, "direction", str)], - state=STATE_OF_EVENT_INVERSE[_get_ts(dct, "state", str)]) + ret = object_class( + id_short=None, + observed=cls._construct_model_reference( + _get_ts(dct, "observed", dict), model.Referable + ), # type: ignore + direction=DIRECTION_INVERSE[_get_ts(dct, "direction", str)], + state=STATE_OF_EVENT_INVERSE[_get_ts(dct, "state", str)], + ) cls._amend_abstract_attributes(ret, dct) - if 'messageTopic' in dct: - ret.message_topic = _get_ts(dct, 'messageTopic', str) - if 'messageBroker' in dct: - ret.message_broker = cls._construct_reference(_get_ts(dct, 'messageBroker', dict)) - if 'lastUpdate' in dct: - ret.last_update = model.datatypes.from_xsd(_get_ts(dct, 'lastUpdate', str), model.datatypes.DateTime) - if 'minInterval' in dct: - ret.min_interval = model.datatypes.from_xsd(_get_ts(dct, 'minInterval', str), model.datatypes.Duration) - if 'maxInterval' in dct: - ret.max_interval = model.datatypes.from_xsd(_get_ts(dct, 'maxInterval', str), model.datatypes.Duration) + if "messageTopic" in dct: + ret.message_topic = _get_ts(dct, "messageTopic", str) + if "messageBroker" in dct: + ret.message_broker = cls._construct_reference( + _get_ts(dct, "messageBroker", dict) + ) + if "lastUpdate" in dct: + ret.last_update = model.datatypes.from_xsd( + _get_ts(dct, "lastUpdate", str), model.datatypes.DateTime + ) + if "minInterval" in dct: + ret.min_interval = model.datatypes.from_xsd( + _get_ts(dct, "minInterval", str), model.datatypes.Duration + ) + if "maxInterval" in dct: + ret.max_interval = model.datatypes.from_xsd( + _get_ts(dct, "maxInterval", str), model.datatypes.Duration + ) return ret @classmethod - def _construct_operation(cls, dct: Dict[str, object], object_class=model.Operation) -> model.Operation: + def _construct_operation( + cls, dct: Dict[str, object], object_class=model.Operation + ) -> model.Operation: ret = object_class(None) cls._amend_abstract_attributes(ret, dct) # Deserialize variables (they are not Referable, thus we don't - for json_name, target in (('inputVariables', ret.input_variable), - ('outputVariables', ret.output_variable), - ('inoutputVariables', ret.in_output_variable)): + for json_name, target in ( + ("inputVariables", ret.input_variable), + ("outputVariables", ret.output_variable), + ("inoutputVariables", ret.in_output_variable), + ): if json_name in dct: for variable_data in _get_ts(dct, json_name, list): try: target.add(cls._construct_operation_variable(variable_data)) except (KeyError, TypeError) as e: error_message = "Error while trying to convert JSON object into {} of {}: {}".format( - json_name, ret, pprint.pformat(variable_data, depth=2, width=2 ** 14, compact=True)) + json_name, + ret, + pprint.pformat( + variable_data, depth=2, width=2**14, compact=True + ), + ) if cls.failsafe: logger.error(error_message, exc_info=e) else: @@ -633,136 +811,191 @@ def _construct_operation(cls, dct: Dict[str, object], object_class=model.Operati @classmethod def _construct_relationship_element( - cls, dct: Dict[str, object], object_class=model.RelationshipElement) -> model.RelationshipElement: - ret = object_class(id_short=None, - first=cls._construct_reference(_get_ts(dct, 'first', dict)) if 'first' in dct else None, - second=cls._construct_reference(_get_ts(dct, 'second', dict)) if 'second' in dct else None) + cls, dct: Dict[str, object], object_class=model.RelationshipElement + ) -> model.RelationshipElement: + ret = object_class( + id_short=None, + first=cls._construct_reference(_get_ts(dct, "first", dict)) + if "first" in dct + else None, + second=cls._construct_reference(_get_ts(dct, "second", dict)) + if "second" in dct + else None, + ) cls._amend_abstract_attributes(ret, dct) return ret @classmethod def _construct_annotated_relationship_element( - cls, dct: Dict[str, object], object_class=model.AnnotatedRelationshipElement)\ - -> model.AnnotatedRelationshipElement: + cls, dct: Dict[str, object], object_class=model.AnnotatedRelationshipElement + ) -> model.AnnotatedRelationshipElement: ret = object_class( id_short=None, - first=cls._construct_reference(_get_ts(dct, 'first', dict)) if 'first' in dct else None, - second=cls._construct_reference(_get_ts(dct, 'second', dict)) if 'second' in dct else None) + first=cls._construct_reference(_get_ts(dct, "first", dict)) + if "first" in dct + else None, + second=cls._construct_reference(_get_ts(dct, "second", dict)) + if "second" in dct + else None, + ) cls._amend_abstract_attributes(ret, dct) - if not cls.stripped and 'annotations' in dct: - for element in _get_ts(dct, 'annotations', list): + if not cls.stripped and "annotations" in dct: + for element in _get_ts(dct, "annotations", list): if _expect_type(element, model.DataElement, str(ret), cls.failsafe): ret.annotation.add(element) return ret @classmethod - def _construct_submodel_element_collection(cls, dct: Dict[str, object], - object_class=model.SubmodelElementCollection)\ - -> model.SubmodelElementCollection: + def _construct_submodel_element_collection( + cls, dct: Dict[str, object], object_class=model.SubmodelElementCollection + ) -> model.SubmodelElementCollection: ret = object_class(id_short=None) cls._amend_abstract_attributes(ret, dct) - if not cls.stripped and 'value' in dct: + if not cls.stripped and "value" in dct: for element in _get_ts(dct, "value", list): if _expect_type(element, model.SubmodelElement, str(ret), cls.failsafe): ret.value.add(element) return ret @classmethod - def _construct_submodel_element_list(cls, dct: Dict[str, object], object_class=model.SubmodelElementList)\ - -> model.SubmodelElementList: + def _construct_submodel_element_list( + cls, dct: Dict[str, object], object_class=model.SubmodelElementList + ) -> model.SubmodelElementList: type_value_list_element = KEY_TYPES_CLASSES_INVERSE[ - KEY_TYPES_INVERSE[_get_ts(dct, 'typeValueListElement', str)]] + KEY_TYPES_INVERSE[_get_ts(dct, "typeValueListElement", str)] + ] if not issubclass(type_value_list_element, model.SubmodelElement): - raise ValueError("Expected a SubmodelElementList with a typeValueListElement that is a subclass of" - f"{model.SubmodelElement}, got {type_value_list_element}!") - order_relevant = _get_ts(dct, 'orderRelevant', bool) if 'orderRelevant' in dct else True - semantic_id_list_element = cls._construct_reference(_get_ts(dct, 'semanticIdListElement', dict))\ - if 'semanticIdListElement' in dct else None - value_type_list_element = model.datatypes.XSD_TYPE_CLASSES[_get_ts(dct, 'valueTypeListElement', str)]\ - if 'valueTypeListElement' in dct else None - ret = object_class(id_short=None, - type_value_list_element=type_value_list_element, - order_relevant=order_relevant, - semantic_id_list_element=semantic_id_list_element, - value_type_list_element=value_type_list_element) + raise ValueError( + "Expected a SubmodelElementList with a typeValueListElement that is a subclass of" + f"{model.SubmodelElement}, got {type_value_list_element}!" + ) + order_relevant = ( + _get_ts(dct, "orderRelevant", bool) if "orderRelevant" in dct else True + ) + semantic_id_list_element = ( + cls._construct_reference(_get_ts(dct, "semanticIdListElement", dict)) + if "semanticIdListElement" in dct + else None + ) + value_type_list_element = ( + model.datatypes.XSD_TYPE_CLASSES[_get_ts(dct, "valueTypeListElement", str)] + if "valueTypeListElement" in dct + else None + ) + ret = object_class( + id_short=None, + type_value_list_element=type_value_list_element, + order_relevant=order_relevant, + semantic_id_list_element=semantic_id_list_element, + value_type_list_element=value_type_list_element, + ) cls._amend_abstract_attributes(ret, dct) - if not cls.stripped and 'value' in dct: - for element in _get_ts(dct, 'value', list): - if _expect_type(element, type_value_list_element, str(ret), cls.failsafe): + if not cls.stripped and "value" in dct: + for element in _get_ts(dct, "value", list): + if _expect_type( + element, type_value_list_element, str(ret), cls.failsafe + ): ret.value.add(element) return ret @classmethod - def _construct_blob(cls, dct: Dict[str, object], object_class=model.Blob) -> model.Blob: + def _construct_blob( + cls, dct: Dict[str, object], object_class=model.Blob + ) -> model.Blob: ret = object_class( id_short=None, - content_type=_get_ts(dct, "contentType", str) if 'contentType' in dct else None + content_type=_get_ts(dct, "contentType", str) + if "contentType" in dct + else None, ) cls._amend_abstract_attributes(ret, dct) - if 'value' in dct: - ret.value = base64.b64decode(_get_ts(dct, 'value', str)) + if "value" in dct: + ret.value = base64.b64decode(_get_ts(dct, "value", str)) return ret @classmethod - def _construct_file(cls, dct: Dict[str, object], object_class=model.File) -> model.File: - content_type = _get_ts(dct, "contentType", str) if 'contentType' in dct else None - ret = object_class(id_short=None, - value=None, - content_type=_get_ts(dct, "contentType", str) if 'contentType' in dct else None) + def _construct_file( + cls, dct: Dict[str, object], object_class=model.File + ) -> model.File: + content_type = ( + _get_ts(dct, "contentType", str) if "contentType" in dct else None + ) + ret = object_class( + id_short=None, + value=None, + content_type=_get_ts(dct, "contentType", str) + if "contentType" in dct + else None, + ) cls._amend_abstract_attributes(ret, dct) - if 'value' in dct and dct['value'] is not None: - ret.value = _get_ts(dct, 'value', str) + if "value" in dct and dct["value"] is not None: + ret.value = _get_ts(dct, "value", str) return ret @classmethod - def _construct_resource(cls, dct: Dict[str, object], object_class=model.Resource) -> model.Resource: + def _construct_resource( + cls, dct: Dict[str, object], object_class=model.Resource + ) -> model.Resource: ret = object_class(path=_get_ts(dct, "path", str)) cls._amend_abstract_attributes(ret, dct) - if 'contentType' in dct and dct['contentType'] is not None: - ret.content_type = _get_ts(dct, 'contentType', str) + if "contentType" in dct and dct["contentType"] is not None: + ret.content_type = _get_ts(dct, "contentType", str) return ret @classmethod def _construct_multi_language_property( - cls, dct: Dict[str, object], object_class=model.MultiLanguageProperty) -> model.MultiLanguageProperty: + cls, dct: Dict[str, object], object_class=model.MultiLanguageProperty + ) -> model.MultiLanguageProperty: ret = object_class(id_short=None) cls._amend_abstract_attributes(ret, dct) - if 'value' in dct and dct['value'] is not None: - ret.value = cls._construct_lang_string_set(_get_ts(dct, 'value', list), model.MultiLanguageTextType) - if 'valueId' in dct: - ret.value_id = cls._construct_reference(_get_ts(dct, 'valueId', dict)) + if "value" in dct and dct["value"] is not None: + ret.value = cls._construct_lang_string_set( + _get_ts(dct, "value", list), model.MultiLanguageTextType + ) + if "valueId" in dct: + ret.value_id = cls._construct_reference(_get_ts(dct, "valueId", dict)) return ret @classmethod - def _construct_property(cls, dct: Dict[str, object], object_class=model.Property) -> model.Property: - ret = object_class(id_short=None, - value_type=model.datatypes.XSD_TYPE_CLASSES[_get_ts(dct, 'valueType', str)],) + def _construct_property( + cls, dct: Dict[str, object], object_class=model.Property + ) -> model.Property: + ret = object_class( + id_short=None, + value_type=model.datatypes.XSD_TYPE_CLASSES[_get_ts(dct, "valueType", str)], + ) cls._amend_abstract_attributes(ret, dct) - if 'value' in dct and dct['value'] is not None: - ret.value = model.datatypes.from_xsd(_get_ts(dct, 'value', str), ret.value_type) - if 'valueId' in dct: - ret.value_id = cls._construct_reference(_get_ts(dct, 'valueId', dict)) + if "value" in dct and dct["value"] is not None: + ret.value = model.datatypes.from_xsd( + _get_ts(dct, "value", str), ret.value_type + ) + if "valueId" in dct: + ret.value_id = cls._construct_reference(_get_ts(dct, "valueId", dict)) return ret @classmethod - def _construct_range(cls, dct: Dict[str, object], object_class=model.Range) -> model.Range: - ret = object_class(id_short=None, - value_type=model.datatypes.XSD_TYPE_CLASSES[_get_ts(dct, 'valueType', str)],) + def _construct_range( + cls, dct: Dict[str, object], object_class=model.Range + ) -> model.Range: + ret = object_class( + id_short=None, + value_type=model.datatypes.XSD_TYPE_CLASSES[_get_ts(dct, "valueType", str)], + ) cls._amend_abstract_attributes(ret, dct) - if 'min' in dct and dct['min'] is not None: - ret.min = model.datatypes.from_xsd(_get_ts(dct, 'min', str), ret.value_type) - if 'max' in dct and dct['max'] is not None: - ret.max = model.datatypes.from_xsd(_get_ts(dct, 'max', str), ret.value_type) + if "min" in dct and dct["min"] is not None: + ret.min = model.datatypes.from_xsd(_get_ts(dct, "min", str), ret.value_type) + if "max" in dct and dct["max"] is not None: + ret.max = model.datatypes.from_xsd(_get_ts(dct, "max", str), ret.value_type) return ret @classmethod def _construct_reference_element( - cls, dct: Dict[str, object], object_class=model.ReferenceElement) -> model.ReferenceElement: - ret = object_class(id_short=None, - value=None) + cls, dct: Dict[str, object], object_class=model.ReferenceElement + ) -> model.ReferenceElement: + ret = object_class(id_short=None, value=None) cls._amend_abstract_attributes(ret, dct) - if 'value' in dct and dct['value'] is not None: - ret.value = cls._construct_reference(_get_ts(dct, 'value', dict)) + if "value" in dct and dct["value"] is not None: + ret.value = cls._construct_reference(_get_ts(dct, "value", dict)) return ret @@ -774,6 +1007,7 @@ class StrictAASFromJsonDecoder(AASFromJsonDecoder): This version has set ``failsafe = False``, which will lead to Exceptions raised for every missing attribute or wrong object type. """ + failsafe = False @@ -781,18 +1015,23 @@ class StrippedAASFromJsonDecoder(AASFromJsonDecoder): """ Decoder for stripped JSON objects. Used in the HTTP adapter. """ + stripped = True -class StrictStrippedAASFromJsonDecoder(StrictAASFromJsonDecoder, StrippedAASFromJsonDecoder): +class StrictStrippedAASFromJsonDecoder( + StrictAASFromJsonDecoder, StrippedAASFromJsonDecoder +): """ Non-failsafe decoder for stripped JSON objects. """ + pass -def _select_decoder(failsafe: bool, stripped: bool, decoder: Optional[Type[AASFromJsonDecoder]]) \ - -> Type[AASFromJsonDecoder]: +def _select_decoder( + failsafe: bool, stripped: bool, decoder: Optional[Type[AASFromJsonDecoder]] +) -> Type[AASFromJsonDecoder]: """ Returns the correct decoder based on the parameters failsafe and stripped. If a decoder class is given, failsafe and stripped are ignored. @@ -815,11 +1054,16 @@ def _select_decoder(failsafe: bool, stripped: bool, decoder: Optional[Type[AASFr return StrictAASFromJsonDecoder -def read_aas_json_file_into(object_store: model.AbstractObjectStore, file: PathOrIO, replace_existing: bool = False, - ignore_existing: bool = False, failsafe: bool = True, stripped: bool = False, - decoder: Optional[Type[AASFromJsonDecoder]] = None, - keys_to_types: Iterable[Tuple[str, Any]] = JSON_AAS_TOP_LEVEL_KEYS_TO_TYPES) \ - -> Set[model.Identifier]: +def read_aas_json_file_into( + object_store: model.AbstractObjectStore, + file: PathOrIO, + replace_existing: bool = False, + ignore_existing: bool = False, + failsafe: bool = True, + stripped: bool = False, + decoder: Optional[Type[AASFromJsonDecoder]] = None, + keys_to_types: Iterable[Tuple[str, Any]] = JSON_AAS_TOP_LEVEL_KEYS_TO_TYPES, +) -> Set[model.Identifier]: """ Read an Asset Administration Shell JSON file according to 'Details of the Asset Administration Shell', chapter 5.5 into a given ObjectStore. @@ -876,7 +1120,9 @@ def read_aas_json_file_into(object_store: model.AbstractObjectStore, file: PathO if not isinstance(item, expected_type): if not decoder_.failsafe: raise TypeError(f"{item} was in the wrong list '{name}'") - logger.warning(f"{item} was in the wrong list '{name}'; nevertheless, we'll use it") + logger.warning( + f"{item} was in the wrong list '{name}'; nevertheless, we'll use it" + ) if item.id in ret: error_msg = f"{item} has a duplicate identifier already parsed in the document!" @@ -904,7 +1150,9 @@ def read_aas_json_file_into(object_store: model.AbstractObjectStore, file: PathO return ret -def read_aas_json_file(file: PathOrIO, failsafe: bool = True, **kwargs) -> model.DictIdentifiableStore: +def read_aas_json_file( + file: PathOrIO, failsafe: bool = True, **kwargs +) -> model.DictIdentifiableStore: """ A wrapper of :meth:`~basyx.aas.adapter.json.json_deserialization.read_aas_json_file_into`, that reads all objects in an empty :class:`~basyx.aas.model.provider.DictIdentifiableStore`. This function supports the same keyword diff --git a/sdk/basyx/aas/adapter/json/json_serialization.py b/sdk/basyx/aas/adapter/json/json_serialization.py index d981f2283..e1034d36b 100644 --- a/sdk/basyx/aas/adapter/json/json_serialization.py +++ b/sdk/basyx/aas/adapter/json/json_serialization.py @@ -26,11 +26,23 @@ later on. The special helper function ``_abstract_classes_to_json`` is called by most of the conversion functions to handle all the attributes of abstract base classes. """ + import base64 import contextlib import inspect import io -from typing import ContextManager, List, Dict, Optional, TextIO, Type, Callable, get_args, Iterable, Tuple +from typing import ( + ContextManager, + List, + Dict, + Optional, + TextIO, + Type, + Callable, + get_args, + Iterable, + Tuple, +) import json from basyx.aas import model @@ -56,6 +68,7 @@ class AASToJsonEncoder(json.JSONEncoder): Defaults to ``False``. See https://git.rwth-aachen.de/acplt/pyi40aas/-/issues/91 """ + stripped = False @classmethod @@ -117,45 +130,50 @@ def _abstract_classes_to_json(cls, obj: object) -> Dict[str, object]: data: Dict[str, object] = {} if isinstance(obj, model.HasExtension) and not cls.stripped: if obj.extension: - data['extensions'] = list(obj.extension) + data["extensions"] = list(obj.extension) if isinstance(obj, model.HasDataSpecification) and not cls.stripped: if obj.embedded_data_specifications: - data['embeddedDataSpecifications'] = [ - {'dataSpecification': spec.data_specification, - 'dataSpecificationContent': spec.data_specification_content} + data["embeddedDataSpecifications"] = [ + { + "dataSpecification": spec.data_specification, + "dataSpecificationContent": spec.data_specification_content, + } for spec in obj.embedded_data_specifications ] if isinstance(obj, model.Referable): if obj.id_short and not isinstance(obj.parent, model.SubmodelElementList): - data['idShort'] = obj.id_short + data["idShort"] = obj.id_short if obj.display_name: - data['displayName'] = obj.display_name + data["displayName"] = obj.display_name if obj.category: - data['category'] = obj.category + data["category"] = obj.category if obj.description: - data['description'] = obj.description + data["description"] = obj.description try: ref_type = model.resolve_referable_class_in_key_types(obj) except StopIteration as e: - raise TypeError("Object of type {} is Referable but does not inherit from a known AAS type" - .format(obj.__class__.__name__)) from e - data['modelType'] = ref_type.__name__ + raise TypeError( + "Object of type {} is Referable but does not inherit from a known AAS type".format( + obj.__class__.__name__ + ) + ) from e + data["modelType"] = ref_type.__name__ if isinstance(obj, model.Identifiable): - data['id'] = obj.id + data["id"] = obj.id if obj.administration: - data['administration'] = obj.administration + data["administration"] = obj.administration if isinstance(obj, model.HasSemantics): if obj.semantic_id: - data['semanticId'] = obj.semantic_id + data["semanticId"] = obj.semantic_id if obj.supplemental_semantic_id: - data['supplementalSemanticIds'] = list(obj.supplemental_semantic_id) + data["supplementalSemanticIds"] = list(obj.supplemental_semantic_id) if isinstance(obj, model.HasKind): if obj.kind is model.ModellingKind.TEMPLATE: - data['kind'] = _generic.MODELLING_KIND[obj.kind] + data["kind"] = _generic.MODELLING_KIND[obj.kind] if isinstance(obj, model.Qualifiable) and not cls.stripped: if obj.qualifier: - data['qualifiers'] = list(obj.qualifier) + data["qualifiers"] = list(obj.qualifier) return data # ############################################################# @@ -163,9 +181,10 @@ def _abstract_classes_to_json(cls, obj: object) -> Dict[str, object]: # ############################################################# @classmethod - def _lang_string_set_to_json(cls, obj: model.LangStringSet) -> List[Dict[str, object]]: - return [{'language': k, 'text': v} - for k, v in obj.items()] + def _lang_string_set_to_json( + cls, obj: model.LangStringSet + ) -> List[Dict[str, object]]: + return [{"language": k, "text": v} for k, v in obj.items()] @classmethod def _key_to_json(cls, obj: model.Key) -> Dict[str, object]: @@ -176,12 +195,13 @@ def _key_to_json(cls, obj: model.Key) -> Dict[str, object]: :return: dict with the serialized attributes of this object """ data = cls._abstract_classes_to_json(obj) - data.update({'type': _generic.KEY_TYPES[obj.type], - 'value': obj.value}) + data.update({"type": _generic.KEY_TYPES[obj.type], "value": obj.value}) return data @classmethod - def _administrative_information_to_json(cls, obj: model.AdministrativeInformation) -> Dict[str, object]: + def _administrative_information_to_json( + cls, obj: model.AdministrativeInformation + ) -> Dict[str, object]: """ serialization of an object from class AdministrativeInformation to json @@ -190,13 +210,13 @@ def _administrative_information_to_json(cls, obj: model.AdministrativeInformatio """ data = cls._abstract_classes_to_json(obj) if obj.version: - data['version'] = obj.version + data["version"] = obj.version if obj.revision: - data['revision'] = obj.revision + data["revision"] = obj.revision if obj.creator: - data['creator'] = obj.creator + data["creator"] = obj.creator if obj.template_id: - data['templateId'] = obj.template_id + data["templateId"] = obj.template_id return data @classmethod @@ -208,10 +228,12 @@ def _reference_to_json(cls, obj: model.Reference) -> Dict[str, object]: :return: dict with the serialized attributes of this object """ data = cls._abstract_classes_to_json(obj) - data['type'] = _generic.REFERENCE_TYPES[obj.__class__] - data['keys'] = list(obj.key) + data["type"] = _generic.REFERENCE_TYPES[obj.__class__] + data["keys"] = list(obj.key) if obj.referred_semantic_id is not None: - data['referredSemanticId'] = cls._reference_to_json(obj.referred_semantic_id) + data["referredSemanticId"] = cls._reference_to_json( + obj.referred_semantic_id + ) return data @classmethod @@ -235,14 +257,16 @@ def _qualifier_to_json(cls, obj: model.Qualifier) -> Dict[str, object]: """ data = cls._abstract_classes_to_json(obj) if obj.value: - data['value'] = model.datatypes.xsd_repr(obj.value) if obj.value is not None else None + data["value"] = ( + model.datatypes.xsd_repr(obj.value) if obj.value is not None else None + ) if obj.value_id: - data['valueId'] = obj.value_id + data["valueId"] = obj.value_id # Even though kind is optional in the schema, it's better to always serialize it instead of specifying # the default value in multiple locations. - data['kind'] = _generic.QUALIFIER_KIND[obj.kind] - data['valueType'] = model.datatypes.XSD_TYPE_NAMES[obj.value_type] - data['type'] = obj.type + data["kind"] = _generic.QUALIFIER_KIND[obj.kind] + data["valueType"] = model.datatypes.XSD_TYPE_NAMES[obj.value_type] + data["type"] = obj.type return data @classmethod @@ -255,16 +279,20 @@ def _extension_to_json(cls, obj: model.Extension) -> Dict[str, object]: """ data = cls._abstract_classes_to_json(obj) if obj.value: - data['value'] = model.datatypes.xsd_repr(obj.value) if obj.value is not None else None + data["value"] = ( + model.datatypes.xsd_repr(obj.value) if obj.value is not None else None + ) if obj.refers_to: - data['refersTo'] = list(obj.refers_to) + data["refersTo"] = list(obj.refers_to) if obj.value_type: - data['valueType'] = model.datatypes.XSD_TYPE_NAMES[obj.value_type] - data['name'] = obj.name + data["valueType"] = model.datatypes.XSD_TYPE_NAMES[obj.value_type] + data["name"] = obj.name return data @classmethod - def _value_reference_pair_to_json(cls, obj: model.ValueReferencePair) -> Dict[str, object]: + def _value_reference_pair_to_json( + cls, obj: model.ValueReferencePair + ) -> Dict[str, object]: """ serialization of an object from class ValueReferencePair to json @@ -272,8 +300,9 @@ def _value_reference_pair_to_json(cls, obj: model.ValueReferencePair) -> Dict[st :return: dict with the serialized attributes of this object """ data = cls._abstract_classes_to_json(obj) - data.update({'value': model.datatypes.xsd_repr(obj.value), - 'valueId': obj.value_id}) + data.update( + {"value": model.datatypes.xsd_repr(obj.value), "valueId": obj.value_id} + ) return data @classmethod @@ -284,14 +313,16 @@ def _value_list_to_json(cls, obj: model.ValueList) -> Dict[str, object]: :param obj: object of class ValueList :return: dict with the serialized attributes of this object """ - return {'valueReferencePairs': list(obj)} + return {"valueReferencePairs": list(obj)} # ############################################################ # transformation functions to serialize classes from model.aas # ############################################################ @classmethod - def _specific_asset_id_to_json(cls, obj: model.SpecificAssetId) -> Dict[str, object]: + def _specific_asset_id_to_json( + cls, obj: model.SpecificAssetId + ) -> Dict[str, object]: """ serialization of an object from class SpecificAssetId to json @@ -299,14 +330,16 @@ def _specific_asset_id_to_json(cls, obj: model.SpecificAssetId) -> Dict[str, obj :return: dict with the serialized attributes of this object """ data = cls._abstract_classes_to_json(obj) - data['name'] = obj.name - data['value'] = obj.value + data["name"] = obj.name + data["value"] = obj.value if obj.external_subject_id: - data['externalSubjectId'] = obj.external_subject_id + data["externalSubjectId"] = obj.external_subject_id return data @classmethod - def _asset_information_to_json(cls, obj: model.AssetInformation) -> Dict[str, object]: + def _asset_information_to_json( + cls, obj: model.AssetInformation + ) -> Dict[str, object]: """ serialization of an object from class AssetInformation to json @@ -314,19 +347,21 @@ def _asset_information_to_json(cls, obj: model.AssetInformation) -> Dict[str, ob :return: dict with the serialized attributes of this object """ data = cls._abstract_classes_to_json(obj) - data['assetKind'] = _generic.ASSET_KIND[obj.asset_kind] + data["assetKind"] = _generic.ASSET_KIND[obj.asset_kind] if obj.global_asset_id: - data['globalAssetId'] = obj.global_asset_id + data["globalAssetId"] = obj.global_asset_id if obj.specific_asset_id: - data['specificAssetIds'] = list(obj.specific_asset_id) + data["specificAssetIds"] = list(obj.specific_asset_id) if obj.asset_type: - data['assetType'] = obj.asset_type + data["assetType"] = obj.asset_type if obj.default_thumbnail: - data['defaultThumbnail'] = obj.default_thumbnail + data["defaultThumbnail"] = obj.default_thumbnail return data @classmethod - def _concept_description_to_json(cls, obj: model.ConceptDescription) -> Dict[str, object]: + def _concept_description_to_json( + cls, obj: model.ConceptDescription + ) -> Dict[str, object]: """ serialization of an object from class ConceptDescription to json @@ -335,12 +370,13 @@ def _concept_description_to_json(cls, obj: model.ConceptDescription) -> Dict[str """ data = cls._abstract_classes_to_json(obj) if obj.is_case_of: - data['isCaseOf'] = list(obj.is_case_of) + data["isCaseOf"] = list(obj.is_case_of) return data @classmethod def _data_specification_iec61360_to_json( - cls, obj: model.base.DataSpecificationIEC61360) -> Dict[str, object]: + cls, obj: model.base.DataSpecificationIEC61360 + ) -> Dict[str, object]: """ serialization of an object from class DataSpecificationIEC61360 to json @@ -348,35 +384,40 @@ def _data_specification_iec61360_to_json( :return: dict with the serialized attributes of this object """ data_spec: Dict[str, object] = { - 'modelType': 'DataSpecificationIec61360', - 'preferredName': obj.preferred_name + "modelType": "DataSpecificationIec61360", + "preferredName": obj.preferred_name, } if obj.data_type is not None: - data_spec['dataType'] = _generic.IEC61360_DATA_TYPES[obj.data_type] + data_spec["dataType"] = _generic.IEC61360_DATA_TYPES[obj.data_type] if obj.definition is not None: - data_spec['definition'] = obj.definition + data_spec["definition"] = obj.definition if obj.short_name is not None: - data_spec['shortName'] = obj.short_name + data_spec["shortName"] = obj.short_name if obj.unit is not None: - data_spec['unit'] = obj.unit + data_spec["unit"] = obj.unit if obj.unit_id is not None: - data_spec['unitId'] = obj.unit_id + data_spec["unitId"] = obj.unit_id if obj.source_of_definition is not None: - data_spec['sourceOfDefinition'] = obj.source_of_definition + data_spec["sourceOfDefinition"] = obj.source_of_definition if obj.symbol is not None: - data_spec['symbol'] = obj.symbol + data_spec["symbol"] = obj.symbol if obj.value_format is not None: - data_spec['valueFormat'] = obj.value_format + data_spec["valueFormat"] = obj.value_format if obj.value_list is not None: - data_spec['valueList'] = cls._value_list_to_json(obj.value_list) + data_spec["valueList"] = cls._value_list_to_json(obj.value_list) if obj.value is not None: - data_spec['value'] = obj.value + data_spec["value"] = obj.value if obj.level_types: - data_spec['levelType'] = {v: k in obj.level_types for k, v in _generic.IEC61360_LEVEL_TYPES.items()} + data_spec["levelType"] = { + v: k in obj.level_types + for k, v in _generic.IEC61360_LEVEL_TYPES.items() + } return data_spec @classmethod - def _asset_administration_shell_to_json(cls, obj: model.AssetAdministrationShell) -> Dict[str, object]: + def _asset_administration_shell_to_json( + cls, obj: model.AssetAdministrationShell + ) -> Dict[str, object]: """ serialization of an object from class AssetAdministrationShell to json @@ -398,7 +439,9 @@ def _asset_administration_shell_to_json(cls, obj: model.AssetAdministrationShell # ################################################################# @classmethod - def _submodel_to_json(cls, obj: model.Submodel) -> Dict[str, object]: # TODO make kind optional + def _submodel_to_json( + cls, obj: model.Submodel + ) -> Dict[str, object]: # TODO make kind optional """ serialization of an object from class Submodel to json @@ -407,11 +450,13 @@ def _submodel_to_json(cls, obj: model.Submodel) -> Dict[str, object]: # TODO ma """ data = cls._abstract_classes_to_json(obj) if not cls.stripped and obj.submodel_element != set(): - data['submodelElements'] = list(obj.submodel_element) + data["submodelElements"] = list(obj.submodel_element) return data @classmethod - def _data_element_to_json(cls, obj: model.DataElement) -> Dict[str, object]: # no attributes in specification yet + def _data_element_to_json( + cls, obj: model.DataElement + ) -> Dict[str, object]: # no attributes in specification yet """ serialization of an object from class DataElement to json @@ -430,14 +475,16 @@ def _property_to_json(cls, obj: model.Property) -> Dict[str, object]: """ data = cls._abstract_classes_to_json(obj) if obj.value is not None: - data['value'] = model.datatypes.xsd_repr(obj.value) + data["value"] = model.datatypes.xsd_repr(obj.value) if obj.value_id: - data['valueId'] = obj.value_id - data['valueType'] = model.datatypes.XSD_TYPE_NAMES[obj.value_type] + data["valueId"] = obj.value_id + data["valueType"] = model.datatypes.XSD_TYPE_NAMES[obj.value_type] return data @classmethod - def _multi_language_property_to_json(cls, obj: model.MultiLanguageProperty) -> Dict[str, object]: + def _multi_language_property_to_json( + cls, obj: model.MultiLanguageProperty + ) -> Dict[str, object]: """ serialization of an object from class MultiLanguageProperty to json @@ -446,9 +493,9 @@ def _multi_language_property_to_json(cls, obj: model.MultiLanguageProperty) -> D """ data = cls._abstract_classes_to_json(obj) if obj.value: - data['value'] = obj.value + data["value"] = obj.value if obj.value_id: - data['valueId'] = obj.value_id + data["valueId"] = obj.value_id return data @classmethod @@ -460,11 +507,11 @@ def _range_to_json(cls, obj: model.Range) -> Dict[str, object]: :return: dict with the serialized attributes of this object """ data = cls._abstract_classes_to_json(obj) - data['valueType'] = model.datatypes.XSD_TYPE_NAMES[obj.value_type] + data["valueType"] = model.datatypes.XSD_TYPE_NAMES[obj.value_type] if obj.min is not None: - data['min'] = model.datatypes.xsd_repr(obj.min) + data["min"] = model.datatypes.xsd_repr(obj.min) if obj.max is not None: - data['max'] = model.datatypes.xsd_repr(obj.max) + data["max"] = model.datatypes.xsd_repr(obj.max) return data @classmethod @@ -477,9 +524,9 @@ def _blob_to_json(cls, obj: model.Blob) -> Dict[str, object]: """ data = cls._abstract_classes_to_json(obj) if obj.content_type is not None: - data['contentType'] = obj.content_type + data["contentType"] = obj.content_type if obj.value is not None: - data['value'] = base64.b64encode(obj.value).decode() + data["value"] = base64.b64encode(obj.value).decode() return data @classmethod @@ -492,9 +539,9 @@ def _file_to_json(cls, obj: model.File) -> Dict[str, object]: """ data = cls._abstract_classes_to_json(obj) if obj.content_type is not None: - data['contentType'] = obj.content_type + data["contentType"] = obj.content_type if obj.value is not None: - data['value'] = obj.value + data["value"] = obj.value return data @classmethod @@ -506,13 +553,15 @@ def _resource_to_json(cls, obj: model.Resource) -> Dict[str, object]: :return: dict with the serialized attributes of this object """ data = cls._abstract_classes_to_json(obj) - data['path'] = obj.path + data["path"] = obj.path if obj.content_type is not None: - data['contentType'] = obj.content_type + data["contentType"] = obj.content_type return data @classmethod - def _reference_element_to_json(cls, obj: model.ReferenceElement) -> Dict[str, object]: + def _reference_element_to_json( + cls, obj: model.ReferenceElement + ) -> Dict[str, object]: """ serialization of an object from class Reference to json @@ -521,11 +570,13 @@ def _reference_element_to_json(cls, obj: model.ReferenceElement) -> Dict[str, ob """ data = cls._abstract_classes_to_json(obj) if obj.value: - data['value'] = obj.value + data["value"] = obj.value return data @classmethod - def _submodel_element_collection_to_json(cls, obj: model.SubmodelElementCollection) -> Dict[str, object]: + def _submodel_element_collection_to_json( + cls, obj: model.SubmodelElementCollection + ) -> Dict[str, object]: """ serialization of an object from class SubmodelElementCollection to json @@ -534,11 +585,13 @@ def _submodel_element_collection_to_json(cls, obj: model.SubmodelElementCollecti """ data = cls._abstract_classes_to_json(obj) if not cls.stripped and len(obj.value) > 0: - data['value'] = list(obj.value) + data["value"] = list(obj.value) return data @classmethod - def _submodel_element_list_to_json(cls, obj: model.SubmodelElementList) -> Dict[str, object]: + def _submodel_element_list_to_json( + cls, obj: model.SubmodelElementList + ) -> Dict[str, object]: """ serialization of an object from class SubmodelElementList to json @@ -548,18 +601,24 @@ def _submodel_element_list_to_json(cls, obj: model.SubmodelElementList) -> Dict[ data = cls._abstract_classes_to_json(obj) # Even though orderRelevant is optional in the schema, it's better to always serialize it instead of specifying # the default value in multiple locations. - data['orderRelevant'] = obj.order_relevant - data['typeValueListElement'] = _generic.KEY_TYPES[model.KEY_TYPES_CLASSES[obj.type_value_list_element]] + data["orderRelevant"] = obj.order_relevant + data["typeValueListElement"] = _generic.KEY_TYPES[ + model.KEY_TYPES_CLASSES[obj.type_value_list_element] + ] if obj.semantic_id_list_element is not None: - data['semanticIdListElement'] = obj.semantic_id_list_element + data["semanticIdListElement"] = obj.semantic_id_list_element if obj.value_type_list_element is not None: - data['valueTypeListElement'] = model.datatypes.XSD_TYPE_NAMES[obj.value_type_list_element] + data["valueTypeListElement"] = model.datatypes.XSD_TYPE_NAMES[ + obj.value_type_list_element + ] if not cls.stripped and len(obj.value) > 0: - data['value'] = list(obj.value) + data["value"] = list(obj.value) return data @classmethod - def _relationship_element_to_json(cls, obj: model.RelationshipElement) -> Dict[str, object]: + def _relationship_element_to_json( + cls, obj: model.RelationshipElement + ) -> Dict[str, object]: """ serialization of an object from class RelationshipElement to json @@ -568,13 +627,15 @@ def _relationship_element_to_json(cls, obj: model.RelationshipElement) -> Dict[s """ data = cls._abstract_classes_to_json(obj) if obj.first is not None: - data.update({'first': obj.first}) + data.update({"first": obj.first}) if obj.second is not None: - data.update({'second': obj.second}) + data.update({"second": obj.second}) return data @classmethod - def _annotated_relationship_element_to_json(cls, obj: model.AnnotatedRelationshipElement) -> Dict[str, object]: + def _annotated_relationship_element_to_json( + cls, obj: model.AnnotatedRelationshipElement + ) -> Dict[str, object]: """ serialization of an object from class AnnotatedRelationshipElement to json @@ -583,11 +644,13 @@ def _annotated_relationship_element_to_json(cls, obj: model.AnnotatedRelationshi """ data = cls._relationship_element_to_json(obj) if not cls.stripped and obj.annotation: - data['annotations'] = list(obj.annotation) + data["annotations"] = list(obj.annotation) return data @classmethod - def _operation_variable_to_json(cls, obj: model.SubmodelElement) -> Dict[str, object]: + def _operation_variable_to_json( + cls, obj: model.SubmodelElement + ) -> Dict[str, object]: """ serialization of an object from class SubmodelElement to a json OperationVariable representation Since we don't implement the ``OperationVariable`` class, which is just a wrapper for a single @@ -598,7 +661,7 @@ def _operation_variable_to_json(cls, obj: model.SubmodelElement) -> Dict[str, ob :return: ``OperationVariable`` wrapper containing the serialized :class:`~basyx.aas.model.submodel.SubmodelElement` """ - return {'value': obj} + return {"value": obj} @classmethod def _operation_to_json(cls, obj: model.Operation) -> Dict[str, object]: @@ -609,9 +672,11 @@ def _operation_to_json(cls, obj: model.Operation) -> Dict[str, object]: :return: dict with the serialized attributes of this object """ data = cls._abstract_classes_to_json(obj) - for tag, nss in (('inputVariables', obj.input_variable), - ('outputVariables', obj.output_variable), - ('inoutputVariables', obj.in_output_variable)): + for tag, nss in ( + ("inputVariables", obj.input_variable), + ("outputVariables", obj.output_variable), + ("inoutputVariables", obj.in_output_variable), + ): if nss: data[tag] = [cls._operation_variable_to_json(obj) for obj in nss] return data @@ -638,17 +703,19 @@ def _entity_to_json(cls, obj: model.Entity) -> Dict[str, object]: """ data = cls._abstract_classes_to_json(obj) if not cls.stripped and obj.statement: - data['statements'] = list(obj.statement) + data["statements"] = list(obj.statement) if obj.entity_type is not None: - data['entityType'] = _generic.ENTITY_TYPES[obj.entity_type] + data["entityType"] = _generic.ENTITY_TYPES[obj.entity_type] if obj.global_asset_id is not None: - data['globalAssetId'] = obj.global_asset_id + data["globalAssetId"] = obj.global_asset_id if obj.specific_asset_id is not None: - data['specificAssetIds'] = list(obj.specific_asset_id) + data["specificAssetIds"] = list(obj.specific_asset_id) return data @classmethod - def _event_element_to_json(cls, obj: model.EventElement) -> Dict[str, object]: # no attributes in specification yet + def _event_element_to_json( + cls, obj: model.EventElement + ) -> Dict[str, object]: # no attributes in specification yet """ serialization of an object from class EventElement to json @@ -658,7 +725,9 @@ def _event_element_to_json(cls, obj: model.EventElement) -> Dict[str, object]: return {} @classmethod - def _basic_event_element_to_json(cls, obj: model.BasicEventElement) -> Dict[str, object]: + def _basic_event_element_to_json( + cls, obj: model.BasicEventElement + ) -> Dict[str, object]: """ serialization of an object from class BasicEventElement to json @@ -666,19 +735,19 @@ def _basic_event_element_to_json(cls, obj: model.BasicEventElement) -> Dict[str, :return: dict with the serialized attributes of this object """ data = cls._abstract_classes_to_json(obj) - data['observed'] = obj.observed - data['direction'] = _generic.DIRECTION[obj.direction] - data['state'] = _generic.STATE_OF_EVENT[obj.state] + data["observed"] = obj.observed + data["direction"] = _generic.DIRECTION[obj.direction] + data["state"] = _generic.STATE_OF_EVENT[obj.state] if obj.message_topic is not None: - data['messageTopic'] = obj.message_topic + data["messageTopic"] = obj.message_topic if obj.message_broker is not None: - data['messageBroker'] = cls._reference_to_json(obj.message_broker) + data["messageBroker"] = cls._reference_to_json(obj.message_broker) if obj.last_update is not None: - data['lastUpdate'] = model.datatypes.xsd_repr(obj.last_update) + data["lastUpdate"] = model.datatypes.xsd_repr(obj.last_update) if obj.min_interval is not None: - data['minInterval'] = model.datatypes.xsd_repr(obj.min_interval) + data["minInterval"] = model.datatypes.xsd_repr(obj.min_interval) if obj.max_interval is not None: - data['maxInterval'] = model.datatypes.xsd_repr(obj.max_interval) + data["maxInterval"] = model.datatypes.xsd_repr(obj.max_interval) return data @@ -687,10 +756,13 @@ class StrippedAASToJsonEncoder(AASToJsonEncoder): AASToJsonEncoder for stripped objects. Used in the HTTP API. See https://git.rwth-aachen.de/acplt/pyi40aas/-/issues/91 """ + stripped = True -def _select_encoder(stripped: bool, encoder: Optional[Type[AASToJsonEncoder]] = None) -> Type[AASToJsonEncoder]: +def _select_encoder( + stripped: bool, encoder: Optional[Type[AASToJsonEncoder]] = None +) -> Type[AASToJsonEncoder]: """ Returns the correct encoder based on the stripped parameter. If an encoder class is given, stripped is ignored. @@ -704,9 +776,10 @@ def _select_encoder(stripped: bool, encoder: Optional[Type[AASToJsonEncoder]] = return AASToJsonEncoder if not stripped else StrippedAASToJsonEncoder -def _create_dict(data: model.AbstractObjectStore, - keys_to_types: Iterable[Tuple[str, Type]] = JSON_AAS_TOP_LEVEL_KEYS_TO_TYPES) \ - -> Dict[str, List[model.Identifiable]]: +def _create_dict( + data: model.AbstractObjectStore, + keys_to_types: Iterable[Tuple[str, Type]] = JSON_AAS_TOP_LEVEL_KEYS_TO_TYPES, +) -> Dict[str, List[model.Identifiable]]: """ Categorizes objects from an AbstractObjectStore into a dictionary based on their types. @@ -734,8 +807,12 @@ def _create_dict(data: model.AbstractObjectStore, return objects -def object_store_to_json(data: model.AbstractObjectStore, stripped: bool = False, - encoder: Optional[Type[AASToJsonEncoder]] = None, **kwargs) -> str: +def object_store_to_json( + data: model.AbstractObjectStore, + stripped: bool = False, + encoder: Optional[Type[AASToJsonEncoder]] = None, + **kwargs, +) -> str: """ Create a json serialization of a set of AAS objects according to 'Details of the Asset Administration Shell', chapter 5.5 @@ -757,12 +834,18 @@ class _DetachingTextIOWrapper(io.TextIOWrapper): """ Like :class:`io.TextIOWrapper`, but detaches on context exit instead of closing the wrapped buffer. """ + def __exit__(self, exc_type, exc_val, exc_tb): self.detach() -def write_aas_json_file(file: _generic.PathOrIO, data: model.AbstractObjectStore, stripped: bool = False, - encoder: Optional[Type[AASToJsonEncoder]] = None, **kwargs) -> None: +def write_aas_json_file( + file: _generic.PathOrIO, + data: model.AbstractObjectStore, + stripped: bool = False, + encoder: Optional[Type[AASToJsonEncoder]] = None, + **kwargs, +) -> None: """ Write a set of AAS objects to an Asset Administration Shell JSON file according to 'Details of the Asset Administration Shell', chapter 5.5 diff --git a/sdk/basyx/aas/adapter/xml/__init__.py b/sdk/basyx/aas/adapter/xml/__init__.py index aa08288a0..15ce534bc 100644 --- a/sdk/basyx/aas/adapter/xml/__init__.py +++ b/sdk/basyx/aas/adapter/xml/__init__.py @@ -10,7 +10,19 @@ :class:`ObjectStore ` from a given xml document. """ -from .xml_serialization import object_store_to_xml_element, write_aas_xml_file, object_to_xml_element, \ - write_aas_xml_element -from .xml_deserialization import AASFromXmlDecoder, StrictAASFromXmlDecoder, StrippedAASFromXmlDecoder, \ - StrictStrippedAASFromXmlDecoder, XMLConstructables, read_aas_xml_file, read_aas_xml_file_into, read_aas_xml_element +from .xml_serialization import ( + object_store_to_xml_element, + write_aas_xml_file, + object_to_xml_element, + write_aas_xml_element, +) +from .xml_deserialization import ( + AASFromXmlDecoder, + StrictAASFromXmlDecoder, + StrippedAASFromXmlDecoder, + StrictStrippedAASFromXmlDecoder, + XMLConstructables, + read_aas_xml_file, + read_aas_xml_file_into, + read_aas_xml_element, +) diff --git a/sdk/basyx/aas/adapter/xml/xml_deserialization.py b/sdk/basyx/aas/adapter/xml/xml_deserialization.py index 6bfb67bce..f50297936 100644 --- a/sdk/basyx/aas/adapter/xml/xml_deserialization.py +++ b/sdk/basyx/aas/adapter/xml/xml_deserialization.py @@ -48,9 +48,22 @@ import enum from typing import Any, Callable, Dict, Iterable, Optional, Set, Tuple, Type, TypeVar -from .._generic import XML_NS_MAP, XML_NS_AAS, MODELLING_KIND_INVERSE, ASSET_KIND_INVERSE, KEY_TYPES_INVERSE, \ - ENTITY_TYPES_INVERSE, IEC61360_DATA_TYPES_INVERSE, IEC61360_LEVEL_TYPES_INVERSE, KEY_TYPES_CLASSES_INVERSE, \ - REFERENCE_TYPES_INVERSE, DIRECTION_INVERSE, STATE_OF_EVENT_INVERSE, QUALIFIER_KIND_INVERSE, PathOrIO +from .._generic import ( + XML_NS_MAP, + XML_NS_AAS, + MODELLING_KIND_INVERSE, + ASSET_KIND_INVERSE, + KEY_TYPES_INVERSE, + ENTITY_TYPES_INVERSE, + IEC61360_DATA_TYPES_INVERSE, + IEC61360_LEVEL_TYPES_INVERSE, + KEY_TYPES_CLASSES_INVERSE, + REFERENCE_TYPES_INVERSE, + DIRECTION_INVERSE, + STATE_OF_EVENT_INVERSE, + QUALIFIER_KIND_INVERSE, + PathOrIO, +) NS_AAS = XML_NS_AAS REQUIRED_NAMESPACES: Set[str] = {XML_NS_MAP["aas"]} @@ -73,7 +86,9 @@ def _str_to_bool(string: str) -> bool: :raises ValueError: If string is neither ``true`` nor ``false``. """ if string not in ("true", "false"): - raise ValueError(f"{string} is not a valid boolean! Only true and false are allowed.") + raise ValueError( + f"{string} is not a valid boolean! Only true and false are allowed." + ) return string == "true" @@ -140,12 +155,16 @@ def _get_child_mandatory(parent: etree._Element, child_tag: str) -> etree._Eleme """ child = parent.find(child_tag) if child is None: - raise KeyError(_element_pretty_identifier(parent) - + f" has no child {_tag_replace_namespace(child_tag, parent.nsmap)}!") + raise KeyError( + _element_pretty_identifier(parent) + + f" has no child {_tag_replace_namespace(child_tag, parent.nsmap)}!" + ) return child -def _get_all_children_expect_tag(parent: etree._Element, expected_tag: str, failsafe: bool) -> Iterable[etree._Element]: +def _get_all_children_expect_tag( + parent: etree._Element, expected_tag: str, failsafe: bool +) -> Iterable[etree._Element]: """ Iterates over all children, matching the tag. @@ -159,8 +178,10 @@ def _get_all_children_expect_tag(parent: etree._Element, expected_tag: str, fail """ for child in parent: if child.tag != expected_tag: - error_message = f"{_element_pretty_identifier(child)}, child of {_element_pretty_identifier(parent)}, " \ - f"doesn't match the expected tag {_tag_replace_namespace(expected_tag, child.nsmap)}!" + error_message = ( + f"{_element_pretty_identifier(child)}, child of {_element_pretty_identifier(parent)}, " + f"doesn't match the expected tag {_tag_replace_namespace(expected_tag, child.nsmap)}!" + ) if not failsafe: raise KeyError(error_message) logger.warning(error_message) @@ -178,11 +199,15 @@ def _get_attrib_mandatory(element: etree._Element, attrib: str) -> str: :raises KeyError: If the attribute does not exist. """ if attrib not in element.attrib: - raise KeyError(f"{_element_pretty_identifier(element)} has no attribute with name {attrib}!") + raise KeyError( + f"{_element_pretty_identifier(element)} has no attribute with name {attrib}!" + ) return element.attrib[attrib] # type: ignore[return-value] -def _get_attrib_mandatory_mapped(element: etree._Element, attrib: str, dct: Dict[str, T]) -> T: +def _get_attrib_mandatory_mapped( + element: etree._Element, attrib: str, dct: Dict[str, T] +) -> T: """ A helper function for getting a mapped mandatory attribute of an xml element. @@ -198,8 +223,10 @@ def _get_attrib_mandatory_mapped(element: etree._Element, attrib: str, dct: Dict """ attrib_value = _get_attrib_mandatory(element, attrib) if attrib_value not in dct: - raise ValueError(f"Attribute {attrib} of {_element_pretty_identifier(element)} " - f"has invalid value: {attrib_value}") + raise ValueError( + f"Attribute {attrib} of {_element_pretty_identifier(element)} " + f"has invalid value: {attrib_value}" + ) return dct[attrib_value] @@ -219,7 +246,9 @@ def _get_text_or_none(element: Optional[etree._Element]) -> Optional[str]: return element.text if element is not None else None -def _get_text_or_empty_string_or_none(element: Optional[etree._Element]) -> Optional[str]: +def _get_text_or_empty_string_or_none( + element: Optional[etree._Element], +) -> Optional[str]: """ Returns element.text or "" if the element has no text or None, if the element is None. @@ -234,7 +263,9 @@ def _get_text_or_empty_string_or_none(element: Optional[etree._Element]) -> Opti return element.text if element.text is not None else "" -def _get_text_mapped_or_none(element: Optional[etree._Element], dct: Dict[str, T]) -> Optional[T]: +def _get_text_mapped_or_none( + element: Optional[etree._Element], dct: Dict[str, T] +) -> Optional[T]: """ Returns dct[element.text] or None, if the element is None, has no text or the text is not in dct. @@ -277,12 +308,18 @@ def _get_text_mandatory_mapped(element: etree._Element, dct: Dict[str, T]) -> T: """ text = _get_text_mandatory(element) if text not in dct: - raise ValueError(_element_pretty_identifier(element) + f" has invalid text: {text}") + raise ValueError( + _element_pretty_identifier(element) + f" has invalid text: {text}" + ) return dct[text] -def _failsafe_construct(element: Optional[etree._Element], constructor: Callable[..., T], failsafe: bool, - **kwargs: Any) -> Optional[T]: +def _failsafe_construct( + element: Optional[etree._Element], + constructor: Callable[..., T], + failsafe: bool, + **kwargs: Any, +) -> Optional[T]: """ A wrapper function that is used to handle exceptions raised in constructor functions. @@ -307,7 +344,9 @@ def _failsafe_construct(element: Optional[etree._Element], constructor: Callable except (KeyError, ValueError, model.AASConstraintViolation) as e: error_message = f"Failed to construct {_element_pretty_identifier(element)} using {constructor.__name__}!" if not failsafe: - raise (type(e) if isinstance(e, (KeyError, ValueError)) else ValueError)(error_message) from e + raise (type(e) if isinstance(e, (KeyError, ValueError)) else ValueError)( + error_message + ) from e error_type = type(e).__name__ cause: Optional[BaseException] = e while cause is not None: @@ -317,7 +356,9 @@ def _failsafe_construct(element: Optional[etree._Element], constructor: Callable return None -def _failsafe_construct_mandatory(element: etree._Element, constructor: Callable[..., T], **kwargs: Any) -> T: +def _failsafe_construct_mandatory( + element: etree._Element, constructor: Callable[..., T], **kwargs: Any +) -> T: """ _failsafe_construct() but not failsafe and it returns T instead of Optional[T] @@ -330,13 +371,19 @@ def _failsafe_construct_mandatory(element: etree._Element, constructor: Callable """ constructed = _failsafe_construct(element, constructor, False, **kwargs) if constructed is None: - raise AssertionError("The result of a non-failsafe _failsafe_construct() call was None! " - "This is a bug in the Eclipse BaSyx Python SDK XML deserialization, please report it!") + raise AssertionError( + "The result of a non-failsafe _failsafe_construct() call was None! " + "This is a bug in the Eclipse BaSyx Python SDK XML deserialization, please report it!" + ) return constructed -def _failsafe_construct_multiple(elements: Iterable[etree._Element], constructor: Callable[..., T], failsafe: bool, - **kwargs: Any) -> Iterable[T]: +def _failsafe_construct_multiple( + elements: Iterable[etree._Element], + constructor: Callable[..., T], + failsafe: bool, + **kwargs: Any, +) -> Iterable[T]: """ A generator function that applies _failsafe_construct() to multiple elements. @@ -354,8 +401,9 @@ def _failsafe_construct_multiple(elements: Iterable[etree._Element], constructor yield parsed -def _child_construct_mandatory(parent: etree._Element, child_tag: str, constructor: Callable[..., T], **kwargs: Any) \ - -> T: +def _child_construct_mandatory( + parent: etree._Element, child_tag: str, constructor: Callable[..., T], **kwargs: Any +) -> T: """ Shorthand for _failsafe_construct_mandatory() in combination with _get_child_mandatory(). @@ -365,11 +413,18 @@ def _child_construct_mandatory(parent: etree._Element, child_tag: str, construct :param kwargs: Optional keyword arguments that are passed to the constructor function. :return: The constructed child element. """ - return _failsafe_construct_mandatory(_get_child_mandatory(parent, child_tag), constructor, **kwargs) + return _failsafe_construct_mandatory( + _get_child_mandatory(parent, child_tag), constructor, **kwargs + ) -def _child_construct_multiple(parent: etree._Element, expected_tag: str, constructor: Callable[..., T], - failsafe: bool, **kwargs: Any) -> Iterable[T]: +def _child_construct_multiple( + parent: etree._Element, + expected_tag: str, + constructor: Callable[..., T], + failsafe: bool, + **kwargs: Any, +) -> Iterable[T]: """ Shorthand for _failsafe_construct_multiple() in combination with _get_child_multiple(). @@ -381,8 +436,12 @@ def _child_construct_multiple(parent: etree._Element, expected_tag: str, constru If an error occurred while constructing an element and while in failsafe mode, the respective element will be skipped. """ - return _failsafe_construct_multiple(_get_all_children_expect_tag(parent, expected_tag, failsafe), constructor, - failsafe, **kwargs) + return _failsafe_construct_multiple( + _get_all_children_expect_tag(parent, expected_tag, failsafe), + constructor, + failsafe, + **kwargs, + ) def _child_text_mandatory(parent: etree._Element, child_tag: str) -> str: @@ -396,7 +455,9 @@ def _child_text_mandatory(parent: etree._Element, child_tag: str) -> str: return _get_text_mandatory(_get_child_mandatory(parent, child_tag)) -def _child_text_mandatory_mapped(parent: etree._Element, child_tag: str, dct: Dict[str, T]) -> T: +def _child_text_mandatory_mapped( + parent: etree._Element, child_tag: str, dct: Dict[str, T] +) -> T: """ Shorthand for _get_text_mandatory_mapped() in combination with _get_child_mandatory(). @@ -415,11 +476,17 @@ def _get_kind(element: etree._Element) -> model.ModellingKind: :param element: The xml element. :return: The modelling kind of the element. """ - modelling_kind = _get_text_mapped_or_none(element.find(NS_AAS + "kind"), MODELLING_KIND_INVERSE) - return modelling_kind if modelling_kind is not None else model.ModellingKind.INSTANCE + modelling_kind = _get_text_mapped_or_none( + element.find(NS_AAS + "kind"), MODELLING_KIND_INVERSE + ) + return ( + modelling_kind if modelling_kind is not None else model.ModellingKind.INSTANCE + ) -def _expect_reference_type(element: etree._Element, expected_type: Type[model.Reference]) -> None: +def _expect_reference_type( + element: etree._Element, expected_type: Type[model.Reference] +) -> None: """ Validates the type attribute of a Reference. @@ -427,9 +494,13 @@ def _expect_reference_type(element: etree._Element, expected_type: Type[model.Re :param expected_type: The expected type of the Reference. :return: None """ - actual_type = _child_text_mandatory_mapped(element, NS_AAS + "type", REFERENCE_TYPES_INVERSE) + actual_type = _child_text_mandatory_mapped( + element, NS_AAS + "type", REFERENCE_TYPES_INVERSE + ) if actual_type is not expected_type: - raise ValueError(f"{_element_pretty_identifier(element)} is of type {actual_type}, expected {expected_type}!") + raise ValueError( + f"{_element_pretty_identifier(element)} is of type {actual_type}, expected {expected_type}!" + ) class AASFromXmlDecoder: @@ -441,6 +512,7 @@ class AASFromXmlDecoder: Most member functions support the ``object_class`` parameter. It was introduced, so they can be overwritten in subclasses, which allows constructing instances of subtypes. """ + failsafe = True stripped = False @@ -459,146 +531,215 @@ def _amend_abstract_attributes(cls, obj: object, element: etree._Element) -> Non if id_short is not None: obj.id_short = id_short category = _get_text_or_none(element.find(NS_AAS + "category")) - display_name = _failsafe_construct(element.find(NS_AAS + "displayName"), - cls.construct_multi_language_name_type, cls.failsafe) + display_name = _failsafe_construct( + element.find(NS_AAS + "displayName"), + cls.construct_multi_language_name_type, + cls.failsafe, + ) if display_name is not None: obj.display_name = display_name if category is not None: obj.category = category - description = _failsafe_construct(element.find(NS_AAS + "description"), - cls.construct_multi_language_text_type, cls.failsafe) + description = _failsafe_construct( + element.find(NS_AAS + "description"), + cls.construct_multi_language_text_type, + cls.failsafe, + ) if description is not None: obj.description = description if isinstance(obj, model.Identifiable): - administration = _failsafe_construct(element.find(NS_AAS + "administration"), - cls.construct_administrative_information, cls.failsafe) + administration = _failsafe_construct( + element.find(NS_AAS + "administration"), + cls.construct_administrative_information, + cls.failsafe, + ) if administration: obj.administration = administration if isinstance(obj, model.HasSemantics): - semantic_id = _failsafe_construct(element.find(NS_AAS + "semanticId"), cls.construct_reference, - cls.failsafe) + semantic_id = _failsafe_construct( + element.find(NS_AAS + "semanticId"), + cls.construct_reference, + cls.failsafe, + ) if semantic_id is not None: obj.semantic_id = semantic_id supplemental_semantic_ids = element.find(NS_AAS + "supplementalSemanticIds") if supplemental_semantic_ids is not None: - for supplemental_semantic_id in _child_construct_multiple(supplemental_semantic_ids, - NS_AAS + "reference", cls.construct_reference, - cls.failsafe): + for supplemental_semantic_id in _child_construct_multiple( + supplemental_semantic_ids, + NS_AAS + "reference", + cls.construct_reference, + cls.failsafe, + ): obj.supplemental_semantic_id.append(supplemental_semantic_id) if isinstance(obj, model.Qualifiable) and not cls.stripped: qualifiers_elem = element.find(NS_AAS + "qualifiers") if qualifiers_elem is not None and len(qualifiers_elem) > 0: - for qualifier in _failsafe_construct_multiple(qualifiers_elem, cls.construct_qualifier, cls.failsafe): + for qualifier in _failsafe_construct_multiple( + qualifiers_elem, cls.construct_qualifier, cls.failsafe + ): obj.qualifier.add(qualifier) if isinstance(obj, model.HasDataSpecification) and not cls.stripped: - embedded_data_specifications_elem = element.find(NS_AAS + "embeddedDataSpecifications") + embedded_data_specifications_elem = element.find( + NS_AAS + "embeddedDataSpecifications" + ) if embedded_data_specifications_elem is not None: - for eds in _failsafe_construct_multiple(embedded_data_specifications_elem, - cls.construct_embedded_data_specification, cls.failsafe): + for eds in _failsafe_construct_multiple( + embedded_data_specifications_elem, + cls.construct_embedded_data_specification, + cls.failsafe, + ): obj.embedded_data_specifications.append(eds) if isinstance(obj, model.HasExtension) and not cls.stripped: extension_elem = element.find(NS_AAS + "extensions") if extension_elem is not None: - for extension in _child_construct_multiple(extension_elem, NS_AAS + "extension", - cls.construct_extension, cls.failsafe): + for extension in _child_construct_multiple( + extension_elem, + NS_AAS + "extension", + cls.construct_extension, + cls.failsafe, + ): obj.extension.add(extension) @classmethod - def _construct_relationship_element_internal(cls, element: etree._Element, object_class: Type[RE], **_kwargs: Any) \ - -> RE: + def _construct_relationship_element_internal( + cls, element: etree._Element, object_class: Type[RE], **_kwargs: Any + ) -> RE: """ Helper function used by construct_relationship_element() and construct_annotated_relationship_element() to reduce duplicate code """ relationship_element = object_class( None, - _failsafe_construct(element.find(NS_AAS + "first"), cls.construct_reference, cls.failsafe), - _failsafe_construct(element.find(NS_AAS + "second"), cls.construct_reference, cls.failsafe) + _failsafe_construct( + element.find(NS_AAS + "first"), cls.construct_reference, cls.failsafe + ), + _failsafe_construct( + element.find(NS_AAS + "second"), cls.construct_reference, cls.failsafe + ), ) cls._amend_abstract_attributes(relationship_element, element) return relationship_element @classmethod - def _construct_key_tuple(cls, element: etree._Element, namespace: str = NS_AAS, **_kwargs: Any) \ - -> Tuple[model.Key, ...]: + def _construct_key_tuple( + cls, element: etree._Element, namespace: str = NS_AAS, **_kwargs: Any + ) -> Tuple[model.Key, ...]: """ Helper function used by construct_reference() and construct_aas_reference() to reduce duplicate code """ keys = _get_child_mandatory(element, namespace + "keys") - return tuple(_child_construct_multiple(keys, namespace + "key", cls.construct_key, cls.failsafe)) + return tuple( + _child_construct_multiple( + keys, namespace + "key", cls.construct_key, cls.failsafe + ) + ) @classmethod - def _construct_submodel_reference(cls, element: etree._Element, **kwargs: Any) \ - -> model.ModelReference[model.Submodel]: + def _construct_submodel_reference( + cls, element: etree._Element, **kwargs: Any + ) -> model.ModelReference[model.Submodel]: """ Helper function. Doesn't support the object_class parameter. Overwrite construct_aas_reference instead. """ - return cls.construct_model_reference_expect_type(element, model.Submodel, **kwargs) + return cls.construct_model_reference_expect_type( + element, model.Submodel, **kwargs + ) @classmethod - def _construct_asset_administration_shell_reference(cls, element: etree._Element, **kwargs: Any) \ - -> model.ModelReference[model.AssetAdministrationShell]: + def _construct_asset_administration_shell_reference( + cls, element: etree._Element, **kwargs: Any + ) -> model.ModelReference[model.AssetAdministrationShell]: """ Helper function. Doesn't support the object_class parameter. Overwrite construct_aas_reference instead. """ - return cls.construct_model_reference_expect_type(element, model.AssetAdministrationShell, **kwargs) + return cls.construct_model_reference_expect_type( + element, model.AssetAdministrationShell, **kwargs + ) @classmethod - def _construct_referable_reference(cls, element: etree._Element, **kwargs: Any) \ - -> model.ModelReference[model.Referable]: + def _construct_referable_reference( + cls, element: etree._Element, **kwargs: Any + ) -> model.ModelReference[model.Referable]: """ Helper function. Doesn't support the object_class parameter. Overwrite construct_aas_reference instead. """ # TODO: remove the following type: ignore comments when mypy supports abstract types for Type[T] # see https://github.com/python/mypy/issues/5374 - return cls.construct_model_reference_expect_type(element, model.Referable, **kwargs) # type: ignore + return cls.construct_model_reference_expect_type( + element, model.Referable, **kwargs + ) # type: ignore @classmethod - def _construct_operation_variable(cls, element: etree._Element, **kwargs: Any) -> model.SubmodelElement: + def _construct_operation_variable( + cls, element: etree._Element, **kwargs: Any + ) -> model.SubmodelElement: """ Since we don't implement ``OperationVariable``, this constructor discards the wrapping `aas:operationVariable` and `aas:value` and just returns the contained :class:`~basyx.aas.model.submodel.SubmodelElement`. """ value = _get_child_mandatory(element, NS_AAS + "value") if len(value) == 0: - raise KeyError(f"{_element_pretty_identifier(value)} has no submodel element!") + raise KeyError( + f"{_element_pretty_identifier(value)} has no submodel element!" + ) if len(value) > 1: - logger.warning(f"{_element_pretty_identifier(value)} has more than one submodel element, " - "using the first one...") + logger.warning( + f"{_element_pretty_identifier(value)} has more than one submodel element, " + "using the first one..." + ) return cls.construct_submodel_element(value[0], **kwargs) @classmethod - def construct_key(cls, element: etree._Element, object_class=model.Key, **_kwargs: Any) \ - -> model.Key: + def construct_key( + cls, element: etree._Element, object_class=model.Key, **_kwargs: Any + ) -> model.Key: return object_class( _child_text_mandatory_mapped(element, NS_AAS + "type", KEY_TYPES_INVERSE), - _child_text_mandatory(element, NS_AAS + "value") + _child_text_mandatory(element, NS_AAS + "value"), ) @classmethod - def construct_reference(cls, element: etree._Element, namespace: str = NS_AAS, **kwargs: Any) -> model.Reference: - reference_type: Type[model.Reference] = _child_text_mandatory_mapped(element, NS_AAS + "type", - REFERENCE_TYPES_INVERSE) + def construct_reference( + cls, element: etree._Element, namespace: str = NS_AAS, **kwargs: Any + ) -> model.Reference: + reference_type: Type[model.Reference] = _child_text_mandatory_mapped( + element, NS_AAS + "type", REFERENCE_TYPES_INVERSE + ) references: Dict[Type[model.Reference], Callable[..., model.Reference]] = { model.ExternalReference: cls.construct_external_reference, - model.ModelReference: cls.construct_model_reference + model.ModelReference: cls.construct_model_reference, } if reference_type not in references: - raise KeyError(_element_pretty_identifier(element) + f" is of unsupported Reference type {reference_type}!") + raise KeyError( + _element_pretty_identifier(element) + + f" is of unsupported Reference type {reference_type}!" + ) return references[reference_type](element, namespace=namespace, **kwargs) @classmethod - def construct_external_reference(cls, element: etree._Element, namespace: str = NS_AAS, - object_class=model.ExternalReference, **_kwargs: Any) \ - -> model.ExternalReference: + def construct_external_reference( + cls, + element: etree._Element, + namespace: str = NS_AAS, + object_class=model.ExternalReference, + **_kwargs: Any, + ) -> model.ExternalReference: _expect_reference_type(element, model.ExternalReference) - return object_class(cls._construct_key_tuple(element, namespace=namespace), - _failsafe_construct(element.find(NS_AAS + "referredSemanticId"), cls.construct_reference, - cls.failsafe, namespace=namespace)) + return object_class( + cls._construct_key_tuple(element, namespace=namespace), + _failsafe_construct( + element.find(NS_AAS + "referredSemanticId"), + cls.construct_reference, + cls.failsafe, + namespace=namespace, + ), + ) @classmethod - def construct_model_reference(cls, element: etree._Element, object_class=model.ModelReference, **_kwargs: Any) \ - -> model.ModelReference: + def construct_model_reference( + cls, element: etree._Element, object_class=model.ModelReference, **_kwargs: Any + ) -> model.ModelReference: """ This constructor for ModelReference determines the type of the ModelReference by its keys. If no keys are present, it will default to the type Referable. This behaviour is wanted in read_aas_xml_element(). @@ -610,13 +751,24 @@ def construct_model_reference(cls, element: etree._Element, object_class=model.M type_: Type[model.Referable] = model.Referable # type: ignore if len(keys) > 0: type_ = KEY_TYPES_CLASSES_INVERSE.get(keys[-1].type, model.Referable) # type: ignore - return object_class(keys, type_, _failsafe_construct(element.find(NS_AAS + "referredSemanticId"), - cls.construct_reference, cls.failsafe)) + return object_class( + keys, + type_, + _failsafe_construct( + element.find(NS_AAS + "referredSemanticId"), + cls.construct_reference, + cls.failsafe, + ), + ) @classmethod - def construct_model_reference_expect_type(cls, element: etree._Element, type_: Type[model.base._RT], - object_class=model.ModelReference, **_kwargs: Any) \ - -> model.ModelReference[model.base._RT]: + def construct_model_reference_expect_type( + cls, + element: etree._Element, + type_: Type[model.base._RT], + object_class=model.ModelReference, + **_kwargs: Any, + ) -> model.ModelReference[model.base._RT]: """ This constructor for ModelReference allows passing an expected type, which is checked against the type of the last key of the reference. This constructor function is used by other constructor functions, since all expect a @@ -624,88 +776,151 @@ def construct_model_reference_expect_type(cls, element: etree._Element, type_: T """ _expect_reference_type(element, model.ModelReference) keys = cls._construct_key_tuple(element) - if keys and not issubclass(KEY_TYPES_CLASSES_INVERSE.get(keys[-1].type, type(None)), type_): - logger.warning("type %s of last key of reference to %s does not match reference type %s", - keys[-1].type.name, " / ".join(str(k) for k in keys), type_.__name__) - return object_class(keys, type_, _failsafe_construct(element.find(NS_AAS + "referredSemanticId"), - cls.construct_reference, cls.failsafe)) + if keys and not issubclass( + KEY_TYPES_CLASSES_INVERSE.get(keys[-1].type, type(None)), type_ + ): + logger.warning( + "type %s of last key of reference to %s does not match reference type %s", + keys[-1].type.name, + " / ".join(str(k) for k in keys), + type_.__name__, + ) + return object_class( + keys, + type_, + _failsafe_construct( + element.find(NS_AAS + "referredSemanticId"), + cls.construct_reference, + cls.failsafe, + ), + ) @classmethod - def construct_administrative_information(cls, element: etree._Element, object_class=model.AdministrativeInformation, - **_kwargs: Any) -> model.AdministrativeInformation: + def construct_administrative_information( + cls, + element: etree._Element, + object_class=model.AdministrativeInformation, + **_kwargs: Any, + ) -> model.AdministrativeInformation: administrative_information = object_class( revision=_get_text_or_none(element.find(NS_AAS + "revision")), version=_get_text_or_none(element.find(NS_AAS + "version")), - template_id=_get_text_or_none(element.find(NS_AAS + "templateId")) + template_id=_get_text_or_none(element.find(NS_AAS + "templateId")), + ) + creator = _failsafe_construct( + element.find(NS_AAS + "creator"), cls.construct_reference, cls.failsafe ) - creator = _failsafe_construct(element.find(NS_AAS + "creator"), cls.construct_reference, cls.failsafe) if creator is not None: administrative_information.creator = creator cls._amend_abstract_attributes(administrative_information, element) return administrative_information @classmethod - def construct_lang_string_set(cls, element: etree._Element, expected_tag: str, object_class: Type[LSS], - **_kwargs: Any) -> LSS: + def construct_lang_string_set( + cls, + element: etree._Element, + expected_tag: str, + object_class: Type[LSS], + **_kwargs: Any, + ) -> LSS: collected_lang_strings: Dict[str, str] = {} - for lang_string_elem in _get_all_children_expect_tag(element, expected_tag, cls.failsafe): - collected_lang_strings[_child_text_mandatory(lang_string_elem, NS_AAS + "language")] = \ - _child_text_mandatory(lang_string_elem, NS_AAS + "text") + for lang_string_elem in _get_all_children_expect_tag( + element, expected_tag, cls.failsafe + ): + collected_lang_strings[ + _child_text_mandatory(lang_string_elem, NS_AAS + "language") + ] = _child_text_mandatory(lang_string_elem, NS_AAS + "text") return object_class(collected_lang_strings) @classmethod - def construct_multi_language_name_type(cls, element: etree._Element, object_class=model.MultiLanguageNameType, - **kwargs: Any) -> model.MultiLanguageNameType: - return cls.construct_lang_string_set(element, NS_AAS + "langStringNameType", object_class, **kwargs) + def construct_multi_language_name_type( + cls, + element: etree._Element, + object_class=model.MultiLanguageNameType, + **kwargs: Any, + ) -> model.MultiLanguageNameType: + return cls.construct_lang_string_set( + element, NS_AAS + "langStringNameType", object_class, **kwargs + ) @classmethod - def construct_multi_language_text_type(cls, element: etree._Element, object_class=model.MultiLanguageTextType, - **kwargs: Any) -> model.MultiLanguageTextType: - return cls.construct_lang_string_set(element, NS_AAS + "langStringTextType", object_class, **kwargs) + def construct_multi_language_text_type( + cls, + element: etree._Element, + object_class=model.MultiLanguageTextType, + **kwargs: Any, + ) -> model.MultiLanguageTextType: + return cls.construct_lang_string_set( + element, NS_AAS + "langStringTextType", object_class, **kwargs + ) @classmethod - def construct_definition_type_iec61360(cls, element: etree._Element, object_class=model.DefinitionTypeIEC61360, - **kwargs: Any) -> model.DefinitionTypeIEC61360: - return cls.construct_lang_string_set(element, NS_AAS + "langStringDefinitionTypeIec61360", object_class, - **kwargs) + def construct_definition_type_iec61360( + cls, + element: etree._Element, + object_class=model.DefinitionTypeIEC61360, + **kwargs: Any, + ) -> model.DefinitionTypeIEC61360: + return cls.construct_lang_string_set( + element, NS_AAS + "langStringDefinitionTypeIec61360", object_class, **kwargs + ) @classmethod - def construct_preferred_name_type_iec61360(cls, element: etree._Element, - object_class=model.PreferredNameTypeIEC61360, - **kwargs: Any) -> model.PreferredNameTypeIEC61360: - return cls.construct_lang_string_set(element, NS_AAS + "langStringPreferredNameTypeIec61360", object_class, - **kwargs) + def construct_preferred_name_type_iec61360( + cls, + element: etree._Element, + object_class=model.PreferredNameTypeIEC61360, + **kwargs: Any, + ) -> model.PreferredNameTypeIEC61360: + return cls.construct_lang_string_set( + element, + NS_AAS + "langStringPreferredNameTypeIec61360", + object_class, + **kwargs, + ) @classmethod - def construct_short_name_type_iec61360(cls, element: etree._Element, object_class=model.ShortNameTypeIEC61360, - **kwargs: Any) -> model.ShortNameTypeIEC61360: - return cls.construct_lang_string_set(element, NS_AAS + "langStringShortNameTypeIec61360", object_class, - **kwargs) + def construct_short_name_type_iec61360( + cls, + element: etree._Element, + object_class=model.ShortNameTypeIEC61360, + **kwargs: Any, + ) -> model.ShortNameTypeIEC61360: + return cls.construct_lang_string_set( + element, NS_AAS + "langStringShortNameTypeIec61360", object_class, **kwargs + ) @classmethod - def construct_qualifier(cls, element: etree._Element, object_class=model.Qualifier, **_kwargs: Any) \ - -> model.Qualifier: + def construct_qualifier( + cls, element: etree._Element, object_class=model.Qualifier, **_kwargs: Any + ) -> model.Qualifier: qualifier = object_class( _child_text_mandatory(element, NS_AAS + "type"), - _child_text_mandatory_mapped(element, NS_AAS + "valueType", model.datatypes.XSD_TYPE_CLASSES) + _child_text_mandatory_mapped( + element, NS_AAS + "valueType", model.datatypes.XSD_TYPE_CLASSES + ), + ) + kind = _get_text_mapped_or_none( + element.find(NS_AAS + "kind"), QUALIFIER_KIND_INVERSE ) - kind = _get_text_mapped_or_none(element.find(NS_AAS + "kind"), QUALIFIER_KIND_INVERSE) if kind is not None: qualifier.kind = kind value = _get_text_or_empty_string_or_none(element.find(NS_AAS + "value")) if value is not None: qualifier.value = model.datatypes.from_xsd(value, qualifier.value_type) - value_id = _failsafe_construct(element.find(NS_AAS + "valueId"), cls.construct_reference, cls.failsafe) + value_id = _failsafe_construct( + element.find(NS_AAS + "valueId"), cls.construct_reference, cls.failsafe + ) if value_id is not None: qualifier.value_id = value_id cls._amend_abstract_attributes(qualifier, element) return qualifier @classmethod - def construct_extension(cls, element: etree._Element, object_class=model.Extension, **_kwargs: Any) \ - -> model.Extension: - extension = object_class( - _child_text_mandatory(element, NS_AAS + "name")) + def construct_extension( + cls, element: etree._Element, object_class=model.Extension, **_kwargs: Any + ) -> model.Extension: + extension = object_class(_child_text_mandatory(element, NS_AAS + "name")) value_type = _get_text_or_none(element.find(NS_AAS + "valueType")) if value_type is not None: extension.value_type = model.datatypes.XSD_TYPE_CLASSES[value_type] @@ -714,97 +929,142 @@ def construct_extension(cls, element: etree._Element, object_class=model.Extensi extension.value = model.datatypes.from_xsd(value, extension.value_type) refers_to = element.find(NS_AAS + "refersTo") if refers_to is not None: - for ref in _child_construct_multiple(refers_to, NS_AAS + "reference", cls._construct_referable_reference, - cls.failsafe): + for ref in _child_construct_multiple( + refers_to, + NS_AAS + "reference", + cls._construct_referable_reference, + cls.failsafe, + ): extension.refers_to.add(ref) cls._amend_abstract_attributes(extension, element) return extension @classmethod - def construct_submodel_element(cls, element: etree._Element, **kwargs: Any) -> model.SubmodelElement: + def construct_submodel_element( + cls, element: etree._Element, **kwargs: Any + ) -> model.SubmodelElement: """ This function doesn't support the object_class parameter. Overwrite each individual SubmodelElement/DataElement constructor function instead. """ - submodel_elements: Dict[str, Callable[..., model.SubmodelElement]] = {NS_AAS + k: v for k, v in { - "annotatedRelationshipElement": cls.construct_annotated_relationship_element, - "basicEventElement": cls.construct_basic_event_element, - "capability": cls.construct_capability, - "entity": cls.construct_entity, - "operation": cls.construct_operation, - "relationshipElement": cls.construct_relationship_element, - "submodelElementCollection": cls.construct_submodel_element_collection, - "submodelElementList": cls.construct_submodel_element_list - }.items()} + submodel_elements: Dict[str, Callable[..., model.SubmodelElement]] = { + NS_AAS + k: v + for k, v in { + "annotatedRelationshipElement": cls.construct_annotated_relationship_element, + "basicEventElement": cls.construct_basic_event_element, + "capability": cls.construct_capability, + "entity": cls.construct_entity, + "operation": cls.construct_operation, + "relationshipElement": cls.construct_relationship_element, + "submodelElementCollection": cls.construct_submodel_element_collection, + "submodelElementList": cls.construct_submodel_element_list, + }.items() + } if element.tag not in submodel_elements: - return cls.construct_data_element(element, abstract_class_name="SubmodelElement", **kwargs) + return cls.construct_data_element( + element, abstract_class_name="SubmodelElement", **kwargs + ) return submodel_elements[element.tag](element, **kwargs) @classmethod - def construct_data_element(cls, element: etree._Element, abstract_class_name: str = "DataElement", **kwargs: Any) \ - -> model.DataElement: + def construct_data_element( + cls, + element: etree._Element, + abstract_class_name: str = "DataElement", + **kwargs: Any, + ) -> model.DataElement: """ This function does not support the object_class parameter. Overwrite each individual DataElement constructor function instead. """ - data_elements: Dict[str, Callable[..., model.DataElement]] = {NS_AAS + k: v for k, v in { - "blob": cls.construct_blob, - "file": cls.construct_file, - "multiLanguageProperty": cls.construct_multi_language_property, - "property": cls.construct_property, - "range": cls.construct_range, - "referenceElement": cls.construct_reference_element, - }.items()} + data_elements: Dict[str, Callable[..., model.DataElement]] = { + NS_AAS + k: v + for k, v in { + "blob": cls.construct_blob, + "file": cls.construct_file, + "multiLanguageProperty": cls.construct_multi_language_property, + "property": cls.construct_property, + "range": cls.construct_range, + "referenceElement": cls.construct_reference_element, + }.items() + } if element.tag not in data_elements: - raise KeyError(_element_pretty_identifier(element) + f" is not a valid {abstract_class_name}!") + raise KeyError( + _element_pretty_identifier(element) + + f" is not a valid {abstract_class_name}!" + ) return data_elements[element.tag](element, **kwargs) @classmethod - def construct_annotated_relationship_element(cls, element: etree._Element, - object_class=model.AnnotatedRelationshipElement, **_kwargs: Any) \ - -> model.AnnotatedRelationshipElement: - annotated_relationship_element = cls._construct_relationship_element_internal(element, object_class) + def construct_annotated_relationship_element( + cls, + element: etree._Element, + object_class=model.AnnotatedRelationshipElement, + **_kwargs: Any, + ) -> model.AnnotatedRelationshipElement: + annotated_relationship_element = cls._construct_relationship_element_internal( + element, object_class + ) if not cls.stripped: annotations = element.find(NS_AAS + "annotations") if annotations is not None: - for data_element in _failsafe_construct_multiple(annotations, cls.construct_data_element, - cls.failsafe): + for data_element in _failsafe_construct_multiple( + annotations, cls.construct_data_element, cls.failsafe + ): annotated_relationship_element.annotation.add(data_element) return annotated_relationship_element @classmethod - def construct_basic_event_element(cls, element: etree._Element, object_class=model.BasicEventElement, - **_kwargs: Any) -> model.BasicEventElement: + def construct_basic_event_element( + cls, + element: etree._Element, + object_class=model.BasicEventElement, + **_kwargs: Any, + ) -> model.BasicEventElement: basic_event_element = object_class( None, - _child_construct_mandatory(element, NS_AAS + "observed", cls._construct_referable_reference), - _child_text_mandatory_mapped(element, NS_AAS + "direction", DIRECTION_INVERSE), - _child_text_mandatory_mapped(element, NS_AAS + "state", STATE_OF_EVENT_INVERSE) + _child_construct_mandatory( + element, NS_AAS + "observed", cls._construct_referable_reference + ), + _child_text_mandatory_mapped( + element, NS_AAS + "direction", DIRECTION_INVERSE + ), + _child_text_mandatory_mapped( + element, NS_AAS + "state", STATE_OF_EVENT_INVERSE + ), ) message_topic = _get_text_or_none(element.find(NS_AAS + "messageTopic")) if message_topic is not None: basic_event_element.message_topic = message_topic message_broker = element.find(NS_AAS + "messageBroker") if message_broker is not None: - basic_event_element.message_broker = _failsafe_construct(message_broker, cls.construct_reference, - cls.failsafe) + basic_event_element.message_broker = _failsafe_construct( + message_broker, cls.construct_reference, cls.failsafe + ) last_update = _get_text_or_none(element.find(NS_AAS + "lastUpdate")) if last_update is not None: - basic_event_element.last_update = model.datatypes.from_xsd(last_update, model.datatypes.DateTime) + basic_event_element.last_update = model.datatypes.from_xsd( + last_update, model.datatypes.DateTime + ) min_interval = _get_text_or_none(element.find(NS_AAS + "minInterval")) if min_interval is not None: - basic_event_element.min_interval = model.datatypes.from_xsd(min_interval, model.datatypes.Duration) + basic_event_element.min_interval = model.datatypes.from_xsd( + min_interval, model.datatypes.Duration + ) max_interval = _get_text_or_none(element.find(NS_AAS + "maxInterval")) if max_interval is not None: - basic_event_element.max_interval = model.datatypes.from_xsd(max_interval, model.datatypes.Duration) + basic_event_element.max_interval = model.datatypes.from_xsd( + max_interval, model.datatypes.Duration + ) cls._amend_abstract_attributes(basic_event_element, element) return basic_event_element @classmethod - def construct_blob(cls, element: etree._Element, object_class=model.Blob, **_kwargs: Any) -> model.Blob: + def construct_blob( + cls, element: etree._Element, object_class=model.Blob, **_kwargs: Any + ) -> model.Blob: blob = object_class( - None, - _get_text_or_none(element.find(NS_AAS + "contentType")) + None, _get_text_or_none(element.find(NS_AAS + "contentType")) ) value = _get_text_or_none(element.find(NS_AAS + "value")) if value is not None: @@ -813,19 +1073,26 @@ def construct_blob(cls, element: etree._Element, object_class=model.Blob, **_kwa return blob @classmethod - def construct_capability(cls, element: etree._Element, object_class=model.Capability, **_kwargs: Any) \ - -> model.Capability: + def construct_capability( + cls, element: etree._Element, object_class=model.Capability, **_kwargs: Any + ) -> model.Capability: capability = object_class(None) cls._amend_abstract_attributes(capability, element) return capability @classmethod - def construct_entity(cls, element: etree._Element, object_class=model.Entity, **_kwargs: Any) -> model.Entity: + def construct_entity( + cls, element: etree._Element, object_class=model.Entity, **_kwargs: Any + ) -> model.Entity: specific_asset_id = set() specific_asset_ids = element.find(NS_AAS + "specificAssetIds") if specific_asset_ids is not None: - for id in _child_construct_multiple(specific_asset_ids, NS_AAS + "specificAssetId", - cls.construct_specific_asset_id, cls.failsafe): + for id in _child_construct_multiple( + specific_asset_ids, + NS_AAS + "specificAssetId", + cls.construct_specific_asset_id, + cls.failsafe, + ): specific_asset_id.add(id) entity_type_text = _get_text_or_none(element.find(NS_AAS + "entityType")) if entity_type_text is not None: @@ -836,22 +1103,25 @@ def construct_entity(cls, element: etree._Element, object_class=model.Entity, ** id_short=None, entity_type=entity_type, global_asset_id=_get_text_or_none(element.find(NS_AAS + "globalAssetId")), - specific_asset_id=specific_asset_id) + specific_asset_id=specific_asset_id, + ) if not cls.stripped: statements = element.find(NS_AAS + "statements") if statements is not None: - for submodel_element in _failsafe_construct_multiple(statements, cls.construct_submodel_element, - cls.failsafe): + for submodel_element in _failsafe_construct_multiple( + statements, cls.construct_submodel_element, cls.failsafe + ): entity.statement.add(submodel_element) cls._amend_abstract_attributes(entity, element) return entity @classmethod - def construct_file(cls, element: etree._Element, object_class=model.File, **_kwargs: Any) -> model.File: + def construct_file( + cls, element: etree._Element, object_class=model.File, **_kwargs: Any + ) -> model.File: file = object_class( - None, - _get_text_or_none(element.find(NS_AAS + "contentType")) + None, _get_text_or_none(element.find(NS_AAS + "contentType")) ) value = _get_text_or_none(element.find(NS_AAS + "value")) if value is not None: @@ -860,10 +1130,10 @@ def construct_file(cls, element: etree._Element, object_class=model.File, **_kwa return file @classmethod - def construct_resource(cls, element: etree._Element, object_class=model.Resource, **_kwargs: Any) -> model.Resource: - resource = object_class( - _child_text_mandatory(element, NS_AAS + "path") - ) + def construct_resource( + cls, element: etree._Element, object_class=model.Resource, **_kwargs: Any + ) -> model.Resource: + resource = object_class(_child_text_mandatory(element, NS_AAS + "path")) content_type = _get_text_or_none(element.find(NS_AAS + "contentType")) if content_type is not None: resource.content_type = content_type @@ -871,54 +1141,80 @@ def construct_resource(cls, element: etree._Element, object_class=model.Resource return resource @classmethod - def construct_multi_language_property(cls, element: etree._Element, object_class=model.MultiLanguageProperty, - **_kwargs: Any) -> model.MultiLanguageProperty: + def construct_multi_language_property( + cls, + element: etree._Element, + object_class=model.MultiLanguageProperty, + **_kwargs: Any, + ) -> model.MultiLanguageProperty: multi_language_property = object_class(None) - value = _failsafe_construct(element.find(NS_AAS + "value"), cls.construct_multi_language_text_type, - cls.failsafe) + value = _failsafe_construct( + element.find(NS_AAS + "value"), + cls.construct_multi_language_text_type, + cls.failsafe, + ) if value is not None: multi_language_property.value = value - value_id = _failsafe_construct(element.find(NS_AAS + "valueId"), cls.construct_reference, cls.failsafe) + value_id = _failsafe_construct( + element.find(NS_AAS + "valueId"), cls.construct_reference, cls.failsafe + ) if value_id is not None: multi_language_property.value_id = value_id cls._amend_abstract_attributes(multi_language_property, element) return multi_language_property @classmethod - def construct_operation(cls, element: etree._Element, object_class=model.Operation, **_kwargs: Any) \ - -> model.Operation: + def construct_operation( + cls, element: etree._Element, object_class=model.Operation, **_kwargs: Any + ) -> model.Operation: operation = object_class(None) - for tag, target in ((NS_AAS + "inputVariables", operation.input_variable), - (NS_AAS + "outputVariables", operation.output_variable), - (NS_AAS + "inoutputVariables", operation.in_output_variable)): + for tag, target in ( + (NS_AAS + "inputVariables", operation.input_variable), + (NS_AAS + "outputVariables", operation.output_variable), + (NS_AAS + "inoutputVariables", operation.in_output_variable), + ): variables = element.find(tag) if variables is not None: - for var in _child_construct_multiple(variables, NS_AAS + "operationVariable", - cls._construct_operation_variable, cls.failsafe): + for var in _child_construct_multiple( + variables, + NS_AAS + "operationVariable", + cls._construct_operation_variable, + cls.failsafe, + ): target.add(var) cls._amend_abstract_attributes(operation, element) return operation @classmethod - def construct_property(cls, element: etree._Element, object_class=model.Property, **_kwargs: Any) -> model.Property: + def construct_property( + cls, element: etree._Element, object_class=model.Property, **_kwargs: Any + ) -> model.Property: property_ = object_class( None, - value_type=_child_text_mandatory_mapped(element, NS_AAS + "valueType", model.datatypes.XSD_TYPE_CLASSES) + value_type=_child_text_mandatory_mapped( + element, NS_AAS + "valueType", model.datatypes.XSD_TYPE_CLASSES + ), ) value = _get_text_or_empty_string_or_none(element.find(NS_AAS + "value")) if value is not None: property_.value = model.datatypes.from_xsd(value, property_.value_type) - value_id = _failsafe_construct(element.find(NS_AAS + "valueId"), cls.construct_reference, cls.failsafe) + value_id = _failsafe_construct( + element.find(NS_AAS + "valueId"), cls.construct_reference, cls.failsafe + ) if value_id is not None: property_.value_id = value_id cls._amend_abstract_attributes(property_, element) return property_ @classmethod - def construct_range(cls, element: etree._Element, object_class=model.Range, **_kwargs: Any) -> model.Range: + def construct_range( + cls, element: etree._Element, object_class=model.Range, **_kwargs: Any + ) -> model.Range: range_ = object_class( None, - value_type=_child_text_mandatory_mapped(element, NS_AAS + "valueType", model.datatypes.XSD_TYPE_CLASSES) + value_type=_child_text_mandatory_mapped( + element, NS_AAS + "valueType", model.datatypes.XSD_TYPE_CLASSES + ), ) max_ = _get_text_or_empty_string_or_none(element.find(NS_AAS + "max")) if max_ is not None: @@ -930,105 +1226,170 @@ def construct_range(cls, element: etree._Element, object_class=model.Range, **_k return range_ @classmethod - def construct_reference_element(cls, element: etree._Element, object_class=model.ReferenceElement, **_kwargs: Any) \ - -> model.ReferenceElement: + def construct_reference_element( + cls, + element: etree._Element, + object_class=model.ReferenceElement, + **_kwargs: Any, + ) -> model.ReferenceElement: reference_element = object_class(None) - value = _failsafe_construct(element.find(NS_AAS + "value"), cls.construct_reference, cls.failsafe) + value = _failsafe_construct( + element.find(NS_AAS + "value"), cls.construct_reference, cls.failsafe + ) if value is not None: reference_element.value = value cls._amend_abstract_attributes(reference_element, element) return reference_element @classmethod - def construct_relationship_element(cls, element: etree._Element, object_class=model.RelationshipElement, - **_kwargs: Any) -> model.RelationshipElement: - return cls._construct_relationship_element_internal(element, object_class=object_class, **_kwargs) + def construct_relationship_element( + cls, + element: etree._Element, + object_class=model.RelationshipElement, + **_kwargs: Any, + ) -> model.RelationshipElement: + return cls._construct_relationship_element_internal( + element, object_class=object_class, **_kwargs + ) @classmethod - def construct_submodel_element_collection(cls, element: etree._Element, - object_class=model.SubmodelElementCollection, - **_kwargs: Any) -> model.SubmodelElementCollection: + def construct_submodel_element_collection( + cls, + element: etree._Element, + object_class=model.SubmodelElementCollection, + **_kwargs: Any, + ) -> model.SubmodelElementCollection: collection = object_class(None) if not cls.stripped: value = element.find(NS_AAS + "value") if value is not None: - for submodel_element in _failsafe_construct_multiple(value, cls.construct_submodel_element, - cls.failsafe): + for submodel_element in _failsafe_construct_multiple( + value, cls.construct_submodel_element, cls.failsafe + ): collection.value.add(submodel_element) cls._amend_abstract_attributes(collection, element) return collection @classmethod - def construct_submodel_element_list(cls, element: etree._Element, object_class=model.SubmodelElementList, - **_kwargs: Any) -> model.SubmodelElementList: + def construct_submodel_element_list( + cls, + element: etree._Element, + object_class=model.SubmodelElementList, + **_kwargs: Any, + ) -> model.SubmodelElementList: type_value_list_element = KEY_TYPES_CLASSES_INVERSE[ - _child_text_mandatory_mapped(element, NS_AAS + "typeValueListElement", KEY_TYPES_INVERSE)] + _child_text_mandatory_mapped( + element, NS_AAS + "typeValueListElement", KEY_TYPES_INVERSE + ) + ] if not issubclass(type_value_list_element, model.SubmodelElement): - raise ValueError("Expected a SubmodelElementList with a typeValueListElement that is a subclass of" - f"{model.SubmodelElement}, got {type_value_list_element}!") + raise ValueError( + "Expected a SubmodelElementList with a typeValueListElement that is a subclass of" + f"{model.SubmodelElement}, got {type_value_list_element}!" + ) order_relevant = element.find(NS_AAS + "orderRelevant") list_ = object_class( None, type_value_list_element, - semantic_id_list_element=_failsafe_construct(element.find(NS_AAS + "semanticIdListElement"), - cls.construct_reference, cls.failsafe), - value_type_list_element=_get_text_mapped_or_none(element.find(NS_AAS + "valueTypeListElement"), - model.datatypes.XSD_TYPE_CLASSES), + semantic_id_list_element=_failsafe_construct( + element.find(NS_AAS + "semanticIdListElement"), + cls.construct_reference, + cls.failsafe, + ), + value_type_list_element=_get_text_mapped_or_none( + element.find(NS_AAS + "valueTypeListElement"), + model.datatypes.XSD_TYPE_CLASSES, + ), order_relevant=_str_to_bool(_get_text_mandatory(order_relevant)) - if order_relevant is not None else True + if order_relevant is not None + else True, ) if not cls.stripped: value = element.find(NS_AAS + "value") if value is not None: - list_.value.extend(_failsafe_construct_multiple(value, cls.construct_submodel_element, cls.failsafe)) + list_.value.extend( + _failsafe_construct_multiple( + value, cls.construct_submodel_element, cls.failsafe + ) + ) cls._amend_abstract_attributes(list_, element) return list_ @classmethod - def construct_asset_administration_shell(cls, element: etree._Element, object_class=model.AssetAdministrationShell, - **_kwargs: Any) -> model.AssetAdministrationShell: + def construct_asset_administration_shell( + cls, + element: etree._Element, + object_class=model.AssetAdministrationShell, + **_kwargs: Any, + ) -> model.AssetAdministrationShell: aas = object_class( id_=_child_text_mandatory(element, NS_AAS + "id"), - asset_information=_child_construct_mandatory(element, NS_AAS + "assetInformation", - cls.construct_asset_information) + asset_information=_child_construct_mandatory( + element, NS_AAS + "assetInformation", cls.construct_asset_information + ), ) if not cls.stripped: submodels = element.find(NS_AAS + "submodels") if submodels is not None: - for ref in _child_construct_multiple(submodels, NS_AAS + "reference", - cls._construct_submodel_reference, cls.failsafe): + for ref in _child_construct_multiple( + submodels, + NS_AAS + "reference", + cls._construct_submodel_reference, + cls.failsafe, + ): aas.submodel.add(ref) - derived_from = _failsafe_construct(element.find(NS_AAS + "derivedFrom"), - cls._construct_asset_administration_shell_reference, cls.failsafe) + derived_from = _failsafe_construct( + element.find(NS_AAS + "derivedFrom"), + cls._construct_asset_administration_shell_reference, + cls.failsafe, + ) if derived_from is not None: aas.derived_from = derived_from cls._amend_abstract_attributes(aas, element) return aas @classmethod - def construct_specific_asset_id(cls, element: etree._Element, object_class=model.SpecificAssetId, - **_kwargs: Any) -> model.SpecificAssetId: + def construct_specific_asset_id( + cls, element: etree._Element, object_class=model.SpecificAssetId, **_kwargs: Any + ) -> model.SpecificAssetId: # semantic_id can't be applied by _amend_abstract_attributes because specificAssetId is immutable return object_class( name=_get_text_or_none(element.find(NS_AAS + "name")), value=_get_text_or_none(element.find(NS_AAS + "value")), - external_subject_id=_failsafe_construct(element.find(NS_AAS + "externalSubjectId"), - cls.construct_external_reference, cls.failsafe), - semantic_id=_failsafe_construct(element.find(NS_AAS + "semanticId"), cls.construct_reference, cls.failsafe) + external_subject_id=_failsafe_construct( + element.find(NS_AAS + "externalSubjectId"), + cls.construct_external_reference, + cls.failsafe, + ), + semantic_id=_failsafe_construct( + element.find(NS_AAS + "semanticId"), + cls.construct_reference, + cls.failsafe, + ), ) @classmethod - def construct_asset_information(cls, element: etree._Element, object_class=model.AssetInformation, **_kwargs: Any) \ - -> model.AssetInformation: + def construct_asset_information( + cls, + element: etree._Element, + object_class=model.AssetInformation, + **_kwargs: Any, + ) -> model.AssetInformation: specific_asset_id = set() specific_asset_ids = element.find(NS_AAS + "specificAssetIds") if specific_asset_ids is not None: - for id in _child_construct_multiple(specific_asset_ids, NS_AAS + "specificAssetId", - cls.construct_specific_asset_id, cls.failsafe): + for id in _child_construct_multiple( + specific_asset_ids, + NS_AAS + "specificAssetId", + cls.construct_specific_asset_id, + cls.failsafe, + ): specific_asset_id.add(id) asset_information = object_class( - _child_text_mandatory_mapped(element, NS_AAS + "assetKind", ASSET_KIND_INVERSE), + _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, ) @@ -1036,8 +1397,11 @@ def construct_asset_information(cls, element: etree._Element, object_class=model asset_type = _get_text_or_none(element.find(NS_AAS + "assetType")) if asset_type is not None: asset_information.asset_type = asset_type - thumbnail = _failsafe_construct(element.find(NS_AAS + "defaultThumbnail"), - cls.construct_resource, cls.failsafe) + thumbnail = _failsafe_construct( + element.find(NS_AAS + "defaultThumbnail"), + cls.construct_resource, + cls.failsafe, + ) if thumbnail is not None: asset_information.default_thumbnail = thumbnail @@ -1045,118 +1409,178 @@ def construct_asset_information(cls, element: etree._Element, object_class=model return asset_information @classmethod - def construct_submodel(cls, element: etree._Element, object_class=model.Submodel, **_kwargs: Any) \ - -> model.Submodel: + def construct_submodel( + cls, element: etree._Element, object_class=model.Submodel, **_kwargs: Any + ) -> model.Submodel: submodel = object_class( - _child_text_mandatory(element, NS_AAS + "id"), - kind=_get_kind(element) + _child_text_mandatory(element, NS_AAS + "id"), kind=_get_kind(element) ) if not cls.stripped: submodel_elements = element.find(NS_AAS + "submodelElements") if submodel_elements is not None: - for submodel_element in _failsafe_construct_multiple(submodel_elements, cls.construct_submodel_element, - cls.failsafe): + for submodel_element in _failsafe_construct_multiple( + submodel_elements, cls.construct_submodel_element, cls.failsafe + ): submodel.submodel_element.add(submodel_element) cls._amend_abstract_attributes(submodel, element) return submodel @classmethod - def construct_value_reference_pair(cls, element: etree._Element, object_class=model.ValueReferencePair, - **_kwargs: Any) -> model.ValueReferencePair: + def construct_value_reference_pair( + cls, + element: etree._Element, + object_class=model.ValueReferencePair, + **_kwargs: Any, + ) -> model.ValueReferencePair: value_id_element = element.find(NS_AAS + "valueId") - value_id = cls.construct_reference(value_id_element, **_kwargs) if value_id_element is not None else None - return object_class(_child_text_mandatory(element, NS_AAS + "value"), - value_id) + value_id = ( + cls.construct_reference(value_id_element, **_kwargs) + if value_id_element is not None + else None + ) + return object_class(_child_text_mandatory(element, NS_AAS + "value"), value_id) @classmethod - def construct_value_list(cls, element: etree._Element, **_kwargs: Any) -> model.ValueList: + def construct_value_list( + cls, element: etree._Element, **_kwargs: Any + ) -> model.ValueList: """ This function doesn't support the object_class parameter, because ValueList is just a generic type alias. """ return set( - _child_construct_multiple(_get_child_mandatory(element, NS_AAS + "valueReferencePairs"), - NS_AAS + "valueReferencePair", cls.construct_value_reference_pair, - cls.failsafe) + _child_construct_multiple( + _get_child_mandatory(element, NS_AAS + "valueReferencePairs"), + NS_AAS + "valueReferencePair", + cls.construct_value_reference_pair, + cls.failsafe, + ) ) @classmethod - def construct_concept_description(cls, element: etree._Element, object_class=model.ConceptDescription, - **_kwargs: Any) -> model.ConceptDescription: + def construct_concept_description( + cls, + element: etree._Element, + object_class=model.ConceptDescription, + **_kwargs: Any, + ) -> model.ConceptDescription: cd = object_class(_child_text_mandatory(element, NS_AAS + "id")) is_case_of = element.find(NS_AAS + "isCaseOf") if is_case_of is not None: - for ref in _child_construct_multiple(is_case_of, NS_AAS + "reference", cls.construct_reference, - cls.failsafe): + for ref in _child_construct_multiple( + is_case_of, NS_AAS + "reference", cls.construct_reference, cls.failsafe + ): cd.is_case_of.add(ref) cls._amend_abstract_attributes(cd, element) return cd @classmethod - def construct_embedded_data_specification(cls, element: etree._Element, - object_class=model.EmbeddedDataSpecification, - **_kwargs: Any) -> model.EmbeddedDataSpecification: - data_specification_content = _get_child_mandatory(element, NS_AAS + "dataSpecificationContent") + def construct_embedded_data_specification( + cls, + element: etree._Element, + object_class=model.EmbeddedDataSpecification, + **_kwargs: Any, + ) -> model.EmbeddedDataSpecification: + data_specification_content = _get_child_mandatory( + element, NS_AAS + "dataSpecificationContent" + ) if len(data_specification_content) == 0: - raise KeyError(f"{_element_pretty_identifier(data_specification_content)} has no data specification!") + raise KeyError( + f"{_element_pretty_identifier(data_specification_content)} has no data specification!" + ) if len(data_specification_content) > 1: - logger.warning(f"{_element_pretty_identifier(data_specification_content)} has more than one " - "data specification, using the first one...") + logger.warning( + f"{_element_pretty_identifier(data_specification_content)} has more than one " + "data specification, using the first one..." + ) embedded_data_specification = object_class( - _child_construct_mandatory(element, NS_AAS + "dataSpecification", cls.construct_external_reference), - _failsafe_construct_mandatory(data_specification_content[0], cls.construct_data_specification_content) + _child_construct_mandatory( + element, NS_AAS + "dataSpecification", cls.construct_external_reference + ), + _failsafe_construct_mandatory( + data_specification_content[0], cls.construct_data_specification_content + ), ) cls._amend_abstract_attributes(embedded_data_specification, element) return embedded_data_specification @classmethod - def construct_data_specification_content(cls, element: etree._Element, **kwargs: Any) \ - -> model.DataSpecificationContent: + def construct_data_specification_content( + cls, element: etree._Element, **kwargs: Any + ) -> model.DataSpecificationContent: """ This function doesn't support the object_class parameter. Overwrite each individual DataSpecificationContent constructor function instead. """ - data_specification_contents: Dict[str, Callable[..., model.DataSpecificationContent]] = \ - {NS_AAS + k: v for k, v in { + data_specification_contents: Dict[ + str, Callable[..., model.DataSpecificationContent] + ] = { + NS_AAS + k: v + for k, v in { "dataSpecificationIec61360": cls.construct_data_specification_iec61360, - }.items()} + }.items() + } if element.tag not in data_specification_contents: - raise KeyError(f"{_element_pretty_identifier(element)} is not a valid DataSpecificationContent!") + raise KeyError( + f"{_element_pretty_identifier(element)} is not a valid DataSpecificationContent!" + ) return data_specification_contents[element.tag](element, **kwargs) @classmethod - def construct_data_specification_iec61360(cls, element: etree._Element, - object_class=model.DataSpecificationIEC61360, - **_kwargs: Any) -> model.DataSpecificationIEC61360: - ds_iec = object_class(_child_construct_mandatory(element, NS_AAS + "preferredName", - cls.construct_preferred_name_type_iec61360)) - short_name = _failsafe_construct(element.find(NS_AAS + "shortName"), cls.construct_short_name_type_iec61360, - cls.failsafe) + def construct_data_specification_iec61360( + cls, + element: etree._Element, + object_class=model.DataSpecificationIEC61360, + **_kwargs: Any, + ) -> model.DataSpecificationIEC61360: + ds_iec = object_class( + _child_construct_mandatory( + element, + NS_AAS + "preferredName", + cls.construct_preferred_name_type_iec61360, + ) + ) + short_name = _failsafe_construct( + element.find(NS_AAS + "shortName"), + cls.construct_short_name_type_iec61360, + cls.failsafe, + ) if short_name is not None: ds_iec.short_name = short_name unit = _get_text_or_none(element.find(NS_AAS + "unit")) if unit is not None: ds_iec.unit = unit - unit_id = _failsafe_construct(element.find(NS_AAS + "unitId"), cls.construct_reference, cls.failsafe) + unit_id = _failsafe_construct( + element.find(NS_AAS + "unitId"), cls.construct_reference, cls.failsafe + ) if unit_id is not None: ds_iec.unit_id = unit_id - source_of_definition = _get_text_or_none(element.find(NS_AAS + "sourceOfDefinition")) + source_of_definition = _get_text_or_none( + element.find(NS_AAS + "sourceOfDefinition") + ) if source_of_definition is not None: ds_iec.source_of_definition = source_of_definition symbol = _get_text_or_none(element.find(NS_AAS + "symbol")) if symbol is not None: ds_iec.symbol = symbol - data_type = _get_text_mapped_or_none(element.find(NS_AAS + "dataType"), IEC61360_DATA_TYPES_INVERSE) + data_type = _get_text_mapped_or_none( + element.find(NS_AAS + "dataType"), IEC61360_DATA_TYPES_INVERSE + ) if data_type is not None: ds_iec.data_type = data_type - definition = _failsafe_construct(element.find(NS_AAS + "definition"), cls.construct_definition_type_iec61360, - cls.failsafe) + definition = _failsafe_construct( + element.find(NS_AAS + "definition"), + cls.construct_definition_type_iec61360, + cls.failsafe, + ) if definition is not None: ds_iec.definition = definition value_format = _get_text_or_none(element.find(NS_AAS + "valueFormat")) if value_format is not None: ds_iec.value_format = value_format - value_list = _failsafe_construct(element.find(NS_AAS + "valueList"), cls.construct_value_list, cls.failsafe) + value_list = _failsafe_construct( + element.find(NS_AAS + "valueList"), cls.construct_value_list, cls.failsafe + ) if value_list is not None: ds_iec.value_list = value_list value = _get_text_or_none(element.find(NS_AAS + "value")) @@ -1177,8 +1601,10 @@ def construct_data_specification_iec61360(cls, element: etree._Element, raise ValueError level_type_value = _str_to_bool(child.text) except ValueError: - error_message = f"levelType {tag} of {_element_pretty_identifier(element)} has invalid boolean: " \ - + str(child.text) + error_message = ( + f"levelType {tag} of {_element_pretty_identifier(element)} has invalid boolean: " + + str(child.text) + ) if not cls.failsafe: raise ValueError(error_message) logger.warning(error_message) @@ -1193,6 +1619,7 @@ class StrictAASFromXmlDecoder(AASFromXmlDecoder): """ Non-failsafe XML decoder. Encountered errors won't be caught and abort parsing. """ + failsafe = False @@ -1200,17 +1627,23 @@ class StrippedAASFromXmlDecoder(AASFromXmlDecoder): """ Decoder for stripped XML elements. Used in the HTTP adapter. """ + stripped = True -class StrictStrippedAASFromXmlDecoder(StrictAASFromXmlDecoder, StrippedAASFromXmlDecoder): +class StrictStrippedAASFromXmlDecoder( + StrictAASFromXmlDecoder, StrippedAASFromXmlDecoder +): """ Non-failsafe decoder for stripped XML elements. """ + pass -def _parse_xml_document(file: PathOrIO, failsafe: bool = True, **parser_kwargs: Any) -> Optional[etree._Element]: +def _parse_xml_document( + file: PathOrIO, failsafe: bool = True, **parser_kwargs: Any +) -> Optional[etree._Element]: """ Parse an XML document into an element tree @@ -1223,7 +1656,9 @@ def _parse_xml_document(file: PathOrIO, failsafe: bool = True, **parser_kwargs: :return: The root element of the element tree """ - parser = etree.XMLParser(remove_blank_text=True, remove_comments=True, **parser_kwargs) + parser = etree.XMLParser( + remove_blank_text=True, remove_comments=True, **parser_kwargs + ) try: root = etree.parse(file, parser).getroot() @@ -1235,16 +1670,19 @@ def _parse_xml_document(file: PathOrIO, failsafe: bool = True, **parser_kwargs: missing_namespaces: Set[str] = REQUIRED_NAMESPACES - set(root.nsmap.values()) if missing_namespaces: - error_message = f"The following required namespaces are not declared: {' | '.join(missing_namespaces)}" \ - + " - Is the input document of an older version?" + error_message = ( + f"The following required namespaces are not declared: {' | '.join(missing_namespaces)}" + + " - Is the input document of an older version?" + ) if not failsafe: raise KeyError(error_message) logger.error(error_message) return root -def _select_decoder(failsafe: bool, stripped: bool, decoder: Optional[Type[AASFromXmlDecoder]]) \ - -> Type[AASFromXmlDecoder]: +def _select_decoder( + failsafe: bool, stripped: bool, decoder: Optional[Type[AASFromXmlDecoder]] +) -> Type[AASFromXmlDecoder]: """ Returns the correct decoder based on the parameters failsafe and stripped. If a decoder class is given, failsafe and stripped are ignored. @@ -1272,6 +1710,7 @@ class XMLConstructables(enum.Enum): """ This enum is used to specify which type to construct in read_aas_xml_element(). """ + KEY = enum.auto() REFERENCE = enum.auto() MODEL_REFERENCE = enum.auto() @@ -1315,8 +1754,14 @@ class XMLConstructables(enum.Enum): DATA_SPECIFICATION_IEC61360 = enum.auto() -def read_aas_xml_element(file: PathOrIO, construct: XMLConstructables, failsafe: bool = True, stripped: bool = False, - decoder: Optional[Type[AASFromXmlDecoder]] = None, **constructor_kwargs) -> Optional[object]: +def read_aas_xml_element( + file: PathOrIO, + construct: XMLConstructables, + failsafe: bool = True, + stripped: bool = False, + decoder: Optional[Type[AASFromXmlDecoder]] = None, + **constructor_kwargs, +) -> Optional[object]: """ Construct a single object from an XML string. The namespaces have to be declared on the object itself, since there is no surrounding environment element. @@ -1424,18 +1869,20 @@ def read_aas_xml_element(file: PathOrIO, construct: XMLConstructables, failsafe: raise ValueError(f"{construct.name} cannot be constructed!") element = _parse_xml_document(file, failsafe=decoder_.failsafe) - return _failsafe_construct(element, constructor, decoder_.failsafe, **constructor_kwargs) + return _failsafe_construct( + element, constructor, decoder_.failsafe, **constructor_kwargs + ) def read_aas_xml_file_into( - object_store: model.AbstractObjectStore[model.Identifier, model.Identifiable], - file: PathOrIO, - replace_existing: bool = False, - ignore_existing: bool = False, - failsafe: bool = True, - stripped: bool = False, - decoder: Optional[Type[AASFromXmlDecoder]] = None, - **parser_kwargs: Any + object_store: model.AbstractObjectStore[model.Identifier, model.Identifiable], + file: PathOrIO, + replace_existing: bool = False, + ignore_existing: bool = False, + failsafe: bool = True, + stripped: bool = False, + decoder: Optional[Type[AASFromXmlDecoder]] = None, + **parser_kwargs: Any, ) -> Set[model.Identifier]: """ Read an Asset Administration Shell XML file according to 'Details of the Asset Administration Shell', chapter 5.4 @@ -1472,7 +1919,7 @@ def read_aas_xml_file_into( element_constructors: Dict[str, Callable[..., model.Identifiable]] = { "assetAdministrationShell": decoder_.construct_asset_administration_shell, "conceptDescription": decoder_.construct_concept_description, - "submodel": decoder_.construct_submodel + "submodel": decoder_.construct_submodel, } element_constructors = {NS_AAS + k: v for k, v in element_constructors.items()} @@ -1486,13 +1933,17 @@ def read_aas_xml_file_into( for list_ in root: element_tag = list_.tag[:-1] if list_.tag[-1] != "s" or element_tag not in element_constructors: - error_message = f"Unexpected top-level list {_element_pretty_identifier(list_)}!" + error_message = ( + f"Unexpected top-level list {_element_pretty_identifier(list_)}!" + ) if not decoder_.failsafe: raise TypeError(error_message) logger.warning(error_message) continue constructor = element_constructors[element_tag] - for element in _child_construct_multiple(list_, element_tag, constructor, decoder_.failsafe): + for element in _child_construct_multiple( + list_, element_tag, constructor, decoder_.failsafe + ): if element.id in ret: error_message = f"{element} has a duplicate identifier already parsed in the document!" if not decoder_.failsafe: @@ -1502,8 +1953,10 @@ def read_aas_xml_file_into( existing_element = object_store.get(element.id) if existing_element is not None: if not replace_existing: - error_message = f"object with identifier {element.id} already exists " \ - f"in the object store: {existing_element}!" + error_message = ( + f"object with identifier {element.id} already exists " + f"in the object store: {existing_element}!" + ) if not ignore_existing: raise KeyError(error_message + f" failed to insert {element}!") logger.info(error_message + f" skipping insertion of {element}...") @@ -1514,8 +1967,9 @@ def read_aas_xml_file_into( return ret -def read_aas_xml_file(file: PathOrIO, failsafe: bool = True, **kwargs: Any)\ - -> model.DictIdentifiableStore: +def read_aas_xml_file( + file: PathOrIO, failsafe: bool = True, **kwargs: Any +) -> model.DictIdentifiableStore: """ A wrapper of :meth:`~basyx.aas.adapter.xml.xml_deserialization.read_aas_xml_file_into`, that reads all objects in an empty :class:`~basyx.aas.model.provider.DictIdentifiableStore`. This function supports @@ -1533,6 +1987,8 @@ def read_aas_xml_file(file: PathOrIO, failsafe: bool = True, **kwargs: Any)\ :raises TypeError: **Non-failsafe**: Encountered an undefined top-level list (e.g. ````) :return: A :class:`~basyx.aas.model.provider.DictIdentifiableStore` containing all AAS objects from the XML file """ - identifiable_store: model.DictIdentifiableStore[model.Identifiable] = model.DictIdentifiableStore() + identifiable_store: model.DictIdentifiableStore[model.Identifiable] = ( + model.DictIdentifiableStore() + ) read_aas_xml_file_into(identifiable_store, file, failsafe=failsafe, **kwargs) return identifiable_store diff --git a/sdk/basyx/aas/adapter/xml/xml_serialization.py b/sdk/basyx/aas/adapter/xml/xml_serialization.py index 4b80e5954..f994ad1bb 100644 --- a/sdk/basyx/aas/adapter/xml/xml_serialization.py +++ b/sdk/basyx/aas/adapter/xml/xml_serialization.py @@ -45,9 +45,10 @@ # functions to manipulate etree.Elements more effectively # ############################################################## -def _generate_element(name: str, - text: Optional[str] = None, - attributes: Optional[Dict] = None) -> etree._Element: + +def _generate_element( + name: str, text: Optional[str] = None, attributes: Optional[Dict] = None +) -> etree._Element: """ generate an :class:`~lxml.etree._Element` object @@ -100,7 +101,9 @@ def abstract_classes_to_xml(tag: str, obj: object) -> etree._Element: et_extension = _generate_element(NS_AAS + "extensions") for extension in obj.extension: if isinstance(extension, model.Extension): - et_extension.append(extension_to_xml(extension, tag=NS_AAS + "extension")) + et_extension.append( + extension_to_xml(extension, tag=NS_AAS + "extension") + ) elm.append(et_extension) if isinstance(obj, model.Referable): if obj.category: @@ -108,9 +111,13 @@ def abstract_classes_to_xml(tag: str, obj: object) -> etree._Element: 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")) + elm.append( + lang_string_set_to_xml(obj.display_name, tag=NS_AAS + "displayName") + ) if obj.description: - elm.append(lang_string_set_to_xml(obj.description, tag=NS_AAS + "description")) + elm.append( + lang_string_set_to_xml(obj.description, tag=NS_AAS + "description") + ) if isinstance(obj, model.Identifiable): if obj.administration: elm.append(administrative_information_to_xml(obj.administration)) @@ -123,23 +130,33 @@ def abstract_classes_to_xml(tag: str, obj: object) -> etree._Element: elm.append(_generate_element(name=NS_AAS + "kind", text="Instance")) if isinstance(obj, model.HasSemantics): if obj.semantic_id: - elm.append(reference_to_xml(obj.semantic_id, tag=NS_AAS+"semanticId")) + elm.append(reference_to_xml(obj.semantic_id, tag=NS_AAS + "semanticId")) if obj.supplemental_semantic_id: - et_supplemental_semantic_ids = _generate_element(NS_AAS + "supplementalSemanticIds") + et_supplemental_semantic_ids = _generate_element( + NS_AAS + "supplementalSemanticIds" + ) for supplemental_semantic_id in obj.supplemental_semantic_id: - et_supplemental_semantic_ids.append(reference_to_xml(supplemental_semantic_id, NS_AAS+"reference")) + et_supplemental_semantic_ids.append( + reference_to_xml(supplemental_semantic_id, NS_AAS + "reference") + ) elm.append(et_supplemental_semantic_ids) if isinstance(obj, model.Qualifiable): if obj.qualifier: et_qualifier = _generate_element(NS_AAS + "qualifiers") for qualifier in obj.qualifier: - et_qualifier.append(qualifier_to_xml(qualifier, tag=NS_AAS+"qualifier")) + et_qualifier.append( + qualifier_to_xml(qualifier, tag=NS_AAS + "qualifier") + ) elm.append(et_qualifier) if isinstance(obj, model.HasDataSpecification): if obj.embedded_data_specifications: - et_embedded_data_specifications = _generate_element(NS_AAS + "embeddedDataSpecifications") + et_embedded_data_specifications = _generate_element( + NS_AAS + "embeddedDataSpecifications" + ) for eds in obj.embedded_data_specifications: - et_embedded_data_specifications.append(embedded_data_specification_to_xml(eds)) + et_embedded_data_specifications.append( + embedded_data_specification_to_xml(eds) + ) elm.append(et_embedded_data_specifications) return elm @@ -149,9 +166,11 @@ def abstract_classes_to_xml(tag: str, obj: object) -> etree._Element: # ############################################################## -def _value_to_xml(value: model.ValueDataType, - value_type: model.DataTypeDefXsd, - tag: str = NS_AAS+"value") -> etree._Element: +def _value_to_xml( + value: model.ValueDataType, + value_type: model.DataTypeDefXsd, + tag: str = NS_AAS + "value", +) -> etree._Element: """ Serialization of objects of :class:`~basyx.aas.model.base.ValueDataType` to XML @@ -163,8 +182,7 @@ def _value_to_xml(value: model.ValueDataType, # todo: add "{NS_XSI+"type": "xs:"+model.datatypes.XSD_TYPE_NAMES[value_type]}" as attribute, if the schema allows # it # TODO: if this is ever changed, check value_reference_pair_to_xml() - return _generate_element(tag, - text=model.datatypes.xsd_repr(value)) + return _generate_element(tag, text=model.datatypes.xsd_repr(value)) def lang_string_set_to_xml(obj: model.LangStringSet, tag: str) -> etree._Element: @@ -175,13 +193,16 @@ def lang_string_set_to_xml(obj: model.LangStringSet, tag: str) -> etree._Element :param tag: Namespace+Tag name of the returned XML element. :return: Serialized :class:`~lxml.etree._Element` object """ - LANG_STRING_SET_TAGS: Dict[Type[model.LangStringSet], str] = {k: NS_AAS + v for k, v in { - model.MultiLanguageNameType: "langStringNameType", - model.MultiLanguageTextType: "langStringTextType", - model.DefinitionTypeIEC61360: "langStringDefinitionTypeIec61360", - model.PreferredNameTypeIEC61360: "langStringPreferredNameTypeIec61360", - model.ShortNameTypeIEC61360: "langStringShortNameTypeIec61360" - }.items()} + LANG_STRING_SET_TAGS: Dict[Type[model.LangStringSet], str] = { + k: NS_AAS + v + for k, v in { + model.MultiLanguageNameType: "langStringNameType", + model.MultiLanguageTextType: "langStringTextType", + model.DefinitionTypeIEC61360: "langStringDefinitionTypeIec61360", + model.PreferredNameTypeIEC61360: "langStringPreferredNameTypeIec61360", + model.ShortNameTypeIEC61360: "langStringShortNameTypeIec61360", + }.items() + } et_lss = _generate_element(name=tag) for language, text in obj.items(): et_ls = _generate_element(name=LANG_STRING_SET_TAGS[type(obj)]) @@ -191,8 +212,9 @@ def lang_string_set_to_xml(obj: model.LangStringSet, tag: str) -> etree._Element return et_lss -def administrative_information_to_xml(obj: model.AdministrativeInformation, - tag: str = NS_AAS+"administration") -> etree._Element: +def administrative_information_to_xml( + obj: model.AdministrativeInformation, tag: str = NS_AAS + "administration" +) -> etree._Element: """ Serialization of objects of class :class:`~basyx.aas.model.base.AdministrativeInformation` to XML @@ -202,13 +224,19 @@ def administrative_information_to_xml(obj: model.AdministrativeInformation, """ et_administration = abstract_classes_to_xml(tag, obj) if obj.version: - et_administration.append(_generate_element(name=NS_AAS + "version", text=obj.version)) + et_administration.append( + _generate_element(name=NS_AAS + "version", text=obj.version) + ) if obj.revision: - et_administration.append(_generate_element(name=NS_AAS + "revision", text=obj.revision)) + et_administration.append( + _generate_element(name=NS_AAS + "revision", text=obj.revision) + ) if obj.creator: et_administration.append(reference_to_xml(obj.creator, tag=NS_AAS + "creator")) if obj.template_id: - et_administration.append(_generate_element(name=NS_AAS + "templateId", text=obj.template_id)) + et_administration.append( + _generate_element(name=NS_AAS + "templateId", text=obj.template_id) + ) return et_administration @@ -231,10 +259,12 @@ def data_element_to_xml(obj: model.DataElement) -> etree._Element: return file_to_xml(obj) if isinstance(obj, model.ReferenceElement): return reference_element_to_xml(obj) - raise AssertionError(f"Type {obj.__class__.__name__} is not yet supported by the XML serialization!") + raise AssertionError( + f"Type {obj.__class__.__name__} is not yet supported by the XML serialization!" + ) -def key_to_xml(obj: model.Key, tag: str = NS_AAS+"key") -> etree._Element: +def key_to_xml(obj: model.Key, tag: str = NS_AAS + "key") -> etree._Element: """ Serialization of objects of class :class:`~basyx.aas.model.base.Key` to XML @@ -243,12 +273,16 @@ def key_to_xml(obj: model.Key, tag: str = NS_AAS+"key") -> etree._Element: :return: Serialized :class:`~lxml.etree._Element` object """ et_key = _generate_element(tag) - et_key.append(_generate_element(name=NS_AAS + "type", text=_generic.KEY_TYPES[obj.type])) + et_key.append( + _generate_element(name=NS_AAS + "type", text=_generic.KEY_TYPES[obj.type]) + ) et_key.append(_generate_element(name=NS_AAS + "value", text=obj.value)) return et_key -def reference_to_xml(obj: model.Reference, tag: str = NS_AAS+"reference") -> etree._Element: +def reference_to_xml( + obj: model.Reference, tag: str = NS_AAS + "reference" +) -> etree._Element: """ Serialization of objects of class :class:`~basyx.aas.model.base.Reference` to XML @@ -257,9 +291,13 @@ def reference_to_xml(obj: model.Reference, tag: str = NS_AAS+"reference") -> etr :return: Serialized :class:`~lxml.etree._Element` object """ et_reference = _generate_element(tag) - et_reference.append(_generate_element(NS_AAS + "type", text=_generic.REFERENCE_TYPES[obj.__class__])) + et_reference.append( + _generate_element(NS_AAS + "type", text=_generic.REFERENCE_TYPES[obj.__class__]) + ) if obj.referred_semantic_id is not None: - et_reference.append(reference_to_xml(obj.referred_semantic_id, NS_AAS + "referredSemanticId")) + et_reference.append( + reference_to_xml(obj.referred_semantic_id, NS_AAS + "referredSemanticId") + ) et_keys = _generate_element(name=NS_AAS + "keys") for aas_key in obj.key: et_keys.append(key_to_xml(aas_key)) @@ -268,7 +306,9 @@ def reference_to_xml(obj: model.Reference, tag: str = NS_AAS+"reference") -> etr return et_reference -def qualifier_to_xml(obj: model.Qualifier, tag: str = NS_AAS+"qualifier") -> etree._Element: +def qualifier_to_xml( + obj: model.Qualifier, tag: str = NS_AAS + "qualifier" +) -> etree._Element: """ Serialization of objects of class :class:`~basyx.aas.model.base.Qualifier` to XML @@ -277,17 +317,25 @@ def qualifier_to_xml(obj: model.Qualifier, tag: str = NS_AAS+"qualifier") -> etr :return: Serialized :class:`~lxml.etree._Element` object """ et_qualifier = abstract_classes_to_xml(tag, obj) - et_qualifier.append(_generate_element(NS_AAS + "kind", text=_generic.QUALIFIER_KIND[obj.kind])) + et_qualifier.append( + _generate_element(NS_AAS + "kind", text=_generic.QUALIFIER_KIND[obj.kind]) + ) et_qualifier.append(_generate_element(NS_AAS + "type", text=obj.type)) - et_qualifier.append(_generate_element(NS_AAS + "valueType", text=model.datatypes.XSD_TYPE_NAMES[obj.value_type])) + et_qualifier.append( + _generate_element( + NS_AAS + "valueType", text=model.datatypes.XSD_TYPE_NAMES[obj.value_type] + ) + ) if obj.value: et_qualifier.append(_value_to_xml(obj.value, obj.value_type)) if obj.value_id: - et_qualifier.append(reference_to_xml(obj.value_id, NS_AAS+"valueId")) + et_qualifier.append(reference_to_xml(obj.value_id, NS_AAS + "valueId")) return et_qualifier -def extension_to_xml(obj: model.Extension, tag: str = NS_AAS+"extension") -> etree._Element: +def extension_to_xml( + obj: model.Extension, tag: str = NS_AAS + "extension" +) -> etree._Element: """ Serialization of objects of class :class:`~basyx.aas.model.base.Extension` to XML @@ -298,20 +346,25 @@ def extension_to_xml(obj: model.Extension, tag: str = NS_AAS+"extension") -> etr et_extension = abstract_classes_to_xml(tag, obj) et_extension.append(_generate_element(NS_AAS + "name", text=obj.name)) if obj.value_type: - et_extension.append(_generate_element(NS_AAS + "valueType", - text=model.datatypes.XSD_TYPE_NAMES[obj.value_type])) + et_extension.append( + _generate_element( + NS_AAS + "valueType", + text=model.datatypes.XSD_TYPE_NAMES[obj.value_type], + ) + ) if obj.value: et_extension.append(_value_to_xml(obj.value, obj.value_type)) # type: ignore # (value_type could be None) if len(obj.refers_to) > 0: - refers_to = _generate_element(NS_AAS+"refersTo") + refers_to = _generate_element(NS_AAS + "refersTo") for reference in obj.refers_to: - refers_to.append(reference_to_xml(reference, NS_AAS+"reference")) + refers_to.append(reference_to_xml(reference, NS_AAS + "reference")) et_extension.append(refers_to) return et_extension -def value_reference_pair_to_xml(obj: model.ValueReferencePair, - tag: str = NS_AAS+"valueReferencePair") -> etree._Element: +def value_reference_pair_to_xml( + obj: model.ValueReferencePair, tag: str = NS_AAS + "valueReferencePair" +) -> etree._Element: """ Serialization of objects of class :class:`~basyx.aas.model.base.ValueReferencePair` to XML @@ -321,14 +374,15 @@ def value_reference_pair_to_xml(obj: model.ValueReferencePair, """ et_vrp = _generate_element(tag) # TODO: value_type isn't used at all by _value_to_xml(), thus we can ignore the type here for now - et_vrp.append(_generate_element(NS_AAS+"value", text=obj.value)) # type: ignore + et_vrp.append(_generate_element(NS_AAS + "value", text=obj.value)) # type: ignore if obj.value_id is not None: - et_vrp.append(reference_to_xml(obj.value_id, NS_AAS+"valueId")) + et_vrp.append(reference_to_xml(obj.value_id, NS_AAS + "valueId")) return et_vrp -def value_list_to_xml(obj: model.ValueList, - tag: str = NS_AAS+"valueList") -> etree._Element: +def value_list_to_xml( + obj: model.ValueList, tag: str = NS_AAS + "valueList" +) -> etree._Element: """ Serialization of objects of class :class:`~basyx.aas.model.base.ValueList` to XML @@ -339,9 +393,13 @@ def value_list_to_xml(obj: model.ValueList, :return: Serialized :class:`~lxml.etree._Element` object """ et_value_list = _generate_element(tag) - et_value_reference_pairs = _generate_element(NS_AAS+"valueReferencePairs") + et_value_reference_pairs = _generate_element(NS_AAS + "valueReferencePairs") for aas_reference_pair in obj: - et_value_reference_pairs.append(value_reference_pair_to_xml(aas_reference_pair, NS_AAS+"valueReferencePair")) + et_value_reference_pairs.append( + value_reference_pair_to_xml( + aas_reference_pair, NS_AAS + "valueReferencePair" + ) + ) et_value_list.append(et_value_reference_pairs) return et_value_list @@ -351,8 +409,9 @@ def value_list_to_xml(obj: model.ValueList, # ############################################################## -def specific_asset_id_to_xml(obj: model.SpecificAssetId, tag: str = NS_AAS + "specificAssetId") \ - -> etree._Element: +def specific_asset_id_to_xml( + obj: model.SpecificAssetId, tag: str = NS_AAS + "specificAssetId" +) -> etree._Element: """ Serialization of objects of class :class:`~basyx.aas.model.base.SpecificAssetId` to XML @@ -362,14 +421,20 @@ def specific_asset_id_to_xml(obj: model.SpecificAssetId, tag: str = NS_AAS + "sp """ et_asset_information = abstract_classes_to_xml(tag, obj) et_asset_information.append(_generate_element(name=NS_AAS + "name", text=obj.name)) - et_asset_information.append(_generate_element(name=NS_AAS + "value", text=obj.value)) + et_asset_information.append( + _generate_element(name=NS_AAS + "value", text=obj.value) + ) if obj.external_subject_id: - et_asset_information.append(reference_to_xml(obj.external_subject_id, NS_AAS + "externalSubjectId")) + et_asset_information.append( + reference_to_xml(obj.external_subject_id, NS_AAS + "externalSubjectId") + ) return et_asset_information -def asset_information_to_xml(obj: model.AssetInformation, tag: str = NS_AAS+"assetInformation") -> etree._Element: +def asset_information_to_xml( + obj: model.AssetInformation, tag: str = NS_AAS + "assetInformation" +) -> etree._Element: """ Serialization of objects of class :class:`~basyx.aas.model.aas.AssetInformation` to XML @@ -378,24 +443,37 @@ def asset_information_to_xml(obj: model.AssetInformation, tag: str = NS_AAS+"ass :return: Serialized :class:`~lxml.etree._Element` object """ et_asset_information = abstract_classes_to_xml(tag, obj) - et_asset_information.append(_generate_element(name=NS_AAS + "assetKind", text=_generic.ASSET_KIND[obj.asset_kind])) + et_asset_information.append( + _generate_element( + name=NS_AAS + "assetKind", text=_generic.ASSET_KIND[obj.asset_kind] + ) + ) if obj.global_asset_id: - et_asset_information.append(_generate_element(name=NS_AAS + "globalAssetId", text=obj.global_asset_id)) + et_asset_information.append( + _generate_element(name=NS_AAS + "globalAssetId", text=obj.global_asset_id) + ) if obj.specific_asset_id: 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_specific_asset_id.append( + specific_asset_id_to_xml(specific_asset_id, NS_AAS + "specificAssetId") + ) et_asset_information.append(et_specific_asset_id) if obj.asset_type: - et_asset_information.append(_generate_element(name=NS_AAS + "assetType", text=obj.asset_type)) + et_asset_information.append( + _generate_element(name=NS_AAS + "assetType", text=obj.asset_type) + ) if obj.default_thumbnail: - et_asset_information.append(resource_to_xml(obj.default_thumbnail, NS_AAS+"defaultThumbnail")) + et_asset_information.append( + resource_to_xml(obj.default_thumbnail, NS_AAS + "defaultThumbnail") + ) return et_asset_information -def concept_description_to_xml(obj: model.ConceptDescription, - tag: str = NS_AAS+"conceptDescription") -> etree._Element: +def concept_description_to_xml( + obj: model.ConceptDescription, tag: str = NS_AAS + "conceptDescription" +) -> etree._Element: """ Serialization of objects of class :class:`~basyx.aas.model.concept.ConceptDescription` to XML @@ -405,15 +483,17 @@ def concept_description_to_xml(obj: model.ConceptDescription, """ et_concept_description = abstract_classes_to_xml(tag, obj) if obj.is_case_of: - et_is_case_of = _generate_element(NS_AAS+"isCaseOf") + et_is_case_of = _generate_element(NS_AAS + "isCaseOf") for reference in obj.is_case_of: - et_is_case_of.append(reference_to_xml(reference, NS_AAS+"reference")) + et_is_case_of.append(reference_to_xml(reference, NS_AAS + "reference")) et_concept_description.append(et_is_case_of) return et_concept_description -def embedded_data_specification_to_xml(obj: model.EmbeddedDataSpecification, - tag: str = NS_AAS+"embeddedDataSpecification") -> etree._Element: +def embedded_data_specification_to_xml( + obj: model.EmbeddedDataSpecification, + tag: str = NS_AAS + "embeddedDataSpecification", +) -> etree._Element: """ Serialization of objects of class :class:`~basyx.aas.model.base.EmbeddedDataSpecification` to XML @@ -422,13 +502,18 @@ def embedded_data_specification_to_xml(obj: model.EmbeddedDataSpecification, :return: Serialized :class:`~lxml.etree._Element` object """ et_embedded_data_specification = abstract_classes_to_xml(tag, obj) - et_embedded_data_specification.append(reference_to_xml(obj.data_specification, tag=NS_AAS + "dataSpecification")) - et_embedded_data_specification.append(data_specification_content_to_xml(obj.data_specification_content)) + et_embedded_data_specification.append( + reference_to_xml(obj.data_specification, tag=NS_AAS + "dataSpecification") + ) + et_embedded_data_specification.append( + data_specification_content_to_xml(obj.data_specification_content) + ) return et_embedded_data_specification -def data_specification_content_to_xml(obj: model.DataSpecificationContent, - tag: str = NS_AAS+"dataSpecificationContent") -> etree._Element: +def data_specification_content_to_xml( + obj: model.DataSpecificationContent, tag: str = NS_AAS + "dataSpecificationContent" +) -> etree._Element: """ Serialization of objects of class :class:`~basyx.aas.model.base.DataSpecificationContent` to XML @@ -444,8 +529,10 @@ def data_specification_content_to_xml(obj: model.DataSpecificationContent, return et_data_specification_content -def data_specification_iec61360_to_xml(obj: model.DataSpecificationIEC61360, - tag: str = NS_AAS+"dataSpecificationIec61360") -> etree._Element: +def data_specification_iec61360_to_xml( + obj: model.DataSpecificationIEC61360, + tag: str = NS_AAS + "dataSpecificationIec61360", +) -> etree._Element: """ Serialization of objects of class :class:`~basyx.aas.model.base.DataSpecificationIEC61360` to XML @@ -454,42 +541,67 @@ def data_specification_iec61360_to_xml(obj: model.DataSpecificationIEC61360, :return: Serialized :class:`~lxml.etree._Element` object """ et_data_specification_iec61360 = abstract_classes_to_xml(tag, obj) - et_data_specification_iec61360.append(lang_string_set_to_xml(obj.preferred_name, NS_AAS + "preferredName")) + et_data_specification_iec61360.append( + lang_string_set_to_xml(obj.preferred_name, NS_AAS + "preferredName") + ) if obj.short_name is not None: - et_data_specification_iec61360.append(lang_string_set_to_xml(obj.short_name, NS_AAS + "shortName")) + et_data_specification_iec61360.append( + lang_string_set_to_xml(obj.short_name, NS_AAS + "shortName") + ) if obj.unit is not None: - et_data_specification_iec61360.append(_generate_element(NS_AAS + "unit", text=obj.unit)) + et_data_specification_iec61360.append( + _generate_element(NS_AAS + "unit", text=obj.unit) + ) if obj.unit_id is not None: - et_data_specification_iec61360.append(reference_to_xml(obj.unit_id, NS_AAS + "unitId")) + et_data_specification_iec61360.append( + reference_to_xml(obj.unit_id, NS_AAS + "unitId") + ) if obj.source_of_definition is not None: - et_data_specification_iec61360.append(_generate_element(NS_AAS + "sourceOfDefinition", - text=obj.source_of_definition)) + et_data_specification_iec61360.append( + _generate_element( + NS_AAS + "sourceOfDefinition", text=obj.source_of_definition + ) + ) if obj.symbol is not None: - et_data_specification_iec61360.append(_generate_element(NS_AAS + "symbol", text=obj.symbol)) + et_data_specification_iec61360.append( + _generate_element(NS_AAS + "symbol", text=obj.symbol) + ) if obj.data_type is not None: - et_data_specification_iec61360.append(_generate_element(NS_AAS + "dataType", - text=_generic.IEC61360_DATA_TYPES[obj.data_type])) + et_data_specification_iec61360.append( + _generate_element( + NS_AAS + "dataType", text=_generic.IEC61360_DATA_TYPES[obj.data_type] + ) + ) if obj.definition is not None: - et_data_specification_iec61360.append(lang_string_set_to_xml(obj.definition, NS_AAS + "definition")) + et_data_specification_iec61360.append( + lang_string_set_to_xml(obj.definition, NS_AAS + "definition") + ) if obj.value_format is not None: - et_data_specification_iec61360.append(_generate_element(NS_AAS + "valueFormat", text=obj.value_format)) + et_data_specification_iec61360.append( + _generate_element(NS_AAS + "valueFormat", text=obj.value_format) + ) # this can be either None or an empty set, both of which are equivalent to the bool false # thus we don't check 'is not None' for this property if obj.value_list: et_data_specification_iec61360.append(value_list_to_xml(obj.value_list)) if obj.value is not None: - et_data_specification_iec61360.append(_generate_element(NS_AAS + "value", text=obj.value)) + et_data_specification_iec61360.append( + _generate_element(NS_AAS + "value", text=obj.value) + ) if obj.level_types: et_level_types = _generate_element(NS_AAS + "levelType") for k, v in _generic.IEC61360_LEVEL_TYPES.items(): - et_level_types.append(_generate_element(NS_AAS + v, text=boolean_to_xml(k in obj.level_types))) + et_level_types.append( + _generate_element(NS_AAS + v, text=boolean_to_xml(k in obj.level_types)) + ) et_data_specification_iec61360.append(et_level_types) return et_data_specification_iec61360 -def asset_administration_shell_to_xml(obj: model.AssetAdministrationShell, - tag: str = NS_AAS+"assetAdministrationShell") -> etree._Element: +def asset_administration_shell_to_xml( + obj: model.AssetAdministrationShell, tag: str = NS_AAS + "assetAdministrationShell" +) -> etree._Element: """ Serialization of objects of class :class:`~basyx.aas.model.aas.AssetAdministrationShell` to XML @@ -499,12 +611,14 @@ def asset_administration_shell_to_xml(obj: model.AssetAdministrationShell, """ et_aas = abstract_classes_to_xml(tag, obj) if obj.derived_from: - et_aas.append(reference_to_xml(obj.derived_from, tag=NS_AAS+"derivedFrom")) - et_aas.append(asset_information_to_xml(obj.asset_information, tag=NS_AAS + "assetInformation")) + et_aas.append(reference_to_xml(obj.derived_from, tag=NS_AAS + "derivedFrom")) + et_aas.append( + asset_information_to_xml(obj.asset_information, tag=NS_AAS + "assetInformation") + ) if obj.submodel: et_submodels = _generate_element(NS_AAS + "submodels") for reference in obj.submodel: - et_submodels.append(reference_to_xml(reference, tag=NS_AAS+"reference")) + et_submodels.append(reference_to_xml(reference, tag=NS_AAS + "reference")) et_aas.append(et_submodels) return et_aas @@ -539,11 +653,14 @@ def submodel_element_to_xml(obj: model.SubmodelElement) -> etree._Element: return submodel_element_collection_to_xml(obj) if isinstance(obj, model.SubmodelElementList): return submodel_element_list_to_xml(obj) - raise AssertionError(f"Type {obj.__class__.__name__} is not yet supported by the XML serialization!") + raise AssertionError( + f"Type {obj.__class__.__name__} is not yet supported by the XML serialization!" + ) -def submodel_to_xml(obj: model.Submodel, - tag: str = NS_AAS+"submodel") -> etree._Element: +def submodel_to_xml( + obj: model.Submodel, tag: str = NS_AAS + "submodel" +) -> etree._Element: """ Serialization of objects of class :class:`~basyx.aas.model.submodel.Submodel` to XML @@ -560,8 +677,9 @@ def submodel_to_xml(obj: model.Submodel, return et_submodel -def property_to_xml(obj: model.Property, - tag: str = NS_AAS+"property") -> etree._Element: +def property_to_xml( + obj: model.Property, tag: str = NS_AAS + "property" +) -> etree._Element: """ Serialization of objects of class :class:`~basyx.aas.model.submodel.Property` to XML @@ -570,7 +688,11 @@ def property_to_xml(obj: model.Property, :return: Serialized :class:`~lxml.etree._Element` object """ et_property = abstract_classes_to_xml(tag, obj) - et_property.append(_generate_element(NS_AAS + "valueType", text=model.datatypes.XSD_TYPE_NAMES[obj.value_type])) + et_property.append( + _generate_element( + NS_AAS + "valueType", text=model.datatypes.XSD_TYPE_NAMES[obj.value_type] + ) + ) if obj.value is not None: et_property.append(_value_to_xml(obj.value, obj.value_type)) if obj.value_id: @@ -578,8 +700,9 @@ def property_to_xml(obj: model.Property, return et_property -def multi_language_property_to_xml(obj: model.MultiLanguageProperty, - tag: str = NS_AAS+"multiLanguageProperty") -> etree._Element: +def multi_language_property_to_xml( + obj: model.MultiLanguageProperty, tag: str = NS_AAS + "multiLanguageProperty" +) -> etree._Element: """ Serialization of objects of class :class:`~basyx.aas.model.submodel.MultiLanguageProperty` to XML @@ -589,14 +712,17 @@ def multi_language_property_to_xml(obj: model.MultiLanguageProperty, """ et_multi_language_property = abstract_classes_to_xml(tag, obj) if obj.value: - et_multi_language_property.append(lang_string_set_to_xml(obj.value, tag=NS_AAS + "value")) + et_multi_language_property.append( + lang_string_set_to_xml(obj.value, tag=NS_AAS + "value") + ) if obj.value_id: - et_multi_language_property.append(reference_to_xml(obj.value_id, NS_AAS+"valueId")) + et_multi_language_property.append( + reference_to_xml(obj.value_id, NS_AAS + "valueId") + ) return et_multi_language_property -def range_to_xml(obj: model.Range, - tag: str = NS_AAS+"range") -> etree._Element: +def range_to_xml(obj: model.Range, tag: str = NS_AAS + "range") -> etree._Element: """ Serialization of objects of class :class:`~basyx.aas.model.submodel.Range` to XML @@ -605,8 +731,12 @@ def range_to_xml(obj: model.Range, :return: Serialized :class:`~lxml.etree._Element` object """ et_range = abstract_classes_to_xml(tag, obj) - et_range.append(_generate_element(name=NS_AAS + "valueType", - text=model.datatypes.XSD_TYPE_NAMES[obj.value_type])) + et_range.append( + _generate_element( + name=NS_AAS + "valueType", + text=model.datatypes.XSD_TYPE_NAMES[obj.value_type], + ) + ) if obj.min is not None: et_range.append(_value_to_xml(obj.min, obj.value_type, tag=NS_AAS + "min")) if obj.max is not None: @@ -614,8 +744,7 @@ def range_to_xml(obj: model.Range, return et_range -def blob_to_xml(obj: model.Blob, - tag: str = NS_AAS+"blob") -> etree._Element: +def blob_to_xml(obj: model.Blob, tag: str = NS_AAS + "blob") -> etree._Element: """ Serialization of objects of class :class:`~basyx.aas.model.submodel.Blob` to XML @@ -633,8 +762,7 @@ def blob_to_xml(obj: model.Blob, return et_blob -def file_to_xml(obj: model.File, - tag: str = NS_AAS+"file") -> etree._Element: +def file_to_xml(obj: model.File, tag: str = NS_AAS + "file") -> etree._Element: """ Serialization of objects of class :class:`~basyx.aas.model.submodel.File` to XML @@ -650,8 +778,9 @@ def file_to_xml(obj: model.File, return et_file -def resource_to_xml(obj: model.Resource, - tag: str = NS_AAS+"resource") -> etree._Element: +def resource_to_xml( + obj: model.Resource, tag: str = NS_AAS + "resource" +) -> etree._Element: """ Serialization of objects of class :class:`~basyx.aas.model.base.Resource` to XML @@ -662,12 +791,15 @@ def resource_to_xml(obj: model.Resource, et_resource = abstract_classes_to_xml(tag, obj) et_resource.append(_generate_element(NS_AAS + "path", text=obj.path)) if obj.content_type: - et_resource.append(_generate_element(NS_AAS + "contentType", text=obj.content_type)) + et_resource.append( + _generate_element(NS_AAS + "contentType", text=obj.content_type) + ) return et_resource -def reference_element_to_xml(obj: model.ReferenceElement, - tag: str = NS_AAS+"referenceElement") -> etree._Element: +def reference_element_to_xml( + obj: model.ReferenceElement, tag: str = NS_AAS + "referenceElement" +) -> etree._Element: """ Serialization of objects of class :class:`~basyx.aas.model.submodel.ReferenceElement` to XMl @@ -677,12 +809,14 @@ def reference_element_to_xml(obj: model.ReferenceElement, """ et_reference_element = abstract_classes_to_xml(tag, obj) if obj.value: - et_reference_element.append(reference_to_xml(obj.value, NS_AAS+"value")) + et_reference_element.append(reference_to_xml(obj.value, NS_AAS + "value")) return et_reference_element -def submodel_element_collection_to_xml(obj: model.SubmodelElementCollection, - tag: str = NS_AAS+"submodelElementCollection") -> etree._Element: +def submodel_element_collection_to_xml( + obj: model.SubmodelElementCollection, + tag: str = NS_AAS + "submodelElementCollection", +) -> etree._Element: """ Serialization of objects of class :class:`~basyx.aas.model.submodel.SubmodelElementCollection` to XML @@ -699,8 +833,9 @@ def submodel_element_collection_to_xml(obj: model.SubmodelElementCollection, return et_submodel_element_collection -def submodel_element_list_to_xml(obj: model.SubmodelElementList, - tag: str = NS_AAS+"submodelElementList") -> etree._Element: +def submodel_element_list_to_xml( + obj: model.SubmodelElementList, tag: str = NS_AAS + "submodelElementList" +) -> etree._Element: """ Serialization of objects of class :class:`~basyx.aas.model.submodel.SubmodelElementList` to XML @@ -709,15 +844,28 @@ def submodel_element_list_to_xml(obj: model.SubmodelElementList, :return: Serialized :class:`~lxml.etree._Element` object """ et_submodel_element_list = abstract_classes_to_xml(tag, obj) - et_submodel_element_list.append(_generate_element(NS_AAS + "orderRelevant", boolean_to_xml(obj.order_relevant))) + et_submodel_element_list.append( + _generate_element(NS_AAS + "orderRelevant", boolean_to_xml(obj.order_relevant)) + ) if obj.semantic_id_list_element is not None: - et_submodel_element_list.append(reference_to_xml(obj.semantic_id_list_element, - NS_AAS + "semanticIdListElement")) - et_submodel_element_list.append(_generate_element(NS_AAS + "typeValueListElement", _generic.KEY_TYPES[ - model.KEY_TYPES_CLASSES[obj.type_value_list_element]])) + et_submodel_element_list.append( + reference_to_xml( + obj.semantic_id_list_element, NS_AAS + "semanticIdListElement" + ) + ) + et_submodel_element_list.append( + _generate_element( + NS_AAS + "typeValueListElement", + _generic.KEY_TYPES[model.KEY_TYPES_CLASSES[obj.type_value_list_element]], + ) + ) if obj.value_type_list_element is not None: - et_submodel_element_list.append(_generate_element(NS_AAS + "valueTypeListElement", - model.datatypes.XSD_TYPE_NAMES[obj.value_type_list_element])) + et_submodel_element_list.append( + _generate_element( + NS_AAS + "valueTypeListElement", + model.datatypes.XSD_TYPE_NAMES[obj.value_type_list_element], + ) + ) if len(obj.value) > 0: et_value = _generate_element(NS_AAS + "value") for se in obj.value: @@ -726,8 +874,9 @@ def submodel_element_list_to_xml(obj: model.SubmodelElementList, return et_submodel_element_list -def relationship_element_to_xml(obj: model.RelationshipElement, - tag: str = NS_AAS+"relationshipElement") -> etree._Element: +def relationship_element_to_xml( + obj: model.RelationshipElement, tag: str = NS_AAS + "relationshipElement" +) -> etree._Element: """ Serialization of objects of class :class:`~basyx.aas.model.submodel.RelationshipElement` to XML @@ -737,14 +886,16 @@ def relationship_element_to_xml(obj: model.RelationshipElement, """ et_relationship_element = abstract_classes_to_xml(tag, obj) if obj.first is not None: - et_relationship_element.append(reference_to_xml(obj.first, NS_AAS+"first")) + et_relationship_element.append(reference_to_xml(obj.first, NS_AAS + "first")) if obj.second is not None: - et_relationship_element.append(reference_to_xml(obj.second, NS_AAS+"second")) + et_relationship_element.append(reference_to_xml(obj.second, NS_AAS + "second")) return et_relationship_element -def annotated_relationship_element_to_xml(obj: model.AnnotatedRelationshipElement, - tag: str = NS_AAS+"annotatedRelationshipElement") -> etree._Element: +def annotated_relationship_element_to_xml( + obj: model.AnnotatedRelationshipElement, + tag: str = NS_AAS + "annotatedRelationshipElement", +) -> etree._Element: """ Serialization of objects of class :class:`~basyx.aas.model.submodel.AnnotatedRelationshipElement` to XML @@ -761,7 +912,9 @@ def annotated_relationship_element_to_xml(obj: model.AnnotatedRelationshipElemen return et_annotated_relationship_element -def operation_variable_to_xml(obj: model.SubmodelElement, tag: str = NS_AAS+"operationVariable") -> etree._Element: +def operation_variable_to_xml( + obj: model.SubmodelElement, tag: str = NS_AAS + "operationVariable" +) -> etree._Element: """ Serialization of :class:`~basyx.aas.model.submodel.SubmodelElement` to the XML OperationVariable representation Since we don't implement the ``OperationVariable`` class, which is just a wrapper for a single @@ -773,14 +926,15 @@ def operation_variable_to_xml(obj: model.SubmodelElement, tag: str = NS_AAS+"ope :return: Serialized :class:`~lxml.etree._Element` object """ et_operation_variable = _generate_element(tag) - et_value = _generate_element(NS_AAS+"value") + et_value = _generate_element(NS_AAS + "value") et_value.append(submodel_element_to_xml(obj)) et_operation_variable.append(et_value) return et_operation_variable -def operation_to_xml(obj: model.Operation, - tag: str = NS_AAS+"operation") -> etree._Element: +def operation_to_xml( + obj: model.Operation, tag: str = NS_AAS + "operation" +) -> etree._Element: """ Serialization of objects of class :class:`~basyx.aas.model.submodel.Operation` to XML @@ -789,9 +943,11 @@ def operation_to_xml(obj: model.Operation, :return: Serialized :class:`~lxml.etree._Element` object """ et_operation = abstract_classes_to_xml(tag, obj) - for tag, nss in ((NS_AAS+"inputVariables", obj.input_variable), - (NS_AAS+"outputVariables", obj.output_variable), - (NS_AAS+"inoutputVariables", obj.in_output_variable)): + for tag, nss in ( + (NS_AAS + "inputVariables", obj.input_variable), + (NS_AAS + "outputVariables", obj.output_variable), + (NS_AAS + "inoutputVariables", obj.in_output_variable), + ): if nss: et_variables = _generate_element(tag) for submodel_element in nss: @@ -800,8 +956,9 @@ def operation_to_xml(obj: model.Operation, return et_operation -def capability_to_xml(obj: model.Capability, - tag: str = NS_AAS+"capability") -> etree._Element: +def capability_to_xml( + obj: model.Capability, tag: str = NS_AAS + "capability" +) -> etree._Element: """ Serialization of objects of class :class:`~basyx.aas.model.submodel.Capability` to XML @@ -812,8 +969,7 @@ def capability_to_xml(obj: model.Capability, return abstract_classes_to_xml(tag, obj) -def entity_to_xml(obj: model.Entity, - tag: str = NS_AAS+"entity") -> etree._Element: +def entity_to_xml(obj: model.Entity, tag: str = NS_AAS + "entity") -> etree._Element: """ Serialization of objects of class :class:`~basyx.aas.model.submodel.Entity` to XML @@ -828,18 +984,28 @@ def entity_to_xml(obj: model.Entity, et_statements.append(submodel_element_to_xml(statement)) et_entity.append(et_statements) if obj.entity_type: - et_entity.append(_generate_element(NS_AAS + "entityType", text=_generic.ENTITY_TYPES[obj.entity_type])) + et_entity.append( + _generate_element( + NS_AAS + "entityType", text=_generic.ENTITY_TYPES[obj.entity_type] + ) + ) if obj.global_asset_id: - et_entity.append(_generate_element(NS_AAS + "globalAssetId", text=obj.global_asset_id)) + et_entity.append( + _generate_element(NS_AAS + "globalAssetId", text=obj.global_asset_id) + ) if obj.specific_asset_id: 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_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 -def basic_event_element_to_xml(obj: model.BasicEventElement, tag: str = NS_AAS+"basicEventElement") -> etree._Element: +def basic_event_element_to_xml( + obj: model.BasicEventElement, tag: str = NS_AAS + "basicEventElement" +) -> etree._Element: """ Serialization of objects of class :class:`~basyx.aas.model.submodel.BasicEventElement` to XML @@ -848,22 +1014,39 @@ def basic_event_element_to_xml(obj: model.BasicEventElement, tag: str = NS_AAS+" :return: Serialized :class:`~lxml.etree._Element` object """ et_basic_event_element = abstract_classes_to_xml(tag, obj) - et_basic_event_element.append(reference_to_xml(obj.observed, NS_AAS+"observed")) - et_basic_event_element.append(_generate_element(NS_AAS+"direction", text=_generic.DIRECTION[obj.direction])) - et_basic_event_element.append(_generate_element(NS_AAS+"state", text=_generic.STATE_OF_EVENT[obj.state])) + et_basic_event_element.append(reference_to_xml(obj.observed, NS_AAS + "observed")) + et_basic_event_element.append( + _generate_element(NS_AAS + "direction", text=_generic.DIRECTION[obj.direction]) + ) + et_basic_event_element.append( + _generate_element(NS_AAS + "state", text=_generic.STATE_OF_EVENT[obj.state]) + ) if obj.message_topic is not None: - et_basic_event_element.append(_generate_element(NS_AAS+"messageTopic", text=obj.message_topic)) + et_basic_event_element.append( + _generate_element(NS_AAS + "messageTopic", text=obj.message_topic) + ) if obj.message_broker is not None: - et_basic_event_element.append(reference_to_xml(obj.message_broker, NS_AAS+"messageBroker")) + et_basic_event_element.append( + reference_to_xml(obj.message_broker, NS_AAS + "messageBroker") + ) if obj.last_update is not None: - et_basic_event_element.append(_generate_element(NS_AAS+"lastUpdate", - text=model.datatypes.xsd_repr(obj.last_update))) + et_basic_event_element.append( + _generate_element( + NS_AAS + "lastUpdate", text=model.datatypes.xsd_repr(obj.last_update) + ) + ) if obj.min_interval is not None: - et_basic_event_element.append(_generate_element(NS_AAS+"minInterval", - text=model.datatypes.xsd_repr(obj.min_interval))) + et_basic_event_element.append( + _generate_element( + NS_AAS + "minInterval", text=model.datatypes.xsd_repr(obj.min_interval) + ) + ) if obj.max_interval is not None: - et_basic_event_element.append(_generate_element(NS_AAS+"maxInterval", - text=model.datatypes.xsd_repr(obj.max_interval))) + et_basic_event_element.append( + _generate_element( + NS_AAS + "maxInterval", text=model.datatypes.xsd_repr(obj.max_interval) + ) + ) return et_basic_event_element @@ -871,8 +1054,13 @@ def basic_event_element_to_xml(obj: model.BasicEventElement, tag: str = NS_AAS+" # general functions # ############################################################## -def _write_element(file: _generic.PathOrBinaryIO, element: etree._Element, **kwargs) -> None: - etree.ElementTree(element).write(file, encoding="UTF-8", xml_declaration=True, method="xml", **kwargs) + +def _write_element( + file: _generic.PathOrBinaryIO, element: etree._Element, **kwargs +) -> None: + etree.ElementTree(element).write( + file, encoding="UTF-8", xml_declaration=True, method="xml", **kwargs + ) def object_to_xml_element(obj: object) -> etree._Element: @@ -957,9 +1145,13 @@ def object_store_to_xml_element(data: model.AbstractObjectStore) -> etree._Eleme # serialize objects to XML root = etree.Element(NS_AAS + "environment", nsmap=_generic.XML_NS_MAP) if asset_administration_shells: - et_asset_administration_shells = etree.Element(NS_AAS + "assetAdministrationShells") + et_asset_administration_shells = etree.Element( + NS_AAS + "assetAdministrationShells" + ) for aas_obj in asset_administration_shells: - et_asset_administration_shells.append(asset_administration_shell_to_xml(aas_obj)) + et_asset_administration_shells.append( + asset_administration_shell_to_xml(aas_obj) + ) root.append(et_asset_administration_shells) if submodels: et_submodels = etree.Element(NS_AAS + "submodels") @@ -975,9 +1167,9 @@ def object_store_to_xml_element(data: model.AbstractObjectStore) -> etree._Eleme return root -def write_aas_xml_file(file: _generic.PathOrBinaryIO, - data: model.AbstractObjectStore, - **kwargs) -> None: +def write_aas_xml_file( + file: _generic.PathOrBinaryIO, data: model.AbstractObjectStore, **kwargs +) -> None: """ Write a set of AAS objects to an Asset Administration Shell XML file according to 'Details of the Asset Administration Shell', chapter 5.4 diff --git a/sdk/basyx/aas/backend/couchdb.py b/sdk/basyx/aas/backend/couchdb.py index fe02345eb..a3e383241 100644 --- a/sdk/basyx/aas/backend/couchdb.py +++ b/sdk/basyx/aas/backend/couchdb.py @@ -11,6 +11,7 @@ The :class:`~CouchDBIdentifiableStore` handles adding, deleting and otherwise managing the AAS objects in a specific CouchDB. """ + import threading import warnings import weakref @@ -89,7 +90,9 @@ def delete_couchdb_revision(url: str): del _revision_store[url] -class CouchDBIdentifiableStore(model.AbstractObjectStore[model.Identifier, model.Identifiable]): +class CouchDBIdentifiableStore( + model.AbstractObjectStore[model.Identifier, model.Identifiable] +): """ An ObjectStore implementation for :class:`~basyx.aas.model.base.Identifiable` BaSyx Python SDK objects backed by a CouchDB database server. @@ -99,6 +102,7 @@ class CouchDBIdentifiableStore(model.AbstractObjectStore[model.Identifier, model objects are thread-safe, as long as no CouchDB credentials are added (via ``register_credentials()``) during transactions. """ + def __init__(self, url: str, database: str): """ Initializer of class CouchDBIdentifiableStore @@ -114,8 +118,9 @@ def __init__(self, url: str, database: str): # local replication of each object is kept in the application and retrieving an object from the store always # returns the **same** (not only equal) object. Still, objects are forgotten, when they are not referenced # anywhere else to save memory. - self._object_cache: weakref.WeakValueDictionary[model.Identifier, model.Identifiable]\ - = weakref.WeakValueDictionary() + self._object_cache: weakref.WeakValueDictionary[ + model.Identifier, model.Identifiable + ] = weakref.WeakValueDictionary() self._object_cache_lock = threading.Lock() def check_database(self, create=False): @@ -128,7 +133,7 @@ def check_database(self, create=False): """ try: - self._do_request("{}/{}".format(self.url, self.database_name), 'HEAD') + self._do_request("{}/{}".format(self.url, self.database_name), "HEAD") except CouchDBServerError as e: # If an HTTPError is raised, re-raise it, unless it is a 404 error and we are requested to create the # database @@ -140,7 +145,7 @@ def check_database(self, create=False): # Create database logger.info("Creating CouchDB database %s/%s ...", self.url, self.database_name) - self._do_request("{}/{}".format(self.url, self.database_name), 'PUT') + self._do_request("{}/{}".format(self.url, self.database_name), "PUT") def get_identifiable_by_couchdb_id(self, couchdb_id: str) -> model.Identifiable: """ @@ -154,18 +159,34 @@ def get_identifiable_by_couchdb_id(self, couchdb_id: str) -> model.Identifiable: try: data = self._do_request( - "{}/{}/{}".format(self.url, self.database_name, urllib.parse.quote(couchdb_id, safe=''))) + "{}/{}/{}".format( + self.url, + self.database_name, + urllib.parse.quote(couchdb_id, safe=""), + ) + ) except CouchDBServerError as e: if e.code == 404: - raise KeyError("No Identifiable with couchdb-id {} found in CouchDB database".format(couchdb_id)) from e + raise KeyError( + "No Identifiable with couchdb-id {} found in CouchDB database".format( + couchdb_id + ) + ) from e raise - obj = data['data'] + obj = data["data"] if not isinstance(obj, model.Identifiable): - raise CouchDBResponseError("The CouchDB document with id {} does not contain an identifiable AAS object." - .format(couchdb_id)) - set_couchdb_revision("{}/{}/{}".format(self.url, self.database_name, urllib.parse.quote(couchdb_id, safe='')), - data["_rev"]) + raise CouchDBResponseError( + "The CouchDB document with id {} does not contain an identifiable AAS object.".format( + couchdb_id + ) + ) + set_couchdb_revision( + "{}/{}/{}".format( + self.url, self.database_name, urllib.parse.quote(couchdb_id, safe="") + ), + data["_rev"], + ) with self._object_cache_lock: if obj.id in self._object_cache: @@ -185,9 +206,15 @@ def get_item(self, identifier: model.Identifier) -> model.Identifiable: if identifier in self._object_cache: return self._object_cache[identifier] try: - return self.get_identifiable_by_couchdb_id(self._transform_id(identifier, False)) + return self.get_identifiable_by_couchdb_id( + self._transform_id(identifier, False) + ) except KeyError as e: - raise KeyError("No Identifiable with id {} found in CouchDB database".format(identifier)) from e + raise KeyError( + "No Identifiable with id {} found in CouchDB database".format( + identifier + ) + ) from e def add(self, x: model.Identifiable) -> None: """ @@ -199,21 +226,32 @@ def add(self, x: model.Identifiable) -> None: """ logger.debug("Adding object %s to CouchDB database ...", repr(x)) # Serialize data - data = json.dumps({'data': x}, cls=json_serialization.AASToJsonEncoder) + data = json.dumps({"data": x}, cls=json_serialization.AASToJsonEncoder) # Create and issue HTTP request (raises HTTPError on status != 200) try: response = self._do_request( - "{}/{}/{}".format(self.url, self.database_name, self._transform_id(x.id)), - 'PUT', - {'Content-type': 'application/json'}, - data.encode('utf-8')) - set_couchdb_revision("{}/{}/{}".format(self.url, self.database_name, self._transform_id(x.id)), - response["rev"]) + "{}/{}/{}".format( + self.url, self.database_name, self._transform_id(x.id) + ), + "PUT", + {"Content-type": "application/json"}, + data.encode("utf-8"), + ) + set_couchdb_revision( + "{}/{}/{}".format( + self.url, self.database_name, self._transform_id(x.id) + ), + response["rev"], + ) except CouchDBServerError as e: if e.code == 409: - raise KeyError("Identifiable with id {} already exists in CouchDB database".format(x.id)) from e + raise KeyError( + "Identifiable with id {} already exists in CouchDB database".format( + x.id + ) + ) from e raise with self._object_cache_lock: self._object_cache[x.id] = x @@ -228,25 +266,36 @@ def commit(self, x: model.Identifiable) -> None: :raises CouchDBError: If error occur during the request to the CouchDB server (see ``_do_request()`` for details) """ - doc_url = "{}/{}/{}".format(self.url, self.database_name, self._transform_id(x.id)) + doc_url = "{}/{}/{}".format( + self.url, self.database_name, self._transform_id(x.id) + ) rev = get_couchdb_revision(doc_url) if rev is None: - raise KeyError("No revision found for object with id {} — not fetched from this store".format(x.id)) - data = json.dumps({'data': x}, cls=json_serialization.AASToJsonEncoder) + raise KeyError( + "No revision found for object with id {} — not fetched from this store".format( + x.id + ) + ) + data = json.dumps({"data": x}, cls=json_serialization.AASToJsonEncoder) try: response = self._do_request( "{}?rev={}".format(doc_url, rev), - 'PUT', - {'Content-type': 'application/json'}, - data.encode('utf-8')) + "PUT", + {"Content-type": "application/json"}, + data.encode("utf-8"), + ) set_couchdb_revision(doc_url, response["rev"]) except CouchDBServerError as e: if e.code == 404: - raise KeyError("No AAS object with id {} exists in CouchDB database".format(x.id)) from e + raise KeyError( + "No AAS object with id {} exists in CouchDB database".format(x.id) + ) from e elif e.code == 409: raise CouchDBConflictError( - "Object with id {} has been modified in the database since it was last fetched." - .format(x.id)) from e + "Object with id {} has been modified in the database since it was last fetched.".format( + x.id + ) + ) from e raise def discard(self, x: model.Identifiable, safe_delete=False) -> None: @@ -264,12 +313,14 @@ def discard(self, x: model.Identifiable, safe_delete=False) -> None: (see ``_do_request()`` for details) """ logger.debug("Deleting object %s from CouchDB database ...", repr(x)) - rev = get_couchdb_revision("{}/{}/{}".format(self.url, - self.database_name, - self._transform_id(x.id))) + rev = get_couchdb_revision( + "{}/{}/{}".format(self.url, self.database_name, self._transform_id(x.id)) + ) if rev is not None and safe_delete: - logger.debug("using the object's stored revision token %s for deletion." % rev) + logger.debug( + "using the object's stored revision token %s for deletion." % rev + ) elif safe_delete: raise CouchDBConflictError("No CouchDBRevision found for the object") else: @@ -278,34 +329,52 @@ def discard(self, x: model.Identifiable, safe_delete=False) -> None: try: logger.debug("fetching the current object revision for deletion ...") headers = self._do_request( - "{}/{}/{}".format(self.url, self.database_name, self._transform_id(x.id)), 'HEAD') - rev = headers['ETag'][1:-1] + "{}/{}/{}".format( + self.url, self.database_name, self._transform_id(x.id) + ), + "HEAD", + ) + rev = headers["ETag"][1:-1] except CouchDBServerError as e: if e.code == 404: - raise KeyError("No AAS object with id {} exists in CouchDB database".format(x.id))\ - from e + raise KeyError( + "No AAS object with id {} exists in CouchDB database".format( + x.id + ) + ) from e raise try: self._do_request( - "{}/{}/{}?rev={}".format(self.url, self.database_name, self._transform_id(x.id), rev), - 'DELETE') + "{}/{}/{}?rev={}".format( + self.url, self.database_name, self._transform_id(x.id), rev + ), + "DELETE", + ) except CouchDBServerError as e: if e.code == 404: - raise KeyError("No AAS object with id {} exists in CouchDB database".format(x.id)) from e + raise KeyError( + "No AAS object with id {} exists in CouchDB database".format(x.id) + ) from e elif e.code == 409: raise CouchDBConflictError( "Object with id {} has been modified in the database since " - "the version requested to be deleted.".format(x.id)) from e + "the version requested to be deleted.".format(x.id) + ) from e raise - delete_couchdb_revision("{}/{}/{}".format(self.url, - self.database_name, - self._transform_id(x.id))) + delete_couchdb_revision( + "{}/{}/{}".format(self.url, self.database_name, self._transform_id(x.id)) + ) with self._object_cache_lock: del self._object_cache[x.id] @classmethod - def _do_request(cls, url: str, method: str = "GET", additional_headers: Dict[str, str] = {}, - body: Optional[bytes] = None) -> MutableMapping[str, Any]: + def _do_request( + cls, + url: str, + method: str = "GET", + additional_headers: Dict[str, str] = {}, + body: Optional[bytes] = None, + ) -> MutableMapping[str, Any]: """ Perform an HTTP(S) request to the CouchDBServer, parse the result and handle errors @@ -320,46 +389,80 @@ def _do_request(cls, url: str, method: str = "GET", additional_headers: Dict[str url_parts = urllib.parse.urlparse(url) host = url_parts.scheme + url_parts.netloc auth = _credentials_store.get(host) - headers = urllib3.make_headers(keep_alive=True, accept_encoding=True, - basic_auth="{}:{}".format(*auth) if auth else None) - headers['Accept'] = 'application/json' + headers = urllib3.make_headers( + keep_alive=True, + accept_encoding=True, + basic_auth="{}:{}".format(*auth) if auth else None, + ) + headers["Accept"] = "application/json" headers.update(additional_headers) try: - response = _http_pool_manager.request(method, url, headers=headers, body=body) - except (urllib3.exceptions.TimeoutError, urllib3.exceptions.SSLError, urllib3.exceptions.ProtocolError) as e: - raise CouchDBConnectionError("Error while connecting to the CouchDB server: {}".format(e)) from e + response = _http_pool_manager.request( + method, url, headers=headers, body=body + ) + except ( + urllib3.exceptions.TimeoutError, + urllib3.exceptions.SSLError, + urllib3.exceptions.ProtocolError, + ) as e: + raise CouchDBConnectionError( + "Error while connecting to the CouchDB server: {}".format(e) + ) from e except urllib3.exceptions.HTTPError as e: - raise CouchDBResponseError("Error while connecting to the CouchDB server: {}".format(e)) from e + raise CouchDBResponseError( + "Error while connecting to the CouchDB server: {}".format(e) + ) from e if not (200 <= response.status < 300): - logger.debug("Request %s %s finished with HTTP status code %s.", - method, url, response.status) - if response.headers.get('Content-type', None) != 'application/json': - raise CouchDBResponseError("Unexpected Content-type header {} of response from CouchDB server" - .format(response.headers.get('Content-type', None))) - - if method == 'HEAD': - raise CouchDBServerError(response.status, "", "", "HTTP {}".format(response.status)) + logger.debug( + "Request %s %s finished with HTTP status code %s.", + method, + url, + response.status, + ) + if response.headers.get("Content-type", None) != "application/json": + raise CouchDBResponseError( + "Unexpected Content-type header {} of response from CouchDB server".format( + response.headers.get("Content-type", None) + ) + ) + + if method == "HEAD": + raise CouchDBServerError( + response.status, "", "", "HTTP {}".format(response.status) + ) try: - data = json.loads(response.data.decode('utf-8')) + data = json.loads(response.data.decode("utf-8")) except json.JSONDecodeError: - raise CouchDBResponseError("Could not parse error message of HTTP {}" - .format(response.status)) - raise CouchDBServerError(response.status, data['error'], data['reason'], - "HTTP {}: {} (reason: {})".format(response.status, data['error'], data['reason'])) + raise CouchDBResponseError( + "Could not parse error message of HTTP {}".format(response.status) + ) + raise CouchDBServerError( + response.status, + data["error"], + data["reason"], + "HTTP {}: {} (reason: {})".format( + response.status, data["error"], data["reason"] + ), + ) # Check response & parse data logger.debug("Request %s %s finished successfully.", method, url) - if method == 'HEAD': + if method == "HEAD": return response.headers - if response.headers.get('Content-type') != 'application/json': + if response.headers.get("Content-type") != "application/json": raise CouchDBResponseError("Unexpected Content-type header") try: - data = json.loads(response.data.decode('utf-8'), cls=json_deserialization.AASFromJsonDecoder) + data = json.loads( + response.data.decode("utf-8"), + cls=json_deserialization.AASFromJsonDecoder, + ) except json.JSONDecodeError as e: - raise CouchDBResponseError("Could not parse CouchDB server response as JSON data.") from e + raise CouchDBResponseError( + "Could not parse CouchDB server response as JSON data." + ) from e return data def __contains__(self, x: object) -> bool: @@ -383,7 +486,11 @@ def __contains__(self, x: object) -> bool: try: self._do_request( - "{}/{}/{}".format(self.url, self.database_name, self._transform_id(identifier)), 'HEAD') + "{}/{}/{}".format( + self.url, self.database_name, self._transform_id(identifier) + ), + "HEAD", + ) except CouchDBServerError as e: if e.code == 404: return False @@ -400,7 +507,7 @@ def __len__(self) -> int: """ logger.debug("Fetching number of documents from database ...") data = self._do_request("{}/{}".format(self.url, self.database_name)) - return data['doc_count'] + return data["doc_count"] def __iter__(self) -> Iterator[model.Identifiable]: """ @@ -412,6 +519,7 @@ def __iter__(self) -> Iterator[model.Identifiable]: :raises CouchDBError: If error occur during fetching the list of objects from the CouchDB server (see ``_do_request()`` for details) """ + # Iterator class storing the list of ids and fetching Identifiable objects on the fly class CouchDBIdentifiableIterator(Iterator[model.Identifiable]): def __init__(self, store: CouchDBIdentifiableStore, ids: Iterable[str]): @@ -425,7 +533,7 @@ def __next__(self): # Fetch a list of all ids and construct Iterator object logger.debug("Creating iterator over objects in database ...") data = self._do_request("{}/{}/_all_docs".format(self.url, self.database_name)) - return CouchDBIdentifiableIterator(self, (row['id'] for row in data['rows'])) + return CouchDBIdentifiableIterator(self, (row["id"] for row in data["rows"])) @staticmethod def _transform_id(identifier: model.Identifier, url_quote=True) -> str: @@ -435,7 +543,7 @@ def _transform_id(identifier: model.Identifier, url_quote=True) -> str: :param url_quote: If True, the result id string is url-encoded to be used in an HTTP request URL """ if url_quote: - identifier = urllib.parse.quote(identifier, safe='') + identifier = urllib.parse.quote(identifier, safe="") return identifier @@ -444,6 +552,7 @@ class CouchDBObjectStore(CouchDBIdentifiableStore): `CouchDBObjectStore` has been renamed to :class:`~.CouchDBIdentifiableStore` and will be removed in a future release. Please migrate to :class:`~.CouchDBIdentifiableStore`. """ + def __init__(self, url: str, database: str): warnings.warn( "`CouchDBObjectStore` is deprecated and will be removed in a future release. Use " @@ -465,23 +574,27 @@ def get_identifiable(self, identifier: model.Identifier) -> model.Identifiable: # ################################################################################################# # Custom Exception classes for reporting errors during interaction with the CouchDB server + class CouchDBError(Exception): pass class CouchDBConnectionError(CouchDBError): """Exception raised when the CouchDB server could not be reached""" + pass class CouchDBResponseError(CouchDBError): """Exception raised by when an HTTP of the CouchDB server could not be handled (e.g. no JSON body)""" + pass class CouchDBServerError(CouchDBError): """Exception raised when the CouchDB server returns an unexpected error code""" + def __init__(self, code: int, error: str, reason: str, *args): super().__init__(*args) self.code = code @@ -491,4 +604,5 @@ def __init__(self, code: int, error: str, reason: str, *args): class CouchDBConflictError(CouchDBError): """Exception raised when an object could not be committed due to a concurrent modification in the database""" + pass diff --git a/sdk/basyx/aas/backend/local_file.py b/sdk/basyx/aas/backend/local_file.py index df21ddfee..e87c4aa78 100644 --- a/sdk/basyx/aas/backend/local_file.py +++ b/sdk/basyx/aas/backend/local_file.py @@ -11,6 +11,7 @@ The :class:`~LocalFileIdentifiableStore` handles adding, deleting and otherwise managing the AAS objects in a specific Directory. """ + from typing import Iterator import logging import json @@ -28,7 +29,9 @@ logger = logging.getLogger(__name__) -class LocalFileIdentifiableStore(model.AbstractObjectStore[model.Identifier, model.Identifiable]): +class LocalFileIdentifiableStore( + model.AbstractObjectStore[model.Identifier, model.Identifiable] +): """ An ObjectStore implementation for :class:`~basyx.aas.model.base.Identifiable` BaSyx Python SDK objects backed by a local file based local backend @@ -40,6 +43,7 @@ class LocalFileIdentifiableStore(model.AbstractObjectStore[model.Identifier, mod with the last writer winning and no error raised. Use a dedicated database backend for any production deployment. """ + def __init__(self, directory_path: str): """ Initializer of class LocalFileIdentifiableStore @@ -53,8 +57,9 @@ def __init__(self, directory_path: str): # local replication of each object is kept in the application and retrieving an object from the store always # returns the **same** (not only equal) object. Still, objects are forgotten, when they are not referenced # anywhere else to save memory. - self._object_cache: weakref.WeakValueDictionary[model.Identifier, model.Identifiable] \ - = weakref.WeakValueDictionary() + self._object_cache: weakref.WeakValueDictionary[ + model.Identifier, model.Identifiable + ] = weakref.WeakValueDictionary() self._object_cache_lock = threading.Lock() def check_directory(self, create=False): @@ -65,7 +70,11 @@ def check_directory(self, create=False): """ if not os.path.exists(self.directory_path): if not create: - raise FileNotFoundError("The given directory ({}) does not exist".format(self.directory_path)) + raise FileNotFoundError( + "The given directory ({}) does not exist".format( + self.directory_path + ) + ) # Create directory os.mkdir(self.directory_path) logger.info("Creating directory {}".format(self.directory_path)) @@ -81,7 +90,11 @@ def get_identifiable_by_hash(self, hash_: str) -> model.Identifiable: data = json.load(file, cls=json_deserialization.AASFromJsonDecoder) obj = data["data"] except FileNotFoundError as e: - raise KeyError("No Identifiable with hash {} found in local file database".format(hash_)) from e + raise KeyError( + "No Identifiable with hash {} found in local file database".format( + hash_ + ) + ) from e with self._object_cache_lock: if obj.id in self._object_cache: return self._object_cache[obj.id] @@ -100,7 +113,11 @@ def get_item(self, identifier: model.Identifier) -> model.Identifiable: try: return self.get_identifiable_by_hash(self._transform_id(identifier)) except KeyError as e: - raise KeyError("No Identifiable with id {} found in local file database".format(identifier)) from e + raise KeyError( + "No Identifiable with id {} found in local file database".format( + identifier + ) + ) from e def _write_atomic(self, x: model.Identifiable) -> None: """ @@ -113,7 +130,12 @@ def _write_atomic(self, x: model.Identifiable) -> None: tmp_fd, tmp_path = tempfile.mkstemp(dir=self.directory_path, suffix=".tmp") try: with os.fdopen(tmp_fd, "w") as tmp_file: - json.dump({"data": x}, tmp_file, cls=json_serialization.AASToJsonEncoder, indent=4) + json.dump( + {"data": x}, + tmp_file, + cls=json_serialization.AASToJsonEncoder, + indent=4, + ) os.replace(tmp_path, final_path) # Catch all `Exception`s, as well as `KeyboardInterrupt` and `SystemExit` too, so the temp # file is never left behind even if the process is being torn down: @@ -128,8 +150,14 @@ def add(self, x: model.Identifiable) -> None: :raises KeyError: If an object with the same id exists already in the object store """ logger.debug("Adding object %s to Local File Store ...", repr(x)) - if os.path.exists("{}/{}.json".format(self.directory_path, self._transform_id(x.id))): - raise KeyError("Identifiable with id {} already exists in local file database".format(x.id)) + if os.path.exists( + "{}/{}.json".format(self.directory_path, self._transform_id(x.id)) + ): + raise KeyError( + "Identifiable with id {} already exists in local file database".format( + x.id + ) + ) self._write_atomic(x) with self._object_cache_lock: self._object_cache[x.id] = x @@ -141,8 +169,12 @@ def commit(self, x: model.Identifiable) -> None: :param x: The object to persist :raises KeyError: If the object is not present in the store """ - if not os.path.exists("{}/{}.json".format(self.directory_path, self._transform_id(x.id))): - raise KeyError("No AAS object with id {} exists in local file database".format(x.id)) + if not os.path.exists( + "{}/{}.json".format(self.directory_path, self._transform_id(x.id)) + ): + raise KeyError( + "No AAS object with id {} exists in local file database".format(x.id) + ) self._write_atomic(x) def discard(self, x: model.Identifiable) -> None: @@ -154,9 +186,13 @@ def discard(self, x: model.Identifiable) -> None: """ logger.debug("Deleting object %s from Local File Store database ...", repr(x)) try: - os.remove("{}/{}.json".format(self.directory_path, self._transform_id(x.id))) + os.remove( + "{}/{}.json".format(self.directory_path, self._transform_id(x.id)) + ) except FileNotFoundError as e: - raise KeyError("No AAS object with id {} exists in local file database".format(x.id)) from e + raise KeyError( + "No AAS object with id {} exists in local file database".format(x.id) + ) from e with self._object_cache_lock: self._object_cache.pop(x.id, None) @@ -176,7 +212,9 @@ def __contains__(self, x: object) -> bool: else: return False logger.debug("Checking existence of object with id %s in database ...", repr(x)) - return os.path.exists("{}/{}.json".format(self.directory_path, self._transform_id(identifier))) + return os.path.exists( + "{}/{}.json".format(self.directory_path, self._transform_id(identifier)) + ) def __len__(self) -> int: """ @@ -185,7 +223,9 @@ def __len__(self) -> int: :return: The number of objects (determined from the number of documents) """ logger.debug("Fetching number of documents from database ...") - return sum(1 for f in os.listdir(self.directory_path) if f.lower().endswith(".json")) + return sum( + 1 for f in os.listdir(self.directory_path) if f.lower().endswith(".json") + ) def __iter__(self) -> Iterator[model.Identifiable]: """ @@ -212,6 +252,7 @@ class LocalFileObjectStore(LocalFileIdentifiableStore): `LocalFileObjectStore` has been renamed to :class:`~.LocalFileIdentifiableStore` and will be removed in a future release. Please migrate to :class:`~.LocalFileIdentifiableStore`. """ + def __init__(self, directory_path: str): warnings.warn( "`LocalFileObjectStore` is deprecated and will be removed in a future release. Use " diff --git a/sdk/basyx/aas/examples/data/__init__.py b/sdk/basyx/aas/examples/data/__init__.py index 3dce392cb..fa8a1cb40 100644 --- a/sdk/basyx/aas/examples/data/__init__.py +++ b/sdk/basyx/aas/examples/data/__init__.py @@ -17,13 +17,18 @@ Module for the creation of an example submodel template containing all kind of submodel elements where the kind is always TEMPLATE. """ + import os from basyx.aas import model -from basyx.aas.examples.data import example_aas_missing_attributes, example_aas, \ - example_aas_mandatory_attributes, example_submodel_template +from basyx.aas.examples.data import ( + example_aas_missing_attributes, + example_aas, + example_aas_mandatory_attributes, + example_submodel_template, +) -TEST_PDF_FILE = os.path.join(os.path.dirname(__file__), 'TestFile.pdf') +TEST_PDF_FILE = os.path.join(os.path.dirname(__file__), "TestFile.pdf") def create_example() -> model.DictIdentifiableStore: @@ -33,7 +38,9 @@ def create_example() -> model.DictIdentifiableStore: :return: object store """ - identifiable_store: model.DictIdentifiableStore[model.Identifiable] = model.DictIdentifiableStore() + identifiable_store: model.DictIdentifiableStore[model.Identifiable] = ( + model.DictIdentifiableStore() + ) identifiable_store.update(example_aas.create_full_example()) identifiable_store.update(example_aas_mandatory_attributes.create_full_example()) identifiable_store.update(example_aas_missing_attributes.create_full_example()) @@ -49,18 +56,24 @@ def create_example_aas_binding() -> model.DictIdentifiableStore: :return: object store """ - identifiable_store: model.DictIdentifiableStore[model.Identifiable] = model.DictIdentifiableStore() + identifiable_store: model.DictIdentifiableStore[model.Identifiable] = ( + model.DictIdentifiableStore() + ) identifiable_store.update(example_aas.create_full_example()) identifiable_store.update(example_aas_mandatory_attributes.create_full_example()) identifiable_store.update(example_aas_missing_attributes.create_full_example()) identifiable_store.add(example_submodel_template.create_example_submodel_template()) - aas = identifiable_store.get_item('https://example.org/Test_AssetAdministrationShell') - sm = identifiable_store.get_item('https://example.org/Test_Submodel_Template') - assert (isinstance(aas, model.aas.AssetAdministrationShell)) # make mypy happy - assert (isinstance(sm, model.submodel.Submodel)) # make mypy happy + aas = identifiable_store.get_item( + "https://example.org/Test_AssetAdministrationShell" + ) + sm = identifiable_store.get_item("https://example.org/Test_Submodel_Template") + assert isinstance(aas, model.aas.AssetAdministrationShell) # make mypy happy + assert isinstance(sm, model.submodel.Submodel) # make mypy happy aas.submodel.add(model.ModelReference.from_referable(sm)) - cd = identifiable_store.get_item('https://example.org/Test_ConceptDescription_Mandatory') - assert (isinstance(cd, model.concept.ConceptDescription)) # make mypy happy + cd = identifiable_store.get_item( + "https://example.org/Test_ConceptDescription_Mandatory" + ) + assert isinstance(cd, model.concept.ConceptDescription) # make mypy happy return identifiable_store diff --git a/sdk/basyx/aas/examples/data/_helper.py b/sdk/basyx/aas/examples/data/_helper.py index 4093de32f..a4ffcf6be 100644 --- a/sdk/basyx/aas/examples/data/_helper.py +++ b/sdk/basyx/aas/examples/data/_helper.py @@ -10,13 +10,27 @@ .. warning:: This module is intended for internal use only. """ + import pprint -from typing import List, NamedTuple, Iterator, Dict, Any, Type, Union, Set, Iterable, TypeVar +from typing import ( + List, + NamedTuple, + Iterator, + Dict, + Any, + Type, + Union, + Set, + Iterable, + TypeVar, +) from ... import model -_LIST_OR_COLLECTION = TypeVar("_LIST_OR_COLLECTION", model.SubmodelElementList, model.SubmodelElementCollection) +_LIST_OR_COLLECTION = TypeVar( + "_LIST_OR_COLLECTION", model.SubmodelElementList, model.SubmodelElementCollection +) class CheckResult(NamedTuple): @@ -25,10 +39,14 @@ class CheckResult(NamedTuple): data: Dict[str, Any] def __repr__(self): - return "{}: {} ({})".format("OK " if self.result else "FAIL", - self.expectation, - ", ".join("{}={}".format(k, pprint.pformat(v, depth=2, width=2 ** 14, compact=True)) - for k, v in self.data.items())) + return "{}: {} ({})".format( + "OK " if self.result else "FAIL", + self.expectation, + ", ".join( + "{}={}".format(k, pprint.pformat(v, depth=2, width=2**14, compact=True)) + for k, v in self.data.items() + ), + ) class DataChecker: @@ -93,8 +111,10 @@ def raise_failed(self) -> None: """ failed = list(self.failed_checks) if len(failed) > 0: - raise AssertionError("{} of {} checks failed".format(len(failed), len(self.checks)), - [f.expectation for f in failed]) + raise AssertionError( + "{} of {} checks failed".format(len(failed), len(self.checks)), + [f.expectation for f in failed], + ) class AASDataChecker(DataChecker): @@ -102,12 +122,16 @@ def __init__(self, check_extensions: bool = True, **kwargs): super().__init__(**kwargs) self.check_extensions = check_extensions - def _check_submodel_element(self, object_: model.SubmodelElement, expected_object: model.SubmodelElement): + def _check_submodel_element( + self, object_: model.SubmodelElement, expected_object: model.SubmodelElement + ): if self.check_is_instance(object_, expected_object.__class__): if isinstance(object_, model.Property): return self.check_property_equal(object_, expected_object) # type: ignore if isinstance(object_, model.MultiLanguageProperty): - return self.check_multi_language_property_equal(object_, expected_object) # type: ignore + return self.check_multi_language_property_equal( + object_, expected_object + ) # type: ignore if isinstance(object_, model.Range): return self.check_range_equal(object_, expected_object) # type: ignore if isinstance(object_, model.Blob): @@ -117,11 +141,15 @@ def _check_submodel_element(self, object_: model.SubmodelElement, expected_objec if isinstance(object_, model.ReferenceElement): return self.check_reference_element_equal(object_, expected_object) # type: ignore if isinstance(object_, model.SubmodelElementCollection): - return self.check_submodel_element_collection_equal(object_, expected_object) # type: ignore + return self.check_submodel_element_collection_equal( + object_, expected_object + ) # type: ignore if isinstance(object_, model.SubmodelElementList): return self.check_submodel_element_list_equal(object_, expected_object) # type: ignore if isinstance(object_, model.AnnotatedRelationshipElement): - return self.check_annotated_relationship_element_equal(object_, expected_object) # type: ignore + return self.check_annotated_relationship_element_equal( + object_, expected_object + ) # type: ignore if isinstance(object_, model.RelationshipElement): return self.check_relationship_element_equal(object_, expected_object) # type: ignore if isinstance(object_, model.Operation): @@ -133,9 +161,11 @@ def _check_submodel_element(self, object_: model.SubmodelElement, expected_objec if isinstance(object_, model.BasicEventElement): return self.check_basic_event_element_equal(object_, expected_object) # type: ignore else: - raise AttributeError('Submodel Element class not implemented') + raise AttributeError("Submodel Element class not implemented") - def _check_has_extension_equal(self, object_: model.HasExtension, expected_object: model.HasExtension): + def _check_has_extension_equal( + self, object_: model.HasExtension, expected_object: model.HasExtension + ): """ Checks if the HasExtension object_ has the same HasExtension attributes as the expected_value object and adds / stores the check result for later analysis. @@ -146,16 +176,26 @@ def _check_has_extension_equal(self, object_: model.HasExtension, expected_objec """ if not self.check_extensions: return - self.check_contained_element_length(object_, 'extension', model.Extension, len(expected_object.extension)) + self.check_contained_element_length( + object_, "extension", model.Extension, len(expected_object.extension) + ) for expected_extension in expected_object.extension: - extension = object_.extension.get('name', expected_extension.name) - if self.check(extension is not None, f'{expected_extension!r} must exist'): + extension = object_.extension.get("name", expected_extension.name) + if self.check(extension is not None, f"{expected_extension!r} must exist"): self._check_extension_equal(extension, expected_extension) # type: ignore - found_extensions = self._find_extra_namespace_set_elements_by_name(object_.extension, expected_object.extension) - self.check(found_extensions == set(), f'{object_!r} must not have extra extensions', value=found_extensions) - - def _check_extension_equal(self, object_: model.Extension, expected_object: model.Extension): + found_extensions = self._find_extra_namespace_set_elements_by_name( + object_.extension, expected_object.extension + ) + self.check( + found_extensions == set(), + f"{object_!r} must not have extra extensions", + value=found_extensions, + ) + + def _check_extension_equal( + self, object_: model.Extension, expected_object: model.Extension + ): """ Checks if the Extension object_ has the same Extension attributes as the expected_value object and adds / stores the check result for later analysis. @@ -165,12 +205,14 @@ def _check_extension_equal(self, object_: model.Extension, expected_object: mode :return: The value of expression to be used in control statements """ self._check_has_semantics_equal(object_, expected_object) - self.check_attribute_equal(object_, 'name', expected_object.name) - self.check_attribute_equal(object_, 'value_type', expected_object.value_type) - self.check_attribute_equal(object_, 'value', expected_object.value) - self.check_attribute_equal(object_, 'refers_to', expected_object.refers_to) + self.check_attribute_equal(object_, "name", expected_object.name) + self.check_attribute_equal(object_, "value_type", expected_object.value_type) + self.check_attribute_equal(object_, "value", expected_object.value) + self.check_attribute_equal(object_, "refers_to", expected_object.refers_to) - def _check_referable_equal(self, object_: model.Referable, expected_object: model.Referable): + def _check_referable_equal( + self, object_: model.Referable, expected_object: model.Referable + ): """ Checks if the referable object_ has the same referable attributes as the expected_value object and adds / stores the check result for later analysis. @@ -185,10 +227,14 @@ def _check_referable_equal(self, object_: model.Referable, expected_object: mode 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) + self.check_attribute_equal( + object_, "display_name", expected_object.display_name + ) self._check_has_extension_equal(object_, expected_object) - def _check_identifiable_equal(self, object_: model.Identifiable, expected_object: model.Identifiable): + def _check_identifiable_equal( + self, object_: model.Identifiable, expected_object: model.Identifiable + ): """ Checks if the identifiable object_ has the same identifiable attributes as the expected_value object and adds / stores the check result for later analysis. @@ -198,12 +244,21 @@ def _check_identifiable_equal(self, object_: model.Identifiable, expected_object :return: The value of expression to be used in control statements """ self._check_referable_equal(object_, expected_object) - self.check_attribute_equal(object_, "administration", expected_object.administration) - if object_.administration is not None and expected_object.administration is not None: - self._check_has_data_specification_equal(object_.administration, expected_object.administration) + self.check_attribute_equal( + object_, "administration", expected_object.administration + ) + if ( + object_.administration is not None + and expected_object.administration is not None + ): + self._check_has_data_specification_equal( + object_.administration, expected_object.administration + ) self.check_attribute_equal(object_, "id", expected_object.id) - def _check_has_semantics_equal(self, object_: model.HasSemantics, expected_object: model.HasSemantics): + def _check_has_semantics_equal( + self, object_: model.HasSemantics, expected_object: model.HasSemantics + ): """ Checks if the HasSemantic object_ has the same HasSemantics attributes as the expected_value object and adds / stores the check result for later analysis. @@ -226,16 +281,29 @@ def _check_has_semantics_equal(self, object_: model.HasSemantics, expected_objec ), ) for suppl_semantic_id in expected_object.supplemental_semantic_id: - given_semantic_id = self._find_reference(suppl_semantic_id, object_.supplemental_semantic_id) - self.check(given_semantic_id is not None, f"{object_!r} must have supplementalSemanticId", - value=suppl_semantic_id) - - found_elements = self._find_extra_object(object_.supplemental_semantic_id, - expected_object.supplemental_semantic_id, model.Reference) - self.check(found_elements == set(), '{} must not have extra supplementalSemanticId'.format(repr(object_)), - value=found_elements) + given_semantic_id = self._find_reference( + suppl_semantic_id, object_.supplemental_semantic_id + ) + self.check( + given_semantic_id is not None, + f"{object_!r} must have supplementalSemanticId", + value=suppl_semantic_id, + ) - def _check_has_kind_equal(self, object_: model.HasKind, expected_object: model.HasKind): + found_elements = self._find_extra_object( + object_.supplemental_semantic_id, + expected_object.supplemental_semantic_id, + model.Reference, + ) + self.check( + found_elements == set(), + "{} must not have extra supplementalSemanticId".format(repr(object_)), + value=found_elements, + ) + + def _check_has_kind_equal( + self, object_: model.HasKind, expected_object: model.HasKind + ): """ Checks if the HasKind object_ has the same HasKind attributes as the expected_value object and adds / stores the check result for later analysis. @@ -246,7 +314,9 @@ def _check_has_kind_equal(self, object_: model.HasKind, expected_object: model.H """ self.check_attribute_equal(object_, "kind", expected_object.kind) - def _check_qualifiable_equal(self, object_: model.Qualifiable, expected_object: model.Qualifiable): + def _check_qualifiable_equal( + self, object_: model.Qualifiable, expected_object: model.Qualifiable + ): """ Checks if the qualifiable object_ has the same qualifiables attributes as the expected_value object and adds / stores the check result for later analysis. @@ -255,22 +325,35 @@ def _check_qualifiable_equal(self, object_: model.Qualifiable, expected_object: :param expected_object: The expected qualifiable object :return: The value of expression to be used in control statements """ - self.check_contained_element_length(object_, 'qualifier', model.Qualifier, len(expected_object.qualifier)) + self.check_contained_element_length( + object_, "qualifier", model.Qualifier, len(expected_object.qualifier) + ) for expected_element in expected_object.qualifier: - element = self._find_element_by_attribute(expected_element, list(object_.qualifier), 'type') - if self.check(element is not None, '{} must exist'.format(repr(expected_element))): + element = self._find_element_by_attribute( + expected_element, list(object_.qualifier), "type" + ) + if self.check( + element is not None, "{} must exist".format(repr(expected_element)) + ): if isinstance(element, model.Qualifier): self._check_qualifier_equal(element, expected_element) # type: ignore else: - raise TypeError('Qualifier class not implemented') - - found_elements = self._find_extra_elements_by_attribute(list(object_.qualifier), - list(expected_object.qualifier), 'type') - self.check(found_elements == set(), 'Qualifiable Element {} must not have extra elements'.format(repr(object_)), - value=found_elements) - - def _check_has_data_specification_equal(self, object_: model.HasDataSpecification, - expected_object: model.HasDataSpecification): + raise TypeError("Qualifier class not implemented") + + found_elements = self._find_extra_elements_by_attribute( + list(object_.qualifier), list(expected_object.qualifier), "type" + ) + self.check( + found_elements == set(), + "Qualifiable Element {} must not have extra elements".format(repr(object_)), + value=found_elements, + ) + + def _check_has_data_specification_equal( + self, + object_: model.HasDataSpecification, + expected_object: model.HasDataSpecification, + ): """ Checks if the HasDataSpecification object_ has the same HasDataSpecification attributes as the expected_value object and adds / stores the check result for later analysis. @@ -278,24 +361,43 @@ def _check_has_data_specification_equal(self, object_: model.HasDataSpecificatio :param object_: The HasDataSpecification object which shall be checked :param expected_object: The expected HasDataSpecification object """ - self.check_contained_element_length(object_, 'embedded_data_specifications', model.EmbeddedDataSpecification, - len(expected_object.embedded_data_specifications)) + self.check_contained_element_length( + object_, + "embedded_data_specifications", + model.EmbeddedDataSpecification, + len(expected_object.embedded_data_specifications), + ) for expected_dspec in expected_object.embedded_data_specifications: - given_dspec = self._find_element_by_attribute(expected_dspec, object_.embedded_data_specifications, - 'data_specification') - if self.check(given_dspec is not None, 'EmbeddedDataSpecification {} must exist in {}'.format( - repr(expected_dspec.data_specification), repr(object_))): - self.check_data_specification_content_equal(given_dspec.data_specification_content, # type: ignore - expected_dspec.data_specification_content) - - found_elements = self._find_extra_elements_by_attribute(object_.embedded_data_specifications, - expected_object.embedded_data_specifications, - 'data_specification') - self.check(found_elements == set(), '{} must not have extra data specifications'.format(repr(object_)), - value=found_elements) - - def _check_abstract_attributes_submodel_element_equal(self, object_: model.SubmodelElement, - expected_value: model.SubmodelElement): + given_dspec = self._find_element_by_attribute( + expected_dspec, + object_.embedded_data_specifications, + "data_specification", + ) + if self.check( + given_dspec is not None, + "EmbeddedDataSpecification {} must exist in {}".format( + repr(expected_dspec.data_specification), repr(object_) + ), + ): + self.check_data_specification_content_equal( + given_dspec.data_specification_content, # type: ignore + expected_dspec.data_specification_content, + ) + + found_elements = self._find_extra_elements_by_attribute( + object_.embedded_data_specifications, + expected_object.embedded_data_specifications, + "data_specification", + ) + self.check( + found_elements == set(), + "{} must not have extra data specifications".format(repr(object_)), + value=found_elements, + ) + + def _check_abstract_attributes_submodel_element_equal( + self, object_: model.SubmodelElement, expected_value: model.SubmodelElement + ): """ Checks if the given SubmodelElement objects are equal @@ -308,8 +410,9 @@ def _check_abstract_attributes_submodel_element_equal(self, object_: model.Submo self._check_qualifiable_equal(object_, expected_value) self._check_has_data_specification_equal(object_, expected_value) - def _check_submodel_elements_equal_unordered(self, object_: _LIST_OR_COLLECTION, - expected_value: _LIST_OR_COLLECTION): + def _check_submodel_elements_equal_unordered( + self, object_: _LIST_OR_COLLECTION, expected_value: _LIST_OR_COLLECTION + ): """ Checks if the given SubmodelElement objects are equal (in any order) @@ -322,13 +425,23 @@ def _check_submodel_elements_equal_unordered(self, object_: _LIST_OR_COLLECTION, element = object_.get_referable(expected_element.id_short) self._check_submodel_element(element, expected_element) # type: ignore except KeyError: - self.check(False, 'Submodel Element {} must exist'.format(repr(expected_element))) - - found_elements = self._find_extra_namespace_set_elements_by_id_short(object_.value, expected_value.value) - self.check(found_elements == set(), '{} must not have extra elements'.format(repr(object_)), - value=found_elements) - - def check_property_equal(self, object_: model.Property, expected_value: model.Property): + self.check( + False, + "Submodel Element {} must exist".format(repr(expected_element)), + ) + + found_elements = self._find_extra_namespace_set_elements_by_id_short( + object_.value, expected_value.value + ) + self.check( + found_elements == set(), + "{} must not have extra elements".format(repr(object_)), + value=found_elements, + ) + + def check_property_equal( + self, object_: model.Property, expected_value: model.Property + ): """ Checks if the given Property objects are equal @@ -337,12 +450,15 @@ def check_property_equal(self, object_: model.Property, expected_value: model.Pr :return: """ self._check_abstract_attributes_submodel_element_equal(object_, expected_value) - self.check_attribute_equal(object_, 'value_type', expected_value.value_type) - self.check_attribute_equal(object_, 'value', expected_value.value) - self.check_attribute_equal(object_, 'value_id', expected_value.value_id) + self.check_attribute_equal(object_, "value_type", expected_value.value_type) + self.check_attribute_equal(object_, "value", expected_value.value) + self.check_attribute_equal(object_, "value_id", expected_value.value_id) - def check_multi_language_property_equal(self, object_: model.MultiLanguageProperty, - expected_value: model.MultiLanguageProperty): + def check_multi_language_property_equal( + self, + object_: model.MultiLanguageProperty, + expected_value: model.MultiLanguageProperty, + ): """ Checks if the given MultiLanguageProperty objects are equal @@ -351,8 +467,8 @@ def check_multi_language_property_equal(self, object_: model.MultiLanguageProper :return: """ self._check_abstract_attributes_submodel_element_equal(object_, expected_value) - self.check_attribute_equal(object_, 'value', expected_value.value) - self.check_attribute_equal(object_, 'value_id', expected_value.value_id) + self.check_attribute_equal(object_, "value", expected_value.value) + self.check_attribute_equal(object_, "value_id", expected_value.value_id) def check_range_equal(self, object_: model.Range, expected_value: model.Range): """ @@ -363,9 +479,9 @@ def check_range_equal(self, object_: model.Range, expected_value: model.Range): :return: """ self._check_abstract_attributes_submodel_element_equal(object_, expected_value) - self.check_attribute_equal(object_, 'value_type', expected_value.value_type) - self.check_attribute_equal(object_, 'min', expected_value.min) - self.check_attribute_equal(object_, 'max', expected_value.max) + self.check_attribute_equal(object_, "value_type", expected_value.value_type) + self.check_attribute_equal(object_, "min", expected_value.min) + self.check_attribute_equal(object_, "max", expected_value.max) def check_blob_equal(self, object_: model.Blob, expected_value: model.Blob): """ @@ -376,8 +492,8 @@ def check_blob_equal(self, object_: model.Blob, expected_value: model.Blob): :return: """ self._check_abstract_attributes_submodel_element_equal(object_, expected_value) - self.check_attribute_equal(object_, 'value', expected_value.value) - self.check_attribute_equal(object_, 'content_type', expected_value.content_type) + self.check_attribute_equal(object_, "value", expected_value.value) + self.check_attribute_equal(object_, "content_type", expected_value.content_type) def check_file_equal(self, object_: model.File, expected_value: model.File): """ @@ -388,10 +504,12 @@ def check_file_equal(self, object_: model.File, expected_value: model.File): :return: """ self._check_abstract_attributes_submodel_element_equal(object_, expected_value) - self.check_attribute_equal(object_, 'value', expected_value.value) - self.check_attribute_equal(object_, 'content_type', expected_value.content_type) + self.check_attribute_equal(object_, "value", expected_value.value) + self.check_attribute_equal(object_, "content_type", expected_value.content_type) - def check_resource_equal(self, object_: model.Resource, expected_value: model.Resource): + def check_resource_equal( + self, object_: model.Resource, expected_value: model.Resource + ): """ Checks if the given Resource objects are equal @@ -399,10 +517,12 @@ def check_resource_equal(self, object_: model.Resource, expected_value: model.Re :param expected_value: expected Resource object :return: """ - self.check_attribute_equal(object_, 'path', expected_value.path) - self.check_attribute_equal(object_, 'content_type', expected_value.content_type) + self.check_attribute_equal(object_, "path", expected_value.path) + self.check_attribute_equal(object_, "content_type", expected_value.content_type) - def check_reference_element_equal(self, object_: model.ReferenceElement, expected_value: model.ReferenceElement): + def check_reference_element_equal( + self, object_: model.ReferenceElement, expected_value: model.ReferenceElement + ): """ Checks if the given ReferenceElement objects are equal @@ -411,10 +531,13 @@ def check_reference_element_equal(self, object_: model.ReferenceElement, expecte :return: """ self._check_abstract_attributes_submodel_element_equal(object_, expected_value) - self.check_attribute_equal(object_, 'value', expected_value.value) + self.check_attribute_equal(object_, "value", expected_value.value) - def check_submodel_element_collection_equal(self, object_: model.SubmodelElementCollection, - expected_value: model.SubmodelElementCollection): + def check_submodel_element_collection_equal( + self, + object_: model.SubmodelElementCollection, + expected_value: model.SubmodelElementCollection, + ): """ Checks if the given SubmodelElementCollection objects are equal @@ -424,11 +547,16 @@ def check_submodel_element_collection_equal(self, object_: model.SubmodelElement """ # the submodel elements are compared unordered, as collections are unordered self._check_abstract_attributes_submodel_element_equal(object_, expected_value) - self.check_contained_element_length(object_, 'value', model.SubmodelElement, len(expected_value.value)) + self.check_contained_element_length( + object_, "value", model.SubmodelElement, len(expected_value.value) + ) self._check_submodel_elements_equal_unordered(object_, expected_value) - def check_submodel_element_list_equal(self, object_: model.SubmodelElementList, - expected_value: model.SubmodelElementList): + def check_submodel_element_list_equal( + self, + object_: model.SubmodelElementList, + expected_value: model.SubmodelElementList, + ): """ Checks if the given SubmodelElementList objects are equal @@ -437,23 +565,37 @@ def check_submodel_element_list_equal(self, object_: model.SubmodelElementList, :return: """ self._check_abstract_attributes_submodel_element_equal(object_, expected_value) - self.check_attribute_equal(object_, 'order_relevant', expected_value.order_relevant) - self.check_attribute_equal(object_, 'semantic_id_list_element', expected_value.semantic_id_list_element) - self.check_attribute_equal(object_, 'value_type_list_element', expected_value.value_type_list_element) - 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)) + self.check_attribute_equal( + object_, "order_relevant", expected_value.order_relevant + ) + self.check_attribute_equal( + object_, "semantic_id_list_element", expected_value.semantic_id_list_element + ) + self.check_attribute_equal( + object_, "value_type_list_element", expected_value.value_type_list_element + ) + 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 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!") + 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): + def check_relationship_element_equal( + self, + object_: model.RelationshipElement, + expected_value: model.RelationshipElement, + ): """ Checks if the given RelationshipElement objects are equal @@ -462,11 +604,14 @@ def check_relationship_element_equal(self, object_: model.RelationshipElement, :return: """ self._check_abstract_attributes_submodel_element_equal(object_, expected_value) - self.check_attribute_equal(object_, 'first', expected_value.first) - self.check_attribute_equal(object_, 'second', expected_value.second) + self.check_attribute_equal(object_, "first", expected_value.first) + self.check_attribute_equal(object_, "second", expected_value.second) - def check_annotated_relationship_element_equal(self, object_: model.AnnotatedRelationshipElement, - expected_value: model.AnnotatedRelationshipElement): + def check_annotated_relationship_element_equal( + self, + object_: model.AnnotatedRelationshipElement, + expected_value: model.AnnotatedRelationshipElement, + ): """ Checks if the given AnnotatedRelationshipElement objects are equal @@ -475,21 +620,32 @@ def check_annotated_relationship_element_equal(self, object_: model.AnnotatedRel :return: """ self.check_relationship_element_equal(object_, expected_value) - self.check_contained_element_length(object_, 'annotation', model.DataElement, - len(expected_value.annotation)) + self.check_contained_element_length( + object_, "annotation", model.DataElement, len(expected_value.annotation) + ) for expected_data_element in expected_value.annotation: try: object_.get_referable(expected_data_element.id_short) except KeyError: - self.check(False, 'Annotation {} must exist'.format(repr(expected_data_element))) - - found_elements = self._find_extra_namespace_set_elements_by_id_short(object_.annotation, - expected_value.annotation) - self.check(found_elements == set(), 'Annotated Reference {} must not have extra ' - 'references'.format(repr(object_)), - value=found_elements) - - def _check_reference_equal(self, object_: model.Reference, expected_value: model.Reference): + self.check( + False, + "Annotation {} must exist".format(repr(expected_data_element)), + ) + + found_elements = self._find_extra_namespace_set_elements_by_id_short( + object_.annotation, expected_value.annotation + ) + self.check( + found_elements == set(), + "Annotated Reference {} must not have extra references".format( + repr(object_) + ), + value=found_elements, + ) + + def _check_reference_equal( + self, object_: model.Reference, expected_value: model.Reference + ): """ Checks if the given Reference objects are equal @@ -497,9 +653,14 @@ def _check_reference_equal(self, object_: model.Reference, expected_value: model :param expected_value: expected Reference object :return: """ - self.check(object_ == expected_value, "{} must be == {}".format(repr(object_), repr(expected_value))) + self.check( + object_ == expected_value, + "{} must be == {}".format(repr(object_), repr(expected_value)), + ) - def _find_reference(self, object_: model.Reference, search_list: Iterable) -> Union[model.Reference, None]: + def _find_reference( + self, object_: model.Reference, search_list: Iterable + ) -> Union[model.Reference, None]: """ Find a reference in a list @@ -512,8 +673,9 @@ 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: Iterable) \ - -> Union[model.SpecificAssetId, None]: + def _find_specific_asset_id( + self, object_: model.SpecificAssetId, search_list: Iterable + ) -> Union[model.SpecificAssetId, None]: """ Find a SpecificAssetId in a list @@ -526,7 +688,9 @@ def _find_specific_asset_id(self, object_: model.SpecificAssetId, search_list: I return element return None - def _find_element_by_attribute(self, object_: object, search_list: Iterable, *attribute: str) -> object: + def _find_element_by_attribute( + self, object_: object, search_list: Iterable, *attribute: str + ) -> object: """ Find an element in a list @@ -545,8 +709,9 @@ def _find_element_by_attribute(self, object_: object, search_list: Iterable, *at return element return None - def _find_extra_object(self, object_list: Iterable, search_list: Iterable, - type_) -> Union[Set, None]: + def _find_extra_object( + self, object_list: Iterable, search_list: Iterable, type_ + ) -> Union[Set, None]: """ Find extra objects that are in object_list but still in search_list @@ -568,8 +733,12 @@ def _find_extra_object(self, object_list: Iterable, search_list: Iterable, found_elements.add(object_list_element) return found_elements - def _find_extra_elements_by_attribute(self, object_list: Union[Set, List], search_list: Union[Set, List], - *attribute: str) -> Set: + def _find_extra_elements_by_attribute( + self, + object_list: Union[Set, List], + search_list: Union[Set, List], + *attribute: str, + ) -> Set: """ Find extra elements that are in object_list but not in search_list @@ -583,7 +752,9 @@ def _find_extra_elements_by_attribute(self, object_list: Union[Set, List], searc found = False for search_list_element in search_list: for attr in attribute: - if getattr(object_list_element, attr) != getattr(search_list_element, attr): + if getattr(object_list_element, attr) != getattr( + search_list_element, attr + ): found = False else: found = True @@ -593,8 +764,12 @@ def _find_extra_elements_by_attribute(self, object_list: Union[Set, List], searc found_elements.add(object_list_element) return found_elements - def _find_extra_namespace_set_elements_by_attribute(self, object_list: model.NamespaceSet, - search_list: model.NamespaceSet, attr_name: str) -> Set: + def _find_extra_namespace_set_elements_by_attribute( + self, + object_list: model.NamespaceSet, + search_list: model.NamespaceSet, + attr_name: str, + ) -> Set: """ Find extra elements that are in object_list but not in search_list by identifying attribute @@ -605,13 +780,16 @@ def _find_extra_namespace_set_elements_by_attribute(self, object_list: model.Nam """ found_elements = set() for object_list_element in object_list: - element = search_list.get(attr_name, getattr(object_list_element, attr_name)) + element = search_list.get( + attr_name, getattr(object_list_element, attr_name) + ) if element is None: found_elements.add(object_list_element) return found_elements - def _find_extra_namespace_set_elements_by_id_short(self, object_list: model.NamespaceSet, - search_list: model.NamespaceSet) -> Set: + def _find_extra_namespace_set_elements_by_id_short( + self, object_list: model.NamespaceSet, search_list: model.NamespaceSet + ) -> Set: """ Find extra Referable objects that are in object_list but not in search_list @@ -619,10 +797,13 @@ def _find_extra_namespace_set_elements_by_id_short(self, object_list: model.Name :param search_list: List which should be searched :return: Set of elements that are in object_list but not in search_list """ - return self._find_extra_namespace_set_elements_by_attribute(object_list, search_list, 'id_short') + return self._find_extra_namespace_set_elements_by_attribute( + object_list, search_list, "id_short" + ) - def _find_extra_namespace_set_elements_by_name(self, object_list: model.NamespaceSet, - search_list: model.NamespaceSet) -> Set: + def _find_extra_namespace_set_elements_by_name( + self, object_list: model.NamespaceSet, search_list: model.NamespaceSet + ) -> Set: """ Find extra Extension object that are in object_list but not in search_list @@ -630,9 +811,13 @@ def _find_extra_namespace_set_elements_by_name(self, object_list: model.Namespac :param search_list: List which should be searched :return: Set of elements that are in object_list but not in search_list """ - return self._find_extra_namespace_set_elements_by_attribute(object_list, search_list, 'name') + return self._find_extra_namespace_set_elements_by_attribute( + object_list, search_list, "name" + ) - def check_operation_equal(self, object_: model.Operation, expected_value: model.Operation): + def check_operation_equal( + self, object_: model.Operation, expected_value: model.Operation + ): """ Checks if the given Operation objects are equal @@ -642,14 +827,27 @@ def check_operation_equal(self, object_: model.Operation, expected_value: model. """ self._check_abstract_attributes_submodel_element_equal(object_, expected_value) for input_nss, expected_nss, attr_name in ( - (object_.input_variable, expected_value.input_variable, 'input_variable'), - (object_.output_variable, expected_value.output_variable, 'output_variable'), - (object_.in_output_variable, expected_value.in_output_variable, 'in_output_variable')): - self.check_contained_element_length(object_, attr_name, model.SubmodelElement, len(expected_nss)) + (object_.input_variable, expected_value.input_variable, "input_variable"), + ( + object_.output_variable, + expected_value.output_variable, + "output_variable", + ), + ( + object_.in_output_variable, + expected_value.in_output_variable, + "in_output_variable", + ), + ): + self.check_contained_element_length( + object_, attr_name, model.SubmodelElement, len(expected_nss) + ) for var1, var2 in zip(input_nss, expected_nss): self._check_submodel_element(var1, var2) - def check_capability_equal(self, object_: model.Capability, expected_value: model.Capability): + def check_capability_equal( + self, object_: model.Capability, expected_value: model.Capability + ): """ Checks if the given Capability objects are equal @@ -668,32 +866,56 @@ def check_entity_equal(self, object_: model.Entity, expected_value: model.Entity :return: """ 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) - 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)) + self.check_attribute_equal(object_, "entity_type", expected_value.entity_type) + self.check_attribute_equal( + object_, "global_asset_id", expected_value.global_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, f'Entity {repr(expected_element)} must exist') - - found_elements = self._find_extra_namespace_set_elements_by_id_short(object_.statement, - expected_value.statement) - self.check(found_elements == set(), f'Entity {repr(object_)} must not have extra statements', - value=found_elements) + self.check( + element is not None, f"Entity {repr(expected_element)} must exist" + ) - def _check_specific_asset_ids_equal(self, object_: Iterable[model.SpecificAssetId], - expected_value: Iterable[model.SpecificAssetId], - object_parent): + found_elements = self._find_extra_namespace_set_elements_by_id_short( + object_.statement, expected_value.statement + ) + self.check( + found_elements == set(), + f"Entity {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'): + 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): + 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 + ): """ Checks if the given EventElement objects are equal @@ -703,8 +925,9 @@ def _check_event_element_equal(self, object_: model.EventElement, expected_value """ self._check_abstract_attributes_submodel_element_equal(object_, expected_value) - def check_basic_event_element_equal(self, object_: model.BasicEventElement, - expected_value: model.BasicEventElement): + def check_basic_event_element_equal( + self, object_: model.BasicEventElement, expected_value: model.BasicEventElement + ): """ Checks if the given BasicEventElement objects are equal @@ -714,16 +937,22 @@ def check_basic_event_element_equal(self, object_: model.BasicEventElement, """ self._check_abstract_attributes_submodel_element_equal(object_, expected_value) self._check_event_element_equal(object_, expected_value) - self.check_attribute_equal(object_, 'observed', expected_value.observed) - self.check_attribute_equal(object_, 'direction', expected_value.direction) - self.check_attribute_equal(object_, 'state', expected_value.state) - self.check_attribute_equal(object_, 'message_topic', expected_value.message_topic) - self.check_attribute_equal(object_, 'message_broker', expected_value.message_broker) - self.check_attribute_equal(object_, 'last_update', expected_value.last_update) - self.check_attribute_equal(object_, 'min_interval', expected_value.min_interval) - self.check_attribute_equal(object_, 'max_interval', expected_value.max_interval) - - def check_submodel_equal(self, object_: model.Submodel, expected_value: model.Submodel): + self.check_attribute_equal(object_, "observed", expected_value.observed) + self.check_attribute_equal(object_, "direction", expected_value.direction) + self.check_attribute_equal(object_, "state", expected_value.state) + self.check_attribute_equal( + object_, "message_topic", expected_value.message_topic + ) + self.check_attribute_equal( + object_, "message_broker", expected_value.message_broker + ) + self.check_attribute_equal(object_, "last_update", expected_value.last_update) + self.check_attribute_equal(object_, "min_interval", expected_value.min_interval) + self.check_attribute_equal(object_, "max_interval", expected_value.max_interval) + + def check_submodel_equal( + self, object_: model.Submodel, expected_value: model.Submodel + ): """ Checks if the given Submodel objects are equal @@ -736,21 +965,34 @@ def check_submodel_equal(self, object_: model.Submodel, expected_value: model.Su self._check_has_kind_equal(object_, expected_value) self._check_qualifiable_equal(object_, expected_value) self._check_has_data_specification_equal(object_, expected_value) - self.check_contained_element_length(object_, 'submodel_element', model.SubmodelElement, - len(expected_value.submodel_element)) + self.check_contained_element_length( + object_, + "submodel_element", + model.SubmodelElement, + len(expected_value.submodel_element), + ) for expected_element in expected_value.submodel_element: try: element = object_.get_referable(expected_element.id_short) self._check_submodel_element(element, expected_element) # type: ignore except KeyError: - self.check(False, 'Submodel Element {} must exist'.format(repr(expected_element))) - - found_elements = self._find_extra_namespace_set_elements_by_id_short(object_.submodel_element, - expected_value.submodel_element) - self.check(found_elements == set(), 'Submodel {} must not have extra submodel elements'.format(repr(object_)), - value=found_elements) - - def _check_qualifier_equal(self, object_: model.Qualifier, expected_value: model.Qualifier): + self.check( + False, + "Submodel Element {} must exist".format(repr(expected_element)), + ) + + found_elements = self._find_extra_namespace_set_elements_by_id_short( + object_.submodel_element, expected_value.submodel_element + ) + self.check( + found_elements == set(), + "Submodel {} must not have extra submodel elements".format(repr(object_)), + value=found_elements, + ) + + def _check_qualifier_equal( + self, object_: model.Qualifier, expected_value: model.Qualifier + ): """ Checks if the given Qualifier objects are equal @@ -758,14 +1000,15 @@ def _check_qualifier_equal(self, object_: model.Qualifier, expected_value: model :param expected_value: expected Qualifier object :return: """ - self.check_attribute_equal(object_, 'type', expected_value.type) - self.check_attribute_equal(object_, 'value_type', expected_value.value_type) - self.check_attribute_equal(object_, 'value', expected_value.value) - self.check_attribute_equal(object_, 'value_id', expected_value.value_id) - self.check_attribute_equal(object_, 'kind', expected_value.kind) + self.check_attribute_equal(object_, "type", expected_value.type) + self.check_attribute_equal(object_, "value_type", expected_value.value_type) + self.check_attribute_equal(object_, "value", expected_value.value) + self.check_attribute_equal(object_, "value_id", expected_value.value_id) + self.check_attribute_equal(object_, "kind", expected_value.kind) - def check_specific_asset_id(self, object_: model.SpecificAssetId, - expected_value: model.SpecificAssetId): + def check_specific_asset_id( + self, object_: model.SpecificAssetId, expected_value: model.SpecificAssetId + ): """ Checks if the given SpecificAssetId objects are equal @@ -775,10 +1018,14 @@ def check_specific_asset_id(self, object_: model.SpecificAssetId, """ self.check_attribute_equal(object_, "name", expected_value.name) self.check_attribute_equal(object_, "value", expected_value.value) - self.check_attribute_equal(object_, "external_subject_id", expected_value.external_subject_id) + self.check_attribute_equal( + object_, "external_subject_id", expected_value.external_subject_id + ) self.check_attribute_equal(object_, "semantic_id", expected_value.semantic_id) - def check_asset_information_equal(self, object_: model.AssetInformation, expected_value: model.AssetInformation): + def check_asset_information_equal( + self, object_: model.AssetInformation, expected_value: model.AssetInformation + ): """ Checks if the given AssetInformation objects are equal @@ -786,26 +1033,45 @@ def check_asset_information_equal(self, object_: model.AssetInformation, expecte :param expected_value: expected AssetInformation object :return: """ - self.check_attribute_equal(object_, 'asset_kind', expected_value.asset_kind) - 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)) - 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) + self.check_attribute_equal(object_, "asset_kind", expected_value.asset_kind) + 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), + ) + 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) + self.check_resource_equal( + object_.default_thumbnail, expected_value.default_thumbnail + ) else: if object_.default_thumbnail: - self.check(expected_value.default_thumbnail is not None, - 'defaultThumbnail object {} must exist'.format(repr(object_.default_thumbnail)), - value=expected_value.default_thumbnail) + self.check( + expected_value.default_thumbnail is not None, + "defaultThumbnail object {} must exist".format( + repr(object_.default_thumbnail) + ), + value=expected_value.default_thumbnail, + ) else: - self.check(expected_value.default_thumbnail is None, '{} must not have a ' - 'defaultThumbnail object'.format(repr(object_)), - value=expected_value.default_thumbnail) - - def check_asset_administration_shell_equal(self, object_: model.AssetAdministrationShell, - expected_value: model.AssetAdministrationShell): + self.check( + expected_value.default_thumbnail is None, + "{} must not have a defaultThumbnail object".format(repr(object_)), + value=expected_value.default_thumbnail, + ) + + def check_asset_administration_shell_equal( + self, + object_: model.AssetAdministrationShell, + expected_value: model.AssetAdministrationShell, + ): """ Checks if the given AssetAdministrationShell objects are equal @@ -815,22 +1081,37 @@ def check_asset_administration_shell_equal(self, object_: model.AssetAdministrat """ self._check_identifiable_equal(object_, expected_value) self._check_has_data_specification_equal(object_, expected_value) - self.check_asset_information_equal(object_.asset_information, expected_value.asset_information) - - self.check_attribute_equal(object_, 'derived_from', expected_value.derived_from) - self.check_contained_element_length(object_, 'submodel', model.ModelReference, len(expected_value.submodel)) + self.check_asset_information_equal( + object_.asset_information, expected_value.asset_information + ) + + self.check_attribute_equal(object_, "derived_from", expected_value.derived_from) + self.check_contained_element_length( + object_, "submodel", model.ModelReference, len(expected_value.submodel) + ) for expected_ref in expected_value.submodel: ref = self._find_reference(expected_ref, object_.submodel) - if self.check(ref is not None, 'Submodel Reference {} must exist'.format(repr(expected_ref))): + if self.check( + ref is not None, + "Submodel Reference {} must exist".format(repr(expected_ref)), + ): self._check_reference_equal(ref, expected_ref) # type: ignore - found_elements = self._find_extra_object(object_.submodel, expected_value.submodel, model.ModelReference) - self.check(found_elements == set(), 'Asset Administration Shell {} must not have extra submodel ' - 'references'.format(repr(object_)), - value=found_elements) - - def check_concept_description_equal(self, object_: model.ConceptDescription, - expected_value: model.ConceptDescription): + found_elements = self._find_extra_object( + object_.submodel, expected_value.submodel, model.ModelReference + ) + self.check( + found_elements == set(), + "Asset Administration Shell {} must not have extra submodel " + "references".format(repr(object_)), + value=found_elements, + ) + + def check_concept_description_equal( + self, + object_: model.ConceptDescription, + expected_value: model.ConceptDescription, + ): """ Checks if the given ConceptDescription objects are equal @@ -840,22 +1121,34 @@ def check_concept_description_equal(self, object_: model.ConceptDescription, """ self._check_identifiable_equal(object_, expected_value) self._check_has_data_specification_equal(object_, expected_value) - self.check_contained_element_length(object_, 'is_case_of', model.Reference, - len(expected_value.is_case_of)) + self.check_contained_element_length( + object_, "is_case_of", model.Reference, len(expected_value.is_case_of) + ) for expected_ref in expected_value.is_case_of: ref = self._find_reference(expected_ref, object_.is_case_of) - if self.check(ref is not None, 'Concept Description Reference {} must exist'.format(repr(expected_ref))): + if self.check( + ref is not None, + "Concept Description Reference {} must exist".format( + repr(expected_ref) + ), + ): self._check_reference_equal(ref, expected_ref) # type: ignore - found_elements = self._find_extra_object(object_.is_case_of, expected_value.is_case_of, - model.ModelReference) - self.check(found_elements == set(), 'Concept Description Reference {} must not have extra ' - 'is case of references'.format(repr(object_)), - value=found_elements) + found_elements = self._find_extra_object( + object_.is_case_of, expected_value.is_case_of, model.ModelReference + ) + self.check( + found_elements == set(), + "Concept Description Reference {} must not have extra " + "is case of references".format(repr(object_)), + value=found_elements, + ) def check_data_specification_content_equal( - self, object_: model.DataSpecificationContent, - expected_value: model.DataSpecificationContent): + self, + object_: model.DataSpecificationContent, + expected_value: model.DataSpecificationContent, + ): """ Checks if the given DataSpecificationContent objects are equal @@ -863,13 +1156,18 @@ def check_data_specification_content_equal( :param expected_value: expected DataSpecificationContent object :return: """ - self.check(type(object_) is type(expected_value), "type({}) must be == type({})" - .format(repr(object_), repr(expected_value))) + self.check( + type(object_) is type(expected_value), + "type({}) must be == type({})".format(repr(object_), repr(expected_value)), + ) if isinstance(object_, model.base.DataSpecificationIEC61360): self._check_data_specification_iec61360_equal(object_, expected_value) # type: ignore - def _check_data_specification_iec61360_equal(self, object_: model.base.DataSpecificationIEC61360, - expected_value: model.base.DataSpecificationIEC61360): + def _check_data_specification_iec61360_equal( + self, + object_: model.base.DataSpecificationIEC61360, + expected_value: model.base.DataSpecificationIEC61360, + ): """ Checks if the given IEC61360ConceptDescription objects are equal @@ -877,30 +1175,47 @@ def _check_data_specification_iec61360_equal(self, object_: model.base.DataSpeci :param expected_value: expected IEC61360ConceptDescription object :return: """ - self.check_attribute_equal(object_, 'preferred_name', expected_value.preferred_name) - self.check_attribute_equal(object_, 'short_name', expected_value.short_name) - self.check_attribute_equal(object_, 'data_type', expected_value.data_type) - self.check_attribute_equal(object_, 'definition', expected_value.definition) - self.check_attribute_equal(object_, 'unit', expected_value.unit) - self.check_attribute_equal(object_, 'unit_id', expected_value.unit_id) - self.check_attribute_equal(object_, 'source_of_definition', expected_value.source_of_definition) - self.check_attribute_equal(object_, 'symbol', expected_value.symbol) - self.check_attribute_equal(object_, 'value_format', expected_value.value_format) - self.check_attribute_equal(object_, 'value', expected_value.value) - self.check_attribute_equal(object_, 'level_types', expected_value.level_types) + self.check_attribute_equal( + object_, "preferred_name", expected_value.preferred_name + ) + self.check_attribute_equal(object_, "short_name", expected_value.short_name) + self.check_attribute_equal(object_, "data_type", expected_value.data_type) + self.check_attribute_equal(object_, "definition", expected_value.definition) + self.check_attribute_equal(object_, "unit", expected_value.unit) + self.check_attribute_equal(object_, "unit_id", expected_value.unit_id) + self.check_attribute_equal( + object_, "source_of_definition", expected_value.source_of_definition + ) + self.check_attribute_equal(object_, "symbol", expected_value.symbol) + self.check_attribute_equal(object_, "value_format", expected_value.value_format) + self.check_attribute_equal(object_, "value", expected_value.value) + self.check_attribute_equal(object_, "level_types", expected_value.level_types) if expected_value.value_list is not None: - if self.check(object_.value_list is not None, - "ValueList must contain {} ValueReferencePairs".format(len(expected_value.value_list)), - value=expected_value.value_list): - self._check_value_list_equal(object_.value_list, expected_value.value_list) # type: ignore + if self.check( + object_.value_list is not None, + "ValueList must contain {} ValueReferencePairs".format( + len(expected_value.value_list) + ), + value=expected_value.value_list, + ): + self._check_value_list_equal( + object_.value_list, expected_value.value_list + ) # type: ignore if object_.value_list is not None: - if self.check(expected_value.value_list is not None, - "ValueList must contain 0 ValueReferencePairs", value=len(object_.value_list)): - self._check_value_list_equal(object_.value_list, expected_value.value_list) # type: ignore - - def _check_value_list_equal(self, object_: model.ValueList, expected_value: model.ValueList): + if self.check( + expected_value.value_list is not None, + "ValueList must contain 0 ValueReferencePairs", + value=len(object_.value_list), + ): + self._check_value_list_equal( + object_.value_list, expected_value.value_list + ) # type: ignore + + def _check_value_list_equal( + self, object_: model.ValueList, expected_value: model.ValueList + ): """ Checks if the given ValueList objects are equal @@ -909,18 +1224,29 @@ def _check_value_list_equal(self, object_: model.ValueList, expected_value: mode :return: """ for expected_pair in expected_value: - pair = self._find_element_by_attribute(expected_pair, object_, 'value', 'value_id') - self.check(pair is not None, 'ValueReferencePair[value={}, value_id={}] ' - 'must exist'.format(expected_pair.value, expected_pair.value_id)) + pair = self._find_element_by_attribute( + expected_pair, object_, "value", "value_id" + ) + self.check( + pair is not None, + "ValueReferencePair[value={}, value_id={}] must exist".format( + expected_pair.value, expected_pair.value_id + ), + ) - found_elements = self._find_extra_elements_by_attribute(object_, expected_value, 'value', 'value_id') - self.check(found_elements == set(), 'ValueList must not have extra ValueReferencePairs', - value=found_elements) + found_elements = self._find_extra_elements_by_attribute( + object_, expected_value, "value", "value_id" + ) + self.check( + found_elements == set(), + "ValueList must not have extra ValueReferencePairs", + value=found_elements, + ) def check_identifiable_store( - self, - identifiable_store_1: model.DictIdentifiableStore, - identifiable_store_2: model.DictIdentifiableStore + self, + identifiable_store_1: model.DictIdentifiableStore, + identifiable_store_2: model.DictIdentifiableStore, ): """ Checks if the given object stores are equal @@ -941,7 +1267,7 @@ def check_identifiable_store( elif isinstance(identifiable, model.ConceptDescription): concept_description_list_1.append(identifiable) else: - raise KeyError('Check for {} not implemented'.format(identifiable)) + raise KeyError("Check for {} not implemented".format(identifiable)) # separate different kind of objects submodel_list_2 = [] @@ -955,40 +1281,65 @@ def check_identifiable_store( elif isinstance(identifiable, model.ConceptDescription): concept_description_list_2.append(identifiable) else: - raise KeyError('Check for {} not implemented'.format(identifiable)) + raise KeyError("Check for {} not implemented".format(identifiable)) for shell_2 in shell_list_2: shell_1 = identifiable_store_1.get(shell_2.id) - if self.check(shell_1 is not None, 'Asset administration shell {} must exist in given asset administration' - 'shell list'.format(shell_2)): + if self.check( + shell_1 is not None, + "Asset administration shell {} must exist in given asset administration" + "shell list".format(shell_2), + ): self.check_asset_administration_shell_equal(shell_1, shell_2) # type: ignore - found_elements = self._find_extra_elements_by_attribute(shell_list_1, shell_list_2, 'id') - self.check(found_elements == set(), 'Given asset administration shell list must not have extra asset ' - 'administration shells', value=found_elements) + found_elements = self._find_extra_elements_by_attribute( + shell_list_1, shell_list_2, "id" + ) + self.check( + found_elements == set(), + "Given asset administration shell list must not have extra asset " + "administration shells", + value=found_elements, + ) for submodel_2 in submodel_list_2: submodel_1 = identifiable_store_1.get(submodel_2.id) - if self.check(submodel_1 is not None, 'Submodel {} must exist in given submodel list'.format(submodel_2)): + if self.check( + submodel_1 is not None, + "Submodel {} must exist in given submodel list".format(submodel_2), + ): self.check_submodel_equal(submodel_1, submodel_2) # type: ignore - found_elements = self._find_extra_elements_by_attribute(submodel_list_1, submodel_list_2, 'id') - self.check(found_elements == set(), 'Given submodel list must not have extra submodels', - value=found_elements) + found_elements = self._find_extra_elements_by_attribute( + submodel_list_1, submodel_list_2, "id" + ) + self.check( + found_elements == set(), + "Given submodel list must not have extra submodels", + value=found_elements, + ) for cd_2 in concept_description_list_2: cd_1 = identifiable_store_1.get(cd_2.id) - if self.check(cd_1 is not None, 'Concept description {} must exist in given concept description ' - 'list'.format(cd_2)): + if self.check( + cd_1 is not None, + "Concept description {} must exist in given concept description " + "list".format(cd_2), + ): self.check_concept_description_equal(cd_1, cd_2) # type: ignore - found_elements = self._find_extra_elements_by_attribute(concept_description_list_1, concept_description_list_2, - 'id') - self.check(found_elements == set(), 'Given concept description list must not have extra concept ' - 'descriptions', - value=found_elements) + found_elements = self._find_extra_elements_by_attribute( + concept_description_list_1, concept_description_list_2, "id" + ) + self.check( + found_elements == set(), + "Given concept description list must not have extra concept descriptions", + value=found_elements, + ) - def check_attribute_equal(self, object_: object, attribute_name: str, expected_value: object, **kwargs) -> bool: + def check_attribute_equal( + self, object_: object, attribute_name: str, expected_value: object, **kwargs + ) -> bool: """ Checks if the value of the attribute in ``object_`` is the same as ``expected_value`` and adds / stores the check result for later analysis. @@ -1000,21 +1351,33 @@ def check_attribute_equal(self, object_: object, attribute_name: str, expected_v :return: The value of expression to be used in control statements """ # TODO: going by attribute name here isn't exactly pretty... - if attribute_name in ('value_type', 'value_type_list_element', 'type_value_list_element') \ - and getattr(object_, attribute_name) is not None: + if ( + attribute_name + in ("value_type", "value_type_list_element", "type_value_list_element") + and getattr(object_, attribute_name) is not None + ): # value_type_list_element can be None and doesn't have the __name__ attribute in this case - kwargs['value'] = getattr(object_, attribute_name).__name__ - return self.check(getattr(object_, attribute_name) is expected_value, # type:ignore - "Attribute {} of {} must be == {}".format( - attribute_name, repr(object_), expected_value.__name__), # type:ignore - **kwargs) + kwargs["value"] = getattr(object_, attribute_name).__name__ + return self.check( + getattr(object_, attribute_name) is expected_value, # type:ignore + "Attribute {} of {} must be == {}".format( + attribute_name, repr(object_), expected_value.__name__ + ), # type:ignore + **kwargs, + ) else: - kwargs['value'] = getattr(object_, attribute_name) - return self.check(getattr(object_, attribute_name) == expected_value, - "Attribute {} of {} must be == {}".format(attribute_name, repr(object_), expected_value), - **kwargs) + kwargs["value"] = getattr(object_, attribute_name) + return self.check( + getattr(object_, attribute_name) == expected_value, + "Attribute {} of {} must be == {}".format( + attribute_name, repr(object_), expected_value + ), + **kwargs, + ) - def check_element_in(self, object_: model.Referable, parent: object, **kwargs) -> bool: + def check_element_in( + self, object_: model.Referable, parent: object, **kwargs + ) -> bool: """ Checks if ``object_`` exist in ``parent`` and adds / stores the check result for later analysis. @@ -1023,12 +1386,20 @@ def check_element_in(self, object_: model.Referable, parent: object, **kwargs) - :param kwargs: Relevant values to add to the check result for further analysis (e.g. the compared values) :return: The value of expression to be used in control statements """ - return self.check(object_.parent == parent, - "{} must exist in {}s".format(repr(object_), repr(parent)), - **kwargs) - - def check_contained_element_length(self, object_: object, attribute_name: str, class_name: Type, - length: int, **kwargs) -> bool: + return self.check( + object_.parent == parent, + "{} must exist in {}s".format(repr(object_), repr(parent)), + **kwargs, + ) + + def check_contained_element_length( + self, + object_: object, + attribute_name: str, + class_name: Type, + length: int, + **kwargs, + ) -> bool: """ Checks if the ``object_`` has ``length`` elements of class ``class_name`` in attribute ``attribute_name`` and adds / stores the check result for later analysis. @@ -1044,11 +1415,14 @@ def check_contained_element_length(self, object_: object, attribute_name: str, c for element in getattr(object_, attribute_name): if isinstance(element, class_name): count = count + 1 - kwargs['count'] = count - return self.check(count == length, - "Attribute {} of {} must contain {} {}s".format(attribute_name, repr(object_), - length, class_name.__name__), - **kwargs) + kwargs["count"] = count + return self.check( + count == length, + "Attribute {} of {} must contain {} {}s".format( + attribute_name, repr(object_), length, class_name.__name__ + ), + **kwargs, + ) def check_is_instance(self, object_: object, class_name: Type, **kwargs) -> bool: """ @@ -1059,12 +1433,16 @@ def check_is_instance(self, object_: object, class_name: Type, **kwargs) -> bool :param kwargs: Relevant values to add to the check result for further analysis (e.g. the compared values) :return: The value of expression to be used in control statements """ - kwargs['class'] = object_.__class__.__name__ - return self.check(isinstance(object_, class_name), - "{} must be of class {}".format(repr(object_), class_name.__name__), - **kwargs) + kwargs["class"] = object_.__class__.__name__ + return self.check( + isinstance(object_, class_name), + "{} must be of class {}".format(repr(object_), class_name.__name__), + **kwargs, + ) - def check_attribute_is_none(self, object_: object, attribute_name: str, **kwargs) -> bool: + def check_attribute_is_none( + self, object_: object, attribute_name: str, **kwargs + ) -> bool: """ Checks if the value of the attribute in ``object_`` is :class:`None` and adds / stores the check result for later analysis. @@ -1074,7 +1452,9 @@ def check_attribute_is_none(self, object_: object, attribute_name: str, **kwargs :param kwargs: Relevant values to add to the check result for further analysis (e.g. the compared values) :return: The value of expression to be used in control statements """ - kwargs['value'] = getattr(object_, attribute_name) - return self.check(getattr(object_, attribute_name) is None, - "Attribute {} of {} must be None".format(attribute_name, repr(object_)), - **kwargs) + kwargs["value"] = getattr(object_, attribute_name) + return self.check( + getattr(object_, attribute_name) is None, + "Attribute {} of {} must be None".format(attribute_name, repr(object_)), + **kwargs, + ) diff --git a/sdk/basyx/aas/examples/data/example_aas.py b/sdk/basyx/aas/examples/data/example_aas.py index bb080756a..ca8010328 100644 --- a/sdk/basyx/aas/examples/data/example_aas.py +++ b/sdk/basyx/aas/examples/data/example_aas.py @@ -11,6 +11,7 @@ To get this object store use the function :meth:`~basyx.aas.examples.data.example_aas.create_full_example`. If you want to get single example objects or want to get more information use the other functions. """ + import datetime import logging @@ -21,29 +22,68 @@ _embedded_data_specification_iec61360 = model.EmbeddedDataSpecification( - data_specification=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='https://admin-shell.io/DataSpecificationTemplates/' - 'DataSpecificationIEC61360/3/1'),)), - data_specification_content=model.DataSpecificationIEC61360(preferred_name=model.PreferredNameTypeIEC61360({ - 'de': 'Test Specification', - 'en-US': 'TestSpecification' - }), data_type=model.DataTypeIEC61360.REAL_MEASURE, - definition=model.DefinitionTypeIEC61360({'de': 'Dies ist eine Data Specification für Testzwecke', - 'en-US': 'This is a DataSpecification for testing purposes'}), - short_name=model.ShortNameTypeIEC61360({'de': 'Test Spec', 'en-US': 'TestSpec'}), unit='SpaceUnit', - unit_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/Units/SpaceUnit'),)), - source_of_definition='http://example.org/DataSpec/ExampleDef', symbol='SU', value_format="M", + data_specification=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="https://admin-shell.io/DataSpecificationTemplates/" + "DataSpecificationIEC61360/3/1", + ), + ) + ), + data_specification_content=model.DataSpecificationIEC61360( + preferred_name=model.PreferredNameTypeIEC61360( + {"de": "Test Specification", "en-US": "TestSpecification"} + ), + data_type=model.DataTypeIEC61360.REAL_MEASURE, + definition=model.DefinitionTypeIEC61360( + { + "de": "Dies ist eine Data Specification für Testzwecke", + "en-US": "This is a DataSpecification for testing purposes", + } + ), + short_name=model.ShortNameTypeIEC61360( + {"de": "Test Spec", "en-US": "TestSpec"} + ), + unit="SpaceUnit", + unit_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/Units/SpaceUnit", + ), + ) + ), + source_of_definition="http://example.org/DataSpec/ExampleDef", + symbol="SU", + value_format="M", value_list={ model.ValueReferencePair( - value='exampleValue', - value_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/ValueId/ExampleValueId'),)), ), + value="exampleValue", + value_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/ValueId/ExampleValueId", + ), + ) + ), + ), model.ValueReferencePair( - value='exampleValue2', - value_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/ValueId/ExampleValueId2'),)), )}, - value="TEST", level_types={model.IEC61360LevelType.MIN, model.IEC61360LevelType.MAX}) + value="exampleValue2", + value_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/ValueId/ExampleValueId2", + ), + ) + ), + ), + }, + value="TEST", + level_types={model.IEC61360LevelType.MIN, model.IEC61360LevelType.MAX}, + ), ) @@ -55,7 +95,9 @@ def create_full_example() -> model.DictIdentifiableStore: :return: :class:`~basyx.aas.model.provider.DictIdentifiableStore` """ - identifiable_store: model.DictIdentifiableStore[model.Identifiable] = model.DictIdentifiableStore() + identifiable_store: model.DictIdentifiableStore[model.Identifiable] = ( + model.DictIdentifiableStore() + ) identifiable_store.add(create_example_asset_identification_submodel()) identifiable_store.add(create_example_bill_of_material_submodel()) identifiable_store.add(create_example_submodel()) @@ -74,125 +116,195 @@ def create_example_asset_identification_submodel() -> model.Submodel: """ qualifier = model.Qualifier( - type_='http://example.org/Qualifier/ExampleQualifier', + type_="http://example.org/Qualifier/ExampleQualifier", value_type=model.datatypes.Int, value=100, - value_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/ValueId/ExampleValueId'),)), - kind=model.QualifierKind.CONCEPT_QUALIFIER) + value_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/ValueId/ExampleValueId", + ), + ) + ), + kind=model.QualifierKind.CONCEPT_QUALIFIER, + ) qualifier2 = model.Qualifier( - type_='http://example.org/Qualifier/ExampleQualifier2', + type_="http://example.org/Qualifier/ExampleQualifier2", value_type=model.datatypes.Int, value=50, - value_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/ValueId/ExampleValueId'),)), - kind=model.QualifierKind.TEMPLATE_QUALIFIER) + value_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/ValueId/ExampleValueId", + ), + ) + ), + kind=model.QualifierKind.TEMPLATE_QUALIFIER, + ) qualifier3 = model.Qualifier( - type_='http://example.org/Qualifier/ExampleQualifier3', + type_="http://example.org/Qualifier/ExampleQualifier3", value_type=model.datatypes.DateTime, value=model.datatypes.DateTime(2023, 4, 7, 16, 59, 54, 870123), - value_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/ValueId/ExampleValueId'),)), - kind=model.QualifierKind.VALUE_QUALIFIER) + value_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/ValueId/ExampleValueId", + ), + ) + ), + kind=model.QualifierKind.VALUE_QUALIFIER, + ) extension = model.Extension( - name='ExampleExtension', + name="ExampleExtension", value_type=model.datatypes.String, value="ExampleExtensionValue", - refers_to=[model.ModelReference((model.Key(type_=model.KeyTypes.ASSET_ADMINISTRATION_SHELL, - value='http://example.org/RefersTo/ExampleRefersTo'),), - model.AssetAdministrationShell)],) + refers_to=[ + model.ModelReference( + ( + model.Key( + type_=model.KeyTypes.ASSET_ADMINISTRATION_SHELL, + value="http://example.org/RefersTo/ExampleRefersTo", + ), + ), + model.AssetAdministrationShell, + ) + ], + ) # Property-Element conform to 'Verwaltungsschale in der Praxis' page 41 ManufacturerName: # https://www.plattform-i40.de/PI40/Redaktion/DE/Downloads/Publikation/2019-verwaltungsschale-in-der-praxis.html identification_submodel_element_manufacturer_name = model.Property( - id_short='ManufacturerName', + id_short="ManufacturerName", value_type=model.datatypes.String, - value='ACPLT', - value_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/ValueId/ExampleValueId'),)), + value="ACPLT", + value_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/ValueId/ExampleValueId", + ), + ) + ), category="PARAMETER", - description=model.MultiLanguageTextType({ - 'en-US': 'Legally valid designation of the natural or judicial person which ' - 'is directly responsible for the design, production, packaging and ' - 'labeling of a product in respect to its being brought into ' - 'circulation.', - 'de': 'Bezeichnung für eine natürliche oder juristische Person, die für die ' - 'Auslegung, Herstellung und Verpackung sowie die Etikettierung eines ' - 'Produkts im Hinblick auf das \'Inverkehrbringen\' im eigenen Namen ' - 'verantwortlich ist' - }), + description=model.MultiLanguageTextType( + { + "en-US": "Legally valid designation of the natural or judicial person which " + "is directly responsible for the design, production, packaging and " + "labeling of a product in respect to its being brought into " + "circulation.", + "de": "Bezeichnung für eine natürliche oder juristische Person, die für die " + "Auslegung, Herstellung und Verpackung sowie die Etikettierung eines " + "Produkts im Hinblick auf das 'Inverkehrbringen' im eigenen Namen " + "verantwortlich ist", + } + ), parent=None, - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='0173-1#02-AAO677#002'),)), + semantic_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, value="0173-1#02-AAO677#002" + ), + ) + ), qualifier={qualifier, qualifier2}, extension={extension}, supplemental_semantic_id=(), - embedded_data_specifications=() + embedded_data_specifications=(), ) # Property-Element conform to 'Verwaltungsschale in der Praxis' page 44 InstanceId: # https://www.plattform-i40.de/PI40/Redaktion/DE/Downloads/Publikation/2019-verwaltungsschale-in-der-praxis.html identification_submodel_element_instance_id = model.Property( - id_short='InstanceId', + id_short="InstanceId", value_type=model.datatypes.String, - value='978-8234-234-342', - value_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/ValueId/ExampleValueId'),)), + value="978-8234-234-342", + value_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/ValueId/ExampleValueId", + ), + ) + ), category="PARAMETER", - description=model.MultiLanguageTextType({ - 'en-US': 'Legally valid designation of the natural or judicial person which ' - 'is directly responsible for the design, production, packaging and ' - 'labeling of a product in respect to its being brought into ' - 'circulation.', - 'de': 'Bezeichnung für eine natürliche oder juristische Person, die für die ' - 'Auslegung, Herstellung und Verpackung sowie die Etikettierung eines ' - 'Produkts im Hinblick auf das \'Inverkehrbringen\' im eigenen Namen ' - 'verantwortlich ist' - }), + description=model.MultiLanguageTextType( + { + "en-US": "Legally valid designation of the natural or judicial person which " + "is directly responsible for the design, production, packaging and " + "labeling of a product in respect to its being brought into " + "circulation.", + "de": "Bezeichnung für eine natürliche oder juristische Person, die für die " + "Auslegung, Herstellung und Verpackung sowie die Etikettierung eines " + "Produkts im Hinblick auf das 'Inverkehrbringen' im eigenen Namen " + "verantwortlich ist", + } + ), parent=None, - semantic_id=model.ExternalReference((model.Key( - type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://opcfoundation.org/UA/DI/1.1/DeviceType/Serialnumber' - ),)), + semantic_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://opcfoundation.org/UA/DI/1.1/DeviceType/Serialnumber", + ), + ) + ), qualifier={qualifier3}, extension=(), supplemental_semantic_id=(), - embedded_data_specifications=() + embedded_data_specifications=(), ) # asset identification submodel which will be included in the asset object identification_submodel = model.Submodel( - id_='http://example.org/Submodels/Assets/TestAsset/Identification', - submodel_element=(identification_submodel_element_manufacturer_name, - identification_submodel_element_instance_id), - id_short='Identification', + id_="http://example.org/Submodels/Assets/TestAsset/Identification", + submodel_element=( + identification_submodel_element_manufacturer_name, + identification_submodel_element_instance_id, + ), + id_short="Identification", category=None, - description=model.MultiLanguageTextType({ - 'en-US': 'An example asset identification submodel for the test application', - 'de': 'Ein Beispiel-Identifikations-Submodel für eine Test-Anwendung' - }), + description=model.MultiLanguageTextType( + { + "en-US": "An example asset identification submodel for the test application", + "de": "Ein Beispiel-Identifikations-Submodel für eine Test-Anwendung", + } + ), parent=None, - administration=model.AdministrativeInformation(version='9', - revision='0', - creator=model.ExternalReference(( - model.Key(model.KeyTypes.GLOBAL_REFERENCE, - 'http://example.org/AdministrativeInformation/' - 'TestAsset/Identification'), - )), - template_id='http://example.org/AdministrativeInformation' - 'Templates/TestAsset/Identification'), - semantic_id=model.ModelReference((model.Key(type_=model.KeyTypes.SUBMODEL, - value='http://example.org/SubmodelTemplates/' - 'AssetIdentification'),), - model.Submodel), + administration=model.AdministrativeInformation( + version="9", + revision="0", + creator=model.ExternalReference( + ( + model.Key( + model.KeyTypes.GLOBAL_REFERENCE, + "http://example.org/AdministrativeInformation/" + "TestAsset/Identification", + ), + ) + ), + template_id="http://example.org/AdministrativeInformation" + "Templates/TestAsset/Identification", + ), + semantic_id=model.ModelReference( + ( + model.Key( + type_=model.KeyTypes.SUBMODEL, + value="http://example.org/SubmodelTemplates/AssetIdentification", + ), + ), + model.Submodel, + ), qualifier=(), kind=model.ModellingKind.INSTANCE, extension=(), supplemental_semantic_id=(), - embedded_data_specifications=() + embedded_data_specifications=(), ) return identification_submodel @@ -205,125 +317,180 @@ def create_example_bill_of_material_submodel() -> model.Submodel: :return: example bill of material submodel """ submodel_element_property = model.Property( - id_short='ExampleProperty', + id_short="ExampleProperty", value_type=model.datatypes.String, - value='exampleValue', - value_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/ValueId/ExampleValueId'),)), - category='CONSTANT', - description=model.MultiLanguageTextType({'en-US': 'Example Property object', - 'de': 'Beispiel Property Element'}), + value="exampleValue", + value_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/ValueId/ExampleValueId", + ), + ) + ), + category="CONSTANT", + description=model.MultiLanguageTextType( + {"en-US": "Example Property object", "de": "Beispiel Property Element"} + ), parent=None, - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/Properties/ExampleProperty'),)), + semantic_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/Properties/ExampleProperty", + ), + ) + ), qualifier=(), extension=(), supplemental_semantic_id=(), - embedded_data_specifications=() + embedded_data_specifications=(), ) submodel_element_property2 = model.Property( - id_short='ExampleProperty2', + id_short="ExampleProperty2", value_type=model.datatypes.String, - value='exampleValue2', - value_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/ValueId/ExampleValueId'),)), - category='CONSTANT', - description=model.MultiLanguageTextType({'en-US': 'Example Property object', - 'de': 'Beispiel Property Element'}), + value="exampleValue2", + value_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/ValueId/ExampleValueId", + ), + ) + ), + category="CONSTANT", + description=model.MultiLanguageTextType( + {"en-US": "Example Property object", "de": "Beispiel Property Element"} + ), parent=None, - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/Properties/ExampleProperty'),)), + semantic_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/Properties/ExampleProperty", + ), + ) + ), qualifier=(), extension=(), supplemental_semantic_id=(), - embedded_data_specifications=() + embedded_data_specifications=(), ) entity = model.Entity( - id_short='ExampleEntity', + id_short="ExampleEntity", entity_type=model.EntityType.SELF_MANAGED_ENTITY, statement={submodel_element_property, submodel_element_property2}, - global_asset_id='http://example.org/TestAsset/', + global_asset_id="http://example.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://example.org/SpecificAssetId/'),)) - )}, + model.SpecificAssetId( + name="TestKey", + value="TestValue", + external_subject_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/SpecificAssetId/", + ), + ) + ), + ) + }, category="PARAMETER", - description=model.MultiLanguageTextType({ - 'en-US': 'Legally valid designation of the natural or judicial person which ' - 'is directly responsible for the design, production, packaging and ' - 'labeling of a product in respect to its being brought into ' - 'circulation.', - 'de': 'Bezeichnung für eine natürliche oder juristische Person, die für die ' - 'Auslegung, Herstellung und Verpackung sowie die Etikettierung eines ' - 'Produkts im Hinblick auf das \'Inverkehrbringen\' im eigenen Namen ' - 'verantwortlich ist' - }), + description=model.MultiLanguageTextType( + { + "en-US": "Legally valid designation of the natural or judicial person which " + "is directly responsible for the design, production, packaging and " + "labeling of a product in respect to its being brought into " + "circulation.", + "de": "Bezeichnung für eine natürliche oder juristische Person, die für die " + "Auslegung, Herstellung und Verpackung sowie die Etikettierung eines " + "Produkts im Hinblick auf das 'Inverkehrbringen' im eigenen Namen " + "verantwortlich ist", + } + ), parent=None, - semantic_id=model.ExternalReference((model.Key( - type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://opcfoundation.org/UA/DI/1.1/DeviceType/Serialnumber' - ),)), + semantic_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://opcfoundation.org/UA/DI/1.1/DeviceType/Serialnumber", + ), + ) + ), qualifier=(), extension=(), supplemental_semantic_id=(), - embedded_data_specifications=() + embedded_data_specifications=(), ) entity_2 = model.Entity( - id_short='ExampleEntity2', + id_short="ExampleEntity2", entity_type=model.EntityType.CO_MANAGED_ENTITY, statement=(), global_asset_id=None, specific_asset_id=(), category="PARAMETER", - description=model.MultiLanguageTextType({ - 'en-US': 'Legally valid designation of the natural or judicial person which ' - 'is directly responsible for the design, production, packaging and ' - 'labeling of a product in respect to its being brought into ' - 'circulation.', - 'de': 'Bezeichnung für eine natürliche oder juristische Person, die für die ' - 'Auslegung, Herstellung und Verpackung sowie die Etikettierung eines ' - 'Produkts im Hinblick auf das \'Inverkehrbringen\' im eigenen Namen ' - 'verantwortlich ist' - }), + description=model.MultiLanguageTextType( + { + "en-US": "Legally valid designation of the natural or judicial person which " + "is directly responsible for the design, production, packaging and " + "labeling of a product in respect to its being brought into " + "circulation.", + "de": "Bezeichnung für eine natürliche oder juristische Person, die für die " + "Auslegung, Herstellung und Verpackung sowie die Etikettierung eines " + "Produkts im Hinblick auf das 'Inverkehrbringen' im eigenen Namen " + "verantwortlich ist", + } + ), parent=None, - semantic_id=model.ExternalReference((model.Key( - type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://opcfoundation.org/UA/DI/1.1/DeviceType/Serialnumber' - ),)), + semantic_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://opcfoundation.org/UA/DI/1.1/DeviceType/Serialnumber", + ), + ) + ), qualifier=(), extension=(), supplemental_semantic_id=(), - embedded_data_specifications=() + embedded_data_specifications=(), ) # bill of material submodel which will be included in the asset object bill_of_material = model.Submodel( - id_='http://example.org/Submodels/Assets/TestAsset/BillOfMaterial', - submodel_element=(entity, - entity_2), - id_short='BillOfMaterial', + id_="http://example.org/Submodels/Assets/TestAsset/BillOfMaterial", + submodel_element=(entity, entity_2), + id_short="BillOfMaterial", category=None, - description=model.MultiLanguageTextType({ - 'en-US': 'An example bill of material submodel for the test application', - 'de': 'Ein Beispiel-BillOfMaterial-Submodel für eine Test-Anwendung' - }), + description=model.MultiLanguageTextType( + { + "en-US": "An example bill of material submodel for the test application", + "de": "Ein Beispiel-BillOfMaterial-Submodel für eine Test-Anwendung", + } + ), parent=None, - administration=model.AdministrativeInformation(version='9', - template_id='http://example.org/AdministrativeInformation' - 'Templates/TestAsset/BillOfMaterial'), - semantic_id=model.ModelReference((model.Key(type_=model.KeyTypes.SUBMODEL, - value='http://example.org/SubmodelTemplates/BillOfMaterial'),), - model.Submodel), + administration=model.AdministrativeInformation( + version="9", + template_id="http://example.org/AdministrativeInformation" + "Templates/TestAsset/BillOfMaterial", + ), + semantic_id=model.ModelReference( + ( + model.Key( + type_=model.KeyTypes.SUBMODEL, + value="http://example.org/SubmodelTemplates/BillOfMaterial", + ), + ), + model.Submodel, + ), qualifier=(), kind=model.ModellingKind.INSTANCE, extension=(), supplemental_semantic_id=(), - embedded_data_specifications=() + embedded_data_specifications=(), ) return bill_of_material @@ -339,422 +506,707 @@ def create_example_submodel() -> model.Submodel: submodel_element_property = model.Property( id_short=None, value_type=model.datatypes.String, - value='exampleValue', - value_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/ValueId/ExampleValueId'),)), - display_name=model.MultiLanguageNameType({'en-US': 'ExampleProperty', - 'de': 'BeispielProperty'}), - category='CONSTANT', - description=model.MultiLanguageTextType({'en-US': 'Example Property object', - 'de': 'Beispiel Property Element'}), + value="exampleValue", + value_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/ValueId/ExampleValueId", + ), + ) + ), + display_name=model.MultiLanguageNameType( + {"en-US": "ExampleProperty", "de": "BeispielProperty"} + ), + category="CONSTANT", + description=model.MultiLanguageTextType( + {"en-US": "Example Property object", "de": "Beispiel Property Element"} + ), parent=None, - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/Properties/ExampleProperty'),), ), + semantic_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/Properties/ExampleProperty", + ), + ), + ), qualifier=(), extension=(), - supplemental_semantic_id=(model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/Properties/' - 'ExampleProperty/SupplementalId1'),)), - model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/Properties/' - 'ExampleProperty/SupplementalId2'),))), - embedded_data_specifications=(_embedded_data_specification_iec61360,)) + supplemental_semantic_id=( + model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/Properties/" + "ExampleProperty/SupplementalId1", + ), + ) + ), + model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/Properties/" + "ExampleProperty/SupplementalId2", + ), + ) + ), + ), + embedded_data_specifications=(_embedded_data_specification_iec61360,), + ) submodel_element_property_2 = model.Property( id_short=None, value_type=model.datatypes.String, - value='exampleValue', - value_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/ValueId/ExampleValueId'),)), - display_name=model.MultiLanguageNameType({'en-US': 'ExampleProperty', - 'de': 'BeispielProperty'}), - category='CONSTANT', - description=model.MultiLanguageTextType({'en-US': 'Example Property object', - 'de': 'Beispiel Property Element'}), + value="exampleValue", + value_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/ValueId/ExampleValueId", + ), + ) + ), + display_name=model.MultiLanguageNameType( + {"en-US": "ExampleProperty", "de": "BeispielProperty"} + ), + category="CONSTANT", + description=model.MultiLanguageTextType( + {"en-US": "Example Property object", "de": "Beispiel Property Element"} + ), parent=None, - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/Properties/ExampleProperty'),)), + semantic_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/Properties/ExampleProperty", + ), + ) + ), qualifier=(), extension=(), - supplemental_semantic_id=(model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/Properties/' - 'ExampleProperty2/SupplementalId'),)),), - embedded_data_specifications=() + supplemental_semantic_id=( + model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/Properties/" + "ExampleProperty2/SupplementalId", + ), + ) + ), + ), + embedded_data_specifications=(), ) submodel_element_multi_language_property = model.MultiLanguageProperty( - id_short='ExampleMultiLanguageProperty', - value=model.MultiLanguageTextType({'en-US': 'Example value of a MultiLanguageProperty element', - 'de': 'Beispielwert für ein MultiLanguageProperty-Element'}), - value_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/ValueId/ExampleMultiLanguageValueId'),)), - category='CONSTANT', - description=model.MultiLanguageTextType({'en-US': 'Example MultiLanguageProperty object', - 'de': 'Beispiel MultiLanguageProperty Element'}), + id_short="ExampleMultiLanguageProperty", + value=model.MultiLanguageTextType( + { + "en-US": "Example value of a MultiLanguageProperty element", + "de": "Beispielwert für ein MultiLanguageProperty-Element", + } + ), + value_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/ValueId/ExampleMultiLanguageValueId", + ), + ) + ), + category="CONSTANT", + description=model.MultiLanguageTextType( + { + "en-US": "Example MultiLanguageProperty object", + "de": "Beispiel MultiLanguageProperty Element", + } + ), parent=None, - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/MultiLanguageProperties/' - 'ExampleMultiLanguageProperty'),), - referred_semantic_id=model.ExternalReference((model.Key( - type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/Properties/ExampleProperty/Referred'),))), + semantic_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/MultiLanguageProperties/" + "ExampleMultiLanguageProperty", + ), + ), + referred_semantic_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/Properties/ExampleProperty/Referred", + ), + ) + ), + ), qualifier=(), extension=(), supplemental_semantic_id=(), - embedded_data_specifications=() + embedded_data_specifications=(), ) submodel_element_range = model.Range( - id_short='ExampleRange', + id_short="ExampleRange", value_type=model.datatypes.Int, min=0, max=100, - category='PARAMETER', - description=model.MultiLanguageTextType({'en-US': 'Example Range object', - 'de': 'Beispiel Range Element'}), + category="PARAMETER", + description=model.MultiLanguageTextType( + {"en-US": "Example Range object", "de": "Beispiel Range Element"} + ), parent=None, - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/Ranges/ExampleRange'),)), + semantic_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/Ranges/ExampleRange", + ), + ) + ), qualifier=(), extension=(), supplemental_semantic_id=(), - embedded_data_specifications=() + embedded_data_specifications=(), ) submodel_element_blob = model.Blob( - id_short='ExampleBlob', - content_type='application/pdf', - value=bytes(b'\x01\x02\x03\x04\x05'), - category='PARAMETER', - description=model.MultiLanguageTextType({'en-US': 'Example Blob object', - 'de': 'Beispiel Blob Element'}), + id_short="ExampleBlob", + content_type="application/pdf", + value=bytes(b"\x01\x02\x03\x04\x05"), + category="PARAMETER", + description=model.MultiLanguageTextType( + {"en-US": "Example Blob object", "de": "Beispiel Blob Element"} + ), parent=None, - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/Blobs/ExampleBlob'),)), + semantic_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/Blobs/ExampleBlob", + ), + ) + ), qualifier=(), extension=(), supplemental_semantic_id=(), - embedded_data_specifications=() + embedded_data_specifications=(), ) submodel_element_file = model.File( - id_short='ExampleFile', - content_type='application/pdf', - value='/TestFile.pdf', - category='PARAMETER', - description=model.MultiLanguageTextType({'en-US': 'Example File object', - 'de': 'Beispiel File Element'}), + id_short="ExampleFile", + content_type="application/pdf", + value="/TestFile.pdf", + category="PARAMETER", + description=model.MultiLanguageTextType( + {"en-US": "Example File object", "de": "Beispiel File Element"} + ), parent=None, - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/Files/ExampleFile'),)), + semantic_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/Files/ExampleFile", + ), + ) + ), qualifier=(), extension=(), supplemental_semantic_id=(), - embedded_data_specifications=() + embedded_data_specifications=(), ) submodel_element_file_uri = model.File( - id_short='ExampleFileURI', - content_type='application/pdf', - value='https://www.plattform-i40.de/PI40/Redaktion/DE/Downloads/Publikation/Details-of-the-Asset-' - 'Administration-Shell-Part1.pdf?__blob=publicationFile&v=5', - category='CONSTANT', - description=model.MultiLanguageTextType({ - 'en-US': 'Details of the Asset Administration Shell — An example for an external file reference', - 'de': 'Details of the Asset Administration Shell – Ein Beispiel für eine extern referenzierte Datei' - }), + id_short="ExampleFileURI", + content_type="application/pdf", + value="https://www.plattform-i40.de/PI40/Redaktion/DE/Downloads/Publikation/Details-of-the-Asset-" + "Administration-Shell-Part1.pdf?__blob=publicationFile&v=5", + category="CONSTANT", + description=model.MultiLanguageTextType( + { + "en-US": "Details of the Asset Administration Shell — An example for an external file reference", + "de": "Details of the Asset Administration Shell – Ein Beispiel für eine extern referenzierte Datei", + } + ), parent=None, - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/Files/ExampleFile'),)), + semantic_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/Files/ExampleFile", + ), + ) + ), qualifier=(), extension=(), supplemental_semantic_id=(), - embedded_data_specifications=() + embedded_data_specifications=(), ) submodel_element_reference_element = model.ReferenceElement( - id_short='ExampleReferenceElement', - value=model.ModelReference((model.Key(type_=model.KeyTypes.SUBMODEL, - value='http://example.org/Test_Submodel'), - model.Key(type_=model.KeyTypes.PROPERTY, - value='ExampleProperty'),), - model.Property), - category='PARAMETER', - description=model.MultiLanguageTextType({'en-US': 'Example Reference Element object', - 'de': 'Beispiel Reference Element Element'}), + id_short="ExampleReferenceElement", + value=model.ModelReference( + ( + model.Key( + type_=model.KeyTypes.SUBMODEL, + value="http://example.org/Test_Submodel", + ), + model.Key(type_=model.KeyTypes.PROPERTY, value="ExampleProperty"), + ), + model.Property, + ), + category="PARAMETER", + description=model.MultiLanguageTextType( + { + "en-US": "Example Reference Element object", + "de": "Beispiel Reference Element Element", + } + ), parent=None, - semantic_id=model.ExternalReference((model.Key( - type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/ReferenceElements/ExampleReferenceElement' - ),)), + semantic_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/ReferenceElements/ExampleReferenceElement", + ), + ) + ), qualifier=(), extension=(), supplemental_semantic_id=(), - embedded_data_specifications=() + embedded_data_specifications=(), ) submodel_element_relationship_element = model.RelationshipElement( - id_short='ExampleRelationshipElement', - first=model.ModelReference(( - model.Key( - type_=model.KeyTypes.SUBMODEL, - value='http://example.org/Test_Submodel'), - model.Key( - type_=model.KeyTypes.PROPERTY, - value='ExampleProperty'), - ), model.Property), - second=model.ModelReference(( - model.Key( - type_=model.KeyTypes.SUBMODEL, - value='http://example.org/Test_Submodel'), - model.Key( - type_=model.KeyTypes.PROPERTY, - value='ExampleProperty2'), - ), model.Property), - category='PARAMETER', - description=model.MultiLanguageTextType({'en-US': 'Example RelationshipElement object', - 'de': 'Beispiel RelationshipElement Element'}), - parent=None, - semantic_id=model.ModelReference((model.Key(type_=model.KeyTypes.CONCEPT_DESCRIPTION, - value='https://example.org/Test_ConceptDescription'),), - model.ConceptDescription), - qualifier=(), - extension=(), - supplemental_semantic_id=(), - embedded_data_specifications=() - ) - - submodel_element_annotated_relationship_element = model.AnnotatedRelationshipElement( - id_short='ExampleAnnotatedRelationshipElement', - first=model.ModelReference((model.Key(type_=model.KeyTypes.SUBMODEL, value='http://example.org/Test_Submodel'), - model.Key(type_=model.KeyTypes.PROPERTY, - value='ExampleProperty'),), - model.Property), - second=model.ModelReference((model.Key(type_=model.KeyTypes.SUBMODEL, value='http://example.org/Test_Submodel'), - model.Key(type_=model.KeyTypes.PROPERTY, - value='ExampleProperty2'),), - model.Property), - annotation={model.Property(id_short="ExampleAnnotatedProperty", - value_type=model.datatypes.String, - value='exampleValue', - category="PARAMETER", - parent=None), - model.Range(id_short="ExampleAnnotatedRange", - value_type=model.datatypes.Integer, - min=1, - max=5, - category="PARAMETER", - parent=None) - }, - category='PARAMETER', - description=model.MultiLanguageTextType({'en-US': 'Example AnnotatedRelationshipElement object', - 'de': 'Beispiel AnnotatedRelationshipElement Element'}), + id_short="ExampleRelationshipElement", + first=model.ModelReference( + ( + model.Key( + type_=model.KeyTypes.SUBMODEL, + value="http://example.org/Test_Submodel", + ), + model.Key(type_=model.KeyTypes.PROPERTY, value="ExampleProperty"), + ), + model.Property, + ), + second=model.ModelReference( + ( + model.Key( + type_=model.KeyTypes.SUBMODEL, + value="http://example.org/Test_Submodel", + ), + model.Key(type_=model.KeyTypes.PROPERTY, value="ExampleProperty2"), + ), + model.Property, + ), + category="PARAMETER", + description=model.MultiLanguageTextType( + { + "en-US": "Example RelationshipElement object", + "de": "Beispiel RelationshipElement Element", + } + ), parent=None, - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/RelationshipElements/' - 'ExampleAnnotatedRelationshipElement'),)), + semantic_id=model.ModelReference( + ( + model.Key( + type_=model.KeyTypes.CONCEPT_DESCRIPTION, + value="https://example.org/Test_ConceptDescription", + ), + ), + model.ConceptDescription, + ), qualifier=(), extension=(), supplemental_semantic_id=(), - embedded_data_specifications=() + embedded_data_specifications=(), + ) + + submodel_element_annotated_relationship_element = ( + model.AnnotatedRelationshipElement( + id_short="ExampleAnnotatedRelationshipElement", + first=model.ModelReference( + ( + model.Key( + type_=model.KeyTypes.SUBMODEL, + value="http://example.org/Test_Submodel", + ), + model.Key(type_=model.KeyTypes.PROPERTY, value="ExampleProperty"), + ), + model.Property, + ), + second=model.ModelReference( + ( + model.Key( + type_=model.KeyTypes.SUBMODEL, + value="http://example.org/Test_Submodel", + ), + model.Key(type_=model.KeyTypes.PROPERTY, value="ExampleProperty2"), + ), + model.Property, + ), + annotation={ + model.Property( + id_short="ExampleAnnotatedProperty", + value_type=model.datatypes.String, + value="exampleValue", + category="PARAMETER", + parent=None, + ), + model.Range( + id_short="ExampleAnnotatedRange", + value_type=model.datatypes.Integer, + min=1, + max=5, + category="PARAMETER", + parent=None, + ), + }, + category="PARAMETER", + description=model.MultiLanguageTextType( + { + "en-US": "Example AnnotatedRelationshipElement object", + "de": "Beispiel AnnotatedRelationshipElement Element", + } + ), + parent=None, + semantic_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/RelationshipElements/" + "ExampleAnnotatedRelationshipElement", + ), + ) + ), + qualifier=(), + extension=(), + supplemental_semantic_id=(), + embedded_data_specifications=(), + ) ) input_variable_property = model.Property( - id_short='ExamplePropertyInput', + id_short="ExamplePropertyInput", value_type=model.datatypes.String, - value='exampleValue', - value_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/ValueId/ExampleValueId'),)), - display_name=model.MultiLanguageNameType({'en-US': 'ExampleProperty', - 'de': 'BeispielProperty'}), - category='CONSTANT', - description=model.MultiLanguageTextType({'en-US': 'Example Property object', - 'de': 'Beispiel Property Element'}), + value="exampleValue", + value_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/ValueId/ExampleValueId", + ), + ) + ), + display_name=model.MultiLanguageNameType( + {"en-US": "ExampleProperty", "de": "BeispielProperty"} + ), + category="CONSTANT", + description=model.MultiLanguageTextType( + {"en-US": "Example Property object", "de": "Beispiel Property Element"} + ), parent=None, - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/Properties/ExamplePropertyInput'),)), + semantic_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/Properties/ExamplePropertyInput", + ), + ) + ), qualifier=(), extension=(), supplemental_semantic_id=(), - embedded_data_specifications=() + embedded_data_specifications=(), ) output_variable_property = model.Property( - id_short='ExamplePropertyOutput', + id_short="ExamplePropertyOutput", value_type=model.datatypes.String, - value='exampleValue', - value_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/ValueId/ExampleValueId'),)), - display_name=model.MultiLanguageNameType({'en-US': 'ExampleProperty', - 'de': 'BeispielProperty'}), - category='CONSTANT', - description=model.MultiLanguageTextType({'en-US': 'Example Property object', - 'de': 'Beispiel Property Element'}), + value="exampleValue", + value_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/ValueId/ExampleValueId", + ), + ) + ), + display_name=model.MultiLanguageNameType( + {"en-US": "ExampleProperty", "de": "BeispielProperty"} + ), + category="CONSTANT", + description=model.MultiLanguageTextType( + {"en-US": "Example Property object", "de": "Beispiel Property Element"} + ), parent=None, - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/Properties/ExamplePropertyOutput'),)), + semantic_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/Properties/ExamplePropertyOutput", + ), + ) + ), qualifier=(), extension=(), supplemental_semantic_id=(), - embedded_data_specifications=() + embedded_data_specifications=(), ) in_output_variable_property = model.Property( - id_short='ExamplePropertyInOutput', + id_short="ExamplePropertyInOutput", value_type=model.datatypes.String, - value='exampleValue', - value_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/ValueId/ExampleValueId'),)), - display_name=model.MultiLanguageNameType({'en-US': 'ExampleProperty', - 'de': 'BeispielProperty'}), - category='CONSTANT', - description=model.MultiLanguageTextType({'en-US': 'Example Property object', - 'de': 'Beispiel Property Element'}), + value="exampleValue", + value_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/ValueId/ExampleValueId", + ), + ) + ), + display_name=model.MultiLanguageNameType( + {"en-US": "ExampleProperty", "de": "BeispielProperty"} + ), + category="CONSTANT", + description=model.MultiLanguageTextType( + {"en-US": "Example Property object", "de": "Beispiel Property Element"} + ), parent=None, - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/Properties/' - 'ExamplePropertyInOutput'),)), + semantic_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/Properties/ExamplePropertyInOutput", + ), + ) + ), qualifier=(), extension=(), supplemental_semantic_id=(), - embedded_data_specifications=() + embedded_data_specifications=(), ) submodel_element_operation = model.Operation( - id_short='ExampleOperation', + id_short="ExampleOperation", input_variable=[input_variable_property], output_variable=[output_variable_property], in_output_variable=[in_output_variable_property], - category='PARAMETER', - description=model.MultiLanguageTextType({'en-US': 'Example Operation object', - 'de': 'Beispiel Operation Element'}), + category="PARAMETER", + description=model.MultiLanguageTextType( + {"en-US": "Example Operation object", "de": "Beispiel Operation Element"} + ), parent=None, - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/Operations/' - 'ExampleOperation'),)), + semantic_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/Operations/ExampleOperation", + ), + ) + ), qualifier=(), extension=(), supplemental_semantic_id=(), - embedded_data_specifications=() + embedded_data_specifications=(), ) submodel_element_capability = model.Capability( - id_short='ExampleCapability', - category='PARAMETER', - description=model.MultiLanguageTextType({'en-US': 'Example Capability object', - 'de': 'Beispiel Capability Element'}), + id_short="ExampleCapability", + category="PARAMETER", + description=model.MultiLanguageTextType( + {"en-US": "Example Capability object", "de": "Beispiel Capability Element"} + ), parent=None, - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/Capabilities/' - 'ExampleCapability'),)), + semantic_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/Capabilities/ExampleCapability", + ), + ) + ), qualifier=(), extension=(), supplemental_semantic_id=(), - embedded_data_specifications=() + embedded_data_specifications=(), ) submodel_element_basic_event_element = model.BasicEventElement( - id_short='ExampleBasicEventElement', - observed=model.ModelReference((model.Key(type_=model.KeyTypes.SUBMODEL, - value='http://example.org/Test_Submodel'), - model.Key(type_=model.KeyTypes.PROPERTY, - value='ExampleProperty'),), - model.Property), + id_short="ExampleBasicEventElement", + observed=model.ModelReference( + ( + model.Key( + type_=model.KeyTypes.SUBMODEL, + value="http://example.org/Test_Submodel", + ), + model.Key(type_=model.KeyTypes.PROPERTY, value="ExampleProperty"), + ), + model.Property, + ), direction=model.Direction.OUTPUT, state=model.StateOfEvent.ON, - message_topic='ExampleTopic', - message_broker=model.ModelReference((model.Key(model.KeyTypes.SUBMODEL, - "http://example.org/ExampleMessageBroker"),), - model.Submodel), - last_update=model.datatypes.DateTime(2022, 11, 12, 23, 50, 23, 123456, datetime.timezone.utc), + message_topic="ExampleTopic", + message_broker=model.ModelReference( + ( + model.Key( + model.KeyTypes.SUBMODEL, "http://example.org/ExampleMessageBroker" + ), + ), + model.Submodel, + ), + last_update=model.datatypes.DateTime( + 2022, 11, 12, 23, 50, 23, 123456, datetime.timezone.utc + ), min_interval=model.datatypes.Duration(microseconds=1), - max_interval=model.datatypes.Duration(years=1, months=2, days=3, hours=4, minutes=5, seconds=6, - microseconds=123456), - category='PARAMETER', - description=model.MultiLanguageTextType({'en-US': 'Example BasicEventElement object', - 'de': 'Beispiel BasicEventElement Element'}), + max_interval=model.datatypes.Duration( + years=1, + months=2, + days=3, + hours=4, + minutes=5, + seconds=6, + microseconds=123456, + ), + category="PARAMETER", + description=model.MultiLanguageTextType( + { + "en-US": "Example BasicEventElement object", + "de": "Beispiel BasicEventElement Element", + } + ), parent=None, - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/Events/ExampleBasicEventElement'),)), + semantic_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/Events/ExampleBasicEventElement", + ), + ) + ), qualifier=(), extension=(), supplemental_semantic_id=(), - embedded_data_specifications=() + embedded_data_specifications=(), ) submodel_element_submodel_element_list = model.SubmodelElementList( - id_short='ExampleSubmodelList', + id_short="ExampleSubmodelList", type_value_list_element=model.Property, value=(submodel_element_property, submodel_element_property_2), - semantic_id_list_element=model.ExternalReference((model.Key( - type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/Properties/ExampleProperty' - ),)), + semantic_id_list_element=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/Properties/ExampleProperty", + ), + ) + ), value_type_list_element=model.datatypes.String, order_relevant=True, - category='PARAMETER', - description=model.MultiLanguageTextType({'en-US': 'Example SubmodelElementList object', - 'de': 'Beispiel SubmodelElementList Element'}), + category="PARAMETER", + description=model.MultiLanguageTextType( + { + "en-US": "Example SubmodelElementList object", + "de": "Beispiel SubmodelElementList Element", + } + ), parent=None, - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/SubmodelElementLists/' - 'ExampleSubmodelElementList'),)), + semantic_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/SubmodelElementLists/" + "ExampleSubmodelElementList", + ), + ) + ), qualifier=(), extension=(), supplemental_semantic_id=(), - embedded_data_specifications=() + embedded_data_specifications=(), ) submodel_element_submodel_element_collection = model.SubmodelElementCollection( - id_short='ExampleSubmodelCollection', - value=(submodel_element_blob, - submodel_element_file, - submodel_element_file_uri, - submodel_element_multi_language_property, - submodel_element_range, - submodel_element_reference_element, - submodel_element_submodel_element_list), - category='PARAMETER', - description=model.MultiLanguageTextType({'en-US': 'Example SubmodelElementCollection object', - 'de': 'Beispiel SubmodelElementCollection Element'}), + id_short="ExampleSubmodelCollection", + value=( + submodel_element_blob, + submodel_element_file, + submodel_element_file_uri, + submodel_element_multi_language_property, + submodel_element_range, + submodel_element_reference_element, + submodel_element_submodel_element_list, + ), + category="PARAMETER", + description=model.MultiLanguageTextType( + { + "en-US": "Example SubmodelElementCollection object", + "de": "Beispiel SubmodelElementCollection Element", + } + ), parent=None, - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/SubmodelElementCollections/' - 'ExampleSubmodelElementCollection'),)), + semantic_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/SubmodelElementCollections/" + "ExampleSubmodelElementCollection", + ), + ) + ), qualifier=(), extension=(), supplemental_semantic_id=(), - embedded_data_specifications=() + embedded_data_specifications=(), ) submodel = model.Submodel( - id_='https://example.org/Test_Submodel', - submodel_element=(submodel_element_relationship_element, - submodel_element_annotated_relationship_element, - submodel_element_operation, - submodel_element_capability, - submodel_element_basic_event_element, - submodel_element_submodel_element_collection), - id_short='TestSubmodel', + id_="https://example.org/Test_Submodel", + submodel_element=( + submodel_element_relationship_element, + submodel_element_annotated_relationship_element, + submodel_element_operation, + submodel_element_capability, + submodel_element_basic_event_element, + submodel_element_submodel_element_collection, + ), + id_short="TestSubmodel", category=None, - description=model.MultiLanguageTextType({'en-US': 'An example submodel for the test application', - 'de': 'Ein Beispiel-Teilmodell für eine Test-Anwendung'}), + description=model.MultiLanguageTextType( + { + "en-US": "An example submodel for the test application", + "de": "Ein Beispiel-Teilmodell für eine Test-Anwendung", + } + ), parent=None, - administration=model.AdministrativeInformation(version='9', - revision='0', - creator=model.ExternalReference(( - model.Key(model.KeyTypes.GLOBAL_REFERENCE, - 'http://example.org/AdministrativeInformation/' - 'Test_Submodel'), - )),), - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/SubmodelTemplates/' - 'ExampleSubmodel'),)), + administration=model.AdministrativeInformation( + version="9", + revision="0", + creator=model.ExternalReference( + ( + model.Key( + model.KeyTypes.GLOBAL_REFERENCE, + "http://example.org/AdministrativeInformation/Test_Submodel", + ), + ) + ), + ), + semantic_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/SubmodelTemplates/ExampleSubmodel", + ), + ) + ), qualifier=(), kind=model.ModellingKind.INSTANCE, extension=(), supplemental_semantic_id=(), - embedded_data_specifications=() + embedded_data_specifications=(), ) return submodel @@ -766,33 +1218,48 @@ def create_example_concept_description() -> model.ConceptDescription: :return: example concept description """ concept_description = model.ConceptDescription( - id_='https://example.org/Test_ConceptDescription', - is_case_of={model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/DataSpecifications/' - 'ConceptDescriptions/TestConceptDescription'),))}, - id_short='TestConceptDescription', + id_="https://example.org/Test_ConceptDescription", + is_case_of={ + model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/DataSpecifications/" + "ConceptDescriptions/TestConceptDescription", + ), + ) + ) + }, + id_short="TestConceptDescription", category=None, - description=model.MultiLanguageTextType({'en-US': 'An example concept description for the test application', - 'de': 'Ein Beispiel-ConceptDescription für eine Test-Anwendung'}), + description=model.MultiLanguageTextType( + { + "en-US": "An example concept description for the test application", + "de": "Ein Beispiel-ConceptDescription für eine Test-Anwendung", + } + ), parent=None, - administration=model.AdministrativeInformation(version='9', - revision='0', - creator=model.ExternalReference(( - model.Key(model.KeyTypes.GLOBAL_REFERENCE, - 'http://example.org/AdministrativeInformation/' - 'Test_ConceptDescription'), - )), - template_id='http://example.org/AdministrativeInformation' - 'Templates/Test_ConceptDescription', - embedded_data_specifications=( - _embedded_data_specification_iec61360, - )) + administration=model.AdministrativeInformation( + version="9", + revision="0", + creator=model.ExternalReference( + ( + model.Key( + model.KeyTypes.GLOBAL_REFERENCE, + "http://example.org/AdministrativeInformation/" + "Test_ConceptDescription", + ), + ) + ), + template_id="http://example.org/AdministrativeInformation" + "Templates/Test_ConceptDescription", + embedded_data_specifications=(_embedded_data_specification_iec61360,), + ), ) return concept_description -def create_example_asset_administration_shell() -> \ - model.AssetAdministrationShell: +def create_example_asset_administration_shell() -> model.AssetAdministrationShell: """ Creates an :class:`~basyx.aas.model.aas.AssetAdministrationShell` with references to an example :class:`~basyx.aas.model.submodel.Submodel`. @@ -802,69 +1269,120 @@ def create_example_asset_administration_shell() -> \ asset_information = model.AssetInformation( asset_kind=model.AssetKind.INSTANCE, - global_asset_id='http://example.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://example.org/SpecificAssetId/'),)), - semantic_id=model.ExternalReference((model.Key( - model.KeyTypes.GLOBAL_REFERENCE, - "http://example.org/SpecificAssetId/" - ),)))}, - asset_type='http://example.org/TestAssetType/', - default_thumbnail=model.Resource( - "file:///path/to/thumbnail.png", - "image/png" - ) + global_asset_id="http://example.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://example.org/SpecificAssetId/", + ), + ) + ), + semantic_id=model.ExternalReference( + ( + model.Key( + model.KeyTypes.GLOBAL_REFERENCE, + "http://example.org/SpecificAssetId/", + ), + ) + ), + ) + }, + asset_type="http://example.org/TestAssetType/", + default_thumbnail=model.Resource("file:///path/to/thumbnail.png", "image/png"), ) asset_administration_shell = model.AssetAdministrationShell( asset_information=asset_information, - id_='https://example.org/Test_AssetAdministrationShell', - id_short='TestAssetAdministrationShell', + id_="https://example.org/Test_AssetAdministrationShell", + id_short="TestAssetAdministrationShell", category=None, - description=model.MultiLanguageTextType({ - 'en-US': 'An Example Asset Administration Shell for the test application', - 'de': 'Ein Beispiel-Verwaltungsschale für eine Test-Anwendung' - }), + description=model.MultiLanguageTextType( + { + "en-US": "An Example Asset Administration Shell for the test application", + "de": "Ein Beispiel-Verwaltungsschale für eine Test-Anwendung", + } + ), parent=None, - administration=model.AdministrativeInformation(version='9', - revision='0', - creator=model.ExternalReference(( - model.Key(model.KeyTypes.GLOBAL_REFERENCE, - 'http://example.org/AdministrativeInformation/' - 'Test_AssetAdministrationShell'), - )), - template_id='http://example.org/AdministrativeInformation' - 'Templates/Test_AssetAdministrationShell'), - submodel={model.ModelReference((model.Key(type_=model.KeyTypes.SUBMODEL, - value='https://example.org/Test_Submodel'),), - model.Submodel, - model.ExternalReference(( - model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/SubmodelTemplates/ExampleSubmodel'), - ))), - model.ModelReference((model.Key(type_=model.KeyTypes.SUBMODEL, - value='http://example.org/Submodels/Assets/' - 'TestAsset/Identification'),), - model.Submodel, - model.ModelReference(( - model.Key(type_=model.KeyTypes.SUBMODEL, - value='http://example.org/SubmodelTemplates/' - 'AssetIdentification'),), - model.Submodel - )), - model.ModelReference((model.Key(type_=model.KeyTypes.SUBMODEL, - value='http://example.org/Submodels/Assets/' - 'TestAsset/BillOfMaterial'),), - model.Submodel), - }, - derived_from=model.ModelReference((model.Key(type_=model.KeyTypes.ASSET_ADMINISTRATION_SHELL, - value='https://example.org/TestAssetAdministrationShell2'),), - model.AssetAdministrationShell), + administration=model.AdministrativeInformation( + version="9", + revision="0", + creator=model.ExternalReference( + ( + model.Key( + model.KeyTypes.GLOBAL_REFERENCE, + "http://example.org/AdministrativeInformation/" + "Test_AssetAdministrationShell", + ), + ) + ), + template_id="http://example.org/AdministrativeInformation" + "Templates/Test_AssetAdministrationShell", + ), + submodel={ + model.ModelReference( + ( + model.Key( + type_=model.KeyTypes.SUBMODEL, + value="https://example.org/Test_Submodel", + ), + ), + model.Submodel, + model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/SubmodelTemplates/ExampleSubmodel", + ), + ) + ), + ), + model.ModelReference( + ( + model.Key( + type_=model.KeyTypes.SUBMODEL, + value="http://example.org/Submodels/Assets/" + "TestAsset/Identification", + ), + ), + model.Submodel, + model.ModelReference( + ( + model.Key( + type_=model.KeyTypes.SUBMODEL, + value="http://example.org/SubmodelTemplates/" + "AssetIdentification", + ), + ), + model.Submodel, + ), + ), + model.ModelReference( + ( + model.Key( + type_=model.KeyTypes.SUBMODEL, + value="http://example.org/Submodels/Assets/" + "TestAsset/BillOfMaterial", + ), + ), + model.Submodel, + ), + }, + derived_from=model.ModelReference( + ( + model.Key( + type_=model.KeyTypes.ASSET_ADMINISTRATION_SHELL, + value="https://example.org/TestAssetAdministrationShell2", + ), + ), + model.AssetAdministrationShell, + ), extension=(), - embedded_data_specifications=(_embedded_data_specification_iec61360,) + embedded_data_specifications=(_embedded_data_specification_iec61360,), ) return asset_administration_shell @@ -872,22 +1390,32 @@ def create_example_asset_administration_shell() -> \ ############################################################################## # check functions for checking if a given object is the same as the example # ############################################################################## -def check_example_asset_identification_submodel(checker: AASDataChecker, submodel: model.Submodel) -> None: +def check_example_asset_identification_submodel( + checker: AASDataChecker, submodel: model.Submodel +) -> None: expected_submodel = create_example_asset_identification_submodel() checker.check_submodel_equal(submodel, expected_submodel) -def check_example_bill_of_material_submodel(checker: AASDataChecker, submodel: model.Submodel) -> None: +def check_example_bill_of_material_submodel( + checker: AASDataChecker, submodel: model.Submodel +) -> None: expected_submodel = create_example_bill_of_material_submodel() checker.check_submodel_equal(submodel, expected_submodel) -def check_example_concept_description(checker: AASDataChecker, concept_description: model.ConceptDescription) -> None: +def check_example_concept_description( + checker: AASDataChecker, concept_description: model.ConceptDescription +) -> None: expected_concept_description = create_example_concept_description() - checker.check_concept_description_equal(concept_description, expected_concept_description) + checker.check_concept_description_equal( + concept_description, expected_concept_description + ) -def check_example_asset_administration_shell(checker: AASDataChecker, shell: model.AssetAdministrationShell) -> None: +def check_example_asset_administration_shell( + checker: AASDataChecker, shell: model.AssetAdministrationShell +) -> None: expected_shell = create_example_asset_administration_shell() checker.check_asset_administration_shell_equal(shell, expected_shell) @@ -897,6 +1425,8 @@ def check_example_submodel(checker: AASDataChecker, submodel: model.Submodel) -> checker.check_submodel_equal(submodel, expected_submodel) -def check_full_example(checker: AASDataChecker, identifiable_store: model.DictIdentifiableStore) -> None: +def check_full_example( + checker: AASDataChecker, identifiable_store: model.DictIdentifiableStore +) -> None: expected_data = create_full_example() checker.check_identifiable_store(identifiable_store, expected_data) diff --git a/sdk/basyx/aas/examples/data/example_aas_mandatory_attributes.py b/sdk/basyx/aas/examples/data/example_aas_mandatory_attributes.py index c3911bd99..0a77763df 100644 --- a/sdk/basyx/aas/examples/data/example_aas_mandatory_attributes.py +++ b/sdk/basyx/aas/examples/data/example_aas_mandatory_attributes.py @@ -14,6 +14,7 @@ :meth:`~basyx.aas.examples.data.example_aas_mandatory_attributes.create_full_example`. If you want to get single example objects or want to get more information use the other functions. """ + import logging from ... import model @@ -30,7 +31,9 @@ def create_full_example() -> model.DictIdentifiableStore: :return: :class:`~basyx.aas.model.provider.DictIdentifiableStore` """ - identifiable_store: model.DictIdentifiableStore[model.Identifiable] = model.DictIdentifiableStore() + identifiable_store: model.DictIdentifiableStore[model.Identifiable] = ( + model.DictIdentifiableStore() + ) identifiable_store.add(create_example_submodel()) identifiable_store.add(create_example_empty_submodel()) identifiable_store.add(create_example_concept_description()) @@ -47,104 +50,144 @@ def create_example_submodel() -> model.Submodel: :return: example submodel """ submodel_element_property = model.Property( - id_short='ExampleProperty', + id_short="ExampleProperty", category="PARAMETER", - value_type=model.datatypes.String) + value_type=model.datatypes.String, + ) submodel_element_multi_language_property = model.MultiLanguageProperty( - category="PARAMETER", - id_short='ExampleMultiLanguageProperty') + category="PARAMETER", id_short="ExampleMultiLanguageProperty" + ) submodel_element_range = model.Range( - id_short='ExampleRange', - category="PARAMETER", - value_type=model.datatypes.Int) + id_short="ExampleRange", category="PARAMETER", value_type=model.datatypes.Int + ) submodel_element_blob = model.Blob( - id_short='ExampleBlob', - content_type='application/pdf') + id_short="ExampleBlob", content_type="application/pdf" + ) submodel_element_file = model.File( - id_short='ExampleFile', - content_type='application/pdf') + id_short="ExampleFile", content_type="application/pdf" + ) submodel_element_reference_element = model.ReferenceElement( - category="PARAMETER", - id_short='ExampleReferenceElement') + category="PARAMETER", id_short="ExampleReferenceElement" + ) submodel_element_relationship_element = model.RelationshipElement( - id_short='ExampleRelationshipElement', - first=model.ModelReference((model.Key(type_=model.KeyTypes.SUBMODEL, - value='http://example.org/Test_Submodel'), - model.Key(type_=model.KeyTypes.PROPERTY, - value='ExampleProperty'),), - model.Property), - second=model.ModelReference((model.Key(type_=model.KeyTypes.SUBMODEL, - value='http://example.org/Test_Submodel'), - model.Key(type_=model.KeyTypes.PROPERTY, - value='ExampleProperty'),), - model.Property)) - - submodel_element_annotated_relationship_element = model.AnnotatedRelationshipElement( - id_short='ExampleAnnotatedRelationshipElement', - first=model.ModelReference((model.Key(type_=model.KeyTypes.SUBMODEL, - value='http://example.org/Test_Submodel'), - model.Key(type_=model.KeyTypes.PROPERTY, - value='ExampleProperty'),), - model.Property), - second=model.ModelReference((model.Key(type_=model.KeyTypes.SUBMODEL, - value='http://example.org/Test_Submodel'), - model.Key(type_=model.KeyTypes.PROPERTY, - value='ExampleProperty'),), - model.Property)) - - submodel_element_operation = model.Operation( - id_short='ExampleOperation') - - submodel_element_capability = model.Capability( - id_short='ExampleCapability') + id_short="ExampleRelationshipElement", + first=model.ModelReference( + ( + model.Key( + type_=model.KeyTypes.SUBMODEL, + value="http://example.org/Test_Submodel", + ), + model.Key(type_=model.KeyTypes.PROPERTY, value="ExampleProperty"), + ), + model.Property, + ), + second=model.ModelReference( + ( + model.Key( + type_=model.KeyTypes.SUBMODEL, + value="http://example.org/Test_Submodel", + ), + model.Key(type_=model.KeyTypes.PROPERTY, value="ExampleProperty"), + ), + model.Property, + ), + ) + + submodel_element_annotated_relationship_element = ( + model.AnnotatedRelationshipElement( + id_short="ExampleAnnotatedRelationshipElement", + first=model.ModelReference( + ( + model.Key( + type_=model.KeyTypes.SUBMODEL, + value="http://example.org/Test_Submodel", + ), + model.Key(type_=model.KeyTypes.PROPERTY, value="ExampleProperty"), + ), + model.Property, + ), + second=model.ModelReference( + ( + model.Key( + type_=model.KeyTypes.SUBMODEL, + value="http://example.org/Test_Submodel", + ), + model.Key(type_=model.KeyTypes.PROPERTY, value="ExampleProperty"), + ), + model.Property, + ), + ) + ) + + submodel_element_operation = model.Operation(id_short="ExampleOperation") + + submodel_element_capability = model.Capability(id_short="ExampleCapability") submodel_element_basic_event_element = model.BasicEventElement( - id_short='ExampleBasicEventElement', - observed=model.ModelReference((model.Key(type_=model.KeyTypes.SUBMODEL, - value='http://example.org/Test_Submodel'), - model.Key(type_=model.KeyTypes.PROPERTY, value='ExampleProperty'),), - model.Property), + id_short="ExampleBasicEventElement", + observed=model.ModelReference( + ( + model.Key( + type_=model.KeyTypes.SUBMODEL, + value="http://example.org/Test_Submodel", + ), + model.Key(type_=model.KeyTypes.PROPERTY, value="ExampleProperty"), + ), + model.Property, + ), direction=model.Direction.INPUT, - state=model.StateOfEvent.OFF) + state=model.StateOfEvent.OFF, + ) submodel_element_submodel_element_collection = model.SubmodelElementCollection( id_short=None, - value=(submodel_element_blob, - submodel_element_file, - submodel_element_multi_language_property, - submodel_element_property, - submodel_element_range, - submodel_element_reference_element)) + value=( + submodel_element_blob, + submodel_element_file, + submodel_element_multi_language_property, + submodel_element_property, + submodel_element_range, + submodel_element_reference_element, + ), + ) submodel_element_submodel_element_collection_2 = model.SubmodelElementCollection( - id_short=None, - value=()) + id_short=None, value=() + ) submodel_element_submodel_element_list = model.SubmodelElementList( - id_short='ExampleSubmodelList', + id_short="ExampleSubmodelList", type_value_list_element=model.SubmodelElementCollection, - value=(submodel_element_submodel_element_collection, submodel_element_submodel_element_collection_2)) + value=( + submodel_element_submodel_element_collection, + submodel_element_submodel_element_collection_2, + ), + ) submodel_element_submodel_element_list_2 = model.SubmodelElementList( - id_short='ExampleSubmodelList2', + id_short="ExampleSubmodelList2", type_value_list_element=model.Capability, - value=()) + value=(), + ) submodel = model.Submodel( - id_='https://example.org/Test_Submodel_Mandatory', - submodel_element=(submodel_element_relationship_element, - submodel_element_annotated_relationship_element, - submodel_element_operation, - submodel_element_capability, - submodel_element_basic_event_element, - submodel_element_submodel_element_list, - submodel_element_submodel_element_list_2)) + id_="https://example.org/Test_Submodel_Mandatory", + submodel_element=( + submodel_element_relationship_element, + submodel_element_annotated_relationship_element, + submodel_element_operation, + submodel_element_capability, + submodel_element_basic_event_element, + submodel_element_submodel_element_list, + submodel_element_submodel_element_list_2, + ), + ) return submodel @@ -154,8 +197,7 @@ def create_example_empty_submodel() -> model.Submodel: :return: example submodel """ - return model.Submodel( - id_='https://example.org/Test_Submodel2_Mandatory') + return model.Submodel(id_="https://example.org/Test_Submodel2_Mandatory") def create_example_concept_description() -> model.ConceptDescription: @@ -165,12 +207,12 @@ def create_example_concept_description() -> model.ConceptDescription: :return: example concept description """ concept_description = model.ConceptDescription( - id_='https://example.org/Test_ConceptDescription_Mandatory') + id_="https://example.org/Test_ConceptDescription_Mandatory" + ) return concept_description -def create_example_asset_administration_shell() -> \ - model.AssetAdministrationShell: +def create_example_asset_administration_shell() -> model.AssetAdministrationShell: """ Creates an example :class:`~basyx.aas.model.aas.AssetAdministrationShell` containing references to the example, the example :class:`~Submodels `. @@ -179,17 +221,33 @@ def create_example_asset_administration_shell() -> \ """ asset_information = model.AssetInformation( asset_kind=model.AssetKind.INSTANCE, - global_asset_id='http://example.org/Test_Asset_Mandatory/') + global_asset_id="http://example.org/Test_Asset_Mandatory/", + ) asset_administration_shell = model.AssetAdministrationShell( asset_information=asset_information, - id_='https://example.org/Test_AssetAdministrationShell_Mandatory', - submodel={model.ModelReference((model.Key(type_=model.KeyTypes.SUBMODEL, - value='https://example.org/Test_Submodel_Mandatory'),), - model.Submodel), - model.ModelReference((model.Key(type_=model.KeyTypes.SUBMODEL, - value='https://example.org/Test_Submodel2_Mandatory'),), - model.Submodel)},) + id_="https://example.org/Test_AssetAdministrationShell_Mandatory", + submodel={ + model.ModelReference( + ( + model.Key( + type_=model.KeyTypes.SUBMODEL, + value="https://example.org/Test_Submodel_Mandatory", + ), + ), + model.Submodel, + ), + model.ModelReference( + ( + model.Key( + type_=model.KeyTypes.SUBMODEL, + value="https://example.org/Test_Submodel2_Mandatory", + ), + ), + model.Submodel, + ), + }, + ) return asset_administration_shell @@ -202,26 +260,35 @@ def create_example_empty_asset_administration_shell() -> model.AssetAdministrati """ asset_administration_shell = model.AssetAdministrationShell( asset_information=model.AssetInformation( - global_asset_id='http://example.org/TestAsset2_Mandatory/'), - id_='https://example.org/Test_AssetAdministrationShell2_Mandatory') + global_asset_id="http://example.org/TestAsset2_Mandatory/" + ), + id_="https://example.org/Test_AssetAdministrationShell2_Mandatory", + ) return asset_administration_shell ############################################################################## # check functions for checking if a given object is the same as the example # ############################################################################## -def check_example_concept_description(checker: AASDataChecker, concept_description: model.ConceptDescription) -> None: +def check_example_concept_description( + checker: AASDataChecker, concept_description: model.ConceptDescription +) -> None: expected_concept_description = create_example_concept_description() - checker.check_concept_description_equal(concept_description, expected_concept_description) + checker.check_concept_description_equal( + concept_description, expected_concept_description + ) -def check_example_asset_administration_shell(checker: AASDataChecker, shell: model.AssetAdministrationShell) -> None: +def check_example_asset_administration_shell( + checker: AASDataChecker, shell: model.AssetAdministrationShell +) -> None: expected_shell = create_example_asset_administration_shell() checker.check_asset_administration_shell_equal(shell, expected_shell) -def check_example_empty_asset_administration_shell(checker: AASDataChecker, shell: model.AssetAdministrationShell) \ - -> None: +def check_example_empty_asset_administration_shell( + checker: AASDataChecker, shell: model.AssetAdministrationShell +) -> None: expected_shell = create_example_empty_asset_administration_shell() checker.check_asset_administration_shell_equal(shell, expected_shell) @@ -231,11 +298,15 @@ def check_example_submodel(checker: AASDataChecker, submodel: model.Submodel) -> checker.check_submodel_equal(submodel, expected_submodel) -def check_example_empty_submodel(checker: AASDataChecker, submodel: model.Submodel) -> None: +def check_example_empty_submodel( + checker: AASDataChecker, submodel: model.Submodel +) -> None: expected_submodel = create_example_empty_submodel() checker.check_submodel_equal(submodel, expected_submodel) -def check_full_example(checker: AASDataChecker, identifiable_store: model.DictIdentifiableStore) -> None: +def check_full_example( + checker: AASDataChecker, identifiable_store: model.DictIdentifiableStore +) -> None: expected_data = create_full_example() checker.check_identifiable_store(identifiable_store, expected_data) diff --git a/sdk/basyx/aas/examples/data/example_aas_missing_attributes.py b/sdk/basyx/aas/examples/data/example_aas_missing_attributes.py index 25f0a1714..251bf2937 100644 --- a/sdk/basyx/aas/examples/data/example_aas_missing_attributes.py +++ b/sdk/basyx/aas/examples/data/example_aas_missing_attributes.py @@ -8,6 +8,7 @@ Module for the creation of an :class:`IdentifiableStore ` with missing object attribute combinations for testing the serialization. """ + import datetime import logging @@ -25,7 +26,9 @@ def create_full_example() -> model.DictIdentifiableStore: :return: :class:`basyx.aas.model.provider.DictIdentifiableStore` """ - identifiable_store: model.DictIdentifiableStore[model.Identifiable] = model.DictIdentifiableStore() + identifiable_store: model.DictIdentifiableStore[model.Identifiable] = ( + model.DictIdentifiableStore() + ) identifiable_store.add(create_example_submodel()) identifiable_store.add(create_example_concept_description()) identifiable_store.add(create_example_asset_administration_shell()) @@ -40,301 +43,540 @@ def create_example_submodel() -> model.Submodel: :return: example submodel """ qualifier = model.Qualifier( - type_='http://example.org/Qualifier/ExampleQualifier', - value_type=model.datatypes.String) + type_="http://example.org/Qualifier/ExampleQualifier", + value_type=model.datatypes.String, + ) submodel_element_property = model.Property( - id_short='ExampleProperty', + id_short="ExampleProperty", value_type=model.datatypes.String, - value='exampleValue', + value="exampleValue", value_id=None, # TODO - category='CONSTANT', - description=model.MultiLanguageTextType({'en-US': 'Example Property object', - 'de': 'Beispiel Property Element'}), + category="CONSTANT", + description=model.MultiLanguageTextType( + {"en-US": "Example Property object", "de": "Beispiel Property Element"} + ), parent=None, - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/Properties/ExampleProperty'),)), - qualifier={qualifier}) + semantic_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/Properties/ExampleProperty", + ), + ) + ), + qualifier={qualifier}, + ) submodel_element_multi_language_property = model.MultiLanguageProperty( - id_short='ExampleMultiLanguageProperty', - value=model.MultiLanguageTextType({'en-US': 'Example value of a MultiLanguageProperty element', - 'de': 'Beispielwert für ein MultiLanguageProperty-Element'}), + id_short="ExampleMultiLanguageProperty", + value=model.MultiLanguageTextType( + { + "en-US": "Example value of a MultiLanguageProperty element", + "de": "Beispielwert für ein MultiLanguageProperty-Element", + } + ), value_id=None, # TODO - category='CONSTANT', - description=model.MultiLanguageTextType({'en-US': 'Example MultiLanguageProperty object', - 'de': 'Beispiel MultiLanguageProperty Element'}), + category="CONSTANT", + description=model.MultiLanguageTextType( + { + "en-US": "Example MultiLanguageProperty object", + "de": "Beispiel MultiLanguageProperty Element", + } + ), parent=None, - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/MultiLanguageProperties/' - 'ExampleMultiLanguageProperty'),)), - qualifier=()) + semantic_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/MultiLanguageProperties/" + "ExampleMultiLanguageProperty", + ), + ) + ), + qualifier=(), + ) submodel_element_range = model.Range( - id_short='ExampleRange', + id_short="ExampleRange", value_type=model.datatypes.Int, min=0, max=100, - category='PARAMETER', - description=model.MultiLanguageTextType({'en-US': 'Example Range object', - 'de': 'Beispiel Range Element'}), + category="PARAMETER", + description=model.MultiLanguageTextType( + {"en-US": "Example Range object", "de": "Beispiel Range Element"} + ), parent=None, - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/Ranges/ExampleRange'),)), - qualifier=()) + semantic_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/Ranges/ExampleRange", + ), + ) + ), + qualifier=(), + ) submodel_element_blob = model.Blob( - id_short='ExampleBlob', - content_type='application/pdf', - value=bytearray(b'\x01\x02\x03\x04\x05'), - category='PARAMETER', - description=model.MultiLanguageTextType({'en-US': 'Example Blob object', - 'de': 'Beispiel Blob Element'}), + id_short="ExampleBlob", + content_type="application/pdf", + value=bytearray(b"\x01\x02\x03\x04\x05"), + category="PARAMETER", + description=model.MultiLanguageTextType( + {"en-US": "Example Blob object", "de": "Beispiel Blob Element"} + ), parent=None, - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/Blobs/ExampleBlob'),)), - qualifier=()) + semantic_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/Blobs/ExampleBlob", + ), + ) + ), + qualifier=(), + ) submodel_element_file = model.File( - id_short='ExampleFile', - content_type='application/pdf', - value='/TestFile.pdf', - category='PARAMETER', - description=model.MultiLanguageTextType({'en-US': 'Example File object', - 'de': 'Beispiel File Element'}), + id_short="ExampleFile", + content_type="application/pdf", + value="/TestFile.pdf", + category="PARAMETER", + description=model.MultiLanguageTextType( + {"en-US": "Example File object", "de": "Beispiel File Element"} + ), parent=None, - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/Files/ExampleFile'),)), - qualifier=()) + semantic_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/Files/ExampleFile", + ), + ) + ), + qualifier=(), + ) submodel_element_reference_element = model.ReferenceElement( - id_short='ExampleReferenceElement', - value=model.ModelReference((model.Key(type_=model.KeyTypes.SUBMODEL, - value='http://example.org/Test_Submodel'), - model.Key(type_=model.KeyTypes.PROPERTY, - value='ExampleProperty'),), model.Submodel), - category='PARAMETER', - description=model.MultiLanguageTextType({'en-US': 'Example Reference Element object', - 'de': 'Beispiel Reference Element Element'}), + id_short="ExampleReferenceElement", + value=model.ModelReference( + ( + model.Key( + type_=model.KeyTypes.SUBMODEL, + value="http://example.org/Test_Submodel", + ), + model.Key(type_=model.KeyTypes.PROPERTY, value="ExampleProperty"), + ), + model.Submodel, + ), + category="PARAMETER", + description=model.MultiLanguageTextType( + { + "en-US": "Example Reference Element object", + "de": "Beispiel Reference Element Element", + } + ), parent=None, - semantic_id=model.ExternalReference((model.Key( - type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/ReferenceElements/ExampleReferenceElement' - ),)), - qualifier=()) + semantic_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/ReferenceElements/ExampleReferenceElement", + ), + ) + ), + qualifier=(), + ) submodel_element_relationship_element = model.RelationshipElement( - id_short='ExampleRelationshipElement', - first=model.ModelReference((model.Key(type_=model.KeyTypes.SUBMODEL, - value='http://example.org/Test_Submodel'), - model.Key(type_=model.KeyTypes.PROPERTY, - value='ExampleProperty'),), - model.Property), - second=model.ModelReference((model.Key(type_=model.KeyTypes.SUBMODEL, - value='http://example.org/Test_Submodel'), - model.Key(type_=model.KeyTypes.PROPERTY, - value='ExampleProperty'),), - model.Property), - category='PARAMETER', - description=model.MultiLanguageTextType({'en-US': 'Example RelationshipElement object', - 'de': 'Beispiel RelationshipElement Element'}), - parent=None, - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/RelationshipElements/' - 'ExampleRelationshipElement'),)), - qualifier=()) - - submodel_element_annotated_relationship_element = model.AnnotatedRelationshipElement( - id_short='ExampleAnnotatedRelationshipElement', - first=model.ModelReference((model.Key(type_=model.KeyTypes.SUBMODEL, - value='http://example.org/Test_Submodel'), - model.Key(type_=model.KeyTypes.PROPERTY, - value='ExampleProperty'),), - model.Property), - second=model.ModelReference((model.Key(type_=model.KeyTypes.SUBMODEL, - value='http://example.org/Test_Submodel'), - model.Key(type_=model.KeyTypes.PROPERTY, - value='ExampleProperty'),), - model.Property), - annotation={model.Property(id_short="ExampleAnnotatedProperty", - value_type=model.datatypes.String, - value='exampleValue', - category="PARAMETER", - parent=None), - model.Range(id_short="ExampleAnnotatedRange", - value_type=model.datatypes.Integer, - min=1, - max=5, - category="PARAMETER", - parent=None) - }, - category='PARAMETER', - description=model.MultiLanguageTextType({'en-US': 'Example AnnotatedRelationshipElement object', - 'de': 'Beispiel AnnotatedRelationshipElement Element'}), + id_short="ExampleRelationshipElement", + first=model.ModelReference( + ( + model.Key( + type_=model.KeyTypes.SUBMODEL, + value="http://example.org/Test_Submodel", + ), + model.Key(type_=model.KeyTypes.PROPERTY, value="ExampleProperty"), + ), + model.Property, + ), + second=model.ModelReference( + ( + model.Key( + type_=model.KeyTypes.SUBMODEL, + value="http://example.org/Test_Submodel", + ), + model.Key(type_=model.KeyTypes.PROPERTY, value="ExampleProperty"), + ), + model.Property, + ), + category="PARAMETER", + description=model.MultiLanguageTextType( + { + "en-US": "Example RelationshipElement object", + "de": "Beispiel RelationshipElement Element", + } + ), parent=None, - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/RelationshipElements/' - 'ExampleAnnotatedRelationshipElement'),)), - qualifier=()) + semantic_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/RelationshipElements/" + "ExampleRelationshipElement", + ), + ) + ), + qualifier=(), + ) + + submodel_element_annotated_relationship_element = ( + model.AnnotatedRelationshipElement( + id_short="ExampleAnnotatedRelationshipElement", + first=model.ModelReference( + ( + model.Key( + type_=model.KeyTypes.SUBMODEL, + value="http://example.org/Test_Submodel", + ), + model.Key(type_=model.KeyTypes.PROPERTY, value="ExampleProperty"), + ), + model.Property, + ), + second=model.ModelReference( + ( + model.Key( + type_=model.KeyTypes.SUBMODEL, + value="http://example.org/Test_Submodel", + ), + model.Key(type_=model.KeyTypes.PROPERTY, value="ExampleProperty"), + ), + model.Property, + ), + annotation={ + model.Property( + id_short="ExampleAnnotatedProperty", + value_type=model.datatypes.String, + value="exampleValue", + category="PARAMETER", + parent=None, + ), + model.Range( + id_short="ExampleAnnotatedRange", + value_type=model.datatypes.Integer, + min=1, + max=5, + category="PARAMETER", + parent=None, + ), + }, + category="PARAMETER", + description=model.MultiLanguageTextType( + { + "en-US": "Example AnnotatedRelationshipElement object", + "de": "Beispiel AnnotatedRelationshipElement Element", + } + ), + parent=None, + semantic_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/RelationshipElements/" + "ExampleAnnotatedRelationshipElement", + ), + ) + ), + qualifier=(), + ) + ) operation_variable_property = model.Property( - id_short='ExampleProperty', + id_short="ExampleProperty", value_type=model.datatypes.String, - value='exampleValue', - value_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/ValueId/ExampleValueId'),)), - display_name=model.MultiLanguageNameType({'en-US': 'ExampleProperty', - 'de': 'BeispielProperty'}), - category='CONSTANT', - description=model.MultiLanguageTextType({'en-US': 'Example Property object', - 'de': 'Beispiel Property Element'}), + value="exampleValue", + value_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/ValueId/ExampleValueId", + ), + ) + ), + display_name=model.MultiLanguageNameType( + {"en-US": "ExampleProperty", "de": "BeispielProperty"} + ), + category="CONSTANT", + description=model.MultiLanguageTextType( + {"en-US": "Example Property object", "de": "Beispiel Property Element"} + ), parent=None, - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/Properties/ExampleProperty'),)), - qualifier=()) + semantic_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/Properties/ExampleProperty", + ), + ) + ), + qualifier=(), + ) input_variable_property = model.Property( - id_short='ExamplePropertyInput', + id_short="ExamplePropertyInput", value_type=model.datatypes.String, - value='exampleValue', - value_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/ValueId/ExampleValueId'),)), - display_name=model.MultiLanguageNameType({'en-US': 'ExampleProperty', - 'de': 'BeispielProperty'}), - category='CONSTANT', - description=model.MultiLanguageTextType({'en-US': 'Example Property object', - 'de': 'Beispiel Property Element'}), + value="exampleValue", + value_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/ValueId/ExampleValueId", + ), + ) + ), + display_name=model.MultiLanguageNameType( + {"en-US": "ExampleProperty", "de": "BeispielProperty"} + ), + category="CONSTANT", + description=model.MultiLanguageTextType( + {"en-US": "Example Property object", "de": "Beispiel Property Element"} + ), parent=None, - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/Properties/ExamplePropertyInput'),)), - qualifier=()) + semantic_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/Properties/ExamplePropertyInput", + ), + ) + ), + qualifier=(), + ) output_variable_property = model.Property( - id_short='ExamplePropertyOutput', + id_short="ExamplePropertyOutput", value_type=model.datatypes.String, - value='exampleValue', - value_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/ValueId/ExampleValueId'),)), - display_name=model.MultiLanguageNameType({'en-US': 'ExampleProperty', - 'de': 'BeispielProperty'}), - category='CONSTANT', - description=model.MultiLanguageTextType({'en-US': 'Example Property object', - 'de': 'Beispiel Property Element'}), + value="exampleValue", + value_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/ValueId/ExampleValueId", + ), + ) + ), + display_name=model.MultiLanguageNameType( + {"en-US": "ExampleProperty", "de": "BeispielProperty"} + ), + category="CONSTANT", + description=model.MultiLanguageTextType( + {"en-US": "Example Property object", "de": "Beispiel Property Element"} + ), parent=None, - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/Properties/ExamplePropertyOutput'),)), + semantic_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/Properties/ExamplePropertyOutput", + ), + ) + ), qualifier=(), extension=(), supplemental_semantic_id=(), - embedded_data_specifications=()) + embedded_data_specifications=(), + ) in_output_variable_property = model.Property( - id_short='ExamplePropertyInOutput', + id_short="ExamplePropertyInOutput", value_type=model.datatypes.String, - value='exampleValue', - value_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/ValueId/ExampleValueId'),)), - display_name=model.MultiLanguageNameType({'en-US': 'ExampleProperty', - 'de': 'BeispielProperty'}), - category='CONSTANT', - description=model.MultiLanguageTextType({'en-US': 'Example Property object', - 'de': 'Beispiel Property Element'}), + value="exampleValue", + value_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/ValueId/ExampleValueId", + ), + ) + ), + display_name=model.MultiLanguageNameType( + {"en-US": "ExampleProperty", "de": "BeispielProperty"} + ), + category="CONSTANT", + description=model.MultiLanguageTextType( + {"en-US": "Example Property object", "de": "Beispiel Property Element"} + ), parent=None, - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/Properties/' - 'ExamplePropertyInOutput'),)), + semantic_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/Properties/ExamplePropertyInOutput", + ), + ) + ), qualifier=(), extension=(), supplemental_semantic_id=(), - embedded_data_specifications=()) + embedded_data_specifications=(), + ) submodel_element_operation = model.Operation( - id_short='ExampleOperation', + id_short="ExampleOperation", input_variable=[input_variable_property], output_variable=[output_variable_property], in_output_variable=[in_output_variable_property], - category='PARAMETER', - description=model.MultiLanguageTextType({'en-US': 'Example Operation object', - 'de': 'Beispiel Operation Element'}), + category="PARAMETER", + description=model.MultiLanguageTextType( + {"en-US": "Example Operation object", "de": "Beispiel Operation Element"} + ), parent=None, - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/Operations/' - 'ExampleOperation'),)), - qualifier=()) + semantic_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/Operations/ExampleOperation", + ), + ) + ), + qualifier=(), + ) submodel_element_capability = model.Capability( - id_short='ExampleCapability', - category='PARAMETER', - description=model.MultiLanguageTextType({'en-US': 'Example Capability object', - 'de': 'Beispiel Capability Element'}), + id_short="ExampleCapability", + category="PARAMETER", + description=model.MultiLanguageTextType( + {"en-US": "Example Capability object", "de": "Beispiel Capability Element"} + ), parent=None, - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/Capabilities/' - 'ExampleCapability'),)), - qualifier=()) + semantic_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/Capabilities/ExampleCapability", + ), + ) + ), + qualifier=(), + ) submodel_element_basic_event_element = model.BasicEventElement( - id_short='ExampleBasicEventElement', - observed=model.ModelReference((model.Key(type_=model.KeyTypes.SUBMODEL, - value='http://example.org/Test_Submodel'), - model.Key(type_=model.KeyTypes.PROPERTY, - value='ExampleProperty'),), - model.Property), + id_short="ExampleBasicEventElement", + observed=model.ModelReference( + ( + model.Key( + type_=model.KeyTypes.SUBMODEL, + value="http://example.org/Test_Submodel", + ), + model.Key(type_=model.KeyTypes.PROPERTY, value="ExampleProperty"), + ), + model.Property, + ), direction=model.Direction.OUTPUT, state=model.StateOfEvent.ON, - message_topic='ExampleTopic', - message_broker=model.ModelReference((model.Key(model.KeyTypes.SUBMODEL, - "http://example.org/ExampleMessageBroker"),), - model.Submodel), - last_update=model.datatypes.DateTime(2022, 11, 12, 23, 50, 23, 123456, datetime.timezone.utc), + message_topic="ExampleTopic", + message_broker=model.ModelReference( + ( + model.Key( + model.KeyTypes.SUBMODEL, "http://example.org/ExampleMessageBroker" + ), + ), + model.Submodel, + ), + last_update=model.datatypes.DateTime( + 2022, 11, 12, 23, 50, 23, 123456, datetime.timezone.utc + ), min_interval=model.datatypes.Duration(microseconds=1), - max_interval=model.datatypes.Duration(years=1, months=2, days=3, hours=4, minutes=5, seconds=6, - microseconds=123456), - category='PARAMETER', - description=model.MultiLanguageTextType({'en-US': 'Example BasicEventElement object', - 'de': 'Beispiel BasicEventElement Element'}), + max_interval=model.datatypes.Duration( + years=1, + months=2, + days=3, + hours=4, + minutes=5, + seconds=6, + microseconds=123456, + ), + category="PARAMETER", + description=model.MultiLanguageTextType( + { + "en-US": "Example BasicEventElement object", + "de": "Beispiel BasicEventElement Element", + } + ), parent=None, - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/Events/ExampleBasicEventElement'),)), - qualifier=()) + semantic_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/Events/ExampleBasicEventElement", + ), + ) + ), + qualifier=(), + ) submodel_element_submodel_element_collection = model.SubmodelElementCollection( - id_short='ExampleSubmodelCollection', - value=(submodel_element_blob, - submodel_element_file, - submodel_element_multi_language_property, - submodel_element_property, - submodel_element_range, - submodel_element_reference_element), - category='PARAMETER', - description=model.MultiLanguageTextType({'en-US': 'Example SubmodelElementCollection object', - 'de': 'Beispiel SubmodelElementCollection Element'}), + id_short="ExampleSubmodelCollection", + value=( + submodel_element_blob, + submodel_element_file, + submodel_element_multi_language_property, + submodel_element_property, + submodel_element_range, + submodel_element_reference_element, + ), + category="PARAMETER", + description=model.MultiLanguageTextType( + { + "en-US": "Example SubmodelElementCollection object", + "de": "Beispiel SubmodelElementCollection Element", + } + ), parent=None, - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/SubmodelElementCollections/' - 'ExampleSubmodelElementCollection'),)), - qualifier=()) + semantic_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/SubmodelElementCollections/" + "ExampleSubmodelElementCollection", + ), + ) + ), + qualifier=(), + ) submodel = model.Submodel( - id_='https://example.org/Test_Submodel_Missing', - submodel_element=(submodel_element_relationship_element, - submodel_element_annotated_relationship_element, - submodel_element_operation, - submodel_element_capability, - submodel_element_basic_event_element, - submodel_element_submodel_element_collection), - id_short='TestSubmodel', + id_="https://example.org/Test_Submodel_Missing", + submodel_element=( + submodel_element_relationship_element, + submodel_element_annotated_relationship_element, + submodel_element_operation, + submodel_element_capability, + submodel_element_basic_event_element, + submodel_element_submodel_element_collection, + ), + id_short="TestSubmodel", category=None, - description=model.MultiLanguageTextType({'en-US': 'An example submodel for the test application', - 'de': 'Ein Beispiel-Teilmodell für eine Test-Anwendung'}), + description=model.MultiLanguageTextType( + { + "en-US": "An example submodel for the test application", + "de": "Ein Beispiel-Teilmodell für eine Test-Anwendung", + } + ), parent=None, - administration=model.AdministrativeInformation(version='9', - revision='0'), - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/SubmodelTemplates/' - 'ExampleSubmodel'),)), + administration=model.AdministrativeInformation(version="9", revision="0"), + semantic_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/SubmodelTemplates/ExampleSubmodel", + ), + ) + ), qualifier=(), - kind=model.ModellingKind.INSTANCE) + kind=model.ModellingKind.INSTANCE, + ) return submodel @@ -345,15 +587,19 @@ def create_example_concept_description() -> model.ConceptDescription: :return: example concept description """ concept_description = model.ConceptDescription( - id_='https://example.org/Test_ConceptDescription_Missing', + id_="https://example.org/Test_ConceptDescription_Missing", is_case_of=None, - id_short='TestConceptDescription', + id_short="TestConceptDescription", category=None, - description=model.MultiLanguageTextType({'en-US': 'An example concept description for the test application', - 'de': 'Ein Beispiel-ConceptDescription für eine Test-Anwendung'}), + description=model.MultiLanguageTextType( + { + "en-US": "An example concept description for the test application", + "de": "Ein Beispiel-ConceptDescription für eine Test-Anwendung", + } + ), parent=None, - administration=model.AdministrativeInformation(version='9', - revision='0')) + administration=model.AdministrativeInformation(version="9", revision="0"), + ) return concept_description @@ -366,46 +612,74 @@ def create_example_asset_administration_shell() -> model.AssetAdministrationShel """ resource = model.Resource( - content_type='application/pdf', - path='file:///TestFile.pdf') + content_type="application/pdf", path="file:///TestFile.pdf" + ) asset_information = model.AssetInformation( asset_kind=model.AssetKind.INSTANCE, - global_asset_id='http://example.org/Test_Asset_Missing/', - specific_asset_id={model.SpecificAssetId(name="TestKey", value="TestValue", - external_subject_id=model.ExternalReference( - (model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/' - 'SpecificAssetId/'),)))}, - default_thumbnail=resource) + global_asset_id="http://example.org/Test_Asset_Missing/", + specific_asset_id={ + model.SpecificAssetId( + name="TestKey", + value="TestValue", + external_subject_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/SpecificAssetId/", + ), + ) + ), + ) + }, + default_thumbnail=resource, + ) asset_administration_shell = model.AssetAdministrationShell( asset_information=asset_information, - id_='https://example.org/Test_AssetAdministrationShell_Missing', - id_short='TestAssetAdministrationShell', + id_="https://example.org/Test_AssetAdministrationShell_Missing", + id_short="TestAssetAdministrationShell", category=None, - description=model.MultiLanguageTextType({'en-US': 'An Example Asset Administration Shell for the test ' - 'application', - 'de': 'Ein Beispiel-Verwaltungsschale für eine Test-Anwendung'}), + description=model.MultiLanguageTextType( + { + "en-US": "An Example Asset Administration Shell for the test " + "application", + "de": "Ein Beispiel-Verwaltungsschale für eine Test-Anwendung", + } + ), parent=None, - administration=model.AdministrativeInformation(version='9', - revision='0'), - submodel={model.ModelReference((model.Key(type_=model.KeyTypes.SUBMODEL, - value='https://example.org/Test_Submodel_Missing'),), - model.Submodel)}, - derived_from=None) + administration=model.AdministrativeInformation(version="9", revision="0"), + submodel={ + model.ModelReference( + ( + model.Key( + type_=model.KeyTypes.SUBMODEL, + value="https://example.org/Test_Submodel_Missing", + ), + ), + model.Submodel, + ) + }, + derived_from=None, + ) return asset_administration_shell ############################################################################## # check functions for checking if a given object is the same as the example # ############################################################################## -def check_example_concept_description(checker: AASDataChecker, concept_description: model.ConceptDescription) -> None: +def check_example_concept_description( + checker: AASDataChecker, concept_description: model.ConceptDescription +) -> None: expected_concept_description = create_example_concept_description() - checker.check_concept_description_equal(concept_description, expected_concept_description) + checker.check_concept_description_equal( + concept_description, expected_concept_description + ) -def check_example_asset_administration_shell(checker: AASDataChecker, shell: model.AssetAdministrationShell) -> None: +def check_example_asset_administration_shell( + checker: AASDataChecker, shell: model.AssetAdministrationShell +) -> None: expected_shell = create_example_asset_administration_shell() checker.check_asset_administration_shell_equal(shell, expected_shell) @@ -415,6 +689,8 @@ def check_example_submodel(checker: AASDataChecker, submodel: model.Submodel) -> checker.check_submodel_equal(submodel, expected_submodel) -def check_full_example(checker: AASDataChecker, identifiable_store: model.DictIdentifiableStore) -> None: +def check_full_example( + checker: AASDataChecker, identifiable_store: model.DictIdentifiableStore +) -> None: expected_data = create_full_example() checker.check_identifiable_store(identifiable_store, expected_data) diff --git a/sdk/basyx/aas/examples/data/example_submodel_template.py b/sdk/basyx/aas/examples/data/example_submodel_template.py index 10d19e0bb..4dc2a7189 100644 --- a/sdk/basyx/aas/examples/data/example_submodel_template.py +++ b/sdk/basyx/aas/examples/data/example_submodel_template.py @@ -9,6 +9,7 @@ :class:`SubmodelElements ` where the kind is always ``TEMPLATE``. """ + import datetime import logging @@ -27,311 +28,557 @@ def create_example_submodel_template() -> model.Submodel: :return: example submodel """ submodel_element_property = model.Property( - id_short='ExampleProperty', + id_short="ExampleProperty", value_type=model.datatypes.String, value=None, value_id=None, # TODO - category='CONSTANT', - description=model.MultiLanguageTextType({'en-US': 'Example Property object', - 'de': 'Beispiel Property Element'}), + category="CONSTANT", + description=model.MultiLanguageTextType( + {"en-US": "Example Property object", "de": "Beispiel Property Element"} + ), parent=None, - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/Properties/ExampleProperty'),)), - qualifier=()) + semantic_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/Properties/ExampleProperty", + ), + ) + ), + qualifier=(), + ) submodel_element_multi_language_property = model.MultiLanguageProperty( - id_short='ExampleMultiLanguageProperty', + id_short="ExampleMultiLanguageProperty", value=None, value_id=None, # TODO - category='CONSTANT', - description=model.MultiLanguageTextType({'en-US': 'Example MultiLanguageProperty object', - 'de': 'Beispiel MultiLanguageProperty Element'}), + category="CONSTANT", + description=model.MultiLanguageTextType( + { + "en-US": "Example MultiLanguageProperty object", + "de": "Beispiel MultiLanguageProperty Element", + } + ), parent=None, - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/MultiLanguageProperties/' - 'ExampleMultiLanguageProperty'),)), - qualifier=(),) + semantic_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/MultiLanguageProperties/" + "ExampleMultiLanguageProperty", + ), + ) + ), + qualifier=(), + ) submodel_element_range = model.Range( - id_short='ExampleRange', + id_short="ExampleRange", value_type=model.datatypes.Int, min=None, max=100, - category='PARAMETER', - description=model.MultiLanguageTextType({'en-US': 'Example Range object', - 'de': 'Beispiel Range Element'}), + category="PARAMETER", + description=model.MultiLanguageTextType( + {"en-US": "Example Range object", "de": "Beispiel Range Element"} + ), parent=None, - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/Ranges/ExampleRange'),)), - qualifier=(),) + semantic_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/Ranges/ExampleRange", + ), + ) + ), + qualifier=(), + ) submodel_element_range_2 = model.Range( - id_short='ExampleRange2', + id_short="ExampleRange2", value_type=model.datatypes.Int, min=0, max=None, - category='PARAMETER', - description=model.MultiLanguageTextType({'en-US': 'Example Range object', - 'de': 'Beispiel Range Element'}), + category="PARAMETER", + description=model.MultiLanguageTextType( + {"en-US": "Example Range object", "de": "Beispiel Range Element"} + ), parent=None, - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/Ranges/ExampleRange'),)), - qualifier=()) + semantic_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/Ranges/ExampleRange", + ), + ) + ), + qualifier=(), + ) submodel_element_blob = model.Blob( - id_short='ExampleBlob', - content_type='application/pdf', + id_short="ExampleBlob", + content_type="application/pdf", value=None, - category='PARAMETER', - description=model.MultiLanguageTextType({'en-US': 'Example Blob object', - 'de': 'Beispiel Blob Element'}), + category="PARAMETER", + description=model.MultiLanguageTextType( + {"en-US": "Example Blob object", "de": "Beispiel Blob Element"} + ), parent=None, - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/Blobs/ExampleBlob'),)), - qualifier=()) + semantic_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/Blobs/ExampleBlob", + ), + ) + ), + qualifier=(), + ) submodel_element_file = model.File( - id_short='ExampleFile', - content_type='application/pdf', + id_short="ExampleFile", + content_type="application/pdf", value=None, - category='PARAMETER', - description=model.MultiLanguageTextType({'en-US': 'Example File object', - 'de': 'Beispiel File Element'}), + category="PARAMETER", + description=model.MultiLanguageTextType( + {"en-US": "Example File object", "de": "Beispiel File Element"} + ), parent=None, - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/Files/ExampleFile'),)), - qualifier=()) + semantic_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/Files/ExampleFile", + ), + ) + ), + qualifier=(), + ) submodel_element_reference_element = model.ReferenceElement( - id_short='ExampleReferenceElement', + id_short="ExampleReferenceElement", value=None, - category='PARAMETER', - description=model.MultiLanguageTextType({'en-US': 'Example Reference Element object', - 'de': 'Beispiel Reference Element Element'}), + category="PARAMETER", + description=model.MultiLanguageTextType( + { + "en-US": "Example Reference Element object", + "de": "Beispiel Reference Element Element", + } + ), parent=None, - semantic_id=model.ExternalReference((model.Key( - type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/ReferenceElements/ExampleReferenceElement' - ),)), - qualifier=()) + semantic_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/ReferenceElements/ExampleReferenceElement", + ), + ) + ), + qualifier=(), + ) submodel_element_relationship_element = model.RelationshipElement( - id_short='ExampleRelationshipElement', - first=model.ModelReference((model.Key(type_=model.KeyTypes.SUBMODEL, value='http://example.org/Test_Submodel'), - model.Key(type_=model.KeyTypes.PROPERTY, - value='ExampleProperty'),), - model.Property), - second=model.ModelReference((model.Key(type_=model.KeyTypes.SUBMODEL, value='http://example.org/Test_Submodel'), - model.Key(type_=model.KeyTypes.PROPERTY, - value='ExampleProperty'),), - model.Property), - category='PARAMETER', - description=model.MultiLanguageTextType({'en-US': 'Example RelationshipElement object', - 'de': 'Beispiel RelationshipElement Element'}), + id_short="ExampleRelationshipElement", + first=model.ModelReference( + ( + model.Key( + type_=model.KeyTypes.SUBMODEL, + value="http://example.org/Test_Submodel", + ), + model.Key(type_=model.KeyTypes.PROPERTY, value="ExampleProperty"), + ), + model.Property, + ), + second=model.ModelReference( + ( + model.Key( + type_=model.KeyTypes.SUBMODEL, + value="http://example.org/Test_Submodel", + ), + model.Key(type_=model.KeyTypes.PROPERTY, value="ExampleProperty"), + ), + model.Property, + ), + category="PARAMETER", + description=model.MultiLanguageTextType( + { + "en-US": "Example RelationshipElement object", + "de": "Beispiel RelationshipElement Element", + } + ), parent=None, - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/RelationshipElements/' - 'ExampleRelationshipElement'),)), - qualifier=()) + semantic_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/RelationshipElements/" + "ExampleRelationshipElement", + ), + ) + ), + qualifier=(), + ) - submodel_element_annotated_relationship_element = model.AnnotatedRelationshipElement( - id_short='ExampleAnnotatedRelationshipElement', - first=model.ModelReference((model.Key(type_=model.KeyTypes.SUBMODEL, value='http://example.org/Test_Submodel'), - model.Key(type_=model.KeyTypes.PROPERTY, - value='ExampleProperty'),), - model.Property), - second=model.ModelReference((model.Key(type_=model.KeyTypes.SUBMODEL, value='http://example.org/Test_Submodel'), - model.Key(type_=model.KeyTypes.PROPERTY, - value='ExampleProperty'),), - model.Property), - annotation=(), - category='PARAMETER', - description=model.MultiLanguageTextType({'en-US': 'Example AnnotatedRelationshipElement object', - 'de': 'Beispiel AnnotatedRelationshipElement Element'}), - parent=None, - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/RelationshipElements/' - 'ExampleAnnotatedRelationshipElement'),)), - qualifier=()) + submodel_element_annotated_relationship_element = ( + model.AnnotatedRelationshipElement( + id_short="ExampleAnnotatedRelationshipElement", + first=model.ModelReference( + ( + model.Key( + type_=model.KeyTypes.SUBMODEL, + value="http://example.org/Test_Submodel", + ), + model.Key(type_=model.KeyTypes.PROPERTY, value="ExampleProperty"), + ), + model.Property, + ), + second=model.ModelReference( + ( + model.Key( + type_=model.KeyTypes.SUBMODEL, + value="http://example.org/Test_Submodel", + ), + model.Key(type_=model.KeyTypes.PROPERTY, value="ExampleProperty"), + ), + model.Property, + ), + annotation=(), + category="PARAMETER", + description=model.MultiLanguageTextType( + { + "en-US": "Example AnnotatedRelationshipElement object", + "de": "Beispiel AnnotatedRelationshipElement Element", + } + ), + parent=None, + semantic_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/RelationshipElements/" + "ExampleAnnotatedRelationshipElement", + ), + ) + ), + qualifier=(), + ) + ) input_variable_property = model.Property( - id_short='ExamplePropertyInput', + id_short="ExamplePropertyInput", value_type=model.datatypes.String, value=None, value_id=None, - category='CONSTANT', - description=model.MultiLanguageTextType({'en-US': 'Example Property object', - 'de': 'Beispiel Property Element'}), + category="CONSTANT", + description=model.MultiLanguageTextType( + {"en-US": "Example Property object", "de": "Beispiel Property Element"} + ), parent=None, - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/Properties/ExamplePropertyInput'),)), - qualifier=()) + semantic_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/Properties/ExamplePropertyInput", + ), + ) + ), + qualifier=(), + ) output_variable_property = model.Property( - id_short='ExamplePropertyOutput', + id_short="ExamplePropertyOutput", value_type=model.datatypes.String, value=None, value_id=None, - category='CONSTANT', - description=model.MultiLanguageTextType({'en-US': 'Example Property object', - 'de': 'Beispiel Property Element'}), + category="CONSTANT", + description=model.MultiLanguageTextType( + {"en-US": "Example Property object", "de": "Beispiel Property Element"} + ), parent=None, - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/Properties/ExamplePropertyOutput'),)), - qualifier=()) + semantic_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/Properties/ExamplePropertyOutput", + ), + ) + ), + qualifier=(), + ) in_output_variable_property = model.Property( - id_short='ExamplePropertyInOutput', + id_short="ExamplePropertyInOutput", value_type=model.datatypes.String, value=None, value_id=None, - category='CONSTANT', - description=model.MultiLanguageTextType({'en-US': 'Example Property object', - 'de': 'Beispiel Property Element'}), + category="CONSTANT", + description=model.MultiLanguageTextType( + {"en-US": "Example Property object", "de": "Beispiel Property Element"} + ), parent=None, - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/Properties/' - 'ExamplePropertyInOutput'),)), - qualifier=()) + semantic_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/Properties/ExamplePropertyInOutput", + ), + ) + ), + qualifier=(), + ) submodel_element_operation = model.Operation( - id_short='ExampleOperation', + id_short="ExampleOperation", input_variable=[input_variable_property], output_variable=[output_variable_property], in_output_variable=[in_output_variable_property], - category='PARAMETER', - description=model.MultiLanguageTextType({'en-US': 'Example Operation object', - 'de': 'Beispiel Operation Element'}), + category="PARAMETER", + description=model.MultiLanguageTextType( + {"en-US": "Example Operation object", "de": "Beispiel Operation Element"} + ), parent=None, - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/Operations/' - 'ExampleOperation'),)), - qualifier=()) + semantic_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/Operations/ExampleOperation", + ), + ) + ), + qualifier=(), + ) submodel_element_capability = model.Capability( - id_short='ExampleCapability', - category='PARAMETER', - description=model.MultiLanguageTextType({'en-US': 'Example Capability object', - 'de': 'Beispiel Capability Element'}), + id_short="ExampleCapability", + category="PARAMETER", + description=model.MultiLanguageTextType( + {"en-US": "Example Capability object", "de": "Beispiel Capability Element"} + ), parent=None, - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/Capabilities/' - 'ExampleCapability'),)), - qualifier=()) + semantic_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/Capabilities/ExampleCapability", + ), + ) + ), + qualifier=(), + ) submodel_element_basic_event_element = model.BasicEventElement( - id_short='ExampleBasicEventElement', - observed=model.ModelReference((model.Key(type_=model.KeyTypes.SUBMODEL, - value='http://example.org/Test_Submodel'), - model.Key(type_=model.KeyTypes.PROPERTY, - value='ExampleProperty'),), - model.Property), + id_short="ExampleBasicEventElement", + observed=model.ModelReference( + ( + model.Key( + type_=model.KeyTypes.SUBMODEL, + value="http://example.org/Test_Submodel", + ), + model.Key(type_=model.KeyTypes.PROPERTY, value="ExampleProperty"), + ), + model.Property, + ), direction=model.Direction.OUTPUT, state=model.StateOfEvent.ON, - message_topic='ExampleTopic', - message_broker=model.ModelReference((model.Key(model.KeyTypes.SUBMODEL, - "http://example.org/ExampleMessageBroker"),), - model.Submodel), - last_update=model.datatypes.DateTime(2022, 11, 12, 23, 50, 23, 123456, datetime.timezone.utc), + message_topic="ExampleTopic", + message_broker=model.ModelReference( + ( + model.Key( + model.KeyTypes.SUBMODEL, "http://example.org/ExampleMessageBroker" + ), + ), + model.Submodel, + ), + last_update=model.datatypes.DateTime( + 2022, 11, 12, 23, 50, 23, 123456, datetime.timezone.utc + ), min_interval=model.datatypes.Duration(microseconds=1), - max_interval=model.datatypes.Duration(years=1, months=2, days=3, hours=4, minutes=5, seconds=6, - microseconds=123456), - category='PARAMETER', - description=model.MultiLanguageTextType({'en-US': 'Example BasicEventElement object', - 'de': 'Beispiel BasicEventElement Element'}), + max_interval=model.datatypes.Duration( + years=1, + months=2, + days=3, + hours=4, + minutes=5, + seconds=6, + microseconds=123456, + ), + category="PARAMETER", + description=model.MultiLanguageTextType( + { + "en-US": "Example BasicEventElement object", + "de": "Beispiel BasicEventElement Element", + } + ), parent=None, - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/Events/ExampleBasicEventElement'),)), - qualifier=()) + semantic_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/Events/ExampleBasicEventElement", + ), + ) + ), + qualifier=(), + ) submodel_element_submodel_element_collection = model.SubmodelElementCollection( id_short=None, value=( - submodel_element_property, - submodel_element_multi_language_property, - submodel_element_range, - submodel_element_range_2, - submodel_element_blob, - submodel_element_file, - submodel_element_reference_element), - category='PARAMETER', - description=model.MultiLanguageTextType({'en-US': 'Example SubmodelElementCollection object', - 'de': 'Beispiel SubmodelElementCollection Element'}), + submodel_element_property, + submodel_element_multi_language_property, + submodel_element_range, + submodel_element_range_2, + submodel_element_blob, + submodel_element_file, + submodel_element_reference_element, + ), + category="PARAMETER", + description=model.MultiLanguageTextType( + { + "en-US": "Example SubmodelElementCollection object", + "de": "Beispiel SubmodelElementCollection Element", + } + ), parent=None, - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/SubmodelElementCollections/' - 'ExampleSubmodelElementCollection'),)), - qualifier=()) + semantic_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/SubmodelElementCollections/" + "ExampleSubmodelElementCollection", + ), + ) + ), + qualifier=(), + ) submodel_element_submodel_element_collection_2 = model.SubmodelElementCollection( id_short=None, value=(), - category='PARAMETER', - description=model.MultiLanguageTextType({'en-US': 'Example SubmodelElementCollection object', - 'de': 'Beispiel SubmodelElementCollection Element'}), + category="PARAMETER", + description=model.MultiLanguageTextType( + { + "en-US": "Example SubmodelElementCollection object", + "de": "Beispiel SubmodelElementCollection Element", + } + ), parent=None, - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/SubmodelElementCollections/' - 'ExampleSubmodelElementCollection'),)), - qualifier=()) + semantic_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/SubmodelElementCollections/" + "ExampleSubmodelElementCollection", + ), + ) + ), + qualifier=(), + ) submodel_element_submodel_element_list = model.SubmodelElementList( - id_short='ExampleSubmodelList', + id_short="ExampleSubmodelList", type_value_list_element=model.SubmodelElementCollection, - value=(submodel_element_submodel_element_collection, submodel_element_submodel_element_collection_2), - semantic_id_list_element=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/' - 'SubmodelElementCollections/' - 'ExampleSubmodelElementCollection'),)), + value=( + submodel_element_submodel_element_collection, + submodel_element_submodel_element_collection_2, + ), + semantic_id_list_element=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/" + "SubmodelElementCollections/" + "ExampleSubmodelElementCollection", + ), + ) + ), order_relevant=True, - category='PARAMETER', - description=model.MultiLanguageTextType({'en-US': 'Example SubmodelElementList object', - 'de': 'Beispiel SubmodelElementList Element'}), + category="PARAMETER", + description=model.MultiLanguageTextType( + { + "en-US": "Example SubmodelElementList object", + "de": "Beispiel SubmodelElementList Element", + } + ), parent=None, - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/SubmodelElementLists/' - 'ExampleSubmodelElementList'),)), - qualifier=()) + semantic_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/SubmodelElementLists/" + "ExampleSubmodelElementList", + ), + ) + ), + qualifier=(), + ) submodel_element_submodel_element_list_2 = model.SubmodelElementList( - id_short='ExampleSubmodelList2', + id_short="ExampleSubmodelList2", type_value_list_element=model.Capability, value=(), - semantic_id_list_element=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/' - 'SubmodelElementCollections/' - 'ExampleSubmodelElementCollection'),)), + semantic_id_list_element=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/" + "SubmodelElementCollections/" + "ExampleSubmodelElementCollection", + ), + ) + ), order_relevant=True, - category='PARAMETER', - description=model.MultiLanguageTextType({'en-US': 'Example SubmodelElementList object', - 'de': 'Beispiel SubmodelElementList Element'}), + category="PARAMETER", + description=model.MultiLanguageTextType( + { + "en-US": "Example SubmodelElementList object", + "de": "Beispiel SubmodelElementList Element", + } + ), parent=None, - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/SubmodelElementLists/' - 'ExampleSubmodelElementList'),)), - qualifier=()) + semantic_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/SubmodelElementLists/" + "ExampleSubmodelElementList", + ), + ) + ), + qualifier=(), + ) submodel = model.Submodel( - id_='https://example.org/Test_Submodel_Template', - submodel_element=(submodel_element_relationship_element, - submodel_element_annotated_relationship_element, - submodel_element_operation, - submodel_element_capability, - submodel_element_basic_event_element, - submodel_element_submodel_element_list, - submodel_element_submodel_element_list_2), - id_short='TestSubmodel', + id_="https://example.org/Test_Submodel_Template", + submodel_element=( + submodel_element_relationship_element, + submodel_element_annotated_relationship_element, + submodel_element_operation, + submodel_element_capability, + submodel_element_basic_event_element, + submodel_element_submodel_element_list, + submodel_element_submodel_element_list_2, + ), + id_short="TestSubmodel", category=None, - description=model.MultiLanguageTextType({'en-US': 'An example submodel for the test application', - 'de': 'Ein Beispiel-Teilmodell für eine Test-Anwendung'}), + description=model.MultiLanguageTextType( + { + "en-US": "An example submodel for the test application", + "de": "Ein Beispiel-Teilmodell für eine Test-Anwendung", + } + ), parent=None, - administration=model.AdministrativeInformation(version='9', - revision='0'), - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/SubmodelTemplates/' - 'ExampleSubmodel'),)), + administration=model.AdministrativeInformation(version="9", revision="0"), + semantic_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/SubmodelTemplates/ExampleSubmodel", + ), + ) + ), qualifier=(), - kind=model.ModellingKind.TEMPLATE) + kind=model.ModellingKind.TEMPLATE, + ) return submodel @@ -343,7 +590,11 @@ def check_example_submodel(checker: AASDataChecker, submodel: model.Submodel) -> checker.check_submodel_equal(submodel, expected_submodel) -def check_full_example(checker: AASDataChecker, identifiable_store: model.DictIdentifiableStore) -> None: - expected_data: model.DictIdentifiableStore[model.Identifiable] = model.DictIdentifiableStore() +def check_full_example( + checker: AASDataChecker, identifiable_store: model.DictIdentifiableStore +) -> None: + expected_data: model.DictIdentifiableStore[model.Identifiable] = ( + model.DictIdentifiableStore() + ) expected_data.add(create_example_submodel_template()) checker.check_identifiable_store(identifiable_store, expected_data) diff --git a/sdk/basyx/aas/examples/tutorial_aasx.py b/sdk/basyx/aas/examples/tutorial_aasx.py index 8878ed0c8..3c28359a5 100755 --- a/sdk/basyx/aas/examples/tutorial_aasx.py +++ b/sdk/basyx/aas/examples/tutorial_aasx.py @@ -10,6 +10,7 @@ *Details of the Asset Administration Shell* some specifications of AASX files will change, resulting in changes of the :class:`~basyx.aas.adapter.aasx.AASXWriter` interface. """ + import datetime from pathlib import Path # Used for easier handling of auxiliary file's local path @@ -29,22 +30,18 @@ # Let's first create a basic Asset Administration Shell with a simple submodel. # See `tutorial_create_simple_aas.py` for more details. -submodel = model.Submodel( - id_='https://example.org/Simple_Submodel' -) +submodel = model.Submodel(id_="https://example.org/Simple_Submodel") aas = model.AssetAdministrationShell( - id_='https://example.org/Simple_AAS', + id_="https://example.org/Simple_AAS", asset_information=model.AssetInformation( asset_kind=model.AssetKind.INSTANCE, - global_asset_id='http://example.org/Simple_Asset' + global_asset_id="http://example.org/Simple_Asset", ), - submodel={model.ModelReference.from_referable(submodel)} + submodel={model.ModelReference.from_referable(submodel)}, ) # Another submodel, which is not related to the AAS: -unrelated_submodel = model.Submodel( - id_='https://example.org/Unrelated_Submodel' -) +unrelated_submodel = model.Submodel(id_="https://example.org/Unrelated_Submodel") # We add these objects to an IdentifiableStore for easy retrieval by id. # See `tutorial_storage.py` for more details. We could also use a database-backed IdentifiableStore here @@ -69,17 +66,22 @@ # (This is actually a requirement of the underlying Open Packaging Conventions (ECMA376-2) format, which imposes the # specification of the MIME type ("content type") of every single file within the package.) -with open(Path(__file__).parent / 'data' / 'TestFile.pdf', 'rb') as f: - actual_file_name = file_store.add_file("/aasx/suppl/MyExampleFile.pdf", f, "application/pdf") +with open(Path(__file__).parent / "data" / "TestFile.pdf", "rb") as f: + actual_file_name = file_store.add_file( + "/aasx/suppl/MyExampleFile.pdf", f, "application/pdf" + ) # With the actual_file_name in the SupplementaryFileContainer, we can create a reference to that file in our AAS # Submodel, in the form of a `File` object: submodel.submodel_element.add( - model.File(id_short="documentationFile", - content_type="application/pdf", - value=actual_file_name)) + model.File( + id_short="documentationFile", + content_type="application/pdf", + value=actual_file_name, + ) +) ###################################################################### @@ -102,9 +104,11 @@ # ATTENTION: As of Version 3.0 RC01 of Details of the Asset Administration Shell, it is no longer valid to add more # than one "aas-spec" part (JSON/XML part with AAS objects) to an AASX package. Thus, `write_aas` MUST # only be called once per AASX package! - writer.write_aas(aas_ids=['https://example.org/Simple_AAS'], - object_store=identifiable_store, - file_store=file_store) + writer.write_aas( + aas_ids=["https://example.org/Simple_AAS"], + object_store=identifiable_store, + file_store=file_store, + ) # Alternatively, we can use a more low-level interface to add a JSON/XML part with any Identifiable objects (not # only an AAS and referenced objects) in the AASX package manually. `write_aas_objects()` will also take care of @@ -113,12 +117,14 @@ # ATTENTION: As of Version 3.0 RC01 of Details of the Asset Administration Shell, it is no longer valid to add more # than one "aas-spec" part (JSON/XML part with AAS objects) to an AASX package. Thus, `write_all_aas_objects` SHALL # only be used as an alternative to `write_aas` and SHALL only be called once! - identifiables_to_be_written: model.DictIdentifiableStore[model.Identifiable] = model.DictIdentifiableStore( - [unrelated_submodel] + identifiables_to_be_written: model.DictIdentifiableStore[model.Identifiable] = ( + model.DictIdentifiableStore([unrelated_submodel]) + ) + writer.write_all_aas_objects( + part_name="/aasx/my_aas_part.xml", + objects=identifiables_to_be_written, + file_store=file_store, ) - writer.write_all_aas_objects(part_name="/aasx/my_aas_part.xml", - objects=identifiables_to_be_written, - file_store=file_store) # We can also add a thumbnail image to the package (using `writer.write_thumbnail()`) or add metadata: meta_data = pyecma376_2.OPCCoreProperties() @@ -137,15 +143,16 @@ # Let's read the AASX package file, we have just written. # We'll use a fresh IdentifiableStore and SupplementaryFileContainer to read AAS objects and auxiliary files into. -new_identifiable_store: model.DictIdentifiableStore[model.Identifiable] = model.DictIdentifiableStore() +new_identifiable_store: model.DictIdentifiableStore[model.Identifiable] = ( + model.DictIdentifiableStore() +) new_file_store = aasx.DictSupplementaryFileContainer() # Again, we need to use the AASXReader as a context manager (or call `.close()` in the end) to make sure the AASX # package file is properly closed when we are finished. with aasx.AASXReader("MyAASXPackage.aasx") as reader: # Read all contained AAS objects and all referenced auxiliary files - reader.read_into(object_store=new_identifiable_store, - file_store=new_file_store) + reader.read_into(object_store=new_identifiable_store, file_store=new_file_store) # We can also read the metadata new_meta_data = reader.get_core_properties() @@ -154,6 +161,6 @@ # Some quick checks to make sure, reading worked as expected -assert 'https://example.org/Simple_Submodel' in new_identifiable_store +assert "https://example.org/Simple_Submodel" in new_identifiable_store assert actual_file_name in new_file_store assert new_meta_data.creator == "Chair of Process Control Engineering" diff --git a/sdk/basyx/aas/examples/tutorial_backend_couchdb.py b/sdk/basyx/aas/examples/tutorial_backend_couchdb.py index ed09cf432..a71b1e087 100755 --- a/sdk/basyx/aas/examples/tutorial_backend_couchdb.py +++ b/sdk/basyx/aas/examples/tutorial_backend_couchdb.py @@ -44,21 +44,29 @@ # password of a CouchDB user account which is "member" of this database (see above). Alternatively, you can provide # your CouchDB server's admin credentials. config = ConfigParser() -config.read([Path(__file__).parent.parent.parent.parent / 'test' / 'test_config.default.ini', - Path(__file__).parent.parent.parent.parent / 'test' / 'test_config.ini']) +config.read( + [ + Path(__file__).parent.parent.parent.parent / "test" / "test_config.default.ini", + Path(__file__).parent.parent.parent.parent / "test" / "test_config.ini", + ] +) -couchdb_url = config['couchdb']['url'] -couchdb_database = config['couchdb']['database'] -couchdb_user = config['couchdb']['user'] -couchdb_password = config['couchdb']['password'] +couchdb_url = config["couchdb"]["url"] +couchdb_database = config["couchdb"]["database"] +couchdb_user = config["couchdb"]["user"] +couchdb_password = config["couchdb"]["password"] # Provide the login credentials to the CouchDB backend. # These credentials are used when communication with this CouchDB server is required via the CouchDBIdentifiableStore. -basyx.aas.backend.couchdb.register_credentials(couchdb_url, couchdb_user, couchdb_password) +basyx.aas.backend.couchdb.register_credentials( + couchdb_url, couchdb_user, couchdb_password +) # Now, we create a CouchDBIdentifiableStore as an interface for managing the objects in the CouchDB server. -identifiable_store = basyx.aas.backend.couchdb.CouchDBIdentifiableStore(couchdb_url, couchdb_database) +identifiable_store = basyx.aas.backend.couchdb.CouchDBIdentifiableStore( + couchdb_url, couchdb_database +) ########################################################### @@ -66,8 +74,12 @@ ########################################################### # Create some example objects -example_submodel1 = basyx.aas.examples.data.example_aas.create_example_asset_identification_submodel() -example_submodel2 = basyx.aas.examples.data.example_aas.create_example_bill_of_material_submodel() +example_submodel1 = ( + basyx.aas.examples.data.example_aas.create_example_asset_identification_submodel() +) +example_submodel2 = ( + basyx.aas.examples.data.example_aas.create_example_bill_of_material_submodel() +) # The CouchDBIdentifiableStore behaves just like other ObjectStore implementations (see `tutorial_storage.py`). The # objects are transferred to the CouchDB immediately. diff --git a/sdk/basyx/aas/examples/tutorial_create_simple_aas.py b/sdk/basyx/aas/examples/tutorial_create_simple_aas.py index 3cacd74ab..7a9185b71 100755 --- a/sdk/basyx/aas/examples/tutorial_create_simple_aas.py +++ b/sdk/basyx/aas/examples/tutorial_create_simple_aas.py @@ -26,14 +26,14 @@ # Step 1.1: create the AssetInformation object asset_information = model.AssetInformation( asset_kind=model.AssetKind.INSTANCE, - global_asset_id='http://example.org/Simple_Asset' + global_asset_id="http://example.org/Simple_Asset", ) # step 1.2: create the Asset Administration Shell -identifier = 'https://example.org/Simple_AAS' +identifier = "https://example.org/Simple_AAS" aas = model.AssetAdministrationShell( id_=identifier, # set identifier - asset_information=asset_information + asset_information=asset_information, ) @@ -42,10 +42,8 @@ ############################################################# # Step 2.1: create the Submodel object -identifier = 'https://example.org/Simple_Submodel' -submodel = model.Submodel( - id_=identifier -) +identifier = "https://example.org/Simple_Submodel" +submodel = model.Submodel(id_=identifier) # Step 2.2: create a reference to that Submodel and add it to the Asset Administration Shell's `submodel` set aas.submodel.add(model.ModelReference.from_referable(submodel)) @@ -54,13 +52,11 @@ # =============================================================== # ALTERNATIVE: step 1 and 2 can alternatively be done in one step # In this version, the Submodel reference is passed to the Asset Administration Shell's constructor. -submodel = model.Submodel( - id_='https://example.org/Simple_Submodel' -) +submodel = model.Submodel(id_="https://example.org/Simple_Submodel") aas = model.AssetAdministrationShell( - id_='https://example.org/Simple_AAS', + id_="https://example.org/Simple_AAS", asset_information=asset_information, - submodel={model.ModelReference.from_referable(submodel)} + submodel={model.ModelReference.from_referable(submodel)}, ) @@ -71,18 +67,20 @@ # Step 3.1: create a global reference to a semantic description of the Property # A global reference consist of one key which points to the address where the semantic description is stored semantic_reference = model.ExternalReference( - (model.Key( - type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/Properties/SimpleProperty' - ),) + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/Properties/SimpleProperty", + ), + ) ) # Step 3.2: create the simple Property property_ = model.Property( - id_short='ExampleProperty', # Identifying string of the element within the Submodel namespace + id_short="ExampleProperty", # Identifying string of the element within the Submodel namespace value_type=model.datatypes.String, # Data type of the value - value='exampleValue', # Value of the Property - semantic_id=semantic_reference # set the semantic reference + value="exampleValue", # Value of the Property + semantic_id=semantic_reference, # set the semantic reference ) # Step 3.3: add the Property to the Submodel @@ -93,18 +91,20 @@ # ALTERNATIVE: step 2 and 3 can also be combined in a single statement: # Again, we pass the Property to the Submodel's constructor instead of adding it afterward. submodel = model.Submodel( - id_='https://example.org/Simple_Submodel', + id_="https://example.org/Simple_Submodel", submodel_element={ model.Property( - id_short='ExampleProperty', + id_short="ExampleProperty", value_type=model.datatypes.String, - value='exampleValue', + value="exampleValue", semantic_id=model.ExternalReference( - (model.Key( - type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/Properties/SimpleProperty' - ),) - ) + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/Properties/SimpleProperty", + ), + ) + ), ) - } + }, ) diff --git a/sdk/basyx/aas/examples/tutorial_navigate_aas.py b/sdk/basyx/aas/examples/tutorial_navigate_aas.py index 2bbb9f89d..c373eed16 100644 --- a/sdk/basyx/aas/examples/tutorial_navigate_aas.py +++ b/sdk/basyx/aas/examples/tutorial_navigate_aas.py @@ -48,7 +48,7 @@ my_property = model.Property( id_short="MyProperty", value_type=model.datatypes.String, - value="I am a simple Property" + value="I am a simple Property", ) submodel.submodel_element.add(my_property) @@ -59,14 +59,14 @@ model.Property( id_short="MyProperty0", value_type=model.datatypes.String, - value="I am the first of two Properties within a SubmodelElementCollection" + value="I am the first of two Properties within a SubmodelElementCollection", ), model.Property( id_short="MyProperty1", value_type=model.datatypes.String, - value="I am the second of two Properties within a SubmodelElementCollection" - ) - } + value="I am the second of two Properties within a SubmodelElementCollection", + ), + }, ) submodel.submodel_element.add(my_property_collection) @@ -80,47 +80,57 @@ model.Property( id_short=None, value_type=model.datatypes.String, - value="I am Property 0 within a SubmodelElementList" + value="I am Property 0 within a SubmodelElementList", ), model.Property( id_short=None, value_type=model.datatypes.String, - value="I am Property 1 within a SubmodelElementList" - ) - ] + value="I am Property 1 within a SubmodelElementList", + ), + ], ) submodel.submodel_element.add(my_property_list) # Step 1.5: Add a SubmodelElementList of SubmodelElementCollections to the Submodel my_property_collection_0 = model.SubmodelElementCollection( id_short=None, - value={model.Property( - id_short="MyProperty", - value_type=model.datatypes.String, - value="I am a simple Property within SubmodelElementCollection 0" - )} + value={ + model.Property( + id_short="MyProperty", + value_type=model.datatypes.String, + value="I am a simple Property within SubmodelElementCollection 0", + ) + }, ) my_property_collection_1 = model.SubmodelElementCollection( id_short=None, - value={model.Property( - id_short="MyProperty", - value_type=model.datatypes.String, - value="I am a simple Property within SubmodelElementCollection 1" - )} + value={ + model.Property( + id_short="MyProperty", + value_type=model.datatypes.String, + value="I am a simple Property within SubmodelElementCollection 1", + ) + }, ) my_property_collection_2 = model.SubmodelElementCollection( id_short=None, - value={model.Property( - id_short="MyProperty", - value_type=model.datatypes.String, - value="I am a simple Property within SubmodelElementCollection 2" - )} + value={ + model.Property( + id_short="MyProperty", + value_type=model.datatypes.String, + value="I am a simple Property within SubmodelElementCollection 2", + ) + }, ) my_collection_list = model.SubmodelElementList( id_short="MyCollectionList", type_value_list_element=model.SubmodelElementCollection, order_relevant=True, - value=[my_property_collection_0, my_property_collection_1, my_property_collection_2] + value=[ + my_property_collection_0, + my_property_collection_1, + my_property_collection_2, + ], ) submodel.submodel_element.add(my_collection_list) @@ -135,8 +145,12 @@ # Step 2.2: Navigate through a SubmodelElementCollection of Properties # Step 2.2.1: Access a Property within a SubmodelElementCollection step by step via its IdShort -my_property_collection = cast(model.SubmodelElementCollection, submodel.get_referable("MyPropertyCollection")) -my_property_collection_property_0 = cast(model.Property, my_property_collection.get_referable("MyProperty0")) +my_property_collection = cast( + model.SubmodelElementCollection, submodel.get_referable("MyPropertyCollection") +) +my_property_collection_property_0 = cast( + model.Property, my_property_collection.get_referable("MyProperty0") +) print( f"my_property_collection_property_0: " f"id_short = {my_property_collection_property_0}, " @@ -145,8 +159,7 @@ # Step 2.2.2: Access a Property within a SubmodelElementCollection via its IdShortPath my_property_collection_property_1 = cast( - model.Property, - submodel.get_referable(["MyPropertyCollection", "MyProperty1"]) + model.Property, submodel.get_referable(["MyPropertyCollection", "MyProperty1"]) ) print( f"my_property_collection_property_1: " @@ -156,7 +169,9 @@ # Step 2.3: Navigate through a SubmodelElementList of Properties # Step 2.3.1: Access a Property within a SubmodelElementList step by step via its index -my_property_list = cast(model.SubmodelElementList, submodel.get_referable("MyPropertyList")) +my_property_list = cast( + model.SubmodelElementList, submodel.get_referable("MyPropertyList") +) my_property_list_property_0 = cast(model.Property, my_property_list.get_referable("0")) print( f"my_property_list_property_0: " @@ -165,7 +180,9 @@ ) # Step 2.3.2: Access a Property within a SubmodelElementList via its IdShortPath -my_property_list_property_1 = cast(model.Property, submodel.get_referable(["MyPropertyList", "1"])) +my_property_list_property_1 = cast( + model.Property, submodel.get_referable(["MyPropertyList", "1"]) +) print( f"my_property_list_property_1: " f"id_short = {my_property_list_property_1}, " @@ -175,11 +192,14 @@ # Step 2.4: Navigate through a SubmodelElementList of SubmodelElementCollections # Step 2.4.1: Access a Property within a SubmodelElementList of SubmodelElementCollections step by step via its index # and IdShort -my_collection_list = cast(model.SubmodelElementList, submodel.get_referable("MyCollectionList")) -my_collection_list_collection_0 = cast(model.SubmodelElementCollection, my_collection_list.get_referable("0")) +my_collection_list = cast( + model.SubmodelElementList, submodel.get_referable("MyCollectionList") +) +my_collection_list_collection_0 = cast( + model.SubmodelElementCollection, my_collection_list.get_referable("0") +) my_collection_list_collection_0_property_0 = cast( - model.Property, - my_collection_list_collection_0.get_referable("MyProperty") + model.Property, my_collection_list_collection_0.get_referable("MyProperty") ) print( f"my_collection_list_collection_0_property_0: " @@ -189,8 +209,7 @@ # Step 2.4.2: Access a Property within a SubmodelElementList of SubmodelElementCollections via its IdShortPath my_collection_list_collection_2_property_0 = cast( - model.Property, - submodel.get_referable(["MyCollectionList", "2", "MyProperty"]) + model.Property, submodel.get_referable(["MyCollectionList", "2", "MyProperty"]) ) print( f"my_collection_list_collection_2_property_0: " diff --git a/sdk/basyx/aas/examples/tutorial_serialization_deserialization.py b/sdk/basyx/aas/examples/tutorial_serialization_deserialization.py index 955cb062a..7d724de07 100755 --- a/sdk/basyx/aas/examples/tutorial_serialization_deserialization.py +++ b/sdk/basyx/aas/examples/tutorial_serialization_deserialization.py @@ -32,23 +32,27 @@ # For more details, take a look at `tutorial_create_simple_aas.py` submodel = model.Submodel( - id_='https://example.org/Simple_Submodel', + id_="https://example.org/Simple_Submodel", submodel_element={ model.Property( - id_short='ExampleProperty', + id_short="ExampleProperty", value_type=basyx.aas.model.datatypes.String, - value='exampleValue', - semantic_id=model.ExternalReference((model.Key( - type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/Properties/SimpleProperty' - ),) - ) - )} + value="exampleValue", + semantic_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/Properties/SimpleProperty", + ), + ) + ), + ) + }, ) aashell = model.AssetAdministrationShell( - id_='https://example.org/Simple_AAS', + id_="https://example.org/Simple_AAS", asset_information=model.AssetInformation(global_asset_id="test"), - submodel={model.ModelReference.from_referable(submodel)} + submodel={model.ModelReference.from_referable(submodel)}, ) @@ -62,14 +66,16 @@ # dumped data structure. aashell_json_string = json.dumps(aashell, cls=basyx.aas.adapter.json.AASToJsonEncoder) -property_json_string = json.dumps(submodel.submodel_element.get_object_by_attribute("id_short", 'ExampleProperty'), - cls=basyx.aas.adapter.json.AASToJsonEncoder) +property_json_string = json.dumps( + submodel.submodel_element.get_object_by_attribute("id_short", "ExampleProperty"), + cls=basyx.aas.adapter.json.AASToJsonEncoder, +) # Using this technique, we can also serialize Python dict and list data structures with nested AAS objects: -json_string = json.dumps({'the_submodel': submodel, - 'the_aas': aashell - }, - cls=basyx.aas.adapter.json.AASToJsonEncoder) +json_string = json.dumps( + {"the_submodel": submodel, "the_aas": aashell}, + cls=basyx.aas.adapter.json.AASToJsonEncoder, +) ###################################################################### @@ -80,7 +86,9 @@ # JSONDecoder class, called `AASFromJSONDecoder` which can be passed to `json.load()` or `json.loads()` to ensure that # AAS objects contained in the JSON data are transformed into their BaSyx Python SDK object representation instead of # simple Python dicts: -submodel_and_aas = json.loads(json_string, cls=basyx.aas.adapter.json.AASFromJsonDecoder) +submodel_and_aas = json.loads( + json_string, cls=basyx.aas.adapter.json.AASFromJsonDecoder +) # Alternatively, one can use the `StrictAASFromJsonDecoder` which works in just the same way, but enforces the format # specification more strictly. While `AASFromJSONDecoder` will tolerate some semantic errors by simple skipping the @@ -93,18 +101,20 @@ # step 4.1: creating an IdentifiableStore containing the objects to be serialized # For more information, take a look into `tutorial_storage.py` -identifiable_store: model.DictIdentifiableStore[model.Identifiable] = model.DictIdentifiableStore() +identifiable_store: model.DictIdentifiableStore[model.Identifiable] = ( + model.DictIdentifiableStore() +) identifiable_store.add(submodel) identifiable_store.add(aashell) # step 4.2: writing the contents of the IdentifiableStore to a JSON file -basyx.aas.adapter.json.write_aas_json_file('data.json', identifiable_store) +basyx.aas.adapter.json.write_aas_json_file("data.json", identifiable_store) # We can pass the additional keyword argument `indent=4` to `write_aas_json_file()` to format the JSON file in a more # human-readable (but much more space-consuming) manner. # step 4.3: writing the contents of the IdentifiableStore to an XML file -basyx.aas.adapter.xml.write_aas_xml_file('data.xml', identifiable_store) +basyx.aas.adapter.xml.write_aas_xml_file("data.xml", identifiable_store) ################################################################## @@ -112,17 +122,17 @@ ################################################################## # step 5.1: reading contents of the JSON file as an IdentifiableStore -json_file_data = basyx.aas.adapter.json.read_aas_json_file('data.json') +json_file_data = basyx.aas.adapter.json.read_aas_json_file("data.json") # By passing the `failsafe=False` argument to `read_aas_json_file()`, we can switch to the `StrictAASFromJsonDecoder` # (see step 3) for a stricter error reporting. # step 5.2: reading contents of the XML file as an IdentifiableStore -xml_file_data = basyx.aas.adapter.xml.read_aas_xml_file('data.xml') +xml_file_data = basyx.aas.adapter.xml.read_aas_xml_file("data.xml") # Again, we can use `failsafe=False` for switching on stricter error reporting in the parser. # step 5.3: Retrieving the objects from the IdentifiableStore # For more information on the available techniques, see `tutorial_storage.py`. -submodel_from_xml = xml_file_data.get_item('https://example.org/Simple_Submodel') +submodel_from_xml = xml_file_data.get_item("https://example.org/Simple_Submodel") assert isinstance(submodel_from_xml, model.Submodel) diff --git a/sdk/basyx/aas/examples/tutorial_storage.py b/sdk/basyx/aas/examples/tutorial_storage.py index c422a7710..1e067cb69 100755 --- a/sdk/basyx/aas/examples/tutorial_storage.py +++ b/sdk/basyx/aas/examples/tutorial_storage.py @@ -18,7 +18,6 @@ # Step 3: retrieving objects from the store by their identifier # Step 4: using the IdentifiableStore to resolve a reference - from basyx.aas import model from basyx.aas.model import AssetInformation, AssetAdministrationShell, Submodel @@ -31,28 +30,27 @@ asset_information = AssetInformation( asset_kind=model.AssetKind.INSTANCE, - global_asset_id='http://example.org/Simple_Asset' + global_asset_id="http://example.org/Simple_Asset", ) prop = model.Property( - id_short='ExampleProperty', + id_short="ExampleProperty", value_type=model.datatypes.String, - value='exampleValue', + value="exampleValue", semantic_id=model.ExternalReference( - (model.Key( - type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/Properties/SimpleProperty' - ),) - ) -) -submodel = Submodel( - id_='https://example.org/Simple_Submodel', - submodel_element={prop} + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/Properties/SimpleProperty", + ), + ) + ), ) +submodel = Submodel(id_="https://example.org/Simple_Submodel", submodel_element={prop}) aas = AssetAdministrationShell( - id_='https://example.org/Simple_AAS', + id_="https://example.org/Simple_AAS", asset_information=asset_information, - submodel={model.ModelReference.from_referable(submodel)} + submodel={model.ModelReference.from_referable(submodel)}, ) @@ -69,7 +67,9 @@ # `aas.backends.couchdb` to use a CouchDB database server as persistent storage. Both ObjectStore implementations # provide the same interface. In addition, the CouchDBIdentifiableStore allows synchronizing the local object with the # database via a Backend. See the `tutorial_backend_couchdb.py` for more information. -identifiable_store: model.DictIdentifiableStore[model.Identifiable] = model.DictIdentifiableStore() +identifiable_store: model.DictIdentifiableStore[model.Identifiable] = ( + model.DictIdentifiableStore() +) # step 2.2: add submodel and asset administration shell to store identifiable_store.add(submodel) @@ -80,8 +80,7 @@ # Step 3: Retrieving Objects From the Store by Their Identifier # ################################################################# -tmp_submodel = identifiable_store.get_item( - 'https://example.org/Simple_Submodel') +tmp_submodel = identifiable_store.get_item("https://example.org/Simple_Submodel") assert submodel is tmp_submodel @@ -92,8 +91,7 @@ # The `aas` object already contains a reference to the submodel. # Let's create a list of all submodels, to which the AAS has references, by resolving each of the submodel references: -submodels = [reference.resolve(identifiable_store) - for reference in aas.submodel] +submodels = [reference.resolve(identifiable_store) for reference in aas.submodel] # The first (and only) element of this list should be our example submodel: assert submodel is submodels[0] @@ -102,14 +100,13 @@ # identifying the submodel by its id, the second one resolving to the Property within the submodel by its # idShort. property_reference = model.ModelReference( - (model.Key( - type_=model.KeyTypes.SUBMODEL, - value='https://example.org/Simple_Submodel'), - model.Key( - type_=model.KeyTypes.PROPERTY, - value='ExampleProperty'), - ), - type_=model.Property + ( + model.Key( + type_=model.KeyTypes.SUBMODEL, value="https://example.org/Simple_Submodel" + ), + model.Key(type_=model.KeyTypes.PROPERTY, value="ExampleProperty"), + ), + type_=model.Property, ) # Now, we can resolve this new reference. diff --git a/sdk/basyx/aas/model/__init__.py b/sdk/basyx/aas/model/__init__.py index 2ddd93b51..893740935 100644 --- a/sdk/basyx/aas/model/__init__.py +++ b/sdk/basyx/aas/model/__init__.py @@ -49,7 +49,11 @@ def resolve_referable_class_in_key_types(referable: Referable) -> type: :raises TypeError: If the type of the referable or any of its parent classes is not given in `KEY_TYPES_CLASSES`. """ try: - ref_type = next(iter(t for t in inspect.getmro(type(referable)) if t in KEY_TYPES_CLASSES)) + ref_type = next( + iter(t for t in inspect.getmro(type(referable)) if t in KEY_TYPES_CLASSES) + ) except StopIteration: - raise TypeError(f"Could not find a matching class in KEY_TYPES_CLASSES for {type(referable)}") + raise TypeError( + f"Could not find a matching class in KEY_TYPES_CLASSES for {type(referable)}" + ) return ref_type diff --git a/sdk/basyx/aas/model/_string_constraints.py b/sdk/basyx/aas/model/_string_constraints.py index da4c8b178..60b8501fd 100644 --- a/sdk/basyx/aas/model/_string_constraints.py +++ b/sdk/basyx/aas/model/_string_constraints.py @@ -32,34 +32,47 @@ _T = TypeVar("_T") -AASD130_RE = re.compile("[\x09\x0A\x0D\x20-\uD7FF\uE000-\uFFFD\U00010000-\U0010FFFF]*") +AASD130_RE = re.compile("[\x09\x0a\x0d\x20-\ud7ff\ue000-\ufffd\U00010000-\U0010ffff]*") def _unicode_escape(value: str) -> str: """ - Escapes unicode characters such as \uD7FF, that may be used in regular expressions, for better error messages. + Escapes unicode characters such as \ud7ff, that may be used in regular expressions, for better error messages. """ return value.encode("unicode_escape").decode("utf-8") # Functions to verify the constraints for a given value. -def check(value: str, type_name: str, min_length: int = 0, max_length: Optional[int] = None, - pattern: Optional[re.Pattern] = None) -> None: +def check( + value: str, + type_name: str, + min_length: int = 0, + max_length: Optional[int] = None, + pattern: Optional[re.Pattern] = None, +) -> None: if len(value) < min_length: - raise ValueError(f"{type_name} has a minimum length of {min_length}! (length: {len(value)})") + raise ValueError( + f"{type_name} has a minimum length of {min_length}! (length: {len(value)})" + ) if max_length is not None and len(value) > max_length: - raise ValueError(f"{type_name} has a maximum length of {max_length}! (length: {len(value)})") + raise ValueError( + f"{type_name} has a maximum length of {max_length}! (length: {len(value)})" + ) if pattern is not None and not pattern.fullmatch(value): - raise ValueError(f"{type_name} must match the pattern '{_unicode_escape(pattern.pattern)}'! " - f"(value: '{_unicode_escape(value)}')") + raise ValueError( + f"{type_name} must match the pattern '{_unicode_escape(pattern.pattern)}'! " + f"(value: '{_unicode_escape(value)}')" + ) # Constraint AASd-130: an attribute with data type "string" shall consist of these characters only: if not AASD130_RE.fullmatch(value): # It's easier to implement this as a ValueError, because otherwise AASConstraintViolation would need to be # imported from `base` and the ConstrainedLangStringSet would need to except AASConstraintViolation errors # as well, while only re-raising ValueErrors. Thus, even if an AASConstraintViolation would be raised here, # in case of a ConstrainedLangStringSet it would be re-raised as a ValueError anyway. - raise ValueError(f"Every string must match the pattern '{_unicode_escape(AASD130_RE.pattern)}'! " - f"(value: '{_unicode_escape(value)}')") + raise ValueError( + f"Every string must match the pattern '{_unicode_escape(AASD130_RE.pattern)}'! " + f"(value: '{_unicode_escape(value)}')" + ) def check_content_type(value: str, type_name: str = "ContentType") -> None: @@ -102,8 +115,11 @@ def check_version_type(value: str, type_name: str = "VersionType") -> None: return check(value, type_name, 1, 4, re.compile(r"([0-9]|[1-9][0-9]*)")) -def create_check_function(min_length: int = 0, max_length: Optional[int] = None, pattern: Optional[re.Pattern] = None) \ - -> Callable[[str, str], None]: +def create_check_function( + min_length: int = 0, + max_length: Optional[int] = None, + pattern: Optional[re.Pattern] = None, +) -> Callable[[str, str], None]: """ Returns a ``check_type`` function for the given constraints. @@ -112,14 +128,17 @@ def create_check_function(min_length: int = 0, max_length: Optional[int] = None, :class:`ConstrainedLangStringSets ` that define their own length and pattern rules. """ + def check_fn(value: str, type_name: str) -> None: return check(value, type_name, min_length, max_length, pattern) + return check_fn # Decorator functions to add getter/setter to classes for verification, whenever a value is updated. -def constrain_attr(pub_attr_name: str, constraint_check_fn: Callable[[str], None]) \ - -> Callable[[Type[_T]], Type[_T]]: +def constrain_attr( + pub_attr_name: str, constraint_check_fn: Callable[[str], None] +) -> Callable[[Type[_T]], Type[_T]]: def decorator_fn(decorated_class: Type[_T]) -> Type[_T]: def _getter(self) -> Optional[str]: return getattr(self, "_" + pub_attr_name) @@ -131,7 +150,9 @@ def _setter(self, value: Optional[str]) -> None: setattr(self, "_" + pub_attr_name, value) if hasattr(decorated_class, pub_attr_name): - raise AttributeError(f"{decorated_class.__name__} already has an attribute named '{pub_attr_name}'") + raise AttributeError( + f"{decorated_class.__name__} already has an attribute named '{pub_attr_name}'" + ) setattr(decorated_class, pub_attr_name, property(_getter, _setter)) return decorated_class diff --git a/sdk/basyx/aas/model/aas.py b/sdk/basyx/aas/model/aas.py index 4a9fb4640..52ad7067c 100644 --- a/sdk/basyx/aas/model/aas.py +++ b/sdk/basyx/aas/model/aas.py @@ -49,22 +49,26 @@ class AssetInformation: :ivar default_thumbnail: Thumbnail of the asset represented by the asset administration shell. Used as default. """ - def __init__(self, - asset_kind: base.AssetKind = base.AssetKind.INSTANCE, - global_asset_id: Optional[base.Identifier] = None, - specific_asset_id: Iterable[base.SpecificAssetId] = (), - asset_type: Optional[base.Identifier] = None, - default_thumbnail: Optional[base.Resource] = None): + def __init__( + self, + asset_kind: base.AssetKind = base.AssetKind.INSTANCE, + global_asset_id: Optional[base.Identifier] = 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.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._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) @@ -85,18 +89,26 @@ 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: + 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_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: + 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 @@ -105,20 +117,33 @@ def _validate_global_asset_id(global_asset_id: Optional[base.Identifier]) -> Non _string_constraints.check_identifier(global_asset_id) @staticmethod - def _validate_aasd_131(global_asset_id: Optional[base.Identifier], specific_asset_id_nonempty: bool) -> None: + 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") + 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={}, " \ - "defaultThumbnail={})".format(self.asset_kind, self._global_asset_id, str(self.specific_asset_id), - self.asset_type, str(self.default_thumbnail)) + return ( + "AssetInformation(assetKind={}, globalAssetId={}, specificAssetId={}, assetType={}, " + "defaultThumbnail={})".format( + self.asset_kind, + self._global_asset_id, + str(self.specific_asset_id), + self.asset_type, + str(self.default_thumbnail), + ) + ) -class AssetAdministrationShell(base.Identifiable, base.UniqueIdShortNamespace, base.HasDataSpecification): +class AssetAdministrationShell( + base.Identifiable, base.UniqueIdShortNamespace, base.HasDataSpecification +): """ An Asset Administration Shell @@ -145,20 +170,22 @@ class AssetAdministrationShell(base.Identifiable, base.UniqueIdShortNamespace, b :ivar extension: An extension of the element. (from :class:`~basyx.aas.model.base.HasExtension`) """ - def __init__(self, - asset_information: AssetInformation, - id_: base.Identifier, - id_short: Optional[base.NameType] = None, - display_name: Optional[base.MultiLanguageNameType] = None, - category: Optional[base.NameType] = None, - description: Optional[base.MultiLanguageTextType] = None, - parent: Optional[base.UniqueIdShortNamespace] = None, - administration: Optional[base.AdministrativeInformation] = None, - submodel: Optional[Set[base.ModelReference[Submodel]]] = None, - derived_from: Optional[base.ModelReference["AssetAdministrationShell"]] = None, - embedded_data_specifications: Iterable[base.EmbeddedDataSpecification] - = (), - extension: Iterable[base.Extension] = ()): + + def __init__( + self, + asset_information: AssetInformation, + id_: base.Identifier, + id_short: Optional[base.NameType] = None, + display_name: Optional[base.MultiLanguageNameType] = None, + category: Optional[base.NameType] = None, + description: Optional[base.MultiLanguageTextType] = None, + parent: Optional[base.UniqueIdShortNamespace] = None, + administration: Optional[base.AdministrativeInformation] = None, + submodel: Optional[Set[base.ModelReference[Submodel]]] = None, + derived_from: Optional[base.ModelReference["AssetAdministrationShell"]] = None, + embedded_data_specifications: Iterable[base.EmbeddedDataSpecification] = (), + extension: Iterable[base.Extension] = (), + ): super().__init__() self.id: base.Identifier = id_ self.asset_information: AssetInformation = asset_information @@ -168,7 +195,13 @@ def __init__(self, self.description: Optional[base.MultiLanguageTextType] = description self.parent: Optional[base.UniqueIdShortNamespace] = parent self.administration: Optional[base.AdministrativeInformation] = administration - self.derived_from: Optional[base.ModelReference["AssetAdministrationShell"]] = derived_from - self.submodel: Set[base.ModelReference[Submodel]] = set() if submodel is None else submodel - self.embedded_data_specifications: List[base.EmbeddedDataSpecification] = list(embedded_data_specifications) + self.derived_from: Optional[base.ModelReference["AssetAdministrationShell"]] = ( + derived_from + ) + self.submodel: Set[base.ModelReference[Submodel]] = ( + set() if submodel is None else submodel + ) + self.embedded_data_specifications: List[base.EmbeddedDataSpecification] = list( + embedded_data_specifications + ) self.extension = base.NamespaceSet(self, [("name", True)], extension) diff --git a/sdk/basyx/aas/model/base.py b/sdk/basyx/aas/model/base.py index bd64a1e6d..098e43dea 100644 --- a/sdk/basyx/aas/model/base.py +++ b/sdk/basyx/aas/model/base.py @@ -13,8 +13,26 @@ import inspect import itertools from enum import Enum, unique -from typing import List, Optional, Set, TypeVar, MutableSet, Generic, Iterable, Dict, Iterator, Union, overload, \ - MutableSequence, Type, Any, TYPE_CHECKING, Tuple, Callable, MutableMapping +from typing import ( + List, + Optional, + Set, + TypeVar, + MutableSet, + Generic, + Iterable, + Dict, + Iterator, + Union, + overload, + MutableSequence, + Type, + Any, + TYPE_CHECKING, + Tuple, + Callable, + MutableMapping, +) import re from . import datatypes, _string_constraints @@ -41,7 +59,9 @@ VersionType = str ValueTypeIEC61360 = str -MAX_RECURSION_DEPTH = 32*2 # see https://github.com/admin-shell-io/aas-specs-metamodel/issues/333 +MAX_RECURSION_DEPTH = ( + 32 * 2 +) # see https://github.com/admin-shell-io/aas-specs-metamodel/issues/333 @unique @@ -131,7 +151,11 @@ class KeyTypes(Enum): @property def is_aas_identifiable(self) -> bool: - return self in (self.ASSET_ADMINISTRATION_SHELL, self.CONCEPT_DESCRIPTION, self.SUBMODEL) + return self in ( + self.ASSET_ADMINISTRATION_SHELL, + self.CONCEPT_DESCRIPTION, + self.SUBMODEL, + ) @property def is_generic_globally_identifiable(self) -> bool: @@ -160,7 +184,7 @@ def is_aas_submodel_element(self) -> bool: self.RELATIONSHIP_ELEMENT, self.SUBMODEL_ELEMENT, self.SUBMODEL_ELEMENT_COLLECTION, - self.SUBMODEL_ELEMENT_LIST + self.SUBMODEL_ELEMENT_LIST, ) @property @@ -291,10 +315,13 @@ class LangStringSet(MutableMapping[str, str]): "en-GB" for English (United Kingdom) and English (United States). IETF language tags are referencing ISO 639, ISO 3166 and ISO 15924. """ + def __init__(self, dict_: Dict[str, str]): self._dict: Dict[str, str] = {} if not isinstance(dict_, dict): - raise TypeError(f"A {self.__class__.__name__} must be initialized with a dict!, got {type(dict_)}") + raise TypeError( + f"A {self.__class__.__name__} must be initialized with a dict!, got {type(dict_)}" + ) if len(dict_) < 1: raise ValueError(f"A {self.__class__.__name__} must not be empty!") for ltag in dict_: @@ -331,8 +358,10 @@ def _check_language_tag_constraints(cls, ltag: str): pattern = f"^{language_tag}$" if re.match(pattern, ltag) is None: - raise ValueError(f"The language tag must follow the format defined in BCP 47. " - f"Given language tag: {ltag}") + raise ValueError( + f"The language tag must follow the format defined in BCP 47. " + f"Given language tag: {ltag}" + ) def __getitem__(self, item: str) -> str: return self._dict[item] @@ -353,7 +382,12 @@ def __len__(self) -> int: return len(self._dict) def __repr__(self) -> str: - return self.__class__.__name__ + "(" + ", ".join(f'{k}="{v}"' for k, v in self.items()) + ")" + return ( + self.__class__.__name__ + + "(" + + ", ".join(f'{k}="{v}"' for k, v in self.items()) + + ")" + ) def clear(self) -> None: raise KeyError(f"A {self.__class__.__name__} must not be empty!") @@ -363,8 +397,11 @@ class ConstrainedLangStringSet(LangStringSet, metaclass=abc.ABCMeta): """ A :class:`LangStringSet` with constrained values. """ + @abc.abstractmethod - def __init__(self, dict_: Dict[str, str], constraint_check_fn: Callable[[str, str], None]): + def __init__( + self, dict_: Dict[str, str], constraint_check_fn: Callable[[str, str], None] + ): super().__init__(dict_) self._constraint_check_fn: Callable[[str, str], None] = constraint_check_fn for ltag, text in self._dict.items(): @@ -374,7 +411,9 @@ def _check_text_constraints(self, ltag: str, text: str) -> None: try: self._constraint_check_fn(text, self.__class__.__name__) except ValueError as e: - raise ValueError(f"The text for the language tag '{ltag}' is invalid: {e}") from e + raise ValueError( + f"The text for the language tag '{ltag}' is invalid: {e}" + ) from e def __setitem__(self, key: str, value: str) -> None: self._check_text_constraints(key, value) @@ -386,6 +425,7 @@ class MultiLanguageNameType(ConstrainedLangStringSet): A :class:`~.ConstrainedLangStringSet` where each value is a :class:`NameType`. See also: :func:`basyx.aas.model._string_constraints.check_name_type` """ + def __init__(self, dict_: Dict[str, str]): super().__init__(dict_, _string_constraints.check_name_type) @@ -394,32 +434,48 @@ class MultiLanguageTextType(ConstrainedLangStringSet): """ A :class:`~.ConstrainedLangStringSet` where each value must have at least 1 and at most 1023 characters. """ + def __init__(self, dict_: Dict[str, str]): - super().__init__(dict_, _string_constraints.create_check_function(min_length=1, max_length=1023)) + super().__init__( + dict_, + _string_constraints.create_check_function(min_length=1, max_length=1023), + ) class DefinitionTypeIEC61360(ConstrainedLangStringSet): """ A :class:`~.ConstrainedLangStringSet` where each value must have at least 1 and at most 1023 characters. """ + def __init__(self, dict_: Dict[str, str]): - super().__init__(dict_, _string_constraints.create_check_function(min_length=1, max_length=1023)) + super().__init__( + dict_, + _string_constraints.create_check_function(min_length=1, max_length=1023), + ) class PreferredNameTypeIEC61360(ConstrainedLangStringSet): """ A :class:`~.ConstrainedLangStringSet` where each value must have at least 1 and at most 255 characters. """ + def __init__(self, dict_: Dict[str, str]): - super().__init__(dict_, _string_constraints.create_check_function(min_length=1, max_length=255)) + super().__init__( + dict_, + _string_constraints.create_check_function(min_length=1, max_length=255), + ) class ShortNameTypeIEC61360(ConstrainedLangStringSet): """ A :class:`~.ConstrainedLangStringSet` where each value must have at least 1 and at most 18 characters. """ + def __init__(self, dict_: Dict[str, str]): - super().__init__(dict_, _string_constraints.create_check_function(min_length=1, max_length=18)) + super().__init__( + dict_, + _string_constraints.create_check_function(min_length=1, max_length=18), + ) class Key: @@ -432,21 +488,19 @@ class Key: :ivar value: The key value, for example an IRDI or IRI """ - def __init__(self, - type_: KeyTypes, - value: Identifier): + def __init__(self, type_: KeyTypes, value: Identifier): """ TODO: Add instruction what to do after construction """ _string_constraints.check_identifier(value) self.type: KeyTypes self.value: Identifier - super().__setattr__('type', type_) - super().__setattr__('value', value) + super().__setattr__("type", type_) + super().__setattr__("value", value) def __setattr__(self, key, value): """Prevent modification of attributes.""" - raise AttributeError('Reference is immutable') + raise AttributeError("Reference is immutable") def __repr__(self) -> str: return "Key(type={}, value={})".format(self.type.name, self.value) @@ -457,8 +511,7 @@ def __str__(self) -> str: def __eq__(self, other: object) -> bool: if not isinstance(other, Key): return NotImplemented - return (self.value == other.value - and self.type == other.type) + return self.value == other.value and self.type == other.type def __hash__(self): return hash((self.value, self.type)) @@ -490,6 +543,7 @@ def from_referable(referable: "Referable") -> "Key": @staticmethod def _get_key_type_for_referable(referable: "Referable") -> KeyTypes: from . import KEY_TYPES_CLASSES, resolve_referable_class_in_key_types + ref_type = resolve_referable_class_in_key_types(referable) key_type = KEY_TYPES_CLASSES[ref_type] return key_type @@ -497,20 +551,27 @@ def _get_key_type_for_referable(referable: "Referable") -> KeyTypes: @staticmethod def _get_key_value_for_referable(referable: "Referable") -> str: from . import SubmodelElementList + if isinstance(referable, Identifiable): return referable.id elif isinstance(referable.parent, SubmodelElementList): try: return str(referable.parent.value.index(referable)) # type: ignore except ValueError as e: - raise ValueError(f"Object {referable!r} is not contained within its parent {referable.parent!r}") from e + raise ValueError( + f"Object {referable!r} is not contained within its parent {referable.parent!r}" + ) from e else: if referable.id_short is None: - raise ValueError(f"Can't create Key value for {referable!r} without an id_short!") + raise ValueError( + f"Can't create Key value for {referable!r} without an id_short!" + ) return referable.id_short -_NSO = TypeVar('_NSO', bound=Union["Referable", "Qualifier", "HasSemantics", "Extension"]) +_NSO = TypeVar( + "_NSO", bound=Union["Referable", "Qualifier", "HasSemantics", "Extension"] +) class Namespace(metaclass=abc.ABCMeta): @@ -522,12 +583,15 @@ class Namespace(metaclass=abc.ABCMeta): :ivar namespace_element_sets: List of :class:`NamespaceSets ` """ + @abc.abstractmethod def __init__(self) -> None: super().__init__() self.namespace_element_sets: List[NamespaceSet] = [] - def _get_object(self, object_type: Type[_NSO], attribute_name: str, attribute) -> _NSO: + def _get_object( + self, object_type: Type[_NSO], attribute_name: str, attribute + ) -> _NSO: """ Find an :class:`~._NSO` in this namespace by its attribute @@ -538,7 +602,9 @@ def _get_object(self, object_type: Type[_NSO], attribute_name: str, attribute) - return ns_set.get_object_by_attribute(attribute_name, attribute) except KeyError: continue - raise KeyError(f"{object_type.__name__} with {attribute_name} {attribute} not found in {self!r}") + raise KeyError( + f"{object_type.__name__} with {attribute_name} {attribute} not found in {self!r}" + ) def _add_object(self, attribute_name: str, obj: _NSO) -> None: """ @@ -566,7 +632,9 @@ def _remove_object(self, object_type: type, attribute_name: str, attribute) -> N return except KeyError: continue - raise KeyError(f"{object_type.__name__} with {attribute_name} {attribute} not found in {self!r}") + raise KeyError( + f"{object_type.__name__} with {attribute_name} {attribute} not found in {self!r}" + ) class HasExtension(Namespace, metaclass=abc.ABCMeta): @@ -581,6 +649,7 @@ class HasExtension(Namespace, metaclass=abc.ABCMeta): :ivar namespace_element_sets: List of :class:`NamespaceSets ` :ivar extension: A :class:`~.NamespaceSet` of :class:`Extensions <.Extension>` of the element. """ + @abc.abstractmethod def __init__(self) -> None: super().__init__() @@ -639,6 +708,7 @@ class Referable(HasExtension, metaclass=abc.ABCMeta): :ivar parent: Reference (in form of a :class:`~.UniqueIdShortNamespace`) to the next referable parent element of the element. """ + @abc.abstractmethod def __init__(self): super().__init__() @@ -661,7 +731,9 @@ def __repr__(self) -> str: if root is None: item_path = f"[{id_short_path}]" if id_short_path else "" else: - item_path = f"[{root.id} / {id_short_path}]" if id_short_path else f"[{root.id}]" + item_path = ( + f"[{root.id} / {id_short_path}]" if id_short_path else f"[{root.id}]" + ) return f"{item_cls_name}{item_path}" @@ -678,8 +750,10 @@ def get_identifiable_root(self) -> Optional["Identifiable"]: elif isinstance(item, Referable): item = item.parent else: - raise AttributeError('Referable must have an identifiable as root object and only parents that are ' - 'referable') + raise AttributeError( + "Referable must have an identifiable as root object and only parents that are " + "referable" + ) return None def get_id_short_path(self) -> str: @@ -702,16 +776,21 @@ def get_id_short_path_as_a_list(self) -> List[str]: :class:`~.Identifiable` """ from .submodel import SubmodelElementList + if self.id_short is None and not isinstance(self.parent, SubmodelElementList): - raise ValueError(f"Can't create id_short_path for {self.__class__.__name__} without an id_short or " - f"if its parent is a SubmodelElementList!") + raise ValueError( + f"Can't create id_short_path for {self.__class__.__name__} without an id_short or " + f"if its parent is a SubmodelElementList!" + ) item = self # type: Any path: List[str] = [] while item is not None: if not isinstance(item, Referable): - raise AttributeError('Referable must have an identifiable as root object and only parents that are ' - 'referable') + raise AttributeError( + "Referable must have an identifiable as root object and only parents that are " + "referable" + ) if isinstance(item, Identifiable): break elif isinstance(item.parent, SubmodelElementList): @@ -749,13 +828,15 @@ def parse_id_short_path(cls, id_short_path: str) -> List[str]: """ id_shorts_and_indexes = [] for part in id_short_path.split("."): - id_short = part[0:part.find('[')] if '[' in part else part + id_short = part[0 : part.find("[")] if "[" in part else part id_shorts_and_indexes.append(id_short) indexes_part = part.removeprefix(id_short) if indexes_part: - if not re.fullmatch(r'(?:\[\d+\])+', indexes_part): - raise ValueError(f"Invalid index format in id_short_path: '{id_short_path}', part: '{part}'") + if not re.fullmatch(r"(?:\[\d+\])+", indexes_part): + raise ValueError( + f"Invalid index format in id_short_path: '{id_short_path}', part: '{part}'" + ) indexes = indexes_part.strip("[]").split("][") id_shorts_and_indexes.extend(indexes) cls.validate_id_short_path(id_shorts_and_indexes) @@ -767,13 +848,19 @@ def build_id_short_path(cls, id_short_path: Iterable[str]) -> str: Build an id_short_path string from a list of id_shorts and indexes. """ if isinstance(id_short_path, str): - raise ValueError("id_short_path must be an Iterable of strings, not a single string") - path_list_with_dots_and_brackets = [f"[{part}]" if part.isdigit() else f".{part}" for part in id_short_path] + raise ValueError( + "id_short_path must be an Iterable of strings, not a single string" + ) + path_list_with_dots_and_brackets = [ + f"[{part}]" if part.isdigit() else f".{part}" for part in id_short_path + ] id_short_path = "".join(path_list_with_dots_and_brackets).removeprefix(".") return id_short_path @classmethod - def validate_id_short_path(cls, id_short_path: Union[str, NameType, Iterable[NameType]]): + def validate_id_short_path( + cls, id_short_path: Union[str, NameType, Iterable[NameType]] + ): if isinstance(id_short_path, str): id_short_path = cls.parse_id_short_path(id_short_path) for id_short in id_short_path: @@ -801,18 +888,12 @@ def validate_id_short(cls, id_short: NameType) -> None: if not re.fullmatch("[A-Za-z0-9_-]*", test_id_short): raise AASConstraintViolation( 2, - "The id_short must contain only letters, digits underscore and hyphen" + "The id_short must contain only letters, digits underscore and hyphen", ) if not test_id_short[0].isalpha(): - raise AASConstraintViolation( - 2, - "The id_short must start with a letter" - ) + raise AASConstraintViolation(2, "The id_short must start with a letter") if test_id_short.endswith("-"): - raise AASConstraintViolation( - 2, - "The id_short must not end with a hyphen" - ) + raise AASConstraintViolation(2, "The id_short must not end with a hyphen") category = property(_get_category, _set_category) @@ -841,13 +922,20 @@ def _set_id_short(self, id_short: Optional[NameType]): if self.parent is not None: 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}") + 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 + 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 " - "Namespace".format(id_short)) + raise AASConstraintViolation( + 22, + "Object with id_short '{}' is already present in the parent " + "Namespace".format(id_short), + ) set_add_list: List[NamespaceSet] = [] for set_ in self.parent.namespace_element_sets: @@ -893,7 +981,7 @@ def update_from(self, other: "Referable"): """ for name in dir(other): # Skip private and protected attributes - if name.startswith('_'): + if name.startswith("_"): continue # Do not update 'parent', 'namespace_element_sets' @@ -913,14 +1001,16 @@ def update_from(self, other: "Referable"): prop = getattr(type(self), name, None) if isinstance(prop, property) and prop.fset is None: if getattr(self, name) != attr: - raise ValueError(f"property {name} is immutable but has changed between versions of the object") + raise ValueError( + f"property {name} is immutable but has changed between versions of the object" + ) else: setattr(self, name, attr) id_short = property(_get_id_short, _set_id_short) -_RT = TypeVar('_RT', bound=Referable) +_RT = TypeVar("_RT", bound=Referable) class UnexpectedTypeError(TypeError): @@ -930,6 +1020,7 @@ class UnexpectedTypeError(TypeError): :ivar value: The object of unexpected type """ + def __init__(self, value: Referable, *args): super().__init__(*args) self.value = value @@ -957,8 +1048,11 @@ class Reference(metaclass=abc.ABCMeta): :ivar referred_semantic_id: SemanticId of the referenced model element. For external references there typically is no semantic id. """ + @abc.abstractmethod - def __init__(self, key: Tuple[Key, ...], referred_semantic_id: Optional["Reference"] = None): + def __init__( + self, key: Tuple[Key, ...], referred_semantic_id: Optional["Reference"] = None + ): if len(key) < 1: raise ValueError("A reference must have at least one key!") @@ -966,12 +1060,12 @@ def __init__(self, key: Tuple[Key, ...], referred_semantic_id: Optional["Referen self.key: Tuple[Key, ...] self.referred_semantic_id: Optional["Reference"] - super().__setattr__('key', key) - super().__setattr__('referred_semantic_id', referred_semantic_id) + super().__setattr__("key", key) + super().__setattr__("referred_semantic_id", referred_semantic_id) def __setattr__(self, key, value): """Prevent modification of attributes.""" - raise AttributeError('Reference is immutable') + raise AttributeError("Reference is immutable") def __hash__(self): return hash((self.__class__, self.key)) @@ -981,8 +1075,10 @@ def __eq__(self, other: object) -> bool: return NotImplemented if len(self.key) != len(other.key): return False - return all(k1 == k2 for k1, k2 in zip(self.key, other.key)) \ + return ( + all(k1 == k2 for k1, k2 in zip(self.key, other.key)) and self.referred_semantic_id == other.referred_semantic_id + ) class ExternalReference(Reference): @@ -1006,15 +1102,26 @@ class ExternalReference(Reference): no semantic id. """ - def __init__(self, key: Tuple[Key, ...], referred_semantic_id: Optional["Reference"] = None): + def __init__( + self, key: Tuple[Key, ...], referred_semantic_id: Optional["Reference"] = None + ): super().__init__(key, referred_semantic_id) if not key[0].type.is_generic_globally_identifiable: - raise AASConstraintViolation(122, "The type of the first key of an ExternalReference must be a " - f"GenericGloballyIdentifiable: {key[0]!r}") - if not key[-1].type.is_generic_globally_identifiable and not key[-1].type.is_generic_fragment_key: - raise AASConstraintViolation(124, "The type of the last key of an ExternalReference must be a " - f"GenericGloballyIdentifiable or a GenericFragmentKey: {key[-1]!r}") + raise AASConstraintViolation( + 122, + "The type of the first key of an ExternalReference must be a " + f"GenericGloballyIdentifiable: {key[0]!r}", + ) + if ( + not key[-1].type.is_generic_globally_identifiable + and not key[-1].type.is_generic_fragment_key + ): + raise AASConstraintViolation( + 124, + "The type of the last key of an ExternalReference must be a " + f"GenericGloballyIdentifiable or a GenericFragmentKey: {key[-1]!r}", + ) def __repr__(self) -> str: return "ExternalReference(key={})".format(self.key) @@ -1054,31 +1161,55 @@ class ModelReference(Reference, Generic[_RT]): :ivar referred_semantic_id: SemanticId of the referenced model element. For external references there typically is no semantic id. """ - def __init__(self, key: Tuple[Key, ...], type_: Type[_RT], referred_semantic_id: Optional[Reference] = None): + + def __init__( + self, + key: Tuple[Key, ...], + type_: Type[_RT], + referred_semantic_id: Optional[Reference] = None, + ): super().__init__(key, referred_semantic_id) if not key[0].type.is_aas_identifiable: - raise AASConstraintViolation(123, "The type of the first key of a ModelReference must be an " - f"AasIdentifiable: {key[0]!r}") + raise AASConstraintViolation( + 123, + "The type of the first key of a ModelReference must be an " + f"AasIdentifiable: {key[0]!r}", + ) for k in key[1:]: if not k.type.is_fragment_key_element: - raise AASConstraintViolation(125, "The type of all keys following the first of a ModelReference " - f"must be one of FragmentKeyElements: {k!r}") + raise AASConstraintViolation( + 125, + "The type of all keys following the first of a ModelReference " + f"must be one of FragmentKeyElements: {k!r}", + ) if not key[-1].type.is_generic_fragment_key: for k in key[:-1]: if k.type.is_generic_fragment_key: - raise AASConstraintViolation(126, f"Key {k!r} is a GenericFragmentKey, " - f"but the last key of the chain is not: {key[-1]!r}") + raise AASConstraintViolation( + 126, + f"Key {k!r} is a GenericFragmentKey, " + f"but the last key of the chain is not: {key[-1]!r}", + ) for pk, k in zip(key, key[1:]): - if k.type == KeyTypes.FRAGMENT_REFERENCE and pk.type not in (KeyTypes.BLOB, KeyTypes.FILE): - raise AASConstraintViolation(127, f"{k!r} is not preceded by a key of type File or Blob, but {pk!r}") + if k.type == KeyTypes.FRAGMENT_REFERENCE and pk.type not in ( + KeyTypes.BLOB, + KeyTypes.FILE, + ): + raise AASConstraintViolation( + 127, + f"{k!r} is not preceded by a key of type File or Blob, but {pk!r}", + ) if pk.type == KeyTypes.SUBMODEL_ELEMENT_LIST and not k.value.isnumeric(): - raise AASConstraintViolation(128, f"Key {pk!r} references a SubmodelElementList, " - f"but the value of the succeeding key ({k!r}) is not a non-negative " - f"integer: {k.value}") + raise AASConstraintViolation( + 128, + f"Key {pk!r} references a SubmodelElementList, " + f"but the value of the succeeding key ({k!r}) is not a non-negative " + f"integer: {k.value}", + ) self.type: Type[_RT] - object.__setattr__(self, 'type', type_) + object.__setattr__(self, "type", type_) def resolve(self, provider_: "provider.AbstractObjectProvider") -> _RT: """ @@ -1099,7 +1230,9 @@ def resolve(self, provider_: "provider.AbstractObjectProvider") -> _RT: # For ModelReferences, the first key must be an AasIdentifiable. So resolve the first key via the provider. identifier: Optional[Identifier] = self.key[0].get_identifier() if identifier is None: - raise AssertionError(f"Retrieving the identifier of the first {self.key[0]!r} failed.") + raise AssertionError( + f"Retrieving the identifier of the first {self.key[0]!r} failed." + ) try: item: Referable = provider_.get_item(identifier) @@ -1110,13 +1243,19 @@ def resolve(self, provider_: "provider.AbstractObjectProvider") -> _RT: # id_short path via get_referable(). # This is cursed af, but at least it keeps the code DRY. get_referable() will check the type of self in the # first iteration, so we can ignore the type here. - item = UniqueIdShortNamespace.get_referable(item, # type: ignore[arg-type] - map(lambda k: k.value, self.key[1:])) + item = UniqueIdShortNamespace.get_referable( + item, # type: ignore[arg-type] + map(lambda k: k.value, self.key[1:]), + ) # Check type if not isinstance(item, self.type): - raise UnexpectedTypeError(item, "Retrieved object {} is not an instance of referenced type {}" - .format(item, self.type.__name__)) + raise UnexpectedTypeError( + item, + "Retrieved object {} is not an instance of referenced type {}".format( + item, self.type.__name__ + ), + ) return item def get_identifier(self) -> Identifier: @@ -1128,13 +1267,17 @@ def get_identifier(self) -> Identifier: :raises ValueError: If this :class:`~.ModelReference` does not include a Key of AasIdentifiable type """ try: - last_identifier = next(key.get_identifier() - for key in reversed(self.key) - if key.get_identifier()) + last_identifier = next( + key.get_identifier() + for key in reversed(self.key) + if key.get_identifier() + ) return last_identifier # type: ignore # MyPy doesn't get the generator expression above except StopIteration: - raise ValueError("ModelReference cannot be represented as an Identifier, since it does not contain a Key" - f" of an AasIdentifiable type ({[t.name for t in KeyTypes if t.is_aas_identifiable]})") + raise ValueError( + "ModelReference cannot be represented as an Identifier, since it does not contain a Key" + f" of an AasIdentifiable type ({[t.name for t in KeyTypes if t.is_aas_identifiable]})" + ) def __repr__(self) -> str: return "ModelReference<{}>(key={})".format(self.type.__name__, self.key) @@ -1157,6 +1300,7 @@ def from_referable(referable: Referable) -> "ModelReference": """ # Get the first class from the base classes list (via inspect.getmro), that is contained in KEY_ELEMENTS_CLASSES from . import resolve_referable_class_in_key_types + try: ref_type = resolve_referable_class_in_key_types(referable) except StopIteration: @@ -1169,11 +1313,15 @@ def from_referable(referable: Referable) -> "ModelReference": if isinstance(ref, Identifiable): return ModelReference(tuple(keys), ref_type) if ref.parent is None or not isinstance(ref.parent, Referable): - raise ValueError(f"The given Referable object is not embedded within an Identifiable object: {ref}") + raise ValueError( + f"The given Referable object is not embedded within an Identifiable object: {ref}" + ) ref = ref.parent if len(keys) > MAX_RECURSION_DEPTH: - raise ValueError(f"The given Referable object is embedded in >64 layers of Referables " - f"or there is a loop in the parent chain {ref}") + raise ValueError( + f"The given Referable object is embedded in >64 layers of Referables " + f"or there is a loop in the parent chain {ref}" + ) @_string_constraints.constrain_content_type("content_type") @@ -1187,6 +1335,7 @@ class Resource: :ivar content_type: Content type of the content of the file. The content type states which file extensions the file can have. """ + def __init__(self, path: PathType, content_type: Optional[ContentType] = None): self.path: PathType = path self.content_type: Optional[ContentType] = content_type @@ -1206,6 +1355,7 @@ class DataSpecificationContent: shall contain the external reference to the IRI of the corresponding data specification template ``https://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/1`` """ + @abc.abstractmethod def __init__(self): pass @@ -1218,13 +1368,16 @@ class EmbeddedDataSpecification: :ivar data_specification: Reference to the data specification :ivar data_specification_content: Actual content of the data specification """ + def __init__( self, data_specification: Reference, data_specification_content: DataSpecificationContent, ) -> None: self.data_specification: Reference = data_specification - self.data_specification_content: DataSpecificationContent = data_specification_content + self.data_specification_content: DataSpecificationContent = ( + data_specification_content + ) def __repr__(self): return f"EmbeddedDataSpecification[{self.data_specification}]" @@ -1243,6 +1396,7 @@ class HasDataSpecification(metaclass=abc.ABCMeta): :ivar embedded_data_specifications: List of :class:`~.EmbeddedDataSpecification`. """ + @abc.abstractmethod def __init__( self, @@ -1281,12 +1435,14 @@ class AdministrativeInformation(HasDataSpecification): The creation of submodel templates can also be guided by another submodel template. """ - def __init__(self, - version: Optional[VersionType] = None, - revision: Optional[RevisionType] = None, - creator: Optional[Reference] = None, - template_id: Optional[Identifier] = None, - embedded_data_specifications: Iterable[EmbeddedDataSpecification] = ()): + def __init__( + self, + version: Optional[VersionType] = None, + revision: Optional[RevisionType] = None, + creator: Optional[Reference] = None, + template_id: Optional[Identifier] = None, + embedded_data_specifications: Iterable[EmbeddedDataSpecification] = (), + ): """ Initializer of AdministrativeInformation @@ -1300,15 +1456,20 @@ def __init__(self, self.revision = revision self.creator: Optional[Reference] = creator self.template_id: Optional[Identifier] = template_id - self.embedded_data_specifications: List[EmbeddedDataSpecification] = list(embedded_data_specifications) + self.embedded_data_specifications: List[EmbeddedDataSpecification] = list( + embedded_data_specifications + ) def _get_revision(self): return self._revision def _set_revision(self, revision: Optional[RevisionType]): if self.version is None and revision: - raise AASConstraintViolation(5, "A revision requires a version. This means, if there is no version " - "there is no revision neither. Please set version first.") + raise AASConstraintViolation( + 5, + "A revision requires a version. This means, if there is no version " + "there is no revision neither. Please set version first.", + ) if revision is not None: _string_constraints.check_revision_type(revision) self._revision = revision @@ -1318,14 +1479,17 @@ def _set_revision(self, revision: Optional[RevisionType]): def __eq__(self, other) -> bool: if not isinstance(other, AdministrativeInformation): return NotImplemented - return self.version == other.version \ - and self._revision == other._revision \ - and self.creator == other.creator \ + return ( + self.version == other.version + and self._revision == other._revision + and self.creator == other.creator and self.template_id == other.template_id + ) def __repr__(self) -> str: return "AdministrativeInformation(version={}, revision={}, creator={}, template_id={})".format( - self.version, self.revision, self.creator, self.template_id) + self.version, self.revision, self.creator, self.template_id + ) @_string_constraints.constrain_identifier("id") @@ -1339,6 +1503,7 @@ class Identifiable(Referable, metaclass=abc.ABCMeta): :ivar administration: :class:`~.AdministrativeInformation` of an identifiable element. :ivar id: The globally unique id of the element. """ + @abc.abstractmethod def __init__(self) -> None: super().__init__() @@ -1371,13 +1536,19 @@ class ConstrainedList(MutableSequence[_T], Generic[_T]): or ``del list[i]``. It is passed the item about to be deleted and the current list elements. """ - def __init__(self, items: Iterable[_T], item_add_hook: Optional[Callable[[_T, List[_T]], None]] = None, - item_set_hook: Optional[Callable[[List[_T], List[_T], List[_T]], None]] = None, - item_del_hook: Optional[Callable[[_T, List[_T]], None]] = None) -> None: + def __init__( + self, + items: Iterable[_T], + item_add_hook: Optional[Callable[[_T, List[_T]], None]] = None, + item_set_hook: Optional[Callable[[List[_T], List[_T], List[_T]], None]] = None, + item_del_hook: Optional[Callable[[_T, List[_T]], None]] = None, + ) -> None: super().__init__() self._list: List[_T] = [] self._item_add_hook: Optional[Callable[[_T, List[_T]], None]] = item_add_hook - self._item_set_hook: Optional[Callable[[List[_T], List[_T], List[_T]], None]] = item_set_hook + self._item_set_hook: Optional[ + Callable[[List[_T], List[_T], List[_T]], None] + ] = item_set_hook self._item_del_hook: Optional[Callable[[_T, List[_T]], None]] = item_del_hook self.extend(items) @@ -1412,7 +1583,9 @@ def __setitem__(self, index: int, value: _T) -> None: ... @overload def __setitem__(self, index: slice, value: Iterable[_T]) -> None: ... - def __setitem__(self, index: Union[int, slice], value: Union[_T, Iterable[_T]]) -> None: + def __setitem__( + self, index: Union[int, slice], value: Union[_T, Iterable[_T]] + ) -> None: # TODO: remove the following type: ignore once mypy supports type narrowing using overload information # https://github.com/python/mypy/issues/4063 if isinstance(index, int): @@ -1472,6 +1645,7 @@ class HasSemantics(metaclass=abc.ABCMeta): :ivar supplemental_semantic_id: Identifier of a supplemental semantic definition of the element. It is called supplemental semantic ID of the element. """ + @abc.abstractmethod def __init__(self) -> None: super().__init__() @@ -1479,12 +1653,16 @@ def __init__(self) -> None: # of Referable.parent as `UniqueIdShortNamespace` self.parent: Optional[Any] = None self._supplemental_semantic_id: ConstrainedList[Reference] = ConstrainedList( - [], item_add_hook=self._check_constraint_add) + [], item_add_hook=self._check_constraint_add + ) self._semantic_id: Optional[Reference] = None def _check_constraint_add(self, _new: Reference, _list: List[Reference]) -> None: if self.semantic_id is None: - raise AASConstraintViolation(118, "A semantic_id must be defined before adding a supplemental_semantic_id!") + raise AASConstraintViolation( + 118, + "A semantic_id must be defined before adding a supplemental_semantic_id!", + ) @property def semantic_id(self) -> Optional[Reference]: @@ -1493,13 +1671,18 @@ def semantic_id(self) -> Optional[Reference]: @semantic_id.setter def semantic_id(self, semantic_id: Optional[Reference]) -> None: if semantic_id is None and len(self.supplemental_semantic_id) > 0: - raise AASConstraintViolation(118, "semantic_id can not be removed while there is at least one " - f"supplemental_semantic_id: {self.supplemental_semantic_id!r}") + raise AASConstraintViolation( + 118, + "semantic_id can not be removed while there is at least one " + f"supplemental_semantic_id: {self.supplemental_semantic_id!r}", + ) if self.parent is not None: if semantic_id is not None: for set_ in self.parent.namespace_element_sets: if set_.contains_id("semantic_id", semantic_id): - raise KeyError("Object with semantic_id is already present in the parent Namespace") + raise KeyError( + "Object with semantic_id is already present in the parent Namespace" + ) set_add_list: List[NamespaceSet] = [] for set_ in self.parent.namespace_element_sets: if self in set_: @@ -1534,13 +1717,15 @@ class Extension(HasSemantics): :class:`~basyx.aas.model.base.HasSemantics`) """ - def __init__(self, - name: NameType, - value_type: Optional[DataTypeDefXsd] = None, - value: Optional[ValueDataType] = None, - refers_to: Iterable[ModelReference] = (), - semantic_id: Optional[Reference] = None, - supplemental_semantic_id: Iterable[Reference] = ()): + def __init__( + self, + name: NameType, + value_type: Optional[DataTypeDefXsd] = None, + value: Optional[ValueDataType] = None, + refers_to: Iterable[ModelReference] = (), + semantic_id: Optional[Reference] = None, + supplemental_semantic_id: Iterable[Reference] = (), + ): super().__init__() self.parent: Optional[HasExtension] = None self._name: NameType @@ -1565,7 +1750,7 @@ def value(self, value) -> None: self._value = None else: if self.value_type is None: - raise ValueError('ValueType must be set, if value is not None') + raise ValueError("ValueType must be set, if value is not None") self._value = datatypes.trivial_cast(value, self.value_type) @property @@ -1578,8 +1763,11 @@ def name(self, name: NameType) -> None: if self.parent is not None: for set_ in self.parent.namespace_element_sets: if set_.contains_id("name", name): - raise KeyError("Object with name '{}' is already present in the parent Namespace" - .format(name)) + raise KeyError( + "Object with name '{}' is already present in the parent Namespace".format( + name + ) + ) set_add_list: List[NamespaceSet] = [] for set_ in self.parent.namespace_element_sets: if self in set_: @@ -1601,6 +1789,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) -> None: super().__init__() @@ -1626,6 +1815,7 @@ class Qualifiable(Namespace, metaclass=abc.ABCMeta): :ivar qualifier: Unordered list of :class:`Qualifiers ` that gives additional qualification of a qualifiable element. """ + @abc.abstractmethod def __init__(self) -> None: super().__init__() @@ -1679,14 +1869,16 @@ class Qualifier(HasSemantics): :class:`~basyx.aas.model.base.HasSemantics`) """ - def __init__(self, - type_: QualifierType, - value_type: DataTypeDefXsd, - value: Optional[ValueDataType] = None, - value_id: Optional[Reference] = None, - kind: QualifierKind = QualifierKind.CONCEPT_QUALIFIER, - semantic_id: Optional[Reference] = None, - supplemental_semantic_id: Iterable[Reference] = ()): + def __init__( + self, + type_: QualifierType, + value_type: DataTypeDefXsd, + value: Optional[ValueDataType] = None, + value_id: Optional[Reference] = None, + kind: QualifierKind = QualifierKind.CONCEPT_QUALIFIER, + semantic_id: Optional[Reference] = None, + supplemental_semantic_id: Iterable[Reference] = (), + ): """ TODO: Add instruction what to do after construction """ @@ -1695,7 +1887,9 @@ def __init__(self, self._type: QualifierType self.type: QualifierType = type_ self.value_type: DataTypeDefXsd = value_type - self._value: Optional[ValueDataType] = datatypes.trivial_cast(value, value_type) if value is not None else None + self._value: Optional[ValueDataType] = ( + datatypes.trivial_cast(value, value_type) if value is not None else None + ) self.value_id: Optional[Reference] = value_id self.kind: QualifierKind = kind self.semantic_id: Optional[Reference] = semantic_id @@ -1725,8 +1919,11 @@ def type(self, type_: QualifierType) -> None: if self.parent is not None: for set_ in self.parent.namespace_element_sets: if set_.contains_id("type", type_): - raise KeyError("Object with type '{}' is already present in the parent Namespace" - .format(type_)) + raise KeyError( + "Object with type '{}' is already present in the parent Namespace".format( + type_ + ) + ) set_add_list: List[NamespaceSet] = [] for set_ in self.parent.namespace_element_sets: if self in set_: @@ -1750,9 +1947,7 @@ class ValueReferencePair: :ivar value_id: Global unique id of the value. """ - def __init__(self, - value: ValueTypeIEC61360, - value_id: Optional[Reference] = None): + def __init__(self, value: ValueTypeIEC61360, value_id: Optional[Reference] = None): """ @@ -1762,7 +1957,9 @@ def __init__(self, self.value_id: Optional[Reference] = value_id def __repr__(self) -> str: - return "ValueReferencePair(value={}, value_id={})".format(self.value, self.value_id) + return "ValueReferencePair(value={}, value_id={})".format( + self.value, self.value_id + ) class UniqueIdShortNamespace(Namespace, metaclass=abc.ABCMeta): @@ -1777,12 +1974,15 @@ class UniqueIdShortNamespace(Namespace, metaclass=abc.ABCMeta): :ivar namespace_element_sets: A list of all :class:`NamespaceSets <.NamespaceSet>` of this Namespace """ + @abc.abstractmethod def __init__(self) -> None: super().__init__() self.namespace_element_sets: List[NamespaceSet] = [] - def get_referable(self, id_short_path: Union[str, NameType, Iterable[NameType]]) -> Referable: + def get_referable( + self, id_short_path: Union[str, NameType, Iterable[NameType]] + ) -> Referable: """ Find a :class:`~.Referable` in this Namespace by its id_short or by its id_short path. The id_short path may contain :class:`~basyx.aas.model.submodel.SubmodelElementList` indices. @@ -1796,6 +1996,7 @@ def get_referable(self, id_short_path: Union[str, NameType, Iterable[NameType]]) :raises KeyError: If no such :class:`~.Referable` can be found """ from .submodel import SubmodelElementList + if isinstance(id_short_path, (str, NameType)): id_short_path = Referable.parse_id_short_path(id_short_path) item: Union[UniqueIdShortNamespace, Referable] = self @@ -1803,8 +2004,10 @@ def get_referable(self, id_short_path: Union[str, NameType, Iterable[NameType]]) # This is redundant on first iteration, but it's a negligible overhead. # Also, ModelReference.resolve() relies on this check. if not isinstance(item, UniqueIdShortNamespace): - raise TypeError(f"Cannot resolve id_short or index '{id_}' at {item!r}, " - f"because it is not a {UniqueIdShortNamespace.__name__}!") + raise TypeError( + f"Cannot resolve id_short or index '{id_}' at {item!r}, " + f"because it is not a {UniqueIdShortNamespace.__name__}!" + ) is_submodel_element_list = isinstance(item, SubmodelElementList) try: if is_submodel_element_list: @@ -1815,10 +2018,17 @@ def get_referable(self, id_short_path: Union[str, NameType, Iterable[NameType]]) else: item = item._get_object(Referable, "id_short", id_) # type: ignore[type-abstract] except ValueError as e: - raise ValueError(f"Cannot resolve '{id_}' at {item!r}, because it is not a numeric index!") from e + raise ValueError( + f"Cannot resolve '{id_}' at {item!r}, because it is not a numeric index!" + ) from e except (KeyError, IndexError) as e: - raise KeyError("Referable with {} {} not found in {}".format( - "index" if is_submodel_element_list else "id_short", id_, repr(item))) from e + raise KeyError( + "Referable with {} {} not found in {}".format( + "index" if is_submodel_element_list else "id_short", + id_, + repr(item), + ) + ) from e # All UniqueIdShortNamespaces are Referables, and we only ever assign Referable to item. return item # type: ignore[return-value] @@ -1862,6 +2072,7 @@ class UniqueSemanticIdNamespace(Namespace, metaclass=abc.ABCMeta): :ivar namespace_element_sets: A list of all NamespaceSets of this Namespace """ + @abc.abstractmethod def __init__(self) -> None: super().__init__() @@ -1919,11 +2130,18 @@ class NamespaceSet(MutableSet[_NSO], Generic[_NSO]): :raises KeyError: When ``items`` contains multiple objects with same unique attribute """ - def __init__(self, parent: Union[UniqueIdShortNamespace, UniqueSemanticIdNamespace, Qualifiable, HasExtension], - attribute_names: List[Tuple[str, bool]], items: Iterable[_NSO] = (), - item_add_hook: Optional[Callable[[_NSO, Iterable[_NSO]], None]] = None, - item_id_set_hook: Optional[Callable[[_NSO], None]] = None, - item_id_del_hook: Optional[Callable[[_NSO], None]] = None) -> None: + + def __init__( + self, + parent: Union[ + UniqueIdShortNamespace, UniqueSemanticIdNamespace, Qualifiable, HasExtension + ], + attribute_names: List[Tuple[str, bool]], + items: Iterable[_NSO] = (), + item_add_hook: Optional[Callable[[_NSO, Iterable[_NSO]], None]] = None, + item_id_set_hook: Optional[Callable[[_NSO], None]] = None, + item_id_del_hook: Optional[Callable[[_NSO], None]] = None, + ) -> None: """ Initialize a new NamespaceSet. @@ -1948,7 +2166,9 @@ def __init__(self, parent: Union[UniqueIdShortNamespace, UniqueSemanticIdNamespa self.parent = parent parent.namespace_element_sets.append(self) self._backend: Dict[str, Tuple[Dict[ATTRIBUTE_TYPES, _NSO], bool]] = {} - self._item_add_hook: Optional[Callable[[_NSO, Iterable[_NSO]], None]] = item_add_hook + self._item_add_hook: Optional[Callable[[_NSO, Iterable[_NSO]], None]] = ( + item_add_hook + ) self._item_id_set_hook: Optional[Callable[[_NSO], None]] = item_id_set_hook self._item_id_del_hook: Optional[Callable[[_NSO], None]] = item_id_del_hook for name, case_sensitive in attribute_names: @@ -1964,7 +2184,11 @@ def __init__(self, parent: Union[UniqueIdShortNamespace, UniqueSemanticIdNamespa @staticmethod def _get_attribute(x: object, attr_name: str, case_sensitive: bool): attr_value = getattr(x, attr_name) - return attr_value if case_sensitive or not isinstance(attr_value, str) else attr_value.upper() + return ( + attr_value + if case_sensitive or not isinstance(attr_value, str) + else attr_value.upper() + ) def get_attribute_name_list(self) -> List[str]: return list(self._backend.keys()) @@ -1982,7 +2206,9 @@ def contains_id(self, attribute_name: str, identifier: ATTRIBUTE_TYPES) -> bool: def __contains__(self, obj: object) -> bool: attr_name = next(iter(self._backend)) try: - attr_value = self._get_attribute(obj, attr_name, self._backend[attr_name][1]) + attr_value = self._get_attribute( + obj, attr_name, self._backend[attr_name][1] + ) except AttributeError: return False return self._backend[attr_name][0].get(attr_value) is obj @@ -1995,7 +2221,9 @@ def __iter__(self) -> Iterator[_NSO]: 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.") + raise ValueError( + "Object has already a parent; it cannot belong to two namespaces." + ) # TODO remove from current parent instead (allow moving)? self._execute_item_id_set_hook(element) @@ -2004,34 +2232,57 @@ def add(self, element: _NSO): 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 + 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 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) + 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_) + 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__}!") + 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!") + 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"): + 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" + 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) + 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: @@ -2090,7 +2341,9 @@ def clear(self) -> None: for attr_name, (backend, case_sensitive) in self._backend.items(): backend.clear() - def get_object_by_attribute(self, attribute_name: str, attribute_value: ATTRIBUTE_TYPES) -> _NSO: + def get_object_by_attribute( + self, attribute_name: str, attribute_value: ATTRIBUTE_TYPES + ) -> _NSO: """ Find an object in this set by its unique attribute @@ -2099,7 +2352,9 @@ def get_object_by_attribute(self, attribute_name: str, attribute_value: ATTRIBUT backend, case_sensitive = self._backend[attribute_name] return backend[attribute_value if case_sensitive else attribute_value.upper()] # type: ignore - def get(self, attribute_name: str, attribute_value: str, default: Optional[_NSO] = None) -> Optional[_NSO]: + def get( + self, attribute_name: str, attribute_value: str, default: Optional[_NSO] = None + ) -> Optional[_NSO]: """ Find an object in this set by its attribute, with fallback parameter @@ -2110,7 +2365,9 @@ def get(self, attribute_name: str, attribute_value: str, default: Optional[_NSO] none is given. """ backend, case_sensitive = self._backend[attribute_name] - return backend.get(attribute_value if case_sensitive else attribute_value.upper(), default) + return backend.get( + attribute_value if case_sensitive else attribute_value.upper(), default + ) # Todo: Implement function including tests def update_nss_from(self, other: "NamespaceSet"): @@ -2127,15 +2384,27 @@ def update_nss_from(self, other: "NamespaceSet"): try: if isinstance(other_object, Referable): backend, case_sensitive = self._backend["id_short"] - referable = backend[other_object.id_short if case_sensitive else other_object.id_short.upper()] + referable = backend[ + other_object.id_short + if case_sensitive + else other_object.id_short.upper() + ] referable.update_from(other_object) # type: ignore elif isinstance(other_object, Qualifier): backend, case_sensitive = self._backend["type"] - qualifier = backend[other_object.type if case_sensitive else other_object.type.upper()] + qualifier = backend[ + other_object.type + if case_sensitive + else other_object.type.upper() + ] # qualifier.update_from(other_object) # TODO: What should happend here? elif isinstance(other_object, Extension): backend, case_sensitive = self._backend["name"] - extension = backend[other_object.name if case_sensitive else other_object.name.upper()] + extension = backend[ + other_object.name + if case_sensitive + else other_object.name.upper() + ] # extension.update_from(other_object) # TODO: What should happend here? else: raise TypeError("Type not implemented") @@ -2143,10 +2412,15 @@ def update_nss_from(self, other: "NamespaceSet"): # other object is not in NamespaceSet objects_to_add.append(other_object) for attr_name, (backend, case_sensitive) in self._backend.items(): - for attr_name_other, (backend_other, case_sensitive_other) in other._backend.items(): + for attr_name_other, ( + backend_other, + case_sensitive_other, + ) in other._backend.items(): if attr_name is attr_name_other: for item in backend.values(): - if not backend_other.get(self._get_attribute(item, attr_name, case_sensitive)): + if not backend_other.get( + self._get_attribute(item, attr_name, case_sensitive) + ): # referable does not exist in the other NamespaceSet objects_to_remove.append(item) for object_to_add in objects_to_add: @@ -2165,11 +2439,18 @@ class OrderedNamespaceSet(NamespaceSet[_NSO], MutableSequence[_NSO], Generic[_NS (actually it is derived from MutableSequence). However, we don't permit duplicate entries in the ordered list of objects. """ - def __init__(self, parent: Union[UniqueIdShortNamespace, UniqueSemanticIdNamespace, Qualifiable, HasExtension], - attribute_names: List[Tuple[str, bool]], items: Iterable[_NSO] = (), - item_add_hook: Optional[Callable[[_NSO, Iterable[_NSO]], None]] = None, - item_id_set_hook: Optional[Callable[[_NSO], None]] = None, - item_id_del_hook: Optional[Callable[[_NSO], None]] = None) -> None: + + def __init__( + self, + parent: Union[ + UniqueIdShortNamespace, UniqueSemanticIdNamespace, Qualifiable, HasExtension + ], + attribute_names: List[Tuple[str, bool]], + items: Iterable[_NSO] = (), + item_add_hook: Optional[Callable[[_NSO, Iterable[_NSO]], None]] = None, + item_id_set_hook: Optional[Callable[[_NSO], None]] = None, + item_id_del_hook: Optional[Callable[[_NSO], None]] = None, + ) -> None: """ Initialize a new OrderedNamespaceSet. @@ -2192,7 +2473,14 @@ def __init__(self, parent: Union[UniqueIdShortNamespace, UniqueSemanticIdNamespa item doesn't have an identifying attribute """ self._order: List[_NSO] = [] - super().__init__(parent, attribute_names, items, item_add_hook, item_id_set_hook, item_id_del_hook) + super().__init__( + parent, + attribute_names, + items, + item_add_hook, + item_id_set_hook, + item_id_del_hook, + ) def __iter__(self) -> Iterator[_NSO]: return iter(self._order) @@ -2275,7 +2563,7 @@ def __delitem__(self, i: slice) -> None: ... def __delitem__(self, i: Union[int, slice]) -> None: if isinstance(i, int): - i = slice(i, i+1) + i = slice(i, i + 1) for o in self._order[i]: super().remove(o) del self._order[i] @@ -2301,12 +2589,14 @@ class SpecificAssetId(HasSemantics): :class:`~basyx.aas.model.base.HasSemantics`) """ - def __init__(self, - name: LabelType, - value: Identifier, - external_subject_id: Optional[ExternalReference] = None, - semantic_id: Optional[Reference] = None, - supplemental_semantic_id: Iterable[Reference] = ()): + def __init__( + self, + name: LabelType, + value: Identifier, + external_subject_id: Optional[ExternalReference] = None, + semantic_id: Optional[Reference] = None, + supplemental_semantic_id: Iterable[Reference] = (), + ): super().__init__() if value == "": raise ValueError("value is not allowed to be an empty string") @@ -2316,11 +2606,11 @@ def __init__(self, self.value: Identifier self.external_subject_id: ExternalReference - super().__setattr__('name', name) - super().__setattr__('value', value) - super().__setattr__('external_subject_id', external_subject_id) - super().__setattr__('semantic_id', semantic_id) - super().__setattr__('supplemental_semantic_id', supplemental_semantic_id) + super().__setattr__("name", name) + super().__setattr__("value", value) + super().__setattr__("external_subject_id", external_subject_id) + super().__setattr__("semantic_id", semantic_id) + super().__setattr__("supplemental_semantic_id", supplemental_semantic_id) def __setattr__(self, key, value): """Prevent modification of attributes.""" @@ -2328,27 +2618,39 @@ def __setattr__(self, key, value): # HasSemantics.__init__ sets the parent attribute to None, so that has to be possible. It needs to be set # because its value is checked in the semantic_id setter and since every subclass of HasSemantics is expected # to have this attribute. Additionally, the protected _semantic_id attribute must be settable. - if key == '_semantic_id' or key == '_supplemental_semantic_id' or (key == 'parent' and value is None): + if ( + key == "_semantic_id" + or key == "_supplemental_semantic_id" + or (key == "parent" and value is None) + ): return super(HasSemantics, self).__setattr__(key, value) - raise AttributeError('SpecificAssetId is immutable') + raise AttributeError("SpecificAssetId is immutable") def __eq__(self, other: object) -> bool: if not isinstance(other, SpecificAssetId): return NotImplemented - return (self.name == other.name - and self.value == other.value - and self.external_subject_id == other.external_subject_id - and self.semantic_id == other.semantic_id - and self.supplemental_semantic_id == other.supplemental_semantic_id) + return ( + self.name == other.name + and self.value == other.value + and self.external_subject_id == other.external_subject_id + and self.semantic_id == other.semantic_id + and self.supplemental_semantic_id == other.supplemental_semantic_id + ) def __hash__(self): return hash((self.name, self.value, self.external_subject_id)) def __repr__(self) -> str: - return "SpecificAssetId(key={}, value={}, external_subject_id={}, " \ - "semantic_id={}, supplemental_semantic_id={})".format( - self.name, self.value, self.external_subject_id, self.semantic_id, - self.supplemental_semantic_id) + return ( + "SpecificAssetId(key={}, value={}, external_subject_id={}, " + "semantic_id={}, supplemental_semantic_id={})".format( + self.name, + self.value, + self.external_subject_id, + self.semantic_id, + self.supplemental_semantic_id, + ) + ) class AASConstraintViolation(Exception): @@ -2359,9 +2661,12 @@ class AASConstraintViolation(Exception): :ivar constraint_id: The ID of the constraint that is violated :ivar message: The error message of the Exception """ + def __init__(self, constraint_id: int, message: str): self.constraint_id: int = constraint_id - self.message: str = message + " (Constraint AASd-" + str(constraint_id).zfill(3) + ")" + self.message: str = ( + message + " (Constraint AASd-" + str(constraint_id).zfill(3) + ")" + ) super().__init__(self.message) @@ -2391,6 +2696,7 @@ class DataTypeIEC61360(Enum): :cvar BLOB: :cvar FILE: """ + DATE = 0 STRING = 1 STRING_TRANSLATABLE = 2 @@ -2423,6 +2729,7 @@ class IEC61360LevelType(Enum): :cvar NOM: :cvar TYP: """ + MIN = 0 MAX = 1 NOM = 2 @@ -2449,19 +2756,22 @@ class DataSpecificationIEC61360(DataSpecificationContent): :ivar value: Optional value data type object :ivar level_types: Optional set of level types of the DataSpecificationContent """ - def __init__(self, - preferred_name: PreferredNameTypeIEC61360, - data_type: Optional[DataTypeIEC61360] = None, - definition: Optional[DefinitionTypeIEC61360] = None, - short_name: Optional[ShortNameTypeIEC61360] = None, - unit: Optional[str] = None, - unit_id: Optional[Reference] = None, - source_of_definition: Optional[str] = None, - symbol: Optional[str] = None, - value_format: Optional[str] = None, - value_list: Optional[ValueList] = None, - value: Optional[ValueTypeIEC61360] = None, - level_types: Iterable[IEC61360LevelType] = ()): + + def __init__( + self, + preferred_name: PreferredNameTypeIEC61360, + data_type: Optional[DataTypeIEC61360] = None, + definition: Optional[DefinitionTypeIEC61360] = None, + short_name: Optional[ShortNameTypeIEC61360] = None, + unit: Optional[str] = None, + unit_id: Optional[Reference] = None, + source_of_definition: Optional[str] = None, + symbol: Optional[str] = None, + value_format: Optional[str] = None, + value_list: Optional[ValueList] = None, + value: Optional[ValueTypeIEC61360] = None, + level_types: Iterable[IEC61360LevelType] = (), + ): self.preferred_name: PreferredNameTypeIEC61360 = preferred_name self.short_name: Optional[ShortNameTypeIEC61360] = short_name @@ -2502,7 +2812,9 @@ def _set_source_of_definition(self, source_of_definition: Optional[str]): def _get_source_of_definition(self): return self._source_of_definition - source_of_definition = property(_get_source_of_definition, _set_source_of_definition) + source_of_definition = property( + _get_source_of_definition, _set_source_of_definition + ) def _set_symbol(self, symbol: Optional[str]): """ diff --git a/sdk/basyx/aas/model/concept.py b/sdk/basyx/aas/model/concept.py index a9cbd1a33..e217dc03d 100644 --- a/sdk/basyx/aas/model/concept.py +++ b/sdk/basyx/aas/model/concept.py @@ -7,6 +7,7 @@ """ This module contains the class :class:`~.ConceptDescription` from the AAS metamodel. """ + from typing import Optional, Set, Iterable, List from . import base @@ -23,7 +24,7 @@ "EVENT", "ENTITY", "APPLICATION_CLASS", - "QUALIFIER" + "QUALIFIER", } @@ -54,31 +55,36 @@ class ConceptDescription(base.Identifiable, base.HasDataSpecification): :ivar embedded_data_specifications: List of Embedded data specification. :ivar extension: An extension of the element. (from :class:`~basyx.aas.model.base.HasExtension`) -""" + """ - def __init__(self, - id_: base.Identifier, - is_case_of: Optional[Set[base.Reference]] = None, - id_short: Optional[base.NameType] = None, - display_name: Optional[base.MultiLanguageNameType] = None, - category: Optional[base.NameType] = None, - description: Optional[base.MultiLanguageTextType] = None, - parent: Optional[base.UniqueIdShortNamespace] = None, - administration: Optional[base.AdministrativeInformation] = None, - embedded_data_specifications: Iterable[base.EmbeddedDataSpecification] - = (), - extension: Iterable[base.Extension] = ()): + def __init__( + self, + id_: base.Identifier, + is_case_of: Optional[Set[base.Reference]] = None, + id_short: Optional[base.NameType] = None, + display_name: Optional[base.MultiLanguageNameType] = None, + category: Optional[base.NameType] = None, + description: Optional[base.MultiLanguageTextType] = None, + parent: Optional[base.UniqueIdShortNamespace] = None, + administration: Optional[base.AdministrativeInformation] = None, + embedded_data_specifications: Iterable[base.EmbeddedDataSpecification] = (), + extension: Iterable[base.Extension] = (), + ): super().__init__() self.id: base.Identifier = id_ - self.is_case_of: Set[base.Reference] = set() if is_case_of is None else is_case_of + self.is_case_of: Set[base.Reference] = ( + set() if is_case_of is None else is_case_of + ) self.id_short = id_short self.display_name: Optional[base.MultiLanguageNameType] = display_name self.category = category self.description: Optional[base.MultiLanguageTextType] = description self.parent: Optional[base.UniqueIdShortNamespace] = parent self.administration: Optional[base.AdministrativeInformation] = administration - self.embedded_data_specifications: List[base.EmbeddedDataSpecification] = list(embedded_data_specifications) + self.embedded_data_specifications: List[base.EmbeddedDataSpecification] = list( + embedded_data_specifications + ) self.extension = base.NamespaceSet(self, [("name", True)], extension) def _set_category(self, category: Optional[str]): @@ -89,6 +95,6 @@ def _set_category(self, category: Optional[str]): raise base.AASConstraintViolation( 51, "ConceptDescription must have one of the following " - "categories: " + str(ALLOWED_CONCEPT_DESCRIPTION_CATEGORIES) + "categories: " + str(ALLOWED_CONCEPT_DESCRIPTION_CATEGORIES), ) self._category = category diff --git a/sdk/basyx/aas/model/datatypes.py b/sdk/basyx/aas/model/datatypes.py index 07de9700c..ba067d47d 100644 --- a/sdk/basyx/aas/model/datatypes.py +++ b/sdk/basyx/aas/model/datatypes.py @@ -23,6 +23,7 @@ Meant for fixing the type of :class:`Properties' ` values automatically, esp. for literal values. """ + import base64 import datetime import decimal @@ -42,17 +43,24 @@ class Date(datetime.date): - __slots__ = '_tzinfo' - - def __new__(cls, year: int, month: Optional[int] = None, day: Optional[int] = None, - tzinfo: Optional[datetime.tzinfo] = None) -> "Date": + __slots__ = "_tzinfo" + + def __new__( + cls, + year: int, + month: Optional[int] = None, + day: Optional[int] = None, + tzinfo: Optional[datetime.tzinfo] = None, + ) -> "Date": res: "Date" = datetime.date.__new__(cls, year, month, day) # type: ignore # pickle support is not in typeshed # TODO normalize tzinfo to '+12:00' through '-11:59' res._tzinfo = tzinfo # type: ignore # Workaround for MyPy bug, not recognizing our additional __slots__ return res def begin(self) -> datetime.datetime: - return datetime.datetime(self.year, self.month, self.day, 0, 0, 0, 0, self.tzinfo) + return datetime.datetime( + self.year, self.month, self.day, 0, 0, 0, 0, self.tzinfo + ) @property def tzinfo(self): @@ -75,7 +83,7 @@ def __repr__(self): def __eq__(self, other: object) -> bool: if not isinstance(other, datetime.date): return NotImplemented - other_tzinfo = other.tzinfo if hasattr(other, 'tzinfo') else None # type: ignore + other_tzinfo = other.tzinfo if hasattr(other, "tzinfo") else None # type: ignore return datetime.date.__eq__(self, other) and self.tzinfo == other_tzinfo def __copy__(self): @@ -95,7 +103,7 @@ def __deepcopy__(self, memo): class GYearMonth: - __slots__ = ('year', 'month', 'tzinfo') + __slots__ = ("year", "month", "tzinfo") def __init__(self, year: int, month: int, tzinfo: Optional[datetime.tzinfo] = None): # TODO normalize tzinfo to '+12:00' through '-11:59' @@ -110,25 +118,31 @@ def into_date(self, day: int = 1) -> Date: return Date(self.year, self.month, day, self.tzinfo) except ValueError as e: if self.year < 0: - raise ValueError("Negative years are not supported by Python's `datetime` library.") from e + raise ValueError( + "Negative years are not supported by Python's `datetime` library." + ) from e raise e @classmethod def from_date(cls, date: datetime.date) -> "GYearMonth": - tzinfo = date.tzinfo if hasattr(date, 'tzinfo') else None # type: ignore + tzinfo = date.tzinfo if hasattr(date, "tzinfo") else None # type: ignore return cls(date.year, date.month, tzinfo) def __eq__(self, other: object) -> bool: if not isinstance(other, GYearMonth): return NotImplemented - return self.year == other.year and self.month == other.month and self.tzinfo == other.tzinfo + return ( + self.year == other.year + and self.month == other.month + and self.tzinfo == other.tzinfo + ) # TODO override comparison operators # TODO add includes(:Union[DateTime, Date]) -> bool function class GYear: - __slots__ = ('year', 'tzinfo') + __slots__ = ("year", "tzinfo") def __init__(self, year: int, tzinfo: Optional[datetime.tzinfo] = None): # TODO normalize tzinfo to '+12:00' through '-11:59' @@ -140,12 +154,14 @@ def into_date(self, month: int = 1, day: int = 1) -> Date: return Date(self.year, month, day, self.tzinfo) except ValueError as e: if self.year < 0: - raise ValueError("Negative years are not supported by Python's `datetime` library.") from e + raise ValueError( + "Negative years are not supported by Python's `datetime` library." + ) from e raise e @classmethod def from_date(cls, date: datetime.date) -> "GYear": - tzinfo = date.tzinfo if hasattr(date, 'tzinfo') else None # type: ignore + tzinfo = date.tzinfo if hasattr(date, "tzinfo") else None # type: ignore return cls(date.year, tzinfo) def __eq__(self, other: object) -> bool: @@ -158,12 +174,14 @@ def __eq__(self, other: object) -> bool: class GMonthDay: - __slots__ = ('month', 'day', 'tzinfo') + __slots__ = ("month", "day", "tzinfo") def __init__(self, month: int, day: int, tzinfo: Optional[datetime.tzinfo] = None): # TODO normalize tzinfo to '+12:00' through '-11:59' if not 1 <= day <= 31: - raise ValueError("{} is out of the allowed range for day of month".format(day)) + raise ValueError( + "{} is out of the allowed range for day of month".format(day) + ) if not 1 <= month <= 12: raise ValueError("{} is out of the allowed range for month".format(month)) self.month: int = month @@ -175,25 +193,31 @@ def into_date(self, year: int = 1970) -> Date: @classmethod def from_date(cls, date: datetime.date) -> "GMonthDay": - tzinfo = date.tzinfo if hasattr(date, 'tzinfo') else None # type: ignore + tzinfo = date.tzinfo if hasattr(date, "tzinfo") else None # type: ignore return cls(date.month, date.day, tzinfo) def __eq__(self, other: object) -> bool: if not isinstance(other, GMonthDay): return NotImplemented - return self.month == other.month and self.day == other.day and self.tzinfo == other.tzinfo + return ( + self.month == other.month + and self.day == other.day + and self.tzinfo == other.tzinfo + ) # TODO override comparison operators # TODO add includes(:Union[DateTime, Date]) -> bool function class GDay: - __slots__ = ('day', 'tzinfo') + __slots__ = ("day", "tzinfo") def __init__(self, day: int, tzinfo: Optional[datetime.tzinfo] = None): # TODO normalize tzinfo to '+12:00' through '-11:59' if not 1 <= day <= 31: - raise ValueError("{} is out of the allowed range for day of month".format(day)) + raise ValueError( + "{} is out of the allowed range for day of month".format(day) + ) self.day: int = day self.tzinfo: Optional[datetime.tzinfo] = tzinfo @@ -202,7 +226,7 @@ def into_date(self, year: int = 1970, month: int = 1) -> Date: @classmethod def from_date(cls, date: datetime.date) -> "GDay": - tzinfo = date.tzinfo if hasattr(date, 'tzinfo') else None # type: ignore + tzinfo = date.tzinfo if hasattr(date, "tzinfo") else None # type: ignore return cls(date.day, tzinfo) def __eq__(self, other: object) -> bool: @@ -215,7 +239,7 @@ def __eq__(self, other: object) -> bool: class GMonth: - __slots__ = ('month', 'tzinfo') + __slots__ = ("month", "tzinfo") def __init__(self, month: int, tzinfo: Optional[datetime.tzinfo] = None): # TODO normalize tzinfo to '+12:00' through '-11:59' @@ -229,7 +253,7 @@ def into_date(self, year: int = 1970, day: int = 1) -> Date: @classmethod def from_date(cls, date: datetime.date) -> "GMonth": - tzinfo = date.tzinfo if hasattr(date, 'tzinfo') else None # type: ignore + tzinfo = date.tzinfo if hasattr(date, "tzinfo") else None # type: ignore return cls(date.month, tzinfo) def __eq__(self, other: object) -> bool: @@ -250,7 +274,8 @@ class HexBinary(bytearray): class Float(float): - """ A 32bit IEEE754 float. This can not be represented with Python """ + """A 32bit IEEE754 float. This can not be represented with Python""" + pass @@ -258,8 +283,10 @@ class Long(int): def __new__(cls, *args, **kwargs): res = int.__new__(cls, *args, **kwargs) # [-9223372036854775808, 9223372036854775807] - if res > 2**63-1 or res < -2**63: - raise ValueError("{} is out of the allowed range for type {}".format(res, cls.__name__)) + if res > 2**63 - 1 or res < -(2**63): + raise ValueError( + "{} is out of the allowed range for type {}".format(res, cls.__name__) + ) return res @@ -267,8 +294,10 @@ class Int(int): def __new__(cls, *args, **kwargs): res = int.__new__(cls, *args, **kwargs) # [-2147483648, 2147483647] - if res > 2**31-1 or res < -2**31: - raise ValueError("{} is out of the allowed range for type {}".format(res, cls.__name__)) + if res > 2**31 - 1 or res < -(2**31): + raise ValueError( + "{} is out of the allowed range for type {}".format(res, cls.__name__) + ) return res @@ -276,8 +305,10 @@ class Short(int): def __new__(cls, *args, **kwargs): res = int.__new__(cls, *args, **kwargs) # [-32768, 32767] - if res > 2**15-1 or res < -2**15: - raise ValueError("{} is out of the allowed range for type {}".format(res, cls.__name__)) + if res > 2**15 - 1 or res < -(2**15): + raise ValueError( + "{} is out of the allowed range for type {}".format(res, cls.__name__) + ) return res @@ -285,8 +316,10 @@ class Byte(int): def __new__(cls, *args, **kwargs): res = int.__new__(cls, *args, **kwargs) # [-128,127] - if res > 2**7-1 or res < -2**7: - raise ValueError("{} is out of the allowed range for type {}".format(res, cls.__name__)) + if res > 2**7 - 1 or res < -(2**7): + raise ValueError( + "{} is out of the allowed range for type {}".format(res, cls.__name__) + ) return res @@ -294,7 +327,9 @@ class NonPositiveInteger(int): def __new__(cls, *args, **kwargs): res = int.__new__(cls, *args, **kwargs) if res > 0: - raise ValueError("{} is out of the allowed range for type {}".format(res, cls.__name__)) + raise ValueError( + "{} is out of the allowed range for type {}".format(res, cls.__name__) + ) return res @@ -302,7 +337,9 @@ class NegativeInteger(int): def __new__(cls, *args, **kwargs): res = int.__new__(cls, *args, **kwargs) if res >= 0: - raise ValueError("{} is out of the allowed range for type {}".format(res, cls.__name__)) + raise ValueError( + "{} is out of the allowed range for type {}".format(res, cls.__name__) + ) return res @@ -310,7 +347,9 @@ class NonNegativeInteger(int): def __new__(cls, *args, **kwargs): res = int.__new__(cls, *args, **kwargs) if res < 0: - raise ValueError("{} is out of the allowed range for type {}".format(res, cls.__name__)) + raise ValueError( + "{} is out of the allowed range for type {}".format(res, cls.__name__) + ) return res @@ -318,39 +357,49 @@ class PositiveInteger(int): def __new__(cls, *args, **kwargs): res = int.__new__(cls, *args, **kwargs) if res <= 0: - raise ValueError("{} is out of the allowed range for type {}".format(res, cls.__name__)) + raise ValueError( + "{} is out of the allowed range for type {}".format(res, cls.__name__) + ) return res class UnsignedLong(int): def __new__(cls, *args, **kwargs): res = int.__new__(cls, *args, **kwargs) - if not 0 <= res <= 2**64-1: - raise ValueError("{} is out of the allowed range for type {}".format(res, cls.__name__)) + if not 0 <= res <= 2**64 - 1: + raise ValueError( + "{} is out of the allowed range for type {}".format(res, cls.__name__) + ) return res class UnsignedInt(int): def __new__(cls, *args, **kwargs): res = int.__new__(cls, *args, **kwargs) - if not 0 <= res <= 2**32-1: - raise ValueError("{} is out of the allowed range for type {}".format(res, cls.__name__)) + if not 0 <= res <= 2**32 - 1: + raise ValueError( + "{} is out of the allowed range for type {}".format(res, cls.__name__) + ) return res class UnsignedShort(int): def __new__(cls, *args, **kwargs): res = int.__new__(cls, *args, **kwargs) - if not 0 <= res <= 2**16-1: - raise ValueError("{} is out of the allowed range for type {}".format(res, cls.__name__)) + if not 0 <= res <= 2**16 - 1: + raise ValueError( + "{} is out of the allowed range for type {}".format(res, cls.__name__) + ) return res class UnsignedByte(int): def __new__(cls, *args, **kwargs): res = int.__new__(cls, *args, **kwargs) - if not 0 <= res <= 2**8-1: - raise ValueError("{} is out of the allowed range for type {}".format(res, cls.__name__)) + if not 0 <= res <= 2**8 - 1: + raise ValueError( + "{} is out of the allowed range for type {}".format(res, cls.__name__) + ) return res @@ -362,7 +411,7 @@ class AnyURI(str): class NormalizedString(str): def __new__(cls, *args, **kwargs): res = str.__new__(cls, *args, **kwargs) - if ('\r' in res) or ('\n' in res) or ('\t' in res): + if ("\r" in res) or ("\n" in res) or ("\t" in res): raise ValueError("\\r, \\n and \\t are not allowed in NormalizedStrings") return res @@ -375,49 +424,84 @@ def from_string(cls, value: str) -> "NormalizedString": AnyXSDType = Union[ - Duration, DateTime, Date, Time, GYearMonth, GYear, GMonthDay, GMonth, GDay, Boolean, Base64Binary, - HexBinary, Float, Double, Decimal, Integer, Long, Int, Short, Byte, NonPositiveInteger, NegativeInteger, - NonNegativeInteger, PositiveInteger, UnsignedLong, UnsignedInt, UnsignedShort, UnsignedByte, AnyURI, String, - NormalizedString] - - -XSD_TYPE_NAMES: Dict[Type[AnyXSDType], str] = {k: "xs:" + v for k, v in { - Duration: "duration", - DateTime: "dateTime", - Date: "date", - Time: "time", - GYearMonth: "gYearMonth", - GYear: "gYear", - GMonthDay: "gMonthDay", - GMonth: "gMonth", - GDay: "gDay", - Boolean: "boolean", - Base64Binary: "base64Binary", - HexBinary: "hexBinary", - Float: "float", - Double: "double", - Decimal: "decimal", - Integer: "integer", - Long: "long", - Int: "int", - Short: "short", - Byte: "byte", - NonPositiveInteger: "nonPositiveInteger", - NegativeInteger: "negativeInteger", - NonNegativeInteger: "nonNegativeInteger", - PositiveInteger: "positiveInteger", - UnsignedLong: "unsignedLong", - UnsignedShort: "unsignedShort", - UnsignedInt: "unsignedInt", - UnsignedByte: "unsignedByte", - AnyURI: "anyURI", - String: "string", - NormalizedString: "normalizedString", -}.items()} -XSD_TYPE_CLASSES: Dict[str, Type[AnyXSDType]] = {v: k for k, v in XSD_TYPE_NAMES.items()} - - -def trivial_cast(value, type_: Type[AnyXSDType]) -> AnyXSDType: # workaround. We should be able to use a TypeVar here + Duration, + DateTime, + Date, + Time, + GYearMonth, + GYear, + GMonthDay, + GMonth, + GDay, + Boolean, + Base64Binary, + HexBinary, + Float, + Double, + Decimal, + Integer, + Long, + Int, + Short, + Byte, + NonPositiveInteger, + NegativeInteger, + NonNegativeInteger, + PositiveInteger, + UnsignedLong, + UnsignedInt, + UnsignedShort, + UnsignedByte, + AnyURI, + String, + NormalizedString, +] + + +XSD_TYPE_NAMES: Dict[Type[AnyXSDType], str] = { + k: "xs:" + v + for k, v in { + Duration: "duration", + DateTime: "dateTime", + Date: "date", + Time: "time", + GYearMonth: "gYearMonth", + GYear: "gYear", + GMonthDay: "gMonthDay", + GMonth: "gMonth", + GDay: "gDay", + Boolean: "boolean", + Base64Binary: "base64Binary", + HexBinary: "hexBinary", + Float: "float", + Double: "double", + Decimal: "decimal", + Integer: "integer", + Long: "long", + Int: "int", + Short: "short", + Byte: "byte", + NonPositiveInteger: "nonPositiveInteger", + NegativeInteger: "negativeInteger", + NonNegativeInteger: "nonNegativeInteger", + PositiveInteger: "positiveInteger", + UnsignedLong: "unsignedLong", + UnsignedShort: "unsignedShort", + UnsignedInt: "unsignedInt", + UnsignedByte: "unsignedByte", + AnyURI: "anyURI", + String: "string", + NormalizedString: "normalizedString", + }.items() +} +XSD_TYPE_CLASSES: Dict[str, Type[AnyXSDType]] = { + v: k for k, v in XSD_TYPE_NAMES.items() +} + + +def trivial_cast( + value, type_: Type[AnyXSDType] +) -> AnyXSDType: # workaround. We should be able to use a TypeVar here """ Type-cast a python value into an XSD type, if this is a trivial conversion @@ -444,7 +528,9 @@ def trivial_cast(value, type_: Type[AnyXSDType]) -> AnyXSDType: # workaround. W return type_(value) # type: ignore if isinstance(value, datetime.date) and issubclass(type_, Date): return Date(value.year, value.month, value.day) - raise TypeError("{} cannot be trivially casted into {}".format(repr(value), type_.__name__)) + raise TypeError( + "{} cannot be trivially casted into {}".format(repr(value), type_.__name__) + ) def xsd_repr(value: AnyXSDType) -> str: @@ -463,11 +549,15 @@ def xsd_repr(value: AnyXSDType) -> str: elif isinstance(value, Date): return value.isoformat() + _serialize_date_tzinfo(value) elif isinstance(value, GYearMonth): - return "{:02d}-{:02d}".format(value.year, value.month) + _serialize_date_tzinfo(value) + return "{:02d}-{:02d}".format(value.year, value.month) + _serialize_date_tzinfo( + value + ) elif isinstance(value, GYear): return "{:04d}".format(value.year) + _serialize_date_tzinfo(value) elif isinstance(value, GMonthDay): - return "--{:02d}-{:02d}".format(value.month, value.day) + _serialize_date_tzinfo(value) + return "--{:02d}-{:02d}".format( + value.month, value.day + ) + _serialize_date_tzinfo(value) elif isinstance(value, GDay): return "---{:02d}".format(value.day) + _serialize_date_tzinfo(value) elif isinstance(value, GMonth): @@ -481,33 +571,50 @@ def xsd_repr(value: AnyXSDType) -> str: elif isinstance(value, str): return value elif isinstance(value, float): - return repr(value).translate({0x65: 'E', 0x66: 'F', 0x69: 'I', 0x6e: 'N'}) + return repr(value).translate({0x65: "E", 0x66: "F", 0x69: "I", 0x6E: "N"}) else: return str(value) -def _serialize_date_tzinfo(date: Union[Date, GYear, GMonth, GDay, GYearMonth, GMonthDay]) -> str: +def _serialize_date_tzinfo( + date: Union[Date, GYear, GMonth, GDay, GYearMonth, GMonthDay], +) -> str: if date.tzinfo is not None: if not isinstance(date, Date): date = date.into_date() - offset: datetime.timedelta = date.tzinfo.utcoffset(datetime.datetime(date.year, date.month, date.day, 0, 0, 0)) - offset_seconds = (offset.total_seconds() + 3600*12) % (3600*24) - 3600*12 + offset: datetime.timedelta = date.tzinfo.utcoffset( + datetime.datetime(date.year, date.month, date.day, 0, 0, 0) + ) + offset_seconds = (offset.total_seconds() + 3600 * 12) % (3600 * 24) - 3600 * 12 if offset_seconds // 60 == 0: return "Z" - return "{}{:02.0f}:{:02.0f}".format("+" if offset_seconds >= 0 else "-", - abs(offset_seconds) // 3600, - (abs(offset_seconds) // 60) % 60) + return "{}{:02.0f}:{:02.0f}".format( + "+" if offset_seconds >= 0 else "-", + abs(offset_seconds) // 3600, + (abs(offset_seconds) // 60) % 60, + ) return "" def _serialize_duration(value: Duration) -> str: value = value.normalized() - signs = set(val < 0 - for val in (value.years, value.months, value.days, value.hours, value.minutes, value.seconds, - value.microseconds) - if val != 0) + signs = set( + val < 0 + for val in ( + value.years, + value.months, + value.days, + value.hours, + value.minutes, + value.seconds, + value.microseconds, + ) + if val != 0 + ) if len(signs) > 1: - raise ValueError("Relative Durations with mixed signs are not allowed according to XSD.") + raise ValueError( + "Relative Durations with mixed signs are not allowed according to XSD." + ) elif len(signs) == 0: return "P0D" @@ -526,14 +633,18 @@ def _serialize_duration(value: Duration) -> str: if value.minutes: time += "{:.0f}M".format(abs(value.minutes)) if value.seconds or value.microseconds: - time += "{:.8g}S".format(decimal.Decimal(abs(value.seconds)) - + decimal.Decimal(abs(value.microseconds)) / 1000000) + time += "{:.8g}S".format( + decimal.Decimal(abs(value.seconds)) + + decimal.Decimal(abs(value.microseconds)) / 1000000 + ) if time: result += "T" + time return result -def from_xsd(value: str, type_: Type[AnyXSDType]) -> AnyXSDType: # workaround. We should be able to use a TypeVar here +def from_xsd( + value: str, type_: Type[AnyXSDType] +) -> AnyXSDType: # workaround. We should be able to use a TypeVar here """ Parse an XSD type value from its lexical representation @@ -576,26 +687,34 @@ def from_xsd(value: str, type_: Type[AnyXSDType]) -> AnyXSDType: # workaround. return _parse_xsd_gyearmonth(value) elif type_ is GMonthDay: return _parse_xsd_gmonthday(value) - raise ValueError("{} is not a valid simple built-in XSD type".format(type_.__name__)) + raise ValueError( + "{} is not a valid simple built-in XSD type".format(type_.__name__) + ) -DURATION_RE = re.compile(r'^(-?)P(\d+Y)?(\d+M)?(\d+D)?(T(\d+H)?(\d+M)?((\d+)(\.\d+)?S)?)?$') -DATETIME_RE = re.compile(r'^(-?)(\d\d\d\d)-(\d\d)-(\d\d)T(\d\d):(\d\d):(\d\d)(\.\d+)?([+\-](\d\d):(\d\d)|Z)?$') -TIME_RE = re.compile(r'^(\d\d):(\d\d):(\d\d)(\.\d+)?([+\-](\d\d):(\d\d)|Z)?$') -DATE_RE = re.compile(r'^(-?)(\d\d\d\d)-(\d\d)-(\d\d)([+\-](\d\d):(\d\d)|Z)?$') +DURATION_RE = re.compile( + r"^(-?)P(\d+Y)?(\d+M)?(\d+D)?(T(\d+H)?(\d+M)?((\d+)(\.\d+)?S)?)?$" +) +DATETIME_RE = re.compile( + r"^(-?)(\d\d\d\d)-(\d\d)-(\d\d)T(\d\d):(\d\d):(\d\d)(\.\d+)?([+\-](\d\d):(\d\d)|Z)?$" +) +TIME_RE = re.compile(r"^(\d\d):(\d\d):(\d\d)(\.\d+)?([+\-](\d\d):(\d\d)|Z)?$") +DATE_RE = re.compile(r"^(-?)(\d\d\d\d)-(\d\d)-(\d\d)([+\-](\d\d):(\d\d)|Z)?$") def _parse_xsd_duration(value: str) -> Duration: match = DURATION_RE.match(value) if not match: raise ValueError("Value is not a valid XSD duration string") - res = Duration(years=int(match[2][:-1]) if match[2] else 0, - months=int(match[3][:-1]) if match[3] else 0, - days=int(match[4][:-1]) if match[4] else 0, - hours=int(match[6][:-1]) if match[6] else 0, - minutes=int(match[7][:-1]) if match[7] else 0, - seconds=int(match[9]) if match[8] else 0, - microseconds=int(float(match[10])*1e6) if match[10] else 0) + res = Duration( + years=int(match[2][:-1]) if match[2] else 0, + months=int(match[3][:-1]) if match[3] else 0, + days=int(match[4][:-1]) if match[4] else 0, + hours=int(match[6][:-1]) if match[6] else 0, + minutes=int(match[7][:-1]) if match[7] else 0, + seconds=int(match[9]) if match[8] else 0, + microseconds=int(float(match[10]) * 1e6) if match[10] else 0, + ) if match[1]: res = -res return res @@ -606,8 +725,10 @@ def _parse_xsd_date_tzinfo(value: str) -> Optional[datetime.tzinfo]: return None if value == "Z": return datetime.timezone.utc - return datetime.timezone(datetime.timedelta(hours=int(value[1:3]), minutes=int(value[4:6])) - * (-1 if value[0] == '-' else 1)) + return datetime.timezone( + datetime.timedelta(hours=int(value[1:3]), minutes=int(value[4:6])) + * (-1 if value[0] == "-" else 1) + ) def _parse_xsd_date(value: str) -> Date: @@ -615,8 +736,10 @@ def _parse_xsd_date(value: str) -> Date: if not match: raise ValueError("Value is not a valid XSD date string") if match[1]: - raise NotImplementedError("Negative dates are not supported: Python stdlib datetime requires year >= 1. " - "Report at https://github.com/eclipse-basyx/basyx-python-sdk/issues") + raise NotImplementedError( + "Negative dates are not supported: Python stdlib datetime requires year >= 1. " + "Report at https://github.com/eclipse-basyx/basyx-python-sdk/issues" + ) return Date( year=int(match[2]), month=int(match[3]), @@ -630,8 +753,10 @@ def _parse_xsd_datetime(value: str) -> DateTime: if not match: raise ValueError(f"{value} is not a valid XSD datetime string") if match[1]: - raise NotImplementedError("Negative dates are not supported: Python stdlib datetime requires year >= 1. " - "Report at https://github.com/eclipse-basyx/basyx-python-sdk/issues") + raise NotImplementedError( + "Negative dates are not supported: Python stdlib datetime requires year >= 1. " + "Report at https://github.com/eclipse-basyx/basyx-python-sdk/issues" + ) microseconds = int(float(match[8]) * 1e6) if match[8] else 0 hour = int(match[5]) # xsd_datetime allows for hour=24 to represent midnight, @@ -689,11 +814,11 @@ def _parse_xsd_bool(value: str) -> Boolean: raise ValueError("Invalid literal for XSD bool type") -GYEAR_RE = re.compile(r'^(-?)(\d{4,})([+\-]\d\d:\d\d|Z)?$') -GMONTH_RE = re.compile(r'^--(\d\d)([+\-]\d\d:\d\d|Z)?$') -GDAY_RE = re.compile(r'^---(\d\d)([+\-]\d\d:\d\d|Z)?$') -GYEARMONTH_RE = re.compile(r'^(-?)(\d{4,})-(\d\d)([+\-]\d\d:\d\d|Z)?$') -GMONTHDAY_RE = re.compile(r'^--(\d\d)-(\d\d)([+\-]\d\d:\d\d|Z)?$') +GYEAR_RE = re.compile(r"^(-?)(\d{4,})([+\-]\d\d:\d\d|Z)?$") +GMONTH_RE = re.compile(r"^--(\d\d)([+\-]\d\d:\d\d|Z)?$") +GDAY_RE = re.compile(r"^---(\d\d)([+\-]\d\d:\d\d|Z)?$") +GYEARMONTH_RE = re.compile(r"^(-?)(\d{4,})-(\d\d)([+\-]\d\d:\d\d|Z)?$") +GMONTHDAY_RE = re.compile(r"^--(\d\d)-(\d\d)([+\-]\d\d:\d\d|Z)?$") def _parse_xsd_gyear(value: str) -> GYear: diff --git a/sdk/basyx/aas/model/provider.py b/sdk/basyx/aas/model/provider.py index 9a91a346d..be4bca156 100644 --- a/sdk/basyx/aas/model/provider.py +++ b/sdk/basyx/aas/model/provider.py @@ -12,13 +12,24 @@ import abc import warnings -from typing import MutableSet, Iterator, Generic, TypeVar, Dict, List, Optional, Iterable, Set, Tuple +from typing import ( + MutableSet, + Iterator, + Generic, + TypeVar, + Dict, + List, + Optional, + Iterable, + Set, + Tuple, +) from .base import Identifier, Identifiable -_KEY = TypeVar('_KEY') # Generic key type -_VALUE = TypeVar('_VALUE') # Generic value type +_KEY = TypeVar("_KEY") # Generic key type +_VALUE = TypeVar("_VALUE") # Generic value type class AbstractObjectProvider(Generic[_KEY, _VALUE], metaclass=abc.ABCMeta): @@ -113,8 +124,12 @@ class ObjectProviderMultiplexer(AbstractObjectProvider[_KEY, _VALUE]): key """ - def __init__(self, registries: Optional[List[AbstractObjectProvider[_KEY, _VALUE]]] = None) -> None: - self.providers: List[AbstractObjectProvider[_KEY, _VALUE]] = registries if registries is not None else [] + def __init__( + self, registries: Optional[List[AbstractObjectProvider[_KEY, _VALUE]]] = None + ) -> None: + self.providers: List[AbstractObjectProvider[_KEY, _VALUE]] = ( + registries if registries is not None else [] + ) def get_item(self, key: _KEY) -> _VALUE: for provider in self.providers: @@ -122,11 +137,14 @@ def get_item(self, key: _KEY) -> _VALUE: return provider.get_item(key) except KeyError: pass - raise KeyError("Key could not be found in any of the {} consulted registries." - .format(len(self.providers))) + raise KeyError( + "Key could not be found in any of the {} consulted registries.".format( + len(self.providers) + ) + ) -_IDENTIFIABLE = TypeVar('_IDENTIFIABLE', bound=Identifiable) +_IDENTIFIABLE = TypeVar("_IDENTIFIABLE", bound=Identifiable) class DictIdentifiableStore(AbstractObjectStore[Identifier, _IDENTIFIABLE]): @@ -154,8 +172,11 @@ def get_item(self, identifier: Identifier) -> _IDENTIFIABLE: def add(self, x: _IDENTIFIABLE) -> None: if x.id in self._backend and self._backend.get(x.id) is not x: - raise KeyError("Identifiable object with same id {} is already stored in this store" - .format(x.id)) + raise KeyError( + "Identifiable object with same id {} is already stored in this store".format( + x.id + ) + ) self._backend[x.id] = x def commit(self, x: _IDENTIFIABLE) -> None: @@ -236,7 +257,9 @@ def add(self, x: _IDENTIFIABLE) -> None: except KeyError: self._backend.add(x) else: - raise KeyError(f"Identifiable object with same id {x.id} is already stored in this store") + raise KeyError( + f"Identifiable object with same id {x.id} is already stored in this store" + ) def commit(self, x: _IDENTIFIABLE) -> None: pass diff --git a/sdk/basyx/aas/model/submodel.py b/sdk/basyx/aas/model/submodel.py index a361ce341..535d3f1dd 100644 --- a/sdk/basyx/aas/model/submodel.py +++ b/sdk/basyx/aas/model/submodel.py @@ -10,15 +10,31 @@ import abc import uuid -from typing import Optional, Set, Iterable, TYPE_CHECKING, List, Type, TypeVar, Generic, Union +from typing import ( + Optional, + Set, + Iterable, + TYPE_CHECKING, + List, + Type, + TypeVar, + Generic, + Union, +) from . import base, datatypes, _string_constraints + if TYPE_CHECKING: from . import aas -class SubmodelElement(base.Referable, base.Qualifiable, base.HasSemantics, - base.HasDataSpecification, metaclass=abc.ABCMeta): +class SubmodelElement( + base.Referable, + base.Qualifiable, + base.HasSemantics, + base.HasDataSpecification, + metaclass=abc.ABCMeta, +): """ A submodel element is an element suitable for the description and differentiation of assets. @@ -51,18 +67,21 @@ class SubmodelElement(base.Referable, base.Qualifiable, base.HasSemantics, :class:`~basyx.aas.model.base.HasSemantics`) :ivar embedded_data_specifications: List of Embedded data specification. """ + @abc.abstractmethod - def __init__(self, - id_short: Optional[base.NameType], - display_name: Optional[base.MultiLanguageNameType] = None, - category: Optional[base.NameType] = None, - description: Optional[base.MultiLanguageTextType] = None, - parent: Optional[base.UniqueIdShortNamespace] = None, - semantic_id: Optional[base.Reference] = None, - qualifier: Iterable[base.Qualifier] = (), - extension: Iterable[base.Extension] = (), - supplemental_semantic_id: Iterable[base.Reference] = (), - embedded_data_specifications: Iterable[base.EmbeddedDataSpecification] = ()): + def __init__( + self, + id_short: Optional[base.NameType], + display_name: Optional[base.MultiLanguageNameType] = None, + category: Optional[base.NameType] = None, + description: Optional[base.MultiLanguageTextType] = None, + parent: Optional[base.UniqueIdShortNamespace] = None, + semantic_id: Optional[base.Reference] = None, + qualifier: Iterable[base.Qualifier] = (), + extension: Iterable[base.Extension] = (), + supplemental_semantic_id: Iterable[base.Reference] = (), + embedded_data_specifications: Iterable[base.EmbeddedDataSpecification] = (), + ): """ TODO: Add instruction what to do after construction """ @@ -77,11 +96,19 @@ def __init__(self, self.qualifier = base.NamespaceSet(self, [("type", True)], qualifier) self.extension = base.NamespaceSet(self, [("name", True)], extension) self.supplemental_semantic_id = base.ConstrainedList(supplemental_semantic_id) - self.embedded_data_specifications: List[base.EmbeddedDataSpecification] = list(embedded_data_specifications) + self.embedded_data_specifications: List[base.EmbeddedDataSpecification] = list( + embedded_data_specifications + ) -class Submodel(base.Identifiable, base.HasSemantics, base.HasKind, base.Qualifiable, - base.UniqueIdShortNamespace, base.HasDataSpecification): +class Submodel( + base.Identifiable, + base.HasSemantics, + base.HasKind, + base.Qualifiable, + base.UniqueIdShortNamespace, + base.HasDataSpecification, +): """ A Submodel defines a specific aspect of the asset represented by the AAS. @@ -118,24 +145,28 @@ class Submodel(base.Identifiable, base.HasSemantics, base.HasKind, base.Qualifia :ivar embedded_data_specifications: List of Embedded data specification. """ - def __init__(self, - id_: base.Identifier, - submodel_element: Iterable[SubmodelElement] = (), - id_short: Optional[base.NameType] = None, - display_name: Optional[base.MultiLanguageNameType] = None, - category: Optional[base.NameType] = None, - description: Optional[base.MultiLanguageTextType] = None, - parent: Optional[base.UniqueIdShortNamespace] = None, - administration: Optional[base.AdministrativeInformation] = None, - semantic_id: Optional[base.Reference] = None, - qualifier: Iterable[base.Qualifier] = (), - kind: base.ModellingKind = base.ModellingKind.INSTANCE, - extension: Iterable[base.Extension] = (), - supplemental_semantic_id: Iterable[base.Reference] = (), - embedded_data_specifications: Iterable[base.EmbeddedDataSpecification] = ()): + def __init__( + self, + id_: base.Identifier, + submodel_element: Iterable[SubmodelElement] = (), + id_short: Optional[base.NameType] = None, + display_name: Optional[base.MultiLanguageNameType] = None, + category: Optional[base.NameType] = None, + description: Optional[base.MultiLanguageTextType] = None, + parent: Optional[base.UniqueIdShortNamespace] = None, + administration: Optional[base.AdministrativeInformation] = None, + semantic_id: Optional[base.Reference] = None, + qualifier: Iterable[base.Qualifier] = (), + kind: base.ModellingKind = base.ModellingKind.INSTANCE, + extension: Iterable[base.Extension] = (), + supplemental_semantic_id: Iterable[base.Reference] = (), + embedded_data_specifications: Iterable[base.EmbeddedDataSpecification] = (), + ): super().__init__() self.id: base.Identifier = id_ - self.submodel_element = base.NamespaceSet(self, [("id_short", True)], submodel_element) + self.submodel_element = base.NamespaceSet( + self, [("id_short", True)], submodel_element + ) self.id_short = id_short self.display_name: Optional[base.MultiLanguageNameType] = display_name self.category = category @@ -147,7 +178,9 @@ def __init__(self, self._kind: base.ModellingKind = kind self.extension = base.NamespaceSet(self, [("name", True)], extension) self.supplemental_semantic_id = base.ConstrainedList(supplemental_semantic_id) - self.embedded_data_specifications: List[base.EmbeddedDataSpecification] = list(embedded_data_specifications) + self.embedded_data_specifications: List[base.EmbeddedDataSpecification] = list( + embedded_data_specifications + ) class DataElement(SubmodelElement, metaclass=abc.ABCMeta): @@ -181,20 +214,33 @@ class DataElement(SubmodelElement, metaclass=abc.ABCMeta): :class:`~basyx.aas.model.base.HasSemantics`) :ivar embedded_data_specifications: List of Embedded data specification. """ + @abc.abstractmethod - def __init__(self, - id_short: Optional[base.NameType], - display_name: Optional[base.MultiLanguageNameType] = None, - category: Optional[base.NameType] = None, - description: Optional[base.MultiLanguageTextType] = None, - parent: Optional[base.UniqueIdShortNamespace] = None, - semantic_id: Optional[base.Reference] = None, - qualifier: Iterable[base.Qualifier] = (), - extension: Iterable[base.Extension] = (), - supplemental_semantic_id: Iterable[base.Reference] = (), - 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) + def __init__( + self, + id_short: Optional[base.NameType], + display_name: Optional[base.MultiLanguageNameType] = None, + category: Optional[base.NameType] = None, + description: Optional[base.MultiLanguageTextType] = None, + parent: Optional[base.UniqueIdShortNamespace] = None, + semantic_id: Optional[base.Reference] = None, + qualifier: Iterable[base.Qualifier] = (), + extension: Iterable[base.Extension] = (), + supplemental_semantic_id: Iterable[base.Reference] = (), + 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, + ) class Property(DataElement): @@ -230,29 +276,42 @@ class Property(DataElement): :ivar embedded_data_specifications: List of Embedded data specification. """ - def __init__(self, - id_short: Optional[base.NameType], - value_type: base.DataTypeDefXsd, - value: Optional[base.ValueDataType] = None, - value_id: Optional[base.Reference] = None, - display_name: Optional[base.MultiLanguageNameType] = None, - category: Optional[base.NameType] = None, - description: Optional[base.MultiLanguageTextType] = None, - parent: Optional[base.UniqueIdShortNamespace] = None, - semantic_id: Optional[base.Reference] = None, - qualifier: Iterable[base.Qualifier] = (), - extension: Iterable[base.Extension] = (), - supplemental_semantic_id: Iterable[base.Reference] = (), - embedded_data_specifications: Iterable[base.EmbeddedDataSpecification] = ()): + def __init__( + self, + id_short: Optional[base.NameType], + value_type: base.DataTypeDefXsd, + value: Optional[base.ValueDataType] = None, + value_id: Optional[base.Reference] = None, + display_name: Optional[base.MultiLanguageNameType] = None, + category: Optional[base.NameType] = None, + description: Optional[base.MultiLanguageTextType] = None, + parent: Optional[base.UniqueIdShortNamespace] = None, + semantic_id: Optional[base.Reference] = None, + qualifier: Iterable[base.Qualifier] = (), + extension: Iterable[base.Extension] = (), + supplemental_semantic_id: Iterable[base.Reference] = (), + embedded_data_specifications: Iterable[base.EmbeddedDataSpecification] = (), + ): """ TODO: Add instruction what to do after construction """ - super().__init__(id_short, display_name, category, description, parent, semantic_id, qualifier, extension, - supplemental_semantic_id, embedded_data_specifications) + super().__init__( + id_short, + display_name, + category, + description, + parent, + semantic_id, + qualifier, + extension, + supplemental_semantic_id, + embedded_data_specifications, + ) self.value_type: base.DataTypeDefXsd = value_type - self._value: Optional[base.ValueDataType] = (datatypes.trivial_cast(value, value_type) - if value is not None else None) + self._value: Optional[base.ValueDataType] = ( + datatypes.trivial_cast(value, value_type) if value is not None else None + ) self.value_id: Optional[base.Reference] = value_id @property @@ -300,25 +359,37 @@ class MultiLanguageProperty(DataElement): :ivar embedded_data_specifications: List of Embedded data specification. """ - def __init__(self, - id_short: Optional[base.NameType], - value: Optional[base.MultiLanguageTextType] = None, - value_id: Optional[base.Reference] = None, - display_name: Optional[base.MultiLanguageNameType] = None, - category: Optional[base.NameType] = None, - description: Optional[base.MultiLanguageTextType] = None, - parent: Optional[base.UniqueIdShortNamespace] = None, - semantic_id: Optional[base.Reference] = None, - qualifier: Iterable[base.Qualifier] = (), - extension: Iterable[base.Extension] = (), - supplemental_semantic_id: Iterable[base.Reference] = (), - embedded_data_specifications: Iterable[base.EmbeddedDataSpecification] = ()): + def __init__( + self, + id_short: Optional[base.NameType], + value: Optional[base.MultiLanguageTextType] = None, + value_id: Optional[base.Reference] = None, + display_name: Optional[base.MultiLanguageNameType] = None, + category: Optional[base.NameType] = None, + description: Optional[base.MultiLanguageTextType] = None, + parent: Optional[base.UniqueIdShortNamespace] = None, + semantic_id: Optional[base.Reference] = None, + qualifier: Iterable[base.Qualifier] = (), + extension: Iterable[base.Extension] = (), + supplemental_semantic_id: Iterable[base.Reference] = (), + embedded_data_specifications: Iterable[base.EmbeddedDataSpecification] = (), + ): """ TODO: Add instruction what to do after construction """ - super().__init__(id_short, display_name, category, description, parent, semantic_id, qualifier, extension, - supplemental_semantic_id, embedded_data_specifications) + super().__init__( + id_short, + display_name, + category, + description, + parent, + semantic_id, + qualifier, + extension, + supplemental_semantic_id, + embedded_data_specifications, + ) self.value: Optional[base.MultiLanguageTextType] = value self.value_id: Optional[base.Reference] = value_id @@ -367,29 +438,45 @@ class Range(DataElement): :ivar embedded_data_specifications: List of Embedded data specification. """ - def __init__(self, - id_short: Optional[base.NameType], - value_type: base.DataTypeDefXsd, - min: Optional[base.ValueDataType] = None, - max: Optional[base.ValueDataType] = None, - display_name: Optional[base.MultiLanguageNameType] = None, - category: Optional[base.NameType] = None, - description: Optional[base.MultiLanguageTextType] = None, - parent: Optional[base.UniqueIdShortNamespace] = None, - semantic_id: Optional[base.Reference] = None, - qualifier: Iterable[base.Qualifier] = (), - extension: Iterable[base.Extension] = (), - supplemental_semantic_id: Iterable[base.Reference] = (), - embedded_data_specifications: Iterable[base.EmbeddedDataSpecification] = ()): + def __init__( + self, + id_short: Optional[base.NameType], + value_type: base.DataTypeDefXsd, + min: Optional[base.ValueDataType] = None, + max: Optional[base.ValueDataType] = None, + display_name: Optional[base.MultiLanguageNameType] = None, + category: Optional[base.NameType] = None, + description: Optional[base.MultiLanguageTextType] = None, + parent: Optional[base.UniqueIdShortNamespace] = None, + semantic_id: Optional[base.Reference] = None, + qualifier: Iterable[base.Qualifier] = (), + extension: Iterable[base.Extension] = (), + supplemental_semantic_id: Iterable[base.Reference] = (), + embedded_data_specifications: Iterable[base.EmbeddedDataSpecification] = (), + ): """ TODO: Add instruction what to do after construction """ - super().__init__(id_short, display_name, category, description, parent, semantic_id, qualifier, extension, - supplemental_semantic_id, embedded_data_specifications) + super().__init__( + id_short, + display_name, + category, + description, + parent, + semantic_id, + qualifier, + extension, + supplemental_semantic_id, + embedded_data_specifications, + ) self.value_type: base.DataTypeDefXsd = value_type - self._min: Optional[base.ValueDataType] = datatypes.trivial_cast(min, value_type) if min is not None else None - self._max: Optional[base.ValueDataType] = datatypes.trivial_cast(max, value_type) if max is not None else None + self._min: Optional[base.ValueDataType] = ( + datatypes.trivial_cast(min, value_type) if min is not None else None + ) + self._max: Optional[base.ValueDataType] = ( + datatypes.trivial_cast(max, value_type) if max is not None else None + ) @property def min(self): @@ -450,25 +537,37 @@ class Blob(DataElement): :ivar embedded_data_specifications: List of Embedded data specification. """ - def __init__(self, - id_short: Optional[base.NameType], - content_type: Optional[base.ContentType] = None, - value: Optional[base.BlobType] = None, - display_name: Optional[base.MultiLanguageNameType] = None, - category: Optional[base.NameType] = None, - description: Optional[base.MultiLanguageTextType] = None, - parent: Optional[base.UniqueIdShortNamespace] = None, - semantic_id: Optional[base.Reference] = None, - qualifier: Iterable[base.Qualifier] = (), - extension: Iterable[base.Extension] = (), - supplemental_semantic_id: Iterable[base.Reference] = (), - embedded_data_specifications: Iterable[base.EmbeddedDataSpecification] = ()): + def __init__( + self, + id_short: Optional[base.NameType], + content_type: Optional[base.ContentType] = None, + value: Optional[base.BlobType] = None, + display_name: Optional[base.MultiLanguageNameType] = None, + category: Optional[base.NameType] = None, + description: Optional[base.MultiLanguageTextType] = None, + parent: Optional[base.UniqueIdShortNamespace] = None, + semantic_id: Optional[base.Reference] = None, + qualifier: Iterable[base.Qualifier] = (), + extension: Iterable[base.Extension] = (), + supplemental_semantic_id: Iterable[base.Reference] = (), + embedded_data_specifications: Iterable[base.EmbeddedDataSpecification] = (), + ): """ TODO: Add instruction what to do after construction """ - super().__init__(id_short, display_name, category, description, parent, semantic_id, qualifier, extension, - supplemental_semantic_id, embedded_data_specifications) + super().__init__( + id_short, + display_name, + category, + description, + parent, + semantic_id, + qualifier, + extension, + supplemental_semantic_id, + embedded_data_specifications, + ) self.value: Optional[base.BlobType] = value self.content_type: Optional[base.ContentType] = content_type @@ -504,25 +603,37 @@ class File(DataElement): :ivar embedded_data_specifications: List of Embedded data specification. """ - def __init__(self, - id_short: Optional[base.NameType], - content_type: Optional[base.ContentType] = None, - value: Optional[base.PathType] = None, - display_name: Optional[base.MultiLanguageNameType] = None, - category: Optional[base.NameType] = None, - description: Optional[base.MultiLanguageTextType] = None, - parent: Optional[base.UniqueIdShortNamespace] = None, - semantic_id: Optional[base.Reference] = None, - qualifier: Iterable[base.Qualifier] = (), - extension: Iterable[base.Extension] = (), - supplemental_semantic_id: Iterable[base.Reference] = (), - embedded_data_specifications: Iterable[base.EmbeddedDataSpecification] = ()): + def __init__( + self, + id_short: Optional[base.NameType], + content_type: Optional[base.ContentType] = None, + value: Optional[base.PathType] = None, + display_name: Optional[base.MultiLanguageNameType] = None, + category: Optional[base.NameType] = None, + description: Optional[base.MultiLanguageTextType] = None, + parent: Optional[base.UniqueIdShortNamespace] = None, + semantic_id: Optional[base.Reference] = None, + qualifier: Iterable[base.Qualifier] = (), + extension: Iterable[base.Extension] = (), + supplemental_semantic_id: Iterable[base.Reference] = (), + embedded_data_specifications: Iterable[base.EmbeddedDataSpecification] = (), + ): """ TODO: Add instruction what to do after construction """ - super().__init__(id_short, display_name, category, description, parent, semantic_id, qualifier, extension, - supplemental_semantic_id, embedded_data_specifications) + super().__init__( + id_short, + display_name, + category, + description, + parent, + semantic_id, + qualifier, + extension, + supplemental_semantic_id, + embedded_data_specifications, + ) self.value: Optional[base.PathType] = value self.content_type: Optional[base.ContentType] = content_type @@ -559,24 +670,36 @@ class ReferenceElement(DataElement): :ivar embedded_data_specifications: List of Embedded data specification. """ - def __init__(self, - id_short: Optional[base.NameType], - value: Optional[base.Reference] = None, - display_name: Optional[base.MultiLanguageNameType] = None, - category: Optional[base.NameType] = None, - description: Optional[base.MultiLanguageTextType] = None, - parent: Optional[base.UniqueIdShortNamespace] = None, - semantic_id: Optional[base.Reference] = None, - qualifier: Iterable[base.Qualifier] = (), - extension: Iterable[base.Extension] = (), - supplemental_semantic_id: Iterable[base.Reference] = (), - embedded_data_specifications: Iterable[base.EmbeddedDataSpecification] = ()): + def __init__( + self, + id_short: Optional[base.NameType], + value: Optional[base.Reference] = None, + display_name: Optional[base.MultiLanguageNameType] = None, + category: Optional[base.NameType] = None, + description: Optional[base.MultiLanguageTextType] = None, + parent: Optional[base.UniqueIdShortNamespace] = None, + semantic_id: Optional[base.Reference] = None, + qualifier: Iterable[base.Qualifier] = (), + extension: Iterable[base.Extension] = (), + supplemental_semantic_id: Iterable[base.Reference] = (), + embedded_data_specifications: Iterable[base.EmbeddedDataSpecification] = (), + ): """ TODO: Add instruction what to do after construction """ - super().__init__(id_short, display_name, category, description, parent, semantic_id, qualifier, extension, - supplemental_semantic_id, embedded_data_specifications) + super().__init__( + id_short, + display_name, + category, + description, + parent, + semantic_id, + qualifier, + extension, + supplemental_semantic_id, + embedded_data_specifications, + ) self.value: Optional[base.Reference] = value @@ -607,22 +730,37 @@ class SubmodelElementCollection(SubmodelElement, base.UniqueIdShortNamespace): :class:`~basyx.aas.model.base.HasSemantics`) :ivar embedded_data_specifications: List of Embedded data specification. """ - def __init__(self, - id_short: Optional[base.NameType], - value: Iterable[SubmodelElement] = (), - display_name: Optional[base.MultiLanguageNameType] = None, - category: Optional[base.NameType] = None, - description: Optional[base.MultiLanguageTextType] = None, - parent: Optional[base.UniqueIdShortNamespace] = None, - semantic_id: Optional[base.Reference] = None, - qualifier: Iterable[base.Qualifier] = (), - extension: Iterable[base.Extension] = (), - supplemental_semantic_id: Iterable[base.Reference] = (), - 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) - self.value: base.NamespaceSet[SubmodelElement] = base.NamespaceSet(self, [("id_short", True)], value) + + def __init__( + self, + id_short: Optional[base.NameType], + value: Iterable[SubmodelElement] = (), + display_name: Optional[base.MultiLanguageNameType] = None, + category: Optional[base.NameType] = None, + description: Optional[base.MultiLanguageTextType] = None, + parent: Optional[base.UniqueIdShortNamespace] = None, + semantic_id: Optional[base.Reference] = None, + qualifier: Iterable[base.Qualifier] = (), + extension: Iterable[base.Extension] = (), + supplemental_semantic_id: Iterable[base.Reference] = (), + 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, + ) + self.value: base.NamespaceSet[SubmodelElement] = base.NamespaceSet( + self, [("id_short", True)], value + ) _SE = TypeVar("_SE", bound=SubmodelElement) @@ -678,43 +816,70 @@ class SubmodelElementList(SubmodelElement, base.UniqueIdShortNamespace, Generic[ :class:`~basyx.aas.model.base.HasSemantics`) :ivar embedded_data_specifications: List of Embedded data specification. """ - def __init__(self, - id_short: Optional[base.NameType], - type_value_list_element: Type[_SE], - value: Iterable[_SE] = (), - semantic_id_list_element: Optional[base.Reference] = None, - value_type_list_element: Optional[base.DataTypeDefXsd] = None, - order_relevant: bool = True, - display_name: Optional[base.MultiLanguageNameType] = None, - category: Optional[base.NameType] = None, - description: Optional[base.MultiLanguageTextType] = None, - parent: Optional[base.UniqueIdShortNamespace] = None, - semantic_id: Optional[base.Reference] = None, - qualifier: Iterable[base.Qualifier] = (), - extension: Iterable[base.Extension] = (), - supplemental_semantic_id: Iterable[base.Reference] = (), - 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) + + def __init__( + self, + id_short: Optional[base.NameType], + type_value_list_element: Type[_SE], + value: Iterable[_SE] = (), + semantic_id_list_element: Optional[base.Reference] = None, + value_type_list_element: Optional[base.DataTypeDefXsd] = None, + order_relevant: bool = True, + display_name: Optional[base.MultiLanguageNameType] = None, + category: Optional[base.NameType] = None, + description: Optional[base.MultiLanguageTextType] = None, + parent: Optional[base.UniqueIdShortNamespace] = None, + semantic_id: Optional[base.Reference] = None, + qualifier: Iterable[base.Qualifier] = (), + extension: Iterable[base.Extension] = (), + supplemental_semantic_id: Iterable[base.Reference] = (), + 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 - self._semantic_id_list_element: Optional[base.Reference] = semantic_id_list_element - self._value_type_list_element: Optional[base.DataTypeDefXsd] = value_type_list_element + self._semantic_id_list_element: Optional[base.Reference] = ( + semantic_id_list_element + ) + self._value_type_list_element: Optional[base.DataTypeDefXsd] = ( + value_type_list_element + ) - if self.type_value_list_element in (Property, Range) and self.value_type_list_element is None: - raise base.AASConstraintViolation(109, f"type_value_list_element={self.type_value_list_element.__name__}, " - "but value_type_list_element is not set!") + if ( + self.type_value_list_element in (Property, Range) + and self.value_type_list_element is None + ): + raise base.AASConstraintViolation( + 109, + f"type_value_list_element={self.type_value_list_element.__name__}, " + "but value_type_list_element is not set!", + ) # Items must be added after the above constraint 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)], (), - item_add_hook=self._check_constraints, - item_id_set_hook=self._generate_id_short, - item_id_del_hook=self._unset_id_short) + self._value: base.OrderedNamespaceSet[_SE] = base.OrderedNamespaceSet( + self, + [("id_short", True)], + (), + 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 @@ -732,11 +897,16 @@ def _generate_id_short(self, new: _SE) -> None: # 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). if new.id_short is None: - new.id_short = "generated_submodel_list_hack_" + uuid.uuid1(clock_seq=self._uuid_seq).hex + 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: - if old.id_short is not None and old.id_short.startswith("generated_submodel_list_hack_"): + if old.id_short is not None and old.id_short.startswith( + "generated_submodel_list_hack_" + ): old.id_short = None def _check_constraints(self, new: _SE, existing: Iterable[_SE]) -> None: @@ -749,40 +919,57 @@ def _check_constraints(self, new: _SE, existing: Iterable[_SE]) -> None: # self.type_value_list_element wouldn't raise a ConstraintViolation, when it should. # Example: AnnotatedRelationshipElement is a subclass of RelationshipElement if type(new) is not self.type_value_list_element: - raise base.AASConstraintViolation(108, "All first level elements must be of the type specified in " - f"type_value_list_element={self.type_value_list_element.__name__}, " - f"got {new!r}") - - if self.semantic_id_list_element is not None and new.semantic_id is not None \ - and new.semantic_id != self.semantic_id_list_element: + raise base.AASConstraintViolation( + 108, + "All first level elements must be of the type specified in " + f"type_value_list_element={self.type_value_list_element.__name__}, " + f"got {new!r}", + ) + + if ( + self.semantic_id_list_element is not None + and new.semantic_id is not None + and new.semantic_id != self.semantic_id_list_element + ): # Constraint AASd-115 specifies that if the semantic_id of an item is not specified # but semantic_id_list_element is, the semantic_id of the new is assumed to be identical. # Not really a constraint... # TODO: maybe set the semantic_id of new to semantic_id_list_element if it is None - raise base.AASConstraintViolation(107, f"If semantic_id_list_element={self.semantic_id_list_element!r} " - "is specified all first level children must have the same " - f"semantic_id, got {new!r} with semantic_id={new.semantic_id!r}") + raise base.AASConstraintViolation( + 107, + f"If semantic_id_list_element={self.semantic_id_list_element!r} " + "is specified all first level children must have the same " + f"semantic_id, got {new!r} with semantic_id={new.semantic_id!r}", + ) # If we got here we know that `new` is an instance of type_value_list_element and that type_value_list_element # is either Property or Range. Thus, `new` must have the value_type property. # Furthermore, value_type_list_element cannot be None, as this is already checked in __init__(). # Ignore the types here because the typechecker doesn't get it. - if self.type_value_list_element in (Property, Range) \ - and new.value_type is not self.value_type_list_element: # type: ignore - raise base.AASConstraintViolation(109, "All first level elements must have the value_type " # type: ignore - "specified by value_type_list_element=" - f"{self.value_type_list_element.__name__}, got " # type: ignore - f"{new!r} with value_type={new.value_type.__name__}") # type: ignore + if ( + self.type_value_list_element in (Property, Range) + and new.value_type is not self.value_type_list_element + ): # type: ignore + raise base.AASConstraintViolation( + 109, + "All first level elements must have the value_type " # type: ignore + "specified by value_type_list_element=" + f"{self.value_type_list_element.__name__}, got " # type: ignore + f"{new!r} with value_type={new.value_type.__name__}", + ) # type: ignore # If semantic_id_list_element is not None that would already enforce the semantic_id for all first level # elements. Thus, we only need to perform this check if semantic_id_list_element is None. if new.semantic_id is not None and self.semantic_id_list_element is None: for item in existing: if item.semantic_id is not None and new.semantic_id != item.semantic_id: - raise base.AASConstraintViolation(114, f"Element to be added {new!r} has semantic_id " - f"{new.semantic_id!r}, while already contained element " - f"{item!r} has semantic_id {item.semantic_id!r}, which " - "aren't equal.") + raise base.AASConstraintViolation( + 114, + f"Element to be added {new!r} has semantic_id " + f"{new.semantic_id!r}, while already contained element " + f"{item!r} has semantic_id {item.semantic_id!r}, which " + "aren't equal.", + ) # Re-assign id_short new.id_short = saved_id_short @@ -845,25 +1032,37 @@ class RelationshipElement(SubmodelElement): :ivar embedded_data_specifications: List of Embedded data specification. """ - def __init__(self, - id_short: Optional[base.NameType], - first: Optional[base.Reference] = None, - second: Optional[base.Reference] = None, - display_name: Optional[base.MultiLanguageNameType] = None, - category: Optional[base.NameType] = None, - description: Optional[base.MultiLanguageTextType] = None, - parent: Optional[base.UniqueIdShortNamespace] = None, - semantic_id: Optional[base.Reference] = None, - qualifier: Iterable[base.Qualifier] = (), - extension: Iterable[base.Extension] = (), - supplemental_semantic_id: Iterable[base.Reference] = (), - embedded_data_specifications: Iterable[base.EmbeddedDataSpecification] = ()): + def __init__( + self, + id_short: Optional[base.NameType], + first: Optional[base.Reference] = None, + second: Optional[base.Reference] = None, + display_name: Optional[base.MultiLanguageNameType] = None, + category: Optional[base.NameType] = None, + description: Optional[base.MultiLanguageTextType] = None, + parent: Optional[base.UniqueIdShortNamespace] = None, + semantic_id: Optional[base.Reference] = None, + qualifier: Iterable[base.Qualifier] = (), + extension: Iterable[base.Extension] = (), + supplemental_semantic_id: Iterable[base.Reference] = (), + embedded_data_specifications: Iterable[base.EmbeddedDataSpecification] = (), + ): """ TODO: Add instruction what to do after construction """ - super().__init__(id_short, display_name, category, description, parent, semantic_id, qualifier, extension, - supplemental_semantic_id, embedded_data_specifications) + super().__init__( + id_short, + display_name, + category, + description, + parent, + semantic_id, + qualifier, + extension, + supplemental_semantic_id, + embedded_data_specifications, + ) self.first: Optional[base.Reference] = first self.second: Optional[base.Reference] = second @@ -902,26 +1101,40 @@ class AnnotatedRelationshipElement(RelationshipElement, base.UniqueIdShortNamesp :ivar embedded_data_specifications: List of Embedded data specification. """ - def __init__(self, - id_short: Optional[base.NameType], - first: Optional[base.Reference] = None, - second: Optional[base.Reference] = None, - display_name: Optional[base.MultiLanguageNameType] = None, - annotation: Iterable[DataElement] = (), - category: Optional[base.NameType] = None, - description: Optional[base.MultiLanguageTextType] = None, - parent: Optional[base.UniqueIdShortNamespace] = None, - semantic_id: Optional[base.Reference] = None, - qualifier: Iterable[base.Qualifier] = (), - extension: Iterable[base.Extension] = (), - supplemental_semantic_id: Iterable[base.Reference] = (), - embedded_data_specifications: Iterable[base.EmbeddedDataSpecification] = ()): + def __init__( + self, + id_short: Optional[base.NameType], + first: Optional[base.Reference] = None, + second: Optional[base.Reference] = None, + display_name: Optional[base.MultiLanguageNameType] = None, + annotation: Iterable[DataElement] = (), + category: Optional[base.NameType] = None, + description: Optional[base.MultiLanguageTextType] = None, + parent: Optional[base.UniqueIdShortNamespace] = None, + semantic_id: Optional[base.Reference] = None, + qualifier: Iterable[base.Qualifier] = (), + extension: Iterable[base.Extension] = (), + supplemental_semantic_id: Iterable[base.Reference] = (), + embedded_data_specifications: Iterable[base.EmbeddedDataSpecification] = (), + ): """ TODO: Add instruction what to do after construction """ - super().__init__(id_short, first, second, display_name, category, description, parent, semantic_id, qualifier, - extension, supplemental_semantic_id, embedded_data_specifications) + super().__init__( + id_short, + first, + second, + display_name, + category, + description, + parent, + semantic_id, + qualifier, + extension, + supplemental_semantic_id, + embedded_data_specifications, + ) self.annotation = base.NamespaceSet(self, [("id_short", True)], annotation) @@ -964,29 +1177,48 @@ class Operation(SubmodelElement, base.UniqueIdShortNamespace): :class:`~basyx.aas.model.base.HasSemantics`) :ivar embedded_data_specifications: List of Embedded data specification. """ - def __init__(self, - id_short: Optional[base.NameType], - input_variable: Iterable[SubmodelElement] = (), - output_variable: Iterable[SubmodelElement] = (), - in_output_variable: Iterable[SubmodelElement] = (), - display_name: Optional[base.MultiLanguageNameType] = None, - category: Optional[base.NameType] = None, - description: Optional[base.MultiLanguageTextType] = None, - parent: Optional[base.UniqueIdShortNamespace] = None, - semantic_id: Optional[base.Reference] = None, - qualifier: Iterable[base.Qualifier] = (), - extension: Iterable[base.Extension] = (), - supplemental_semantic_id: Iterable[base.Reference] = (), - embedded_data_specifications: Iterable[base.EmbeddedDataSpecification] = ()): + + def __init__( + self, + id_short: Optional[base.NameType], + input_variable: Iterable[SubmodelElement] = (), + output_variable: Iterable[SubmodelElement] = (), + in_output_variable: Iterable[SubmodelElement] = (), + display_name: Optional[base.MultiLanguageNameType] = None, + category: Optional[base.NameType] = None, + description: Optional[base.MultiLanguageTextType] = None, + parent: Optional[base.UniqueIdShortNamespace] = None, + semantic_id: Optional[base.Reference] = None, + qualifier: Iterable[base.Qualifier] = (), + extension: Iterable[base.Extension] = (), + supplemental_semantic_id: Iterable[base.Reference] = (), + embedded_data_specifications: Iterable[base.EmbeddedDataSpecification] = (), + ): """ TODO: Add instruction what to do after construction """ - super().__init__(id_short, display_name, category, description, parent, semantic_id, qualifier, extension, - supplemental_semantic_id, embedded_data_specifications) - self.input_variable = base.NamespaceSet(self, [("id_short", True)], input_variable) - self.output_variable = base.NamespaceSet(self, [("id_short", True)], output_variable) - self.in_output_variable = base.NamespaceSet(self, [("id_short", True)], in_output_variable) + super().__init__( + id_short, + display_name, + category, + description, + parent, + semantic_id, + qualifier, + extension, + supplemental_semantic_id, + embedded_data_specifications, + ) + self.input_variable = base.NamespaceSet( + self, [("id_short", True)], input_variable + ) + self.output_variable = base.NamespaceSet( + self, [("id_short", True)], output_variable + ) + self.in_output_variable = base.NamespaceSet( + self, [("id_short", True)], in_output_variable + ) class Capability(SubmodelElement): @@ -1017,23 +1249,35 @@ class Capability(SubmodelElement): :ivar embedded_data_specifications: List of Embedded data specification. """ - def __init__(self, - id_short: Optional[base.NameType], - display_name: Optional[base.MultiLanguageNameType] = None, - category: Optional[base.NameType] = None, - description: Optional[base.MultiLanguageTextType] = None, - parent: Optional[base.UniqueIdShortNamespace] = None, - semantic_id: Optional[base.Reference] = None, - qualifier: Iterable[base.Qualifier] = (), - extension: Iterable[base.Extension] = (), - supplemental_semantic_id: Iterable[base.Reference] = (), - embedded_data_specifications: Iterable[base.EmbeddedDataSpecification] = ()): + def __init__( + self, + id_short: Optional[base.NameType], + display_name: Optional[base.MultiLanguageNameType] = None, + category: Optional[base.NameType] = None, + description: Optional[base.MultiLanguageTextType] = None, + parent: Optional[base.UniqueIdShortNamespace] = None, + semantic_id: Optional[base.Reference] = None, + qualifier: Iterable[base.Qualifier] = (), + extension: Iterable[base.Extension] = (), + supplemental_semantic_id: Iterable[base.Reference] = (), + embedded_data_specifications: Iterable[base.EmbeddedDataSpecification] = (), + ): """ TODO: Add instruction what to do after construction """ - super().__init__(id_short, display_name, category, description, parent, semantic_id, qualifier, extension, - supplemental_semantic_id, embedded_data_specifications) + super().__init__( + id_short, + display_name, + category, + description, + parent, + semantic_id, + qualifier, + extension, + supplemental_semantic_id, + embedded_data_specifications, + ) class Entity(SubmodelElement, base.UniqueIdShortNamespace): @@ -1073,35 +1317,49 @@ class Entity(SubmodelElement, base.UniqueIdShortNamespace): :ivar embedded_data_specifications: List of Embedded data specification. """ - def __init__(self, - id_short: Optional[base.NameType], - entity_type: Optional[base.EntityType], - statement: Iterable[SubmodelElement] = (), - global_asset_id: Optional[base.Identifier] = None, - specific_asset_id: Iterable[base.SpecificAssetId] = (), - display_name: Optional[base.MultiLanguageNameType] = None, - category: Optional[base.NameType] = None, - description: Optional[base.MultiLanguageTextType] = None, - parent: Optional[base.UniqueIdShortNamespace] = None, - semantic_id: Optional[base.Reference] = None, - qualifier: Iterable[base.Qualifier] = (), - extension: Iterable[base.Extension] = (), - supplemental_semantic_id: Iterable[base.Reference] = (), - embedded_data_specifications: Iterable[base.EmbeddedDataSpecification] = ()): + def __init__( + self, + id_short: Optional[base.NameType], + entity_type: Optional[base.EntityType], + statement: Iterable[SubmodelElement] = (), + global_asset_id: Optional[base.Identifier] = None, + specific_asset_id: Iterable[base.SpecificAssetId] = (), + display_name: Optional[base.MultiLanguageNameType] = None, + category: Optional[base.NameType] = None, + description: Optional[base.MultiLanguageTextType] = None, + parent: Optional[base.UniqueIdShortNamespace] = None, + semantic_id: Optional[base.Reference] = None, + qualifier: Iterable[base.Qualifier] = (), + extension: Iterable[base.Extension] = (), + supplemental_semantic_id: Iterable[base.Reference] = (), + embedded_data_specifications: Iterable[base.EmbeddedDataSpecification] = (), + ): """ TODO: Add instruction what to do after construction """ - super().__init__(id_short, display_name, category, description, parent, semantic_id, qualifier, extension, - supplemental_semantic_id, embedded_data_specifications) + 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) # assign private attributes, bypassing setters, as constraints will be checked below self._entity_type: Optional[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._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)) @@ -1112,7 +1370,9 @@ def entity_type(self) -> Optional[base.EntityType]: @entity_type.setter def entity_type(self, entity_type: Optional[base.EntityType]) -> None: - self._validate_aasd_014(entity_type, self.global_asset_id, bool(self.specific_asset_id)) + self._validate_aasd_014( + entity_type, self.global_asset_id, bool(self.specific_asset_id) + ) self._entity_type = entity_type @property @@ -1122,7 +1382,9 @@ def global_asset_id(self) -> Optional[base.Identifier]: @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._validate_aasd_014( + self.entity_type, global_asset_id, bool(self.specific_asset_id) + ) self._global_asset_id = global_asset_id @property @@ -1130,23 +1392,35 @@ 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: + 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: + 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_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) + 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: @@ -1154,19 +1428,29 @@ def _validate_global_asset_id(global_asset_id: Optional[base.Identifier]) -> Non _string_constraints.check_identifier(global_asset_id) @staticmethod - def _validate_aasd_014(entity_type: Optional[base.EntityType], - global_asset_id: Optional[base.Identifier], - specific_asset_id_nonempty: bool) -> None: + def _validate_aasd_014( + entity_type: Optional[base.EntityType], + global_asset_id: Optional[base.Identifier], + specific_asset_id_nonempty: bool, + ) -> None: if entity_type is None: return - if entity_type == base.EntityType.SELF_MANAGED_ENTITY and global_asset_id is None \ - and not specific_asset_id_nonempty: + 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): + 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") + 14, + "A co-managed entity has to have neither a globalAssetId nor a specificAssetId", + ) class EventElement(SubmodelElement, metaclass=abc.ABCMeta): @@ -1196,20 +1480,33 @@ class EventElement(SubmodelElement, metaclass=abc.ABCMeta): :class:`~basyx.aas.model.base.HasSemantics`) :ivar embedded_data_specifications: List of Embedded data specification. """ + @abc.abstractmethod - def __init__(self, - id_short: Optional[base.NameType], - display_name: Optional[base.MultiLanguageNameType] = None, - category: Optional[base.NameType] = None, - description: Optional[base.MultiLanguageTextType] = None, - parent: Optional[base.UniqueIdShortNamespace] = None, - semantic_id: Optional[base.Reference] = None, - qualifier: Iterable[base.Qualifier] = (), - extension: Iterable[base.Extension] = (), - supplemental_semantic_id: Iterable[base.Reference] = (), - 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) + def __init__( + self, + id_short: Optional[base.NameType], + display_name: Optional[base.MultiLanguageNameType] = None, + category: Optional[base.NameType] = None, + description: Optional[base.MultiLanguageTextType] = None, + parent: Optional[base.UniqueIdShortNamespace] = None, + semantic_id: Optional[base.Reference] = None, + qualifier: Iterable[base.Qualifier] = (), + extension: Iterable[base.Extension] = (), + supplemental_semantic_id: Iterable[base.Reference] = (), + 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, + ) @_string_constraints.constrain_message_topic_type("message_topic") @@ -1265,40 +1562,62 @@ class BasicEventElement(EventElement): :ivar embedded_data_specifications: List of Embedded data specification. """ - def __init__(self, - id_short: Optional[base.NameType], - observed: base.ModelReference[Union["aas.AssetAdministrationShell", Submodel, SubmodelElement]], - direction: base.Direction, - state: base.StateOfEvent, - message_topic: Optional[base.MessageTopicType] = None, - message_broker: Optional[base.ModelReference[Union[Submodel, SubmodelElementList, - SubmodelElementCollection, Entity]]] = None, - last_update: Optional[datatypes.DateTime] = None, - min_interval: Optional[datatypes.Duration] = None, - max_interval: Optional[datatypes.Duration] = None, - display_name: Optional[base.MultiLanguageNameType] = None, - category: Optional[base.NameType] = None, - description: Optional[base.MultiLanguageTextType] = None, - parent: Optional[base.UniqueIdShortNamespace] = None, - semantic_id: Optional[base.Reference] = None, - qualifier: Iterable[base.Qualifier] = (), - extension: Iterable[base.Extension] = (), - supplemental_semantic_id: Iterable[base.Reference] = (), - embedded_data_specifications: Iterable[base.EmbeddedDataSpecification] = ()): + def __init__( + self, + id_short: Optional[base.NameType], + observed: base.ModelReference[ + Union["aas.AssetAdministrationShell", Submodel, SubmodelElement] + ], + direction: base.Direction, + state: base.StateOfEvent, + message_topic: Optional[base.MessageTopicType] = None, + message_broker: Optional[ + base.ModelReference[ + Union[Submodel, SubmodelElementList, SubmodelElementCollection, Entity] + ] + ] = None, + last_update: Optional[datatypes.DateTime] = None, + min_interval: Optional[datatypes.Duration] = None, + max_interval: Optional[datatypes.Duration] = None, + display_name: Optional[base.MultiLanguageNameType] = None, + category: Optional[base.NameType] = None, + description: Optional[base.MultiLanguageTextType] = None, + parent: Optional[base.UniqueIdShortNamespace] = None, + semantic_id: Optional[base.Reference] = None, + qualifier: Iterable[base.Qualifier] = (), + extension: Iterable[base.Extension] = (), + supplemental_semantic_id: Iterable[base.Reference] = (), + embedded_data_specifications: Iterable[base.EmbeddedDataSpecification] = (), + ): """ TODO: Add instruction what to do after construction """ - super().__init__(id_short, display_name, category, description, parent, semantic_id, qualifier, extension, - supplemental_semantic_id, embedded_data_specifications) - self.observed: base.ModelReference[Union["aas.AssetAdministrationShell", Submodel, SubmodelElement]] = observed + super().__init__( + id_short, + display_name, + category, + description, + parent, + semantic_id, + qualifier, + extension, + supplemental_semantic_id, + embedded_data_specifications, + ) + self.observed: base.ModelReference[ + Union["aas.AssetAdministrationShell", Submodel, SubmodelElement] + ] = observed # max_interval must be set here because the direction setter attempts to read it self.max_interval: Optional[datatypes.Duration] = None self.direction: base.Direction = direction self.state: base.StateOfEvent = state self.message_topic: Optional[base.MessageTopicType] = message_topic - self.message_broker: Optional[base.ModelReference[Union[Submodel, SubmodelElementList, - SubmodelElementCollection, Entity]]] = message_broker + self.message_broker: Optional[ + base.ModelReference[ + Union[Submodel, SubmodelElementList, SubmodelElementCollection, Entity] + ] + ] = message_broker self.last_update: Optional[datatypes.DateTime] = last_update self.min_interval: Optional[datatypes.Duration] = min_interval self.max_interval: Optional[datatypes.Duration] = max_interval diff --git a/sdk/basyx/aas/util/identification.py b/sdk/basyx/aas/util/identification.py index 8d6a1b77e..60a4f0a3d 100644 --- a/sdk/basyx/aas/util/identification.py +++ b/sdk/basyx/aas/util/identification.py @@ -31,6 +31,7 @@ class AbstractIdentifierGenerator(metaclass=abc.ABCMeta): IRDIs, etc. Some of them may use a given private namespace and create ids within this namespace, others may just use long random numbers to ensure uniqueness. """ + @abc.abstractmethod def generate_id(self, proposal: Optional[str] = None) -> model.Identifier: """ @@ -47,6 +48,7 @@ class UUIDGenerator(AbstractIdentifierGenerator): """ An IdentifierGenerator, that generates URNs of version 1 UUIDs according to RFC 4122. """ + def __init__(self): super().__init__() self._sequence = 0 @@ -70,7 +72,12 @@ class NamespaceIRIGenerator(AbstractIdentifierGenerator): :ivar provider: An :class:`~basyx.aas.model.provider.AbstractObjectProvider` to check existence of :class:`Identifiers ` """ - def __init__(self, namespace: str, provider: model.AbstractObjectProvider[model.Identifier, model.Identifiable]): + + def __init__( + self, + namespace: str, + provider: model.AbstractObjectProvider[model.Identifier, model.Identifiable], + ): """ Create a new NamespaceIRIGenerator :param namespace: The IRI Namespace to generate Identifiers in. It must be a valid IRI (starting with a @@ -78,7 +85,7 @@ def __init__(self, namespace: str, provider: model.AbstractObjectProvider[model. :param provider: An AbstractObjectProvider to check existence of Identifiers """ super().__init__() - if not re.match(r'^[a-zA-Z][a-zA-Z0-9+\-\.]*:.*[#/=]$', namespace): + if not re.match(r"^[a-zA-Z][a-zA-Z0-9+\-\.]*:.*[#/=]$", namespace): raise ValueError("Namespace must be a valid IRI, ending with #, / or =") self.provider = provider self._namespace = namespace @@ -95,7 +102,9 @@ def generate_id(self, proposal: Optional[str] = None) -> model.Identifier: counter: int = self._counter_cache.get(proposal, 0) while True: if counter or not proposal: - iri = "{}{}{}{:04d}".format(self._namespace, proposal, "_" if proposal else "", counter) + iri = "{}{}{}{:04d}".format( + self._namespace, proposal, "_" if proposal else "", counter + ) else: iri = "{}{}".format(self._namespace, proposal) # Try to find iri in provider. If it does not exist (KeyError), we found a unique one to return @@ -111,18 +120,39 @@ def generate_id(self, proposal: Optional[str] = None) -> model.Identifier: # minus '/', '?', '=', '&', '#', which can be used in a path, querystring and fragment # plus not allowed characters (see) https://stackoverflow.com/a/36667242/10315508 _iri_segment_quote_table_tmpl: Dict[Union[str, int], Optional[str]] = { - c: '%{:02X}'.format(c.encode()[0]) + c: "%{:02X}".format(c.encode()[0]) for c in [ - ':', '[', ']', '@', # '/', '?', '#', - '!', '$', '\'', '(', ')', '*', '+', ',', ';', # '=', '&', - ' ', '"', '<', '>', '\\', '^', '`', '{', '|', '}', - ]} + ":", + "[", + "]", + "@", # '/', '?', '#', + "!", + "$", + "'", + "(", + ")", + "*", + "+", + ",", + ";", # '=', '&', + " ", + '"', + "<", + ">", + "\\", + "^", + "`", + "{", + "|", + "}", + ] +} # Remove ASCII control characters -_iri_segment_quote_table_tmpl.update({ - i: None - for i in range(0, 0x1f)}) -_iri_segment_quote_table_tmpl[0x7f] = None -_iri_segment_quote_table: Dict[int, Optional[str]] = str.maketrans(_iri_segment_quote_table_tmpl) +_iri_segment_quote_table_tmpl.update({i: None for i in range(0, 0x1F)}) +_iri_segment_quote_table_tmpl[0x7F] = None +_iri_segment_quote_table: Dict[int, Optional[str]] = str.maketrans( + _iri_segment_quote_table_tmpl +) def _quote_iri_segment(segment: str) -> str: diff --git a/sdk/basyx/aas/util/traversal.py b/sdk/basyx/aas/util/traversal.py index 34f809a72..cfe9ed60f 100644 --- a/sdk/basyx/aas/util/traversal.py +++ b/sdk/basyx/aas/util/traversal.py @@ -13,7 +13,9 @@ from .. import model -def walk_submodel_element(element: model.SubmodelElement) -> Iterator[model.SubmodelElement]: +def walk_submodel_element( + element: model.SubmodelElement, +) -> Iterator[model.SubmodelElement]: """ Traverse all :class:`SubmodelElements ` contained within the given element recursively, i.e. the children of: @@ -27,12 +29,18 @@ def walk_submodel_element(element: model.SubmodelElement) -> Iterator[model.Subm No :class:`SubmodelElements ` should be added, removed or moved while iterating, as this could result in undefined behaviour. """ - if isinstance(element, (model.SubmodelElementCollection, model.SubmodelElementList)): + if isinstance( + element, (model.SubmodelElementCollection, model.SubmodelElementList) + ): for sub_element in element.value: yield from walk_submodel_element(sub_element) yield sub_element elif isinstance(element, model.Operation): - for var_list in (element.input_variable, element.output_variable, element.in_output_variable): + for var_list in ( + element.input_variable, + element.output_variable, + element.in_output_variable, + ): for sub_element in var_list: yield from walk_submodel_element(sub_element) yield sub_element @@ -72,7 +80,10 @@ def walk_semantic_ids_recursive(root: model.Referable) -> Iterator[model.Referen # Qualifier is the only non-Referable class which HasSemantics if isinstance(root, model.Qualifiable): for qualifier in root.qualifier: - if isinstance(qualifier, model.Qualifier) and qualifier.semantic_id is not None: + if ( + isinstance(qualifier, model.Qualifier) + and qualifier.semantic_id is not None + ): yield qualifier.semantic_id if isinstance(root, model.UniqueIdShortNamespace): for element in root: # iterates Referable objects in Namespace diff --git a/sdk/docs/source/conf.py b/sdk/docs/source/conf.py index 976c72dac..5c4aedb5e 100644 --- a/sdk/docs/source/conf.py +++ b/sdk/docs/source/conf.py @@ -15,15 +15,15 @@ import datetime -sys.path.insert(0, os.path.abspath('../..')) +sys.path.insert(0, os.path.abspath("../..")) from basyx.aas import __version__ # -- Project information ----------------------------------------------------- -project = 'Eclipse BaSyx Python SDK' -project_copyright = str(datetime.datetime.now().year) + ', the Eclipse BaSyx Authors' -author = 'The Eclipse BaSyx Authors' +project = "Eclipse BaSyx Python SDK" +project_copyright = str(datetime.datetime.now().year) + ", the Eclipse BaSyx Authors" +author = "The Eclipse BaSyx Authors" # The full version, including alpha/beta/rc tags release = __version__ @@ -36,11 +36,11 @@ # ones. extensions = [ - 'sphinx.ext.autodoc', - 'sphinx.ext.coverage', - 'sphinx.ext.intersphinx', - 'sphinx_rtd_theme', - 'sphinxarg.ext' + "sphinx.ext.autodoc", + "sphinx.ext.coverage", + "sphinx.ext.intersphinx", + "sphinx_rtd_theme", + "sphinxarg.ext", ] # Add any paths that contain templates here, relative to this directory. @@ -55,16 +55,13 @@ add_module_names = False # Include all public documented and undocumented members by default. -autodoc_default_options = { - 'members': True, - 'undoc-members': True -} +autodoc_default_options = {"members": True, "undoc-members": True} # Mapping for correctly linking other module documentations. intersphinx_mapping = { - 'python': ('https://docs.python.org/3', None), - 'dateutil': ('https://dateutil.readthedocs.io/en/stable/', None), - 'lxml': ('https://lxml.de/apidoc/', None) + "python": ("https://docs.python.org/3", None), + "dateutil": ("https://dateutil.readthedocs.io/en/stable/", None), + "lxml": ("https://lxml.de/apidoc/", None), } @@ -86,23 +83,23 @@ def setup(app): # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # -html_theme = 'sphinx_rtd_theme' +html_theme = "sphinx_rtd_theme" # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] +html_static_path = ["_static"] # Fix white-space wrapping in tables. # See https://github.com/readthedocs/sphinx_rtd_theme/issues/1505 # Once fixed, this can be removed and '_static' can be removed from html_static_path. -html_style = 'custom.css' +html_style = "custom.css" # Configuration of the 'Edit on GitHub' button at the top right. html_context = { - 'display_github': True, - 'github_user': 'eclipse-basyx', - 'github_repo': 'basyx-python-sdk', - 'github_version': release, - 'conf_py_path': '/docs/source/' + "display_github": True, + "github_user": "eclipse-basyx", + "github_repo": "basyx-python-sdk", + "github_version": release, + "conf_py_path": "/docs/source/", } diff --git a/sdk/test/_helper/setup_testdb.py b/sdk/test/_helper/setup_testdb.py index 20f56c4d9..63dc2f5ad 100755 --- a/sdk/test/_helper/setup_testdb.py +++ b/sdk/test/_helper/setup_testdb.py @@ -16,6 +16,7 @@ If no CouchDB server at the configured URL, the script will exit with exit code 1. To avoid the error exit code (for use in CI), provide the ``--failsafe`` option. """ + import base64 import configparser import argparse @@ -27,69 +28,92 @@ # Parse test config (required to setup the CouchDB as expected) TEST_CONFIG = configparser.ConfigParser() -TEST_CONFIG.read((os.path.join(os.path.dirname(__file__), "..", "test_config.default.ini"), - os.path.join(os.path.dirname(__file__), "..", "test_config.ini"))) +TEST_CONFIG.read( + ( + os.path.join(os.path.dirname(__file__), "..", "test_config.default.ini"), + os.path.join(os.path.dirname(__file__), "..", "test_config.ini"), + ) +) # Parse command line arguments -parser = argparse.ArgumentParser(description='Setup CouchDB test database according to test_config.ini') -parser.add_argument('--admin-user', '-u', help='Name of the CouchDB admin user') -parser.add_argument('--admin-password', '-p', help='Password of the CouchDB admin user', default='') -parser.add_argument('--failsafe', '-f', action='store_true', - help='Exit with exit code 0 even if no database server could be reached or the database already ' - 'exists on the server.') +parser = argparse.ArgumentParser( + description="Setup CouchDB test database according to test_config.ini" +) +parser.add_argument("--admin-user", "-u", help="Name of the CouchDB admin user") +parser.add_argument( + "--admin-password", "-p", help="Password of the CouchDB admin user", default="" +) +parser.add_argument( + "--failsafe", + "-f", + action="store_true", + help="Exit with exit code 0 even if no database server could be reached or the database already " + "exists on the server.", +) args = parser.parse_args() # Some basic data for couchdb setup default_headers = { - 'Accept': 'application/json', + "Accept": "application/json", } if args.admin_user is not None: - default_headers['Authorization'] = 'Basic %s' % base64.b64encode( - ('%s:%s' % (args.admin_user, args.admin_password)).encode('ascii')).decode("ascii") + default_headers["Authorization"] = "Basic %s" % base64.b64encode( + ("%s:%s" % (args.admin_user, args.admin_password)).encode("ascii") + ).decode("ascii") # Check if CouchDB server is available request = urllib.request.Request( - TEST_CONFIG['couchdb']['url'], - headers=default_headers, - method='HEAD') + TEST_CONFIG["couchdb"]["url"], headers=default_headers, method="HEAD" +) try: urllib.request.urlopen(request) except urllib.error.URLError as e: - print("Could not reach CouchDB server at {}: {}".format(TEST_CONFIG['couchdb']['url'], e)) + print( + "Could not reach CouchDB server at {}: {}".format( + TEST_CONFIG["couchdb"]["url"], e + ) + ) sys.exit(0 if args.failsafe else 1) # The actual work # Check if the System databases exist and create them otherwise request = urllib.request.Request( - "{}/_users".format(TEST_CONFIG['couchdb']['url']), + "{}/_users".format(TEST_CONFIG["couchdb"]["url"]), headers=default_headers, - method='HEAD') + method="HEAD", +) try: urllib.request.urlopen(request) except urllib.error.HTTPError as e: if e.code != 404: raise - for db in ('_global_changes', '_replicator', '_users'): + for db in ("_global_changes", "_replicator", "_users"): request = urllib.request.Request( - "{}/{}".format(TEST_CONFIG['couchdb']['url'], db), + "{}/{}".format(TEST_CONFIG["couchdb"]["url"], db), headers=default_headers, - method='PUT') + method="PUT", + ) urllib.request.urlopen(request) # Create the database if not existing request = urllib.request.Request( - "{}/{}".format(TEST_CONFIG['couchdb']['url'], TEST_CONFIG['couchdb']['database']), + "{}/{}".format(TEST_CONFIG["couchdb"]["url"], TEST_CONFIG["couchdb"]["database"]), headers=default_headers, - method='PUT') + method="PUT", +) try: urllib.request.urlopen(request) except urllib.error.HTTPError as e: if e.code == 412: # TODO make more failsafe: Delete and recreate database if it exists already - print("CouchDB database {} already existed. Exiting ...".format(TEST_CONFIG['couchdb']['database'])) + print( + "CouchDB database {} already existed. Exiting ...".format( + TEST_CONFIG["couchdb"]["database"] + ) + ) sys.exit(0 if args.failsafe else 1) else: raise @@ -97,15 +121,20 @@ # Create the user if not existing request = urllib.request.Request( - "{}/_users/org.couchdb.user:{}".format(TEST_CONFIG['couchdb']['url'], TEST_CONFIG['couchdb']['user']), + "{}/_users/org.couchdb.user:{}".format( + TEST_CONFIG["couchdb"]["url"], TEST_CONFIG["couchdb"]["user"] + ), headers=default_headers, - method='PUT', - data=json.dumps({ - "name": TEST_CONFIG['couchdb']['user'], - "password": TEST_CONFIG['couchdb']['password'], - "roles": [], - "type": "user" - }).encode()) + method="PUT", + data=json.dumps( + { + "name": TEST_CONFIG["couchdb"]["user"], + "password": TEST_CONFIG["couchdb"]["password"], + "roles": [], + "type": "user", + } + ).encode(), +) # TODO make more failsafe: Set password of user if they exist already urllib.request.urlopen(request) @@ -113,15 +142,16 @@ # Add user as member of database # TODO make more failsafe: Keep existing authorizations request = urllib.request.Request( - "{}/{}/_security".format(TEST_CONFIG['couchdb']['url'], TEST_CONFIG['couchdb']['database']), + "{}/{}/_security".format( + TEST_CONFIG["couchdb"]["url"], TEST_CONFIG["couchdb"]["database"] + ), headers=default_headers, - method='PUT', - data=json.dumps({ - "admins": { - "names": [], - "roles": []}, - "members": { - "names": [TEST_CONFIG['couchdb']['user']], - "roles": []} - }).encode()) + method="PUT", + data=json.dumps( + { + "admins": {"names": [], "roles": []}, + "members": {"names": [TEST_CONFIG["couchdb"]["user"]], "roles": []}, + } + ).encode(), +) urllib.request.urlopen(request) diff --git a/sdk/test/_helper/test_helpers.py b/sdk/test/_helper/test_helpers.py index 24be3bc7f..96cae288f 100644 --- a/sdk/test/_helper/test_helpers.py +++ b/sdk/test/_helper/test_helpers.py @@ -5,20 +5,34 @@ import base64 TEST_CONFIG = configparser.ConfigParser() -TEST_CONFIG.read((os.path.join(os.path.dirname(__file__), "..", "test_config.default.ini"), - os.path.join(os.path.dirname(__file__), "..", "test_config.ini"))) +TEST_CONFIG.read( + ( + os.path.join(os.path.dirname(__file__), "..", "test_config.default.ini"), + os.path.join(os.path.dirname(__file__), "..", "test_config.ini"), + ) +) # Check if CouchDB database is available. Otherwise, skip tests. try: request = urllib.request.Request( - "{}/{}".format(TEST_CONFIG['couchdb']['url'], TEST_CONFIG['couchdb']['database']), + "{}/{}".format( + TEST_CONFIG["couchdb"]["url"], TEST_CONFIG["couchdb"]["database"] + ), headers={ - 'Authorization': 'Basic %s' % base64.b64encode( - ('%s:%s' % (TEST_CONFIG['couchdb']['user'], TEST_CONFIG['couchdb']['password'])) - .encode('ascii')).decode("ascii") - }, - method='HEAD') + "Authorization": "Basic %s" + % base64.b64encode( + ( + "%s:%s" + % ( + TEST_CONFIG["couchdb"]["user"], + TEST_CONFIG["couchdb"]["password"], + ) + ).encode("ascii") + ).decode("ascii") + }, + method="HEAD", + ) urllib.request.urlopen(request) COUCHDB_OKAY = True COUCHDB_ERROR = None diff --git a/sdk/test/adapter/aasx/test_aasx.py b/sdk/test/adapter/aasx/test_aasx.py index e4ab1a1dd..1d2f35393 100644 --- a/sdk/test/adapter/aasx/test_aasx.py +++ b/sdk/test/adapter/aasx/test_aasx.py @@ -16,25 +16,33 @@ import pyecma376_2 from basyx.aas import model from basyx.aas.adapter import aasx -from basyx.aas.examples.data import example_aas, example_aas_mandatory_attributes, _helper +from basyx.aas.examples.data import ( + example_aas, + example_aas_mandatory_attributes, + _helper, +) class TestAASXUtils(unittest.TestCase): def test_supplementary_file_container(self) -> None: container = aasx.DictSupplementaryFileContainer() - with open(os.path.join(os.path.dirname(__file__), 'TestFile.pdf'), 'rb') as f: + with open(os.path.join(os.path.dirname(__file__), "TestFile.pdf"), "rb") as f: saved_file_name = container.add_file("/TestFile.pdf", f, "application/pdf") # Name should not be modified, since there is no conflict self.assertEqual("/TestFile.pdf", saved_file_name) f.seek(0) # Add the same file again with the same name - same_file_with_same_name = container.add_file("/TestFile.pdf", f, "application/pdf") + same_file_with_same_name = container.add_file( + "/TestFile.pdf", f, "application/pdf" + ) # Name should not be modified, since there is still no conflict self.assertEqual("/TestFile.pdf", same_file_with_same_name) # Add other file with the same name to create a conflict - with open(__file__, 'rb') as f: - saved_file_name_2 = container.add_file("/TestFile.pdf", f, "application/pdf") + with open(__file__, "rb") as f: + saved_file_name_2 = container.add_file( + "/TestFile.pdf", f, "application/pdf" + ) # Now, we have a conflict self.assertNotEqual(saved_file_name, saved_file_name_2) self.assertIn(saved_file_name_2, container) @@ -63,17 +71,22 @@ def test_supplementary_file_container(self) -> None: # Check metadata self.assertEqual("application/pdf", container.get_content_type("/TestFile.pdf")) - self.assertEqual("142a0061de1ef5c22137ab05bb6001335596c0fc8693d33fa9b011ceac652342", - container.get_sha256("/TestFile.pdf").hex()) + self.assertEqual( + "142a0061de1ef5c22137ab05bb6001335596c0fc8693d33fa9b011ceac652342", + container.get_sha256("/TestFile.pdf").hex(), + ) self.assertIn("/TestFile.pdf", container) # Check contents file_content = io.BytesIO() container.write_file("/TestFile.pdf", file_content) - self.assertEqual(hashlib.sha1(file_content.getvalue()).hexdigest(), "241e62aef8b4cdad0975f6c68a4ed8b3923d8db1") + self.assertEqual( + hashlib.sha1(file_content.getvalue()).hexdigest(), + "241e62aef8b4cdad0975f6c68a4ed8b3923d8db1", + ) # Add same file again with different content_type to test reference counting - with open(__file__, 'rb') as f: + with open(__file__, "rb") as f: duplicate_file = container.add_file("/TestFile.pdf", f, "image/jpeg") self.assertIn(duplicate_file, container) @@ -92,8 +105,12 @@ def test_supplementary_file_container(self) -> None: def test_supplementary_file_container_refcount(self) -> None: container = aasx.DictSupplementaryFileContainer() data = b"test content" - name1 = container.add_file("/file1.bin", io.BytesIO(data), "application/octet-stream") - name2 = container.add_file("/file2.bin", io.BytesIO(data), "application/octet-stream") + name1 = container.add_file( + "/file1.bin", io.BytesIO(data), "application/octet-stream" + ) + name2 = container.add_file( + "/file2.bin", io.BytesIO(data), "application/octet-stream" + ) content_hash = container.get_sha256(name1) # Both names point to same content — backing store must be present @@ -122,14 +139,19 @@ def test_write_missing_aas_objects(self): # try to write non-existing object writer.write_aas_objects( "/aasx/selection.xml", - ["https://example.org/Test_AssetAdministrationShell", - "http://false-identifier.org/", - "http://example.org/Submodels/Assets/TestAsset/Identification"], - data, aasx.DictSupplementaryFileContainer() + [ + "https://example.org/Test_AssetAdministrationShell", + "http://false-identifier.org/", + "http://example.org/Submodels/Assets/TestAsset/Identification", + ], + data, + aasx.DictSupplementaryFileContainer(), ) - self.assertIn("Could not find identifiable http://false-identifier.org/ in IdentifiableStore", - log.output[0]) + self.assertIn( + "Could not find identifiable http://false-identifier.org/ in IdentifiableStore", + log.output[0], + ) # assert only the two existing objects have been written to aasx file object_store = model.DictIdentifiableStore() @@ -151,43 +173,56 @@ def test_writing_with_missing_file(self) -> None: # assert warning is present in failsafe mode with self.assertLogs(level="WARNING") as log: with aasx.AASXWriter(tmpdir_path / "tmp.aasx", failsafe=True) as writer: - writer.write_all_aas_objects("/aasx/data.xml", data, empty_file_store) + writer.write_all_aas_objects( + "/aasx/data.xml", data, empty_file_store + ) self.assertIn("Could not find file", log.output[0]) # assert exception is rose in non-failsafe mode with self.assertRaises(KeyError) as cm: - with aasx.AASXWriter(tmpdir_path / "tmp.aasx", failsafe=False) as writer: - writer.write_all_aas_objects("/aasx/data.xml", data, empty_file_store) + with aasx.AASXWriter( + tmpdir_path / "tmp.aasx", failsafe=False + ) as writer: + writer.write_all_aas_objects( + "/aasx/data.xml", data, empty_file_store + ) self.assertIn("Could not find file", cm.exception.args[0]) def test_writing_file_twice(self) -> None: - with (tempfile.TemporaryDirectory() as tmpdir): + with tempfile.TemporaryDirectory() as tmpdir: tmpdir_path = Path(tmpdir) # ---- Arange ---- file_store = aasx.DictSupplementaryFileContainer() with open(Path(__file__).parent / "TestFile.pdf", "rb") as pdf: - resulting_file_name = file_store.add_file("/TestFile.pdf", pdf, "application/pdf") + resulting_file_name = file_store.add_file( + "/TestFile.pdf", pdf, "application/pdf" + ) # create two submodels that reference the same file in file_store first_submodel = model.Submodel( id_="http://example.org/First_Submodel", - submodel_element=[model.File( - id_short="ExampleFile", - content_type="application/pdf", - value=resulting_file_name - )] + submodel_element=[ + model.File( + id_short="ExampleFile", + content_type="application/pdf", + value=resulting_file_name, + ) + ], ) second_submodel = model.Submodel( id_="http://example.org/SecondSubmodel", - submodel_element=[model.File( - id_short="ExampleFile", - content_type="application/pdf", - value=resulting_file_name - )] + submodel_element=[ + model.File( + id_short="ExampleFile", + content_type="application/pdf", + value=resulting_file_name, + ) + ], + ) + data: model.DictIdentifiableStore[model.Identifiable] = ( + model.DictIdentifiableStore([first_submodel, second_submodel]) ) - data: model.DictIdentifiableStore[model.Identifiable] \ - = model.DictIdentifiableStore([first_submodel, second_submodel]) # ---- Act & Assert ---- with self.assertNoLogs(level="WARNING"): @@ -209,16 +244,27 @@ def test_write_non_aas(self) -> None: with self.assertLogs(level="WARNING") as log: with aasx.AASXWriter(tmpdir_path / "tmp.aasx", failsafe=True) as writer: # try to write a non AAS object - writer.write_aas("https://example.org/Test_Submodel", data, file_store) - self.assertIn("Skipping AAS https://example.org/Test_Submodel", log.output[0]) + writer.write_aas( + "https://example.org/Test_Submodel", data, file_store + ) + self.assertIn( + "Skipping AAS https://example.org/Test_Submodel", log.output[0] + ) # assert exception is rose in non-failsafe mode with self.assertRaises(TypeError) as cm: - with aasx.AASXWriter(tmpdir_path / "tmp.aasx", failsafe=False) as writer: + with aasx.AASXWriter( + tmpdir_path / "tmp.aasx", failsafe=False + ) as writer: # try to write a non AAS object - writer.write_aas("https://example.org/Test_Submodel", data, file_store) - self.assertIn("Identifier https://example.org/Test_Submodel does not belong " - "to an AssetAdministrationShell", cm.exception.args[0]) + writer.write_aas( + "https://example.org/Test_Submodel", data, file_store + ) + self.assertIn( + "Identifier https://example.org/Test_Submodel does not belong " + "to an AssetAdministrationShell", + cm.exception.args[0], + ) def test_write_aas_missing_submodel(self) -> None: with tempfile.TemporaryDirectory() as tmpdir: @@ -226,24 +272,36 @@ def test_write_aas_missing_submodel(self) -> None: # ---- Arange ---- # leave example_submodel out of object store - data = model.DictIdentifiableStore([ - example_aas.create_example_asset_administration_shell(), - example_aas.create_example_asset_identification_submodel(), - example_aas.create_example_bill_of_material_submodel() - ]) + data = model.DictIdentifiableStore( + [ + example_aas.create_example_asset_administration_shell(), + example_aas.create_example_asset_identification_submodel(), + example_aas.create_example_bill_of_material_submodel(), + ] + ) empty_file_store = aasx.DictSupplementaryFileContainer() # ---- Act & Assert ---- # assert warning is present in failsafe mode with self.assertLogs(level="WARNING") as log: with aasx.AASXWriter(tmpdir_path / "tmp.aasx", failsafe=True) as writer: - writer.write_aas("https://example.org/Test_AssetAdministrationShell", data, empty_file_store) + writer.write_aas( + "https://example.org/Test_AssetAdministrationShell", + data, + empty_file_store, + ) self.assertIn("Could not find Submodel", log.output[0]) # assert exception is rose in non-failsafe mode with self.assertRaises(KeyError) as cm: - with aasx.AASXWriter(tmpdir_path / "tmp.aasx", failsafe=False) as writer: - writer.write_aas("https://example.org/Test_AssetAdministrationShell", data, empty_file_store) + with aasx.AASXWriter( + tmpdir_path / "tmp.aasx", failsafe=False + ) as writer: + writer.write_aas( + "https://example.org/Test_AssetAdministrationShell", + data, + empty_file_store, + ) self.assertIn("Could not find Submodel", cm.exception.args[0]) def test_write_aas_missing_concept_description(self) -> None: @@ -252,12 +310,14 @@ def test_write_aas_missing_concept_description(self) -> None: # ---- Arange ---- # leave example_concept_description out of object store - data = model.DictIdentifiableStore([ - example_aas.create_example_asset_administration_shell(), - example_aas.create_example_submodel(), - example_aas.create_example_asset_identification_submodel(), - example_aas.create_example_bill_of_material_submodel() - ]) + data = model.DictIdentifiableStore( + [ + example_aas.create_example_asset_administration_shell(), + example_aas.create_example_submodel(), + example_aas.create_example_asset_identification_submodel(), + example_aas.create_example_bill_of_material_submodel(), + ] + ) file_store = aasx.DictSupplementaryFileContainer() with open(Path(__file__).parent / "TestFile.pdf", "rb") as pdf: file_store.add_file("/TestFile.pdf", pdf, "application/pdf") @@ -266,15 +326,27 @@ def test_write_aas_missing_concept_description(self) -> None: # assert warning is present in failsafe mode with self.assertLogs(level="WARNING") as log: with aasx.AASXWriter(tmpdir_path / "tmp.aasx", failsafe=True) as writer: - writer.write_aas("https://example.org/Test_AssetAdministrationShell", data, file_store) + writer.write_aas( + "https://example.org/Test_AssetAdministrationShell", + data, + file_store, + ) self.assertIn("https://example.org/Test_ConceptDescription", log.output[0]) self.assertRegex(log.output[0], "ConceptDescription .* not found") # assert exception is rose in non-failsafe mode with self.assertRaises(KeyError) as cm: - with aasx.AASXWriter(tmpdir_path / "tmp.aasx", failsafe=False) as writer: - writer.write_aas("https://example.org/Test_AssetAdministrationShell", data, file_store) - self.assertIn("https://example.org/Test_ConceptDescription", cm.exception.args[0]) + with aasx.AASXWriter( + tmpdir_path / "tmp.aasx", failsafe=False + ) as writer: + writer.write_aas( + "https://example.org/Test_AssetAdministrationShell", + data, + file_store, + ) + self.assertIn( + "https://example.org/Test_ConceptDescription", cm.exception.args[0] + ) self.assertRegex(cm.exception.args[0], "ConceptDescription .* not found") def test_write_aas_false_semantic_id(self) -> None: @@ -284,35 +356,51 @@ def test_write_aas_false_semantic_id(self) -> None: # ---- Arange ---- # semanticId of submodel holds reference to an object # that is no ContentDescription - second_submodel = model.Submodel( - id_="https://example.org/Second_Submodel" - ) + second_submodel = model.Submodel(id_="https://example.org/Second_Submodel") submodel = model.Submodel( id_="https://example.org/Test_Submodel", semantic_id=model.ModelReference( - key=(model.Key(type_=model.KeyTypes.SUBMODEL, value="https://example.org/Second_Submodel"),), - type_=model.ConceptDescription - ) + key=( + model.Key( + type_=model.KeyTypes.SUBMODEL, + value="https://example.org/Second_Submodel", + ), + ), + type_=model.ConceptDescription, + ), + ) + data = model.DictIdentifiableStore( + [ + example_aas.create_example_asset_administration_shell(), + example_aas.create_example_asset_identification_submodel(), + example_aas.create_example_bill_of_material_submodel(), + submodel, + second_submodel, + ] ) - data = model.DictIdentifiableStore([ - example_aas.create_example_asset_administration_shell(), - example_aas.create_example_asset_identification_submodel(), - example_aas.create_example_bill_of_material_submodel(), - submodel, second_submodel - ]) empty_file_store = aasx.DictSupplementaryFileContainer() # ---- Act & Assert ---- # assert warning is present in failsafe mode with self.assertLogs(level="WARNING") as log: with aasx.AASXWriter(tmpdir_path / "tmp.aasx", failsafe=True) as writer: - writer.write_aas("https://example.org/Test_AssetAdministrationShell", data, empty_file_store) + writer.write_aas( + "https://example.org/Test_AssetAdministrationShell", + data, + empty_file_store, + ) self.assertIn("which is not a ConceptDescription", log.output[0]) # assert exception is rose in non-failsafe mode with self.assertRaises(TypeError) as cm: - with aasx.AASXWriter(tmpdir_path / "tmp.aasx", failsafe=False) as writer: - writer.write_aas("https://example.org/Test_AssetAdministrationShell", data, empty_file_store) + with aasx.AASXWriter( + tmpdir_path / "tmp.aasx", failsafe=False + ) as writer: + writer.write_aas( + "https://example.org/Test_AssetAdministrationShell", + data, + empty_file_store, + ) self.assertIn("which is not a ConceptDescription", cm.exception.args[0]) def test_write_core_properties_twice(self) -> None: @@ -332,7 +420,9 @@ def test_write_core_properties_twice(self) -> None: with self.assertRaises(RuntimeError) as cm: writer.write_core_properties(cp) - self.assertIn("Core Properties have already been written", cm.exception.args[0]) + self.assertIn( + "Core Properties have already been written", cm.exception.args[0] + ) def test_write_thumbnail_twice(self) -> None: with tempfile.TemporaryDirectory() as tmpdir: @@ -344,20 +434,32 @@ def test_write_thumbnail_twice(self) -> None: # ---- Act & Assert ---- with aasx.AASXWriter(tmpdir_path / "tmp.aasx") as writer: - writer.write_thumbnail("/aasx/thumbnail.png", bytearray(thumbnail), "image/png") + writer.write_thumbnail( + "/aasx/thumbnail.png", bytearray(thumbnail), "image/png" + ) # expect RuntimeError on second write with self.assertRaises(RuntimeError) as cm: - writer.write_thumbnail("/aasx/thumbnail.png", bytearray(thumbnail), "image/png") + writer.write_thumbnail( + "/aasx/thumbnail.png", bytearray(thumbnail), "image/png" + ) - self.assertIn("package thumbnail has already been written", cm.exception.args[0]) + self.assertIn( + "package thumbnail has already been written", cm.exception.args[0] + ) def test_writing_reading_example_aas(self) -> None: # Create example data and file_store - data = example_aas.create_full_example() # creates a complete, valid example AAS - files = aasx.DictSupplementaryFileContainer() # in-memory store for attached files - with open(os.path.join(os.path.dirname(__file__), 'TestFile.pdf'), 'rb') as f: - files.add_file("/TestFile.pdf", f, "application/pdf") # add a real supplementary pdf file + data = ( + example_aas.create_full_example() + ) # creates a complete, valid example AAS + files = ( + aasx.DictSupplementaryFileContainer() + ) # in-memory store for attached files + with open(os.path.join(os.path.dirname(__file__), "TestFile.pdf"), "rb") as f: + files.add_file( + "/TestFile.pdf", f, "application/pdf" + ) # add a real supplementary pdf file f.seek(0) # Create OPC/AASX core properties # create AASX metadata (core properties) @@ -366,10 +468,10 @@ def test_writing_reading_example_aas(self) -> None: cp.creator = "Eclipse BaSyx Python Testing Framework" # Write AASX file - for write_json in (False, True): # Loop over both XML and JSON modes + for write_json in (False, True): # Loop over both XML and JSON modes with self.subTest(write_json=write_json): - fd, filename = tempfile.mkstemp(suffix=".aasx") # create temporary file - os.close(fd) # close file descriptor + fd, filename = tempfile.mkstemp(suffix=".aasx") # create temporary file + os.close(fd) # close file descriptor # Write AASX file # the zipfile library reports errors as UserWarnings via the warnings library. Let's check for @@ -377,16 +479,28 @@ def test_writing_reading_example_aas(self) -> None: with warnings.catch_warnings(record=True) as w: with aasx.AASXWriter(filename) as writer: # TODO test writing multiple AAS - writer.write_aas('https://example.org/Test_AssetAdministrationShell', - data, files, write_json=write_json) + writer.write_aas( + "https://example.org/Test_AssetAdministrationShell", + data, + files, + write_json=write_json, + ) writer.write_core_properties(cp) - assert isinstance(w, list) # This should be True due to the record=True parameter - self.assertEqual(0, len(w), f"Warnings were issued while writing the AASX file: " - f"{[warning.message for warning in w]}") + assert isinstance( + w, list + ) # This should be True due to the record=True parameter + self.assertEqual( + 0, + len(w), + f"Warnings were issued while writing the AASX file: " + f"{[warning.message for warning in w]}", + ) # Read AASX file - new_data: model.DictIdentifiableStore[model.Identifiable] = model.DictIdentifiableStore() + new_data: model.DictIdentifiableStore[model.Identifiable] = ( + model.DictIdentifiableStore() + ) new_files = aasx.DictSupplementaryFileContainer() with aasx.AASXReader(filename) as reader: reader.read_into(new_data, new_files) @@ -399,17 +513,29 @@ def test_writing_reading_example_aas(self) -> None: # Check core properties assert isinstance(cp.created, datetime.datetime) # to make mypy happy self.assertIsInstance(new_cp.created, datetime.datetime) - assert isinstance(new_cp.created, datetime.datetime) # to make mypy happy - self.assertAlmostEqual(new_cp.created, cp.created, delta=datetime.timedelta(milliseconds=20)) - self.assertEqual(new_cp.creator, "Eclipse BaSyx Python Testing Framework") + assert isinstance( + new_cp.created, datetime.datetime + ) # to make mypy happy + self.assertAlmostEqual( + new_cp.created, + cp.created, + delta=datetime.timedelta(milliseconds=20), + ) + self.assertEqual( + new_cp.creator, "Eclipse BaSyx Python Testing Framework" + ) self.assertIsNone(new_cp.lastModifiedBy) # Check files - self.assertEqual(new_files.get_content_type("/TestFile.pdf"), "application/pdf") + self.assertEqual( + new_files.get_content_type("/TestFile.pdf"), "application/pdf" + ) file_content = io.BytesIO() new_files.write_file("/TestFile.pdf", file_content) - self.assertEqual(hashlib.sha1(file_content.getvalue()).hexdigest(), - "241e62aef8b4cdad0975f6c68a4ed8b3923d8db1") + self.assertEqual( + hashlib.sha1(file_content.getvalue()).hexdigest(), + "241e62aef8b4cdad0975f6c68a4ed8b3923d8db1", + ) os.unlink(filename) @@ -419,7 +545,7 @@ def _create_test_aasx(self) -> str: data = example_aas.create_full_example() files = aasx.DictSupplementaryFileContainer() - with open(os.path.join(os.path.dirname(__file__), 'TestFile.pdf'), 'rb') as f: + with open(os.path.join(os.path.dirname(__file__), "TestFile.pdf"), "rb") as f: files.add_file("/TestFile.pdf", f, "application/pdf") f.seek(0) @@ -433,8 +559,10 @@ def _create_test_aasx(self) -> str: with aasx.AASXWriter(filename) as writer: writer.write_aas( - 'https://example.org/Test_AssetAdministrationShell', - data, files, write_json=False + "https://example.org/Test_AssetAdministrationShell", + data, + files, + write_json=False, ) writer.write_core_properties(cp) @@ -474,23 +602,31 @@ def test_get_thumbnail(self) -> None: # ---- Arange ---- tmpdir_path = Path(tmpdir) - data: model.DictIdentifiableStore[model.Identifiable] = model.DictIdentifiableStore([ - model.AssetAdministrationShell( - id_="http://example.org/Test_AAS", - asset_information=model.AssetInformation( - global_asset_id="http://example.org/Test_Asset" - ) + data: model.DictIdentifiableStore[model.Identifiable] = ( + model.DictIdentifiableStore( + [ + model.AssetAdministrationShell( + id_="http://example.org/Test_AAS", + asset_information=model.AssetInformation( + global_asset_id="http://example.org/Test_Asset" + ), + ) + ] ) - ]) + ) with aasx.AASXWriter(tmpdir_path / "test_thumbnail.aasx") as writer: writer.write_aas( - 'http://example.org/Test_AAS', - data, aasx.DictSupplementaryFileContainer(), write_json=False + "http://example.org/Test_AAS", + data, + aasx.DictSupplementaryFileContainer(), + write_json=False, ) with open(Path(__file__).parent / "test.png", "rb") as png: thumbnail = png.read() - writer.write_thumbnail("/aasx/thumbnail.png", bytearray(thumbnail), "image/png") + writer.write_thumbnail( + "/aasx/thumbnail.png", bytearray(thumbnail), "image/png" + ) # ---- Act ---- with aasx.AASXReader(tmpdir_path / "test_thumbnail.aasx") as reader: @@ -517,7 +653,9 @@ def test_read_into(self) -> None: filename = self._create_test_aasx() try: - objects: model.DictIdentifiableStore[model.Identifiable] = model.DictIdentifiableStore() + objects: model.DictIdentifiableStore[model.Identifiable] = ( + model.DictIdentifiableStore() + ) files = aasx.DictSupplementaryFileContainer() with warnings.catch_warnings(record=True) as w: @@ -525,15 +663,12 @@ def test_read_into(self) -> None: ids = reader.read_into(objects, files) assert isinstance(w, list) - self.assertEqual(0, len(w)) # Ensure no warnings were raised + self.assertEqual(0, len(w)) # Ensure no warnings were raised - self.assertGreater(len(ids), 0) # Ensure at least one AAS was read - self.assertGreater(len(objects), 0) # Ensure objects were populated + self.assertGreater(len(ids), 0) # Ensure at least one AAS was read + self.assertGreater(len(objects), 0) # Ensure objects were populated self.assertGreater(len(files), 0) - self.assertEqual( - files.get_content_type("/TestFile.pdf"), - "application/pdf" - ) + self.assertEqual(files.get_content_type("/TestFile.pdf"), "application/pdf") finally: os.unlink(filename) @@ -541,7 +676,9 @@ def test_supplementary_file_integrity(self) -> None: filename = self._create_test_aasx() try: - objects: model.DictIdentifiableStore[model.Identifiable] = model.DictIdentifiableStore() + objects: model.DictIdentifiableStore[model.Identifiable] = ( + model.DictIdentifiableStore() + ) files = aasx.DictSupplementaryFileContainer() with aasx.AASXReader(filename) as reader: @@ -552,14 +689,13 @@ def test_supplementary_file_integrity(self) -> None: self.assertEqual( hashlib.sha1(buf.getvalue()).hexdigest(), - "241e62aef8b4cdad0975f6c68a4ed8b3923d8db1" + "241e62aef8b4cdad0975f6c68a4ed8b3923d8db1", ) finally: os.unlink(filename) class AASXWriterReferencedSubmodelsTest(unittest.TestCase): - def test_only_referenced_submodels(self): """ Test that verifies that all Submodels (referenced and unreferenced) are written to the AASX package when using @@ -575,13 +711,15 @@ def test_only_referenced_submodels(self): id_="Test_AAS", asset_information=model.AssetInformation( asset_kind=model.AssetKind.INSTANCE, - global_asset_id="http://example.org/Test_Asset" + global_asset_id="http://example.org/Test_Asset", ), - submodel={model.ModelReference.from_referable(referenced_submodel)} + submodel={model.ModelReference.from_referable(referenced_submodel)}, ) # IdentifiableStore containing all objects - identifiable_store = model.DictIdentifiableStore([aas, referenced_submodel, unreferenced_submodel]) + identifiable_store = model.DictIdentifiableStore( + [aas, referenced_submodel, unreferenced_submodel] + ) # Empty SupplementaryFileContainer (no files needed) file_store = aasx.DictSupplementaryFileContainer() @@ -599,18 +737,24 @@ def test_only_referenced_submodels(self): aas_ids=[aas.id], object_store=identifiable_store, file_store=file_store, - write_json=write_json + write_json=write_json, ) # Read back - new_data: model.DictIdentifiableStore[model.Identifiable] = model.DictIdentifiableStore() + new_data: model.DictIdentifiableStore[model.Identifiable] = ( + model.DictIdentifiableStore() + ) new_files = aasx.DictSupplementaryFileContainer() with aasx.AASXReader(filename) as reader: reader.read_into(new_data, new_files) # Assertions - self.assertIn(referenced_submodel.id, new_data) # referenced Submodel is included - self.assertNotIn(unreferenced_submodel.id, new_data) # unreferenced Submodel is excluded + self.assertIn( + referenced_submodel.id, new_data + ) # referenced Submodel is included + self.assertNotIn( + unreferenced_submodel.id, new_data + ) # unreferenced Submodel is excluded os.unlink(filename) @@ -626,16 +770,20 @@ def test_only_referenced_submodels(self): part_name="/aasx/my_aas_part.xml", objects=identifiable_store, file_store=file_store, - write_json=write_json + write_json=write_json, ) # Read back - new_data: model.DictIdentifiableStore[model.Identifiable] = model.DictIdentifiableStore() + new_data: model.DictIdentifiableStore[model.Identifiable] = ( + model.DictIdentifiableStore() + ) new_files = aasx.DictSupplementaryFileContainer() with aasx.AASXReader(filename) as reader: reader.read_into(new_data, new_files) # Assertions self.assertIn(referenced_submodel.id, new_data) - self.assertIn(unreferenced_submodel.id, new_data) # all objects are written + self.assertIn( + unreferenced_submodel.id, new_data + ) # all objects are written os.unlink(filename) diff --git a/sdk/test/adapter/json/test_json_deserialization.py b/sdk/test/adapter/json/test_json_deserialization.py index 9067d3e7b..fa337dcc9 100644 --- a/sdk/test/adapter/json/test_json_deserialization.py +++ b/sdk/test/adapter/json/test_json_deserialization.py @@ -11,12 +11,18 @@ when trying to reconstruct the serialized data structure. This module additionally tests error behaviour and verifies deserialization results. """ + import io import json import logging import unittest -from basyx.aas.adapter.json import AASFromJsonDecoder, StrictAASFromJsonDecoder, StrictStrippedAASFromJsonDecoder, \ - read_aas_json_file, read_aas_json_file_into +from basyx.aas.adapter.json import ( + AASFromJsonDecoder, + StrictAASFromJsonDecoder, + StrictStrippedAASFromJsonDecoder, + read_aas_json_file, + read_aas_json_file_into, +) from basyx.aas import model @@ -37,8 +43,11 @@ def test_file_format_wrong_list(self) -> None: } ] }""" - with self.assertRaisesRegex(TypeError, r"AssetAdministrationShell.* was " - r"in the wrong list 'submodels'"): + with self.assertRaisesRegex( + TypeError, + r"AssetAdministrationShell.* was " + r"in the wrong list 'submodels'", + ): read_aas_json_file(io.StringIO(data), failsafe=False) with self.assertLogs(logging.getLogger(), level=logging.WARNING) as cm: read_aas_json_file(io.StringIO(data), failsafe=True) @@ -166,7 +175,9 @@ def test_duplicate_identifier_identifiable_store(self) -> None: sm_id = "http://example.org/test_submodel" def get_clean_store() -> model.DictIdentifiableStore: - store: model.DictIdentifiableStore[model.Identifiable] = model.DictIdentifiableStore() + store: model.DictIdentifiableStore[model.Identifiable] = ( + model.DictIdentifiableStore() + ) submodel_ = model.Submodel(sm_id, id_short="test123") store.add(submodel_) return store @@ -198,7 +209,10 @@ def get_clean_store() -> model.DictIdentifiableStore: identifiable_store = get_clean_store() with self.assertLogs(logging.getLogger(), level=logging.INFO) as log_ctx: identifiers = read_aas_json_file_into( - identifiable_store, string_io, replace_existing=False, ignore_existing=True + identifiable_store, + string_io, + replace_existing=False, + ignore_existing=True, ) self.assertEqual(len(identifiers), 0) self.assertIn("already exists in store", log_ctx.output[0]) # type: ignore @@ -210,8 +224,12 @@ def get_clean_store() -> model.DictIdentifiableStore: identifiable_store = get_clean_store() with self.assertRaisesRegex(KeyError, r"already exists in store"): - identifiers = read_aas_json_file_into(identifiable_store, string_io, replace_existing=False, - ignore_existing=False) + identifiers = read_aas_json_file_into( + identifiable_store, + string_io, + replace_existing=False, + ignore_existing=False, + ) self.assertEqual(len(identifiers), 0) submodel = identifiable_store.pop() self.assertIsInstance(submodel, model.Submodel) diff --git a/sdk/test/adapter/json/test_json_serialization.py b/sdk/test/adapter/json/test_json_serialization.py index 136c04110..88b0a6ee1 100644 --- a/sdk/test/adapter/json/test_json_serialization.py +++ b/sdk/test/adapter/json/test_json_serialization.py @@ -10,21 +10,38 @@ import json from basyx.aas import model -from basyx.aas.adapter.json import AASToJsonEncoder, StrippedAASToJsonEncoder, write_aas_json_file +from basyx.aas.adapter.json import ( + AASToJsonEncoder, + StrippedAASToJsonEncoder, + write_aas_json_file, +) from jsonschema import validate # type: ignore from typing import Set, Union -from basyx.aas.examples.data import example_aas_missing_attributes, example_aas, \ - example_aas_mandatory_attributes, example_submodel_template, create_example +from basyx.aas.examples.data import ( + example_aas_missing_attributes, + example_aas, + example_aas_mandatory_attributes, + example_submodel_template, + create_example, +) -JSON_SCHEMA_FILE = os.path.join(os.path.dirname(__file__), '../schemas/aasJSONSchema.json') +JSON_SCHEMA_FILE = os.path.join( + os.path.dirname(__file__), "../schemas/aasJSONSchema.json" +) class JsonSerializationTest(unittest.TestCase): def test_serialize_object(self) -> None: - test_object = model.Property("test_id_short", model.datatypes.String, category="PARAMETER", - description=model.MultiLanguageTextType({"en-US": "Germany", "de": "Deutschland"})) + test_object = model.Property( + "test_id_short", + model.datatypes.String, + category="PARAMETER", + description=model.MultiLanguageTextType( + {"en-US": "Germany", "de": "Deutschland"} + ), + ) json_data = json.dumps(test_object, cls=AASToJsonEncoder) def test_random_object_serialization(self) -> None: @@ -34,16 +51,22 @@ def test_random_object_serialization(self) -> None: assert submodel_identifier is not None submodel_reference = model.ModelReference(submodel_key, model.Submodel) submodel = model.Submodel(submodel_identifier) - test_aas = model.AssetAdministrationShell(model.AssetInformation(global_asset_id="test"), - aas_identifier, submodel={submodel_reference}) + test_aas = model.AssetAdministrationShell( + model.AssetInformation(global_asset_id="test"), + aas_identifier, + submodel={submodel_reference}, + ) # serialize object to json - json_data = json.dumps({ - 'assetAdministrationShells': [test_aas], - 'submodels': [submodel], - 'assets': [], - 'conceptDescriptions': [], - }, cls=AASToJsonEncoder) + json_data = json.dumps( + { + "assetAdministrationShells": [test_aas], + "submodels": [submodel], + "assets": [], + "conceptDescriptions": [], + }, + cls=AASToJsonEncoder, + ) json_data_new = json.loads(json_data) @@ -51,7 +74,9 @@ class JsonSerializationSchemaTest(unittest.TestCase): @classmethod def setUpClass(cls): if not os.path.exists(JSON_SCHEMA_FILE): - raise unittest.SkipTest(f"JSON Schema does not exist at {JSON_SCHEMA_FILE}, skipping test") + raise unittest.SkipTest( + f"JSON Schema does not exist at {JSON_SCHEMA_FILE}, skipping test" + ) def test_random_object_serialization(self) -> None: aas_identifier = "AAS1" @@ -61,21 +86,32 @@ def test_random_object_serialization(self) -> None: submodel_reference = model.ModelReference(submodel_key, model.Submodel) # The JSONSchema expects every object with HasSemnatics (like Submodels) to have a `semanticId` Reference, which # must be a Reference. (This seems to be a bug in the JSONSchema.) - submodel = model.Submodel(submodel_identifier, - semantic_id=model.ExternalReference((model.Key(model.KeyTypes.GLOBAL_REFERENCE, - "http://example.org/TestSemanticId"),))) - test_aas = model.AssetAdministrationShell(model.AssetInformation(global_asset_id="test"), - aas_identifier, submodel={submodel_reference}) + submodel = model.Submodel( + submodel_identifier, + semantic_id=model.ExternalReference( + ( + model.Key( + model.KeyTypes.GLOBAL_REFERENCE, + "http://example.org/TestSemanticId", + ), + ) + ), + ) + test_aas = model.AssetAdministrationShell( + model.AssetInformation(global_asset_id="test"), + aas_identifier, + submodel={submodel_reference}, + ) # serialize object to json - json_data = json.dumps({ - 'assetAdministrationShells': [test_aas], - 'submodels': [submodel] - }, cls=AASToJsonEncoder) + json_data = json.dumps( + {"assetAdministrationShells": [test_aas], "submodels": [submodel]}, + cls=AASToJsonEncoder, + ) json_data_new = json.loads(json_data) # load schema - with open(JSON_SCHEMA_FILE, 'r') as json_file: + with open(JSON_SCHEMA_FILE, "r") as json_file: aas_schema = json.load(json_file) # validate serialization against schema @@ -86,7 +122,7 @@ def test_aas_example_serialization(self) -> None: file = io.StringIO() write_aas_json_file(file=file, data=data) - with open(JSON_SCHEMA_FILE, 'r') as json_file: + with open(JSON_SCHEMA_FILE, "r") as json_file: aas_json_schema = json.load(json_file) file.seek(0) @@ -96,12 +132,14 @@ def test_aas_example_serialization(self) -> None: validate(instance=json_data, schema=aas_json_schema) def test_submodel_template_serialization(self) -> None: - data: model.DictIdentifiableStore[model.Identifiable] = model.DictIdentifiableStore() + data: model.DictIdentifiableStore[model.Identifiable] = ( + model.DictIdentifiableStore() + ) data.add(example_submodel_template.create_example_submodel_template()) file = io.StringIO() write_aas_json_file(file=file, data=data) - with open(JSON_SCHEMA_FILE, 'r') as json_file: + with open(JSON_SCHEMA_FILE, "r") as json_file: aas_json_schema = json.load(json_file) file.seek(0) @@ -115,7 +153,7 @@ def test_full_empty_example_serialization(self) -> None: file = io.StringIO() write_aas_json_file(file=file, data=data) - with open(JSON_SCHEMA_FILE, 'r') as json_file: + with open(JSON_SCHEMA_FILE, "r") as json_file: aas_json_schema = json.load(json_file) file.seek(0) @@ -129,7 +167,7 @@ def test_missing_serialization(self) -> None: file = io.StringIO() write_aas_json_file(file=file, data=data) - with open(JSON_SCHEMA_FILE, 'r') as json_file: + with open(JSON_SCHEMA_FILE, "r") as json_file: aas_json_schema = json.load(json_file) file.seek(0) @@ -139,12 +177,14 @@ def test_missing_serialization(self) -> None: validate(instance=json_data, schema=aas_json_schema) def test_concept_description_serialization(self) -> None: - data: model.DictIdentifiableStore[model.Identifiable] = model.DictIdentifiableStore() + data: model.DictIdentifiableStore[model.Identifiable] = ( + model.DictIdentifiableStore() + ) data.add(example_aas.create_example_concept_description()) file = io.StringIO() write_aas_json_file(file=file, data=data) - with open(JSON_SCHEMA_FILE, 'r') as json_file: + with open(JSON_SCHEMA_FILE, "r") as json_file: aas_json_schema = json.load(json_file) file.seek(0) @@ -158,7 +198,7 @@ def test_full_example_serialization(self) -> None: file = io.StringIO() write_aas_json_file(file=file, data=data) - with open(JSON_SCHEMA_FILE, 'r') as json_file: + with open(JSON_SCHEMA_FILE, "r") as json_file: aas_json_schema = json.load(json_file) file.seek(0) @@ -169,13 +209,18 @@ def test_full_example_serialization(self) -> None: class JsonSerializationStrippedObjectsTest(unittest.TestCase): - def _checkNormalAndStripped(self, attributes: Union[Set[str], str], obj: object) -> None: + def _checkNormalAndStripped( + self, attributes: Union[Set[str], str], obj: object + ) -> None: if isinstance(attributes, str): attributes = {attributes} # attributes should be present when using the normal encoder, # but must not be present when using the stripped encoder - for cls, assert_fn in ((AASToJsonEncoder, self.assertIn), (StrippedAASToJsonEncoder, self.assertNotIn)): + for cls, assert_fn in ( + (AASToJsonEncoder, self.assertIn), + (StrippedAASToJsonEncoder, self.assertNotIn), + ): data = json.loads(json.dumps(obj, cls=cls)) for attr in attributes: assert_fn(attr, data) @@ -187,23 +232,22 @@ def test_stripped_qualifiable(self) -> None: submodel = model.Submodel( "http://example.org/test_submodel", submodel_element=[operation], - qualifier={qualifier2} + qualifier={qualifier2}, ) self._checkNormalAndStripped({"submodelElements", "qualifiers"}, submodel) self._checkNormalAndStripped("qualifiers", operation) def test_stripped_annotated_relationship_element(self) -> None: - mlp = model.MultiLanguageProperty("test_multi_language_property", category="PARAMETER") + mlp = model.MultiLanguageProperty( + "test_multi_language_property", category="PARAMETER" + ) ref = model.ModelReference( (model.Key(model.KeyTypes.SUBMODEL, "http://example.org/test_ref"),), - model.Submodel + model.Submodel, ) are = model.AnnotatedRelationshipElement( - "test_annotated_relationship_element", - ref, - ref, - annotation=[mlp] + "test_annotated_relationship_element", ref, ref, annotation=[mlp] ) self._checkNormalAndStripped("annotations", are) @@ -215,26 +259,34 @@ def test_relationship_element_omits_none_first_second(self) -> None: self.assertNotIn("second", data) def test_stripped_entity(self) -> None: - mlp = model.MultiLanguageProperty("test_multi_language_property", category="PARAMETER") - entity = model.Entity("test_entity", model.EntityType.CO_MANAGED_ENTITY, statement=[mlp]) + mlp = model.MultiLanguageProperty( + "test_multi_language_property", category="PARAMETER" + ) + entity = model.Entity( + "test_entity", model.EntityType.CO_MANAGED_ENTITY, statement=[mlp] + ) self._checkNormalAndStripped("statements", entity) def test_stripped_submodel_element_collection(self) -> None: - mlp = model.MultiLanguageProperty("test_multi_language_property", category="PARAMETER") - sec = model.SubmodelElementCollection("test_submodel_element_collection", value=[mlp]) + mlp = model.MultiLanguageProperty( + "test_multi_language_property", category="PARAMETER" + ) + sec = model.SubmodelElementCollection( + "test_submodel_element_collection", value=[mlp] + ) self._checkNormalAndStripped("value", sec) def test_stripped_asset_administration_shell(self) -> None: submodel_ref = model.ModelReference( (model.Key(model.KeyTypes.SUBMODEL, "http://example.org/test_ref"),), - model.Submodel + model.Submodel, ) aas = model.AssetAdministrationShell( model.AssetInformation(global_asset_id="http://example.org/test_ref"), "http://example.org/test_aas", - submodel={submodel_ref} + submodel={submodel_ref}, ) self._checkNormalAndStripped({"submodels"}, aas) diff --git a/sdk/test/adapter/json/test_json_serialization_deserialization.py b/sdk/test/adapter/json/test_json_serialization_deserialization.py index 48beb4c27..7f621427c 100644 --- a/sdk/test/adapter/json/test_json_serialization_deserialization.py +++ b/sdk/test/adapter/json/test_json_serialization_deserialization.py @@ -10,10 +10,19 @@ import unittest from basyx.aas import model -from basyx.aas.adapter.json import AASToJsonEncoder, write_aas_json_file, read_aas_json_file - -from basyx.aas.examples.data import example_aas_missing_attributes, example_aas, \ - example_aas_mandatory_attributes, example_submodel_template, create_example +from basyx.aas.adapter.json import ( + AASToJsonEncoder, + write_aas_json_file, + read_aas_json_file, +) + +from basyx.aas.examples.data import ( + example_aas_missing_attributes, + example_aas, + example_aas_mandatory_attributes, + example_submodel_template, + create_example, +) from basyx.aas.examples.data._helper import AASDataChecker from typing import Iterable, IO @@ -27,20 +36,28 @@ def test_random_object_serialization_deserialization(self) -> None: assert submodel_identifier is not None submodel_reference = model.ModelReference(submodel_key, model.Submodel) submodel = model.Submodel(submodel_identifier) - test_aas = model.AssetAdministrationShell(model.AssetInformation(global_asset_id="test"), - aas_identifier, submodel={submodel_reference}) + test_aas = model.AssetAdministrationShell( + model.AssetInformation(global_asset_id="test"), + aas_identifier, + submodel={submodel_reference}, + ) # serialize object to json - json_data = json.dumps({ - 'assetAdministrationShells': [test_aas], - 'submodels': [submodel], - 'assets': [], - 'conceptDescriptions': [], - }, cls=AASToJsonEncoder) + json_data = json.dumps( + { + "assetAdministrationShells": [test_aas], + "submodels": [submodel], + "assets": [], + "conceptDescriptions": [], + }, + cls=AASToJsonEncoder, + ) json_data_new = json.loads(json_data) # try deserializing the json string into a DictIdentifiableStore of AAS objects with help of the json module - json_identifiable_store = read_aas_json_file(io.StringIO(json_data), failsafe=False) + json_identifiable_store = read_aas_json_file( + io.StringIO(json_data), failsafe=False + ) def test_example_serialization_deserialization(self) -> None: # test with TextIO and BinaryIO, which should both be supported @@ -66,7 +83,9 @@ def test_example_mandatory_attributes_serialization_deserialization(self) -> Non file.seek(0) json_identifiable_store = read_aas_json_file(file, failsafe=False) checker = AASDataChecker(raise_immediately=True) - example_aas_mandatory_attributes.check_full_example(checker, json_identifiable_store) + example_aas_mandatory_attributes.check_full_example( + checker, json_identifiable_store + ) class JsonSerializationDeserializationTest3(unittest.TestCase): @@ -78,12 +97,16 @@ def test_example_missing_attributes_serialization_deserialization(self) -> None: file.seek(0) json_identifiable_store = read_aas_json_file(file, failsafe=False) checker = AASDataChecker(raise_immediately=True) - example_aas_missing_attributes.check_full_example(checker, json_identifiable_store) + example_aas_missing_attributes.check_full_example( + checker, json_identifiable_store + ) class JsonSerializationDeserializationTest4(unittest.TestCase): def test_example_submodel_template_serialization_deserialization(self) -> None: - data: model.DictIdentifiableStore[model.Identifiable] = model.DictIdentifiableStore() + data: model.DictIdentifiableStore[model.Identifiable] = ( + model.DictIdentifiableStore() + ) data.add(example_submodel_template.create_example_submodel_template()) file = io.StringIO() write_aas_json_file(file=file, data=data) diff --git a/sdk/test/adapter/test_load_directory.py b/sdk/test/adapter/test_load_directory.py index 46b87f5d0..3922d3ee6 100644 --- a/sdk/test/adapter/test_load_directory.py +++ b/sdk/test/adapter/test_load_directory.py @@ -14,28 +14,29 @@ def test_reading_all_files(self): id_="http://example.org/JSON_AAS", asset_information=model.AssetInformation( global_asset_id="http://example.org/JSON_Asset" - ) + ), ) xml_aas = model.AssetAdministrationShell( id_="http://example.org/XML_AAS", asset_information=model.AssetInformation( global_asset_id="http://example.org/XML_Asset" - ) + ), ) aasx_aas = model.AssetAdministrationShell( id_="http://example.org/aasx_AAS", asset_information=model.AssetInformation( global_asset_id="http://example.org/aasx_Asset" - ) + ), ) # load TestFile.pdf to save into aasx file_container = adapter.aasx.DictSupplementaryFileContainer() with open(Path(__file__).parent / "aasx" / "TestFile.pdf", "rb") as pdf: resulting_file_name = file_container.add_file( - "/aasx/suppl/file.pdf", pdf, "application/json") + "/aasx/suppl/file.pdf", pdf, "application/json" + ) # create submodel for aasx_aas that refers to pdf sm_with_file = model.Submodel( @@ -44,9 +45,9 @@ def test_reading_all_files(self): model.File( id_short="SampleFile", content_type="application/json", - value=resulting_file_name + value=resulting_file_name, ) - } + }, ) aasx_aas.submodel.add(model.ModelReference.from_referable(sm_with_file)) @@ -54,17 +55,19 @@ def test_reading_all_files(self): temp_dir_path = Path(temp_dir) # save to json file - adapter.json.write_aas_json_file(temp_dir_path / "testAAS.json", - model.DictIdentifiableStore([json_aas])) + adapter.json.write_aas_json_file( + temp_dir_path / "testAAS.json", model.DictIdentifiableStore([json_aas]) + ) # save to xml file - adapter.xml.write_aas_xml_file(temp_dir_path / "testAAS.xml", - model.DictIdentifiableStore([xml_aas])) + adapter.xml.write_aas_xml_file( + temp_dir_path / "testAAS.xml", model.DictIdentifiableStore([xml_aas]) + ) # save to aasx file with adapter.aasx.AASXWriter(temp_dir_path / "testAAS.aasx") as writer: writer.write_aas( aas_ids=["http://example.org/aasx_AAS"], object_store=model.DictIdentifiableStore([aasx_aas, sm_with_file]), - file_store=file_container + file_store=file_container, ) # ---- Act ---- diff --git a/sdk/test/adapter/xml/test_xml_deserialization.py b/sdk/test/adapter/xml/test_xml_deserialization.py index 395d23ed7..1d6582221 100644 --- a/sdk/test/adapter/xml/test_xml_deserialization.py +++ b/sdk/test/adapter/xml/test_xml_deserialization.py @@ -10,8 +10,13 @@ import unittest from basyx.aas import model -from basyx.aas.adapter.xml import StrictAASFromXmlDecoder, XMLConstructables, read_aas_xml_file, \ - read_aas_xml_file_into, read_aas_xml_element +from basyx.aas.adapter.xml import ( + StrictAASFromXmlDecoder, + XMLConstructables, + read_aas_xml_file, + read_aas_xml_file_into, + read_aas_xml_element, +) from basyx.aas.adapter.xml.xml_deserialization import _tag_replace_namespace from basyx.aas.adapter._generic import XML_NS_MAP from lxml import etree @@ -29,8 +34,13 @@ def _root_cause(exception: BaseException) -> BaseException: class XmlDeserializationTest(unittest.TestCase): - def _assertInExceptionAndLog(self, xml: str, strings: Union[Iterable[str], str], error_type: Type[BaseException], - log_level: int) -> None: + def _assertInExceptionAndLog( + self, + xml: str, + strings: Union[Iterable[str], str], + error_type: Type[BaseException], + log_level: int, + ) -> None: """ Runs read_xml_aas_file in failsafe mode and checks if each string is contained in the first message logged. Then runs it in non-failsafe mode and checks if each string is contained in the first error raised. @@ -56,14 +66,16 @@ def test_malformed_xml(self) -> None: xml = ( "invalid xml", _xml_wrap("<<>>><<<<<"), - _xml_wrap("") + _xml_wrap(""), ) for s in xml: self._assertInExceptionAndLog(s, [], etree.XMLSyntaxError, logging.ERROR) def test_invalid_list_name(self) -> None: xml = _xml_wrap("") - self._assertInExceptionAndLog(xml, "aas:invalidList", TypeError, logging.WARNING) + self._assertInExceptionAndLog( + xml, "aas:invalidList", TypeError, logging.WARNING + ) def test_invalid_element_in_list(self) -> None: xml = _xml_wrap(""" @@ -71,7 +83,9 @@ def test_invalid_element_in_list(self) -> None: """) - self._assertInExceptionAndLog(xml, ["aas:invalidElement", "aas:submodels"], KeyError, logging.WARNING) + self._assertInExceptionAndLog( + xml, ["aas:invalidElement", "aas:submodels"], KeyError, logging.WARNING + ) def test_missing_asset_kind(self) -> None: xml = _xml_wrap(""" @@ -112,7 +126,9 @@ def test_invalid_asset_kind_text(self) -> None: """) - self._assertInExceptionAndLog(xml, ["aas:assetKind", "invalidKind"], ValueError, logging.ERROR) + self._assertInExceptionAndLog( + xml, ["aas:assetKind", "invalidKind"], ValueError, logging.ERROR + ) def test_invalid_boolean(self) -> None: xml = _xml_wrap(""" @@ -170,7 +186,11 @@ def test_reference_kind_mismatch(self) -> None: """) with self.assertLogs(logging.getLogger(), level=logging.WARNING) as context: read_aas_xml_file(io.StringIO(xml), failsafe=False) - for s in ("SUBMODEL", "http://example.org/test_ref", "AssetAdministrationShell"): + for s in ( + "SUBMODEL", + "http://example.org/test_ref", + "AssetAdministrationShell", + ): self.assertIn(s, context.output[0]) def test_invalid_submodel_element(self) -> None: @@ -184,7 +204,9 @@ def test_invalid_submodel_element(self) -> None: """) - self._assertInExceptionAndLog(xml, "aas:invalidSubmodelElement", KeyError, logging.ERROR) + self._assertInExceptionAndLog( + xml, "aas:invalidSubmodelElement", KeyError, logging.ERROR + ) def test_empty_qualifier(self) -> None: xml = _xml_wrap(""" @@ -197,7 +219,9 @@ def test_empty_qualifier(self) -> None: """) - self._assertInExceptionAndLog(xml, ["aas:qualifier", "has no child aas:type"], KeyError, logging.ERROR) + self._assertInExceptionAndLog( + xml, ["aas:qualifier", "has no child aas:type"], KeyError, logging.ERROR + ) def test_operation_variable_no_submodel_element(self) -> None: xml = _xml_wrap(""" @@ -217,7 +241,9 @@ def test_operation_variable_no_submodel_element(self) -> None: """) - self._assertInExceptionAndLog(xml, ["aas:value", "has no submodel element"], KeyError, logging.ERROR) + self._assertInExceptionAndLog( + xml, ["aas:value", "has no submodel element"], KeyError, logging.ERROR + ) def test_operation_variable_too_many_submodel_elements(self) -> None: xml = _xml_wrap(""" @@ -269,13 +295,17 @@ def test_duplicate_identifier(self) -> None: """) - self._assertInExceptionAndLog(xml, "duplicate identifier", KeyError, logging.ERROR) + self._assertInExceptionAndLog( + xml, "duplicate identifier", KeyError, logging.ERROR + ) def test_duplicate_identifier_identifiable_store(self) -> None: sm_id = "http://example.org/test_submodel" def get_clean_store() -> model.DictIdentifiableStore: - store: model.DictIdentifiableStore[model.Identifiable] = model.DictIdentifiableStore() + store: model.DictIdentifiableStore[model.Identifiable] = ( + model.DictIdentifiableStore() + ) submodel_ = model.Submodel(sm_id, id_short="test123") store.add(submodel_) return store @@ -302,7 +332,10 @@ def get_clean_store() -> model.DictIdentifiableStore: identifiable_store = get_clean_store() with self.assertLogs(logging.getLogger(), level=logging.INFO) as log_ctx: identifiers = read_aas_xml_file_into( - identifiable_store, string_io, replace_existing=False, ignore_existing=True + identifiable_store, + string_io, + replace_existing=False, + ignore_existing=True, ) self.assertEqual(len(identifiers), 0) self.assertIn("already exists in the object store", log_ctx.output[0]) @@ -313,7 +346,10 @@ def get_clean_store() -> model.DictIdentifiableStore: identifiable_store = get_clean_store() with self.assertRaises(KeyError) as err_ctx: identifiers = read_aas_xml_file_into( - identifiable_store, string_io, replace_existing=False, ignore_existing=False + identifiable_store, + string_io, + replace_existing=False, + ignore_existing=False, ) self.assertEqual(len(identifiers), 0) cause = _root_cause(err_ctx.exception) @@ -345,8 +381,12 @@ def xml(id_: str) -> str: """ - self._assertInExceptionAndLog(xml(""), f'{{{XML_NS_MAP["aas"]}}}id on line 5 has no text', KeyError, - logging.ERROR) + self._assertInExceptionAndLog( + xml(""), + f"{{{XML_NS_MAP['aas']}}}id on line 5 has no text", + KeyError, + logging.ERROR, + ) read_aas_xml_file(io.StringIO(xml("urn:x-test:test-submodel"))) @@ -377,7 +417,9 @@ def test_stripped_qualifiable(self) -> None: string_io = io.StringIO(xml) # check if XML with qualifiers can be parsed successfully - submodel = read_aas_xml_element(string_io, XMLConstructables.SUBMODEL, failsafe=False) + submodel = read_aas_xml_element( + string_io, XMLConstructables.SUBMODEL, failsafe=False + ) self.assertIsInstance(submodel, model.Submodel) assert isinstance(submodel, model.Submodel) self.assertEqual(len(submodel.qualifier), 1) @@ -385,7 +427,9 @@ def test_stripped_qualifiable(self) -> None: self.assertEqual(len(operation.qualifier), 1) # check if qualifiers are ignored in stripped mode - submodel = read_aas_xml_element(string_io, XMLConstructables.SUBMODEL, failsafe=False, stripped=True) + submodel = read_aas_xml_element( + string_io, XMLConstructables.SUBMODEL, failsafe=False, stripped=True + ) self.assertIsInstance(submodel, model.Submodel) assert isinstance(submodel, model.Submodel) self.assertEqual(len(submodel.qualifier), 0) @@ -415,14 +459,20 @@ def test_stripped_asset_administration_shell(self) -> None: string_io = io.StringIO(xml) # check if XML with submodels can be parsed successfully - aas = read_aas_xml_element(string_io, XMLConstructables.ASSET_ADMINISTRATION_SHELL, failsafe=False) + aas = read_aas_xml_element( + string_io, XMLConstructables.ASSET_ADMINISTRATION_SHELL, failsafe=False + ) self.assertIsInstance(aas, model.AssetAdministrationShell) assert isinstance(aas, model.AssetAdministrationShell) self.assertEqual(len(aas.submodel), 1) # check if submodels are ignored in stripped mode - aas = read_aas_xml_element(string_io, XMLConstructables.ASSET_ADMINISTRATION_SHELL, failsafe=False, - stripped=True) + aas = read_aas_xml_element( + string_io, + XMLConstructables.ASSET_ADMINISTRATION_SHELL, + failsafe=False, + stripped=True, + ) self.assertIsInstance(aas, model.AssetAdministrationShell) assert isinstance(aas, model.AssetAdministrationShell) self.assertEqual(len(aas.submodel), 0) @@ -516,9 +566,12 @@ def __init__(self, *args, **kwargs): class EnhancedAASDecoder(StrictAASFromXmlDecoder): @classmethod - def construct_submodel(cls, element: etree._Element, object_class=EnhancedSubmodel, **kwargs) \ - -> model.Submodel: - return super().construct_submodel(element, object_class=object_class, **kwargs) + def construct_submodel( + cls, element: etree._Element, object_class=EnhancedSubmodel, **kwargs + ) -> model.Submodel: + return super().construct_submodel( + element, object_class=object_class, **kwargs + ) xml = f""" @@ -527,7 +580,9 @@ def construct_submodel(cls, element: etree._Element, object_class=EnhancedSubmod """ string_io = io.StringIO(xml) - submodel = read_aas_xml_element(string_io, XMLConstructables.SUBMODEL, decoder=EnhancedAASDecoder) + submodel = read_aas_xml_element( + string_io, XMLConstructables.SUBMODEL, decoder=EnhancedAASDecoder + ) self.assertIsInstance(submodel, EnhancedSubmodel) assert isinstance(submodel, EnhancedSubmodel) self.assertEqual(submodel.enhanced_attribute, "fancy!") @@ -535,25 +590,25 @@ def construct_submodel(cls, element: etree._Element, object_class=EnhancedSubmod class TestTagReplaceNamespace(unittest.TestCase): def test_known_namespace(self): - tag = '{https://admin-shell.io/aas/3/1}tag' - expected = 'aas:tag' + tag = "{https://admin-shell.io/aas/3/1}tag" + expected = "aas:tag" self.assertEqual(_tag_replace_namespace(tag, XML_NS_MAP), expected) def test_empty_prefix(self): # Empty prefix should not be replaced as otherwise it would apply everywhere - tag = '{https://admin-shell.io/aas/3/1}tag' + tag = "{https://admin-shell.io/aas/3/1}tag" nsmap = {"": "https://admin-shell.io/aas/3/1"} - expected = '{https://admin-shell.io/aas/3/1}tag' + expected = "{https://admin-shell.io/aas/3/1}tag" self.assertEqual(_tag_replace_namespace(tag, nsmap), expected) def test_empty_namespace(self): # Empty namespaces should also have no effect - tag = '{https://admin-shell.io/aas/3/1}tag' + tag = "{https://admin-shell.io/aas/3/1}tag" nsmap = {"aas": ""} - expected = '{https://admin-shell.io/aas/3/1}tag' + expected = "{https://admin-shell.io/aas/3/1}tag" self.assertEqual(_tag_replace_namespace(tag, nsmap), expected) def test_unknown_namespace(self): - tag = '{http://unknownnamespace.com}unknown' - expected = '{http://unknownnamespace.com}unknown' # Unknown namespace should remain unchanged + tag = "{http://unknownnamespace.com}unknown" + expected = "{http://unknownnamespace.com}unknown" # Unknown namespace should remain unchanged self.assertEqual(_tag_replace_namespace(tag, XML_NS_MAP), expected) diff --git a/sdk/test/adapter/xml/test_xml_serialization.py b/sdk/test/adapter/xml/test_xml_serialization.py index 9d0d6a6dd..e2311d9ce 100644 --- a/sdk/test/adapter/xml/test_xml_serialization.py +++ b/sdk/test/adapter/xml/test_xml_serialization.py @@ -13,33 +13,48 @@ from basyx.aas import model from basyx.aas.adapter.xml import write_aas_xml_file, xml_serialization -from basyx.aas.examples.data import example_aas_missing_attributes, example_aas, \ - example_submodel_template, example_aas_mandatory_attributes +from basyx.aas.examples.data import ( + example_aas_missing_attributes, + example_aas, + example_submodel_template, + example_aas_mandatory_attributes, +) -XML_SCHEMA_FILE = os.path.join(os.path.dirname(__file__), '../schemas/aasXMLSchema.xsd') +XML_SCHEMA_FILE = os.path.join(os.path.dirname(__file__), "../schemas/aasXMLSchema.xsd") class XMLSerializationTest(unittest.TestCase): def test_serialize_object(self) -> None: - test_object = model.Property("test_id_short", - model.datatypes.String, - category="PARAMETER", - description=model.MultiLanguageTextType({"en-US": "Germany", "de": "Deutschland"})) - xml_data = xml_serialization.property_to_xml(test_object, xml_serialization.NS_AAS+"test_object") + test_object = model.Property( + "test_id_short", + model.datatypes.String, + category="PARAMETER", + description=model.MultiLanguageTextType( + {"en-US": "Germany", "de": "Deutschland"} + ), + ) + xml_data = xml_serialization.property_to_xml( + test_object, xml_serialization.NS_AAS + "test_object" + ) # todo: is this a correct way to test it? def test_random_object_serialization(self) -> None: aas_identifier = "AAS1" submodel_key = (model.Key(model.KeyTypes.SUBMODEL, "SM1"),) submodel_identifier = submodel_key[0].get_identifier() - assert (submodel_identifier is not None) + assert submodel_identifier is not None submodel_reference = model.ModelReference(submodel_key, model.Submodel) submodel = model.Submodel(submodel_identifier) - test_aas = model.AssetAdministrationShell(model.AssetInformation(global_asset_id="Test"), - aas_identifier, submodel={submodel_reference}) - - test_data: model.DictIdentifiableStore[model.Identifiable] = model.DictIdentifiableStore() + test_aas = model.AssetAdministrationShell( + model.AssetInformation(global_asset_id="Test"), + aas_identifier, + submodel={submodel_reference}, + ) + + test_data: model.DictIdentifiableStore[model.Identifiable] = ( + model.DictIdentifiableStore() + ) test_data.add(test_aas) test_data.add(submodel) @@ -51,7 +66,9 @@ class XMLSerializationSchemaTest(unittest.TestCase): @classmethod def setUpClass(cls): if not os.path.exists(XML_SCHEMA_FILE): - raise unittest.SkipTest(f"XSD schema does not exist at {XML_SCHEMA_FILE}, skipping test") + raise unittest.SkipTest( + f"XSD schema does not exist at {XML_SCHEMA_FILE}, skipping test" + ) def test_random_object_serialization(self) -> None: aas_identifier = "AAS1" @@ -59,14 +76,26 @@ def test_random_object_serialization(self) -> None: submodel_identifier = submodel_key[0].get_identifier() assert submodel_identifier is not None submodel_reference = model.ModelReference(submodel_key, model.Submodel) - submodel = model.Submodel(submodel_identifier, - semantic_id=model.ExternalReference((model.Key(model.KeyTypes.GLOBAL_REFERENCE, - "http://example.org/" - "TestSemanticId"),))) - test_aas = model.AssetAdministrationShell(model.AssetInformation(global_asset_id="Test"), - aas_identifier, submodel={submodel_reference}) + submodel = model.Submodel( + submodel_identifier, + semantic_id=model.ExternalReference( + ( + model.Key( + model.KeyTypes.GLOBAL_REFERENCE, + "http://example.org/TestSemanticId", + ), + ) + ), + ) + test_aas = model.AssetAdministrationShell( + model.AssetInformation(global_asset_id="Test"), + aas_identifier, + submodel={submodel_reference}, + ) # serialize object to xml - test_data: model.DictIdentifiableStore[model.Identifiable] = model.DictIdentifiableStore() + test_data: model.DictIdentifiableStore[model.Identifiable] = ( + model.DictIdentifiableStore() + ) test_data.add(test_aas) test_data.add(submodel) @@ -95,7 +124,9 @@ def test_full_example_serialization(self) -> None: root = etree.parse(file, parser=parser) def test_submodel_template_serialization(self) -> None: - data: model.DictIdentifiableStore[model.Identifiable] = model.DictIdentifiableStore() + data: model.DictIdentifiableStore[model.Identifiable] = ( + model.DictIdentifiableStore() + ) data.add(example_submodel_template.create_example_submodel_template()) file = io.BytesIO() write_aas_xml_file(file=file, data=data) @@ -135,7 +166,9 @@ def test_missing_serialization(self) -> None: root = etree.parse(file, parser=parser) def test_concept_description(self) -> None: - data: model.DictIdentifiableStore[model.Identifiable] = model.DictIdentifiableStore() + data: model.DictIdentifiableStore[model.Identifiable] = ( + model.DictIdentifiableStore() + ) data.add(example_aas.create_example_concept_description()) file = io.BytesIO() write_aas_xml_file(file=file, data=data) diff --git a/sdk/test/adapter/xml/test_xml_serialization_deserialization.py b/sdk/test/adapter/xml/test_xml_serialization_deserialization.py index e38aefd68..3b89a3ba2 100644 --- a/sdk/test/adapter/xml/test_xml_serialization_deserialization.py +++ b/sdk/test/adapter/xml/test_xml_serialization_deserialization.py @@ -9,15 +9,27 @@ import unittest from basyx.aas import model -from basyx.aas.adapter.xml import write_aas_xml_file, read_aas_xml_file, write_aas_xml_element, read_aas_xml_element, \ - XMLConstructables +from basyx.aas.adapter.xml import ( + write_aas_xml_file, + read_aas_xml_file, + write_aas_xml_element, + read_aas_xml_element, + XMLConstructables, +) -from basyx.aas.examples.data import example_aas_missing_attributes, example_aas, \ - example_aas_mandatory_attributes, example_submodel_template, create_example +from basyx.aas.examples.data import ( + example_aas_missing_attributes, + example_aas, + example_aas_mandatory_attributes, + example_submodel_template, + create_example, +) from basyx.aas.examples.data._helper import AASDataChecker -def _serialize_and_deserialize(data: model.DictIdentifiableStore) -> model.DictIdentifiableStore: +def _serialize_and_deserialize( + data: model.DictIdentifiableStore, +) -> model.DictIdentifiableStore: file = io.BytesIO() write_aas_xml_file(file=file, data=data) @@ -28,22 +40,30 @@ def _serialize_and_deserialize(data: model.DictIdentifiableStore) -> model.DictI class XMLSerializationDeserializationTest(unittest.TestCase): def test_example_serialization_deserialization(self) -> None: - identifiable_store = _serialize_and_deserialize(example_aas.create_full_example()) + identifiable_store = _serialize_and_deserialize( + example_aas.create_full_example() + ) checker = AASDataChecker(raise_immediately=True) example_aas.check_full_example(checker, identifiable_store) def test_example_mandatory_attributes_serialization_deserialization(self) -> None: - identifiable_store = _serialize_and_deserialize(example_aas_mandatory_attributes.create_full_example()) + identifiable_store = _serialize_and_deserialize( + example_aas_mandatory_attributes.create_full_example() + ) checker = AASDataChecker(raise_immediately=True) example_aas_mandatory_attributes.check_full_example(checker, identifiable_store) def test_example_missing_attributes_serialization_deserialization(self) -> None: - identifiable_store = _serialize_and_deserialize(example_aas_missing_attributes.create_full_example()) + identifiable_store = _serialize_and_deserialize( + example_aas_missing_attributes.create_full_example() + ) checker = AASDataChecker(raise_immediately=True) example_aas_missing_attributes.check_full_example(checker, identifiable_store) def test_example_submodel_template_serialization_deserialization(self) -> None: - data: model.DictIdentifiableStore[model.Identifiable] = model.DictIdentifiableStore() + data: model.DictIdentifiableStore[model.Identifiable] = ( + model.DictIdentifiableStore() + ) data.add(example_submodel_template.create_example_submodel_template()) identifiable_store = _serialize_and_deserialize(data) checker = AASDataChecker(raise_immediately=True) @@ -58,11 +78,16 @@ def test_example_all_examples_serialization_deserialization(self) -> None: class XMLSerializationDeserializationSingleObjectTest(unittest.TestCase): def test_submodel_serialization_deserialization(self) -> None: - submodel: model.Submodel = example_submodel_template.create_example_submodel_template() + submodel: model.Submodel = ( + example_submodel_template.create_example_submodel_template() + ) bytes_io = io.BytesIO() write_aas_xml_element(bytes_io, submodel) bytes_io.seek(0) - submodel2: model.Submodel = read_aas_xml_element(bytes_io, # type: ignore[assignment] - XMLConstructables.SUBMODEL, failsafe=False) + submodel2: model.Submodel = read_aas_xml_element( + bytes_io, # type: ignore[assignment] + XMLConstructables.SUBMODEL, + failsafe=False, + ) checker = AASDataChecker(raise_immediately=True) checker.check_submodel_equal(submodel2, submodel) diff --git a/sdk/test/backend/test_couchdb.py b/sdk/test/backend/test_couchdb.py index 16f607047..29913127d 100644 --- a/sdk/test/backend/test_couchdb.py +++ b/sdk/test/backend/test_couchdb.py @@ -14,20 +14,31 @@ from test._helper.test_helpers import TEST_CONFIG, COUCHDB_OKAY, COUCHDB_ERROR -source_core: str = "couchdb://" + TEST_CONFIG["couchdb"]["url"].lstrip("http://") + "/" + \ - TEST_CONFIG["couchdb"]["database"] + "/" - - -@unittest.skipUnless(COUCHDB_OKAY, "No CouchDB is reachable at {}/{}: {}".format(TEST_CONFIG['couchdb']['url'], - TEST_CONFIG['couchdb']['database'], - COUCHDB_ERROR)) +source_core: str = ( + "couchdb://" + + TEST_CONFIG["couchdb"]["url"].lstrip("http://") + + "/" + + TEST_CONFIG["couchdb"]["database"] + + "/" +) + + +@unittest.skipUnless( + COUCHDB_OKAY, + "No CouchDB is reachable at {}/{}: {}".format( + TEST_CONFIG["couchdb"]["url"], TEST_CONFIG["couchdb"]["database"], COUCHDB_ERROR + ), +) class CouchDBBackendTest(unittest.TestCase): def setUp(self) -> None: - self.couch_identifiable_store = couchdb.CouchDBIdentifiableStore(TEST_CONFIG['couchdb']['url'], - TEST_CONFIG['couchdb']['database']) - couchdb.register_credentials(TEST_CONFIG["couchdb"]["url"], - TEST_CONFIG["couchdb"]["user"], - TEST_CONFIG["couchdb"]["password"]) + self.couch_identifiable_store = couchdb.CouchDBIdentifiableStore( + TEST_CONFIG["couchdb"]["url"], TEST_CONFIG["couchdb"]["database"] + ) + couchdb.register_credentials( + TEST_CONFIG["couchdb"]["url"], + TEST_CONFIG["couchdb"]["user"], + TEST_CONFIG["couchdb"]["password"], + ) self.couch_identifiable_store.check_database() def tearDown(self) -> None: @@ -44,12 +55,16 @@ def test_retrieval(self): self.couch_identifiable_store.add(test_object) # When retrieving the object, we should get the *same* instance as we added - test_object_retrieved = self.couch_identifiable_store.get_item('https://example.org/Test_Submodel') + test_object_retrieved = self.couch_identifiable_store.get_item( + "https://example.org/Test_Submodel" + ) self.assertIs(test_object, test_object_retrieved) # When retrieving it again, we should still get the same object del test_object - test_object_retrieved_again = self.couch_identifiable_store.get_item('https://example.org/Test_Submodel') + test_object_retrieved_again = self.couch_identifiable_store.get_item( + "https://example.org/Test_Submodel" + ) self.assertIs(test_object_retrieved, test_object_retrieved_again) def test_example_submodel_storing(self) -> None: @@ -61,8 +76,10 @@ def test_example_submodel_storing(self) -> None: self.assertIn(example_submodel, self.couch_identifiable_store) # Restore example submodel and check data - submodel_restored = self.couch_identifiable_store.get_item('https://example.org/Test_Submodel') - assert (isinstance(submodel_restored, model.Submodel)) + submodel_restored = self.couch_identifiable_store.get_item( + "https://example.org/Test_Submodel" + ) + assert isinstance(submodel_restored, model.Submodel) checker = AASDataChecker(raise_immediately=True) check_example_submodel(checker, submodel_restored) @@ -80,9 +97,9 @@ def test_iterating(self) -> None: self.assertEqual(5, len(self.couch_identifiable_store)) # Iterate objects, add them to a DictIdentifiableStore and check them - retrieved_data_store: model.provider.DictIdentifiableStore[model.Identifiable] = ( - model.provider.DictIdentifiableStore() - ) + retrieved_data_store: model.provider.DictIdentifiableStore[ + model.Identifiable + ] = model.provider.DictIdentifiableStore() for item in self.couch_identifiable_store: retrieved_data_store.add(item) checker = AASDataChecker(raise_immediately=True) @@ -94,19 +111,29 @@ def test_key_errors(self) -> None: self.couch_identifiable_store.add(example_submodel) with self.assertRaises(KeyError) as cm: self.couch_identifiable_store.add(example_submodel) - self.assertEqual("'Identifiable with id https://example.org/Test_Submodel already exists in " - "CouchDB database'", str(cm.exception)) + self.assertEqual( + "'Identifiable with id https://example.org/Test_Submodel already exists in " + "CouchDB database'", + str(cm.exception), + ) # Querying a deleted object should raise a KeyError - retrieved_submodel = self.couch_identifiable_store.get_item('https://example.org/Test_Submodel') + retrieved_submodel = self.couch_identifiable_store.get_item( + "https://example.org/Test_Submodel" + ) self.couch_identifiable_store.discard(example_submodel) with self.assertRaises(KeyError) as cm: - self.couch_identifiable_store.get_item('https://example.org/Test_Submodel') - self.assertEqual("'No Identifiable with id https://example.org/Test_Submodel found in CouchDB database'", - str(cm.exception)) + self.couch_identifiable_store.get_item("https://example.org/Test_Submodel") + self.assertEqual( + "'No Identifiable with id https://example.org/Test_Submodel found in CouchDB database'", + str(cm.exception), + ) # Double deleting should also raise a KeyError with self.assertRaises(KeyError) as cm: self.couch_identifiable_store.discard(retrieved_submodel) - self.assertEqual("'No AAS object with id https://example.org/Test_Submodel exists in " - "CouchDB database'", str(cm.exception)) + self.assertEqual( + "'No AAS object with id https://example.org/Test_Submodel exists in " + "CouchDB database'", + str(cm.exception), + ) diff --git a/sdk/test/backend/test_local_file.py b/sdk/test/backend/test_local_file.py index e71f4f27a..e13d20fab 100644 --- a/sdk/test/backend/test_local_file.py +++ b/sdk/test/backend/test_local_file.py @@ -40,12 +40,16 @@ def test_retrieval(self): self.identifiable_store.add(test_object) # When retrieving the object, we should get the *same* instance as we added - test_object_retrieved = self.identifiable_store.get_item('https://example.org/Test_Submodel') + test_object_retrieved = self.identifiable_store.get_item( + "https://example.org/Test_Submodel" + ) self.assertIs(test_object, test_object_retrieved) # When retrieving it again, we should still get the same object del test_object - test_object_retrieved_again = self.identifiable_store.get_item('https://example.org/Test_Submodel') + test_object_retrieved_again = self.identifiable_store.get_item( + "https://example.org/Test_Submodel" + ) self.assertIs(test_object_retrieved, test_object_retrieved_again) def test_example_submodel_storing(self) -> None: @@ -57,8 +61,10 @@ def test_example_submodel_storing(self) -> None: self.assertIn(example_submodel, self.identifiable_store) # Restore example submodel and check data - submodel_restored = self.identifiable_store.get_item('https://example.org/Test_Submodel') - assert (isinstance(submodel_restored, model.Submodel)) + submodel_restored = self.identifiable_store.get_item( + "https://example.org/Test_Submodel" + ) + assert isinstance(submodel_restored, model.Submodel) checker = AASDataChecker(raise_immediately=True) check_example_submodel(checker, submodel_restored) @@ -95,9 +101,9 @@ def test_iterating(self) -> None: self.assertEqual(5, len(self.identifiable_store)) # Iterate objects, add them to a DictIdentifiableStore and check them - retrieved_data_store: model.provider.DictIdentifiableStore[model.Identifiable] = ( - model.provider.DictIdentifiableStore() - ) + retrieved_data_store: model.provider.DictIdentifiableStore[ + model.Identifiable + ] = model.provider.DictIdentifiableStore() for item in self.identifiable_store: retrieved_data_store.add(item) checker = AASDataChecker(raise_immediately=True) @@ -109,23 +115,33 @@ def test_key_errors(self) -> None: self.identifiable_store.add(example_submodel) with self.assertRaises(KeyError) as cm: self.identifiable_store.add(example_submodel) - self.assertEqual("'Identifiable with id https://example.org/Test_Submodel already exists in " - "local file database'", str(cm.exception)) + self.assertEqual( + "'Identifiable with id https://example.org/Test_Submodel already exists in " + "local file database'", + str(cm.exception), + ) # Querying a deleted object should raise a KeyError - retrieved_submodel = self.identifiable_store.get_item('https://example.org/Test_Submodel') + retrieved_submodel = self.identifiable_store.get_item( + "https://example.org/Test_Submodel" + ) self.identifiable_store.discard(example_submodel) with self.assertRaises(KeyError) as cm: - self.identifiable_store.get_item('https://example.org/Test_Submodel') - self.assertEqual("'No Identifiable with id https://example.org/Test_Submodel " - "found in local file database'", - str(cm.exception)) + self.identifiable_store.get_item("https://example.org/Test_Submodel") + self.assertEqual( + "'No Identifiable with id https://example.org/Test_Submodel " + "found in local file database'", + str(cm.exception), + ) # Double deleting should also raise a KeyError with self.assertRaises(KeyError) as cm: self.identifiable_store.discard(retrieved_submodel) - self.assertEqual("'No AAS object with id https://example.org/Test_Submodel exists in " - "local file database'", str(cm.exception)) + self.assertEqual( + "'No AAS object with id https://example.org/Test_Submodel exists in " + "local file database'", + str(cm.exception), + ) def test_add_and_len_consistent(self) -> None: # Each add() must increment len() by exactly 1 @@ -156,29 +172,35 @@ def test_iter_ignores_non_json_files(self) -> None: def test_mutation_persistence(self) -> None: submodel = model.Submodel( - id_='https://example.org/MutationTest', + id_="https://example.org/MutationTest", submodel_element={ - model.Property(id_short='Prop', value_type=model.datatypes.String, value='before') - } + model.Property( + id_short="Prop", value_type=model.datatypes.String, value="before" + ) + }, ) self.identifiable_store.add(submodel) - retrieved = self.identifiable_store.get_item('https://example.org/MutationTest') + retrieved = self.identifiable_store.get_item("https://example.org/MutationTest") assert isinstance(retrieved, model.Submodel) - prop = retrieved.get_referable(['Prop']) + prop = retrieved.get_referable(["Prop"]) assert isinstance(prop, model.Property) - prop.update_from(model.Property(id_short='Prop', value_type=model.datatypes.String, value='after')) + prop.update_from( + model.Property( + id_short="Prop", value_type=model.datatypes.String, value="after" + ) + ) self.identifiable_store.commit(retrieved) # Drop all strong references to evict the WeakValueDictionary cache del submodel, retrieved, prop gc.collect() - fresh = self.identifiable_store.get_item('https://example.org/MutationTest') + fresh = self.identifiable_store.get_item("https://example.org/MutationTest") assert isinstance(fresh, model.Submodel) - fresh_prop = fresh.get_referable(['Prop']) + fresh_prop = fresh.get_referable(["Prop"]) assert isinstance(fresh_prop, model.Property) - self.assertEqual('after', fresh_prop.value) + self.assertEqual("after", fresh_prop.value) def test_reload_discard(self) -> None: # Load example submodel diff --git a/sdk/test/examples/test__init__.py b/sdk/test/examples/test__init__.py index 1f1e26234..d5535e644 100644 --- a/sdk/test/examples/test__init__.py +++ b/sdk/test/examples/test__init__.py @@ -11,7 +11,6 @@ class TestExampleFunctions(unittest.TestCase): - def test_create_example(self): identifiable_store = create_example() @@ -20,9 +19,9 @@ def test_create_example(self): # Check that the object store contains expected elements expected_ids = [ - 'https://example.org/Test_AssetAdministrationShell', - 'https://example.org/Test_Submodel_Template', - 'https://example.org/Test_ConceptDescription_Mandatory' + "https://example.org/Test_AssetAdministrationShell", + "https://example.org/Test_Submodel_Template", + "https://example.org/Test_ConceptDescription_Mandatory", ] for id in expected_ids: self.assertIsNotNone(identifiable_store.get_item(id)) @@ -34,9 +33,9 @@ def test_create_example_aas_binding(self): self.assertGreater(len(identifiable_store), 0) # Check that the object store contains expected elements - aas_id = 'https://example.org/Test_AssetAdministrationShell' - sm_id = 'https://example.org/Test_Submodel_Template' - cd_id = 'https://example.org/Test_ConceptDescription_Mandatory' + aas_id = "https://example.org/Test_AssetAdministrationShell" + sm_id = "https://example.org/Test_Submodel_Template" + cd_id = "https://example.org/Test_ConceptDescription_Mandatory" aas = identifiable_store.get_item(aas_id) sm = identifiable_store.get_item(sm_id) diff --git a/sdk/test/examples/test_examples.py b/sdk/test/examples/test_examples.py index 7f17a5db6..d806e8aa1 100644 --- a/sdk/test/examples/test_examples.py +++ b/sdk/test/examples/test_examples.py @@ -6,8 +6,12 @@ # SPDX-License-Identifier: MIT import unittest -from basyx.aas.examples.data import example_aas, example_aas_mandatory_attributes, example_aas_missing_attributes, \ - example_submodel_template +from basyx.aas.examples.data import ( + example_aas, + example_aas_mandatory_attributes, + example_aas_missing_attributes, + example_submodel_template, +) from basyx.aas.examples.data._helper import AASDataChecker from basyx.aas import model @@ -43,15 +47,16 @@ def test_full_example(self): identifiable_store = model.DictIdentifiableStore() with self.assertRaises(AssertionError) as cm: example_aas.check_full_example(checker, identifiable_store) - self.assertIn("AssetAdministrationShell[https://example.org/Test_AssetAdministrationShell]", - str(cm.exception)) + self.assertIn( + "AssetAdministrationShell[https://example.org/Test_AssetAdministrationShell]", + str(cm.exception), + ) identifiable_store = example_aas.create_full_example() example_aas.check_full_example(checker, identifiable_store) failed_shell = model.AssetAdministrationShell( - asset_information=model.AssetInformation(global_asset_id='test'), - id_='test' + asset_information=model.AssetInformation(global_asset_id="test"), id_="test" ) identifiable_store.add(failed_shell) with self.assertRaises(AssertionError) as cm: @@ -59,14 +64,14 @@ def test_full_example(self): self.assertIn("AssetAdministrationShell[test]", str(cm.exception)) identifiable_store.discard(failed_shell) - failed_submodel = model.Submodel(id_='test') + failed_submodel = model.Submodel(id_="test") identifiable_store.add(failed_submodel) with self.assertRaises(AssertionError) as cm: example_aas.check_full_example(checker, identifiable_store) self.assertIn("Submodel[test]", str(cm.exception)) identifiable_store.discard(failed_submodel) - failed_cd = model.ConceptDescription(id_='test') + failed_cd = model.ConceptDescription(id_="test") identifiable_store.add(failed_cd) with self.assertRaises(AssertionError) as cm: example_aas.check_full_example(checker, identifiable_store) @@ -77,11 +82,14 @@ class DummyIdentifiable(model.Identifiable): def __init__(self, id_: model.Identifier): super().__init__() self.id = id_ - failed_identifiable = DummyIdentifiable(id_='test') + + failed_identifiable = DummyIdentifiable(id_="test") identifiable_store.add(failed_identifiable) with self.assertRaises(KeyError) as cm: example_aas.check_full_example(checker, identifiable_store) - self.assertIn("Check for DummyIdentifiable[test] not implemented", str(cm.exception)) + self.assertIn( + "Check for DummyIdentifiable[test] not implemented", str(cm.exception) + ) identifiable_store.discard(failed_identifiable) example_aas.check_full_example(checker, identifiable_store) @@ -99,24 +107,36 @@ def test_example_empty_submodel(self): def test_example_concept_description(self): checker = AASDataChecker(raise_immediately=True) - concept_description = example_aas_mandatory_attributes.create_example_concept_description() - example_aas_mandatory_attributes.check_example_concept_description(checker, concept_description) + concept_description = ( + example_aas_mandatory_attributes.create_example_concept_description() + ) + example_aas_mandatory_attributes.check_example_concept_description( + checker, concept_description + ) def test_example_asset_administration_shell(self): checker = AASDataChecker(raise_immediately=True) - shell = example_aas_mandatory_attributes.create_example_asset_administration_shell() - example_aas_mandatory_attributes.check_example_asset_administration_shell(checker, shell) + shell = ( + example_aas_mandatory_attributes.create_example_asset_administration_shell() + ) + example_aas_mandatory_attributes.check_example_asset_administration_shell( + checker, shell + ) def test_full_example(self): checker = AASDataChecker(raise_immediately=True) identifiable_store = example_aas_mandatory_attributes.create_full_example() example_aas_mandatory_attributes.check_full_example(checker, identifiable_store) - failed_submodel = model.Submodel(id_='test') + failed_submodel = model.Submodel(id_="test") identifiable_store.add(failed_submodel) with self.assertRaises(AssertionError) as cm: - example_aas_mandatory_attributes.check_full_example(checker, identifiable_store) - self.assertIn("Given submodel list must not have extra submodels", str(cm.exception)) + example_aas_mandatory_attributes.check_full_example( + checker, identifiable_store + ) + self.assertIn( + "Given submodel list must not have extra submodels", str(cm.exception) + ) self.assertIn("Submodel[test]", str(cm.exception)) identifiable_store.discard(failed_submodel) example_aas_mandatory_attributes.check_full_example(checker, identifiable_store) @@ -130,24 +150,36 @@ def test_example_submodel(self): def test_example_concept_description(self): checker = AASDataChecker(raise_immediately=True) - concept_description = example_aas_missing_attributes.create_example_concept_description() - example_aas_missing_attributes.check_example_concept_description(checker, concept_description) + concept_description = ( + example_aas_missing_attributes.create_example_concept_description() + ) + example_aas_missing_attributes.check_example_concept_description( + checker, concept_description + ) def test_example_asset_administration_shell(self): checker = AASDataChecker(raise_immediately=True) - shell = example_aas_missing_attributes.create_example_asset_administration_shell() - example_aas_missing_attributes.check_example_asset_administration_shell(checker, shell) + shell = ( + example_aas_missing_attributes.create_example_asset_administration_shell() + ) + example_aas_missing_attributes.check_example_asset_administration_shell( + checker, shell + ) def test_full_example(self): checker = AASDataChecker(raise_immediately=True) identifiable_store = example_aas_missing_attributes.create_full_example() example_aas_missing_attributes.check_full_example(checker, identifiable_store) - failed_submodel = model.Submodel(id_='test') + failed_submodel = model.Submodel(id_="test") identifiable_store.add(failed_submodel) with self.assertRaises(AssertionError) as cm: - example_aas_missing_attributes.check_full_example(checker, identifiable_store) - self.assertIn("Given submodel list must not have extra submodels", str(cm.exception)) + example_aas_missing_attributes.check_full_example( + checker, identifiable_store + ) + self.assertIn( + "Given submodel list must not have extra submodels", str(cm.exception) + ) self.assertIn("Submodel[test]", str(cm.exception)) identifiable_store.discard(failed_submodel) example_aas_missing_attributes.check_full_example(checker, identifiable_store) @@ -161,15 +193,21 @@ def test_example_submodel_template(self): def test_full_example(self): checker = AASDataChecker(raise_immediately=True) - identifiable_store: model.DictIdentifiableStore[model.Identifiable] = model.DictIdentifiableStore() - identifiable_store.add(example_submodel_template.create_example_submodel_template()) + identifiable_store: model.DictIdentifiableStore[model.Identifiable] = ( + model.DictIdentifiableStore() + ) + identifiable_store.add( + example_submodel_template.create_example_submodel_template() + ) example_submodel_template.check_full_example(checker, identifiable_store) - failed_submodel = model.Submodel(id_='test') + failed_submodel = model.Submodel(id_="test") identifiable_store.add(failed_submodel) with self.assertRaises(AssertionError) as cm: example_submodel_template.check_full_example(checker, identifiable_store) - self.assertIn("Given submodel list must not have extra submodels", str(cm.exception)) + self.assertIn( + "Given submodel list must not have extra submodels", str(cm.exception) + ) self.assertIn("Submodel[test]", str(cm.exception)) identifiable_store.discard(failed_submodel) diff --git a/sdk/test/examples/test_helpers.py b/sdk/test/examples/test_helpers.py index 74aebb518..05b88026f 100644 --- a/sdk/test/examples/test_helpers.py +++ b/sdk/test/examples/test_helpers.py @@ -14,47 +14,51 @@ class DataCheckerTest(unittest.TestCase): def test_check(self): checker = DataChecker(raise_immediately=True) with self.assertRaises(AssertionError) as cm: - checker.check(2 == 3, 'Assertion test') + checker.check(2 == 3, "Assertion test") self.assertEqual("('Check failed: Assertion test', {})", str(cm.exception)) def test_kwargs(self): checker = DataChecker(raise_immediately=True) with self.assertRaises(AssertionError) as cm: - checker.check(2 == 3, 'Assertion test 1', value='kwargs1') + checker.check(2 == 3, "Assertion test 1", value="kwargs1") with self.assertRaises(AssertionError) as cm_2: - checker.check(2 == 3, 'Assertion test 2', value='kwargs2') - self.assertEqual("('Check failed: Assertion test 1', {'value': 'kwargs1'})", str(cm.exception)) - self.assertEqual("('Check failed: Assertion test 2', {'value': 'kwargs2'})", str(cm_2.exception)) + checker.check(2 == 3, "Assertion test 2", value="kwargs2") + self.assertEqual( + "('Check failed: Assertion test 1', {'value': 'kwargs1'})", + str(cm.exception), + ) + self.assertEqual( + "('Check failed: Assertion test 2', {'value': 'kwargs2'})", + str(cm_2.exception), + ) def test_raise_failed(self): checker = DataChecker(raise_immediately=False) - checker.check(2 == 2, 'Assertion test') + checker.check(2 == 2, "Assertion test") checker.raise_failed() # no assertion should be occur self.assertEqual(1, sum(1 for _ in checker.successful_checks)) - checker.check(2 == 3, 'Assertion test') + checker.check(2 == 3, "Assertion test") with self.assertRaises(AssertionError) as cm: checker.raise_failed() - self.assertEqual("('1 of 2 checks failed', ['Assertion test'])", str(cm.exception)) + self.assertEqual( + "('1 of 2 checks failed', ['Assertion test'])", str(cm.exception) + ) class AASDataCheckerTest(unittest.TestCase): def test_qualifiable_checker(self): qualifier_expected = model.Qualifier( - type_='test', - value_type=model.datatypes.String, - value='test value' + type_="test", value_type=model.datatypes.String, value="test value" ) property_expected = model.Property( - id_short='Prop1', + id_short="Prop1", value_type=model.datatypes.String, - value='test', - qualifier={qualifier_expected} + value="test", + qualifier={qualifier_expected}, ) property = model.Property( - id_short='Prop1', - value_type=model.datatypes.String, - value='test' + id_short="Prop1", value_type=model.datatypes.String, value="test" ) checker = AASDataChecker(raise_immediately=False) @@ -63,9 +67,13 @@ def test_qualifiable_checker(self): self.assertEqual(2, sum(1 for _ in checker.failed_checks)) self.assertEqual(14, sum(1 for _ in checker.successful_checks)) checker_iterator = checker.failed_checks - self.assertEqual("FAIL: Attribute qualifier of Property[Prop1] must contain 1 Qualifiers (count=0)", - repr(next(checker_iterator))) - self.assertEqual("FAIL: Qualifier(type=test) must exist ()", repr(next(checker_iterator))) + self.assertEqual( + "FAIL: Attribute qualifier of Property[Prop1] must contain 1 Qualifiers (count=0)", + repr(next(checker_iterator)), + ) + self.assertEqual( + "FAIL: Qualifier(type=test) must exist ()", repr(next(checker_iterator)) + ) def test_submodel_element_list_checker(self): @@ -73,31 +81,35 @@ def test_submodel_element_list_checker(self): range1 = model.Range(None, model.datatypes.Int, 42, 142857) range2 = model.Range(None, model.datatypes.Int, 42, 1337) list_ = model.SubmodelElementList( - id_short='test_list', + id_short="test_list", type_value_list_element=model.Range, value_type_list_element=model.datatypes.Int, order_relevant=True, - value=(range1, range2) + value=(range1, range2), ) range1_expected = model.Range(None, model.datatypes.Int, 42, 142857) range2_expected = model.Range(None, model.datatypes.Int, 42, 1337) list_expected = model.SubmodelElementList( - id_short='test_list', + id_short="test_list", type_value_list_element=model.Range, value_type_list_element=model.datatypes.Int, order_relevant=True, - value=(range2_expected, range1_expected) + value=(range2_expected, range1_expected), ) checker = AASDataChecker(raise_immediately=False) checker.check_submodel_element_list_equal(list_, list_expected) self.assertEqual(2, sum(1 for _ in checker.failed_checks)) checker_iterator = checker.failed_checks - self.assertEqual("FAIL: Attribute max of Range[test_list[0]] must be == 1337 (value=142857)", - repr(next(checker_iterator))) - self.assertEqual("FAIL: Attribute max of Range[test_list[1]] must be == 142857 (value=1337)", - repr(next(checker_iterator))) + self.assertEqual( + "FAIL: Attribute max of Range[test_list[0]] must be == 1337 (value=142857)", + repr(next(checker_iterator)), + ) + self.assertEqual( + "FAIL: Attribute max of Range[test_list[1]] must be == 142857 (value=1337)", + repr(next(checker_iterator)), + ) # order_relevant # Don't set protected attributes like this in production code! @@ -105,20 +117,26 @@ def test_submodel_element_list_checker(self): checker = AASDataChecker(raise_immediately=False) with self.assertRaises(NotImplementedError) as cm: checker.check_submodel_element_list_equal(list_, list_expected) - self.assertEqual("A SubmodelElementList with order_relevant=False cannot be compared!", str(cm.exception)) + self.assertEqual( + "A SubmodelElementList with order_relevant=False cannot be compared!", + str(cm.exception), + ) self.assertEqual(1, sum(1 for _ in checker.failed_checks)) checker_iterator = checker.failed_checks - self.assertEqual("FAIL: Attribute order_relevant of SubmodelElementList[test_list] must be == True " - "(value=False)", repr(next(checker_iterator))) + self.assertEqual( + "FAIL: Attribute order_relevant of SubmodelElementList[test_list] must be == True " + "(value=False)", + repr(next(checker_iterator)), + ) # value_type_list_element list_ = model.SubmodelElementList( - id_short='test_list', + id_short="test_list", type_value_list_element=model.Range, value_type_list_element=model.datatypes.Int, ) list_expected = model.SubmodelElementList( - id_short='test_list', + id_short="test_list", type_value_list_element=model.Range, value_type_list_element=model.datatypes.String, ) @@ -126,8 +144,11 @@ def test_submodel_element_list_checker(self): checker.check_submodel_element_list_equal(list_, list_expected) self.assertEqual(1, sum(1 for _ in checker.failed_checks)) checker_iterator = checker.failed_checks - self.assertEqual("FAIL: Attribute value_type_list_element of SubmodelElementList[test_list] must be == str " - "(value='Int')", repr(next(checker_iterator))) + self.assertEqual( + "FAIL: Attribute value_type_list_element of SubmodelElementList[test_list] must be == str " + "(value='Int')", + repr(next(checker_iterator)), + ) # Don't set protected attributes like this in production code! list_._value_type_list_element = model.datatypes.String @@ -137,12 +158,12 @@ def test_submodel_element_list_checker(self): # type_value_list_element list_ = model.SubmodelElementList( - id_short='test_list', + id_short="test_list", type_value_list_element=model.Range, value_type_list_element=model.datatypes.Int, ) list_expected = model.SubmodelElementList( - id_short='test_list', + id_short="test_list", type_value_list_element=model.Property, value_type_list_element=model.datatypes.Int, ) @@ -150,9 +171,11 @@ def test_submodel_element_list_checker(self): checker.check_submodel_element_list_equal(list_, list_expected) self.assertEqual(1, sum(1 for _ in checker.failed_checks)) checker_iterator = checker.failed_checks - self.assertEqual("FAIL: Attribute type_value_list_element of SubmodelElementList[test_list] must be == " - "Property (value='Range')", - repr(next(checker_iterator))) + self.assertEqual( + "FAIL: Attribute type_value_list_element of SubmodelElementList[test_list] must be == " + "Property (value='Range')", + repr(next(checker_iterator)), + ) # Don't set protected attributes like this in production code! list_._type_value_list_element = model.Property @@ -162,73 +185,70 @@ def test_submodel_element_list_checker(self): # semantic_id_list_element list_ = model.SubmodelElementList( - id_short='test_list', + id_short="test_list", type_value_list_element=model.MultiLanguageProperty, semantic_id_list_element=model.ExternalReference( - (model.Key(model.KeyTypes.GLOBAL_REFERENCE, "urn:x-test:invalid"),)) + (model.Key(model.KeyTypes.GLOBAL_REFERENCE, "urn:x-test:invalid"),) + ), ) list_expected = model.SubmodelElementList( - id_short='test_list', + id_short="test_list", type_value_list_element=model.MultiLanguageProperty, semantic_id_list_element=model.ExternalReference( - (model.Key(model.KeyTypes.GLOBAL_REFERENCE, "urn:x-test:test"),)) + (model.Key(model.KeyTypes.GLOBAL_REFERENCE, "urn:x-test:test"),) + ), ) checker = AASDataChecker(raise_immediately=False) checker.check_submodel_element_list_equal(list_, list_expected) self.assertEqual(1, sum(1 for _ in checker.failed_checks)) checker_iterator = checker.failed_checks - self.assertEqual("FAIL: Attribute semantic_id_list_element of SubmodelElementList[test_list] must be == " - "ExternalReference(key=(Key(type=GLOBAL_REFERENCE, value=urn:x-test:test),)) " - "(value=ExternalReference(key=(Key(type=GLOBAL_REFERENCE, value=urn:x-test:invalid),)))", - repr(next(checker_iterator))) + self.assertEqual( + "FAIL: Attribute semantic_id_list_element of SubmodelElementList[test_list] must be == " + "ExternalReference(key=(Key(type=GLOBAL_REFERENCE, value=urn:x-test:test),)) " + "(value=ExternalReference(key=(Key(type=GLOBAL_REFERENCE, value=urn:x-test:invalid),)))", + repr(next(checker_iterator)), + ) # Don't set protected attributes like this in production code! list_._semantic_id_list_element = model.ExternalReference( - (model.Key(model.KeyTypes.GLOBAL_REFERENCE, "urn:x-test:test"),)) + (model.Key(model.KeyTypes.GLOBAL_REFERENCE, "urn:x-test:test"),) + ) checker = AASDataChecker(raise_immediately=False) checker.check_submodel_element_list_equal(list_, list_expected) self.assertEqual(0, sum(1 for _ in checker.failed_checks)) def test_submodel_element_collection_checker(self): property = model.Property( - id_short='Prop1', - value_type=model.datatypes.String, - value='test' + id_short="Prop1", value_type=model.datatypes.String, value="test" ) range_ = model.Range( - id_short='Range1', - value_type=model.datatypes.Int, - min=100, - max=200 + id_short="Range1", value_type=model.datatypes.Int, min=100, max=200 ) collection = model.SubmodelElementCollection( - id_short='Collection', - value=(range_,) + id_short="Collection", value=(range_,) ) property_expected = model.Property( - id_short='Prop1', - value_type=model.datatypes.String, - value='test' + id_short="Prop1", value_type=model.datatypes.String, value="test" ) range_expected = model.Range( - id_short='Range1', - value_type=model.datatypes.Int, - min=100, - max=200 + id_short="Range1", value_type=model.datatypes.Int, min=100, max=200 ) collection_expected = model.SubmodelElementCollection( - id_short='Collection', - value=(property_expected, range_expected) + id_short="Collection", value=(property_expected, range_expected) ) checker = AASDataChecker(raise_immediately=False) checker.check_submodel_element_collection_equal(collection, collection_expected) self.assertEqual(2, sum(1 for _ in checker.failed_checks)) checker_iterator = checker.failed_checks - self.assertEqual("FAIL: Attribute value of SubmodelElementCollection[Collection] must contain 2 " - "SubmodelElements (count=1)", - repr(next(checker_iterator))) - self.assertEqual("FAIL: Submodel Element Property[Collection.Prop1] must exist ()", - repr(next(checker_iterator))) + self.assertEqual( + "FAIL: Attribute value of SubmodelElementCollection[Collection] must contain 2 " + "SubmodelElements (count=1)", + repr(next(checker_iterator)), + ) + self.assertEqual( + "FAIL: Submodel Element Property[Collection.Prop1] must exist ()", + repr(next(checker_iterator)), + ) collection.add_referable(property) checker = AASDataChecker(raise_immediately=False) @@ -238,121 +258,159 @@ def test_not_implemented(self): class DummySubmodelElement(model.SubmodelElement): def __init__(self, id_short: model.NameType): super().__init__(id_short) - dummy_submodel_element = DummySubmodelElement('test') - submodel_collection = model.SubmodelElementCollection('test') + + dummy_submodel_element = DummySubmodelElement("test") + submodel_collection = model.SubmodelElementCollection("test") submodel_collection.value.add(dummy_submodel_element) checker = AASDataChecker(raise_immediately=True) with self.assertRaises(AttributeError) as cm: - checker.check_submodel_element_collection_equal(submodel_collection, submodel_collection) - self.assertEqual( - 'Submodel Element class not implemented', - str(cm.exception) - ) + checker.check_submodel_element_collection_equal( + submodel_collection, submodel_collection + ) + self.assertEqual("Submodel Element class not implemented", str(cm.exception)) def test_annotated_relationship_element(self): - rel1 = model.AnnotatedRelationshipElement(id_short='test', - first=model.ModelReference(( - model.Key(type_=model.KeyTypes.SUBMODEL, - value='http://example.org/Test_Submodel'), - model.Key( - type_=model.KeyTypes.PROPERTY, - value='ExampleProperty'),), - model.Property), - second=model.ModelReference(( - model.Key(type_=model.KeyTypes.SUBMODEL, - value='http://example.org/Test_Submodel'), - model.Key(type_=model.KeyTypes.PROPERTY, - value='ExampleProperty'),), - model.Property), - ) - rel2 = model.AnnotatedRelationshipElement(id_short='test', - first=model.ModelReference(( - model.Key(type_=model.KeyTypes.SUBMODEL, - value='http://example.org/Test_Submodel'), - model.Key(type_=model.KeyTypes.PROPERTY, - value='ExampleProperty'),), - model.Property), - second=model.ModelReference(( - model.Key(type_=model.KeyTypes.SUBMODEL, - value='http://example.org/Test_Submodel'), - model.Key(type_=model.KeyTypes.PROPERTY, - value='ExampleProperty'),), - model.Property), - annotation={ - model.Property(id_short="ExampleAnnotatedProperty", - value_type=model.datatypes.String, - value='exampleValue', - parent=None) - }) + rel1 = model.AnnotatedRelationshipElement( + id_short="test", + first=model.ModelReference( + ( + model.Key( + type_=model.KeyTypes.SUBMODEL, + value="http://example.org/Test_Submodel", + ), + model.Key(type_=model.KeyTypes.PROPERTY, value="ExampleProperty"), + ), + model.Property, + ), + second=model.ModelReference( + ( + model.Key( + type_=model.KeyTypes.SUBMODEL, + value="http://example.org/Test_Submodel", + ), + model.Key(type_=model.KeyTypes.PROPERTY, value="ExampleProperty"), + ), + model.Property, + ), + ) + rel2 = model.AnnotatedRelationshipElement( + id_short="test", + first=model.ModelReference( + ( + model.Key( + type_=model.KeyTypes.SUBMODEL, + value="http://example.org/Test_Submodel", + ), + model.Key(type_=model.KeyTypes.PROPERTY, value="ExampleProperty"), + ), + model.Property, + ), + second=model.ModelReference( + ( + model.Key( + type_=model.KeyTypes.SUBMODEL, + value="http://example.org/Test_Submodel", + ), + model.Key(type_=model.KeyTypes.PROPERTY, value="ExampleProperty"), + ), + model.Property, + ), + annotation={ + model.Property( + id_short="ExampleAnnotatedProperty", + value_type=model.datatypes.String, + value="exampleValue", + parent=None, + ) + }, + ) checker = AASDataChecker(raise_immediately=False) checker.check_annotated_relationship_element_equal(rel1, rel2) self.assertEqual(2, sum(1 for _ in checker.failed_checks)) checker_iterator = checker.failed_checks - self.assertEqual("FAIL: Attribute annotation of AnnotatedRelationshipElement[test] must contain 1 DataElements " - "(count=0)", - repr(next(checker_iterator))) - self.assertEqual("FAIL: Annotation Property[test.ExampleAnnotatedProperty] must exist ()", - repr(next(checker_iterator))) + self.assertEqual( + "FAIL: Attribute annotation of AnnotatedRelationshipElement[test] must contain 1 DataElements " + "(count=0)", + repr(next(checker_iterator)), + ) + self.assertEqual( + "FAIL: Annotation Property[test.ExampleAnnotatedProperty] must exist ()", + repr(next(checker_iterator)), + ) def test_submodel_checker(self): - submodel = model.Submodel(id_='test') + submodel = model.Submodel(id_="test") property_expected = model.Property( - id_short='Prop1', - value_type=model.datatypes.String, - value='test' + id_short="Prop1", value_type=model.datatypes.String, value="test" + ) + submodel_expected = model.Submodel( + id_="test", submodel_element=(property_expected,) ) - submodel_expected = model.Submodel(id_='test', - submodel_element=(property_expected,) - ) checker = AASDataChecker(raise_immediately=False) checker.check_submodel_equal(submodel, submodel_expected) self.assertEqual(2, sum(1 for _ in checker.failed_checks)) checker_iterator = checker.failed_checks - self.assertEqual("FAIL: Attribute submodel_element of Submodel[test] must contain 1 " - "SubmodelElements (count=0)", - repr(next(checker_iterator))) - self.assertEqual("FAIL: Submodel Element Property[test / Prop1] must exist ()", - repr(next(checker_iterator))) + self.assertEqual( + "FAIL: Attribute submodel_element of Submodel[test] must contain 1 " + "SubmodelElements (count=0)", + repr(next(checker_iterator)), + ) + self.assertEqual( + "FAIL: Submodel Element Property[test / Prop1] must exist ()", + repr(next(checker_iterator)), + ) def test_asset_administration_shell_checker(self): - shell = model.AssetAdministrationShell(asset_information=model.AssetInformation( - global_asset_id='test'), - id_='test') + shell = model.AssetAdministrationShell( + asset_information=model.AssetInformation(global_asset_id="test"), id_="test" + ) shell_expected = model.AssetAdministrationShell( - asset_information=model.AssetInformation( - global_asset_id='test'), - id_='test', - submodel={model.ModelReference((model.Key(type_=model.KeyTypes.SUBMODEL, - value='test'),), - model.Submodel)} - ) + asset_information=model.AssetInformation(global_asset_id="test"), + id_="test", + submodel={ + model.ModelReference( + (model.Key(type_=model.KeyTypes.SUBMODEL, value="test"),), + model.Submodel, + ) + }, + ) checker = AASDataChecker(raise_immediately=False) checker.check_asset_administration_shell_equal(shell, shell_expected) self.assertEqual(2, sum(1 for _ in checker.failed_checks)) checker_iterator = checker.failed_checks - self.assertEqual("FAIL: Attribute submodel of AssetAdministrationShell[test] must contain 1 " - "ModelReferences (count=0)", - repr(next(checker_iterator))) - self.assertEqual("FAIL: Submodel Reference ModelReference(key=(Key(type=SUBMODEL," - " value=test),)) must exist ()", - repr(next(checker_iterator))) + self.assertEqual( + "FAIL: Attribute submodel of AssetAdministrationShell[test] must contain 1 " + "ModelReferences (count=0)", + repr(next(checker_iterator)), + ) + self.assertEqual( + "FAIL: Submodel Reference ModelReference(key=(Key(type=SUBMODEL," + " value=test),)) must exist ()", + repr(next(checker_iterator)), + ) def test_concept_description_checker(self): - cd = model.ConceptDescription(id_='test') - cd_expected = model.ConceptDescription(id_='test', - is_case_of={ - model.ExternalReference((model.Key( - type_=model.KeyTypes.GLOBAL_REFERENCE, - value='test'),))} - ) + cd = model.ConceptDescription(id_="test") + cd_expected = model.ConceptDescription( + id_="test", + is_case_of={ + model.ExternalReference( + (model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, value="test"),) + ) + }, + ) checker = AASDataChecker(raise_immediately=False) checker.check_concept_description_equal(cd, cd_expected) self.assertEqual(2, sum(1 for _ in checker.failed_checks)) checker_iterator = checker.failed_checks - self.assertEqual("FAIL: Attribute is_case_of of ConceptDescription[test] must contain " - "1 References (count=0)", - repr(next(checker_iterator))) - self.assertEqual("FAIL: Concept Description Reference ExternalReference(key=(Key(" - "type=GLOBAL_REFERENCE, value=test),)) must exist ()", - repr(next(checker_iterator))) + self.assertEqual( + "FAIL: Attribute is_case_of of ConceptDescription[test] must contain " + "1 References (count=0)", + repr(next(checker_iterator)), + ) + self.assertEqual( + "FAIL: Concept Description Reference ExternalReference(key=(Key(" + "type=GLOBAL_REFERENCE, value=test),)) must exist ()", + repr(next(checker_iterator)), + ) diff --git a/sdk/test/examples/test_tutorials.py b/sdk/test/examples/test_tutorials.py index 04f201e53..356de44a4 100644 --- a/sdk/test/examples/test_tutorials.py +++ b/sdk/test/examples/test_tutorials.py @@ -9,6 +9,7 @@ Functions to test if a tutorial is executable """ + import os import tempfile import unittest @@ -21,7 +22,11 @@ class TutorialTest(unittest.TestCase): def test_tutorial_create_simple_aas(self): from basyx.aas.examples import tutorial_create_simple_aas - self.assertEqual(tutorial_create_simple_aas.submodel.get_referable('ExampleProperty').value, 'exampleValue') + + self.assertEqual( + tutorial_create_simple_aas.submodel.get_referable("ExampleProperty").value, + "exampleValue", + ) store = model.DictIdentifiableStore({tutorial_create_simple_aas.submodel}) next(iter(tutorial_create_simple_aas.aas.submodel)).resolve(store) @@ -29,21 +34,28 @@ def test_tutorial_storage(self): from basyx.aas.examples import tutorial_storage # The tutorial already includes assert statements for the relevant points. So no further checks are required. - @unittest.skipUnless(COUCHDB_OKAY, "No CouchDB is reachable at {}/{}: {}".format(TEST_CONFIG['couchdb']['url'], - TEST_CONFIG['couchdb']['database'], - COUCHDB_ERROR)) + @unittest.skipUnless( + COUCHDB_OKAY, + "No CouchDB is reachable at {}/{}: {}".format( + TEST_CONFIG["couchdb"]["url"], + TEST_CONFIG["couchdb"]["database"], + COUCHDB_ERROR, + ), + ) def test_tutorial_backend_couchdb(self): from basyx.aas.examples import tutorial_backend_couchdb def test_tutorial_serialization_deserialization_json(self): with temporary_workingdirectory(): from basyx.aas.examples import tutorial_serialization_deserialization + pass # The tutorial already includes assert statements for the relevant points. So no further checks are required. def test_tutorial_aasx(self): with temporary_workingdirectory(): from basyx.aas.examples import tutorial_aasx + pass # The tutorial already includes assert statements for the relevant points. So no further checks are required. diff --git a/sdk/test/model/test_aas.py b/sdk/test/model/test_aas.py index ed0e5bf6e..b9d9b1c25 100644 --- a/sdk/test/model/test_aas.py +++ b/sdk/test/model/test_aas.py @@ -14,35 +14,54 @@ 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://example.org/TestAsset") - model.AssetInformation(model.AssetKind.INSTANCE, specific_asset_id=(model.SpecificAssetId("test", "test"),)) - model.AssetInformation(model.AssetKind.INSTANCE, global_asset_id="https://example.org/TestAsset", - specific_asset_id=(model.SpecificAssetId("test", "test"),)) + 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://example.org/TestAsset" + ) + model.AssetInformation( + model.AssetKind.INSTANCE, + specific_asset_id=(model.SpecificAssetId("test", "test"),), + ) + model.AssetInformation( + model.AssetKind.INSTANCE, + global_asset_id="https://example.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://example.org/TestAsset", - specific_asset_id=(model.SpecificAssetId("test", "test"),)) + asset_information = model.AssetInformation( + model.AssetKind.INSTANCE, + global_asset_id="https://example.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)) + 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://example.org/TestAsset", - specific_asset_id=(model.SpecificAssetId("test", "test"),)) + asset_information = model.AssetInformation( + model.AssetKind.INSTANCE, + global_asset_id="https://example.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)) + 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://example.org/TestAsset") + asset_information = model.AssetInformation( + model.AssetKind.INSTANCE, global_asset_id="https://example.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) @@ -51,12 +70,16 @@ def test_aasd_131_specific_asset_id_add(self) -> None: 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"),)) + 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)) + 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,) @@ -66,21 +89,31 @@ def test_aasd_131_specific_asset_id_set(self) -> None: 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)) + 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)) + 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.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)) + self.assertEqual( + "An AssetInformation has to have a globalAssetId or a specificAssetId (Constraint AASd-131)", + str(cm.exception), + ) diff --git a/sdk/test/model/test_base.py b/sdk/test/model/test_base.py index 826d6ed08..8cdf5a94a 100644 --- a/sdk/test/model/test_base.py +++ b/sdk/test/model/test_base.py @@ -28,22 +28,39 @@ def test_string_representation(self): def test_equality(self): key1 = model.Key(model.KeyTypes.SUBMODEL, "urn:x-test:submodel1") - ident = 'test' + ident = "test" self.assertEqual(key1.__eq__(ident), NotImplemented) def test_from_referable(self): mlp1 = model.MultiLanguageProperty(None) mlp2 = model.MultiLanguageProperty(None) - se_list = model.SubmodelElementList("list", model.MultiLanguageProperty, [mlp1, mlp2]) - self.assertEqual(model.Key(model.KeyTypes.MULTI_LANGUAGE_PROPERTY, "0"), model.Key.from_referable(mlp1)) - self.assertEqual(model.Key(model.KeyTypes.MULTI_LANGUAGE_PROPERTY, "1"), model.Key.from_referable(mlp2)) + se_list = model.SubmodelElementList( + "list", model.MultiLanguageProperty, [mlp1, mlp2] + ) + self.assertEqual( + model.Key(model.KeyTypes.MULTI_LANGUAGE_PROPERTY, "0"), + model.Key.from_referable(mlp1), + ) + self.assertEqual( + model.Key(model.KeyTypes.MULTI_LANGUAGE_PROPERTY, "1"), + model.Key.from_referable(mlp2), + ) del se_list.value[0] - self.assertEqual(model.Key(model.KeyTypes.MULTI_LANGUAGE_PROPERTY, "0"), model.Key.from_referable(mlp2)) + self.assertEqual( + model.Key(model.KeyTypes.MULTI_LANGUAGE_PROPERTY, "0"), + model.Key.from_referable(mlp2), + ) with self.assertRaises(ValueError) as cm: model.Key.from_referable(mlp1) - self.assertEqual("Can't create Key value for MultiLanguageProperty without an id_short!", str(cm.exception)) + self.assertEqual( + "Can't create Key value for MultiLanguageProperty without an id_short!", + str(cm.exception), + ) mlp1.id_short = "mlp1" - self.assertEqual(model.Key(model.KeyTypes.MULTI_LANGUAGE_PROPERTY, "mlp1"), model.Key.from_referable(mlp1)) + self.assertEqual( + model.Key(model.KeyTypes.MULTI_LANGUAGE_PROPERTY, "mlp1"), + model.Key.from_referable(mlp1), + ) class ExampleReferable(model.Referable): @@ -70,8 +87,9 @@ def generate_example_referable_tree() -> model.Referable: :return: example_referable """ - def generate_example_referable_with_namespace(id_short: model.NameType, - child: Optional[model.Referable] = None) -> model.Referable: + def generate_example_referable_with_namespace( + id_short: model.NameType, child: Optional[model.Referable] = None + ) -> model.Referable: """ Generates an example referable with a namespace. @@ -82,15 +100,24 @@ def generate_example_referable_with_namespace(id_short: model.NameType, referable = ExampleReferableWithNamespace() referable.id_short = id_short if child: - namespace_set = model.NamespaceSet(parent=referable, attribute_names=[("id_short", True)], - items=[child]) + namespace_set = model.NamespaceSet( + parent=referable, attribute_names=[("id_short", True)], items=[child] + ) return referable example_grandchild = generate_example_referable_with_namespace("exampleGrandchild") - example_child = generate_example_referable_with_namespace("exampleChild", example_grandchild) - example_referable = generate_example_referable_with_namespace("exampleReferable", example_child) - example_parent = generate_example_referable_with_namespace("exampleParent", example_referable) - example_grandparent = generate_example_referable_with_namespace("exampleGrandparent", example_parent) + example_child = generate_example_referable_with_namespace( + "exampleChild", example_grandchild + ) + example_referable = generate_example_referable_with_namespace( + "exampleReferable", example_child + ) + example_parent = generate_example_referable_with_namespace( + "exampleParent", example_referable + ) + example_grandparent = generate_example_referable_with_namespace( + "exampleGrandparent", example_parent + ) return example_referable @@ -108,23 +135,34 @@ def test_id_short_constraint_aasd_002(self): self.assertEqual("A", test_object.id_short) with self.assertRaises(model.AASConstraintViolation) as cm: test_object.id_short = "Test-" - self.assertEqual("The id_short must not end with a hyphen (Constraint AASd-002)", str(cm.exception)) + self.assertEqual( + "The id_short must not end with a hyphen (Constraint AASd-002)", + str(cm.exception), + ) with self.assertRaises(model.AASConstraintViolation) as cm: test_object.id_short = "98sdsfdAS" - self.assertEqual("The id_short must start with a letter (Constraint AASd-002)", str(cm.exception)) + self.assertEqual( + "The id_short must start with a letter (Constraint AASd-002)", + str(cm.exception), + ) with self.assertRaises(model.AASConstraintViolation) as cm: test_object.id_short = "_sdsfdAS" - self.assertEqual("The id_short must start with a letter (Constraint AASd-002)", str(cm.exception)) + self.assertEqual( + "The id_short must start with a letter (Constraint AASd-002)", + str(cm.exception), + ) with self.assertRaises(model.AASConstraintViolation) as cm: test_object.id_short = "asdlujSAD8348@S" self.assertEqual( "The id_short must contain only letters, digits underscore and hyphen (Constraint AASd-002)", - str(cm.exception)) + str(cm.exception), + ) with self.assertRaises(model.AASConstraintViolation) as cm: test_object.id_short = "abc\n" self.assertEqual( "The id_short must contain only letters, digits underscore and hyphen (Constraint AASd-002)", - str(cm.exception)) + str(cm.exception), + ) def test_representation(self): class DummyClass: @@ -137,8 +175,10 @@ def __init__(self, value: model.Referable): ref.parent = test_object with self.assertRaises(AttributeError) as cm: ref.__repr__() - self.assertEqual('Referable must have an identifiable as root object and only parents that are referable', - str(cm.exception)) + self.assertEqual( + "Referable must have an identifiable as root object and only parents that are referable", + str(cm.exception), + ) def test_get_identifiable_root(self): ref_with_no_parent = ExampleReferableWithNamespace() @@ -151,7 +191,9 @@ def test_get_identifiable_root(self): ref_child.parent = identifiable list1 = model.SubmodelElementList("List1", model.SubmodelElementList) - list2 = model.SubmodelElementList(None, model.Property, value_type_list_element=model.datatypes.Int) + list2 = model.SubmodelElementList( + None, model.Property, value_type_list_element=model.datatypes.Int + ) prop1 = model.Property(None, model.datatypes.Int) list1.parent = ref_child @@ -184,18 +226,29 @@ def test_get_id_short_path(self): - SMC: MySubmodelElementCollectionInSML3 - Property: "MySubTestValue3" """ - MySubmodelElementCollection = model.SubmodelElementCollection("MySubmodelElementCollection") + MySubmodelElementCollection = model.SubmodelElementCollection( + "MySubmodelElementCollection" + ) MySubProperty1 = model.Property("MySubProperty1", model.datatypes.String) MySubProperty2 = model.Property("MySubProperty2", model.datatypes.String) - MySubSubmodelElementCollection = model.SubmodelElementCollection("MySubSubmodelElementCollection") + MySubSubmodelElementCollection = model.SubmodelElementCollection( + "MySubSubmodelElementCollection" + ) MySubSubProperty1 = model.Property("MySubSubProperty1", model.datatypes.String) MySubSubProperty2 = model.Property("MySubSubProperty2", model.datatypes.String) - MySubSubmodelElementList1 = model.SubmodelElementList("MySubSubmodelElementList1", model.Property, - value_type_list_element=model.datatypes.String) + MySubSubmodelElementList1 = model.SubmodelElementList( + "MySubSubmodelElementList1", + model.Property, + value_type_list_element=model.datatypes.String, + ) MySubTestValue1 = model.Property(None, model.datatypes.String) MySubTestValue2 = model.Property(None, model.datatypes.String) - MySubSubmodelElementList2 = model.SubmodelElementList("MySubSubmodelElementList2", model.SubmodelElementList) - MySubSubmodelElementList3 = model.SubmodelElementList(None, model.SubmodelElementCollection) + MySubSubmodelElementList2 = model.SubmodelElementList( + "MySubSubmodelElementList2", model.SubmodelElementList + ) + MySubSubmodelElementList3 = model.SubmodelElementList( + None, model.SubmodelElementCollection + ) MySubmodelElementCollectionInSML3 = model.SubmodelElementCollection(None) MySubTestValue3 = model.Property("MySubTestValue3", model.datatypes.String) @@ -232,10 +285,10 @@ def test_get_id_short_path(self): def test_update_from(self): example_submodel = example_aas.create_example_submodel() - example_relel = example_submodel.get_referable('ExampleRelationshipElement') + example_relel = example_submodel.get_referable("ExampleRelationshipElement") other_submodel = example_aas.create_example_submodel() - other_relel = other_submodel.get_referable('ExampleRelationshipElement') + other_relel = other_submodel.get_referable("ExampleRelationshipElement") other_submodel.category = "NewCat" other_relel.category = "NewRelElCat" @@ -245,10 +298,20 @@ def test_update_from(self): self.assertEqual("NewCat", example_submodel.category) self.assertEqual("NewRelElCat", example_relel.category) # References to Referable objects shall remain stable - self.assertIs(example_relel, example_submodel.get_referable('ExampleRelationshipElement')) - self.assertIs(example_relel, example_submodel.submodel_element.get("id_short", 'ExampleRelationshipElement')) + self.assertIs( + example_relel, example_submodel.get_referable("ExampleRelationshipElement") + ) + self.assertIs( + example_relel, + example_submodel.submodel_element.get( + "id_short", "ExampleRelationshipElement" + ), + ) # Check Namespace & parent consistency - self.assertIs(example_submodel.namespace_element_sets[0], example_submodel.submodel_element) + self.assertIs( + example_submodel.namespace_element_sets[0], + example_submodel.submodel_element, + ) self.assertIs(example_relel.parent, example_submodel) def test_update_commit_qualifier_extension_semantic_id(self): @@ -290,12 +353,16 @@ def test_update_commit_qualifier_extension_semantic_id(self): next(iter(collection.value)) -class ExampleNamespaceReferable(model.UniqueIdShortNamespace, model.UniqueSemanticIdNamespace, model.Identifiable): +class ExampleNamespaceReferable( + model.UniqueIdShortNamespace, model.UniqueSemanticIdNamespace, model.Identifiable +): def __init__(self, values=()): super().__init__() # The 'id' is required by Referable.__repr__() in error messages. self.id = self.__class__.__name__ - self.set1 = model.NamespaceSet(self, [("id_short", False), ("semantic_id", True)]) + self.set1 = model.NamespaceSet( + self, [("id_short", False), ("semantic_id", True)] + ) self.set2 = model.NamespaceSet(self, [("id_short", False)], values) self.set3 = model.NamespaceSet(self, [("name", True)]) self.set4 = model.NamespaceSet(self, [("type", True)]) @@ -312,29 +379,76 @@ class ModelNamespaceTest(unittest.TestCase): _namespace_class_qualifier = ExampleNamespaceQualifier def setUp(self): - self.propSemanticID = model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/Test1'),)) - self.propSemanticID2 = model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/Test2'),)) - self.propSemanticID3 = model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/Test3'),)) - self.prop1 = model.Property("Prop1", model.datatypes.Int, semantic_id=self.propSemanticID) - self.prop2 = model.Property("Prop2", model.datatypes.Int, semantic_id=self.propSemanticID) - self.prop3 = model.Property("Prop2", model.datatypes.Int, semantic_id=self.propSemanticID2) - self.prop4 = model.Property("Prop3", model.datatypes.Int, semantic_id=self.propSemanticID) - self.prop5 = model.Property("Prop3", model.datatypes.Int, semantic_id=self.propSemanticID2) - self.prop6 = model.Property("Prop4", model.datatypes.Int, semantic_id=self.propSemanticID2) - self.prop7 = model.Property("Prop2", model.datatypes.Int, semantic_id=self.propSemanticID3) - self.prop8 = model.Property("ProP2", model.datatypes.Int, semantic_id=self.propSemanticID3) - self.prop1alt = model.Property("Prop1", model.datatypes.Int, semantic_id=self.propSemanticID) + self.propSemanticID = model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/Test1", + ), + ) + ) + self.propSemanticID2 = model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/Test2", + ), + ) + ) + self.propSemanticID3 = model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/Test3", + ), + ) + ) + self.prop1 = model.Property( + "Prop1", model.datatypes.Int, semantic_id=self.propSemanticID + ) + self.prop2 = model.Property( + "Prop2", model.datatypes.Int, semantic_id=self.propSemanticID + ) + self.prop3 = model.Property( + "Prop2", model.datatypes.Int, semantic_id=self.propSemanticID2 + ) + self.prop4 = model.Property( + "Prop3", model.datatypes.Int, semantic_id=self.propSemanticID + ) + self.prop5 = model.Property( + "Prop3", model.datatypes.Int, semantic_id=self.propSemanticID2 + ) + self.prop6 = model.Property( + "Prop4", model.datatypes.Int, semantic_id=self.propSemanticID2 + ) + self.prop7 = model.Property( + "Prop2", model.datatypes.Int, semantic_id=self.propSemanticID3 + ) + self.prop8 = model.Property( + "ProP2", model.datatypes.Int, semantic_id=self.propSemanticID3 + ) + self.prop1alt = model.Property( + "Prop1", model.datatypes.Int, semantic_id=self.propSemanticID + ) self.collection1 = model.SubmodelElementCollection(None) - self.list1 = model.SubmodelElementList("List1", model.SubmodelElementCollection, - semantic_id=self.propSemanticID) - self.qualifier1 = model.Qualifier("type1", model.datatypes.Int, 1, semantic_id=self.propSemanticID) - self.qualifier2 = model.Qualifier("type2", model.datatypes.Int, 1, semantic_id=self.propSemanticID2) - self.qualifier1alt = model.Qualifier("type1", model.datatypes.Int, 1, semantic_id=self.propSemanticID) - self.extension1 = model.Extension("Ext1", model.datatypes.Int, 1, semantic_id=self.propSemanticID) - self.extension2 = model.Extension("Ext2", model.datatypes.Int, 1, semantic_id=self.propSemanticID2) + self.list1 = model.SubmodelElementList( + "List1", model.SubmodelElementCollection, semantic_id=self.propSemanticID + ) + self.qualifier1 = model.Qualifier( + "type1", model.datatypes.Int, 1, semantic_id=self.propSemanticID + ) + self.qualifier2 = model.Qualifier( + "type2", model.datatypes.Int, 1, semantic_id=self.propSemanticID2 + ) + self.qualifier1alt = model.Qualifier( + "type1", model.datatypes.Int, 1, semantic_id=self.propSemanticID + ) + self.extension1 = model.Extension( + "Ext1", model.datatypes.Int, 1, semantic_id=self.propSemanticID + ) + self.extension2 = model.Extension( + "Ext2", model.datatypes.Int, 1, semantic_id=self.propSemanticID2 + ) self.namespace = self._namespace_class() self.namespace3 = self._namespace_class_qualifier() @@ -346,7 +460,9 @@ def test_namespaceset_pop_removes_from_all_backends(self) -> None: self.assertEqual(0, len(self.namespace.set1)) # After pop, adding a new item with the same semantic_id must NOT raise AASConstraintViolation — # it would if the popped item's semantic_id entry were still in the backend - new_prop = model.Property("NewProp", model.datatypes.Int, semantic_id=self.propSemanticID) + new_prop = model.Property( + "NewProp", model.datatypes.Int, semantic_id=self.propSemanticID + ) self.namespace.set1.add(new_prop) self.assertEqual(1, len(self.namespace.set1)) @@ -359,22 +475,26 @@ def test_NamespaceSet(self) -> None: "Object with attribute (name='semantic_id', value='ExternalReference(key=(Key(" "type=GLOBAL_REFERENCE, value=http://example.org/Test1),))') is already present in this set of objects " "(Constraint AASd-000)", - str(cm.exception)) + str(cm.exception), + ) self.namespace.set2.add(self.prop5) self.namespace.set2.add(self.prop6) self.assertEqual(2, len(self.namespace.set2)) with self.assertRaises(model.AASConstraintViolation) as cm: self.namespace.set2.add(self.prop1) - self.assertEqual("Object with attribute (name='id_short', value='Prop1') is already present in another " - "set in the same namespace (Constraint AASd-022)", - str(cm.exception)) + self.assertEqual( + "Object with attribute (name='id_short', value='Prop1') is already present in another " + "set in the same namespace (Constraint AASd-022)", + str(cm.exception), + ) with self.assertRaises(model.AASConstraintViolation) as cm: self.namespace.set2.add(self.prop4) self.assertEqual( "Object with attribute (name='semantic_id', value='" "ExternalReference(key=(Key(type=GLOBAL_REFERENCE, value=http://example.org/Test1),))')" " is already present in another set in the same namespace (Constraint AASd-000)", - str(cm.exception)) + str(cm.exception), + ) self.assertIs(self.prop1, self.namespace.set1.get("id_short", "Prop1")) self.assertIn(self.prop1, self.namespace.set1) @@ -385,26 +505,32 @@ def test_NamespaceSet(self) -> None: with self.assertRaises(model.AASConstraintViolation) as cm: self.namespace.set1.add(self.prop1alt) - self.assertEqual("Object with attribute (name='id_short', value='Prop1') is already present in this set of" - " objects (Constraint AASd-022)", - str(cm.exception)) + self.assertEqual( + "Object with attribute (name='id_short', value='Prop1') is already present in this set of" + " objects (Constraint AASd-022)", + str(cm.exception), + ) self.namespace.set1.add(self.prop3) with self.assertRaises(model.AASConstraintViolation) as cm: self.namespace.set1.add(self.prop7) - self.assertEqual("Object with attribute (name='id_short', value='Prop2') is already present in this set " - "of objects (Constraint AASd-022)", - str(cm.exception)) + self.assertEqual( + "Object with attribute (name='id_short', value='Prop2') is already present in this set " + "of objects (Constraint AASd-022)", + str(cm.exception), + ) with self.assertRaises(model.AASConstraintViolation) as cm: self.namespace.set1.add(self.prop8) - self.assertEqual("Object with attribute (name='id_short', value='ProP2') is already present in this set " - "of objects (Constraint AASd-022)", - str(cm.exception)) + self.assertEqual( + "Object with attribute (name='id_short', value='ProP2') is already present in this set " + "of objects (Constraint AASd-022)", + str(cm.exception), + ) namespace2 = self._namespace_class() with self.assertRaises(ValueError) as cm2: namespace2.set1.add(self.prop1) - self.assertIn('has already a parent', str(cm2.exception)) + self.assertIn("has already a parent", str(cm2.exception)) self.assertEqual(2, len(self.namespace.set1)) self.namespace.set1.remove(self.prop1) @@ -439,26 +565,40 @@ def test_NamespaceSet(self) -> None: self.assertEqual(2, len(self.namespace3.set1)) 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-021)", - str(cm.exception)) + self.assertEqual( + "Object with attribute (name='type', value='type1') is already present in this set " + "of objects (Constraint AASd-021)", + str(cm.exception), + ) def test_namespaceset_hooks(self) -> None: T = TypeVar("T", bound=model.Referable) - nss_types: List[Type[model.NamespaceSet]] = [model.NamespaceSet, model.OrderedNamespaceSet] + nss_types: List[Type[model.NamespaceSet]] = [ + model.NamespaceSet, + model.OrderedNamespaceSet, + ] for nss_type in nss_types: new_item = None old_item = None existing_items = [] class DummyNamespace(model.UniqueIdShortNamespace): - def __init__(self, items: Iterable[T], item_add_hook: Optional[Callable[[T, Iterable[T]], None]] = None, - item_id_set_hook: Optional[Callable[[T], None]] = None, - item_id_del_hook: Optional[Callable[[T], None]] = None): + def __init__( + self, + items: Iterable[T], + item_add_hook: Optional[Callable[[T, Iterable[T]], None]] = None, + item_id_set_hook: Optional[Callable[[T], None]] = None, + item_id_del_hook: Optional[Callable[[T], None]] = None, + ): super().__init__() - self.set1 = nss_type(self, [('id_short', True)], items, item_add_hook=item_add_hook, - item_id_set_hook=item_id_set_hook, - item_id_del_hook=item_id_del_hook) + self.set1 = nss_type( + self, + [("id_short", True)], + items, + item_add_hook=item_add_hook, + item_id_set_hook=item_id_set_hook, + item_id_del_hook=item_id_del_hook, + ) def add_hook(new: T, existing: Iterable[T]) -> None: nonlocal new_item, existing_items @@ -479,8 +619,12 @@ def id_del_hook(old: T) -> None: old.id_short = old.id_short[:-3] cap = model.Capability("test_cap") - dummy_ns = DummyNamespace({cap}, item_add_hook=add_hook, item_id_set_hook=id_set_hook, - item_id_del_hook=id_del_hook) + dummy_ns = DummyNamespace( + {cap}, + item_add_hook=add_hook, + item_id_set_hook=id_set_hook, + item_id_del_hook=id_del_hook, + ) self.assertEqual(cap.id_short, "test_capnew") self.assertIs(new_item, cap) self.assertEqual(len(existing_items), 0) @@ -526,15 +670,23 @@ def add_hook_constraint(_new: T, _existing: Iterable[T]) -> None: self.assertEqual(cap.id_short, "test_cap") self.assertEqual(mlp.id_short, "test_mlp") with self.assertRaises(ValueError): - DummyNamespace([cap, mlp], item_add_hook=add_hook_constraint, item_id_set_hook=id_set_hook, - item_id_del_hook=id_del_hook) + DummyNamespace( + [cap, mlp], + item_add_hook=add_hook_constraint, + item_id_set_hook=id_set_hook, + item_id_del_hook=id_del_hook, + ) self.assertEqual(cap.id_short, "test_cap") self.assertIsNone(cap.parent) self.assertEqual(mlp.id_short, "test_mlp") self.assertIsNone(mlp.parent) - dummy_ns = DummyNamespace((), item_add_hook=add_hook_constraint, item_id_set_hook=id_set_hook, - item_id_del_hook=id_del_hook) + dummy_ns = DummyNamespace( + (), + item_add_hook=add_hook_constraint, + item_id_set_hook=id_set_hook, + item_id_del_hook=id_del_hook, + ) add_hook_counter = 0 dummy_ns.add_referable(cap) self.assertIs(cap.parent, dummy_ns) @@ -546,29 +698,42 @@ def add_hook_constraint(_new: T, _existing: Iterable[T]) -> None: def test_Namespace(self) -> None: with self.assertRaises(model.AASConstraintViolation) as cm: - namespace_test = ExampleNamespaceReferable([self.prop1, self.prop2, self.prop1alt]) - self.assertEqual("Object with attribute (name='id_short', value='Prop1') is already present in this set " - "of objects (Constraint AASd-022)", - str(cm.exception)) + namespace_test = ExampleNamespaceReferable( + [self.prop1, self.prop2, self.prop1alt] + ) + self.assertEqual( + "Object with attribute (name='id_short', value='Prop1') is already present in this set " + "of objects (Constraint AASd-022)", + str(cm.exception), + ) self.assertIsNone(self.prop1.parent) namespace = self._namespace_class([self.prop1, self.prop2]) self.assertIs(self.prop2, namespace.get_referable("Prop2")) with self.assertRaises(KeyError) as cm2: namespace.get_referable("Prop3") - self.assertEqual("'Referable with id_short Prop3 not found in " - f"{self._namespace_class.__name__}[{self.namespace.id}]'", str(cm2.exception)) + self.assertEqual( + "'Referable with id_short Prop3 not found in " + f"{self._namespace_class.__name__}[{self.namespace.id}]'", + str(cm2.exception), + ) namespace.remove_referable("Prop2") with self.assertRaises(KeyError) as cm3: namespace.get_referable("Prop2") - self.assertEqual("'Referable with id_short Prop2 not found in " - f"{self._namespace_class.__name__}[{self.namespace.id}]'", str(cm3.exception)) + self.assertEqual( + "'Referable with id_short Prop2 not found in " + f"{self._namespace_class.__name__}[{self.namespace.id}]'", + str(cm3.exception), + ) with self.assertRaises(KeyError) as cm4: namespace.remove_referable("Prop2") - self.assertEqual("'Referable with id_short Prop2 not found in " - f"{self._namespace_class.__name__}[{self.namespace.id}]'", str(cm4.exception)) + self.assertEqual( + "'Referable with id_short Prop2 not found in " + f"{self._namespace_class.__name__}[{self.namespace.id}]'", + str(cm4.exception), + ) def test_id_short_path_resolution(self) -> None: self.namespace.set2.add(self.list1) @@ -577,19 +742,28 @@ def test_id_short_path_resolution(self) -> None: with self.assertRaises(ValueError) as cm: self.namespace.get_referable(["List1", "a"]) - self.assertEqual(f"Cannot resolve 'a' at SubmodelElementList[{self.namespace.id} / List1], " - "because it is not a numeric index!", str(cm.exception)) + self.assertEqual( + f"Cannot resolve 'a' at SubmodelElementList[{self.namespace.id} / List1], " + "because it is not a numeric index!", + str(cm.exception), + ) with self.assertRaises(KeyError) as cm_2: self.namespace.get_referable(["List1", "0", "Prop2"]) - self.assertEqual("'Referable with id_short Prop2 not found in " - f"SubmodelElementCollection[{self.namespace.id} / List1[0]]'", str(cm_2.exception)) + self.assertEqual( + "'Referable with id_short Prop2 not found in " + f"SubmodelElementCollection[{self.namespace.id} / List1[0]]'", + str(cm_2.exception), + ) with self.assertRaises(TypeError) as cm_3: self.namespace.get_referable(["List1", "0", "Prop1", "Test"]) - self.assertEqual("Cannot resolve id_short or index 'Test' at " - f"Property[{self.namespace.id} / List1[0].Prop1], " - "because it is not a UniqueIdShortNamespace!", str(cm_3.exception)) + self.assertEqual( + "Cannot resolve id_short or index 'Test' at " + f"Property[{self.namespace.id} / List1[0].Prop1], " + "because it is not a UniqueIdShortNamespace!", + str(cm_3.exception), + ) self.namespace.get_referable(["List1", "0", "Prop1"]) @@ -604,9 +778,12 @@ def test_renaming(self) -> None: self.assertEqual(2, len(self.namespace.set2)) self.assertIs(self.prop1, self.namespace.get_referable("Prop3")) with self.assertRaises(KeyError) as cm: - self.namespace.get_referable('Prop1') - self.assertEqual("'Referable with id_short Prop1 not found in " - f"{self._namespace_class.__name__}[{self.namespace.id}]'", str(cm.exception)) + self.namespace.get_referable("Prop1") + self.assertEqual( + "'Referable with id_short Prop1 not found in " + f"{self._namespace_class.__name__}[{self.namespace.id}]'", + str(cm.exception), + ) self.assertIs(self.prop2, self.namespace.get_referable("Prop2")) with self.assertRaises(model.AASConstraintViolation) as cm2: self.prop1.id_short = "Prop2" @@ -633,13 +810,25 @@ def test_Namespaceset_update_from(self) -> None: # Prop2 is getting deleted since it does not exist in namespace2.set1 # Prop3 is getting added, since it does not exist in namespace1.set1 yet namespace1 = self._namespace_class() - prop1 = model.Property("Prop1", model.datatypes.Int, 1, semantic_id=self.propSemanticID) - prop2 = model.Property("Prop2", model.datatypes.Int, 0, semantic_id=self.propSemanticID2) + prop1 = model.Property( + "Prop1", model.datatypes.Int, 1, semantic_id=self.propSemanticID + ) + prop2 = model.Property( + "Prop2", model.datatypes.Int, 0, semantic_id=self.propSemanticID2 + ) namespace1.set2.add(prop1) namespace1.set2.add(prop2) namespace2 = self._namespace_class() - namespace2.set2.add(model.Property("Prop1", model.datatypes.Int, 0, semantic_id=self.propSemanticID)) - namespace2.set2.add(model.Property("Prop3", model.datatypes.Int, 2, semantic_id=self.propSemanticID2)) + namespace2.set2.add( + model.Property( + "Prop1", model.datatypes.Int, 0, semantic_id=self.propSemanticID + ) + ) + namespace2.set2.add( + model.Property( + "Prop3", model.datatypes.Int, 2, semantic_id=self.propSemanticID2 + ) + ) namespace1.set2.update_nss_from(namespace2.set2) # Check that Prop1 got updated correctly self.assertIs(namespace1.get_referable("Prop1"), prop1) @@ -659,33 +848,48 @@ def test_Namespaceset_update_from(self) -> None: def test_qualifiable_id_short_namespace(self) -> None: prop1 = model.Property("Prop1", model.datatypes.Int, 1) qualifier1 = model.Qualifier("Qualifier1", model.datatypes.Int, 2) - submodel_element_collection = model.SubmodelElementCollection("test_SMC", [prop1], - qualifier=[qualifier1]) + submodel_element_collection = model.SubmodelElementCollection( + "test_SMC", [prop1], qualifier=[qualifier1] + ) self.assertIs(submodel_element_collection.get_referable("Prop1"), prop1) - self.assertIs(submodel_element_collection.get_qualifier_by_type("Qualifier1"), qualifier1) + self.assertIs( + submodel_element_collection.get_qualifier_by_type("Qualifier1"), qualifier1 + ) def test_aasd_117(self) -> None: - property = model.Property(None, model.datatypes.Int, semantic_id=self.propSemanticID) + property = model.Property( + None, model.datatypes.Int, semantic_id=self.propSemanticID + ) se_collection = model.SubmodelElementCollection("foo") with self.assertRaises(model.AASConstraintViolation) as cm: se_collection.add_referable(property) - self.assertEqual("Property has attribute id_short=None, which is not allowed within a " - "SubmodelElementCollection! (Constraint AASd-117)", str(cm.exception)) + self.assertEqual( + "Property has attribute id_short=None, which is not allowed within a " + "SubmodelElementCollection! (Constraint AASd-117)", + str(cm.exception), + ) property.id_short = "property" se_collection.add_referable(property) with self.assertRaises(model.AASConstraintViolation) as cm: property.id_short = None - self.assertEqual("id_short of Property[foo.property] cannot be unset, since it is already contained in " - "SubmodelElementCollection[foo] (Constraint AASd-117)", str(cm.exception)) + self.assertEqual( + "id_short of Property[foo.property] cannot be unset, since it is already contained in " + "SubmodelElementCollection[foo] (Constraint AASd-117)", + str(cm.exception), + ) property.id_short = "bar" -class ExampleOrderedNamespace(model.UniqueIdShortNamespace, model.UniqueSemanticIdNamespace, model.Identifiable): +class ExampleOrderedNamespace( + model.UniqueIdShortNamespace, model.UniqueSemanticIdNamespace, model.Identifiable +): def __init__(self, values=()): super().__init__() # The 'id' is required by Referable.__repr__() in error messages. self.id = self.__class__.__name__ - self.set1 = model.OrderedNamespaceSet(self, [("id_short", False), ("semantic_id", True)]) + self.set1 = model.OrderedNamespaceSet( + self, [("id_short", False), ("semantic_id", True)] + ) self.set2 = model.OrderedNamespaceSet(self, [("id_short", False)], values) self.set3 = model.NamespaceSet(self, [("name", True)]) self.set4 = model.NamespaceSet(self, [("type", True)]) @@ -703,18 +907,24 @@ def test_OrderedNamespace(self) -> None: self.assertEqual(2, len(self.namespace.set2)) with self.assertRaises(model.AASConstraintViolation) as cm: self.namespace.set1.insert(0, self.prop1alt) - self.assertEqual('Object with attribute (name=\'id_short\', value=\'Prop1\') is already present in another ' - 'set in the same namespace (Constraint AASd-022)', - str(cm.exception)) + self.assertEqual( + "Object with attribute (name='id_short', value='Prop1') is already present in another " + "set in the same namespace (Constraint AASd-022)", + str(cm.exception), + ) self.assertEqual((self.prop2, self.prop1), tuple(self.namespace.set2)) self.assertEqual(self.prop1, self.namespace.set2[1]) with self.assertRaises(model.AASConstraintViolation) as cm: self.namespace.set2[1] = self.prop2 - self.assertEqual('Object with attribute (name=\'id_short\', value=\'Prop2\') is already present in this ' - 'set of objects (Constraint AASd-022)', - str(cm.exception)) - prop3 = model.Property("Prop3", model.datatypes.Int, semantic_id=self.propSemanticID3) + self.assertEqual( + "Object with attribute (name='id_short', value='Prop2') is already present in this " + "set of objects (Constraint AASd-022)", + str(cm.exception), + ) + prop3 = model.Property( + "Prop3", model.datatypes.Int, semantic_id=self.propSemanticID3 + ) self.assertEqual(2, len(self.namespace.set2)) self.namespace.set2[1] = prop3 self.assertEqual(2, len(self.namespace.set2)) @@ -735,22 +945,34 @@ def test_OrderedNamespace(self) -> None: self.assertEqual(1, len(namespace2.set2)) with self.assertRaises(KeyError) as cm2: namespace2.get_referable("Prop1") - self.assertEqual("'Referable with id_short Prop1 not found in " - f"{self._namespace_class.__name__}[{self.namespace.id}]'", # type: ignore[has-type] - str(cm2.exception)) + self.assertEqual( + "'Referable with id_short Prop1 not found in " + f"{self._namespace_class.__name__}[{self.namespace.id}]'", # type: ignore[has-type] + str(cm2.exception), + ) def test_ordered_namespaceset_int_setitem_preserves_index(self) -> None: # __setitem__ int must place the new item at the exact index of the replaced item. # Items before and after the replaced index must not shift. ns = ExampleOrderedNamespace() - sid1 = model.ExternalReference((model.Key(model.KeyTypes.GLOBAL_REFERENCE, "http://example.org/s1"),)) - sid2 = model.ExternalReference((model.Key(model.KeyTypes.GLOBAL_REFERENCE, "http://example.org/s2"),)) - sid3 = model.ExternalReference((model.Key(model.KeyTypes.GLOBAL_REFERENCE, "http://example.org/s3"),)) - sid4 = model.ExternalReference((model.Key(model.KeyTypes.GLOBAL_REFERENCE, "http://example.org/s4"),)) + sid1 = model.ExternalReference( + (model.Key(model.KeyTypes.GLOBAL_REFERENCE, "http://example.org/s1"),) + ) + sid2 = model.ExternalReference( + (model.Key(model.KeyTypes.GLOBAL_REFERENCE, "http://example.org/s2"),) + ) + sid3 = model.ExternalReference( + (model.Key(model.KeyTypes.GLOBAL_REFERENCE, "http://example.org/s3"),) + ) + sid4 = model.ExternalReference( + (model.Key(model.KeyTypes.GLOBAL_REFERENCE, "http://example.org/s4"),) + ) p0 = model.Property("PA", model.datatypes.Int, semantic_id=sid1) old = model.Property("PB", model.datatypes.Int, semantic_id=sid2) p2 = model.Property("PC", model.datatypes.Int, semantic_id=sid3) - new = model.Property("PB", model.datatypes.Int, semantic_id=sid4) # same id_short as old + new = model.Property( + "PB", model.datatypes.Int, semantic_id=sid4 + ) # same id_short as old ns.set1.add(p0) ns.set1.add(old) ns.set1.add(p2) @@ -769,11 +991,21 @@ def test_ordered_namespaceset_int_setitem_preserves_index(self) -> None: def test_ordered_namespaceset_slice_setitem_preserves_order(self) -> None: # Replace a slice of items; the new items must appear in the correct positions after replacement ns = ExampleOrderedNamespace() - sid1 = model.ExternalReference((model.Key(model.KeyTypes.GLOBAL_REFERENCE, "http://example.org/sid1"),)) - sid2 = model.ExternalReference((model.Key(model.KeyTypes.GLOBAL_REFERENCE, "http://example.org/sid2"),)) - sid3 = model.ExternalReference((model.Key(model.KeyTypes.GLOBAL_REFERENCE, "http://example.org/sid3"),)) - sid4 = model.ExternalReference((model.Key(model.KeyTypes.GLOBAL_REFERENCE, "http://example.org/sid4"),)) - sid5 = model.ExternalReference((model.Key(model.KeyTypes.GLOBAL_REFERENCE, "http://example.org/sid5"),)) + sid1 = model.ExternalReference( + (model.Key(model.KeyTypes.GLOBAL_REFERENCE, "http://example.org/sid1"),) + ) + sid2 = model.ExternalReference( + (model.Key(model.KeyTypes.GLOBAL_REFERENCE, "http://example.org/sid2"),) + ) + sid3 = model.ExternalReference( + (model.Key(model.KeyTypes.GLOBAL_REFERENCE, "http://example.org/sid3"),) + ) + sid4 = model.ExternalReference( + (model.Key(model.KeyTypes.GLOBAL_REFERENCE, "http://example.org/sid4"),) + ) + sid5 = model.ExternalReference( + (model.Key(model.KeyTypes.GLOBAL_REFERENCE, "http://example.org/sid5"),) + ) p1 = model.Property("PA", model.datatypes.Int, semantic_id=sid1) p2 = model.Property("PB", model.datatypes.Int, semantic_id=sid2) p3 = model.Property("PC", model.datatypes.Int, semantic_id=sid3) @@ -806,17 +1038,27 @@ def test_constraints(self): keys = (model.Key(model.KeyTypes.PROPERTY, "urn:x-test:x"),) with self.assertRaises(model.AASConstraintViolation) as cm: model.ExternalReference(keys) - self.assertEqual("The type of the first key of an ExternalReference must be a GenericGloballyIdentifiable: " - f"{keys[0]!r} (Constraint AASd-122)", str(cm.exception)) - model.ExternalReference((model.Key(model.KeyTypes.GLOBAL_REFERENCE, "urn:x-test:x"),)) + self.assertEqual( + "The type of the first key of an ExternalReference must be a GenericGloballyIdentifiable: " + f"{keys[0]!r} (Constraint AASd-122)", + str(cm.exception), + ) + model.ExternalReference( + (model.Key(model.KeyTypes.GLOBAL_REFERENCE, "urn:x-test:x"),) + ) # AASd-124 - keys = (model.Key(model.KeyTypes.GLOBAL_REFERENCE, "urn:x-test:x"), - model.Key(model.KeyTypes.SUBMODEL, "urn:x-test:x"),) + keys = ( + model.Key(model.KeyTypes.GLOBAL_REFERENCE, "urn:x-test:x"), + model.Key(model.KeyTypes.SUBMODEL, "urn:x-test:x"), + ) with self.assertRaises(model.AASConstraintViolation) as cm: model.ExternalReference(keys) - self.assertEqual("The type of the last key of an ExternalReference must be a GenericGloballyIdentifiable or a" - f" GenericFragmentKey: {keys[-1]!r} (Constraint AASd-124)", str(cm.exception)) + self.assertEqual( + "The type of the last key of an ExternalReference must be a GenericGloballyIdentifiable or a" + f" GenericFragmentKey: {keys[-1]!r} (Constraint AASd-124)", + str(cm.exception), + ) keys += (model.Key(model.KeyTypes.FRAGMENT_REFERENCE, "urn:x-test:x"),) model.ExternalReference(keys) @@ -831,171 +1073,250 @@ def test_constraints(self): keys = (model.Key(model.KeyTypes.PROPERTY, "urn:x-test:x"),) with self.assertRaises(model.AASConstraintViolation) as cm: model.ModelReference(keys, model.Property) - self.assertEqual(f"The type of the first key of a ModelReference must be an AasIdentifiable: {keys[0]!r}" - " (Constraint AASd-123)", str(cm.exception)) + self.assertEqual( + f"The type of the first key of a ModelReference must be an AasIdentifiable: {keys[0]!r}" + " (Constraint AASd-123)", + str(cm.exception), + ) keys = (model.Key(model.KeyTypes.SUBMODEL, "urn:x-test:x"),) + keys model.ModelReference(keys, model.Property) # AASd-125 - keys = (model.Key(model.KeyTypes.SUBMODEL, "urn:x-test:x"), - model.Key(model.KeyTypes.ASSET_ADMINISTRATION_SHELL, "urn:x-test:x"), - model.Key(model.KeyTypes.CONCEPT_DESCRIPTION, "urn:x-test:x")) + keys = ( + model.Key(model.KeyTypes.SUBMODEL, "urn:x-test:x"), + model.Key(model.KeyTypes.ASSET_ADMINISTRATION_SHELL, "urn:x-test:x"), + model.Key(model.KeyTypes.CONCEPT_DESCRIPTION, "urn:x-test:x"), + ) with self.assertRaises(model.AASConstraintViolation) as cm: model.ModelReference(keys, model.ConceptDescription) - self.assertEqual("The type of all keys following the first of a ModelReference " - f"must be one of FragmentKeyElements: {keys[1]!r} (Constraint AASd-125)", str(cm.exception)) + self.assertEqual( + "The type of all keys following the first of a ModelReference " + f"must be one of FragmentKeyElements: {keys[1]!r} (Constraint AASd-125)", + str(cm.exception), + ) keys = (keys[0], model.Key(model.KeyTypes.FILE, "urn:x-test:x"), keys[2]) with self.assertRaises(model.AASConstraintViolation) as cm: model.ModelReference(keys, model.ConceptDescription) - self.assertEqual("The type of all keys following the first of a ModelReference " - f"must be one of FragmentKeyElements: {keys[2]!r} (Constraint AASd-125)", str(cm.exception)) - keys = tuple(keys[:2]) + (model.Key(model.KeyTypes.FRAGMENT_REFERENCE, "urn:x-test:x"),) + self.assertEqual( + "The type of all keys following the first of a ModelReference " + f"must be one of FragmentKeyElements: {keys[2]!r} (Constraint AASd-125)", + str(cm.exception), + ) + keys = tuple(keys[:2]) + ( + model.Key(model.KeyTypes.FRAGMENT_REFERENCE, "urn:x-test:x"), + ) model.ModelReference(keys, model.ConceptDescription) # AASd-126 - keys = (model.Key(model.KeyTypes.SUBMODEL, "urn:x-test:x"), - model.Key(model.KeyTypes.FILE, "urn:x-test:x"), - model.Key(model.KeyTypes.FRAGMENT_REFERENCE, "urn:x-test:x"), - model.Key(model.KeyTypes.PROPERTY, "urn:x-test:x")) + keys = ( + model.Key(model.KeyTypes.SUBMODEL, "urn:x-test:x"), + model.Key(model.KeyTypes.FILE, "urn:x-test:x"), + model.Key(model.KeyTypes.FRAGMENT_REFERENCE, "urn:x-test:x"), + model.Key(model.KeyTypes.PROPERTY, "urn:x-test:x"), + ) with self.assertRaises(model.AASConstraintViolation) as cm: model.ModelReference(keys, model.Property) - self.assertEqual(f"Key {keys[2]!r} is a GenericFragmentKey, but the last key of the chain is not: {keys[-1]!r}" - " (Constraint AASd-126)", str(cm.exception)) + self.assertEqual( + f"Key {keys[2]!r} is a GenericFragmentKey, but the last key of the chain is not: {keys[-1]!r}" + " (Constraint AASd-126)", + str(cm.exception), + ) keys = tuple(keys[:3]) model.ModelReference(keys, model.File) # AASd-127 - keys = (model.Key(model.KeyTypes.SUBMODEL, "urn:x-test:x"), - model.Key(model.KeyTypes.PROPERTY, "urn:x-test:x"), - model.Key(model.KeyTypes.FRAGMENT_REFERENCE, "urn:x-test:x")) + keys = ( + model.Key(model.KeyTypes.SUBMODEL, "urn:x-test:x"), + model.Key(model.KeyTypes.PROPERTY, "urn:x-test:x"), + model.Key(model.KeyTypes.FRAGMENT_REFERENCE, "urn:x-test:x"), + ) with self.assertRaises(model.AASConstraintViolation) as cm: model.ModelReference(keys, model.Property) - self.assertEqual(f"{keys[-1]!r} is not preceded by a key of type File or Blob, but {keys[1]!r}" - f" (Constraint AASd-127)", str(cm.exception)) + self.assertEqual( + f"{keys[-1]!r} is not preceded by a key of type File or Blob, but {keys[1]!r}" + f" (Constraint AASd-127)", + str(cm.exception), + ) keys = (keys[0], model.Key(model.KeyTypes.BLOB, "urn:x-test:x"), keys[2]) model.ModelReference(keys, model.Blob) # AASd-128 - keys = (model.Key(model.KeyTypes.SUBMODEL, "urn:x-test:x"), - model.Key(model.KeyTypes.SUBMODEL_ELEMENT_LIST, "urn:x-test:x")) + keys = ( + model.Key(model.KeyTypes.SUBMODEL, "urn:x-test:x"), + model.Key(model.KeyTypes.SUBMODEL_ELEMENT_LIST, "urn:x-test:x"), + ) for invalid_key_value in ("string", "-5", "5.5", "5,5", "+5"): invalid_key = model.Key(model.KeyTypes.PROPERTY, invalid_key_value) with self.assertRaises(model.AASConstraintViolation) as cm: model.ModelReference(keys + (invalid_key,), model.Property) - self.assertEqual(f"Key {keys[1]!r} references a SubmodelElementList, but the value of the succeeding key " - f"({invalid_key!r}) is not a non-negative integer: {invalid_key.value} " - "(Constraint AASd-128)", - str(cm.exception)) + self.assertEqual( + f"Key {keys[1]!r} references a SubmodelElementList, but the value of the succeeding key " + f"({invalid_key!r}) is not a non-negative integer: {invalid_key.value} " + "(Constraint AASd-128)", + str(cm.exception), + ) keys = keys[:1] + (model.Key(model.KeyTypes.PROPERTY, "5"),) model.ModelReference(keys, model.Property) def test_set_reference(self): - ref = model.ModelReference((model.Key(model.KeyTypes.SUBMODEL, "urn:x-test:x"),), model.Submodel) + ref = model.ModelReference( + (model.Key(model.KeyTypes.SUBMODEL, "urn:x-test:x"),), model.Submodel + ) with self.assertRaises(AttributeError) as cm: ref.type = model.Property - self.assertEqual('Reference is immutable', str(cm.exception)) + self.assertEqual("Reference is immutable", str(cm.exception)) with self.assertRaises(AttributeError) as cm: ref.key = model.Key(model.KeyTypes.PROPERTY, "urn:x-test:x") - self.assertEqual('Reference is immutable', str(cm.exception)) + self.assertEqual("Reference is immutable", str(cm.exception)) with self.assertRaises(AttributeError) as cm: ref.key = () - self.assertEqual('Reference is immutable', str(cm.exception)) + self.assertEqual("Reference is immutable", str(cm.exception)) with self.assertRaises(AttributeError) as cm: ref.referred_semantic_id = model.ExternalReference( - (model.Key(model.KeyTypes.GLOBAL_REFERENCE, "urn:x-test:x"),)) - self.assertEqual('Reference is immutable', str(cm.exception)) + (model.Key(model.KeyTypes.GLOBAL_REFERENCE, "urn:x-test:x"),) + ) + self.assertEqual("Reference is immutable", str(cm.exception)) def test_equality(self): - ref = model.ModelReference((model.Key(model.KeyTypes.SUBMODEL, "urn:x-test:x"),), - model.Submodel) - ident = 'test' + ref = model.ModelReference( + (model.Key(model.KeyTypes.SUBMODEL, "urn:x-test:x"),), model.Submodel + ) + ident = "test" self.assertEqual(ref.__eq__(ident), NotImplemented) - ref_2 = model.ModelReference((model.Key(model.KeyTypes.SUBMODEL, "urn:x-test:x"), - model.Key(model.KeyTypes.PROPERTY, "test")), - model.Submodel) + ref_2 = model.ModelReference( + ( + model.Key(model.KeyTypes.SUBMODEL, "urn:x-test:x"), + model.Key(model.KeyTypes.PROPERTY, "test"), + ), + model.Submodel, + ) self.assertNotEqual(ref, ref_2) - ref_3 = model.ModelReference((model.Key(model.KeyTypes.SUBMODEL, "urn:x-test:x"), - model.Key(model.KeyTypes.PROPERTY, "test")), - model.Submodel) + ref_3 = model.ModelReference( + ( + model.Key(model.KeyTypes.SUBMODEL, "urn:x-test:x"), + model.Key(model.KeyTypes.PROPERTY, "test"), + ), + model.Submodel, + ) self.assertEqual(ref_2, ref_3) - referred_semantic_id = model.ExternalReference((model.Key(model.KeyTypes.GLOBAL_REFERENCE, "urn:x-test:x"),)) - object.__setattr__(ref_2, 'referred_semantic_id', referred_semantic_id) + referred_semantic_id = model.ExternalReference( + (model.Key(model.KeyTypes.GLOBAL_REFERENCE, "urn:x-test:x"),) + ) + object.__setattr__(ref_2, "referred_semantic_id", referred_semantic_id) self.assertNotEqual(ref_2, ref_3) - object.__setattr__(ref_3, 'referred_semantic_id', referred_semantic_id) + object.__setattr__(ref_3, "referred_semantic_id", referred_semantic_id) self.assertEqual(ref_2, ref_3) def test_reference_typing(self) -> None: dummy_submodel = model.Submodel("urn:x-test:x") - class DummyIdentifiableProvider(model.AbstractObjectProvider[model.Identifier, model.Identifiable]): + class DummyIdentifiableProvider( + model.AbstractObjectProvider[model.Identifier, model.Identifiable] + ): def get_item(self, identifier: Identifier) -> Identifiable: return dummy_submodel - x = model.ModelReference((model.Key(model.KeyTypes.SUBMODEL, "urn:x-test:x"),), model.Submodel) + x = model.ModelReference( + (model.Key(model.KeyTypes.SUBMODEL, "urn:x-test:x"),), model.Submodel + ) submodel: model.Submodel = x.resolve(DummyIdentifiableProvider()) self.assertIs(submodel, submodel) def test_resolve(self) -> None: prop = model.Property("prop", model.datatypes.Int) collection = model.SubmodelElementCollection(None, {prop}) - list_ = model.SubmodelElementList("list", model.SubmodelElementCollection, {collection}) + list_ = model.SubmodelElementList( + "list", model.SubmodelElementCollection, {collection} + ) submodel = model.Submodel("urn:x-test:submodel", {list_}) - class DummyIdentifiableProvider(model.AbstractObjectProvider[model.Identifier, model.Identifiable]): + class DummyIdentifiableProvider( + model.AbstractObjectProvider[model.Identifier, model.Identifiable] + ): def get_item(self, identifier: Identifier) -> Identifiable: if identifier == submodel.id: return submodel else: raise KeyError() - ref1 = model.ModelReference((model.Key(model.KeyTypes.SUBMODEL, "urn:x-test:submodel"), - model.Key(model.KeyTypes.SUBMODEL_ELEMENT_LIST, "lst"), - model.Key(model.KeyTypes.SUBMODEL_ELEMENT_COLLECTION, "99"), - model.Key(model.KeyTypes.PROPERTY, "prop")), - model.Property) + ref1 = model.ModelReference( + ( + model.Key(model.KeyTypes.SUBMODEL, "urn:x-test:submodel"), + model.Key(model.KeyTypes.SUBMODEL_ELEMENT_LIST, "lst"), + model.Key(model.KeyTypes.SUBMODEL_ELEMENT_COLLECTION, "99"), + model.Key(model.KeyTypes.PROPERTY, "prop"), + ), + model.Property, + ) with self.assertRaises(KeyError) as cm: ref1.resolve(DummyIdentifiableProvider()) - self.assertEqual("'Referable with id_short lst not found in Submodel[urn:x-test:submodel]'", str(cm.exception)) - - ref2 = model.ModelReference((model.Key(model.KeyTypes.SUBMODEL, "urn:x-test:submodel"), - model.Key(model.KeyTypes.SUBMODEL_ELEMENT_LIST, "list"), - model.Key(model.KeyTypes.SUBMODEL_ELEMENT_COLLECTION, "99"), - model.Key(model.KeyTypes.PROPERTY, "prop")), - model.Property) + self.assertEqual( + "'Referable with id_short lst not found in Submodel[urn:x-test:submodel]'", + str(cm.exception), + ) + + ref2 = model.ModelReference( + ( + model.Key(model.KeyTypes.SUBMODEL, "urn:x-test:submodel"), + model.Key(model.KeyTypes.SUBMODEL_ELEMENT_LIST, "list"), + model.Key(model.KeyTypes.SUBMODEL_ELEMENT_COLLECTION, "99"), + model.Key(model.KeyTypes.PROPERTY, "prop"), + ), + model.Property, + ) with self.assertRaises(KeyError) as cm_2: ref2.resolve(DummyIdentifiableProvider()) - self.assertEqual("'Referable with index 99 not found in SubmodelElementList[urn:x-test:submodel / list]'", - str(cm_2.exception)) - - ref3 = model.ModelReference((model.Key(model.KeyTypes.SUBMODEL, "urn:x-test:submodel"), - model.Key(model.KeyTypes.SUBMODEL_ELEMENT_LIST, "list"), - model.Key(model.KeyTypes.SUBMODEL_ELEMENT_COLLECTION, "0"), - model.Key(model.KeyTypes.PROPERTY, "prop")), - model.Property) + self.assertEqual( + "'Referable with index 99 not found in SubmodelElementList[urn:x-test:submodel / list]'", + str(cm_2.exception), + ) + + ref3 = model.ModelReference( + ( + model.Key(model.KeyTypes.SUBMODEL, "urn:x-test:submodel"), + model.Key(model.KeyTypes.SUBMODEL_ELEMENT_LIST, "list"), + model.Key(model.KeyTypes.SUBMODEL_ELEMENT_COLLECTION, "0"), + model.Key(model.KeyTypes.PROPERTY, "prop"), + ), + model.Property, + ) self.assertIs(prop, ref3.resolve(DummyIdentifiableProvider())) - ref4 = model.ModelReference((model.Key(model.KeyTypes.SUBMODEL, "urn:x-test:submodel"), - model.Key(model.KeyTypes.SUBMODEL_ELEMENT_LIST, "list"), - model.Key(model.KeyTypes.SUBMODEL_ELEMENT_COLLECTION, "0"), - model.Key(model.KeyTypes.PROPERTY, "prop"), - model.Key(model.KeyTypes.PROPERTY, "prop")), - model.Property) + ref4 = model.ModelReference( + ( + model.Key(model.KeyTypes.SUBMODEL, "urn:x-test:submodel"), + model.Key(model.KeyTypes.SUBMODEL_ELEMENT_LIST, "list"), + model.Key(model.KeyTypes.SUBMODEL_ELEMENT_COLLECTION, "0"), + model.Key(model.KeyTypes.PROPERTY, "prop"), + model.Key(model.KeyTypes.PROPERTY, "prop"), + ), + model.Property, + ) with self.assertRaises(TypeError) as cm_3: ref4.resolve(DummyIdentifiableProvider()) - self.assertEqual("Cannot resolve id_short or index 'prop' at Property[urn:x-test:submodel / list[0].prop], " - "because it is not a UniqueIdShortNamespace!", str(cm_3.exception)) + self.assertEqual( + "Cannot resolve id_short or index 'prop' at Property[urn:x-test:submodel / list[0].prop], " + "because it is not a UniqueIdShortNamespace!", + str(cm_3.exception), + ) with self.assertRaises(AttributeError) as cm_4: ref1.key[2].value = "prop1" self.assertEqual("Reference is immutable", str(cm_4.exception)) - ref5 = model.ModelReference((model.Key(model.KeyTypes.SUBMODEL, "urn:x-test:sub"),), model.Property) + ref5 = model.ModelReference( + (model.Key(model.KeyTypes.SUBMODEL, "urn:x-test:sub"),), model.Property + ) # Oh no, yet another typo! with self.assertRaises(KeyError) as cm_5: ref5.resolve(DummyIdentifiableProvider()) - self.assertEqual("'Could not resolve identifier urn:x-test:sub'", str(cm_5.exception)) + self.assertEqual( + "'Could not resolve identifier urn:x-test:sub'", str(cm_5.exception) + ) - ref6 = model.ModelReference((model.Key(model.KeyTypes.SUBMODEL, "urn:x-test:submodel"),), model.Property) + ref6 = model.ModelReference( + (model.Key(model.KeyTypes.SUBMODEL, "urn:x-test:submodel"),), model.Property + ) # Okay, typo is fixed, but the type is not what we expect. However, we should get the submodel via the # exception's value attribute with self.assertRaises(model.UnexpectedTypeError) as cm_6: @@ -1004,34 +1325,56 @@ def get_item(self, identifier: Identifier) -> Identifiable: with self.assertRaises(ValueError) as cm_7: ref7 = model.ModelReference((), model.Submodel) - self.assertEqual('A reference must have at least one key!', str(cm_7.exception)) - - ref8 = model.ModelReference((model.Key(model.KeyTypes.SUBMODEL, "urn:x-test:submodel"), - model.Key(model.KeyTypes.SUBMODEL_ELEMENT_LIST, "list"), - model.Key(model.KeyTypes.SUBMODEL_ELEMENT_COLLECTION, "0"), - model.Key(model.KeyTypes.PROPERTY, "prop_false")), model.Property) + self.assertEqual("A reference must have at least one key!", str(cm_7.exception)) + + ref8 = model.ModelReference( + ( + model.Key(model.KeyTypes.SUBMODEL, "urn:x-test:submodel"), + model.Key(model.KeyTypes.SUBMODEL_ELEMENT_LIST, "list"), + model.Key(model.KeyTypes.SUBMODEL_ELEMENT_COLLECTION, "0"), + model.Key(model.KeyTypes.PROPERTY, "prop_false"), + ), + model.Property, + ) with self.assertRaises(KeyError) as cm_8: ref8.resolve(DummyIdentifiableProvider()) - self.assertEqual("'Referable with id_short prop_false not found in " - "SubmodelElementCollection[urn:x-test:submodel / list[0]]'", str(cm_8.exception)) - - ref9 = model.ModelReference((model.Key(model.KeyTypes.SUBMODEL, "urn:x-test:submodel"), - model.Key(model.KeyTypes.SUBMODEL_ELEMENT_COLLECTION, "list"), - model.Key(model.KeyTypes.SUBMODEL_ELEMENT_COLLECTION, "collection")), - model.SubmodelElementCollection) + self.assertEqual( + "'Referable with id_short prop_false not found in " + "SubmodelElementCollection[urn:x-test:submodel / list[0]]'", + str(cm_8.exception), + ) + + ref9 = model.ModelReference( + ( + model.Key(model.KeyTypes.SUBMODEL, "urn:x-test:submodel"), + model.Key(model.KeyTypes.SUBMODEL_ELEMENT_COLLECTION, "list"), + model.Key(model.KeyTypes.SUBMODEL_ELEMENT_COLLECTION, "collection"), + ), + model.SubmodelElementCollection, + ) with self.assertRaises(ValueError) as cm_9: ref9.resolve(DummyIdentifiableProvider()) - self.assertEqual("Cannot resolve 'collection' at SubmodelElementList[urn:x-test:submodel / list], " - "because it is not a numeric index!", str(cm_9.exception)) + self.assertEqual( + "Cannot resolve 'collection' at SubmodelElementList[urn:x-test:submodel / list], " + "because it is not a numeric index!", + str(cm_9.exception), + ) def test_get_identifier(self) -> None: - ref = model.ModelReference((model.Key(model.KeyTypes.SUBMODEL, "urn:x-test:x"),), model.Submodel) + ref = model.ModelReference( + (model.Key(model.KeyTypes.SUBMODEL, "urn:x-test:x"),), model.Submodel + ) self.assertEqual("urn:x-test:x", ref.get_identifier()) - ref2 = model.ModelReference((model.Key(model.KeyTypes.SUBMODEL, "urn:x-test:x"), - model.Key(model.KeyTypes.PROPERTY, "myProperty"),), model.Submodel) + ref2 = model.ModelReference( + ( + model.Key(model.KeyTypes.SUBMODEL, "urn:x-test:x"), + model.Key(model.KeyTypes.PROPERTY, "myProperty"), + ), + model.Submodel, + ) self.assertEqual("urn:x-test:x", ref2.get_identifier()) def test_from_referable(self) -> None: @@ -1059,8 +1402,10 @@ def test_from_referable(self) -> None: submodel.submodel_element.remove(collection) with self.assertRaises(ValueError) as cm: ref3 = model.ModelReference.from_referable(prop) - self.assertEqual("The given Referable object is not embedded within an Identifiable object", - str(cm.exception).split(":")[0]) + self.assertEqual( + "The given Referable object is not embedded within an Identifiable object", + str(cm.exception).split(":")[0], + ) # Test creating a reference to a custom SubmodelElement class class DummyThing(model.SubmodelElement): @@ -1070,7 +1415,9 @@ def __init__(self, id_short: model.NameType): class DummyIdentifyableNamespace(model.Submodel, model.UniqueIdShortNamespace): def __init__(self, id_: model.Identifier): super().__init__(id_) - self.things: model.NamespaceSet = model.NamespaceSet(self, [("id_short", True)]) + self.things: model.NamespaceSet = model.NamespaceSet( + self, [("id_short", True)] + ) thing = DummyThing("thing") identifable_thing = DummyIdentifyableNamespace("urn:x-test:thing") @@ -1080,24 +1427,29 @@ def __init__(self, id_: model.Identifier): class AdministrativeInformationTest(unittest.TestCase): - def test_setting_version_revision(self) -> None: with self.assertRaises(model.AASConstraintViolation) as cm: - obj = model.AdministrativeInformation(revision='9') - self.assertEqual("A revision requires a version. This means, if there is no version there is no " - "revision neither. Please set version first. (Constraint AASd-005)", str(cm.exception)) + obj = model.AdministrativeInformation(revision="9") + self.assertEqual( + "A revision requires a version. This means, if there is no version there is no " + "revision neither. Please set version first. (Constraint AASd-005)", + str(cm.exception), + ) def test_setting_revision(self) -> None: obj = model.AdministrativeInformation() with self.assertRaises(model.AASConstraintViolation) as cm: - obj.revision = '3' - self.assertEqual("A revision requires a version. This means, if there is no version there is no revision " - "neither. Please set version first. (Constraint AASd-005)", str(cm.exception)) + obj.revision = "3" + self.assertEqual( + "A revision requires a version. This means, if there is no version there is no revision " + "neither. Please set version first. (Constraint AASd-005)", + str(cm.exception), + ) class QualifierTest(unittest.TestCase): def test_set_value(self): - qualifier = model.Qualifier('test', model.datatypes.Int, 2) + qualifier = model.Qualifier("test", model.datatypes.Int, 2) self.assertEqual(qualifier.value, 2) qualifier.value = None self.assertIsNone(qualifier.value) @@ -1105,21 +1457,26 @@ def test_set_value(self): class ExtensionTest(unittest.TestCase): def test_set_value(self): - extension = model.Extension('test', model.datatypes.Int, 2) + extension = model.Extension("test", model.datatypes.Int, 2) self.assertEqual(extension.value, 2) extension.value = None self.assertIsNone(extension.value) - extension2 = model.Extension('test') + extension2 = model.Extension("test") with self.assertRaises(ValueError) as cm: extension2.value = 2 - self.assertEqual("ValueType must be set, if value is not None", str(cm.exception)) + self.assertEqual( + "ValueType must be set, if value is not None", str(cm.exception) + ) class ValueReferencePairTest(unittest.TestCase): def test_set_value(self): pair = model.ValueReferencePair( value="2", - value_id=model.ExternalReference((model.Key(model.KeyTypes.GLOBAL_REFERENCE, 'test'),))) + value_id=model.ExternalReference( + (model.Key(model.KeyTypes.GLOBAL_REFERENCE, "test"),) + ), + ) self.assertEqual(pair.value, "2") pair.value = "3" self.assertEqual(pair.value, "3") @@ -1127,7 +1484,7 @@ def test_set_value(self): class HasSemanticsTest(unittest.TestCase): def test_supplemental_semantic_id_constraint(self) -> None: - extension = model.Extension(name='test') + extension = model.Extension(name="test") key: model.Key = model.Key(model.KeyTypes.GLOBAL_REFERENCE, "global_reference") ref_sem_id: model.Reference = model.ExternalReference((key,)) ref1: model.Reference = model.ExternalReference((key,)) @@ -1135,17 +1492,23 @@ def test_supplemental_semantic_id_constraint(self) -> None: with self.assertRaises(model.AASConstraintViolation) as cm: extension.supplemental_semantic_id.append(ref1) self.assertEqual(cm.exception.constraint_id, 118) - self.assertEqual('A semantic_id must be defined before adding a supplemental_semantic_id! ' - '(Constraint AASd-118)', str(cm.exception)) + self.assertEqual( + "A semantic_id must be defined before adding a supplemental_semantic_id! " + "(Constraint AASd-118)", + str(cm.exception), + ) extension.semantic_id = ref_sem_id extension.supplemental_semantic_id.append(ref1) with self.assertRaises(model.AASConstraintViolation) as cm: extension.semantic_id = None self.assertEqual(cm.exception.constraint_id, 118) - self.assertEqual('semantic_id can not be removed while there is at least one supplemental_semantic_id: ' - '[ExternalReference(key=(Key(type=GLOBAL_REFERENCE, value=global_reference),))] ' - '(Constraint AASd-118)', str(cm.exception)) + self.assertEqual( + "semantic_id can not be removed while there is at least one supplemental_semantic_id: " + "[ExternalReference(key=(Key(type=GLOBAL_REFERENCE, value=global_reference),))] " + "(Constraint AASd-118)", + str(cm.exception), + ) extension.supplemental_semantic_id.clear() extension.semantic_id = None @@ -1194,9 +1557,12 @@ def del_hook(itm: int, list_: List[int]) -> None: self.assertIsNone(new) self.assertEqual(len(existing_items), 0) - c_list: model.ConstrainedList[int] = model.ConstrainedList([1, 2, 3], item_add_hook=add_hook, - item_set_hook=set_hook, - item_del_hook=del_hook) + c_list: model.ConstrainedList[int] = model.ConstrainedList( + [1, 2, 3], + item_add_hook=add_hook, + item_set_hook=set_hook, + item_del_hook=del_hook, + ) check_list: List[int] = [1, 2, 3] self.assertEqual(new, 3) @@ -1272,7 +1638,9 @@ def hook(itm: int, _list: List[int]) -> None: if itm > 2: raise ValueError - c_list: model.ConstrainedList[int] = model.ConstrainedList([], item_add_hook=hook) + c_list: model.ConstrainedList[int] = model.ConstrainedList( + [], item_add_hook=hook + ) with self.assertRaises(ValueError): c_list = model.ConstrainedList([1, 2, 3], item_add_hook=hook) self.assertEqual(c_list, []) @@ -1308,13 +1676,19 @@ def test_language_tag_constraints(self) -> None: with self.assertRaises(ValueError) as cm: model.LangStringSet({"x": "bar"}) - self.assertEqual(f"The language tag must follow the format defined in BCP 47. " - f"Given language tag: x", cm.exception.args[0]) + self.assertEqual( + f"The language tag must follow the format defined in BCP 47. " + f"Given language tag: x", + cm.exception.args[0], + ) with self.assertRaises(ValueError) as cm: model.LangStringSet({"foo-oo1": "bar"}) - self.assertEqual(f"The language tag must follow the format defined in BCP 47. " - f"Given language tag: foo-oo1", cm.exception.args[0]) + self.assertEqual( + f"The language tag must follow the format defined in BCP 47. " + f"Given language tag: foo-oo1", + cm.exception.args[0], + ) lss = model.LangStringSet({"fo-OO": "bar"}) self.assertIn("fo-OO", lss) @@ -1343,23 +1717,29 @@ def test_empty(self) -> None: def test_text_constraints(self) -> None: with self.assertRaises(ValueError) as cm: model.MultiLanguageNameType({"fo": "o" * 129}) - self.assertEqual("The text for the language tag 'fo' is invalid: MultiLanguageNameType has a maximum length of " - "128! (length: 129)", str(cm.exception)) + self.assertEqual( + "The text for the language tag 'fo' is invalid: MultiLanguageNameType has a maximum length of " + "128! (length: 129)", + str(cm.exception), + ) mlnt = model.MultiLanguageNameType({"fo": "o" * 128}) with self.assertRaises(ValueError) as cm: mlnt["fo"] = "" - self.assertEqual("The text for the language tag 'fo' is invalid: MultiLanguageNameType has a minimum length of " - "1! (length: 0)", str(cm.exception)) + self.assertEqual( + "The text for the language tag 'fo' is invalid: MultiLanguageNameType has a minimum length of " + "1! (length: 0)", + str(cm.exception), + ) self.assertEqual(mlnt["fo"], "o" * 128) mlnt["fo"] = "o" self.assertEqual(mlnt["fo"], "o") def test_repr(self) -> None: lss = model.LangStringSet({"fo": "bar"}) - self.assertEqual("LangStringSet(fo=\"bar\")", repr(lss)) + self.assertEqual('LangStringSet(fo="bar")', repr(lss)) self.assertEqual(repr(lss), str(lss)) mltt = model.MultiLanguageTextType({"fo": "bar"}) - self.assertEqual("MultiLanguageTextType(fo=\"bar\")", repr(mltt)) + self.assertEqual('MultiLanguageTextType(fo="bar")', repr(mltt)) self.assertEqual(repr(mltt), str(mltt)) def test_len(self) -> None: diff --git a/sdk/test/model/test_datatypes.py b/sdk/test/model/test_datatypes.py index aaab73aa9..cb741a203 100644 --- a/sdk/test/model/test_datatypes.py +++ b/sdk/test/model/test_datatypes.py @@ -18,47 +18,78 @@ class TestIntTypes(unittest.TestCase): def test_parse_int(self) -> None: self.assertEqual(5, model.datatypes.from_xsd("5", model.datatypes.Integer)) self.assertEqual(6, model.datatypes.from_xsd("6", model.datatypes.Byte)) - self.assertEqual(7, model.datatypes.from_xsd("7", model.datatypes.NonNegativeInteger)) + self.assertEqual( + 7, model.datatypes.from_xsd("7", model.datatypes.NonNegativeInteger) + ) self.assertEqual(8, model.datatypes.from_xsd("8", model.datatypes.Long)) self.assertEqual(9, model.datatypes.from_xsd("9", model.datatypes.Int)) self.assertEqual(10, model.datatypes.from_xsd("10", model.datatypes.Short)) - self.assertEqual(-123456789012345678901234567890, - model.datatypes.from_xsd("-123456789012345678901234567890", model.datatypes.Integer)) - self.assertEqual(2147483647, model.datatypes.from_xsd("2147483647", model.datatypes.Int)) - self.assertEqual(-2147483648, model.datatypes.from_xsd("-2147483648", model.datatypes.Int)) - self.assertEqual(-32768, model.datatypes.from_xsd("-32768", model.datatypes.Short)) + self.assertEqual( + -123456789012345678901234567890, + model.datatypes.from_xsd( + "-123456789012345678901234567890", model.datatypes.Integer + ), + ) + self.assertEqual( + 2147483647, model.datatypes.from_xsd("2147483647", model.datatypes.Int) + ) + self.assertEqual( + -2147483648, model.datatypes.from_xsd("-2147483648", model.datatypes.Int) + ) + self.assertEqual( + -32768, model.datatypes.from_xsd("-32768", model.datatypes.Short) + ) self.assertEqual(-128, model.datatypes.from_xsd("-128", model.datatypes.Byte)) - self.assertEqual(-9223372036854775808, - model.datatypes.from_xsd("-9223372036854775808", model.datatypes.Long)) + self.assertEqual( + -9223372036854775808, + model.datatypes.from_xsd("-9223372036854775808", model.datatypes.Long), + ) def test_serialize_int(self) -> None: self.assertEqual("5", model.datatypes.xsd_repr(model.datatypes.Integer(5))) self.assertEqual("6", model.datatypes.xsd_repr(model.datatypes.Byte(6))) - self.assertEqual("7", model.datatypes.xsd_repr(model.datatypes.NonNegativeInteger(7))) + self.assertEqual( + "7", model.datatypes.xsd_repr(model.datatypes.NonNegativeInteger(7)) + ) self.assertEqual("-128", model.datatypes.xsd_repr(model.datatypes.Byte(-128))) def test_range_error(self) -> None: with self.assertRaises(ValueError) as cm: model.datatypes.NonNegativeInteger(-7) - self.assertEqual("-7 is out of the allowed range for type NonNegativeInteger", str(cm.exception)) + self.assertEqual( + "-7 is out of the allowed range for type NonNegativeInteger", + str(cm.exception), + ) with self.assertRaises(ValueError) as cm: model.datatypes.Byte(128) - self.assertEqual("128 is out of the allowed range for type Byte", str(cm.exception)) + self.assertEqual( + "128 is out of the allowed range for type Byte", str(cm.exception) + ) with self.assertRaises(ValueError) as cm: model.datatypes.UnsignedByte(256) - self.assertEqual("256 is out of the allowed range for type UnsignedByte", str(cm.exception)) + self.assertEqual( + "256 is out of the allowed range for type UnsignedByte", str(cm.exception) + ) with self.assertRaises(ValueError) as cm: model.datatypes.UnsignedByte(1000) - self.assertEqual("1000 is out of the allowed range for type UnsignedByte", str(cm.exception)) + self.assertEqual( + "1000 is out of the allowed range for type UnsignedByte", str(cm.exception) + ) with self.assertRaises(ValueError) as cm: model.datatypes.PositiveInteger(0) - self.assertEqual("0 is out of the allowed range for type PositiveInteger", str(cm.exception)) + self.assertEqual( + "0 is out of the allowed range for type PositiveInteger", str(cm.exception) + ) with self.assertRaises(ValueError) as cm: model.datatypes.Int(2147483648) - self.assertEqual("2147483648 is out of the allowed range for type Int", str(cm.exception)) + self.assertEqual( + "2147483648 is out of the allowed range for type Int", str(cm.exception) + ) with self.assertRaises(ValueError) as cm: model.datatypes.Long(2**63) - self.assertEqual(str(2**63)+" is out of the allowed range for type Long", str(cm.exception)) + self.assertEqual( + str(2**63) + " is out of the allowed range for type Long", str(cm.exception) + ) def test_trivial_cast(self) -> None: val = model.datatypes.trivial_cast(5, model.datatypes.UnsignedByte) @@ -71,13 +102,17 @@ def test_trivial_cast(self) -> None: with self.assertRaises(ValueError) as cm: model.datatypes.trivial_cast(-7, model.datatypes.PositiveInteger) - self.assertEqual("-7 is out of the allowed range for type PositiveInteger", str(cm.exception)) + self.assertEqual( + "-7 is out of the allowed range for type PositiveInteger", str(cm.exception) + ) with self.assertRaises(TypeError) as cm_2: model.datatypes.trivial_cast(6.7, model.datatypes.Integer) self.assertEqual("6.7 cannot be trivially casted into int", str(cm_2.exception)) with self.assertRaises(TypeError) as cm_2: model.datatypes.trivial_cast("17", model.datatypes.Int) - self.assertEqual("'17' cannot be trivially casted into Int", str(cm_2.exception)) + self.assertEqual( + "'17' cannot be trivially casted into Int", str(cm_2.exception) + ) class TestStringTypes(unittest.TestCase): @@ -85,61 +120,116 @@ def test_normalized_string(self) -> None: self.assertEqual("abc", model.datatypes.NormalizedString("abc")) with self.assertRaises(ValueError) as cm: model.datatypes.NormalizedString("ab\nc") - self.assertEqual("\\r, \\n and \\t are not allowed in NormalizedStrings", str(cm.exception)) + self.assertEqual( + "\\r, \\n and \\t are not allowed in NormalizedStrings", str(cm.exception) + ) with self.assertRaises(ValueError) as cm: model.datatypes.NormalizedString("ab\tc") - self.assertEqual("\\r, \\n and \\t are not allowed in NormalizedStrings", str(cm.exception)) - self.assertEqual("abc", model.datatypes.NormalizedString.from_string("a\r\nb\tc")) + self.assertEqual( + "\\r, \\n and \\t are not allowed in NormalizedStrings", str(cm.exception) + ) + self.assertEqual( + "abc", model.datatypes.NormalizedString.from_string("a\r\nb\tc") + ) def test_serialize(self) -> None: self.assertEqual("abc", model.datatypes.from_xsd("abc", model.datatypes.String)) - self.assertEqual("abc", model.datatypes.from_xsd("abc", model.datatypes.NormalizedString)) + self.assertEqual( + "abc", model.datatypes.from_xsd("abc", model.datatypes.NormalizedString) + ) self.assertEqual("abc", model.datatypes.xsd_repr(model.datatypes.String("abc"))) - self.assertEqual("abc", model.datatypes.xsd_repr(model.datatypes.NormalizedString("abc"))) + self.assertEqual( + "abc", model.datatypes.xsd_repr(model.datatypes.NormalizedString("abc")) + ) class TestDateTimeTypes(unittest.TestCase): def test_parse_duration(self) -> None: # Examples from https://www.w3.org/TR/xmlschema-2/#duration-lexical-repr - self.assertEqual(dateutil.relativedelta.relativedelta(years=1, months=2, hours=2), - model.datatypes.from_xsd("P1Y2MT2H", model.datatypes.Duration)) - self.assertEqual(dateutil.relativedelta.relativedelta(months=1347), - model.datatypes.from_xsd("P0Y1347M", model.datatypes.Duration)) - self.assertEqual(dateutil.relativedelta.relativedelta(months=1347), - model.datatypes.from_xsd("P0Y1347M0D", model.datatypes.Duration)) - self.assertEqual(dateutil.relativedelta.relativedelta(months=-1347), - model.datatypes.from_xsd("-P1347M", model.datatypes.Duration)) - self.assertEqual(dateutil.relativedelta.relativedelta(years=1, months=2, days=3, hours=10, minutes=30), - model.datatypes.from_xsd("P1Y2M3DT10H30M", model.datatypes.Duration)) + self.assertEqual( + dateutil.relativedelta.relativedelta(years=1, months=2, hours=2), + model.datatypes.from_xsd("P1Y2MT2H", model.datatypes.Duration), + ) + self.assertEqual( + dateutil.relativedelta.relativedelta(months=1347), + model.datatypes.from_xsd("P0Y1347M", model.datatypes.Duration), + ) + self.assertEqual( + dateutil.relativedelta.relativedelta(months=1347), + model.datatypes.from_xsd("P0Y1347M0D", model.datatypes.Duration), + ) + self.assertEqual( + dateutil.relativedelta.relativedelta(months=-1347), + model.datatypes.from_xsd("-P1347M", model.datatypes.Duration), + ) + self.assertEqual( + dateutil.relativedelta.relativedelta( + years=1, months=2, days=3, hours=10, minutes=30 + ), + model.datatypes.from_xsd("P1Y2M3DT10H30M", model.datatypes.Duration), + ) with self.assertRaises(ValueError) as cm: model.datatypes.from_xsd("P-1347M", model.datatypes.Duration) self.assertEqual("Value is not a valid XSD duration string", str(cm.exception)) def test_serialize_duration(self) -> None: - self.assertEqual("P1Y2MT2H", - model.datatypes.xsd_repr(dateutil.relativedelta.relativedelta(years=1, months=2, hours=2))) - self.assertEqual("P112Y3M", - model.datatypes.xsd_repr(dateutil.relativedelta.relativedelta(months=1347))) - self.assertEqual("-P112Y3M", - model.datatypes.xsd_repr(dateutil.relativedelta.relativedelta(months=-1347))) - self.assertEqual("P1Y2M3DT10H30M", - model.datatypes.xsd_repr(dateutil.relativedelta.relativedelta(years=1, months=2, days=3, - hours=10, minutes=30))) + self.assertEqual( + "P1Y2MT2H", + model.datatypes.xsd_repr( + dateutil.relativedelta.relativedelta(years=1, months=2, hours=2) + ), + ) + self.assertEqual( + "P112Y3M", + model.datatypes.xsd_repr(dateutil.relativedelta.relativedelta(months=1347)), + ) + self.assertEqual( + "-P112Y3M", + model.datatypes.xsd_repr( + dateutil.relativedelta.relativedelta(months=-1347) + ), + ) + self.assertEqual( + "P1Y2M3DT10H30M", + model.datatypes.xsd_repr( + dateutil.relativedelta.relativedelta( + years=1, months=2, days=3, hours=10, minutes=30 + ) + ), + ) zero_val = model.datatypes.xsd_repr(dateutil.relativedelta.relativedelta()) self.assertGreaterEqual(len(zero_val), 3) self.assertEqual("P", zero_val[0]) with self.assertRaises(ValueError) as cm: - model.datatypes.xsd_repr(dateutil.relativedelta.relativedelta(months=-5, days=3)) - self.assertEqual("Relative Durations with mixed signs are not allowed according to XSD.", str(cm.exception)) + model.datatypes.xsd_repr( + dateutil.relativedelta.relativedelta(months=-5, days=3) + ) + self.assertEqual( + "Relative Durations with mixed signs are not allowed according to XSD.", + str(cm.exception), + ) def test_parse_date(self) -> None: - self.assertEqual(datetime.date(2020, 1, 24), model.datatypes.from_xsd("2020-01-24", model.datatypes.Date)) - self.assertEqual(model.datatypes.Date(2020, 1, 24, datetime.timezone.utc), - model.datatypes.from_xsd("2020-01-24Z", model.datatypes.Date)) - self.assertEqual(model.datatypes.Date(2020, 1, 24, datetime.timezone(datetime.timedelta(hours=11, minutes=20))), - model.datatypes.from_xsd("2020-01-24+11:20", model.datatypes.Date)) - self.assertEqual(model.datatypes.Date(2020, 1, 24, datetime.timezone(datetime.timedelta(hours=-8))), - model.datatypes.from_xsd("2020-01-24-08:00", model.datatypes.Date)) + self.assertEqual( + datetime.date(2020, 1, 24), + model.datatypes.from_xsd("2020-01-24", model.datatypes.Date), + ) + self.assertEqual( + model.datatypes.Date(2020, 1, 24, datetime.timezone.utc), + model.datatypes.from_xsd("2020-01-24Z", model.datatypes.Date), + ) + self.assertEqual( + model.datatypes.Date( + 2020, 1, 24, datetime.timezone(datetime.timedelta(hours=11, minutes=20)) + ), + model.datatypes.from_xsd("2020-01-24+11:20", model.datatypes.Date), + ) + self.assertEqual( + model.datatypes.Date( + 2020, 1, 24, datetime.timezone(datetime.timedelta(hours=-8)) + ), + model.datatypes.from_xsd("2020-01-24-08:00", model.datatypes.Date), + ) with self.assertRaises(ValueError) as cm: model.datatypes.from_xsd("2020-01-24+11", model.datatypes.Date) self.assertEqual("Value is not a valid XSD date string", str(cm.exception)) @@ -147,31 +237,64 @@ def test_parse_date(self) -> None: model.datatypes.from_xsd("-2020-01-24", model.datatypes.Date) def test_serialize_date(self) -> None: - self.assertEqual("2020-01-24", model.datatypes.xsd_repr(model.datatypes.Date(2020, 1, 24))) - self.assertEqual("2020-01-24Z", model.datatypes.xsd_repr( - model.datatypes.Date(2020, 1, 24, datetime.timezone.utc))) - self.assertEqual("2020-01-24+11:20", model.datatypes.xsd_repr( - model.datatypes.Date(2020, 1, 24, datetime.timezone(datetime.timedelta(hours=11, minutes=20))))) + self.assertEqual( + "2020-01-24", model.datatypes.xsd_repr(model.datatypes.Date(2020, 1, 24)) + ) + self.assertEqual( + "2020-01-24Z", + model.datatypes.xsd_repr( + model.datatypes.Date(2020, 1, 24, datetime.timezone.utc) + ), + ) + self.assertEqual( + "2020-01-24+11:20", + model.datatypes.xsd_repr( + model.datatypes.Date( + 2020, + 1, + 24, + datetime.timezone(datetime.timedelta(hours=11, minutes=20)), + ) + ), + ) def test_parse_partial_dates(self) -> None: - self.assertEqual(model.datatypes.GYear(2019), - model.datatypes.from_xsd("2019", model.datatypes.GYear)) - self.assertEqual(model.datatypes.GYear(-2001), - model.datatypes.from_xsd("-2001", model.datatypes.GYear)) - self.assertEqual(model.datatypes.GYear(20000), - model.datatypes.from_xsd("20000", model.datatypes.GYear)) - self.assertEqual(model.datatypes.GMonth(7), - model.datatypes.from_xsd("--07", model.datatypes.GMonth)) - self.assertEqual(model.datatypes.GYearMonth(2020, 5), - model.datatypes.from_xsd("2020-05", model.datatypes.GYearMonth)) - self.assertEqual(model.datatypes.GYearMonth(-2001, 10), - model.datatypes.from_xsd("-2001-10", model.datatypes.GYearMonth)) - self.assertEqual(model.datatypes.GYearMonth(20000, 5), - model.datatypes.from_xsd("20000-05", model.datatypes.GYearMonth)) - self.assertEqual(model.datatypes.GMonthDay(12, 6), - model.datatypes.from_xsd("--12-06", model.datatypes.GMonthDay)) - self.assertEqual(model.datatypes.GDay(23), - model.datatypes.from_xsd("---23", model.datatypes.GDay)) + self.assertEqual( + model.datatypes.GYear(2019), + model.datatypes.from_xsd("2019", model.datatypes.GYear), + ) + self.assertEqual( + model.datatypes.GYear(-2001), + model.datatypes.from_xsd("-2001", model.datatypes.GYear), + ) + self.assertEqual( + model.datatypes.GYear(20000), + model.datatypes.from_xsd("20000", model.datatypes.GYear), + ) + self.assertEqual( + model.datatypes.GMonth(7), + model.datatypes.from_xsd("--07", model.datatypes.GMonth), + ) + self.assertEqual( + model.datatypes.GYearMonth(2020, 5), + model.datatypes.from_xsd("2020-05", model.datatypes.GYearMonth), + ) + self.assertEqual( + model.datatypes.GYearMonth(-2001, 10), + model.datatypes.from_xsd("-2001-10", model.datatypes.GYearMonth), + ) + self.assertEqual( + model.datatypes.GYearMonth(20000, 5), + model.datatypes.from_xsd("20000-05", model.datatypes.GYearMonth), + ) + self.assertEqual( + model.datatypes.GMonthDay(12, 6), + model.datatypes.from_xsd("--12-06", model.datatypes.GMonthDay), + ) + self.assertEqual( + model.datatypes.GDay(23), + model.datatypes.from_xsd("---23", model.datatypes.GDay), + ) with self.assertRaises(ValueError) as cm: model.datatypes.from_xsd("--23", model.datatypes.GDay) self.assertEqual("Value is not a valid XSD GDay string", str(cm.exception)) @@ -186,15 +309,23 @@ def test_parse_partial_dates(self) -> None: self.assertEqual("Value is not a valid XSD GMonthDay string", str(cm.exception)) with self.assertRaises(ValueError) as cm: model.datatypes.from_xsd("10-10", model.datatypes.GYearMonth) - self.assertEqual("Value is not a valid XSD GYearMonth string", str(cm.exception)) + self.assertEqual( + "Value is not a valid XSD GYearMonth string", str(cm.exception) + ) def test_partial_dates_negative_year_into_date(self) -> None: # Python's `datetime` library does not support negative years. Converting a G-Class with a negative year into a # `Date` must therefore fail with a clear error message instead of the cryptic "year -2001 is out of range". - for value in (model.datatypes.GYear(-2001), model.datatypes.GYearMonth(-2001, 5)): + for value in ( + model.datatypes.GYear(-2001), + model.datatypes.GYearMonth(-2001, 5), + ): with self.assertRaises(ValueError) as cm: value.into_date() - self.assertEqual("Negative years are not supported by Python's `datetime` library.", str(cm.exception)) + self.assertEqual( + "Negative years are not supported by Python's `datetime` library.", + str(cm.exception), + ) def test_copy_date(self) -> None: date = model.datatypes.Date(2020, 1, 24) @@ -205,77 +336,197 @@ def test_copy_date(self) -> None: def test_serialize_partial_dates(self) -> None: self.assertEqual("2019", model.datatypes.xsd_repr(model.datatypes.GYear(2019))) - self.assertEqual("2019Z", model.datatypes.xsd_repr(model.datatypes.GYear(2019, datetime.timezone.utc))) + self.assertEqual( + "2019Z", + model.datatypes.xsd_repr( + model.datatypes.GYear(2019, datetime.timezone.utc) + ), + ) self.assertEqual("--07", model.datatypes.xsd_repr(model.datatypes.GMonth(7))) - self.assertEqual("2020-05", model.datatypes.xsd_repr(model.datatypes.GYearMonth(2020, 5))) - self.assertEqual("2020-05-05:15", model.datatypes.xsd_repr( - model.datatypes.GYearMonth(2020, 5, datetime.timezone(datetime.timedelta(hours=-5, minutes=-15))))) - self.assertEqual("--12-06", model.datatypes.xsd_repr(model.datatypes.GMonthDay(12, 6))) - self.assertEqual("--12-06+07:00", model.datatypes.xsd_repr( - model.datatypes.GMonthDay(12, 6, datetime.timezone(datetime.timedelta(hours=7))))) + self.assertEqual( + "2020-05", model.datatypes.xsd_repr(model.datatypes.GYearMonth(2020, 5)) + ) + self.assertEqual( + "2020-05-05:15", + model.datatypes.xsd_repr( + model.datatypes.GYearMonth( + 2020, + 5, + datetime.timezone(datetime.timedelta(hours=-5, minutes=-15)), + ) + ), + ) + self.assertEqual( + "--12-06", model.datatypes.xsd_repr(model.datatypes.GMonthDay(12, 6)) + ) + self.assertEqual( + "--12-06+07:00", + model.datatypes.xsd_repr( + model.datatypes.GMonthDay( + 12, 6, datetime.timezone(datetime.timedelta(hours=7)) + ) + ), + ) self.assertEqual("---23", model.datatypes.xsd_repr(model.datatypes.GDay(23))) def test_parse_datetime(self) -> None: - self.assertEqual(datetime.datetime(2020, 1, 24, 15, 25, 17), - model.datatypes.from_xsd("2020-01-24T15:25:17", model.datatypes.DateTime)) - self.assertEqual(datetime.datetime(2020, 1, 24, 15, 25, 17, tzinfo=datetime.timezone.utc), - model.datatypes.from_xsd("2020-01-24T15:25:17Z", model.datatypes.DateTime)) - self.assertEqual(datetime.datetime(2020, 1, 24, 15, 25, 17, - tzinfo=datetime.timezone(datetime.timedelta(hours=1))), - model.datatypes.from_xsd("2020-01-24T15:25:17+01:00", model.datatypes.DateTime)) - self.assertEqual(datetime.datetime(2020, 1, 24, 15, 25, 17, - tzinfo=datetime.timezone(datetime.timedelta(minutes=-20))), - model.datatypes.from_xsd("2020-01-24T15:25:17-00:20", model.datatypes.DateTime)) + self.assertEqual( + datetime.datetime(2020, 1, 24, 15, 25, 17), + model.datatypes.from_xsd("2020-01-24T15:25:17", model.datatypes.DateTime), + ) + self.assertEqual( + datetime.datetime(2020, 1, 24, 15, 25, 17, tzinfo=datetime.timezone.utc), + model.datatypes.from_xsd("2020-01-24T15:25:17Z", model.datatypes.DateTime), + ) + self.assertEqual( + datetime.datetime( + 2020, + 1, + 24, + 15, + 25, + 17, + tzinfo=datetime.timezone(datetime.timedelta(hours=1)), + ), + model.datatypes.from_xsd( + "2020-01-24T15:25:17+01:00", model.datatypes.DateTime + ), + ) + self.assertEqual( + datetime.datetime( + 2020, + 1, + 24, + 15, + 25, + 17, + tzinfo=datetime.timezone(datetime.timedelta(minutes=-20)), + ), + model.datatypes.from_xsd( + "2020-01-24T15:25:17-00:20", model.datatypes.DateTime + ), + ) with self.assertRaises(ValueError) as cm: - model.datatypes.from_xsd("--2020-01-24T15:25:17-00:20", model.datatypes.DateTime) - self.assertEqual("--2020-01-24T15:25:17-00:20 is not a valid XSD datetime string", str(cm.exception)) + model.datatypes.from_xsd( + "--2020-01-24T15:25:17-00:20", model.datatypes.DateTime + ) + self.assertEqual( + "--2020-01-24T15:25:17-00:20 is not a valid XSD datetime string", + str(cm.exception), + ) with self.assertRaises(NotImplementedError): - model.datatypes.from_xsd("-2020-01-24T15:25:17+01:00", model.datatypes.DateTime) + model.datatypes.from_xsd( + "-2020-01-24T15:25:17+01:00", model.datatypes.DateTime + ) def test_serialize_datetime(self) -> None: - self.assertEqual("2020-01-24T15:25:17", - model.datatypes.xsd_repr(model.datatypes.DateTime(2020, 1, 24, 15, 25, 17))) - self.assertEqual("2020-01-24T15:25:17+00:00", - model.datatypes.xsd_repr( - model.datatypes.DateTime(2020, 1, 24, 15, 25, 17, tzinfo=datetime.timezone.utc))) - self.assertEqual("2020-01-24T15:25:17+01:00", - model.datatypes.xsd_repr( - model.datatypes.DateTime(2020, 1, 24, 15, 25, 17, - tzinfo=datetime.timezone(datetime.timedelta(hours=1))))) - self.assertEqual("2020-01-24T15:25:17-00:20", - model.datatypes.xsd_repr( - model.datatypes.DateTime(2020, 1, 24, 15, 25, 17, - tzinfo=datetime.timezone(datetime.timedelta(minutes=-20))))) + self.assertEqual( + "2020-01-24T15:25:17", + model.datatypes.xsd_repr(model.datatypes.DateTime(2020, 1, 24, 15, 25, 17)), + ) + self.assertEqual( + "2020-01-24T15:25:17+00:00", + model.datatypes.xsd_repr( + model.datatypes.DateTime( + 2020, 1, 24, 15, 25, 17, tzinfo=datetime.timezone.utc + ) + ), + ) + self.assertEqual( + "2020-01-24T15:25:17+01:00", + model.datatypes.xsd_repr( + model.datatypes.DateTime( + 2020, + 1, + 24, + 15, + 25, + 17, + tzinfo=datetime.timezone(datetime.timedelta(hours=1)), + ) + ), + ) + self.assertEqual( + "2020-01-24T15:25:17-00:20", + model.datatypes.xsd_repr( + model.datatypes.DateTime( + 2020, + 1, + 24, + 15, + 25, + 17, + tzinfo=datetime.timezone(datetime.timedelta(minutes=-20)), + ) + ), + ) def test_parse_time(self) -> None: - self.assertEqual(datetime.time(15, 25, 17), - model.datatypes.from_xsd("15:25:17", model.datatypes.Time)) - self.assertEqual(datetime.time(15, 25, 17, tzinfo=datetime.timezone.utc), - model.datatypes.from_xsd("15:25:17Z", model.datatypes.Time)) - self.assertEqual(datetime.time(15, 25, 17, 250000, tzinfo=datetime.timezone(datetime.timedelta(hours=1))), - model.datatypes.from_xsd("15:25:17.25+01:00", model.datatypes.Time)) - self.assertEqual(datetime.time(15, 25, 17, tzinfo=datetime.timezone(datetime.timedelta(minutes=-20))), - model.datatypes.from_xsd("15:25:17-00:20", model.datatypes.Time)) + self.assertEqual( + datetime.time(15, 25, 17), + model.datatypes.from_xsd("15:25:17", model.datatypes.Time), + ) + self.assertEqual( + datetime.time(15, 25, 17, tzinfo=datetime.timezone.utc), + model.datatypes.from_xsd("15:25:17Z", model.datatypes.Time), + ) + self.assertEqual( + datetime.time( + 15, + 25, + 17, + 250000, + tzinfo=datetime.timezone(datetime.timedelta(hours=1)), + ), + model.datatypes.from_xsd("15:25:17.25+01:00", model.datatypes.Time), + ) + self.assertEqual( + datetime.time( + 15, 25, 17, tzinfo=datetime.timezone(datetime.timedelta(minutes=-20)) + ), + model.datatypes.from_xsd("15:25:17-00:20", model.datatypes.Time), + ) def test_serialize_time(self) -> None: - self.assertEqual("15:25:17", model.datatypes.xsd_repr(datetime.time(15, 25, 17))) - self.assertEqual("15:25:17+00:00", model.datatypes.xsd_repr( - datetime.time(15, 25, 17, tzinfo=datetime.timezone.utc))) - self.assertEqual("15:25:17.250000+01:00", model.datatypes.xsd_repr( - datetime.time(15, 25, 17, 250000, tzinfo=datetime.timezone(datetime.timedelta(hours=1))))) + self.assertEqual( + "15:25:17", model.datatypes.xsd_repr(datetime.time(15, 25, 17)) + ) + self.assertEqual( + "15:25:17+00:00", + model.datatypes.xsd_repr( + datetime.time(15, 25, 17, tzinfo=datetime.timezone.utc) + ), + ) + self.assertEqual( + "15:25:17.250000+01:00", + model.datatypes.xsd_repr( + datetime.time( + 15, + 25, + 17, + 250000, + tzinfo=datetime.timezone(datetime.timedelta(hours=1)), + ) + ), + ) def test_parse_datetime_midnight_24(self) -> None: res = model.datatypes.from_xsd("2020-01-24T24:00:00", model.datatypes.DateTime) self.assertEqual(datetime.datetime(2020, 1, 25, 0, 0, 0), res) - res_tz = model.datatypes.from_xsd("2020-01-24T24:00:00Z", model.datatypes.DateTime) - self.assertEqual(datetime.datetime(2020, 1, 25, 0, 0, 0, tzinfo=datetime.timezone.utc), res_tz) + res_tz = model.datatypes.from_xsd( + "2020-01-24T24:00:00Z", model.datatypes.DateTime + ) + self.assertEqual( + datetime.datetime(2020, 1, 25, 0, 0, 0, tzinfo=datetime.timezone.utc), + res_tz, + ) def test_parse_datetime_midnight_24_invalid(self) -> None: with self.assertRaises(ValueError) as cm: model.datatypes.from_xsd("2020-01-24T24:01:00", model.datatypes.DateTime) self.assertEqual( - "2020-01-24T24:01:00 is not a valid xsd:datetime.", - str(cm.exception)) + "2020-01-24T24:01:00 is not a valid xsd:datetime.", str(cm.exception) + ) def test_parse_time_midnight_24(self) -> None: res = model.datatypes.from_xsd("24:00:00", model.datatypes.Time) @@ -284,25 +535,30 @@ def test_parse_time_midnight_24(self) -> None: def test_parse_time_midnight_24_invalid(self) -> None: with self.assertRaises(ValueError) as cm: model.datatypes.from_xsd("24:00:01", model.datatypes.Time) - self.assertEqual( - "24:00:01 is not a valid xsd:time.", - str(cm.exception) - ) + self.assertEqual("24:00:01 is not a valid xsd:time.", str(cm.exception)) def test_trivial_cast(self) -> None: - val = model.datatypes.trivial_cast(datetime.date(2017, 11, 13), model.datatypes.Date) + val = model.datatypes.trivial_cast( + datetime.date(2017, 11, 13), model.datatypes.Date + ) self.assertEqual(model.datatypes.Date(2017, 11, 13), val) self.assertIsInstance(val, model.datatypes.Date) with self.assertRaises(TypeError) as cm: model.datatypes.trivial_cast("2017-25-13", model.datatypes.Date) - self.assertEqual("'2017-25-13' cannot be trivially casted into Date", str(cm.exception)) + self.assertEqual( + "'2017-25-13' cannot be trivially casted into Date", str(cm.exception) + ) class TestBoolType(unittest.TestCase): def test_parse_bool(self) -> None: - self.assertEqual(True, model.datatypes.from_xsd("true", model.datatypes.Boolean)) + self.assertEqual( + True, model.datatypes.from_xsd("true", model.datatypes.Boolean) + ) self.assertEqual(True, model.datatypes.from_xsd("1", model.datatypes.Boolean)) - self.assertEqual(False, model.datatypes.from_xsd("false", model.datatypes.Boolean)) + self.assertEqual( + False, model.datatypes.from_xsd("false", model.datatypes.Boolean) + ) self.assertEqual(False, model.datatypes.from_xsd("0", model.datatypes.Boolean)) with self.assertRaises(ValueError) as cm: model.datatypes.from_xsd("TRUE", model.datatypes.Boolean) @@ -315,15 +571,27 @@ def test_serialize_bool(self) -> None: class TestBinaryTypes(unittest.TestCase): def test_base64(self) -> None: - self.assertEqual(b"abc\0def", model.datatypes.from_xsd("YWJjAGRlZg==", model.datatypes.Base64Binary)) - self.assertEqual("YWJjAGRlZg==", model.datatypes.xsd_repr(model.datatypes.Base64Binary(b"abc\0def"))) + self.assertEqual( + b"abc\0def", + model.datatypes.from_xsd("YWJjAGRlZg==", model.datatypes.Base64Binary), + ) + self.assertEqual( + "YWJjAGRlZg==", + model.datatypes.xsd_repr(model.datatypes.Base64Binary(b"abc\0def")), + ) val = model.datatypes.trivial_cast(b"abc\0def", model.datatypes.Base64Binary) self.assertEqual(model.datatypes.Base64Binary(b"abc\0def"), val) self.assertIsInstance(val, model.datatypes.Base64Binary) def test_hex(self) -> None: - self.assertEqual(b"abc\0def", model.datatypes.from_xsd("61626300646566", model.datatypes.HexBinary)) - self.assertEqual("61626300646566", model.datatypes.xsd_repr(model.datatypes.HexBinary(b"abc\0def"))) + self.assertEqual( + b"abc\0def", + model.datatypes.from_xsd("61626300646566", model.datatypes.HexBinary), + ) + self.assertEqual( + "61626300646566", + model.datatypes.xsd_repr(model.datatypes.HexBinary(b"abc\0def")), + ) val = model.datatypes.trivial_cast(b"abc\0def", model.datatypes.HexBinary) self.assertEqual(model.datatypes.HexBinary(b"abc\0def"), val) self.assertIsInstance(val, model.datatypes.HexBinary) @@ -333,10 +601,18 @@ class TestFloatType(unittest.TestCase): def test_float(self) -> None: self.assertEqual(5.1, model.datatypes.from_xsd("5.1", model.datatypes.Double)) self.assertEqual(-7.0, model.datatypes.from_xsd("-7", model.datatypes.Double)) - self.assertEqual(5300, model.datatypes.from_xsd("5.3E3", model.datatypes.Double)) - self.assertTrue(math.isnan(model.datatypes.from_xsd("NaN", model.datatypes.Double))) # type: ignore - self.assertEqual(float("inf"), model.datatypes.from_xsd("INF", model.datatypes.Double)) - self.assertEqual(float("-inf"), model.datatypes.from_xsd("-INF", model.datatypes.Double)) + self.assertEqual( + 5300, model.datatypes.from_xsd("5.3E3", model.datatypes.Double) + ) + self.assertTrue( + math.isnan(model.datatypes.from_xsd("NaN", model.datatypes.Double)) + ) # type: ignore + self.assertEqual( + float("inf"), model.datatypes.from_xsd("INF", model.datatypes.Double) + ) + self.assertEqual( + float("-inf"), model.datatypes.from_xsd("-INF", model.datatypes.Double) + ) self.assertEqual("5.1", model.datatypes.xsd_repr(5.1)) self.assertEqual("-7.0", model.datatypes.xsd_repr(-7.0)) @@ -348,10 +624,15 @@ def test_float(self) -> None: class TestDecimalType(unittest.TestCase): def test_parse_decimal(self) -> None: - self.assertEqual(model.datatypes.Decimal("0.1"), model.datatypes.from_xsd("0.1", model.datatypes.Decimal)) + self.assertEqual( + model.datatypes.Decimal("0.1"), + model.datatypes.from_xsd("0.1", model.datatypes.Decimal), + ) with self.assertRaises(ValueError) as cm: model.datatypes.from_xsd("foo", model.datatypes.Decimal) self.assertEqual("Cannot convert 'foo' to Decimal!", str(cm.exception)) def test_serialize_decimal(self) -> None: - self.assertEqual("0.1", model.datatypes.xsd_repr(model.datatypes.Decimal("0.1"))) + self.assertEqual( + "0.1", model.datatypes.xsd_repr(model.datatypes.Decimal("0.1")) + ) diff --git a/sdk/test/model/test_provider.py b/sdk/test/model/test_provider.py index 50e19c0da..5ce60bbcc 100644 --- a/sdk/test/model/test_provider.py +++ b/sdk/test/model/test_provider.py @@ -15,16 +15,22 @@ class ProvidersTest(unittest.TestCase): def setUp(self) -> None: self.aas1 = model.AssetAdministrationShell( - model.AssetInformation(global_asset_id="http://example.org/TestAsset1/"), "urn:x-test:aas1") + model.AssetInformation(global_asset_id="http://example.org/TestAsset1/"), + "urn:x-test:aas1", + ) self.aas2 = model.AssetAdministrationShell( - model.AssetInformation(global_asset_id="http://example.org/TestAsset2/"), "urn:x-test:aas2") + model.AssetInformation(global_asset_id="http://example.org/TestAsset2/"), + "urn:x-test:aas2", + ) self.submodel1 = model.Submodel("urn:x-test:submodel1") self.submodel2 = model.Submodel("urn:x-test:submodel2") def test_store_retrieve(self) -> None: for store_class in self._STORE_CLASSES: with self.subTest(store=store_class.__name__): - store: model.AbstractObjectStore[model.Identifier, model.Identifiable] = store_class([self.aas1]) + store: model.AbstractObjectStore[ + model.Identifier, model.Identifiable + ] = store_class([self.aas1]) store.add(self.aas2) store.add(self.aas1) @@ -33,14 +39,21 @@ def test_store_retrieve(self) -> None: self.assertIn(self.aas1, store) self.assertIn("urn:x-test:aas1", store) self.assertNotIn("urn:x-test:missing", store) - property = model.Property('test', model.datatypes.String) + property = model.Property("test", model.datatypes.String) self.assertFalse(property in store) aas3 = model.AssetAdministrationShell( - model.AssetInformation(global_asset_id="http://example.org/TestAsset/"), "urn:x-test:aas1") + model.AssetInformation( + global_asset_id="http://example.org/TestAsset/" + ), + "urn:x-test:aas1", + ) with self.assertRaises(KeyError) as cm: store.add(aas3) - self.assertEqual("'Identifiable object with same id urn:x-test:aas1 is already " - "stored in this store'", str(cm.exception)) + self.assertEqual( + "'Identifiable object with same id urn:x-test:aas1 is already " + "stored in this store'", + str(cm.exception), + ) self.assertEqual(2, len(store)) self.assertIs(self.aas1, store.get_item("urn:x-test:aas1")) self.assertIs(self.aas1, store.get("urn:x-test:aas1")) @@ -56,9 +69,13 @@ def test_store_retrieve(self) -> None: def test_store_update(self) -> None: for store_class in self._STORE_CLASSES: with self.subTest(store=store_class.__name__): - store1: model.AbstractObjectStore[model.Identifier, model.Identifiable] = store_class() + store1: model.AbstractObjectStore[ + model.Identifier, model.Identifiable + ] = store_class() store1.add(self.aas1) - store2: model.AbstractObjectStore[model.Identifier, model.Identifiable] = store_class() + store2: model.AbstractObjectStore[ + model.Identifier, model.Identifiable + ] = store_class() store2.add(self.aas2) store1.update(store2) self.assertIsInstance(store1, store_class) @@ -67,8 +84,12 @@ def test_store_update(self) -> None: def test_store_sync(self) -> None: for store_class in self._STORE_CLASSES: with self.subTest(store=store_class.__name__): - store: model.AbstractObjectStore[model.Identifier, model.Identifiable] = store_class() - self.assertEqual(store.sync([self.aas1, self.aas2], overwrite=False), (2, 0, 0)) + store: model.AbstractObjectStore[ + model.Identifier, model.Identifiable + ] = store_class() + self.assertEqual( + store.sync([self.aas1, self.aas2], overwrite=False), (2, 0, 0) + ) self.assertIn(self.aas1, store) self.assertIn(self.aas2, store) @@ -77,16 +98,22 @@ def test_store_sync(self) -> None: self.assertEqual(store.sync([self.aas1], overwrite=True), (0, 1, 0)) self.assertIn(self.aas1, store) - self.assertEqual(store.sync([self.aas1, self.submodel1], overwrite=True), (1, 1, 0)) + self.assertEqual( + store.sync([self.aas1, self.submodel1], overwrite=True), (1, 1, 0) + ) - self.assertEqual(store.sync([self.aas1, self.submodel2], overwrite=False), (1, 0, 1)) + self.assertEqual( + store.sync([self.aas1, self.submodel2], overwrite=False), (1, 0, 1) + ) self.assertEqual(store.sync([], overwrite=False), (0, 0, 0)) def test_store_remove(self) -> None: for store_class in self._STORE_CLASSES: with self.subTest(store=store_class.__name__): - store: model.AbstractObjectStore[model.Identifier, model.Identifiable] = store_class() + store: model.AbstractObjectStore[ + model.Identifier, model.Identifiable + ] = store_class() store.add(self.aas1) store.remove(self.aas1) self.assertEqual(0, len(store)) @@ -99,15 +126,22 @@ def test_provider_multiplexer(self) -> None: ) aas_identifiable_store.add(self.aas1) aas_identifiable_store.add(self.aas2) - submodel_identifiable_store: model.DictIdentifiableStore[model.Identifiable] = model.DictIdentifiableStore() + submodel_identifiable_store: model.DictIdentifiableStore[model.Identifiable] = ( + model.DictIdentifiableStore() + ) submodel_identifiable_store.add(self.submodel1) submodel_identifiable_store.add(self.submodel2) - multiplexer: model.ObjectProviderMultiplexer[model.Identifier, model.Identifiable] = ( - model.ObjectProviderMultiplexer([aas_identifiable_store, submodel_identifiable_store]) + multiplexer: model.ObjectProviderMultiplexer[ + model.Identifier, model.Identifiable + ] = model.ObjectProviderMultiplexer( + [aas_identifiable_store, submodel_identifiable_store] ) self.assertIs(self.aas1, multiplexer.get_item("urn:x-test:aas1")) self.assertIs(self.submodel1, multiplexer.get_item("urn:x-test:submodel1")) with self.assertRaises(KeyError) as cm: multiplexer.get_item("urn:x-test:submodel3") - self.assertEqual("'Key could not be found in any of the 2 consulted registries.'", str(cm.exception)) + self.assertEqual( + "'Key could not be found in any of the 2 consulted registries.'", + str(cm.exception), + ) diff --git a/sdk/test/model/test_string_constraints.py b/sdk/test/model/test_string_constraints.py index 4fe5c9f8b..52caf6cc4 100644 --- a/sdk/test/model/test_string_constraints.py +++ b/sdk/test/model/test_string_constraints.py @@ -16,11 +16,16 @@ def test_identifier(self) -> None: identifier: model.Identifier = "" with self.assertRaises(ValueError) as cm: _string_constraints.check_identifier(identifier) - self.assertEqual("Identifier has a minimum length of 1! (length: 0)", cm.exception.args[0]) + self.assertEqual( + "Identifier has a minimum length of 1! (length: 0)", cm.exception.args[0] + ) identifier = "a" * 2049 with self.assertRaises(ValueError) as cm: _string_constraints.check_identifier(identifier) - self.assertEqual("Identifier has a maximum length of 2048! (length: 2049)", cm.exception.args[0]) + self.assertEqual( + "Identifier has a maximum length of 2048! (length: 2049)", + cm.exception.args[0], + ) identifier = "a" * 2048 _string_constraints.check_identifier(identifier) @@ -28,16 +33,22 @@ def test_version_type(self) -> None: version: model.VersionType = "" with self.assertRaises(ValueError) as cm: _string_constraints.check_version_type(version) - self.assertEqual("VersionType has a minimum length of 1! (length: 0)", cm.exception.args[0]) + self.assertEqual( + "VersionType has a minimum length of 1! (length: 0)", cm.exception.args[0] + ) version = "1" * 5 with self.assertRaises(ValueError) as cm: _string_constraints.check_version_type(version) - self.assertEqual("VersionType has a maximum length of 4! (length: 5)", cm.exception.args[0]) + self.assertEqual( + "VersionType has a maximum length of 4! (length: 5)", cm.exception.args[0] + ) version = "0" * 4 with self.assertRaises(ValueError) as cm: _string_constraints.check_version_type(version) - self.assertEqual("VersionType must match the pattern '([0-9]|[1-9][0-9]*)'! (value: '0000')", - cm.exception.args[0]) + self.assertEqual( + "VersionType must match the pattern '([0-9]|[1-9][0-9]*)'! (value: '0000')", + cm.exception.args[0], + ) version = "0" _string_constraints.check_version_type(version) @@ -45,18 +56,27 @@ def test_aasd_130(self) -> None: name: model.NameType = "\0" with self.assertRaises(ValueError) as cm: _string_constraints.check_name_type(name) - self.assertEqual(r"Every string must match the pattern '[\t\n\r -\ud7ff\ue000-\ufffd\U00010000-\U0010ffff]*'! " - r"(value: '\x00')", cm.exception.args[0]) + self.assertEqual( + r"Every string must match the pattern '[\t\n\r -\ud7ff\ue000-\ufffd\U00010000-\U0010ffff]*'! " + r"(value: '\x00')", + cm.exception.args[0], + ) name = "\ud800" with self.assertRaises(ValueError) as cm: _string_constraints.check_name_type(name) - self.assertEqual(r"Every string must match the pattern '[\t\n\r -\ud7ff\ue000-\ufffd\U00010000-\U0010ffff]*'! " - r"(value: '\ud800')", cm.exception.args[0]) + self.assertEqual( + r"Every string must match the pattern '[\t\n\r -\ud7ff\ue000-\ufffd\U00010000-\U0010ffff]*'! " + r"(value: '\ud800')", + cm.exception.args[0], + ) name = "\ufffe" with self.assertRaises(ValueError) as cm: _string_constraints.check_name_type(name) - self.assertEqual(r"Every string must match the pattern '[\t\n\r -\ud7ff\ue000-\ufffd\U00010000-\U0010ffff]*'! " - r"(value: '\ufffe')", cm.exception.args[0]) + self.assertEqual( + r"Every string must match the pattern '[\t\n\r -\ud7ff\ue000-\ufffd\U00010000-\U0010ffff]*'! " + r"(value: '\ufffe')", + cm.exception.args[0], + ) name = "this\ris\na\tvalid täst\uffdd\U0010ab12" _string_constraints.check_name_type(name) @@ -70,11 +90,16 @@ def __init__(self, path: model.PathType): def test_path_type_decoration(self) -> None: with self.assertRaises(ValueError) as cm: self.DummyClass("") - self.assertEqual("PathType has a minimum length of 1! (length: 0)", cm.exception.args[0]) + self.assertEqual( + "PathType has a minimum length of 1! (length: 0)", cm.exception.args[0] + ) dc = self.DummyClass("a") with self.assertRaises(ValueError) as cm: dc.some_attr = "a" * 2049 - self.assertEqual("PathType has a maximum length of 2048! (length: 2049)", cm.exception.args[0]) + self.assertEqual( + "PathType has a maximum length of 2048! (length: 2049)", + cm.exception.args[0], + ) self.assertEqual(dc.some_attr, "a") def test_ignore_none_values(self) -> None: @@ -87,15 +112,23 @@ def test_ignore_none_values(self) -> None: def test_attribute_name_conflict(self) -> None: # We don't want to overwrite existing attributes in case of a name conflict with self.assertRaises(AttributeError) as cm: + @_string_constraints.constrain_revision_type("foo") class DummyClass: foo = property() - self.assertEqual("DummyClass already has an attribute named 'foo'", cm.exception.args[0]) + + self.assertEqual( + "DummyClass already has an attribute named 'foo'", cm.exception.args[0] + ) with self.assertRaises(AttributeError) as cm: + @_string_constraints.constrain_label_type("bar") class DummyClass2: @property def bar(self): return "baz" - self.assertEqual("DummyClass2 already has an attribute named 'bar'", cm.exception.args[0]) + + self.assertEqual( + "DummyClass2 already has an attribute named 'bar'", cm.exception.args[0] + ) diff --git a/sdk/test/model/test_submodel.py b/sdk/test/model/test_submodel.py index b5ee0d7dd..79b99e522 100644 --- a/sdk/test/model/test_submodel.py +++ b/sdk/test/model/test_submodel.py @@ -15,68 +15,118 @@ 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://example.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://example.org/TestAsset", - specific_asset_id=(model.SpecificAssetId("test", "test"),)) + 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://example.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://example.org/TestAsset", + specific_asset_id=(model.SpecificAssetId("test", "test"),), + ) def test_aasd_014_init_co_managed(self) -> None: model.Entity("TestEntity", model.EntityType.CO_MANAGED_ENTITY) with self.assertRaises(model.AASConstraintViolation) as cm: - model.Entity("TestEntity", model.EntityType.CO_MANAGED_ENTITY, - global_asset_id="https://example.org/TestAsset") - self.assertEqual("A co-managed entity has to have neither a globalAssetId nor a specificAssetId " - "(Constraint AASd-014)", str(cm.exception)) + model.Entity( + "TestEntity", + model.EntityType.CO_MANAGED_ENTITY, + global_asset_id="https://example.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: - 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)) + 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://example.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)) + model.Entity( + "TestEntity", + model.EntityType.CO_MANAGED_ENTITY, + global_asset_id="https://example.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://example.org/TestAsset", - specific_asset_id=(model.SpecificAssetId("test", "test"),)) + entity = model.Entity( + "TestEntity", + model.EntityType.SELF_MANAGED_ENTITY, + global_asset_id="https://example.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)) + 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://example.org/TestAsset", - specific_asset_id=(model.SpecificAssetId("test", "test"),)) + entity = model.Entity( + "TestEntity", + model.EntityType.SELF_MANAGED_ENTITY, + global_asset_id="https://example.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)) + 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://example.org/TestAsset" - self.assertEqual("A co-managed entity has to have neither a globalAssetId nor a specificAssetId " - "(Constraint AASd-014)", str(cm.exception)) + 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)) + 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://example.org/TestAsset") + entity = model.Entity( + "TestEntity", + model.EntityType.SELF_MANAGED_ENTITY, + global_asset_id="https://example.org/TestAsset", + ) specific_asset_id1 = model.SpecificAssetId("test", "test") specific_asset_id2 = model.SpecificAssetId("test", "test") entity.specific_asset_id.append(specific_asset_id1) @@ -88,20 +138,31 @@ 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)) + 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)) + 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"),)) + 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)) + 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,) @@ -113,30 +174,44 @@ 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)) + 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)) + 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)) + 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.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)) + self.assertEqual( + "A self-managed entity has to have a globalAssetId or a specificAssetId (Constraint AASd-014)", + str(cm.exception), + ) def test_aasd_014_specific_asset_id_del_co_managed(self) -> None: entity = model.Entity("TestEntity", model.EntityType.CO_MANAGED_ENTITY) @@ -145,7 +220,7 @@ def test_aasd_014_specific_asset_id_del_co_managed(self) -> None: class PropertyTest(unittest.TestCase): def test_set_value(self): - property = model.Property('test', model.datatypes.Int, 2) + property = model.Property("test", model.datatypes.Int, 2) self.assertEqual(property.value, 2) property.value = None self.assertIsNone(property.value) @@ -153,7 +228,7 @@ def test_set_value(self): class RangeTest(unittest.TestCase): def test_set_min_max(self): - range = model.Range('test', model.datatypes.Int, 2, 5) + range = model.Range("test", model.datatypes.Int, 2, 5) self.assertEqual(range.min, 2) self.assertEqual(range.max, 5) range.min = None @@ -165,74 +240,141 @@ def test_set_min_max(self): class SubmodelElementListTest(unittest.TestCase): def test_constraints(self): # AASd-107 - mlp = model.MultiLanguageProperty(None, semantic_id=model.ExternalReference( - (model.Key(model.KeyTypes.GLOBAL_REFERENCE, "urn:x-test:invalid"),) - )) + mlp = model.MultiLanguageProperty( + None, + semantic_id=model.ExternalReference( + (model.Key(model.KeyTypes.GLOBAL_REFERENCE, "urn:x-test:invalid"),) + ), + ) with self.assertRaises(model.AASConstraintViolation) as cm: - model.SubmodelElementList("test_list", model.MultiLanguageProperty, {mlp}, - semantic_id_list_element=model.ExternalReference(( - model.Key(model.KeyTypes.GLOBAL_REFERENCE, "urn:x-test:test"),))) - self.assertEqual("If semantic_id_list_element=ExternalReference(key=(Key(type=GLOBAL_REFERENCE, " - "value=urn:x-test:test),)) is specified all first level children must have " - "the same semantic_id, got MultiLanguageProperty with " - "semantic_id=ExternalReference(key=(Key(type=GLOBAL_REFERENCE, value=urn:x-test:invalid),)) " - "(Constraint AASd-107)", str(cm.exception)) - sel = model.SubmodelElementList("test_list", model.MultiLanguageProperty, {mlp}, - semantic_id_list_element=model.ExternalReference(( - model.Key(model.KeyTypes.GLOBAL_REFERENCE, "urn:x-test:invalid"),))) + model.SubmodelElementList( + "test_list", + model.MultiLanguageProperty, + {mlp}, + semantic_id_list_element=model.ExternalReference( + (model.Key(model.KeyTypes.GLOBAL_REFERENCE, "urn:x-test:test"),) + ), + ) + self.assertEqual( + "If semantic_id_list_element=ExternalReference(key=(Key(type=GLOBAL_REFERENCE, " + "value=urn:x-test:test),)) is specified all first level children must have " + "the same semantic_id, got MultiLanguageProperty with " + "semantic_id=ExternalReference(key=(Key(type=GLOBAL_REFERENCE, value=urn:x-test:invalid),)) " + "(Constraint AASd-107)", + str(cm.exception), + ) + sel = model.SubmodelElementList( + "test_list", + model.MultiLanguageProperty, + {mlp}, + semantic_id_list_element=model.ExternalReference( + (model.Key(model.KeyTypes.GLOBAL_REFERENCE, "urn:x-test:invalid"),) + ), + ) sel.value.clear() - model.SubmodelElementList("test_list", model.MultiLanguageProperty, {mlp}, semantic_id_list_element=None) + model.SubmodelElementList( + "test_list", + model.MultiLanguageProperty, + {mlp}, + semantic_id_list_element=None, + ) mlp = model.MultiLanguageProperty(None) - model.SubmodelElementList("test_list", model.MultiLanguageProperty, {mlp}, - semantic_id_list_element=model.ExternalReference(( - model.Key(model.KeyTypes.GLOBAL_REFERENCE, "urn:x-test:invalid"),))) + model.SubmodelElementList( + "test_list", + model.MultiLanguageProperty, + {mlp}, + semantic_id_list_element=model.ExternalReference( + (model.Key(model.KeyTypes.GLOBAL_REFERENCE, "urn:x-test:invalid"),) + ), + ) # AASd-108 are = model.AnnotatedRelationshipElement( None, - model.ExternalReference((model.Key(model.KeyTypes.GLOBAL_REFERENCE, "urn:x-test:test-first"),)), - model.ExternalReference((model.Key(model.KeyTypes.GLOBAL_REFERENCE, "urn:x-test:test-second"),)) + model.ExternalReference( + (model.Key(model.KeyTypes.GLOBAL_REFERENCE, "urn:x-test:test-first"),) + ), + model.ExternalReference( + (model.Key(model.KeyTypes.GLOBAL_REFERENCE, "urn:x-test:test-second"),) + ), ) # This tests checks if subclasses of the required type are rejected in a SubmodelElementList. # Thus, a requirement is that AnnotatedRelationshipElement is a subclass of RelationshipElement: self.assertIsInstance(are, model.RelationshipElement) with self.assertRaises(model.AASConstraintViolation) as cm: model.SubmodelElementList("test_list", model.RelationshipElement, {are}) - self.assertEqual("All first level elements must be of the type specified in " - "type_value_list_element=RelationshipElement, got AnnotatedRelationshipElement " - "(Constraint AASd-108)", str(cm.exception)) - model.SubmodelElementList("test_list", model.AnnotatedRelationshipElement, {are}) + self.assertEqual( + "All first level elements must be of the type specified in " + "type_value_list_element=RelationshipElement, got AnnotatedRelationshipElement " + "(Constraint AASd-108)", + str(cm.exception), + ) + model.SubmodelElementList( + "test_list", model.AnnotatedRelationshipElement, {are} + ) # AASd-109 # Pass an item to the constructor to assert that this constraint is checked before items are added. prop = model.Property(None, model.datatypes.Int, 0) with self.assertRaises(model.AASConstraintViolation) as cm: model.SubmodelElementList("test_list", model.Property, [prop]) - self.assertEqual("type_value_list_element=Property, but value_type_list_element is not set! " - "(Constraint AASd-109)", str(cm.exception)) - model.SubmodelElementList("test_list", model.Property, [prop], value_type_list_element=model.datatypes.Int) + self.assertEqual( + "type_value_list_element=Property, but value_type_list_element is not set! " + "(Constraint AASd-109)", + str(cm.exception), + ) + model.SubmodelElementList( + "test_list", + model.Property, + [prop], + value_type_list_element=model.datatypes.Int, + ) prop = model.Property(None, model.datatypes.String) with self.assertRaises(model.AASConstraintViolation) as cm: - model.SubmodelElementList("test_list", model.Property, {prop}, value_type_list_element=model.datatypes.Int) - self.assertEqual("All first level elements must have the value_type specified by value_type_list_element=Int, " - "got Property with value_type=str (Constraint AASd-109)", str(cm.exception)) - model.SubmodelElementList("test_list", model.Property, {prop}, value_type_list_element=model.datatypes.String) + model.SubmodelElementList( + "test_list", + model.Property, + {prop}, + value_type_list_element=model.datatypes.Int, + ) + self.assertEqual( + "All first level elements must have the value_type specified by value_type_list_element=Int, " + "got Property with value_type=str (Constraint AASd-109)", + str(cm.exception), + ) + model.SubmodelElementList( + "test_list", + model.Property, + {prop}, + value_type_list_element=model.datatypes.String, + ) # AASd-114 - semantic_id1 = model.ExternalReference((model.Key(model.KeyTypes.GLOBAL_REFERENCE, "urn:x-test:test"),)) - semantic_id2 = model.ExternalReference((model.Key(model.KeyTypes.GLOBAL_REFERENCE, "urn:x-test:different"),)) + semantic_id1 = model.ExternalReference( + (model.Key(model.KeyTypes.GLOBAL_REFERENCE, "urn:x-test:test"),) + ) + semantic_id2 = model.ExternalReference( + (model.Key(model.KeyTypes.GLOBAL_REFERENCE, "urn:x-test:different"),) + ) mlp1 = model.MultiLanguageProperty(None, semantic_id=semantic_id1) mlp2 = model.MultiLanguageProperty(None, semantic_id=semantic_id2) with self.assertRaises(model.AASConstraintViolation) as cm: - model.SubmodelElementList("test_list", model.MultiLanguageProperty, [mlp1, mlp2]) - self.assertEqual("Element to be added MultiLanguageProperty has semantic_id " - "ExternalReference(key=(Key(type=GLOBAL_REFERENCE, value=urn:x-test:different),)), " - "while already contained element MultiLanguageProperty[test_list[0]] has semantic_id " - "ExternalReference(key=(Key(type=GLOBAL_REFERENCE, value=urn:x-test:test),)), " - "which aren't equal. (Constraint AASd-114)", str(cm.exception)) + model.SubmodelElementList( + "test_list", model.MultiLanguageProperty, [mlp1, mlp2] + ) + self.assertEqual( + "Element to be added MultiLanguageProperty has semantic_id " + "ExternalReference(key=(Key(type=GLOBAL_REFERENCE, value=urn:x-test:different),)), " + "while already contained element MultiLanguageProperty[test_list[0]] has semantic_id " + "ExternalReference(key=(Key(type=GLOBAL_REFERENCE, value=urn:x-test:test),)), " + "which aren't equal. (Constraint AASd-114)", + str(cm.exception), + ) mlp2.semantic_id = semantic_id1 - model.SubmodelElementList("test_list", model.MultiLanguageProperty, [mlp1, mlp2]) + model.SubmodelElementList( + "test_list", model.MultiLanguageProperty, [mlp1, mlp2] + ) def test_aasd_108_add_set(self): prop = model.Property(None, model.datatypes.Int) @@ -241,27 +383,39 @@ def test_aasd_108_add_set(self): list_ = model.SubmodelElementList("test_list", model.MultiLanguageProperty) with self.assertRaises(model.AASConstraintViolation) as cm: list_.add_referable(prop) - self.assertEqual("All first level elements must be of the type specified in type_value_list_element=" - "MultiLanguageProperty, got Property (Constraint AASd-108)", str(cm.exception)) + self.assertEqual( + "All first level elements must be of the type specified in type_value_list_element=" + "MultiLanguageProperty, got Property (Constraint AASd-108)", + str(cm.exception), + ) list_.add_referable(mlp1) with self.assertRaises(model.AASConstraintViolation) as cm: list_.value.add(prop) - self.assertEqual("All first level elements must be of the type specified in type_value_list_element=" - "MultiLanguageProperty, got Property (Constraint AASd-108)", str(cm.exception)) + self.assertEqual( + "All first level elements must be of the type specified in type_value_list_element=" + "MultiLanguageProperty, got Property (Constraint AASd-108)", + str(cm.exception), + ) list_.value.add(mlp2) with self.assertRaises(model.AASConstraintViolation) as cm: list_.value[0] = prop - self.assertEqual("All first level elements must be of the type specified in type_value_list_element=" - "MultiLanguageProperty, got Property (Constraint AASd-108)", str(cm.exception)) + self.assertEqual( + "All first level elements must be of the type specified in type_value_list_element=" + "MultiLanguageProperty, got Property (Constraint AASd-108)", + str(cm.exception), + ) del list_.value[1] list_.value[0] = mlp2 with self.assertRaises(model.AASConstraintViolation) as cm: list_.value = [mlp1, prop] - self.assertEqual("All first level elements must be of the type specified in type_value_list_element=" - "MultiLanguageProperty, got Property (Constraint AASd-108)", str(cm.exception)) + self.assertEqual( + "All first level elements must be of the type specified in type_value_list_element=" + "MultiLanguageProperty, got Property (Constraint AASd-108)", + str(cm.exception), + ) list_.value = [mlp1, mlp2] def test_immutable_attributes(self): @@ -271,7 +425,9 @@ def test_immutable_attributes(self): with self.assertRaises(AttributeError): list_.order_relevant = False with self.assertRaises(AttributeError): - list_.semantic_id_list_element = model.ExternalReference((model.Key(model.KeyTypes.GLOBAL_REFERENCE, "t"),)) + list_.semantic_id_list_element = model.ExternalReference( + (model.Key(model.KeyTypes.GLOBAL_REFERENCE, "t"),) + ) with self.assertRaises(AttributeError): list_.value_type_list_element = model.datatypes.Int @@ -279,30 +435,51 @@ def test_immutable_attributes(self): class BasicEventElementTest(unittest.TestCase): def test_constraints(self): with self.assertRaises(ValueError) as cm: - model.BasicEventElement("test_basic_event_element", - model.ModelReference((model.Key(model.KeyTypes.ASSET_ADMINISTRATION_SHELL, - "urn:x-test:AssetAdministrationShell"),), - model.AssetAdministrationShell), - model.Direction.INPUT, - model.StateOfEvent.ON, - max_interval=model.datatypes.Duration(minutes=10)) - self.assertEqual("max_interval is not applicable if direction = input!", str(cm.exception)) - bee = model.BasicEventElement("test_basic_event_element", - model.ModelReference((model.Key(model.KeyTypes.ASSET_ADMINISTRATION_SHELL, - "urn:x-test:AssetAdministrationShell"),), - model.AssetAdministrationShell), - model.Direction.OUTPUT, - model.StateOfEvent.ON, - max_interval=model.datatypes.Duration(minutes=10)) + model.BasicEventElement( + "test_basic_event_element", + model.ModelReference( + ( + model.Key( + model.KeyTypes.ASSET_ADMINISTRATION_SHELL, + "urn:x-test:AssetAdministrationShell", + ), + ), + model.AssetAdministrationShell, + ), + model.Direction.INPUT, + model.StateOfEvent.ON, + max_interval=model.datatypes.Duration(minutes=10), + ) + self.assertEqual( + "max_interval is not applicable if direction = input!", str(cm.exception) + ) + bee = model.BasicEventElement( + "test_basic_event_element", + model.ModelReference( + ( + model.Key( + model.KeyTypes.ASSET_ADMINISTRATION_SHELL, + "urn:x-test:AssetAdministrationShell", + ), + ), + model.AssetAdministrationShell, + ), + model.Direction.OUTPUT, + model.StateOfEvent.ON, + max_interval=model.datatypes.Duration(minutes=10), + ) with self.assertRaises(ValueError) as cm: bee.direction = model.Direction.INPUT - self.assertEqual("max_interval is not applicable if direction = input!", str(cm.exception)) + self.assertEqual( + "max_interval is not applicable if direction = input!", str(cm.exception) + ) bee.max_interval = None bee.direction = model.Direction.INPUT - timestamp_tzinfo = model.datatypes.DateTime(2022, 11, 13, 23, 45, 30, 123456, - dateutil.tz.gettz("Europe/Berlin")) + timestamp_tzinfo = model.datatypes.DateTime( + 2022, 11, 13, 23, 45, 30, 123456, dateutil.tz.gettz("Europe/Berlin") + ) with self.assertRaises(ValueError) as cm: bee.last_update = timestamp_tzinfo self.assertEqual("last_update must be specified in UTC!", str(cm.exception)) @@ -312,5 +489,7 @@ def test_constraints(self): bee.last_update = timestamp self.assertEqual("last_update must be specified in UTC!", str(cm.exception)) - timestamp_tzinfo_utc = model.datatypes.DateTime(2022, 11, 13, 23, 45, 30, 123456, dateutil.tz.UTC) + timestamp_tzinfo_utc = model.datatypes.DateTime( + 2022, 11, 13, 23, 45, 30, 123456, dateutil.tz.UTC + ) bee.last_update = timestamp_tzinfo_utc diff --git a/sdk/test/util/test_identification.py b/sdk/test/util/test_identification.py index 36019ede2..d751e2623 100644 --- a/sdk/test/util/test_identification.py +++ b/sdk/test/util/test_identification.py @@ -15,8 +15,10 @@ class IdentifierGeneratorTest(unittest.TestCase): def test_generate_uuid_identifier(self): generator = UUIDGenerator() identification = generator.generate_id() - self.assertRegex(identification, - r"urn:uuid:[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}") + self.assertRegex( + identification, + r"urn:uuid:[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}", + ) ids = set() for i in range(100): identification = generator.generate_id() @@ -29,10 +31,14 @@ def test_generate_iri_identifier(self): # Check expected Errors when Namespaces are not valid with self.assertRaises(ValueError) as cm: generator = NamespaceIRIGenerator("", provider) - self.assertEqual('Namespace must be a valid IRI, ending with #, / or =', str(cm.exception)) + self.assertEqual( + "Namespace must be a valid IRI, ending with #, / or =", str(cm.exception) + ) with self.assertRaises(ValueError) as cm: generator = NamespaceIRIGenerator("http", provider) - self.assertEqual('Namespace must be a valid IRI, ending with #, / or =', str(cm.exception)) + self.assertEqual( + "Namespace must be a valid IRI, ending with #, / or =", str(cm.exception) + ) generator = NamespaceIRIGenerator("http://example.org/AAS/", provider) self.assertEqual("http://example.org/AAS/", generator.namespace) diff --git a/sdk/test/util/test_traversal.py b/sdk/test/util/test_traversal.py index 881d7f7dc..98dcd7d23 100644 --- a/sdk/test/util/test_traversal.py +++ b/sdk/test/util/test_traversal.py @@ -31,15 +31,21 @@ def test_collection_traversal(self): def test_list_traversal(self): child = model.Property(None, model.datatypes.String) - sml = model.SubmodelElementList("sml", type_value_list_element=model.Property, - value_type_list_element=model.datatypes.String, value=[child]) + sml = model.SubmodelElementList( + "sml", + type_value_list_element=model.Property, + value_type_list_element=model.datatypes.String, + value=[child], + ) sm = self._submodel(sml) result = list(walk_submodel(sm)) self.assertCountEqual([child, sml], result) def test_entity_statement_traversal(self): stmt = model.Property("stmt", model.datatypes.String) - entity = model.Entity("entity", model.EntityType.CO_MANAGED_ENTITY, statement=[stmt]) + entity = model.Entity( + "entity", model.EntityType.CO_MANAGED_ENTITY, statement=[stmt] + ) sm = self._submodel(entity) result = list(walk_submodel(sm)) self.assertCountEqual([stmt, entity], result) @@ -54,8 +60,12 @@ def test_operation_variables_traversal(self): in_var = model.Property("in_var", model.datatypes.String) out_var = model.Property("out_var", model.datatypes.String) inout_var = model.Property("inout_var", model.datatypes.String) - op = model.Operation("op", input_variable=[in_var], output_variable=[out_var], - in_output_variable=[inout_var]) + op = model.Operation( + "op", + input_variable=[in_var], + output_variable=[out_var], + in_output_variable=[inout_var], + ) sm = self._submodel(op) result = list(walk_submodel(sm)) self.assertCountEqual([in_var, out_var, inout_var, op], result) @@ -69,14 +79,18 @@ def test_operation_empty_variables(self): def test_collection_inside_entity_statement(self): inner = model.Property("inner", model.datatypes.String) coll = model.SubmodelElementCollection("coll", value=[inner]) - entity = model.Entity("entity", model.EntityType.CO_MANAGED_ENTITY, statement=[coll]) + entity = model.Entity( + "entity", model.EntityType.CO_MANAGED_ENTITY, statement=[coll] + ) sm = self._submodel(entity) result = list(walk_submodel(sm)) self.assertCountEqual([inner, coll, entity], result) def test_entity_inside_operation_input_variable(self): stmt = model.Property("stmt", model.datatypes.String) - entity = model.Entity("entity", model.EntityType.CO_MANAGED_ENTITY, statement=[stmt]) + entity = model.Entity( + "entity", model.EntityType.CO_MANAGED_ENTITY, statement=[stmt] + ) op = model.Operation("op", input_variable=[entity]) sm = self._submodel(op) result = list(walk_submodel(sm)) @@ -84,7 +98,9 @@ def test_entity_inside_operation_input_variable(self): def test_walk_from_collection(self): prop = model.Property("prop", model.datatypes.String) - entity = model.Entity("entity", model.EntityType.CO_MANAGED_ENTITY, statement=[prop]) + entity = model.Entity( + "entity", model.EntityType.CO_MANAGED_ENTITY, statement=[prop] + ) coll = model.SubmodelElementCollection("coll", value=[entity]) # walk_submodel_element yields descendants of coll, not coll itself result = list(walk_submodel_element(coll)) @@ -92,7 +108,9 @@ def test_walk_from_collection(self): def test_walk_from_list(self): op = model.Operation(None) - sml = model.SubmodelElementList("sml", type_value_list_element=model.Operation, value=[op]) + sml = model.SubmodelElementList( + "sml", type_value_list_element=model.Operation, value=[op] + ) # walk_submodel_element yields descendants of sml, not sml itself result = list(walk_submodel_element(sml)) self.assertCountEqual([op], result) @@ -100,7 +118,9 @@ def test_walk_from_list(self): def test_file_inside_entity_is_found(self): """Regression test for issue #423: File inside Entity.statement must be yielded.""" f = model.File("file", content_type="application/pdf", value="/some/file.pdf") - entity = model.Entity("entity", model.EntityType.CO_MANAGED_ENTITY, statement=[f]) + entity = model.Entity( + "entity", model.EntityType.CO_MANAGED_ENTITY, statement=[f] + ) sm = self._submodel(entity) files = [e for e in walk_submodel(sm) if isinstance(e, model.File)] self.assertIn(f, files) From f6e4bc318622cedf73af2001ec581c2ea08add0e Mon Sep 17 00:00:00 2001 From: Joshua Benning Date: Fri, 17 Jul 2026 14:56:43 +0200 Subject: [PATCH 02/17] Fix mypy issues --- .../aas/adapter/json/json_deserialization.py | 8 +++---- .../aas/adapter/xml/xml_deserialization.py | 4 ++-- sdk/basyx/aas/examples/data/_helper.py | 24 +++++++++---------- sdk/basyx/aas/model/submodel.py | 8 +++---- sdk/test/model/test_datatypes.py | 4 ++-- 5 files changed, 24 insertions(+), 24 deletions(-) diff --git a/sdk/basyx/aas/adapter/json/json_deserialization.py b/sdk/basyx/aas/adapter/json/json_deserialization.py index ee83ad7b7..8144f73a7 100644 --- a/sdk/basyx/aas/adapter/json/json_deserialization.py +++ b/sdk/basyx/aas/adapter/json/json_deserialization.py @@ -340,8 +340,8 @@ def _amend_abstract_attributes(cls, obj: object, dct: Dict[str, object]) -> None data_specification_content=_get_ts( dspec, "dataSpecificationContent", - model.DataSpecificationContent, - ), # type: ignore + model.DataSpecificationContent, # type: ignore + ), ) ) if isinstance(obj, model.HasExtension) and not cls.stripped: @@ -752,8 +752,8 @@ def _construct_basic_event_element( ret = object_class( id_short=None, observed=cls._construct_model_reference( - _get_ts(dct, "observed", dict), model.Referable - ), # type: ignore + _get_ts(dct, "observed", dict), model.Referable # type: ignore + ), direction=DIRECTION_INVERSE[_get_ts(dct, "direction", str)], state=STATE_OF_EVENT_INVERSE[_get_ts(dct, "state", str)], ) diff --git a/sdk/basyx/aas/adapter/xml/xml_deserialization.py b/sdk/basyx/aas/adapter/xml/xml_deserialization.py index f50297936..9579d9e9d 100644 --- a/sdk/basyx/aas/adapter/xml/xml_deserialization.py +++ b/sdk/basyx/aas/adapter/xml/xml_deserialization.py @@ -667,8 +667,8 @@ def _construct_referable_reference( # TODO: remove the following type: ignore comments when mypy supports abstract types for Type[T] # see https://github.com/python/mypy/issues/5374 return cls.construct_model_reference_expect_type( - element, model.Referable, **kwargs - ) # type: ignore + element, model.Referable, **kwargs # type: ignore + ) @classmethod def _construct_operation_variable( diff --git a/sdk/basyx/aas/examples/data/_helper.py b/sdk/basyx/aas/examples/data/_helper.py index a4ffcf6be..59c79dd3d 100644 --- a/sdk/basyx/aas/examples/data/_helper.py +++ b/sdk/basyx/aas/examples/data/_helper.py @@ -130,8 +130,8 @@ def _check_submodel_element( return self.check_property_equal(object_, expected_object) # type: ignore if isinstance(object_, model.MultiLanguageProperty): return self.check_multi_language_property_equal( - object_, expected_object - ) # type: ignore + object_, expected_object # type: ignore + ) if isinstance(object_, model.Range): return self.check_range_equal(object_, expected_object) # type: ignore if isinstance(object_, model.Blob): @@ -142,14 +142,14 @@ def _check_submodel_element( return self.check_reference_element_equal(object_, expected_object) # type: ignore if isinstance(object_, model.SubmodelElementCollection): return self.check_submodel_element_collection_equal( - object_, expected_object - ) # type: ignore + object_, expected_object # type: ignore + ) if isinstance(object_, model.SubmodelElementList): return self.check_submodel_element_list_equal(object_, expected_object) # type: ignore if isinstance(object_, model.AnnotatedRelationshipElement): return self.check_annotated_relationship_element_equal( - object_, expected_object - ) # type: ignore + object_, expected_object # type: ignore + ) if isinstance(object_, model.RelationshipElement): return self.check_relationship_element_equal(object_, expected_object) # type: ignore if isinstance(object_, model.Operation): @@ -1200,8 +1200,8 @@ def _check_data_specification_iec61360_equal( value=expected_value.value_list, ): self._check_value_list_equal( - object_.value_list, expected_value.value_list - ) # type: ignore + object_.value_list, expected_value.value_list # type: ignore + ) if object_.value_list is not None: if self.check( @@ -1210,8 +1210,8 @@ def _check_data_specification_iec61360_equal( value=len(object_.value_list), ): self._check_value_list_equal( - object_.value_list, expected_value.value_list - ) # type: ignore + object_.value_list, expected_value.value_list # type: ignore + ) def _check_value_list_equal( self, object_: model.ValueList, expected_value: model.ValueList @@ -1361,8 +1361,8 @@ def check_attribute_equal( return self.check( getattr(object_, attribute_name) is expected_value, # type:ignore "Attribute {} of {} must be == {}".format( - attribute_name, repr(object_), expected_value.__name__ - ), # type:ignore + attribute_name, repr(object_), expected_value.__name__ # type:ignore + ), **kwargs, ) else: diff --git a/sdk/basyx/aas/model/submodel.py b/sdk/basyx/aas/model/submodel.py index 535d3f1dd..f2d7cb1a4 100644 --- a/sdk/basyx/aas/model/submodel.py +++ b/sdk/basyx/aas/model/submodel.py @@ -948,15 +948,15 @@ def _check_constraints(self, new: _SE, existing: Iterable[_SE]) -> None: # Ignore the types here because the typechecker doesn't get it. if ( self.type_value_list_element in (Property, Range) - and new.value_type is not self.value_type_list_element - ): # type: ignore + and new.value_type is not self.value_type_list_element # type: ignore + ): raise base.AASConstraintViolation( 109, "All first level elements must have the value_type " # type: ignore "specified by value_type_list_element=" f"{self.value_type_list_element.__name__}, got " # type: ignore - f"{new!r} with value_type={new.value_type.__name__}", - ) # type: ignore + f"{new!r} with value_type={new.value_type.__name__}", # type: ignore + ) # If semantic_id_list_element is not None that would already enforce the semantic_id for all first level # elements. Thus, we only need to perform this check if semantic_id_list_element is None. diff --git a/sdk/test/model/test_datatypes.py b/sdk/test/model/test_datatypes.py index cb741a203..a1fcba8aa 100644 --- a/sdk/test/model/test_datatypes.py +++ b/sdk/test/model/test_datatypes.py @@ -605,8 +605,8 @@ def test_float(self) -> None: 5300, model.datatypes.from_xsd("5.3E3", model.datatypes.Double) ) self.assertTrue( - math.isnan(model.datatypes.from_xsd("NaN", model.datatypes.Double)) - ) # type: ignore + math.isnan(model.datatypes.from_xsd("NaN", model.datatypes.Double)) # type: ignore + ) self.assertEqual( float("inf"), model.datatypes.from_xsd("INF", model.datatypes.Double) ) From 07b123fc32fb2294f195ffed47e3988ca9ca6720 Mon Sep 17 00:00:00 2001 From: Joshua Benning Date: Fri, 17 Jul 2026 14:58:17 +0200 Subject: [PATCH 03/17] Update copyright years --- sdk/basyx/aas/model/aas.py | 2 +- sdk/basyx/aas/model/concept.py | 2 +- sdk/test/_helper/setup_testdb.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/sdk/basyx/aas/model/aas.py b/sdk/basyx/aas/model/aas.py index 52ad7067c..75a18dd5d 100644 --- a/sdk/basyx/aas/model/aas.py +++ b/sdk/basyx/aas/model/aas.py @@ -1,4 +1,4 @@ -# Copyright (c) 2025 the Eclipse BaSyx Authors +# Copyright (c) 2026 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. diff --git a/sdk/basyx/aas/model/concept.py b/sdk/basyx/aas/model/concept.py index e217dc03d..3ae7427cc 100644 --- a/sdk/basyx/aas/model/concept.py +++ b/sdk/basyx/aas/model/concept.py @@ -1,4 +1,4 @@ -# Copyright (c) 2025 the Eclipse BaSyx Authors +# Copyright (c) 2026 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. diff --git a/sdk/test/_helper/setup_testdb.py b/sdk/test/_helper/setup_testdb.py index 63dc2f5ad..bb32fe915 100755 --- a/sdk/test/_helper/setup_testdb.py +++ b/sdk/test/_helper/setup_testdb.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# Copyright (c) 2025 the Eclipse BaSyx Authors +# Copyright (c) 2026 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. From e5e82c8b8acc719ddb25f2957108bc9c797ad8e7 Mon Sep 17 00:00:00 2001 From: Joshua Benning Date: Fri, 17 Jul 2026 15:00:59 +0200 Subject: [PATCH 04/17] Remove pycodestyle from ci as covered by linter --- .github/workflows/ci.yml | 9 --------- 1 file changed, 9 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a5e2c2549..e71f1b5eb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -116,9 +116,6 @@ jobs: - name: Check typing with MyPy run: | python -m mypy basyx test - - name: Check code style with PyCodestyle - run: | - python -m pycodestyle --count --max-line-length 120 basyx test sdk-readme-codeblocks: # This job runs the same static code analysis (mypy and pycodestyle) on the codeblocks in our docstrings. @@ -265,9 +262,6 @@ jobs: - name: Check typing with MyPy run: | python -m mypy aas_compliance_tool test - - name: Check code style with PyCodestyle - run: | - python -m pycodestyle --count --max-line-length 120 aas_compliance_tool test compliance-tool-package: # This job checks if we can build our compliance_tool package @@ -316,9 +310,6 @@ jobs: - name: Check typing with MyPy run: | python -m mypy app test - - name: Check code style with PyCodestyle - run: | - python -m pycodestyle --count --max-line-length 120 app test server-repository-docker: # This job checks if we can build our server package From c42445e7169515e06a9dbf562041f1fe1661a8b2 Mon Sep 17 00:00:00 2001 From: Joshua Benning Date: Fri, 17 Jul 2026 15:04:37 +0200 Subject: [PATCH 05/17] Add ruff.toml rerun format --- ruff.toml | 33 +++++++++++ server/app/adapter/jsonization.py | 1 - server/app/interfaces/base.py | 14 +++-- server/app/interfaces/discovery.py | 17 +++--- server/app/interfaces/registry.py | 23 ++++---- server/app/interfaces/repository.py | 69 ++++++++++++++--------- server/app/model/descriptor.py | 2 - server/app/model/endpoint.py | 1 - server/app/model/service_specification.py | 39 ++++++++----- server/test/backend/test_local_file.py | 4 +- server/test/model/test_provider.py | 2 +- server/test/test_api_base_path.py | 6 +- 12 files changed, 133 insertions(+), 78 deletions(-) create mode 100644 ruff.toml diff --git a/ruff.toml b/ruff.toml new file mode 100644 index 000000000..3e683e8ef --- /dev/null +++ b/ruff.toml @@ -0,0 +1,33 @@ +line-length = 120 # matches the current pycodestyle --max-line-length 120 +target-version = "py310" # matches X_PYTHON_MIN_VERSION in ci.yml + +[format] +quote-style = "double" +indent-style = "space" +docstring-code-format = true + + +[lint] +select = [ + "E", # pycodestyle errors + "W", # pycodestyle warnings + "I", # isort + "N", # pep8-naming + "F4", # pyflakes: imports/__future__ + "F5", # pyflakes: format strings + "F7", # pyflakes: control flow (return/break/continue outside loop/func) + "F8", # pyflakes: undefined/unused names + "RUF100", # unused noqa + "COM818", # enforce trailing comma + "B", # flake8-bugbear + "D204", "D211", "D201", "D202", "D300", # basic docstring formatting + "T20", # prevent native print() statements + "A", # prevent shadowing of python builtins +] + +# TODO: Revisit these +ignore = [ + "F403", # star imports - high existing usage + "F405", # may-be-undefined from star imports + "N818", # Exception name should be named with Error suffix +] diff --git a/server/app/adapter/jsonization.py b/server/app/adapter/jsonization.py index 897590c77..8cb6da1bf 100644 --- a/server/app/adapter/jsonization.py +++ b/server/app/adapter/jsonization.py @@ -208,7 +208,6 @@ class ServerStrictStrippedAASFromJsonDecoder(ServerStrictAASFromJsonDecoder, Ser class ServerAASToJsonEncoder(AASToJsonEncoder): - @classmethod def _get_aas_class_serializers(cls) -> Dict[Type, Callable]: serializers = super()._get_aas_class_serializers() diff --git a/server/app/interfaces/base.py b/server/app/interfaces/base.py index d32312370..c6bf6d11f 100644 --- a/server/app/interfaces/base.py +++ b/server/app/interfaces/base.py @@ -90,8 +90,12 @@ def __init__(self, cursor: Optional[str] = None): class APIResponse(abc.ABC, Response): @abc.abstractmethod def __init__( - self, obj: Optional[ResponseData] = None, paging_metadata: Optional[PagingMetadata] = None, - stripped: bool = False, *args, **kwargs + self, + obj: Optional[ResponseData] = None, + paging_metadata: Optional[PagingMetadata] = None, + stripped: bool = False, + *args, + **kwargs, ): super().__init__(*args, **kwargs) if obj is None: @@ -229,8 +233,10 @@ def _get_slice(cls, request: Request, iterator: Iterable[T]) -> Tuple[Iterator[T limit_str = request.args.get("limit", default="100") cursor_str = request.args.get("cursor", default="1") try: - limit, cursor = (NonNegativeInteger(int(limit_str)), - NonNegativeInteger(int(cursor_str) - 1)) # cursor is 1-indexed + limit, cursor = ( + NonNegativeInteger(int(limit_str)), + NonNegativeInteger(int(cursor_str) - 1), + ) # cursor is 1-indexed except ValueError: raise BadRequest("Limit can not be negative, cursor must be positive!") start_index = cursor diff --git a/server/app/interfaces/discovery.py b/server/app/interfaces/discovery.py index a340a0e12..3f290241d 100644 --- a/server/app/interfaces/discovery.py +++ b/server/app/interfaces/discovery.py @@ -19,10 +19,12 @@ from app.util.converters import IdentifierToBase64URLConverter, base64url_decode from app.model import ServiceSpecificationProfileEnum, ServiceDescription -SUPPORTED_PROFILES: ServiceDescription = ServiceDescription([ - ServiceSpecificationProfileEnum.DISCOVERY_FULL, - ServiceSpecificationProfileEnum.DISCOVERY_READ, -]) +SUPPORTED_PROFILES: ServiceDescription = ServiceDescription( + [ + ServiceSpecificationProfileEnum.DISCOVERY_FULL, + ServiceSpecificationProfileEnum.DISCOVERY_READ, + ] +) class DiscoveryStore: @@ -106,10 +108,7 @@ def to_file(self, filename: str) -> None: corrupting the existing store if serialization fails. """ data = { - "aas_id_to_asset_ids": { - aas_id: list(asset_ids) - for aas_id, asset_ids in self.aas_id_to_asset_ids.items() - } + "aas_id_to_asset_ids": {aas_id: list(asset_ids) for aas_id, asset_ids in self.aas_id_to_asset_ids.items()} } temp_filename = f"{filename}.tmp" @@ -164,7 +163,7 @@ def get_description(self, request: Request, url_args: Dict, response_t: Type[API return response_t(SUPPORTED_PROFILES.to_dict()) def get_all_aas_ids_by_asset_link( - self, request: Request, url_args: dict, response_t: Type[APIResponse], **_kwargs + self, request: Request, url_args: dict, response_t: Type[APIResponse], **_kwargs ) -> Response: asset_ids_param = request.args.get("assetIds", "") if not asset_ids_param: diff --git a/server/app/interfaces/registry.py b/server/app/interfaces/registry.py index 274602fff..eadca8753 100644 --- a/server/app/interfaces/registry.py +++ b/server/app/interfaces/registry.py @@ -20,12 +20,14 @@ from app.model import DictDescriptorStore, ServiceSpecificationProfileEnum, ServiceDescription from app.util.converters import IdentifierToBase64URLConverter, base64url_decode -SUPPORTED_PROFILES: ServiceDescription = ServiceDescription([ - ServiceSpecificationProfileEnum.AAS_REGISTRY_FULL, - ServiceSpecificationProfileEnum.SUBMODEL_REGISTRY_FULL, - ServiceSpecificationProfileEnum.AAS_REGISTRY_READ, - ServiceSpecificationProfileEnum.SUBMODEL_REGISTRY_READ, -]) +SUPPORTED_PROFILES: ServiceDescription = ServiceDescription( + [ + ServiceSpecificationProfileEnum.AAS_REGISTRY_FULL, + ServiceSpecificationProfileEnum.SUBMODEL_REGISTRY_FULL, + ServiceSpecificationProfileEnum.AAS_REGISTRY_READ, + ServiceSpecificationProfileEnum.SUBMODEL_REGISTRY_READ, + ] +) class RegistryAPI(ObjectStoreWSGIApp): @@ -127,8 +129,7 @@ def _get_all_aas_descriptors( asset_kind = model.AssetKind[asset_kind_str] except KeyError: raise BadRequest( - f"Invalid assetKind '{asset_kind_str}', " - f"must be one of {list(model.AssetKind.__members__)}" + f"Invalid assetKind '{asset_kind_str}', must be one of {list(model.AssetKind.__members__)}" ) descriptors = filter(lambda desc: desc.asset_kind == asset_kind, descriptors) @@ -147,9 +148,9 @@ def _get_all_aas_descriptors( def _get_aas_descriptor(self, url_args: Dict) -> server_model.AssetAdministrationShellDescriptor: return self._get_obj_ts(url_args["aas_id"], server_model.AssetAdministrationShellDescriptor) - def _get_all_submodel_descriptors(self, request: Request) -> Tuple[ - Iterator[server_model.SubmodelDescriptor], Optional[PagingMetadata] - ]: + def _get_all_submodel_descriptors( + self, request: Request + ) -> Tuple[Iterator[server_model.SubmodelDescriptor], Optional[PagingMetadata]]: submodel_descriptors: Iterator[server_model.SubmodelDescriptor] = self._get_all_obj_of_type( server_model.SubmodelDescriptor ) diff --git a/server/app/interfaces/repository.py b/server/app/interfaces/repository.py index c604408e9..21d8ffe00 100644 --- a/server/app/interfaces/repository.py +++ b/server/app/interfaces/repository.py @@ -27,12 +27,14 @@ from .base import ObjectStoreWSGIApp, APIResponse, is_stripped_request, HTTPApiDecoder, T from app.model import ServiceSpecificationProfileEnum, ServiceDescription -SUPPORTED_PROFILES: ServiceDescription = ServiceDescription([ - ServiceSpecificationProfileEnum.AAS_REPOSITORY_FULL, - ServiceSpecificationProfileEnum.SUBMODEL_REPOSITORY_FULL, - ServiceSpecificationProfileEnum.AAS_REPOSITORY_READ, - ServiceSpecificationProfileEnum.SUBMODEL_REPOSITORY_READ, -]) +SUPPORTED_PROFILES: ServiceDescription = ServiceDescription( + [ + ServiceSpecificationProfileEnum.AAS_REPOSITORY_FULL, + ServiceSpecificationProfileEnum.SUBMODEL_REPOSITORY_FULL, + ServiceSpecificationProfileEnum.AAS_REPOSITORY_READ, + ServiceSpecificationProfileEnum.SUBMODEL_REPOSITORY_READ, + ] +) class WSGIApp(ObjectStoreWSGIApp): @@ -431,7 +433,7 @@ def _get_submodel_reference( raise NotFound(f"The AAS {aas!r} doesn't have a submodel reference to {submodel_id!r}!") def _get_shells( - self, request: Request + self, request: Request ) -> Tuple[Iterator[model.AssetAdministrationShell], Optional[PagingMetadata]]: aas: Iterator[model.AssetAdministrationShell] = self._get_all_obj_of_type(model.AssetAdministrationShell) @@ -489,7 +491,9 @@ def _get_submodels(self, request: Request) -> Tuple[Iterator[model.Submodel], Op semantic_id = request.args.get("semanticId") if semantic_id is not None: spec_semantic_id = HTTPApiDecoder.base64url_json( - semantic_id, model.Reference, False # type: ignore[type-abstract] + semantic_id, + model.Reference, + False, # type: ignore[type-abstract] ) submodels = filter(lambda sm: sm.semantic_id == spec_semantic_id, submodels) paginated_submodels, paging_metadata = self._get_slice(request, submodels) @@ -590,18 +594,20 @@ def get_aas_submodel_refs( submodel_refs, paging_metadata = self._get_slice(request, sorted_submodel_refs) return response_t(list(submodel_refs), paging_metadata=paging_metadata) - def post_aas_submodel_refs(self, request: Request, url_args: Dict, response_t: Type[APIResponse], - map_adapter: MapAdapter, **_kwargs) -> Response: + def post_aas_submodel_refs( + self, request: Request, url_args: Dict, response_t: Type[APIResponse], map_adapter: MapAdapter, **_kwargs + ) -> Response: aas = self._get_shell(url_args) sm_ref = HTTPApiDecoder.request_body(request, model.ModelReference, False) if sm_ref in aas.submodel: raise Conflict(f"{sm_ref!r} already exists!") aas.submodel.add(sm_ref) self.object_store.commit(aas) - created_resource_url = map_adapter.build(self.delete_aas_submodel_refs_specific, { - "aas_id": aas.id, - "submodel_id": sm_ref.key[0].value - }, force_external=True) + created_resource_url = map_adapter.build( + self.delete_aas_submodel_refs_specific, + {"aas_id": aas.id, "submodel_id": sm_ref.key[0].value}, + force_external=True, + ) return response_t(sm_ref, status=201, headers={"Location": created_resource_url}) def delete_aas_submodel_refs_specific( @@ -672,8 +678,9 @@ def post_submodel( created_resource_url = map_adapter.build(self.get_submodel, {"submodel_id": submodel.id}, force_external=True) return response_t(submodel, status=201, headers={"Location": created_resource_url}) - def get_submodel_all_metadata(self, request: Request, url_args: Dict, response_t: Type[APIResponse], - **_kwargs) -> Response: + def get_submodel_all_metadata( + self, request: Request, url_args: Dict, response_t: Type[APIResponse], **_kwargs + ) -> Response: if "level" in request.args: raise BadRequest(f"level cannot be used when retrieving metadata!") submodels, paging_metadata = self._get_submodels(request) @@ -698,8 +705,9 @@ def get_submodel(self, request: Request, url_args: Dict, response_t: Type[APIRes submodel = self._get_submodel(url_args) return response_t(submodel, stripped=is_stripped_request(request)) - def get_submodels_metadata(self, request: Request, url_args: Dict, response_t: Type[APIResponse], - **_kwargs) -> Response: + def get_submodels_metadata( + self, request: Request, url_args: Dict, response_t: Type[APIResponse], **_kwargs + ) -> Response: if "level" in request.args: raise BadRequest(f"level cannot be used when retrieving metadata!") submodel = self._get_submodel(url_args) @@ -726,8 +734,9 @@ def get_submodel_submodel_elements( list(submodel_elements), paging_metadata=paging_metadata, stripped=is_stripped_request(request) ) - def get_submodel_submodel_elements_metadata(self, request: Request, url_args: Dict, response_t: Type[APIResponse], - **_kwargs) -> Response: + def get_submodel_submodel_elements_metadata( + self, request: Request, url_args: Dict, response_t: Type[APIResponse], **_kwargs + ) -> Response: if "level" in request.args: raise BadRequest(f"level cannot be used when retrieving metadata!") submodel_elements, paging_metadata = self._get_submodel_submodel_elements(request, url_args) @@ -748,8 +757,9 @@ def get_submodel_submodel_elements_id_short_path( submodel_element = self._get_submodel_submodel_elements_id_short_path(url_args) return response_t(submodel_element, stripped=is_stripped_request(request)) - def get_submodel_submodel_elements_id_short_path_metadata(self, request: Request, url_args: Dict, - response_t: Type[APIResponse], **_kwargs) -> Response: + def get_submodel_submodel_elements_id_short_path_metadata( + self, request: Request, url_args: Dict, response_t: Type[APIResponse], **_kwargs + ) -> Response: if "level" in request.args: raise BadRequest(f"level cannot be used when retrieving metadata!") submodel_element = self._get_submodel_submodel_elements_id_short_path(url_args) @@ -773,7 +783,9 @@ def post_submodel_submodel_elements_id_short_path( # TODO: remove the following type: ignore comment when mypy supports abstract types for Type[T] # see https://github.com/python/mypy/issues/5374 new_submodel_element = HTTPApiDecoder.request_body( - request, model.SubmodelElement, is_stripped_request(request) # type: ignore[type-abstract] + request, + model.SubmodelElement, + is_stripped_request(request), # type: ignore[type-abstract] ) try: parent.add_referable(new_submodel_element) @@ -781,7 +793,7 @@ def post_submodel_submodel_elements_id_short_path( if e.constraint_id != 22: raise raise Conflict( - f"SubmodelElement with idShort {new_submodel_element.id_short} already exists " f"within {parent}!" + f"SubmodelElement with idShort {new_submodel_element.id_short} already exists within {parent}!" ) self.object_store.commit(self._get_submodel(url_args)) submodel = self._get_submodel(url_args) @@ -800,7 +812,9 @@ def put_submodel_submodel_elements_id_short_path( # TODO: remove the following type: ignore comment when mypy supports abstract types for Type[T] # see https://github.com/python/mypy/issues/5374 new_submodel_element = HTTPApiDecoder.request_body( - request, model.SubmodelElement, is_stripped_request(request) # type: ignore[type-abstract] + request, + model.SubmodelElement, + is_stripped_request(request), # type: ignore[type-abstract] ) submodel_element.update_from(new_submodel_element) self.object_store.commit(self._get_submodel(url_args)) @@ -961,8 +975,9 @@ def get_concept_description_all( ) -> Response: concept_descriptions: Iterator[model.ConceptDescription] = self._get_all_obj_of_type(model.ConceptDescription) concept_descriptions, paging_metadata = self._get_slice(request, concept_descriptions) - return response_t(list(concept_descriptions), paging_metadata=paging_metadata, - stripped=is_stripped_request(request)) + return response_t( + list(concept_descriptions), paging_metadata=paging_metadata, stripped=is_stripped_request(request) + ) def post_concept_description( self, request: Request, url_args: Dict, response_t: Type[APIResponse], map_adapter: MapAdapter diff --git a/server/app/model/descriptor.py b/server/app/model/descriptor.py index 13fdf8d00..2680c256c 100644 --- a/server/app/model/descriptor.py +++ b/server/app/model/descriptor.py @@ -41,7 +41,6 @@ def update_from(self, other: "Descriptor", update_source: bool = False): class SubmodelDescriptor(Descriptor): - def __init__( self, id_: model.Identifier, @@ -63,7 +62,6 @@ def __init__( class AssetAdministrationShellDescriptor(Descriptor): - def __init__( self, id_: model.Identifier, diff --git a/server/app/model/endpoint.py b/server/app/model/endpoint.py index 06301e9a1..cd51e2bb0 100644 --- a/server/app/model/endpoint.py +++ b/server/app/model/endpoint.py @@ -38,7 +38,6 @@ def __init__(self, type_: SecurityTypeEnum, key: str, value: str): class ProtocolInformation: - def __init__( self, href: str, diff --git a/server/app/model/service_specification.py b/server/app/model/service_specification.py index 5181901ad..336525cf2 100644 --- a/server/app/model/service_specification.py +++ b/server/app/model/service_specification.py @@ -25,16 +25,21 @@ class ServiceSpecificationProfileEnum(str, enum.Enum): AASX_FILESERVER_FULL = "https://admin-shell.io/aas/API/3/1/AasxFileServerServiceSpecification/SSP-001" # --- AAS Registry --- - AAS_REGISTRY_FULL = \ + AAS_REGISTRY_FULL = ( "https://admin-shell.io/aas/API/3/1/AssetAdministrationShellRegistryServiceSpecification/SSP-001" - AAS_REGISTRY_READ = \ + ) + AAS_REGISTRY_READ = ( "https://admin-shell.io/aas/API/3/1/AssetAdministrationShellRegistryServiceSpecification/SSP-002" - AAS_REGISTRY_BULK = \ + ) + AAS_REGISTRY_BULK = ( "https://admin-shell.io/aas/API/3/1/AssetAdministrationShellRegistryServiceSpecification/SSP-003" - AAS_REGISTRY_QUERY = \ + ) + AAS_REGISTRY_QUERY = ( "https://admin-shell.io/aas/API/3/1/AssetAdministrationShellRegistryServiceSpecification/SSP-004" - AAS_REGISTRY_MINIMAL_READ = \ + ) + AAS_REGISTRY_MINIMAL_READ = ( "https://admin-shell.io/aas/API/3/1/AssetAdministrationShellRegistryServiceSpecification/SSP-005" + ) # --- Submodel Registry --- SUBMODEL_REGISTRY_FULL = "https://admin-shell.io/aas/API/3/1/SubmodelRegistryServiceSpecification/SSP-001" @@ -43,28 +48,32 @@ class ServiceSpecificationProfileEnum(str, enum.Enum): SUBMODEL_REGISTRY_QUERY = "https://admin-shell.io/aas/API/3/1/SubmodelRegistryServiceSpecification/SSP-004" # --- AAS Repository --- - AAS_REPOSITORY_FULL = \ + AAS_REPOSITORY_FULL = ( "https://admin-shell.io/aas/API/3/1/AssetAdministrationShellRepositoryServiceSpecification/SSP-001" - AAS_REPOSITORY_READ = \ + ) + AAS_REPOSITORY_READ = ( "https://admin-shell.io/aas/API/3/1/AssetAdministrationShellRepositoryServiceSpecification/SSP-002" - AAS_REPOSITORY_QUERY = \ + ) + AAS_REPOSITORY_QUERY = ( "https://admin-shell.io/aas/API/3/1/AssetAdministrationShellRepositoryServiceSpecification/SSP-003" + ) # --- Submodel Repository --- SUBMODEL_REPOSITORY_FULL = "https://admin-shell.io/aas/API/3/1/SubmodelRepositoryServiceSpecification/SSP-001" SUBMODEL_REPOSITORY_READ = "https://admin-shell.io/aas/API/3/1/SubmodelRepositoryServiceSpecification/SSP-002" - SUBMODEL_REPOSITORY_TEMPLATE = \ - "https://admin-shell.io/aas/API/3/1/SubmodelRepositoryServiceSpecification/SSP-003" - SUBMODEL_REPOSITORY_TEMPLATE_READ = \ + SUBMODEL_REPOSITORY_TEMPLATE = "https://admin-shell.io/aas/API/3/1/SubmodelRepositoryServiceSpecification/SSP-003" + SUBMODEL_REPOSITORY_TEMPLATE_READ = ( "https://admin-shell.io/aas/API/3/1/SubmodelRepositoryServiceSpecification/SSP-004" - SUBMODEL_REPOSITORY_QUERY = \ - "https://admin-shell.io/aas/API/3/1/SubmodelRepositoryServiceSpecification/SSP-005" + ) + SUBMODEL_REPOSITORY_QUERY = "https://admin-shell.io/aas/API/3/1/SubmodelRepositoryServiceSpecification/SSP-005" # --- Concept Description Repository --- - CONCEPT_DESCRIPTION_REPOSITORY_FULL = \ + CONCEPT_DESCRIPTION_REPOSITORY_FULL = ( "https://admin-shell.io/aas/API/3/1/ConceptDescriptionRepositoryServiceSpecification/SSP-001" - CONCEPT_DESCRIPTION_REPOSITORY_QUERY = \ + ) + CONCEPT_DESCRIPTION_REPOSITORY_QUERY = ( "https://admin-shell.io/aas/API/3/1/ConceptDescriptionRepositoryServiceSpecification/SSP-002" + ) # --- Discovery --- DISCOVERY_FULL = "https://admin-shell.io/aas/API/3/1/DiscoveryServiceSpecification/SSP-001" diff --git a/server/test/backend/test_local_file.py b/server/test/backend/test_local_file.py index aeafb85c5..93788f113 100644 --- a/server/test/backend/test_local_file.py +++ b/server/test/backend/test_local_file.py @@ -76,7 +76,7 @@ def test_key_errors(self) -> None: with self.assertRaises(KeyError) as cm: self.descriptor_store.add(self.aasd1) self.assertEqual( - "'Descriptor with id https://example.org/AASDescriptor/1 already exists in " "local file database'", + "'Descriptor with id https://example.org/AASDescriptor/1 already exists in local file database'", str(cm.exception), ) @@ -85,7 +85,7 @@ def test_key_errors(self) -> None: self.descriptor_store.get_item("https://example.org/AASDescriptor/1") self.assertIsNone(self.descriptor_store.get("https://example.org/AASDescriptor/1")) self.assertEqual( - "'No Identifiable with id https://example.org/AASDescriptor/1 found in local " "file database'", + "'No Identifiable with id https://example.org/AASDescriptor/1 found in local file database'", str(cm.exception), ) diff --git a/server/test/model/test_provider.py b/server/test/model/test_provider.py index ee3b810da..567eb6fa2 100644 --- a/server/test/model/test_provider.py +++ b/server/test/model/test_provider.py @@ -35,7 +35,7 @@ def test_store_retrieve(self) -> None: with self.assertRaises(KeyError) as cm: descriptor_store.add(aasd3) self.assertEqual( - "'Descriptor object with same id https://example.org/AASDescriptor/1 is already " "stored in this store'", + "'Descriptor object with same id https://example.org/AASDescriptor/1 is already stored in this store'", str(cm.exception), ) self.assertEqual(2, len(descriptor_store)) diff --git a/server/test/test_api_base_path.py b/server/test/test_api_base_path.py index 7617102d7..4d0a88de2 100644 --- a/server/test/test_api_base_path.py +++ b/server/test/test_api_base_path.py @@ -47,8 +47,4 @@ def test_base_path_aligned_across_known_files(self) -> None: values[str(path.relative_to(SERVER_ROOT))] = _extract(path) distinct = set(values.values()) - self.assertEqual( - 1, - len(distinct), - _list_divergences(values) - ) + self.assertEqual(1, len(distinct), _list_divergences(values)) From 6ebb37271793c6c32d2c55196c6f2f78370710eb Mon Sep 17 00:00:00 2001 From: Joshua Benning Date: Fri, 17 Jul 2026 15:08:52 +0200 Subject: [PATCH 06/17] Fix new mypy errors --- server/app/interfaces/repository.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/server/app/interfaces/repository.py b/server/app/interfaces/repository.py index 21d8ffe00..bf3acfdf8 100644 --- a/server/app/interfaces/repository.py +++ b/server/app/interfaces/repository.py @@ -492,8 +492,8 @@ def _get_submodels(self, request: Request) -> Tuple[Iterator[model.Submodel], Op if semantic_id is not None: spec_semantic_id = HTTPApiDecoder.base64url_json( semantic_id, - model.Reference, - False, # type: ignore[type-abstract] + model.Reference, # type: ignore[type-abstract] + False, ) submodels = filter(lambda sm: sm.semantic_id == spec_semantic_id, submodels) paginated_submodels, paging_metadata = self._get_slice(request, submodels) @@ -784,8 +784,8 @@ def post_submodel_submodel_elements_id_short_path( # see https://github.com/python/mypy/issues/5374 new_submodel_element = HTTPApiDecoder.request_body( request, - model.SubmodelElement, - is_stripped_request(request), # type: ignore[type-abstract] + model.SubmodelElement, # type: ignore[type-abstract] + is_stripped_request(request), ) try: parent.add_referable(new_submodel_element) @@ -813,8 +813,8 @@ def put_submodel_submodel_elements_id_short_path( # see https://github.com/python/mypy/issues/5374 new_submodel_element = HTTPApiDecoder.request_body( request, - model.SubmodelElement, - is_stripped_request(request), # type: ignore[type-abstract] + model.SubmodelElement, # type: ignore[type-abstract] + is_stripped_request(request), ) submodel_element.update_from(new_submodel_element) self.object_store.commit(self._get_submodel(url_args)) From a1be1e71b98c8d2f711da58cc3b4f5fe542bd025 Mon Sep 17 00:00:00 2001 From: Joshua Benning Date: Fri, 17 Jul 2026 15:11:48 +0200 Subject: [PATCH 07/17] Fix unused noqa statements --- server/app/interfaces/repository.py | 10 +++++----- server/app/model/endpoint.py | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/server/app/interfaces/repository.py b/server/app/interfaces/repository.py index bf3acfdf8..cf9430f53 100644 --- a/server/app/interfaces/repository.py +++ b/server/app/interfaces/repository.py @@ -192,7 +192,7 @@ def __init__( Rule( "/$metadata", methods=["GET"], - endpoint=self.get_submodel_submodel_elements_id_short_path_metadata, # noqa: E501 + endpoint=self.get_submodel_submodel_elements_id_short_path_metadata, ), Rule( "/$metadata", @@ -202,7 +202,7 @@ def __init__( Rule( "/$reference", methods=["GET"], - endpoint=self.get_submodel_submodel_elements_id_short_path_reference, # noqa: E501 + endpoint=self.get_submodel_submodel_elements_id_short_path_reference, ), Rule("/$value", methods=["GET"], endpoint=self.not_implemented), Rule( @@ -278,17 +278,17 @@ def __init__( Rule( "/", methods=["GET"], - endpoint=self.get_submodel_submodel_element_qualifiers, # noqa: E501 + endpoint=self.get_submodel_submodel_element_qualifiers, ), Rule( "/", methods=["PUT"], - endpoint=self.put_submodel_submodel_element_qualifiers, # noqa: E501 + endpoint=self.put_submodel_submodel_element_qualifiers, ), Rule( "/", methods=["DELETE"], - endpoint=self.delete_submodel_submodel_element_qualifiers, # noqa: E501 + endpoint=self.delete_submodel_submodel_element_qualifiers, ), ], ), diff --git a/server/app/model/endpoint.py b/server/app/model/endpoint.py index cd51e2bb0..c00fa3543 100644 --- a/server/app/model/endpoint.py +++ b/server/app/model/endpoint.py @@ -75,7 +75,7 @@ class Endpoint: } VERSION_PATTERN = re.compile(r"^\d+(\.\d+)*$") - def __init__(self, interface: base.NameType, protocol_information: ProtocolInformation): # noqa: E501 + def __init__(self, interface: base.NameType, protocol_information: ProtocolInformation): self.interface = interface self.protocol_information = protocol_information @@ -111,6 +111,6 @@ def protocol_information(self) -> ProtocolInformation: @protocol_information.setter def protocol_information(self, protocol_information: ProtocolInformation): if protocol_information is None: - raise ValueError("Invalid value for `protocol_information`, must not be `None`") # noqa: E501 + raise ValueError("Invalid value for `protocol_information`, must not be `None`") self._protocol_information = protocol_information From 3ec0b821d52b5d39500d86582e8ed220c76b8d26 Mon Sep 17 00:00:00 2001 From: Joshua Benning Date: Fri, 17 Jul 2026 15:18:57 +0200 Subject: [PATCH 08/17] Fix unnecessary f strings --- sdk/test/adapter/xml/test_xml_deserialization.py | 2 +- sdk/test/model/test_base.py | 8 ++++---- server/app/interfaces/base.py | 2 +- server/app/interfaces/repository.py | 8 ++++---- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/sdk/test/adapter/xml/test_xml_deserialization.py b/sdk/test/adapter/xml/test_xml_deserialization.py index 1d6582221..2ca04bbcd 100644 --- a/sdk/test/adapter/xml/test_xml_deserialization.py +++ b/sdk/test/adapter/xml/test_xml_deserialization.py @@ -480,7 +480,7 @@ def test_stripped_asset_administration_shell(self) -> None: class XmlDeserializationDataSpecTest(unittest.TestCase): def test_data_spec_iec61360_value_without_value_format(self) -> None: - xml = _xml_wrap(f""" + xml = _xml_wrap(""" http://example.org/test_cd diff --git a/sdk/test/model/test_base.py b/sdk/test/model/test_base.py index 8cdf5a94a..d4f887254 100644 --- a/sdk/test/model/test_base.py +++ b/sdk/test/model/test_base.py @@ -1677,16 +1677,16 @@ def test_language_tag_constraints(self) -> None: with self.assertRaises(ValueError) as cm: model.LangStringSet({"x": "bar"}) self.assertEqual( - f"The language tag must follow the format defined in BCP 47. " - f"Given language tag: x", + "The language tag must follow the format defined in BCP 47. " + "Given language tag: x", cm.exception.args[0], ) with self.assertRaises(ValueError) as cm: model.LangStringSet({"foo-oo1": "bar"}) self.assertEqual( - f"The language tag must follow the format defined in BCP 47. " - f"Given language tag: foo-oo1", + "The language tag must follow the format defined in BCP 47. " + "Given language tag: foo-oo1", cm.exception.args[0], ) diff --git a/server/app/interfaces/base.py b/server/app/interfaces/base.py index c6bf6d11f..c39ea0ce3 100644 --- a/server/app/interfaces/base.py +++ b/server/app/interfaces/base.py @@ -487,5 +487,5 @@ def is_stripped_request(request: Request) -> bool: raise BadRequest(f"Level {level} is not a valid level!") extent = request.args.get("extent") if extent is not None: - raise werkzeug.exceptions.NotImplemented(f"The parameter extent is not yet implemented for this server!") + raise werkzeug.exceptions.NotImplemented("The parameter extent is not yet implemented for this server!") return level == "core" diff --git a/server/app/interfaces/repository.py b/server/app/interfaces/repository.py index cf9430f53..f54ea455c 100644 --- a/server/app/interfaces/repository.py +++ b/server/app/interfaces/repository.py @@ -682,7 +682,7 @@ def get_submodel_all_metadata( self, request: Request, url_args: Dict, response_t: Type[APIResponse], **_kwargs ) -> Response: if "level" in request.args: - raise BadRequest(f"level cannot be used when retrieving metadata!") + raise BadRequest("level cannot be used when retrieving metadata!") submodels, paging_metadata = self._get_submodels(request) return response_t(list(submodels), paging_metadata=paging_metadata, stripped=True) @@ -709,7 +709,7 @@ def get_submodels_metadata( self, request: Request, url_args: Dict, response_t: Type[APIResponse], **_kwargs ) -> Response: if "level" in request.args: - raise BadRequest(f"level cannot be used when retrieving metadata!") + raise BadRequest("level cannot be used when retrieving metadata!") submodel = self._get_submodel(url_args) return response_t(submodel, stripped=True) @@ -738,7 +738,7 @@ def get_submodel_submodel_elements_metadata( self, request: Request, url_args: Dict, response_t: Type[APIResponse], **_kwargs ) -> Response: if "level" in request.args: - raise BadRequest(f"level cannot be used when retrieving metadata!") + raise BadRequest("level cannot be used when retrieving metadata!") submodel_elements, paging_metadata = self._get_submodel_submodel_elements(request, url_args) return response_t(list(submodel_elements), paging_metadata=paging_metadata, stripped=True) @@ -761,7 +761,7 @@ def get_submodel_submodel_elements_id_short_path_metadata( self, request: Request, url_args: Dict, response_t: Type[APIResponse], **_kwargs ) -> Response: if "level" in request.args: - raise BadRequest(f"level cannot be used when retrieving metadata!") + raise BadRequest("level cannot be used when retrieving metadata!") submodel_element = self._get_submodel_submodel_elements_id_short_path(url_args) if isinstance(submodel_element, model.Capability) or isinstance(submodel_element, model.Operation): raise BadRequest(f"{submodel_element.id_short} does not allow the content modifier metadata!") From 4997f8ab9d387fc0e4a9c64a4c2114e828ed9fbf Mon Sep 17 00:00:00 2001 From: Joshua Benning Date: Fri, 17 Jul 2026 15:26:31 +0200 Subject: [PATCH 09/17] Fix empy line after docstring (D204) --- compliance_tool/aas_compliance_tool/state_manager.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/compliance_tool/aas_compliance_tool/state_manager.py b/compliance_tool/aas_compliance_tool/state_manager.py index 1efe51991..a4f73c35b 100644 --- a/compliance_tool/aas_compliance_tool/state_manager.py +++ b/compliance_tool/aas_compliance_tool/state_manager.py @@ -25,6 +25,7 @@ class Status(enum.IntEnum): :cvar FAILED: :cvar NOT_EXECUTED: """ + SUCCESS = 0 SUCCESS_WITH_WARNINGS = 1 # never used FAILED = 2 @@ -39,6 +40,7 @@ class Step: :ivar status: Status of the step from type Status :ivar log_list: List of :class:`LogRecords ` which belong to this step """ + def __init__(self, name: str, status: Status, log_list: List[logging.LogRecord]): self.name = name self.status = status @@ -65,6 +67,7 @@ class ComplianceToolStateManager(logging.Handler): :ivar steps: List of :class:`Steps <.Step>` """ + def __init__(self): """ steps: List of steps. Each step consist of a step name, a step status and LogRecords belong to to this step. From bea02d4f6a33a7e8fad4d84cd13916f3658b282d Mon Sep 17 00:00:00 2001 From: Joshua Benning Date: Fri, 17 Jul 2026 15:29:14 +0200 Subject: [PATCH 10/17] Update ruff.toml All applied rules without violations --- ruff.toml | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/ruff.toml b/ruff.toml index 3e683e8ef..0b965ea2a 100644 --- a/ruff.toml +++ b/ruff.toml @@ -11,18 +11,11 @@ docstring-code-format = true select = [ "E", # pycodestyle errors "W", # pycodestyle warnings - "I", # isort - "N", # pep8-naming - "F4", # pyflakes: imports/__future__ - "F5", # pyflakes: format strings - "F7", # pyflakes: control flow (return/break/continue outside loop/func) - "F8", # pyflakes: undefined/unused names "RUF100", # unused noqa + "F5", # pyflakes: format strings "COM818", # enforce trailing comma - "B", # flake8-bugbear - "D204", "D211", "D201", "D202", "D300", # basic docstring formatting - "T20", # prevent native print() statements - "A", # prevent shadowing of python builtins + "F7", # pyflakes: control flow (return/break/continue outside loop/func) + "D204", "D211", "D201", "D300", # basic docstring formatting ] # TODO: Revisit these @@ -30,4 +23,18 @@ ignore = [ "F403", # star imports - high existing usage "F405", # may-be-undefined from star imports "N818", # Exception name should be named with Error suffix + "N", # pep8-naming + "B", # flake8-bugbear + "T20", # prevent native print() statements + "A", # prevent shadowing of python builtins + "FIX", # prevent the creation of T0DO / F1XME comments + # Checked, need manual work: + "I", # isort + "F4", # pyflakes: imports/__future__ + "F8", # pyflakes: undefined/unused names + "PIE", # small readability improvements + "PYI", # typing best practices + # Evaluate if we want to use: + "S", # security related precautions + "BLE", # prevent unspecified excepts ] From 60d3bc5393ab93f3ec0cca176cc9fa8fbd86ad68 Mon Sep 17 00:00:00 2001 From: Joshua Benning Date: Sat, 18 Jul 2026 13:15:06 +0200 Subject: [PATCH 11/17] Add import order (I) --- compliance_tool/aas_compliance_tool/cli.py | 13 ++++----- .../compliance_check_aasx.py | 12 ++++---- .../compliance_check_json.py | 9 +++--- .../compliance_check_xml.py | 7 +++-- .../aas_compliance_tool/state_manager.py | 5 ++-- compliance_tool/test/_test_helper.py | 7 ++--- .../test/test_aas_compliance_tool.py | 2 +- .../test/test_compliance_check_aasx.py | 4 +-- .../test/test_compliance_check_json.py | 4 +-- .../test/test_compliance_check_xml.py | 4 +-- .../test/test_compliance_tool_integration.py | 3 +- etc/scripts/check_python_versions_coincide.py | 6 ++-- .../check_python_versions_supported.py | 4 ++- ruff.toml | 2 +- sdk/basyx/aas/adapter/__init__.py | 5 ++-- sdk/basyx/aas/adapter/_generic.py | 2 +- sdk/basyx/aas/adapter/aasx.py | 9 +++--- sdk/basyx/aas/adapter/json/__init__.py | 14 +++++----- .../aas/adapter/json/json_deserialization.py | 27 +++++++++--------- .../aas/adapter/json/json_serialization.py | 11 ++++---- sdk/basyx/aas/adapter/xml/__init__.py | 16 +++++------ .../aas/adapter/xml/xml_deserialization.py | 21 +++++++------- .../aas/adapter/xml/xml_serialization.py | 6 ++-- sdk/basyx/aas/backend/couchdb.py | 15 +++++----- sdk/basyx/aas/backend/local_file.py | 8 +++--- sdk/basyx/aas/examples/data/__init__.py | 2 +- sdk/basyx/aas/examples/data/_helper.py | 13 ++++----- sdk/basyx/aas/examples/data/example_aas.py | 2 +- sdk/basyx/aas/examples/tutorial_aasx.py | 1 + .../aas/examples/tutorial_backend_couchdb.py | 2 +- .../aas/examples/tutorial_navigate_aas.py | 3 +- .../tutorial_serialization_deserialization.py | 4 +-- sdk/basyx/aas/examples/tutorial_storage.py | 3 +- sdk/basyx/aas/model/__init__.py | 6 ++-- sdk/basyx/aas/model/_string_constraints.py | 2 -- sdk/basyx/aas/model/aas.py | 4 +-- sdk/basyx/aas/model/base.py | 28 +++++++++---------- sdk/basyx/aas/model/concept.py | 2 +- sdk/basyx/aas/model/datatypes.py | 2 +- sdk/basyx/aas/model/provider.py | 13 ++++----- sdk/basyx/aas/model/submodel.py | 10 +++---- sdk/basyx/aas/util/identification.py | 2 +- sdk/docs/source/conf.py | 4 +-- sdk/test/_helper/setup_testdb.py | 4 +-- sdk/test/_helper/test_helpers.py | 4 +-- sdk/test/adapter/aasx/test_aasx.py | 2 +- .../adapter/json/test_json_deserialization.py | 3 +- .../adapter/json/test_json_serialization.py | 14 ++++------ ...test_json_serialization_deserialization.py | 10 +++---- sdk/test/adapter/test_load_directory.py | 5 ++-- .../adapter/xml/test_xml_deserialization.py | 6 ++-- .../adapter/xml/test_xml_serialization.py | 9 ++---- .../test_xml_serialization_deserialization.py | 11 ++++---- sdk/test/backend/test_couchdb.py | 3 +- sdk/test/backend/test_local_file.py | 2 -- sdk/test/examples/test_examples.py | 2 +- sdk/test/examples/test_helpers.py | 2 +- sdk/test/examples/test_tutorials.py | 3 +- sdk/test/model/test_base.py | 6 ++-- sdk/test/model/test_concept.py | 2 +- sdk/test/model/test_datatypes.py | 3 +- sdk/test/model/test_submodel.py | 2 +- sdk/test/util/test_identification.py | 2 +- server/app/interfaces/_string_constraints.py | 3 +- server/app/interfaces/base.py | 1 + server/app/interfaces/discovery.py | 4 +-- server/app/interfaces/registry.py | 6 ++-- server/app/interfaces/repository.py | 5 ++-- server/app/model/service_specification.py | 2 +- server/test/interfaces/test_repository.py | 3 +- .../test/interfaces/test_shells_asset_ids.py | 3 +- server/test/test_api_base_path.py | 2 +- 72 files changed, 220 insertions(+), 223 deletions(-) diff --git a/compliance_tool/aas_compliance_tool/cli.py b/compliance_tool/aas_compliance_tool/cli.py index 1c10bed91..7c957d556 100644 --- a/compliance_tool/aas_compliance_tool/cli.py +++ b/compliance_tool/aas_compliance_tool/cli.py @@ -12,18 +12,17 @@ """ import argparse import datetime - import logging import pyecma376_2 - from basyx.aas.adapter import aasx -from basyx.aas.adapter.xml import write_aas_xml_file -from aas_compliance_tool import compliance_check_xml as compliance_tool_xml, \ - compliance_check_json as compliance_tool_json, \ - compliance_check_aasx as compliance_tool_aasx from basyx.aas.adapter.json import write_aas_json_file -from basyx.aas.examples.data import create_example, create_example_aas_binding, TEST_PDF_FILE +from basyx.aas.adapter.xml import write_aas_xml_file +from basyx.aas.examples.data import TEST_PDF_FILE, create_example, create_example_aas_binding + +from aas_compliance_tool import compliance_check_aasx as compliance_tool_aasx +from aas_compliance_tool import compliance_check_json as compliance_tool_json +from aas_compliance_tool import compliance_check_xml as compliance_tool_xml from aas_compliance_tool.state_manager import ComplianceToolStateManager, Status diff --git a/compliance_tool/aas_compliance_tool/compliance_check_aasx.py b/compliance_tool/aas_compliance_tool/compliance_check_aasx.py index 82b25766d..30e6f5ad3 100644 --- a/compliance_tool/aas_compliance_tool/compliance_check_aasx.py +++ b/compliance_tool/aas_compliance_tool/compliance_check_aasx.py @@ -12,20 +12,20 @@ :class:`LogRecords ` """ import datetime +import io import logging from typing import Optional, Tuple, cast -import io -from lxml import etree # type: ignore import pyecma376_2 - -from aas_compliance_tool import compliance_check_json, compliance_check_xml from basyx.aas import model from basyx.aas.adapter import aasx -from basyx.aas.adapter.xml import xml_deserialization from basyx.aas.adapter.json import json_deserialization -from basyx.aas.examples.data import example_aas, create_example_aas_binding +from basyx.aas.adapter.xml import xml_deserialization +from basyx.aas.examples.data import create_example_aas_binding, example_aas from basyx.aas.examples.data._helper import AASDataChecker, DataChecker +from lxml import etree # type: ignore + +from aas_compliance_tool import compliance_check_json, compliance_check_xml from aas_compliance_tool.state_manager import ComplianceToolStateManager, Status diff --git a/compliance_tool/aas_compliance_tool/compliance_check_json.py b/compliance_tool/aas_compliance_tool/compliance_check_json.py index e50332ecc..3c654bcfc 100644 --- a/compliance_tool/aas_compliance_tool/compliance_check_json.py +++ b/compliance_tool/aas_compliance_tool/compliance_check_json.py @@ -11,15 +11,16 @@ :class:`~basyx.aas.compliance_tool.state_manager.ComplianceToolStateManager` by adding new steps and associated :class:`LogRecords ` """ -import os import json import logging -from typing import Optional, IO +import os +from typing import IO, Optional -from basyx.aas import (model) +from basyx.aas import model from basyx.aas.adapter.json import json_deserialization -from basyx.aas.examples.data import example_aas, create_example +from basyx.aas.examples.data import create_example, example_aas from basyx.aas.examples.data._helper import AASDataChecker + from aas_compliance_tool.state_manager import ComplianceToolStateManager, Status diff --git a/compliance_tool/aas_compliance_tool/compliance_check_xml.py b/compliance_tool/aas_compliance_tool/compliance_check_xml.py index eeb9924c6..78db1a184 100644 --- a/compliance_tool/aas_compliance_tool/compliance_check_xml.py +++ b/compliance_tool/aas_compliance_tool/compliance_check_xml.py @@ -11,15 +11,16 @@ :class:`~basyx.aas.compliance_tool.state_manager.ComplianceToolStateManager` by adding new steps and associated :class:`LogRecords ` """ -import os -from lxml import etree # type: ignore import logging +import os from typing import Optional from basyx.aas import model from basyx.aas.adapter.xml import xml_deserialization -from basyx.aas.examples.data import example_aas, create_example +from basyx.aas.examples.data import create_example, example_aas from basyx.aas.examples.data._helper import AASDataChecker +from lxml import etree # type: ignore + from aas_compliance_tool.state_manager import ComplianceToolStateManager, Status diff --git a/compliance_tool/aas_compliance_tool/state_manager.py b/compliance_tool/aas_compliance_tool/state_manager.py index a4f73c35b..fb291068a 100644 --- a/compliance_tool/aas_compliance_tool/state_manager.py +++ b/compliance_tool/aas_compliance_tool/state_manager.py @@ -8,10 +8,11 @@ This module defines a :class:`~.ComplianceToolStateManager` to store :class:`LogRecords ` for single steps in a compliance check of the compliance tool """ -import logging import enum +import logging import pprint -from typing import List, Dict +from typing import Dict, List + from basyx.aas.examples.data._helper import DataChecker diff --git a/compliance_tool/test/_test_helper.py b/compliance_tool/test/_test_helper.py index bfcac3fd4..60c378d30 100644 --- a/compliance_tool/test/_test_helper.py +++ b/compliance_tool/test/_test_helper.py @@ -1,11 +1,10 @@ -import io -from typing import Literal, Type, Optional import datetime +import io import logging +from typing import Literal, Optional, Type import pyecma376_2 - -from basyx.aas.examples.data import create_example_aas_binding, TEST_PDF_FILE +from basyx.aas.examples.data import TEST_PDF_FILE, create_example_aas_binding def create_example_aas_core_properties() -> pyecma376_2.OPCCoreProperties: diff --git a/compliance_tool/test/test_aas_compliance_tool.py b/compliance_tool/test/test_aas_compliance_tool.py index df314888d..886651ce4 100644 --- a/compliance_tool/test/test_aas_compliance_tool.py +++ b/compliance_tool/test/test_aas_compliance_tool.py @@ -12,7 +12,7 @@ import unittest from contextlib import redirect_stderr, redirect_stdout from io import StringIO -from unittest.mock import patch, ANY +from unittest.mock import ANY, patch from aas_compliance_tool.cli import main, parse_cli_arguments from basyx.aas import model diff --git a/compliance_tool/test/test_compliance_check_aasx.py b/compliance_tool/test/test_compliance_check_aasx.py index b6e3ffbeb..ac3fd7b64 100644 --- a/compliance_tool/test/test_compliance_check_aasx.py +++ b/compliance_tool/test/test_compliance_check_aasx.py @@ -7,12 +7,12 @@ import unittest from unittest import mock -from ._test_helper import create_example_aas_core_properties, create_read_into_mock from aas_compliance_tool import compliance_check_aasx as compliance_tool from aas_compliance_tool.state_manager import ComplianceToolStateManager, Status - from basyx.aas.examples.data._helper import CheckResult +from ._test_helper import create_example_aas_core_properties, create_read_into_mock + class ComplianceToolAASXTest(unittest.TestCase): diff --git a/compliance_tool/test/test_compliance_check_json.py b/compliance_tool/test/test_compliance_check_json.py index e889bc394..19ee5d2bc 100644 --- a/compliance_tool/test/test_compliance_check_json.py +++ b/compliance_tool/test/test_compliance_check_json.py @@ -7,12 +7,12 @@ import unittest from unittest import mock -from ._test_helper import create_mock_effect from aas_compliance_tool import compliance_check_json as compliance_tool from aas_compliance_tool.state_manager import ComplianceToolStateManager, Status - from basyx.aas.examples.data._helper import CheckResult +from ._test_helper import create_mock_effect + class ComplianceToolJsonTest(unittest.TestCase): diff --git a/compliance_tool/test/test_compliance_check_xml.py b/compliance_tool/test/test_compliance_check_xml.py index 0a85e330a..8a4a76a90 100644 --- a/compliance_tool/test/test_compliance_check_xml.py +++ b/compliance_tool/test/test_compliance_check_xml.py @@ -7,12 +7,12 @@ import unittest from unittest import mock -from ._test_helper import create_mock_effect from aas_compliance_tool import compliance_check_xml as compliance_tool from aas_compliance_tool.state_manager import ComplianceToolStateManager, Status - from basyx.aas.examples.data._helper import CheckResult +from ._test_helper import create_mock_effect + class ComplianceToolXmlTest(unittest.TestCase): diff --git a/compliance_tool/test/test_compliance_tool_integration.py b/compliance_tool/test/test_compliance_tool_integration.py index 512c312e8..d869168eb 100644 --- a/compliance_tool/test/test_compliance_tool_integration.py +++ b/compliance_tool/test/test_compliance_tool_integration.py @@ -15,14 +15,13 @@ from typing import cast from unittest.mock import patch +from aas_compliance_tool.cli import main from basyx.aas import model from basyx.aas.adapter import aasx from basyx.aas.adapter.json import write_aas_json_file from basyx.aas.adapter.xml import write_aas_xml_file from basyx.aas.examples.data.example_aas import create_full_example -from aas_compliance_tool.cli import main - class ComplianceToolIntegrationTest(unittest.TestCase): diff --git a/etc/scripts/check_python_versions_coincide.py b/etc/scripts/check_python_versions_coincide.py index 774b67db8..013860d30 100644 --- a/etc/scripts/check_python_versions_coincide.py +++ b/etc/scripts/check_python_versions_coincide.py @@ -2,10 +2,12 @@ This helper script checks if the Python versions defined in a `pyproject.toml` coincide with the given `min_version` and `max_version` and returns an error if they don't. """ -import re import argparse +import re import sys -from packaging.version import Version, InvalidVersion + +from packaging.version import InvalidVersion, Version + def main(pyproject_toml_path: str, min_version: str, max_version: str) -> None: # Load and check `requires-python` version from `pyproject.toml` diff --git a/etc/scripts/check_python_versions_supported.py b/etc/scripts/check_python_versions_supported.py index 5980102f6..93a49b8d5 100644 --- a/etc/scripts/check_python_versions_supported.py +++ b/etc/scripts/check_python_versions_supported.py @@ -4,9 +4,11 @@ """ import argparse import sys +from datetime import datetime + import requests from packaging.version import InvalidVersion -from datetime import datetime + def main(min_version: str, max_version: str) -> None: # Fetch supported Python versions and check min/max versions diff --git a/ruff.toml b/ruff.toml index 0b965ea2a..e6e8b95a6 100644 --- a/ruff.toml +++ b/ruff.toml @@ -16,6 +16,7 @@ select = [ "COM818", # enforce trailing comma "F7", # pyflakes: control flow (return/break/continue outside loop/func) "D204", "D211", "D201", "D300", # basic docstring formatting + "I", # isort ] # TODO: Revisit these @@ -29,7 +30,6 @@ ignore = [ "A", # prevent shadowing of python builtins "FIX", # prevent the creation of T0DO / F1XME comments # Checked, need manual work: - "I", # isort "F4", # pyflakes: imports/__future__ "F8", # pyflakes: undefined/unused names "PIE", # small readability improvements diff --git a/sdk/basyx/aas/adapter/__init__.py b/sdk/basyx/aas/adapter/__init__.py index 80fd05df2..7e0bb9bc9 100644 --- a/sdk/basyx/aas/adapter/__init__.py +++ b/sdk/basyx/aas/adapter/__init__.py @@ -8,12 +8,13 @@ * :ref:`aasx `: This package offers functions for reading and writing AASX-files. """ +from pathlib import Path +from typing import Union + from basyx.aas.adapter.aasx import AASXReader, DictSupplementaryFileContainer from basyx.aas.adapter.json import read_aas_json_file_into from basyx.aas.adapter.xml import read_aas_xml_file_into from basyx.aas.model.provider import DictIdentifiableStore -from pathlib import Path -from typing import Union def load_directory( diff --git a/sdk/basyx/aas/adapter/_generic.py b/sdk/basyx/aas/adapter/_generic.py index c2f333a7c..963a0df87 100644 --- a/sdk/basyx/aas/adapter/_generic.py +++ b/sdk/basyx/aas/adapter/_generic.py @@ -10,7 +10,7 @@ """ import os -from typing import BinaryIO, Dict, IO, Type, Union +from typing import IO, BinaryIO, Dict, Type, Union from basyx.aas import model diff --git a/sdk/basyx/aas/adapter/aasx.py b/sdk/basyx/aas/adapter/aasx.py index 67084f96b..df2b27727 100644 --- a/sdk/basyx/aas/adapter/aasx.py +++ b/sdk/basyx/aas/adapter/aasx.py @@ -28,13 +28,14 @@ import itertools import logging import os -from typing import Dict, Tuple, IO, Union, List, Set, Optional, Iterable, Iterator +from typing import IO, Dict, Iterable, Iterator, List, Optional, Set, Tuple, Union -from .xml import read_aas_xml_file, write_aas_xml_file -from .. import model -from .json import read_aas_json_file, write_aas_json_file import pyecma376_2 + +from .. import model from ..util import traversal +from .json import read_aas_json_file, write_aas_json_file +from .xml import read_aas_xml_file, write_aas_xml_file logger = logging.getLogger(__name__) diff --git a/sdk/basyx/aas/adapter/json/__init__.py b/sdk/basyx/aas/adapter/json/__init__.py index 3d2236431..30141555b 100644 --- a/sdk/basyx/aas/adapter/json/__init__.py +++ b/sdk/basyx/aas/adapter/json/__init__.py @@ -17,17 +17,17 @@ :class:`ObjectStore `. """ -from .json_serialization import ( - AASToJsonEncoder, - StrippedAASToJsonEncoder, - write_aas_json_file, - object_store_to_json, -) from .json_deserialization import ( AASFromJsonDecoder, StrictAASFromJsonDecoder, - StrippedAASFromJsonDecoder, StrictStrippedAASFromJsonDecoder, + StrippedAASFromJsonDecoder, read_aas_json_file, read_aas_json_file_into, ) +from .json_serialization import ( + AASToJsonEncoder, + StrippedAASToJsonEncoder, + object_store_to_json, + write_aas_json_file, +) diff --git a/sdk/basyx/aas/adapter/json/json_deserialization.py b/sdk/basyx/aas/adapter/json/json_deserialization.py index 8144f73a7..772c4edaa 100644 --- a/sdk/basyx/aas/adapter/json/json_deserialization.py +++ b/sdk/basyx/aas/adapter/json/json_deserialization.py @@ -36,37 +36,38 @@ import logging import pprint from typing import ( - Dict, + IO, + Any, Callable, ContextManager, - TypeVar, - Type, + Dict, + Iterable, List, - IO, Optional, Set, - get_args, Tuple, - Iterable, - Any, + Type, + TypeVar, + get_args, ) from basyx.aas import model + from .._generic import ( - MODELLING_KIND_INVERSE, ASSET_KIND_INVERSE, - KEY_TYPES_INVERSE, + DIRECTION_INVERSE, ENTITY_TYPES_INVERSE, IEC61360_DATA_TYPES_INVERSE, IEC61360_LEVEL_TYPES_INVERSE, + JSON_AAS_TOP_LEVEL_KEYS_TO_TYPES, KEY_TYPES_CLASSES_INVERSE, + KEY_TYPES_INVERSE, + MODELLING_KIND_INVERSE, + QUALIFIER_KIND_INVERSE, REFERENCE_TYPES_INVERSE, - DIRECTION_INVERSE, STATE_OF_EVENT_INVERSE, - QUALIFIER_KIND_INVERSE, - PathOrIO, Path, - JSON_AAS_TOP_LEVEL_KEYS_TO_TYPES, + PathOrIO, ) logger = logging.getLogger(__name__) diff --git a/sdk/basyx/aas/adapter/json/json_serialization.py b/sdk/basyx/aas/adapter/json/json_serialization.py index e1034d36b..fcc36ca24 100644 --- a/sdk/basyx/aas/adapter/json/json_serialization.py +++ b/sdk/basyx/aas/adapter/json/json_serialization.py @@ -31,21 +31,22 @@ import contextlib import inspect import io +import json from typing import ( + Callable, ContextManager, - List, Dict, + Iterable, + List, Optional, TextIO, + Tuple, Type, - Callable, get_args, - Iterable, - Tuple, ) -import json from basyx.aas import model + from .. import _generic from .._generic import JSON_AAS_TOP_LEVEL_KEYS_TO_TYPES diff --git a/sdk/basyx/aas/adapter/xml/__init__.py b/sdk/basyx/aas/adapter/xml/__init__.py index 15ce534bc..0f449e74d 100644 --- a/sdk/basyx/aas/adapter/xml/__init__.py +++ b/sdk/basyx/aas/adapter/xml/__init__.py @@ -10,19 +10,19 @@ :class:`ObjectStore ` from a given xml document. """ -from .xml_serialization import ( - object_store_to_xml_element, - write_aas_xml_file, - object_to_xml_element, - write_aas_xml_element, -) from .xml_deserialization import ( AASFromXmlDecoder, StrictAASFromXmlDecoder, - StrippedAASFromXmlDecoder, StrictStrippedAASFromXmlDecoder, + StrippedAASFromXmlDecoder, XMLConstructables, + read_aas_xml_element, read_aas_xml_file, read_aas_xml_file_into, - read_aas_xml_element, +) +from .xml_serialization import ( + object_store_to_xml_element, + object_to_xml_element, + write_aas_xml_element, + write_aas_xml_file, ) diff --git a/sdk/basyx/aas/adapter/xml/xml_deserialization.py b/sdk/basyx/aas/adapter/xml/xml_deserialization.py index 9579d9e9d..be614de86 100644 --- a/sdk/basyx/aas/adapter/xml/xml_deserialization.py +++ b/sdk/basyx/aas/adapter/xml/xml_deserialization.py @@ -41,27 +41,28 @@ and construct them if available, and so on. """ -from ... import model -from lxml import etree -import logging import base64 import enum - +import logging from typing import Any, Callable, Dict, Iterable, Optional, Set, Tuple, Type, TypeVar + +from lxml import etree + +from ... import model from .._generic import ( - XML_NS_MAP, - XML_NS_AAS, - MODELLING_KIND_INVERSE, ASSET_KIND_INVERSE, - KEY_TYPES_INVERSE, + DIRECTION_INVERSE, ENTITY_TYPES_INVERSE, IEC61360_DATA_TYPES_INVERSE, IEC61360_LEVEL_TYPES_INVERSE, KEY_TYPES_CLASSES_INVERSE, + KEY_TYPES_INVERSE, + MODELLING_KIND_INVERSE, + QUALIFIER_KIND_INVERSE, REFERENCE_TYPES_INVERSE, - DIRECTION_INVERSE, STATE_OF_EVENT_INVERSE, - QUALIFIER_KIND_INVERSE, + XML_NS_AAS, + XML_NS_MAP, PathOrIO, ) diff --git a/sdk/basyx/aas/adapter/xml/xml_serialization.py b/sdk/basyx/aas/adapter/xml/xml_serialization.py index f994ad1bb..451a31dd8 100644 --- a/sdk/basyx/aas/adapter/xml/xml_serialization.py +++ b/sdk/basyx/aas/adapter/xml/xml_serialization.py @@ -31,11 +31,13 @@ write_aas_xml_file(fp, object_store) """ -from lxml import etree -from typing import Callable, Dict, Optional, Type import base64 +from typing import Callable, Dict, Optional, Type + +from lxml import etree from basyx.aas import model + from .. import _generic NS_AAS = _generic.XML_NS_AAS diff --git a/sdk/basyx/aas/backend/couchdb.py b/sdk/basyx/aas/backend/couchdb.py index a3e383241..347afca31 100644 --- a/sdk/basyx/aas/backend/couchdb.py +++ b/sdk/basyx/aas/backend/couchdb.py @@ -12,20 +12,21 @@ CouchDB. """ +import json +import logging import threading -import warnings -import weakref -from typing import Dict, Any, Optional, Iterator, Iterable, Tuple, MutableMapping +import urllib.error import urllib.parse import urllib.request -import urllib.error -import logging -import json +import warnings +import weakref +from typing import Any, Dict, Iterable, Iterator, MutableMapping, Optional, Tuple + import urllib3 # type: ignore -from ..adapter.json import json_serialization, json_deserialization from basyx.aas import model +from ..adapter.json import json_deserialization, json_serialization logger = logging.getLogger(__name__) _http_pool_manager = urllib3.PoolManager() diff --git a/sdk/basyx/aas/backend/local_file.py b/sdk/basyx/aas/backend/local_file.py index e87c4aa78..e4060f682 100644 --- a/sdk/basyx/aas/backend/local_file.py +++ b/sdk/basyx/aas/backend/local_file.py @@ -12,19 +12,19 @@ the AAS objects in a specific Directory. """ -from typing import Iterator -import logging +import hashlib import json +import logging import os -import hashlib import tempfile import threading import warnings import weakref +from typing import Iterator -from ..adapter.json import json_serialization, json_deserialization from basyx.aas import model +from ..adapter.json import json_deserialization, json_serialization logger = logging.getLogger(__name__) diff --git a/sdk/basyx/aas/examples/data/__init__.py b/sdk/basyx/aas/examples/data/__init__.py index fa8a1cb40..9e0b71de9 100644 --- a/sdk/basyx/aas/examples/data/__init__.py +++ b/sdk/basyx/aas/examples/data/__init__.py @@ -22,9 +22,9 @@ from basyx.aas import model from basyx.aas.examples.data import ( - example_aas_missing_attributes, example_aas, example_aas_mandatory_attributes, + example_aas_missing_attributes, example_submodel_template, ) diff --git a/sdk/basyx/aas/examples/data/_helper.py b/sdk/basyx/aas/examples/data/_helper.py index 59c79dd3d..dead6d2a1 100644 --- a/sdk/basyx/aas/examples/data/_helper.py +++ b/sdk/basyx/aas/examples/data/_helper.py @@ -13,21 +13,20 @@ import pprint from typing import ( + Any, + Dict, + Iterable, + Iterator, List, NamedTuple, - Iterator, - Dict, - Any, - Type, - Union, Set, - Iterable, + Type, TypeVar, + Union, ) from ... import model - _LIST_OR_COLLECTION = TypeVar( "_LIST_OR_COLLECTION", model.SubmodelElementList, model.SubmodelElementCollection ) diff --git a/sdk/basyx/aas/examples/data/example_aas.py b/sdk/basyx/aas/examples/data/example_aas.py index ca8010328..8394df116 100644 --- a/sdk/basyx/aas/examples/data/example_aas.py +++ b/sdk/basyx/aas/examples/data/example_aas.py @@ -15,8 +15,8 @@ import datetime import logging -from ._helper import AASDataChecker from ... import model +from ._helper import AASDataChecker logger = logging.getLogger(__name__) diff --git a/sdk/basyx/aas/examples/tutorial_aasx.py b/sdk/basyx/aas/examples/tutorial_aasx.py index 3c28359a5..c319f63fc 100755 --- a/sdk/basyx/aas/examples/tutorial_aasx.py +++ b/sdk/basyx/aas/examples/tutorial_aasx.py @@ -15,6 +15,7 @@ from pathlib import Path # Used for easier handling of auxiliary file's local path import pyecma376_2 # The base library for Open Packaging Specifications. We will use the OPCCoreProperties class. + from basyx.aas import model from basyx.aas.adapter import aasx diff --git a/sdk/basyx/aas/examples/tutorial_backend_couchdb.py b/sdk/basyx/aas/examples/tutorial_backend_couchdb.py index a71b1e087..feed47754 100755 --- a/sdk/basyx/aas/examples/tutorial_backend_couchdb.py +++ b/sdk/basyx/aas/examples/tutorial_backend_couchdb.py @@ -9,8 +9,8 @@ from configparser import ConfigParser from pathlib import Path -import basyx.aas.examples.data.example_aas import basyx.aas.backend.couchdb +import basyx.aas.examples.data.example_aas # To execute this tutorial, you'll need a running CouchDB server, including an empty database and a user account with # access to that database. diff --git a/sdk/basyx/aas/examples/tutorial_navigate_aas.py b/sdk/basyx/aas/examples/tutorial_navigate_aas.py index c373eed16..88323bbff 100644 --- a/sdk/basyx/aas/examples/tutorial_navigate_aas.py +++ b/sdk/basyx/aas/examples/tutorial_navigate_aas.py @@ -5,9 +5,10 @@ Tutorial for navigating a Submodel's hierarchy using IdShorts and IdShortPaths. """ -from basyx.aas import model from typing import cast +from basyx.aas import model + # In this tutorial, you will learn how to create a Submodel with different kinds of SubmodelElements and how to navigate # through them using IdShorts and IdShortPaths. # diff --git a/sdk/basyx/aas/examples/tutorial_serialization_deserialization.py b/sdk/basyx/aas/examples/tutorial_serialization_deserialization.py index 7d724de07..88c5c1be7 100755 --- a/sdk/basyx/aas/examples/tutorial_serialization_deserialization.py +++ b/sdk/basyx/aas/examples/tutorial_serialization_deserialization.py @@ -8,9 +8,9 @@ import json -from basyx.aas import model -import basyx.aas.adapter.xml import basyx.aas.adapter.json +import basyx.aas.adapter.xml +from basyx.aas import model # 'Details of the Asset Administration Shell' specifies multiple official serialization formats for AAS data. In this # tutorial, we show how the Eclipse BaSyx Python library can be used to serialize AAS objects into JSON or XML and to diff --git a/sdk/basyx/aas/examples/tutorial_storage.py b/sdk/basyx/aas/examples/tutorial_storage.py index 1e067cb69..c9fecf233 100755 --- a/sdk/basyx/aas/examples/tutorial_storage.py +++ b/sdk/basyx/aas/examples/tutorial_storage.py @@ -19,8 +19,7 @@ # Step 4: using the IdentifiableStore to resolve a reference from basyx.aas import model -from basyx.aas.model import AssetInformation, AssetAdministrationShell, Submodel - +from basyx.aas.model import AssetAdministrationShell, AssetInformation, Submodel ###################################################################################### # Step 1: Creating AssetInformation, Submodel and Asset Administration Shell objects # diff --git a/sdk/basyx/aas/model/__init__.py b/sdk/basyx/aas/model/__init__.py index 893740935..3d24402f6 100644 --- a/sdk/basyx/aas/model/__init__.py +++ b/sdk/basyx/aas/model/__init__.py @@ -8,12 +8,12 @@ from basyx.aas.model import AssetAdministrationShell, Submodel, Property """ +from . import datatypes from .aas import * from .base import * -from .submodel import * -from .provider import * from .concept import ConceptDescription -from . import datatypes +from .provider import * +from .submodel import * # A mapping of BaSyx Python SDK implementation classes to the corresponding `KeyTypes` enum members for all classes # that are covered by this enum. diff --git a/sdk/basyx/aas/model/_string_constraints.py b/sdk/basyx/aas/model/_string_constraints.py index 60b8501fd..b4e8b4bae 100644 --- a/sdk/basyx/aas/model/_string_constraints.py +++ b/sdk/basyx/aas/model/_string_constraints.py @@ -27,10 +27,8 @@ """ import re - from typing import Callable, Optional, Type, TypeVar - _T = TypeVar("_T") AASD130_RE = re.compile("[\x09\x0a\x0d\x20-\ud7ff\ue000-\ufffd\U00010000-\U0010ffff]*") diff --git a/sdk/basyx/aas/model/aas.py b/sdk/basyx/aas/model/aas.py index 75a18dd5d..c1195df9b 100644 --- a/sdk/basyx/aas/model/aas.py +++ b/sdk/basyx/aas/model/aas.py @@ -9,9 +9,9 @@ AssetAdministrationShell. """ -from typing import Optional, Set, Iterable, List +from typing import Iterable, List, Optional, Set -from . import base, _string_constraints +from . import _string_constraints, base from .submodel import Submodel diff --git a/sdk/basyx/aas/model/base.py b/sdk/basyx/aas/model/base.py index 098e43dea..738fd1224 100644 --- a/sdk/basyx/aas/model/base.py +++ b/sdk/basyx/aas/model/base.py @@ -12,30 +12,30 @@ import abc import inspect import itertools +import re from enum import Enum, unique from typing import ( + TYPE_CHECKING, + Any, + Callable, + Dict, + Generic, + Iterable, + Iterator, List, + MutableMapping, + MutableSequence, + MutableSet, Optional, Set, + Tuple, + Type, TypeVar, - MutableSet, - Generic, - Iterable, - Dict, - Iterator, Union, overload, - MutableSequence, - Type, - Any, - TYPE_CHECKING, - Tuple, - Callable, - MutableMapping, ) -import re -from . import datatypes, _string_constraints +from . import _string_constraints, datatypes if TYPE_CHECKING: from . import provider diff --git a/sdk/basyx/aas/model/concept.py b/sdk/basyx/aas/model/concept.py index 3ae7427cc..f13534094 100644 --- a/sdk/basyx/aas/model/concept.py +++ b/sdk/basyx/aas/model/concept.py @@ -8,7 +8,7 @@ This module contains the class :class:`~.ConceptDescription` from the AAS metamodel. """ -from typing import Optional, Set, Iterable, List +from typing import Iterable, List, Optional, Set from . import base diff --git a/sdk/basyx/aas/model/datatypes.py b/sdk/basyx/aas/model/datatypes.py index ba067d47d..bb5fece9b 100644 --- a/sdk/basyx/aas/model/datatypes.py +++ b/sdk/basyx/aas/model/datatypes.py @@ -28,7 +28,7 @@ import datetime import decimal import re -from typing import Type, Union, Dict, Optional +from typing import Dict, Optional, Type, Union import dateutil.relativedelta diff --git a/sdk/basyx/aas/model/provider.py b/sdk/basyx/aas/model/provider.py index be4bca156..76e122e22 100644 --- a/sdk/basyx/aas/model/provider.py +++ b/sdk/basyx/aas/model/provider.py @@ -13,20 +13,19 @@ import abc import warnings from typing import ( - MutableSet, - Iterator, - Generic, - TypeVar, Dict, + Generic, + Iterable, + Iterator, List, + MutableSet, Optional, - Iterable, Set, Tuple, + TypeVar, ) -from .base import Identifier, Identifiable - +from .base import Identifiable, Identifier _KEY = TypeVar("_KEY") # Generic key type _VALUE = TypeVar("_VALUE") # Generic value type diff --git a/sdk/basyx/aas/model/submodel.py b/sdk/basyx/aas/model/submodel.py index f2d7cb1a4..390c95431 100644 --- a/sdk/basyx/aas/model/submodel.py +++ b/sdk/basyx/aas/model/submodel.py @@ -11,18 +11,18 @@ import abc import uuid from typing import ( - Optional, - Set, - Iterable, TYPE_CHECKING, + Generic, + Iterable, List, + Optional, + Set, Type, TypeVar, - Generic, Union, ) -from . import base, datatypes, _string_constraints +from . import _string_constraints, base, datatypes if TYPE_CHECKING: from . import aas diff --git a/sdk/basyx/aas/util/identification.py b/sdk/basyx/aas/util/identification.py index 60a4f0a3d..cc9c4eae4 100644 --- a/sdk/basyx/aas/util/identification.py +++ b/sdk/basyx/aas/util/identification.py @@ -17,7 +17,7 @@ import abc import re import uuid -from typing import Optional, Dict, Union +from typing import Dict, Optional, Union from .. import model diff --git a/sdk/docs/source/conf.py b/sdk/docs/source/conf.py index 5c4aedb5e..12fbc3059 100644 --- a/sdk/docs/source/conf.py +++ b/sdk/docs/source/conf.py @@ -10,15 +10,13 @@ # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # +import datetime import os import sys -import datetime - sys.path.insert(0, os.path.abspath("../..")) from basyx.aas import __version__ - # -- Project information ----------------------------------------------------- project = "Eclipse BaSyx Python SDK" diff --git a/sdk/test/_helper/setup_testdb.py b/sdk/test/_helper/setup_testdb.py index bb32fe915..e5f15d0ae 100755 --- a/sdk/test/_helper/setup_testdb.py +++ b/sdk/test/_helper/setup_testdb.py @@ -17,14 +17,14 @@ in CI), provide the ``--failsafe`` option. """ +import argparse import base64 import configparser -import argparse import json import os.path import sys -import urllib.request import urllib.error +import urllib.request # Parse test config (required to setup the CouchDB as expected) TEST_CONFIG = configparser.ConfigParser() diff --git a/sdk/test/_helper/test_helpers.py b/sdk/test/_helper/test_helpers.py index 96cae288f..7de0423aa 100644 --- a/sdk/test/_helper/test_helpers.py +++ b/sdk/test/_helper/test_helpers.py @@ -1,8 +1,8 @@ +import base64 import configparser import os.path -import urllib.request import urllib.error -import base64 +import urllib.request TEST_CONFIG = configparser.ConfigParser() TEST_CONFIG.read( diff --git a/sdk/test/adapter/aasx/test_aasx.py b/sdk/test/adapter/aasx/test_aasx.py index 1d2f35393..4a735d644 100644 --- a/sdk/test/adapter/aasx/test_aasx.py +++ b/sdk/test/adapter/aasx/test_aasx.py @@ -17,9 +17,9 @@ from basyx.aas import model from basyx.aas.adapter import aasx from basyx.aas.examples.data import ( + _helper, example_aas, example_aas_mandatory_attributes, - _helper, ) diff --git a/sdk/test/adapter/json/test_json_deserialization.py b/sdk/test/adapter/json/test_json_deserialization.py index fa337dcc9..3255e9d36 100644 --- a/sdk/test/adapter/json/test_json_deserialization.py +++ b/sdk/test/adapter/json/test_json_deserialization.py @@ -16,6 +16,8 @@ import json import logging import unittest + +from basyx.aas import model from basyx.aas.adapter.json import ( AASFromJsonDecoder, StrictAASFromJsonDecoder, @@ -23,7 +25,6 @@ read_aas_json_file, read_aas_json_file_into, ) -from basyx.aas import model class JsonDeserializationTest(unittest.TestCase): diff --git a/sdk/test/adapter/json/test_json_serialization.py b/sdk/test/adapter/json/test_json_serialization.py index 88b0a6ee1..a61917383 100644 --- a/sdk/test/adapter/json/test_json_serialization.py +++ b/sdk/test/adapter/json/test_json_serialization.py @@ -4,10 +4,11 @@ # the LICENSE file of this project. # # SPDX-License-Identifier: MIT -import os import io -import unittest import json +import os +import unittest +from typing import Set, Union from basyx.aas import model from basyx.aas.adapter.json import ( @@ -15,17 +16,14 @@ StrippedAASToJsonEncoder, write_aas_json_file, ) -from jsonschema import validate # type: ignore -from typing import Set, Union - from basyx.aas.examples.data import ( - example_aas_missing_attributes, + create_example, example_aas, example_aas_mandatory_attributes, + example_aas_missing_attributes, example_submodel_template, - create_example, ) - +from jsonschema import validate # type: ignore JSON_SCHEMA_FILE = os.path.join( os.path.dirname(__file__), "../schemas/aasJSONSchema.json" diff --git a/sdk/test/adapter/json/test_json_serialization_deserialization.py b/sdk/test/adapter/json/test_json_serialization_deserialization.py index 7f621427c..605d102d6 100644 --- a/sdk/test/adapter/json/test_json_serialization_deserialization.py +++ b/sdk/test/adapter/json/test_json_serialization_deserialization.py @@ -8,25 +8,23 @@ import io import json import unittest +from typing import IO, Iterable from basyx.aas import model from basyx.aas.adapter.json import ( AASToJsonEncoder, - write_aas_json_file, read_aas_json_file, + write_aas_json_file, ) - from basyx.aas.examples.data import ( - example_aas_missing_attributes, + create_example, example_aas, example_aas_mandatory_attributes, + example_aas_missing_attributes, example_submodel_template, - create_example, ) from basyx.aas.examples.data._helper import AASDataChecker -from typing import Iterable, IO - class JsonSerializationDeserializationTest(unittest.TestCase): def test_random_object_serialization_deserialization(self) -> None: diff --git a/sdk/test/adapter/test_load_directory.py b/sdk/test/adapter/test_load_directory.py index 3922d3ee6..908da658e 100644 --- a/sdk/test/adapter/test_load_directory.py +++ b/sdk/test/adapter/test_load_directory.py @@ -1,9 +1,8 @@ -import unittest import tempfile +import unittest from pathlib import Path -from basyx.aas import model -from basyx.aas import adapter +from basyx.aas import adapter, model class LoadDirectoryTest(unittest.TestCase): diff --git a/sdk/test/adapter/xml/test_xml_deserialization.py b/sdk/test/adapter/xml/test_xml_deserialization.py index 2ca04bbcd..511fcad21 100644 --- a/sdk/test/adapter/xml/test_xml_deserialization.py +++ b/sdk/test/adapter/xml/test_xml_deserialization.py @@ -8,19 +8,19 @@ import io import logging import unittest +from typing import Iterable, Type, Union from basyx.aas import model +from basyx.aas.adapter._generic import XML_NS_MAP from basyx.aas.adapter.xml import ( StrictAASFromXmlDecoder, XMLConstructables, + read_aas_xml_element, read_aas_xml_file, read_aas_xml_file_into, - read_aas_xml_element, ) from basyx.aas.adapter.xml.xml_deserialization import _tag_replace_namespace -from basyx.aas.adapter._generic import XML_NS_MAP from lxml import etree -from typing import Iterable, Type, Union def _xml_wrap(xml: str) -> str: diff --git a/sdk/test/adapter/xml/test_xml_serialization.py b/sdk/test/adapter/xml/test_xml_serialization.py index e2311d9ce..212154502 100644 --- a/sdk/test/adapter/xml/test_xml_serialization.py +++ b/sdk/test/adapter/xml/test_xml_serialization.py @@ -8,18 +8,15 @@ import os import unittest -from lxml import etree - from basyx.aas import model from basyx.aas.adapter.xml import write_aas_xml_file, xml_serialization - from basyx.aas.examples.data import ( - example_aas_missing_attributes, example_aas, - example_submodel_template, example_aas_mandatory_attributes, + example_aas_missing_attributes, + example_submodel_template, ) - +from lxml import etree XML_SCHEMA_FILE = os.path.join(os.path.dirname(__file__), "../schemas/aasXMLSchema.xsd") diff --git a/sdk/test/adapter/xml/test_xml_serialization_deserialization.py b/sdk/test/adapter/xml/test_xml_serialization_deserialization.py index 3b89a3ba2..aae0ce214 100644 --- a/sdk/test/adapter/xml/test_xml_serialization_deserialization.py +++ b/sdk/test/adapter/xml/test_xml_serialization_deserialization.py @@ -10,19 +10,18 @@ from basyx.aas import model from basyx.aas.adapter.xml import ( - write_aas_xml_file, + XMLConstructables, + read_aas_xml_element, read_aas_xml_file, write_aas_xml_element, - read_aas_xml_element, - XMLConstructables, + write_aas_xml_file, ) - from basyx.aas.examples.data import ( - example_aas_missing_attributes, + create_example, example_aas, example_aas_mandatory_attributes, + example_aas_missing_attributes, example_submodel_template, - create_example, ) from basyx.aas.examples.data._helper import AASDataChecker diff --git a/sdk/test/backend/test_couchdb.py b/sdk/test/backend/test_couchdb.py index 29913127d..871927575 100644 --- a/sdk/test/backend/test_couchdb.py +++ b/sdk/test/backend/test_couchdb.py @@ -11,8 +11,7 @@ from basyx.aas.backend import couchdb from basyx.aas.examples.data.example_aas import * -from test._helper.test_helpers import TEST_CONFIG, COUCHDB_OKAY, COUCHDB_ERROR - +from test._helper.test_helpers import COUCHDB_ERROR, COUCHDB_OKAY, TEST_CONFIG source_core: str = ( "couchdb://" diff --git a/sdk/test/backend/test_local_file.py b/sdk/test/backend/test_local_file.py index e13d20fab..1008bff4f 100644 --- a/sdk/test/backend/test_local_file.py +++ b/sdk/test/backend/test_local_file.py @@ -7,13 +7,11 @@ import gc import os.path import shutil - from unittest import TestCase from basyx.aas.backend import local_file from basyx.aas.examples.data.example_aas import * - store_path: str = os.path.dirname(__file__) + "/local_file_test_folder" source_core: str = "file://localhost/{}/".format(store_path) diff --git a/sdk/test/examples/test_examples.py b/sdk/test/examples/test_examples.py index d806e8aa1..106b52852 100644 --- a/sdk/test/examples/test_examples.py +++ b/sdk/test/examples/test_examples.py @@ -6,6 +6,7 @@ # SPDX-License-Identifier: MIT import unittest +from basyx.aas import model from basyx.aas.examples.data import ( example_aas, example_aas_mandatory_attributes, @@ -13,7 +14,6 @@ example_submodel_template, ) from basyx.aas.examples.data._helper import AASDataChecker -from basyx.aas import model class ExampleAASTest(unittest.TestCase): diff --git a/sdk/test/examples/test_helpers.py b/sdk/test/examples/test_helpers.py index 05b88026f..b5570df7f 100644 --- a/sdk/test/examples/test_helpers.py +++ b/sdk/test/examples/test_helpers.py @@ -6,8 +6,8 @@ # SPDX-License-Identifier: MIT import unittest -from basyx.aas.examples.data._helper import DataChecker, AASDataChecker from basyx.aas import model +from basyx.aas.examples.data._helper import AASDataChecker, DataChecker class DataCheckerTest(unittest.TestCase): diff --git a/sdk/test/examples/test_tutorials.py b/sdk/test/examples/test_tutorials.py index 356de44a4..6e5452e0b 100644 --- a/sdk/test/examples/test_tutorials.py +++ b/sdk/test/examples/test_tutorials.py @@ -16,7 +16,8 @@ from contextlib import contextmanager from basyx.aas import model -from .._helper.test_helpers import COUCHDB_OKAY, TEST_CONFIG, COUCHDB_ERROR + +from .._helper.test_helpers import COUCHDB_ERROR, COUCHDB_OKAY, TEST_CONFIG class TutorialTest(unittest.TestCase): diff --git a/sdk/test/model/test_base.py b/sdk/test/model/test_base.py index d4f887254..d52ab4f33 100644 --- a/sdk/test/model/test_base.py +++ b/sdk/test/model/test_base.py @@ -6,13 +6,13 @@ # SPDX-License-Identifier: MIT import unittest -from unittest import mock -from typing import Callable, Dict, Iterable, List, Optional, Type, TypeVar from collections import OrderedDict +from typing import Callable, Dict, Iterable, List, Optional, Type, TypeVar +from unittest import mock from basyx.aas import model -from basyx.aas.model import Identifier, Identifiable from basyx.aas.examples.data import example_aas +from basyx.aas.model import Identifiable, Identifier class KeyTest(unittest.TestCase): diff --git a/sdk/test/model/test_concept.py b/sdk/test/model/test_concept.py index 94fc497b8..b2905c7bf 100644 --- a/sdk/test/model/test_concept.py +++ b/sdk/test/model/test_concept.py @@ -6,8 +6,8 @@ # SPDX-License-Identifier: MIT import unittest -from basyx.aas.examples.data import example_aas from basyx.aas import model +from basyx.aas.examples.data import example_aas class ConceptDescriptionTest(unittest.TestCase): diff --git a/sdk/test/model/test_datatypes.py b/sdk/test/model/test_datatypes.py index a1fcba8aa..df5338333 100644 --- a/sdk/test/model/test_datatypes.py +++ b/sdk/test/model/test_datatypes.py @@ -4,13 +4,12 @@ # the LICENSE file of this project. # # SPDX-License-Identifier: MIT +import copy import datetime import math import unittest -import copy import dateutil - from basyx.aas import model diff --git a/sdk/test/model/test_submodel.py b/sdk/test/model/test_submodel.py index 79b99e522..e4cf9b021 100644 --- a/sdk/test/model/test_submodel.py +++ b/sdk/test/model/test_submodel.py @@ -6,8 +6,8 @@ # SPDX-License-Identifier: MIT import unittest -import dateutil.tz +import dateutil.tz from basyx.aas import model diff --git a/sdk/test/util/test_identification.py b/sdk/test/util/test_identification.py index d751e2623..6616d06fb 100644 --- a/sdk/test/util/test_identification.py +++ b/sdk/test/util/test_identification.py @@ -7,8 +7,8 @@ import unittest -from basyx.aas.util.identification import * from basyx.aas import model +from basyx.aas.util.identification import * class IdentifierGeneratorTest(unittest.TestCase): diff --git a/server/app/interfaces/_string_constraints.py b/server/app/interfaces/_string_constraints.py index 8489f4a03..c5f25c683 100644 --- a/server/app/interfaces/_string_constraints.py +++ b/server/app/interfaces/_string_constraints.py @@ -21,7 +21,8 @@ """ from typing import Callable, Type -from basyx.aas.model._string_constraints import check, constrain_attr, _T + +from basyx.aas.model._string_constraints import _T, check, constrain_attr def check_code_type(value: str, type_name: str = "CodeType") -> None: diff --git a/server/app/interfaces/base.py b/server/app/interfaces/base.py index c39ea0ce3..ac974cbad 100644 --- a/server/app/interfaces/base.py +++ b/server/app/interfaces/base.py @@ -29,6 +29,7 @@ from app.adapter import ServerAASToJsonEncoder, ServerStrictAASFromJsonDecoder, ServerStrictStrippedAASFromJsonDecoder from app.model import AssetAdministrationShellDescriptor, AssetLink, SubmodelDescriptor from app.util.converters import base64url_decode + from . import _string_constraints # The following string aliases are constrained by the decorator functions defined in the string_constraints module, diff --git a/server/app/interfaces/discovery.py b/server/app/interfaces/discovery.py index 3f290241d..2a60eb3e9 100644 --- a/server/app/interfaces/discovery.py +++ b/server/app/interfaces/discovery.py @@ -15,9 +15,9 @@ from app import model as server_model from app.adapter import jsonization -from app.interfaces.base import BaseWSGIApp, HTTPApiDecoder, APIResponse +from app.interfaces.base import APIResponse, BaseWSGIApp, HTTPApiDecoder +from app.model import ServiceDescription, ServiceSpecificationProfileEnum from app.util.converters import IdentifierToBase64URLConverter, base64url_decode -from app.model import ServiceSpecificationProfileEnum, ServiceDescription SUPPORTED_PROFILES: ServiceDescription = ServiceDescription( [ diff --git a/server/app/interfaces/registry.py b/server/app/interfaces/registry.py index eadca8753..f69e3a505 100644 --- a/server/app/interfaces/registry.py +++ b/server/app/interfaces/registry.py @@ -4,7 +4,7 @@ – Application Programming Interface'. """ -from typing import Dict, Iterator, Tuple, Type, Optional +from typing import Dict, Iterator, Optional, Tuple, Type import werkzeug.exceptions import werkzeug.routing @@ -16,8 +16,8 @@ from werkzeug.wrappers import Request, Response import app.model as server_model -from app.interfaces.base import APIResponse, HTTPApiDecoder, ObjectStoreWSGIApp, is_stripped_request, PagingMetadata -from app.model import DictDescriptorStore, ServiceSpecificationProfileEnum, ServiceDescription +from app.interfaces.base import APIResponse, HTTPApiDecoder, ObjectStoreWSGIApp, PagingMetadata, is_stripped_request +from app.model import DictDescriptorStore, ServiceDescription, ServiceSpecificationProfileEnum from app.util.converters import IdentifierToBase64URLConverter, base64url_decode SUPPORTED_PROFILES: ServiceDescription = ServiceDescription( diff --git a/server/app/interfaces/repository.py b/server/app/interfaces/repository.py index f54ea455c..8f931c786 100644 --- a/server/app/interfaces/repository.py +++ b/server/app/interfaces/repository.py @@ -23,9 +23,10 @@ from werkzeug.routing import MapAdapter, Rule, Submount from app.interfaces.base import PagingMetadata +from app.model import ServiceDescription, ServiceSpecificationProfileEnum from app.util.converters import IdentifierToBase64URLConverter, IdShortPathConverter, base64url_decode -from .base import ObjectStoreWSGIApp, APIResponse, is_stripped_request, HTTPApiDecoder, T -from app.model import ServiceSpecificationProfileEnum, ServiceDescription + +from .base import APIResponse, HTTPApiDecoder, ObjectStoreWSGIApp, T, is_stripped_request SUPPORTED_PROFILES: ServiceDescription = ServiceDescription( [ diff --git a/server/app/model/service_specification.py b/server/app/model/service_specification.py index 336525cf2..04a843170 100644 --- a/server/app/model/service_specification.py +++ b/server/app/model/service_specification.py @@ -1,5 +1,5 @@ -from typing import List import enum +from typing import List class ServiceSpecificationProfileEnum(str, enum.Enum): diff --git a/server/test/interfaces/test_repository.py b/server/test/interfaces/test_repository.py index 01f3bd61d..d60926132 100644 --- a/server/test/interfaces/test_repository.py +++ b/server/test/interfaces/test_repository.py @@ -32,12 +32,11 @@ import hypothesis.strategies import schemathesis +from app.interfaces.repository import WSGIApp from basyx.aas import model from basyx.aas.adapter.aasx import DictSupplementaryFileContainer from basyx.aas.examples.data.example_aas import create_full_example -from app.interfaces.repository import WSGIApp - def _encode_and_quote(identifier: model.Identifier) -> str: return urllib.parse.quote(urllib.parse.quote(identifier, safe=""), safe="") diff --git a/server/test/interfaces/test_shells_asset_ids.py b/server/test/interfaces/test_shells_asset_ids.py index da103807a..e45af1b90 100644 --- a/server/test/interfaces/test_shells_asset_ids.py +++ b/server/test/interfaces/test_shells_asset_ids.py @@ -9,13 +9,12 @@ import json import unittest +from app.interfaces.repository import WSGIApp from basyx.aas import model from basyx.aas.adapter.aasx import DictSupplementaryFileContainer from basyx.aas.examples.data.example_aas import create_full_example from werkzeug.test import Client -from app.interfaces.repository import WSGIApp - BASE_PATH = "/api/v3.1" diff --git a/server/test/test_api_base_path.py b/server/test/test_api_base_path.py index 4d0a88de2..acf1ceed8 100644 --- a/server/test/test_api_base_path.py +++ b/server/test/test_api_base_path.py @@ -4,9 +4,9 @@ # the LICENSE file of this project. # # SPDX-License-Identifier: MIT -import unittest import pathlib import re +import unittest SERVER_ROOT = pathlib.Path(__file__).resolve().parent.parent From bb0dbb0bed22ed8c130985a299853b26e446cbbd Mon Sep 17 00:00:00 2001 From: Joshua Benning Date: Sat, 18 Jul 2026 13:17:08 +0200 Subject: [PATCH 12/17] Update copyright years --- sdk/test/model/test_concept.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/test/model/test_concept.py b/sdk/test/model/test_concept.py index b2905c7bf..05979342f 100644 --- a/sdk/test/model/test_concept.py +++ b/sdk/test/model/test_concept.py @@ -1,4 +1,4 @@ -# Copyright (c) 2025 the Eclipse BaSyx Authors +# Copyright (c) 2026 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. From fcf7c1d8b1551b16fb1fdb5cb11ba082884c27a2 Mon Sep 17 00:00:00 2001 From: Joshua Benning Date: Sun, 19 Jul 2026 17:17:17 +0200 Subject: [PATCH 13/17] Add undefined/unused names (F8) --- ruff.toml | 2 +- .../aas/adapter/json/json_deserialization.py | 3 -- .../data/example_aas_missing_attributes.py | 30 ------------------- sdk/basyx/aas/model/base.py | 10 +++---- sdk/test/adapter/aasx/test_aasx.py | 4 +-- .../adapter/json/test_json_serialization.py | 4 +-- ...test_json_serialization_deserialization.py | 4 +-- .../adapter/xml/test_xml_serialization.py | 14 ++++----- sdk/test/model/test_base.py | 12 ++++---- server/app/adapter/jsonization.py | 1 - 10 files changed, 25 insertions(+), 59 deletions(-) diff --git a/ruff.toml b/ruff.toml index e6e8b95a6..88f10ac80 100644 --- a/ruff.toml +++ b/ruff.toml @@ -17,6 +17,7 @@ select = [ "F7", # pyflakes: control flow (return/break/continue outside loop/func) "D204", "D211", "D201", "D300", # basic docstring formatting "I", # isort + "F8", # pyflakes: undefined/unused names ] # TODO: Revisit these @@ -31,7 +32,6 @@ ignore = [ "FIX", # prevent the creation of T0DO / F1XME comments # Checked, need manual work: "F4", # pyflakes: imports/__future__ - "F8", # pyflakes: undefined/unused names "PIE", # small readability improvements "PYI", # typing best practices # Evaluate if we want to use: diff --git a/sdk/basyx/aas/adapter/json/json_deserialization.py b/sdk/basyx/aas/adapter/json/json_deserialization.py index 772c4edaa..573742a25 100644 --- a/sdk/basyx/aas/adapter/json/json_deserialization.py +++ b/sdk/basyx/aas/adapter/json/json_deserialization.py @@ -918,9 +918,6 @@ def _construct_blob( def _construct_file( cls, dct: Dict[str, object], object_class=model.File ) -> model.File: - content_type = ( - _get_ts(dct, "contentType", str) if "contentType" in dct else None - ) ret = object_class( id_short=None, value=None, diff --git a/sdk/basyx/aas/examples/data/example_aas_missing_attributes.py b/sdk/basyx/aas/examples/data/example_aas_missing_attributes.py index 251bf2937..feddc9373 100644 --- a/sdk/basyx/aas/examples/data/example_aas_missing_attributes.py +++ b/sdk/basyx/aas/examples/data/example_aas_missing_attributes.py @@ -292,36 +292,6 @@ def create_example_submodel() -> model.Submodel: ) ) - operation_variable_property = model.Property( - id_short="ExampleProperty", - value_type=model.datatypes.String, - value="exampleValue", - value_id=model.ExternalReference( - ( - model.Key( - type_=model.KeyTypes.GLOBAL_REFERENCE, - value="http://example.org/ValueId/ExampleValueId", - ), - ) - ), - display_name=model.MultiLanguageNameType( - {"en-US": "ExampleProperty", "de": "BeispielProperty"} - ), - category="CONSTANT", - description=model.MultiLanguageTextType( - {"en-US": "Example Property object", "de": "Beispiel Property Element"} - ), - parent=None, - semantic_id=model.ExternalReference( - ( - model.Key( - type_=model.KeyTypes.GLOBAL_REFERENCE, - value="http://example.org/Properties/ExampleProperty", - ), - ) - ), - qualifier=(), - ) input_variable_property = model.Property( id_short="ExamplePropertyInput", diff --git a/sdk/basyx/aas/model/base.py b/sdk/basyx/aas/model/base.py index 738fd1224..a7e4698ce 100644 --- a/sdk/basyx/aas/model/base.py +++ b/sdk/basyx/aas/model/base.py @@ -2292,7 +2292,7 @@ def _execute_item_add_hook(self, element: _NSO): if self._item_add_hook is not None: try: self._item_add_hook(element, self.__iter__()) - except Exception as e: + except Exception: self._execute_item_del_hook(element) raise @@ -2392,20 +2392,20 @@ def update_nss_from(self, other: "NamespaceSet"): referable.update_from(other_object) # type: ignore elif isinstance(other_object, Qualifier): backend, case_sensitive = self._backend["type"] - qualifier = backend[ + qualifier = backend[ # noqa: F841 qualifier currently unused other_object.type if case_sensitive else other_object.type.upper() ] - # qualifier.update_from(other_object) # TODO: What should happend here? + # qualifier.update_from(other_object) # TODO: What should happend here? Remove noqa when done elif isinstance(other_object, Extension): backend, case_sensitive = self._backend["name"] - extension = backend[ + extension = backend[ # noqa: F841 extension currently unused other_object.name if case_sensitive else other_object.name.upper() ] - # extension.update_from(other_object) # TODO: What should happend here? + # extension.update_from(other_object) # TODO: What should happend here? Remove noqa when done else: raise TypeError("Type not implemented") except KeyError: diff --git a/sdk/test/adapter/aasx/test_aasx.py b/sdk/test/adapter/aasx/test_aasx.py index 4a735d644..124355eb8 100644 --- a/sdk/test/adapter/aasx/test_aasx.py +++ b/sdk/test/adapter/aasx/test_aasx.py @@ -730,7 +730,7 @@ def test_only_referenced_submodels(self): fd, filename = tempfile.mkstemp(suffix=".aasx") os.close(fd) - with warnings.catch_warnings(record=True) as w: + with warnings.catch_warnings(record=True): with aasx.AASXWriter(filename) as writer: # write_aas only takes the AAS id and IdentifiableStore writer.write_aas( @@ -764,7 +764,7 @@ def test_only_referenced_submodels(self): fd, filename = tempfile.mkstemp(suffix=".aasx") os.close(fd) - with warnings.catch_warnings(record=True) as w: + with warnings.catch_warnings(record=True): with aasx.AASXWriter(filename) as writer: writer.write_all_aas_objects( part_name="/aasx/my_aas_part.xml", diff --git a/sdk/test/adapter/json/test_json_serialization.py b/sdk/test/adapter/json/test_json_serialization.py index a61917383..e53cf6041 100644 --- a/sdk/test/adapter/json/test_json_serialization.py +++ b/sdk/test/adapter/json/test_json_serialization.py @@ -40,7 +40,7 @@ def test_serialize_object(self) -> None: {"en-US": "Germany", "de": "Deutschland"} ), ) - json_data = json.dumps(test_object, cls=AASToJsonEncoder) + json.dumps(test_object, cls=AASToJsonEncoder) def test_random_object_serialization(self) -> None: aas_identifier = "AAS1" @@ -65,7 +65,7 @@ def test_random_object_serialization(self) -> None: }, cls=AASToJsonEncoder, ) - json_data_new = json.loads(json_data) + json.loads(json_data) class JsonSerializationSchemaTest(unittest.TestCase): diff --git a/sdk/test/adapter/json/test_json_serialization_deserialization.py b/sdk/test/adapter/json/test_json_serialization_deserialization.py index 605d102d6..5e815ad92 100644 --- a/sdk/test/adapter/json/test_json_serialization_deserialization.py +++ b/sdk/test/adapter/json/test_json_serialization_deserialization.py @@ -50,10 +50,10 @@ def test_random_object_serialization_deserialization(self) -> None: }, cls=AASToJsonEncoder, ) - json_data_new = json.loads(json_data) + json.loads(json_data) # try deserializing the json string into a DictIdentifiableStore of AAS objects with help of the json module - json_identifiable_store = read_aas_json_file( + read_aas_json_file( io.StringIO(json_data), failsafe=False ) diff --git a/sdk/test/adapter/xml/test_xml_serialization.py b/sdk/test/adapter/xml/test_xml_serialization.py index 212154502..625cd9943 100644 --- a/sdk/test/adapter/xml/test_xml_serialization.py +++ b/sdk/test/adapter/xml/test_xml_serialization.py @@ -31,7 +31,7 @@ def test_serialize_object(self) -> None: {"en-US": "Germany", "de": "Deutschland"} ), ) - xml_data = xml_serialization.property_to_xml( + xml_serialization.property_to_xml( test_object, xml_serialization.NS_AAS + "test_object" ) # todo: is this a correct way to test it? @@ -105,7 +105,7 @@ def test_random_object_serialization(self) -> None: # validate serialization against schema parser = etree.XMLParser(schema=aas_schema) test_file.seek(0) - root = etree.parse(test_file, parser=parser) + etree.parse(test_file, parser=parser) def test_full_example_serialization(self) -> None: data = example_aas.create_full_example() @@ -118,7 +118,7 @@ def test_full_example_serialization(self) -> None: # validate serialization against schema parser = etree.XMLParser(schema=aas_schema) file.seek(0) - root = etree.parse(file, parser=parser) + etree.parse(file, parser=parser) def test_submodel_template_serialization(self) -> None: data: model.DictIdentifiableStore[model.Identifiable] = ( @@ -134,7 +134,7 @@ def test_submodel_template_serialization(self) -> None: # validate serialization against schema parser = etree.XMLParser(schema=aas_schema) file.seek(0) - root = etree.parse(file, parser=parser) + etree.parse(file, parser=parser) def test_full_empty_example_serialization(self) -> None: data = example_aas_mandatory_attributes.create_full_example() @@ -147,7 +147,7 @@ def test_full_empty_example_serialization(self) -> None: # validate serialization against schema parser = etree.XMLParser(schema=aas_schema) file.seek(0) - root = etree.parse(file, parser=parser) + etree.parse(file, parser=parser) def test_missing_serialization(self) -> None: data = example_aas_missing_attributes.create_full_example() @@ -160,7 +160,7 @@ def test_missing_serialization(self) -> None: # validate serialization against schema parser = etree.XMLParser(schema=aas_schema) file.seek(0) - root = etree.parse(file, parser=parser) + etree.parse(file, parser=parser) def test_concept_description(self) -> None: data: model.DictIdentifiableStore[model.Identifiable] = ( @@ -176,4 +176,4 @@ def test_concept_description(self) -> None: # validate serialization against schema parser = etree.XMLParser(schema=aas_schema) file.seek(0) - root = etree.parse(file, parser=parser) + etree.parse(file, parser=parser) diff --git a/sdk/test/model/test_base.py b/sdk/test/model/test_base.py index d52ab4f33..2506c2208 100644 --- a/sdk/test/model/test_base.py +++ b/sdk/test/model/test_base.py @@ -100,7 +100,7 @@ def generate_example_referable_with_namespace( referable = ExampleReferableWithNamespace() referable.id_short = id_short if child: - namespace_set = model.NamespaceSet( + model.NamespaceSet( parent=referable, attribute_names=[("id_short", True)], items=[child] ) return referable @@ -115,7 +115,7 @@ def generate_example_referable_with_namespace( example_parent = generate_example_referable_with_namespace( "exampleParent", example_referable ) - example_grandparent = generate_example_referable_with_namespace( + generate_example_referable_with_namespace( "exampleGrandparent", example_parent ) @@ -698,7 +698,7 @@ def add_hook_constraint(_new: T, _existing: Iterable[T]) -> None: def test_Namespace(self) -> None: with self.assertRaises(model.AASConstraintViolation) as cm: - namespace_test = ExampleNamespaceReferable( + ExampleNamespaceReferable( [self.prop1, self.prop2, self.prop1alt] ) self.assertEqual( @@ -1324,7 +1324,7 @@ def get_item(self, identifier: Identifier) -> Identifiable: self.assertIs(submodel, cm_6.exception.value) with self.assertRaises(ValueError) as cm_7: - ref7 = model.ModelReference((), model.Submodel) + model.ModelReference((), model.Submodel) self.assertEqual("A reference must have at least one key!", str(cm_7.exception)) ref8 = model.ModelReference( @@ -1401,7 +1401,7 @@ def test_from_referable(self) -> None: # Test exception for element without identifiable ancestor submodel.submodel_element.remove(collection) with self.assertRaises(ValueError) as cm: - ref3 = model.ModelReference.from_referable(prop) + model.ModelReference.from_referable(prop) self.assertEqual( "The given Referable object is not embedded within an Identifiable object", str(cm.exception).split(":")[0], @@ -1429,7 +1429,7 @@ def __init__(self, id_: model.Identifier): class AdministrativeInformationTest(unittest.TestCase): def test_setting_version_revision(self) -> None: with self.assertRaises(model.AASConstraintViolation) as cm: - obj = model.AdministrativeInformation(revision="9") + model.AdministrativeInformation(revision="9") self.assertEqual( "A revision requires a version. This means, if there is no version there is no " "revision neither. Please set version first. (Constraint AASd-005)", diff --git a/server/app/adapter/jsonization.py b/server/app/adapter/jsonization.py index 8cb6da1bf..f667a3ade 100644 --- a/server/app/adapter/jsonization.py +++ b/server/app/adapter/jsonization.py @@ -64,7 +64,6 @@ def _construct_asset_administration_shell_descriptor( ret.asset_kind = ASSET_KIND_INVERSE[_get_ts(dct, "assetKind", str)] if "assetType" in dct: ret.asset_type = _get_ts(dct, "assetType", str) - global_asset_id = None if "globalAssetId" in dct: ret.global_asset_id = _get_ts(dct, "globalAssetId", str) specific_asset_id = set() From 371469d8140c5874f9c95ce5e4a3566ab3c8233e Mon Sep 17 00:00:00 2001 From: Joshua Benning Date: Sun, 19 Jul 2026 17:39:33 +0200 Subject: [PATCH 14/17] Add unused / bad practice imports (F4) --- .../aas_compliance_tool/compliance_check_aasx.py | 3 --- .../aas_compliance_tool/compliance_check_json.py | 4 +--- .../aas_compliance_tool/compliance_check_xml.py | 2 -- compliance_tool/test/test_aas_compliance_tool.py | 1 - ruff.toml | 2 +- sdk/basyx/aas/adapter/json/__init__.py | 13 +++++++++++++ sdk/basyx/aas/adapter/json/json_serialization.py | 1 - sdk/basyx/aas/adapter/xml/__init__.py | 15 +++++++++++++++ sdk/basyx/aas/adapter/xml/xml_serialization.py | 2 +- sdk/basyx/aas/model/__init__.py | 3 ++- sdk/basyx/aas/model/base.py | 2 -- sdk/basyx/aas/model/submodel.py | 1 - sdk/test/adapter/aasx/test_aasx.py | 1 - sdk/test/backend/test_couchdb.py | 1 - sdk/test/examples/test_tutorials.py | 8 +++----- sdk/test/model/test_base.py | 2 -- 16 files changed, 36 insertions(+), 25 deletions(-) diff --git a/compliance_tool/aas_compliance_tool/compliance_check_aasx.py b/compliance_tool/aas_compliance_tool/compliance_check_aasx.py index 30e6f5ad3..3fcedc913 100644 --- a/compliance_tool/aas_compliance_tool/compliance_check_aasx.py +++ b/compliance_tool/aas_compliance_tool/compliance_check_aasx.py @@ -12,7 +12,6 @@ :class:`LogRecords ` """ import datetime -import io import logging from typing import Optional, Tuple, cast @@ -23,9 +22,7 @@ from basyx.aas.adapter.xml import xml_deserialization from basyx.aas.examples.data import create_example_aas_binding, example_aas from basyx.aas.examples.data._helper import AASDataChecker, DataChecker -from lxml import etree # type: ignore -from aas_compliance_tool import compliance_check_json, compliance_check_xml from aas_compliance_tool.state_manager import ComplianceToolStateManager, Status diff --git a/compliance_tool/aas_compliance_tool/compliance_check_json.py b/compliance_tool/aas_compliance_tool/compliance_check_json.py index 3c654bcfc..610a341ff 100644 --- a/compliance_tool/aas_compliance_tool/compliance_check_json.py +++ b/compliance_tool/aas_compliance_tool/compliance_check_json.py @@ -11,10 +11,8 @@ :class:`~basyx.aas.compliance_tool.state_manager.ComplianceToolStateManager` by adding new steps and associated :class:`LogRecords ` """ -import json import logging -import os -from typing import IO, Optional +from typing import Optional from basyx.aas import model from basyx.aas.adapter.json import json_deserialization diff --git a/compliance_tool/aas_compliance_tool/compliance_check_xml.py b/compliance_tool/aas_compliance_tool/compliance_check_xml.py index 78db1a184..c5d550676 100644 --- a/compliance_tool/aas_compliance_tool/compliance_check_xml.py +++ b/compliance_tool/aas_compliance_tool/compliance_check_xml.py @@ -12,14 +12,12 @@ :class:`LogRecords ` """ import logging -import os from typing import Optional from basyx.aas import model from basyx.aas.adapter.xml import xml_deserialization from basyx.aas.examples.data import create_example, example_aas from basyx.aas.examples.data._helper import AASDataChecker -from lxml import etree # type: ignore from aas_compliance_tool.state_manager import ComplianceToolStateManager, Status diff --git a/compliance_tool/test/test_aas_compliance_tool.py b/compliance_tool/test/test_aas_compliance_tool.py index 886651ce4..c7f9147e5 100644 --- a/compliance_tool/test/test_aas_compliance_tool.py +++ b/compliance_tool/test/test_aas_compliance_tool.py @@ -7,7 +7,6 @@ import datetime import hashlib import io -import os import tempfile import unittest from contextlib import redirect_stderr, redirect_stdout diff --git a/ruff.toml b/ruff.toml index 88f10ac80..f6659f30e 100644 --- a/ruff.toml +++ b/ruff.toml @@ -18,6 +18,7 @@ select = [ "D204", "D211", "D201", "D300", # basic docstring formatting "I", # isort "F8", # pyflakes: undefined/unused names + "F4", # pyflakes: imports/__future__ ] # TODO: Revisit these @@ -31,7 +32,6 @@ ignore = [ "A", # prevent shadowing of python builtins "FIX", # prevent the creation of T0DO / F1XME comments # Checked, need manual work: - "F4", # pyflakes: imports/__future__ "PIE", # small readability improvements "PYI", # typing best practices # Evaluate if we want to use: diff --git a/sdk/basyx/aas/adapter/json/__init__.py b/sdk/basyx/aas/adapter/json/__init__.py index 30141555b..d2431c211 100644 --- a/sdk/basyx/aas/adapter/json/__init__.py +++ b/sdk/basyx/aas/adapter/json/__init__.py @@ -31,3 +31,16 @@ object_store_to_json, write_aas_json_file, ) + +__all__ = [ + "AASFromJsonDecoder", + "StrictAASFromJsonDecoder", + "StrictStrippedAASFromJsonDecoder", + "StrippedAASFromJsonDecoder", + "read_aas_json_file", + "read_aas_json_file_into", + "AASToJsonEncoder", + "StrippedAASToJsonEncoder", + "object_store_to_json", + "write_aas_json_file", +] diff --git a/sdk/basyx/aas/adapter/json/json_serialization.py b/sdk/basyx/aas/adapter/json/json_serialization.py index fcc36ca24..0817f2bac 100644 --- a/sdk/basyx/aas/adapter/json/json_serialization.py +++ b/sdk/basyx/aas/adapter/json/json_serialization.py @@ -29,7 +29,6 @@ import base64 import contextlib -import inspect import io import json from typing import ( diff --git a/sdk/basyx/aas/adapter/xml/__init__.py b/sdk/basyx/aas/adapter/xml/__init__.py index 0f449e74d..75a750238 100644 --- a/sdk/basyx/aas/adapter/xml/__init__.py +++ b/sdk/basyx/aas/adapter/xml/__init__.py @@ -26,3 +26,18 @@ write_aas_xml_element, write_aas_xml_file, ) + +__all__ = [ + "AASFromXmlDecoder", + "StrictAASFromXmlDecoder", + "StrictStrippedAASFromXmlDecoder", + "StrippedAASFromXmlDecoder", + "XMLConstructables", + "read_aas_xml_element", + "read_aas_xml_file", + "read_aas_xml_file_into", + "object_store_to_xml_element", + "object_to_xml_element", + "write_aas_xml_element", + "write_aas_xml_file", +] diff --git a/sdk/basyx/aas/adapter/xml/xml_serialization.py b/sdk/basyx/aas/adapter/xml/xml_serialization.py index 451a31dd8..8570add66 100644 --- a/sdk/basyx/aas/adapter/xml/xml_serialization.py +++ b/sdk/basyx/aas/adapter/xml/xml_serialization.py @@ -32,7 +32,7 @@ """ import base64 -from typing import Callable, Dict, Optional, Type +from typing import Dict, Optional, Type from lxml import etree diff --git a/sdk/basyx/aas/model/__init__.py b/sdk/basyx/aas/model/__init__.py index 3d24402f6..c87a5cd34 100644 --- a/sdk/basyx/aas/model/__init__.py +++ b/sdk/basyx/aas/model/__init__.py @@ -8,7 +8,8 @@ from basyx.aas.model import AssetAdministrationShell, Submodel, Property """ -from . import datatypes +import inspect + from .aas import * from .base import * from .concept import ConceptDescription diff --git a/sdk/basyx/aas/model/base.py b/sdk/basyx/aas/model/base.py index a7e4698ce..84650fe17 100644 --- a/sdk/basyx/aas/model/base.py +++ b/sdk/basyx/aas/model/base.py @@ -10,7 +10,6 @@ """ import abc -import inspect import itertools import re from enum import Enum, unique @@ -927,7 +926,6 @@ def _set_id_short(self, id_short: Optional[NameType]): f"id_short of {self!r} cannot be unset, since it is already " f"contained in {self.parent!r}", ) - from .submodel import SubmodelElementList for set_ in self.parent.namespace_element_sets: if set_.contains_id("id_short", id_short): diff --git a/sdk/basyx/aas/model/submodel.py b/sdk/basyx/aas/model/submodel.py index 390c95431..c52551532 100644 --- a/sdk/basyx/aas/model/submodel.py +++ b/sdk/basyx/aas/model/submodel.py @@ -16,7 +16,6 @@ Iterable, List, Optional, - Set, Type, TypeVar, Union, diff --git a/sdk/test/adapter/aasx/test_aasx.py b/sdk/test/adapter/aasx/test_aasx.py index 124355eb8..8560513a9 100644 --- a/sdk/test/adapter/aasx/test_aasx.py +++ b/sdk/test/adapter/aasx/test_aasx.py @@ -19,7 +19,6 @@ from basyx.aas.examples.data import ( _helper, example_aas, - example_aas_mandatory_attributes, ) diff --git a/sdk/test/backend/test_couchdb.py b/sdk/test/backend/test_couchdb.py index 871927575..829ccb889 100644 --- a/sdk/test/backend/test_couchdb.py +++ b/sdk/test/backend/test_couchdb.py @@ -6,7 +6,6 @@ # SPDX-License-Identifier: MIT import unittest import unittest.mock -import urllib.error from basyx.aas.backend import couchdb from basyx.aas.examples.data.example_aas import * diff --git a/sdk/test/examples/test_tutorials.py b/sdk/test/examples/test_tutorials.py index 6e5452e0b..e1aed7921 100644 --- a/sdk/test/examples/test_tutorials.py +++ b/sdk/test/examples/test_tutorials.py @@ -32,7 +32,7 @@ def test_tutorial_create_simple_aas(self): next(iter(tutorial_create_simple_aas.aas.submodel)).resolve(store) def test_tutorial_storage(self): - from basyx.aas.examples import tutorial_storage + pass # The tutorial already includes assert statements for the relevant points. So no further checks are required. @unittest.skipUnless( @@ -44,24 +44,22 @@ def test_tutorial_storage(self): ), ) def test_tutorial_backend_couchdb(self): - from basyx.aas.examples import tutorial_backend_couchdb + pass def test_tutorial_serialization_deserialization_json(self): with temporary_workingdirectory(): - from basyx.aas.examples import tutorial_serialization_deserialization pass # The tutorial already includes assert statements for the relevant points. So no further checks are required. def test_tutorial_aasx(self): with temporary_workingdirectory(): - from basyx.aas.examples import tutorial_aasx pass # The tutorial already includes assert statements for the relevant points. So no further checks are required. def test_tutorial_navigate_aas(self): - from basyx.aas.examples import tutorial_navigate_aas + pass # The tutorial already includes assert statements for the relevant points. So no further checks are required diff --git a/sdk/test/model/test_base.py b/sdk/test/model/test_base.py index 2506c2208..2e2e75ca0 100644 --- a/sdk/test/model/test_base.py +++ b/sdk/test/model/test_base.py @@ -6,9 +6,7 @@ # SPDX-License-Identifier: MIT import unittest -from collections import OrderedDict from typing import Callable, Dict, Iterable, List, Optional, Type, TypeVar -from unittest import mock from basyx.aas import model from basyx.aas.examples.data import example_aas From ee68f24346e224276b8079be9d7bdabad441b5c2 Mon Sep 17 00:00:00 2001 From: Joshua Benning Date: Sun, 19 Jul 2026 20:33:58 +0200 Subject: [PATCH 15/17] Add readability improvements (PIE) --- ruff.toml | 2 +- sdk/basyx/aas/adapter/aasx.py | 14 +++++++------- sdk/basyx/aas/adapter/json/json_deserialization.py | 1 - sdk/basyx/aas/adapter/xml/xml_deserialization.py | 1 - sdk/basyx/aas/backend/couchdb.py | 3 --- sdk/basyx/aas/model/datatypes.py | 1 - sdk/basyx/aas/model/provider.py | 1 - sdk/basyx/aas/util/identification.py | 3 +-- server/app/adapter/jsonization.py | 1 - 9 files changed, 9 insertions(+), 18 deletions(-) diff --git a/ruff.toml b/ruff.toml index f6659f30e..7f90d830d 100644 --- a/ruff.toml +++ b/ruff.toml @@ -19,6 +19,7 @@ select = [ "I", # isort "F8", # pyflakes: undefined/unused names "F4", # pyflakes: imports/__future__ + "PIE", # small readability improvements ] # TODO: Revisit these @@ -32,7 +33,6 @@ ignore = [ "A", # prevent shadowing of python builtins "FIX", # prevent the creation of T0DO / F1XME comments # Checked, need manual work: - "PIE", # small readability improvements "PYI", # typing best practices # Evaluate if we want to use: "S", # security related precautions diff --git a/sdk/basyx/aas/adapter/aasx.py b/sdk/basyx/aas/adapter/aasx.py index df2b27727..bbbfac955 100644 --- a/sdk/basyx/aas/adapter/aasx.py +++ b/sdk/basyx/aas/adapter/aasx.py @@ -958,7 +958,7 @@ def add_file(self, name: str, file: IO[bytes], content_type: str) -> str: :return: The file name as stored in the SupplementaryFileContainer. Typically, ``name`` or a modified version of ``name`` to resolve conflicts. """ - pass # pragma: no cover + # pragma: no cover @abc.abstractmethod def get_content_type(self, name: str) -> str: @@ -969,7 +969,7 @@ def get_content_type(self, name: str) -> str: :return: The file's content_type :raises KeyError: If no file with this name is stored """ - pass # pragma: no cover + # pragma: no cover @abc.abstractmethod def get_sha256(self, name: str) -> bytes: @@ -982,7 +982,7 @@ def get_sha256(self, name: str) -> bytes: :return: The file content's sha256 hash sum :raises KeyError: If no file with this name is stored """ - pass # pragma: no cover + # pragma: no cover @abc.abstractmethod def write_file(self, name: str, file: IO[bytes]) -> None: @@ -993,28 +993,28 @@ def write_file(self, name: str, file: IO[bytes]) -> None: :param file: A binary file-like object with write() method to write the file contents into :raises KeyError: If no file with this name is stored """ - pass # pragma: no cover + # pragma: no cover @abc.abstractmethod def delete_file(self, name: str) -> None: """ Deletes a file from this SupplementaryFileContainer given its name. """ - pass # pragma: no cover + # pragma: no cover @abc.abstractmethod def __contains__(self, item: str) -> bool: """ Check if a file with the given name is stored in this SupplementaryFileContainer. """ - pass # pragma: no cover + # pragma: no cover @abc.abstractmethod def __iter__(self) -> Iterator[str]: """ Return an iterator over all file names stored in this SupplementaryFileContainer. """ - pass # pragma: no cover + # pragma: no cover class DictSupplementaryFileContainer(AbstractSupplementaryFileContainer): diff --git a/sdk/basyx/aas/adapter/json/json_deserialization.py b/sdk/basyx/aas/adapter/json/json_deserialization.py index 573742a25..33fd1a88a 100644 --- a/sdk/basyx/aas/adapter/json/json_deserialization.py +++ b/sdk/basyx/aas/adapter/json/json_deserialization.py @@ -1024,7 +1024,6 @@ class StrictStrippedAASFromJsonDecoder( Non-failsafe decoder for stripped JSON objects. """ - pass def _select_decoder( diff --git a/sdk/basyx/aas/adapter/xml/xml_deserialization.py b/sdk/basyx/aas/adapter/xml/xml_deserialization.py index be614de86..a5ddab66f 100644 --- a/sdk/basyx/aas/adapter/xml/xml_deserialization.py +++ b/sdk/basyx/aas/adapter/xml/xml_deserialization.py @@ -1639,7 +1639,6 @@ class StrictStrippedAASFromXmlDecoder( Non-failsafe decoder for stripped XML elements. """ - pass def _parse_xml_document( diff --git a/sdk/basyx/aas/backend/couchdb.py b/sdk/basyx/aas/backend/couchdb.py index 347afca31..d604f4a9e 100644 --- a/sdk/basyx/aas/backend/couchdb.py +++ b/sdk/basyx/aas/backend/couchdb.py @@ -583,14 +583,12 @@ class CouchDBError(Exception): class CouchDBConnectionError(CouchDBError): """Exception raised when the CouchDB server could not be reached""" - pass class CouchDBResponseError(CouchDBError): """Exception raised by when an HTTP of the CouchDB server could not be handled (e.g. no JSON body)""" - pass class CouchDBServerError(CouchDBError): @@ -606,4 +604,3 @@ def __init__(self, code: int, error: str, reason: str, *args): class CouchDBConflictError(CouchDBError): """Exception raised when an object could not be committed due to a concurrent modification in the database""" - pass diff --git a/sdk/basyx/aas/model/datatypes.py b/sdk/basyx/aas/model/datatypes.py index bb5fece9b..5c59db176 100644 --- a/sdk/basyx/aas/model/datatypes.py +++ b/sdk/basyx/aas/model/datatypes.py @@ -276,7 +276,6 @@ class HexBinary(bytearray): class Float(float): """A 32bit IEEE754 float. This can not be represented with Python""" - pass class Long(int): diff --git a/sdk/basyx/aas/model/provider.py b/sdk/basyx/aas/model/provider.py index 76e122e22..8266441e1 100644 --- a/sdk/basyx/aas/model/provider.py +++ b/sdk/basyx/aas/model/provider.py @@ -41,7 +41,6 @@ class AbstractObjectProvider(Generic[_KEY, _VALUE], metaclass=abc.ABCMeta): @abc.abstractmethod def get_item(self, key: _KEY) -> _VALUE: """Retrieve the item or raise a KeyError.""" - pass def get(self, key: _KEY, default: Optional[_VALUE] = None) -> Optional[_VALUE]: """Retrieve the item or return a default value.""" diff --git a/sdk/basyx/aas/util/identification.py b/sdk/basyx/aas/util/identification.py index cc9c4eae4..d7b4f58f3 100644 --- a/sdk/basyx/aas/util/identification.py +++ b/sdk/basyx/aas/util/identification.py @@ -41,7 +41,6 @@ def generate_id(self, proposal: Optional[str] = None) -> model.Identifier: fragment of an IRI). It may be ignored by some implementations of or be changed if the resulting id is already existing. """ - pass class UUIDGenerator(AbstractIdentifierGenerator): @@ -148,7 +147,7 @@ def generate_id(self, proposal: Optional[str] = None) -> model.Identifier: ] } # Remove ASCII control characters -_iri_segment_quote_table_tmpl.update({i: None for i in range(0, 0x1F)}) +_iri_segment_quote_table_tmpl.update({i: None for i in range(0x1F)}) _iri_segment_quote_table_tmpl[0x7F] = None _iri_segment_quote_table: Dict[int, Optional[str]] = str.maketrans( _iri_segment_quote_table_tmpl diff --git a/server/app/adapter/jsonization.py b/server/app/adapter/jsonization.py index f667a3ade..e30349ce4 100644 --- a/server/app/adapter/jsonization.py +++ b/server/app/adapter/jsonization.py @@ -203,7 +203,6 @@ class ServerStrictStrippedAASFromJsonDecoder(ServerStrictAASFromJsonDecoder, Ser Non-failsafe decoder for stripped JSON objects. """ - pass class ServerAASToJsonEncoder(AASToJsonEncoder): From 978cf5ea9b2a034956d61ab18b95bade2649efb1 Mon Sep 17 00:00:00 2001 From: Joshua Benning Date: Mon, 20 Jul 2026 14:45:56 +0200 Subject: [PATCH 16/17] Update CONTRIBUTING.md --- CONTRIBUTING.md | 7 +++++-- ruff.toml | 1 - 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a15c9c20c..051982110 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -143,6 +143,8 @@ before new code can be added: - We run the developed unittests and aim for a code coverage of at least 80%. - We perform static code analysis for type-checking and codestyle, not just in the code itself, but also in codeblocks that are inside docstrings and the `README.md`. +- We apply a set of [ruff](https://docs.astral.sh/ruff/) linter rules (see [ruff.toml](ruff.toml)) to ensure a certain + codestyle and prevent issues / bad practices to arise. - We check that the automatically generated developer documentation compiles. - We check that the Python Versions we support match between the different subprojects in the monorepository and are not End of Life. @@ -164,8 +166,8 @@ pip install .[dev] Running all checks: ```bash +ruff check mypy basyx test -pycodestyle --max-line-length 120 basyx test python -m unittest coverage run --source basyx --branch -m unittest coverage report -m @@ -185,6 +187,7 @@ of it without error. For that, you need to have Docker installed on your system. In the directory with the `Dockerfile`: ```bash +ruff check docker build -t basyx-python-server . docker run --name basyx-python-server basyx-python-server ``` @@ -204,8 +207,8 @@ itself. Then you can run the checks via: ```bash +ruff check mypy basyx test -pycodestyle --max-line-length 120 basyx test python -m unittest coverage run --source basyx --branch -m unittest coverage report -m diff --git a/ruff.toml b/ruff.toml index 7f90d830d..40eb0b23c 100644 --- a/ruff.toml +++ b/ruff.toml @@ -32,7 +32,6 @@ ignore = [ "T20", # prevent native print() statements "A", # prevent shadowing of python builtins "FIX", # prevent the creation of T0DO / F1XME comments - # Checked, need manual work: "PYI", # typing best practices # Evaluate if we want to use: "S", # security related precautions From 38af2f9177878065df875b2e6908cd009fe48d07 Mon Sep 17 00:00:00 2001 From: Joshua Benning Date: Wed, 22 Jul 2026 14:05:19 +0200 Subject: [PATCH 17/17] sdk/test: Add explanation to empty tutorial test --- sdk/test/examples/test_tutorials.py | 1 + 1 file changed, 1 insertion(+) diff --git a/sdk/test/examples/test_tutorials.py b/sdk/test/examples/test_tutorials.py index e1aed7921..e2b7e0277 100644 --- a/sdk/test/examples/test_tutorials.py +++ b/sdk/test/examples/test_tutorials.py @@ -45,6 +45,7 @@ def test_tutorial_storage(self): ) def test_tutorial_backend_couchdb(self): pass + # The tutorial already includes assert statements for the relevant points. So no further checks are required. def test_tutorial_serialization_deserialization_json(self): with temporary_workingdirectory():