diff --git a/compliance_tool/aas_compliance_tool/compliance_check_aasx.py b/compliance_tool/aas_compliance_tool/compliance_check_aasx.py index 9dfbb982c..824b54a47 100644 --- a/compliance_tool/aas_compliance_tool/compliance_check_aasx.py +++ b/compliance_tool/aas_compliance_tool/compliance_check_aasx.py @@ -31,7 +31,7 @@ def check_deserialization(file_path: str, state_manager: ComplianceToolStateManager, file_info: Optional[str] = None) \ - -> Tuple[model.DictObjectStore, aasx.DictSupplementaryFileContainer, pyecma376_2.OPCCoreProperties]: + -> Tuple[model.DictIdentifiableStore, aasx.DictSupplementaryFileContainer, pyecma376_2.OPCCoreProperties]: """ Read a AASX file and reports any issues using the given :class:`~basyx.aas.compliance_tool.state_manager.ComplianceToolStateManager` @@ -68,24 +68,24 @@ def check_deserialization(file_path: str, state_manager: ComplianceToolStateMana state_manager.set_step_status_from_log() state_manager.add_step('Read file') state_manager.set_step_status(Status.NOT_EXECUTED) - return model.DictObjectStore(), aasx.DictSupplementaryFileContainer(), pyecma376_2.OPCCoreProperties() + return model.DictIdentifiableStore(), aasx.DictSupplementaryFileContainer(), pyecma376_2.OPCCoreProperties() try: # read given file state_manager.add_step('Read file') - obj_store: model.DictObjectStore[model.Identifiable] = model.DictObjectStore() + id_store: model.DictIdentifiableStore[model.Identifiable] = model.DictIdentifiableStore() files = aasx.DictSupplementaryFileContainer() - reader.read_into(obj_store, files) + reader.read_into(id_store, files) new_cp = reader.get_core_properties() state_manager.set_step_status(Status.SUCCESS) except (ValueError, KeyError) as error: logger.error(error) state_manager.set_step_status(Status.FAILED) - return model.DictObjectStore(), aasx.DictSupplementaryFileContainer(), pyecma376_2.OPCCoreProperties() + return model.DictIdentifiableStore(), aasx.DictSupplementaryFileContainer(), pyecma376_2.OPCCoreProperties() finally: reader.close() - return obj_store, files, new_cp + return id_store, files, new_cp def check_schema(file_path: str, state_manager: ComplianceToolStateManager) -> None: @@ -187,7 +187,7 @@ def check_aas_example(file_path: str, state_manager: ComplianceToolStateManager, state_manager.add_step('Check if data is equal to example data') example_data = create_example_aas_binding() - checker.check_object_store(obj_store, example_data) + checker.check_identifiable_store(obj_store, example_data) state_manager.add_log_records_from_data_checker(checker) if state_manager.status in (Status.FAILED, Status.NOT_EXECUTED): @@ -238,22 +238,25 @@ def check_aas_example(file_path: str, state_manager: ComplianceToolStateManager, # Check if file in file object is the same list_of_id_shorts = ["ExampleSubmodelCollection", "ExampleFile"] - obj = example_data.get_identifiable("https://acplt.org/Test_Submodel") + identifiable = example_data.get_item("https://acplt.org/Test_Submodel") for id_short in list_of_id_shorts: - obj = obj.get_referable(id_short) - obj2 = obj_store.get_identifiable("https://acplt.org/Test_Submodel") + identifiable = identifiable.get_referable(id_short) + obj2 = obj_store.get_item("https://acplt.org/Test_Submodel") for id_short in list_of_id_shorts: obj2 = obj2.get_referable(id_short) try: - sha_file = files.get_sha256(obj.value) + sha_file = files.get_sha256(identifiable.value) except KeyError as error: state_manager.add_log_records_from_data_checker(checker2) logger.error(error) state_manager.set_step_status(Status.FAILED) return - checker2.check(sha_file == files.get_sha256(obj2.value), "File of {} must be {}.".format(obj.value, obj2.value), - value=obj2.value) + checker2.check( + sha_file == files.get_sha256(obj2.value), + "File of {} must be {}.".format(identifiable.value, obj2.value), + value=obj2.value + ) state_manager.add_log_records_from_data_checker(checker2) if state_manager.status in (Status.FAILED, Status.NOT_EXECUTED): state_manager.set_step_status(Status.FAILED) @@ -294,7 +297,7 @@ def check_aasx_files_equivalence(file_path_1: str, file_path_2: str, state_manag checker = AASDataChecker(raise_immediately=False, **kwargs) try: state_manager.add_step('Check if data in files are equal') - checker.check_object_store(obj_store_1, obj_store_2) + checker.check_identifiable_store(obj_store_1, obj_store_2) except (KeyError, AssertionError) as error: state_manager.set_step_status(Status.FAILED) logger.error(error) diff --git a/compliance_tool/aas_compliance_tool/compliance_check_json.py b/compliance_tool/aas_compliance_tool/compliance_check_json.py index 2050f570c..96c20f8b4 100644 --- a/compliance_tool/aas_compliance_tool/compliance_check_json.py +++ b/compliance_tool/aas_compliance_tool/compliance_check_json.py @@ -102,7 +102,7 @@ def _check_schema(file_to_be_checked: IO[str], state_manager: ComplianceToolStat def check_deserialization(file_path: str, state_manager: ComplianceToolStateManager, - file_info: Optional[str] = None) -> model.DictObjectStore: + file_info: Optional[str] = None) -> model.DictIdentifiableStore: """ Deserializes a JSON AAS file and reports any issues using the given :class:`~basyx.aas.compliance_tool.state_manager.ComplianceToolStateManager` @@ -140,7 +140,7 @@ def check_deserialization(file_path: str, state_manager: ComplianceToolStateMana else: state_manager.add_step('Read file and check if it is deserializable') state_manager.set_step_status(Status.NOT_EXECUTED) - return model.DictObjectStore() + return model.DictIdentifiableStore() with file_to_be_checked: state_manager.set_step_status(Status.SUCCESS) @@ -184,7 +184,7 @@ def check_aas_example(file_path: str, state_manager: ComplianceToolStateManager, checker = AASDataChecker(raise_immediately=False, **kwargs) state_manager.add_step('Check if data is equal to example data') - checker.check_object_store(obj_store, create_example()) + checker.check_identifiable_store(obj_store, create_example()) state_manager.add_log_records_from_data_checker(checker) @@ -220,7 +220,7 @@ def check_json_files_equivalence(file_path_1: str, file_path_2: str, state_manag checker = AASDataChecker(raise_immediately=False, **kwargs) try: state_manager.add_step('Check if data in files are equal') - checker.check_object_store(obj_store_1, obj_store_2) + checker.check_identifiable_store(obj_store_1, obj_store_2) except (KeyError, AssertionError) as error: state_manager.set_step_status(Status.FAILED) logger.error(error) diff --git a/compliance_tool/aas_compliance_tool/compliance_check_xml.py b/compliance_tool/aas_compliance_tool/compliance_check_xml.py index 6d4e10c55..26b8b2540 100644 --- a/compliance_tool/aas_compliance_tool/compliance_check_xml.py +++ b/compliance_tool/aas_compliance_tool/compliance_check_xml.py @@ -101,7 +101,7 @@ def _check_schema(file_to_be_checked, state_manager): def check_deserialization(file_path: str, state_manager: ComplianceToolStateManager, - file_info: Optional[str] = None) -> model.DictObjectStore: + file_info: Optional[str] = None) -> model.DictIdentifiableStore: """ Deserializes a XML AAS file and reports any issues using the given :class:`~basyx.aas.compliance_tool.state_manager.ComplianceToolStateManager` @@ -139,7 +139,7 @@ def check_deserialization(file_path: str, state_manager: ComplianceToolStateMana else: state_manager.add_step('Read file and check if it is deserializable') state_manager.set_step_status(Status.NOT_EXECUTED) - return model.DictObjectStore() + return model.DictIdentifiableStore() with file_to_be_checked: state_manager.set_step_status(Status.SUCCESS) @@ -183,7 +183,7 @@ def check_aas_example(file_path: str, state_manager: ComplianceToolStateManager, checker = AASDataChecker(raise_immediately=False, **kwargs) state_manager.add_step('Check if data is equal to example data') - checker.check_object_store(obj_store, create_example()) + checker.check_identifiable_store(obj_store, create_example()) state_manager.add_log_records_from_data_checker(checker) @@ -219,7 +219,7 @@ def check_xml_files_equivalence(file_path_1: str, file_path_2: str, state_manage checker = AASDataChecker(raise_immediately=False, **kwargs) try: state_manager.add_step('Check if data in files are equal') - checker.check_object_store(obj_store_1, obj_store_2) + checker.check_identifiable_store(obj_store_1, obj_store_2) except (KeyError, AssertionError) as error: state_manager.set_step_status(Status.FAILED) logger.error(error) diff --git a/compliance_tool/pyproject.toml b/compliance_tool/pyproject.toml index c907f90df..bbd3f32ea 100644 --- a/compliance_tool/pyproject.toml +++ b/compliance_tool/pyproject.toml @@ -38,7 +38,7 @@ requires-python = ">=3.9" dependencies = [ "pyecma376-2>=0.2.4", "jsonschema>=4.21.1", - "basyx-python-sdk>=1.0.0", + "basyx-python-sdk @ file:../sdk", ] [project.optional-dependencies] diff --git a/compliance_tool/test/test_aas_compliance_tool.py b/compliance_tool/test/test_aas_compliance_tool.py index cb631cf9c..6eb4bcb15 100644 --- a/compliance_tool/test/test_aas_compliance_tool.py +++ b/compliance_tool/test/test_aas_compliance_tool.py @@ -141,7 +141,7 @@ def test_json_create_example(self) -> None: json_object_store = read_aas_json_file(f, failsafe=False) data = create_example() checker = AASDataChecker(raise_immediately=True) - checker.check_object_store(json_object_store, data) + checker.check_identifiable_store(json_object_store, data) os.unlink(filename) def test_json_deserialization(self) -> None: @@ -187,7 +187,7 @@ def test_xml_create_example(self) -> None: xml_object_store = read_aas_xml_file(f, failsafe=False) data = create_example() checker = AASDataChecker(raise_immediately=True) - checker.check_object_store(xml_object_store, data) + checker.check_identifiable_store(xml_object_store, data) os.unlink(filename) def test_xml_deseralization(self) -> None: @@ -229,7 +229,7 @@ def test_aasx_create_example(self) -> None: self.assertIn('SUCCESS: Write data to file', str(output.stdout)) # Read AASX file - new_data: model.DictObjectStore[model.Identifiable] = model.DictObjectStore() + 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) diff --git a/sdk/README.md b/sdk/README.md index 74d63b8f3..15c556c19 100644 --- a/sdk/README.md +++ b/sdk/README.md @@ -113,7 +113,7 @@ Serialize the `Submodel` to XML: ```python from basyx.aas.adapter.xml import write_aas_xml_file -data: model.DictObjectStore[model.Identifiable] = model.DictObjectStore() +data: model.DictIdentifiableStore[model.Identifiable] = model.DictIdentifiableStore() data.add(submodel) write_aas_xml_file(file='Simple_Submodel.xml', data=data) ``` diff --git a/sdk/basyx/aas/adapter/__init__.py b/sdk/basyx/aas/adapter/__init__.py index 0fca01291..a675d6f11 100644 --- a/sdk/basyx/aas/adapter/__init__.py +++ b/sdk/basyx/aas/adapter/__init__.py @@ -11,12 +11,12 @@ 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 DictObjectStore +from basyx.aas.model.provider import DictIdentifiableStore from pathlib import Path from typing import Union -def load_directory(directory: Union[Path, str]) -> tuple[DictObjectStore, DictSupplementaryFileContainer]: +def load_directory(directory: Union[Path, str]) -> tuple[DictIdentifiableStore, DictSupplementaryFileContainer]: """ Create a new :class:`~basyx.aas.model.provider.DictObjectStore` 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 all @@ -28,7 +28,7 @@ def load_directory(directory: Union[Path, str]) -> tuple[DictObjectStore, DictSu :class:`~basyx.aas.adapter.aasx.DictSupplementaryFileContainer` containing all loaded data """ - dict_object_store: DictObjectStore = DictObjectStore() + dict_identifiable_store: DictIdentifiableStore = DictIdentifiableStore() file_container: DictSupplementaryFileContainer = DictSupplementaryFileContainer() directory = Path(directory) @@ -40,12 +40,12 @@ def load_directory(directory: Union[Path, str]) -> tuple[DictObjectStore, DictSu suffix = file.suffix.lower() if suffix == ".json": with open(file) as f: - read_aas_json_file_into(dict_object_store, f) + read_aas_json_file_into(dict_identifiable_store, f) elif suffix == ".xml": with open(file) as f: - read_aas_xml_file_into(dict_object_store, f) + read_aas_xml_file_into(dict_identifiable_store, f) elif suffix == ".aasx": with AASXReader(file) as reader: - reader.read_into(object_store=dict_object_store, file_store=file_container) + reader.read_into(object_store=dict_identifiable_store, file_store=file_container) - return dict_object_store, file_container + return dict_identifiable_store, file_container diff --git a/sdk/basyx/aas/adapter/aasx.py b/sdk/basyx/aas/adapter/aasx.py index 8bb5958f6..6705400f8 100644 --- a/sdk/basyx/aas/adapter/aasx.py +++ b/sdk/basyx/aas/adapter/aasx.py @@ -232,7 +232,7 @@ def _read_aas_part_into(self, part_name: str, if isinstance(obj, model.Submodel): self._collect_supplementary_files(part_name, obj, file_store) - def _parse_aas_part(self, part_name: str, **kwargs) -> model.DictObjectStore: + def _parse_aas_part(self, part_name: str, **kwargs) -> model.DictIdentifiableStore: """ Helper function to parse the AAS objects from a single JSON or XML part of the AASX package. @@ -259,7 +259,7 @@ def _parse_aas_part(self, part_name: str, **kwargs) -> model.DictObjectStore: logger.error(error_message) else: raise ValueError(error_message) - return model.DictObjectStore() + return model.DictIdentifiableStore() def _collect_supplementary_files(self, part_name: str, submodel: model.Submodel, file_store: "AbstractSupplementaryFileContainer") -> None: @@ -352,7 +352,7 @@ def __init__(self, file: Union[os.PathLike, str, IO], failsafe: bool = True): def write_aas(self, aas_ids: Union[model.Identifier, Iterable[model.Identifier]], - object_store: model.AbstractObjectStore, + object_store: model.AbstractObjectStore[model.Identifier, model.Identifiable], file_store: "AbstractSupplementaryFileContainer", write_json: bool = False) -> None: """ @@ -402,10 +402,10 @@ def write_aas(self, if isinstance(aas_ids, model.Identifier): aas_ids = (aas_ids,) - objects_to_be_written: model.DictObjectStore[model.Identifiable] = model.DictObjectStore() + objects_to_be_written: model.DictIdentifiableStore[model.Identifiable] = model.DictIdentifiableStore() for aas_id in aas_ids: try: - aas = object_store.get_identifiable(aas_id) + 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}") @@ -479,9 +479,10 @@ def write_aas_objects(self, A thin wrapper around :meth:`write_all_aas_objects` to ensure downwards compatibility This method takes the AAS's :class:`~basyx.aas.model.base.Identifier` (as ``aas_id``) to retrieve it - from the given object_store. If the list of written objects includes :class:`~basyx.aas.model.submodel.Submodel` - objects, Supplementary files which are referenced by :class:`~basyx.aas.model.submodel.File` objects within - those Submodels, are also added to the AASX package. + from the given object_store. If the list of written identifiables includes + :class:`~basyx.aas.model.submodel.Submodel` identifiables, Supplementary files which are referenced by + :class:`~basyx.aas.model.submodel.File` identifiables within those Submodels, are also added to the AASX + package. .. attention:: @@ -491,14 +492,15 @@ def write_aas_objects(self, :param part_name: Name of the Part within the AASX package to write the files to. Must be a valid ECMA376-2 part name and unique within the package. The extension of the part should match the data format (i.e. '.json' if ``write_json`` else '.xml'). - :param object_ids: A list of :class:`Identifiers ` of the objects to be written - to the AASX package. Only these :class:`~basyx.aas.model.base.Identifiable` objects (and included - :class:`~basyx.aas.model.base.Referable` objects) are written to the package. - :param object_store: The objects store to retrieve the :class:`~basyx.aas.model.base.Identifiable` objects from + :param object_ids: A list of :class:`Identifiers ` of the identifiables to be + written to the AASX package. Only these :class:`~basyx.aas.model.base.Identifiable` identifiables + (and included :class:`~basyx.aas.model.base.Referable` identifiables) are written to the package. + :param object_store: The identifiables store to retrieve the :class:`~basyx.aas.model.base.Identifiable` + identifiables from :param file_store: The :class:`SupplementaryFileContainer ` to retrieve supplementary files from (if there are any :class:`~basyx.aas.model.submodel.File` - objects within the written objects. + identifiables within the written identifiables. :param write_json: If ``True``, the part is written as a JSON file instead of an XML file. Defaults to ``False``. :param split_part: If ``True``, no aas-spec relationship is added from the aasx-origin to this part. You must @@ -506,29 +508,31 @@ def write_aas_objects(self, :param additional_relationships: Optional OPC/ECMA376 relationships which should originate at the AAS object part to be written, in addition to the aas-suppl relationships which are created automatically. """ - logger.debug(f"Writing AASX part {part_name} with AAS objects ...") + logger.debug(f"Writing AASX part {part_name} with AAS identifiables ...") - objects: model.DictObjectStore[model.Identifiable] = model.DictObjectStore() + identifiables: model.DictIdentifiableStore[model.Identifiable] = model.DictIdentifiableStore() - # Retrieve objects and scan for referenced supplementary files + # Retrieve identifiables and scan for referenced supplementary files for identifier in object_ids: try: - the_object = object_store.get_identifiable(identifier) + the_identifiable = object_store.get_item(identifier) except KeyError: if self.failsafe: - logger.error(f"Could not find object {identifier} in ObjectStore") + logger.error(f"Could not find identifiable {identifier} in IdentifiableStore") continue else: - raise KeyError(f"Could not find object {identifier!r} in ObjectStore") - objects.add(the_object) + raise KeyError(f"Could not find identifiable {identifier!r} in IdentifiableStore") + identifiables.add(the_identifiable) - self.write_all_aas_objects(part_name, objects, file_store, write_json, split_part, additional_relationships) + self.write_all_aas_objects( + 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.Identifiable], + objects: model.AbstractObjectStore[model.Identifier, model.Identifiable], file_store: "AbstractSupplementaryFileContainer", write_json: bool = False, split_part: bool = False, diff --git a/sdk/basyx/aas/adapter/json/json_deserialization.py b/sdk/basyx/aas/adapter/json/json_deserialization.py index 84635703d..6355072bb 100644 --- a/sdk/basyx/aas/adapter/json/json_deserialization.py +++ b/sdk/basyx/aas/adapter/json/json_deserialization.py @@ -898,7 +898,7 @@ 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.DictObjectStore[model.Identifiable]: +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.DictObjectStore`. This function supports the same keyword arguments as @@ -915,6 +915,6 @@ def read_aas_json_file(file: PathOrIO, failsafe: bool = True, **kwargs) -> model (e.g. an AssetAdministrationShell in ``submodels``) :return: A :class:`~basyx.aas.model.provider.DictObjectStore` containing all AAS objects from the JSON file """ - object_store: model.DictObjectStore[model.Identifiable] = model.DictObjectStore() - read_aas_json_file_into(object_store, file, failsafe=failsafe, **kwargs) - return object_store + identifiable_store: model.DictIdentifiableStore = model.DictIdentifiableStore() + read_aas_json_file_into(identifiable_store, file, failsafe=failsafe, **kwargs) + return identifiable_store diff --git a/sdk/basyx/aas/adapter/xml/xml_deserialization.py b/sdk/basyx/aas/adapter/xml/xml_deserialization.py index b263820d1..8299e17a6 100644 --- a/sdk/basyx/aas/adapter/xml/xml_deserialization.py +++ b/sdk/basyx/aas/adapter/xml/xml_deserialization.py @@ -1421,10 +1421,16 @@ def read_aas_xml_element(file: PathOrIO, construct: XMLConstructables, failsafe: return _failsafe_construct(element, constructor, decoder_.failsafe, **constructor_kwargs) -def read_aas_xml_file_into(object_store: model.AbstractObjectStore[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]: +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 +) -> Set[model.Identifier]: """ Read an Asset Administration Shell XML file according to 'Details of the Asset Administration Shell', chapter 5.4 into a given :class:`ObjectStore `. @@ -1503,7 +1509,7 @@ def read_aas_xml_file_into(object_store: model.AbstractObjectStore[model.Identif def read_aas_xml_file(file: PathOrIO, failsafe: bool = True, **kwargs: Any)\ - -> model.DictObjectStore[model.Identifiable]: + -> 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.DictObjectStore`. This function supports @@ -1521,6 +1527,6 @@ 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.DictObjectStore` containing all AAS objects from the XML file """ - object_store: model.DictObjectStore[model.Identifiable] = model.DictObjectStore() - read_aas_xml_file_into(object_store, file, failsafe=failsafe, **kwargs) - return object_store + 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/backend/couchdb.py b/sdk/basyx/aas/backend/couchdb.py index 6f2b3a0fc..aa175460d 100644 --- a/sdk/basyx/aas/backend/couchdb.py +++ b/sdk/basyx/aas/backend/couchdb.py @@ -11,8 +11,9 @@ The :class:`~CouchDBObjectStore` handles adding, deleting and otherwise managing the AAS objects in a specific CouchDB. """ import threading +import warnings import weakref -from typing import List, Dict, Any, Optional, Iterator, Iterable, Union, Tuple, MutableMapping +from typing import Dict, Any, Optional, Iterator, Iterable, Tuple, MutableMapping import urllib.parse import urllib.request import urllib.error @@ -87,7 +88,7 @@ def delete_couchdb_revision(url: str): del _revision_store[url] -class CouchDBObjectStore(model.AbstractObjectStore): +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. @@ -175,7 +176,7 @@ def get_identifiable_by_couchdb_id(self, couchdb_id: str) -> model.Identifiable: self._object_cache[obj.id] = obj return obj - def get_identifiable(self, identifier: model.Identifier) -> model.Identifiable: + def get_item(self, identifier: model.Identifier) -> model.Identifiable: """ Retrieve an AAS object from the CouchDB by its :class:`~basyx.aas.model.base.Identifier` @@ -382,7 +383,7 @@ def __iter__(self) -> Iterator[model.Identifiable]: """ # Iterator class storing the list of ids and fetching Identifiable objects on the fly class CouchDBIdentifiableIterator(Iterator[model.Identifiable]): - def __init__(self, store: CouchDBObjectStore, ids: Iterable[str]): + def __init__(self, store: CouchDBIdentifiableStore, ids: Iterable[str]): self._iter = iter(ids) self._store = store @@ -407,6 +408,21 @@ def _transform_id(identifier: model.Identifier, url_quote=True) -> str: return identifier +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 " + "`CouchDBIdentifiableStore` instead.", + DeprecationWarning, + stacklevel=2, + ) + super().__init__(url, database) + + # ################################################################################################# # Custom Exception classes for reporting errors during interaction with the CouchDB server diff --git a/sdk/basyx/aas/backend/local_file.py b/sdk/basyx/aas/backend/local_file.py index 39ff91415..c5b1eeef1 100644 --- a/sdk/basyx/aas/backend/local_file.py +++ b/sdk/basyx/aas/backend/local_file.py @@ -11,12 +11,13 @@ The :class:`~LocalFileObjectStore` handles adding, deleting and otherwise managing the AAS objects in a specific Directory. """ -from typing import List, Iterator, Iterable, Union +from typing import Iterator import logging import json import os import hashlib import threading +import warnings import weakref from ..adapter.json import json_serialization, json_deserialization @@ -26,7 +27,7 @@ logger = logging.getLogger(__name__) -class LocalFileObjectStore(model.AbstractObjectStore): +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 @@ -84,7 +85,7 @@ def get_identifiable_by_hash(self, hash_: str) -> model.Identifiable: self._object_cache[obj.id] = obj return obj - def get_identifiable(self, identifier: model.Identifier) -> model.Identifiable: + def get_item(self, identifier: model.Identifier) -> model.Identifiable: """ Retrieve an AAS object from the local file by its :class:`~basyx.aas.model.base.Identifier` @@ -168,3 +169,18 @@ def _transform_id(identifier: model.Identifier) -> str: Helper method to represent an ASS Identifier as a string to be used as Local file document id """ return hashlib.sha256(identifier.encode("utf-8")).hexdigest() + + +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 " + "`LocalFileIdentifiableStore` instead.", + DeprecationWarning, + stacklevel=2, + ) + super().__init__(directory_path) diff --git a/sdk/basyx/aas/examples/data/__init__.py b/sdk/basyx/aas/examples/data/__init__.py index b82226ad4..9f0f17062 100644 --- a/sdk/basyx/aas/examples/data/__init__.py +++ b/sdk/basyx/aas/examples/data/__init__.py @@ -26,14 +26,14 @@ TEST_PDF_FILE = os.path.join(os.path.dirname(__file__), 'TestFile.pdf') -def create_example() -> model.DictObjectStore: +def create_example() -> model.DictIdentifiableStore: """ creates an object store which is filled with example assets, submodels, concept descriptions and asset administration shells using the functionality of this package :return: object store """ - obj_store: model.DictObjectStore[model.Identifiable] = model.DictObjectStore() + obj_store: model.DictIdentifiableStore[model.Identifiable] = model.DictIdentifiableStore() obj_store.update(example_aas.create_full_example()) obj_store.update(example_aas_mandatory_attributes.create_full_example()) obj_store.update(example_aas_missing_attributes.create_full_example()) @@ -41,7 +41,7 @@ def create_example() -> model.DictObjectStore: return obj_store -def create_example_aas_binding() -> model.DictObjectStore: +def create_example_aas_binding() -> model.DictIdentifiableStore: """ creates an object store which is filled with example assets, submodels, concept descriptions and asset administration shells using the functionality of this package where each submodel and concept description is @@ -49,18 +49,18 @@ def create_example_aas_binding() -> model.DictObjectStore: :return: object store """ - obj_store: model.DictObjectStore[model.Identifiable] = model.DictObjectStore() + obj_store: model.DictIdentifiableStore[model.Identifiable] = model.DictIdentifiableStore() obj_store.update(example_aas.create_full_example()) obj_store.update(example_aas_mandatory_attributes.create_full_example()) obj_store.update(example_aas_missing_attributes.create_full_example()) obj_store.add(example_submodel_template.create_example_submodel_template()) - aas = obj_store.get_identifiable('https://acplt.org/Test_AssetAdministrationShell') - sm = obj_store.get_identifiable('https://acplt.org/Test_Submodel_Template') + aas = obj_store.get_item('https://acplt.org/Test_AssetAdministrationShell') + sm = obj_store.get_item('https://acplt.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 = obj_store.get_identifiable('https://acplt.org/Test_ConceptDescription_Mandatory') + cd = obj_store.get_item('https://acplt.org/Test_ConceptDescription_Mandatory') assert (isinstance(cd, model.concept.ConceptDescription)) # make mypy happy return obj_store diff --git a/sdk/basyx/aas/examples/data/_helper.py b/sdk/basyx/aas/examples/data/_helper.py index a4c3edfce..cf8190df4 100644 --- a/sdk/basyx/aas/examples/data/_helper.py +++ b/sdk/basyx/aas/examples/data/_helper.py @@ -917,44 +917,48 @@ def _check_value_list_equal(self, object_: model.ValueList, expected_value: mode self.check(found_elements == set(), 'ValueList must not have extra ValueReferencePairs', value=found_elements) - def check_object_store(self, obj_store_1: model.DictObjectStore, obj_store_2: model.DictObjectStore): + def check_identifiable_store( + self, + id_store_1: model.DictIdentifiableStore, + id_store_2: model.DictIdentifiableStore + ): """ Checks if the given object stores are equal - :param obj_store_1: Given object store to check - :param obj_store_2: expected object store + :param id_store_1: Given object store to check + :param id_store_2: expected object store :return: """ # separate different kind of objects submodel_list_1 = [] concept_description_list_1 = [] shell_list_1 = [] - for obj in obj_store_1: - if isinstance(obj, model.AssetAdministrationShell): - shell_list_1.append(obj) - elif isinstance(obj, model.Submodel): - submodel_list_1.append(obj) - elif isinstance(obj, model.ConceptDescription): - concept_description_list_1.append(obj) + for identifiable in id_store_1: + if isinstance(identifiable, model.AssetAdministrationShell): + shell_list_1.append(identifiable) + elif isinstance(identifiable, model.Submodel): + submodel_list_1.append(identifiable) + elif isinstance(identifiable, model.ConceptDescription): + concept_description_list_1.append(identifiable) else: - raise KeyError('Check for {} not implemented'.format(obj)) + raise KeyError('Check for {} not implemented'.format(identifiable)) # separate different kind of objects submodel_list_2 = [] concept_description_list_2 = [] shell_list_2 = [] - for obj in obj_store_2: - if isinstance(obj, model.AssetAdministrationShell): - shell_list_2.append(obj) - elif isinstance(obj, model.Submodel): - submodel_list_2.append(obj) - elif isinstance(obj, model.ConceptDescription): - concept_description_list_2.append(obj) + for identifiable in id_store_2: + if isinstance(identifiable, model.AssetAdministrationShell): + shell_list_2.append(identifiable) + elif isinstance(identifiable, model.Submodel): + submodel_list_2.append(identifiable) + elif isinstance(identifiable, model.ConceptDescription): + concept_description_list_2.append(identifiable) else: - raise KeyError('Check for {} not implemented'.format(obj)) + raise KeyError('Check for {} not implemented'.format(identifiable)) for shell_2 in shell_list_2: - shell_1 = obj_store_1.get(shell_2.id) + shell_1 = id_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)): self.check_asset_administration_shell_equal(shell_1, shell_2) # type: ignore @@ -964,7 +968,7 @@ def check_object_store(self, obj_store_1: model.DictObjectStore, obj_store_2: mo 'administration shells', value=found_elements) for submodel_2 in submodel_list_2: - submodel_1 = obj_store_1.get(submodel_2.id) + submodel_1 = id_store_1.get(submodel_2.id) 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 @@ -973,7 +977,7 @@ def check_object_store(self, obj_store_1: model.DictObjectStore, obj_store_2: mo value=found_elements) for cd_2 in concept_description_list_2: - cd_1 = obj_store_1.get(cd_2.id) + cd_1 = id_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)): self.check_concept_description_equal(cd_1, cd_2) # type: ignore diff --git a/sdk/basyx/aas/examples/data/example_aas.py b/sdk/basyx/aas/examples/data/example_aas.py index e093c603a..3554e7a77 100644 --- a/sdk/basyx/aas/examples/data/example_aas.py +++ b/sdk/basyx/aas/examples/data/example_aas.py @@ -47,7 +47,7 @@ ) -def create_full_example() -> model.DictObjectStore: +def create_full_example() -> model.DictIdentifiableStore: """ Creates an object store which is filled with an example :class:`~basyx.aas.model.submodel.Submodel`, :class:`~basyx.aas.model.concept.ConceptDescription` and :class:`~basyx.aas.model.aas.AssetAdministrationShell` @@ -55,13 +55,13 @@ def create_full_example() -> model.DictObjectStore: :return: :class:`~basyx.aas.model.provider.DictObjectStore` """ - obj_store: model.DictObjectStore[model.Identifiable] = model.DictObjectStore() - obj_store.add(create_example_asset_identification_submodel()) - obj_store.add(create_example_bill_of_material_submodel()) - obj_store.add(create_example_submodel()) - obj_store.add(create_example_concept_description()) - obj_store.add(create_example_asset_administration_shell()) - return obj_store + id_store: model.DictIdentifiableStore[model.Identifiable] = model.DictIdentifiableStore() + id_store.add(create_example_asset_identification_submodel()) + id_store.add(create_example_bill_of_material_submodel()) + id_store.add(create_example_submodel()) + id_store.add(create_example_concept_description()) + id_store.add(create_example_asset_administration_shell()) + return id_store def create_example_asset_identification_submodel() -> model.Submodel: @@ -891,6 +891,6 @@ def check_example_submodel(checker: AASDataChecker, submodel: model.Submodel) -> checker.check_submodel_equal(submodel, expected_submodel) -def check_full_example(checker: AASDataChecker, obj_store: model.DictObjectStore) -> None: +def check_full_example(checker: AASDataChecker, id_store: model.DictIdentifiableStore) -> None: expected_data = create_full_example() - checker.check_object_store(obj_store, expected_data) + checker.check_identifiable_store(id_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 a18f3cbc6..43ce5496c 100644 --- a/sdk/basyx/aas/examples/data/example_aas_mandatory_attributes.py +++ b/sdk/basyx/aas/examples/data/example_aas_mandatory_attributes.py @@ -21,7 +21,7 @@ logger = logging.getLogger(__name__) -def create_full_example() -> model.DictObjectStore: +def create_full_example() -> model.DictIdentifiableStore: """ Creates an :class:`~.basyx.aas.model.provider.DictObjectStore` which is filled with an example :class:`~basyx.aas.model.submodel.Submodel`, :class:`~basyx.aas.model.concept.ConceptDescription` @@ -29,13 +29,13 @@ def create_full_example() -> model.DictObjectStore: :return: :class:`~basyx.aas.model.provider.DictObjectStore` """ - obj_store: model.DictObjectStore[model.Identifiable] = model.DictObjectStore() - obj_store.add(create_example_submodel()) - obj_store.add(create_example_empty_submodel()) - obj_store.add(create_example_concept_description()) - obj_store.add(create_example_asset_administration_shell()) - obj_store.add(create_example_empty_asset_administration_shell()) - return obj_store + id_store: model.DictIdentifiableStore[model.Identifiable] = model.DictIdentifiableStore() + id_store.add(create_example_submodel()) + id_store.add(create_example_empty_submodel()) + id_store.add(create_example_concept_description()) + id_store.add(create_example_asset_administration_shell()) + id_store.add(create_example_empty_asset_administration_shell()) + return id_store def create_example_submodel() -> model.Submodel: @@ -234,6 +234,6 @@ def check_example_empty_submodel(checker: AASDataChecker, submodel: model.Submod checker.check_submodel_equal(submodel, expected_submodel) -def check_full_example(checker: AASDataChecker, obj_store: model.DictObjectStore) -> None: +def check_full_example(checker: AASDataChecker, id_store: model.DictIdentifiableStore) -> None: expected_data = create_full_example() - checker.check_object_store(obj_store, expected_data) + checker.check_identifiable_store(id_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 83232914a..d5a8be1c6 100644 --- a/sdk/basyx/aas/examples/data/example_aas_missing_attributes.py +++ b/sdk/basyx/aas/examples/data/example_aas_missing_attributes.py @@ -17,7 +17,7 @@ logger = logging.getLogger(__name__) -def create_full_example() -> model.DictObjectStore: +def create_full_example() -> model.DictIdentifiableStore: """ Creates an :class:`~basyx.aas.model.provider.DictObjectStore` containing an example :class:`~basyx.aas.model.submodel.Submodel`, an example :class:`~basyx.aas.model.concept.ConceptDescription` and an @@ -25,11 +25,11 @@ def create_full_example() -> model.DictObjectStore: :return: :class:`basyx.aas.model.provider.DictObjectStore` """ - obj_store: model.DictObjectStore[model.Identifiable] = model.DictObjectStore() - obj_store.add(create_example_submodel()) - obj_store.add(create_example_concept_description()) - obj_store.add(create_example_asset_administration_shell()) - return obj_store + id_store: model.DictIdentifiableStore[model.Identifiable] = model.DictIdentifiableStore() + id_store.add(create_example_submodel()) + id_store.add(create_example_concept_description()) + id_store.add(create_example_asset_administration_shell()) + return id_store def create_example_submodel() -> model.Submodel: @@ -413,6 +413,6 @@ def check_example_submodel(checker: AASDataChecker, submodel: model.Submodel) -> checker.check_submodel_equal(submodel, expected_submodel) -def check_full_example(checker: AASDataChecker, obj_store: model.DictObjectStore) -> None: +def check_full_example(checker: AASDataChecker, id_store: model.DictIdentifiableStore) -> None: expected_data = create_full_example() - checker.check_object_store(obj_store, expected_data) + checker.check_identifiable_store(id_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 358e06b05..d31ef6dea 100644 --- a/sdk/basyx/aas/examples/data/example_submodel_template.py +++ b/sdk/basyx/aas/examples/data/example_submodel_template.py @@ -339,7 +339,7 @@ def check_example_submodel(checker: AASDataChecker, submodel: model.Submodel) -> checker.check_submodel_equal(submodel, expected_submodel) -def check_full_example(checker: AASDataChecker, obj_store: model.DictObjectStore) -> None: - expected_data: model.DictObjectStore[model.Identifiable] = model.DictObjectStore() +def check_full_example(checker: AASDataChecker, id_store: model.DictIdentifiableStore) -> None: + expected_data: model.DictIdentifiableStore[model.Identifiable] = model.DictIdentifiableStore() expected_data.add(create_example_submodel_template()) - checker.check_object_store(obj_store, expected_data) + checker.check_identifiable_store(id_store, expected_data) diff --git a/sdk/basyx/aas/examples/tutorial_aasx.py b/sdk/basyx/aas/examples/tutorial_aasx.py index 9f5ffa261..a21bcfd7f 100755 --- a/sdk/basyx/aas/examples/tutorial_aasx.py +++ b/sdk/basyx/aas/examples/tutorial_aasx.py @@ -49,7 +49,7 @@ # We add these objects to an ObjectStore for easy retrieval by id. # See `tutorial_storage.py` for more details. We could also use a database-backed ObjectStore here # (see `tutorial_backend_couchdb.py`). -object_store = model.DictObjectStore([submodel, aas, unrelated_submodel]) +identifiable_store = model.DictIdentifiableStore([submodel, aas, unrelated_submodel]) # For holding auxiliary files, which will eventually be added to an AASX package, we need a SupplementaryFileContainer. @@ -103,7 +103,7 @@ # 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://acplt.org/Simple_AAS'], - object_store=object_store, + 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 @@ -113,9 +113,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_all_aas_objects` SHALL # only be used as an alternative to `write_aas` and SHALL only be called once! - objects_to_be_written: model.DictObjectStore[model.Identifiable] = model.DictObjectStore([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=objects_to_be_written, + 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: @@ -135,14 +137,14 @@ # Let's read the AASX package file, we have just written. # We'll use a fresh ObjectStore and SupplementaryFileContainer to read AAS objects and auxiliary files into. -new_object_store: model.DictObjectStore[model.Identifiable] = model.DictObjectStore() +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_object_store, + reader.read_into(object_store=new_identifiable_store, file_store=new_file_store) # We can also read the metadata @@ -152,6 +154,6 @@ # Some quick checks to make sure, reading worked as expected -assert 'https://acplt.org/Simple_Submodel' in new_object_store +assert 'https://acplt.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_serialization_deserialization.py b/sdk/basyx/aas/examples/tutorial_serialization_deserialization.py index ec281818b..84b5e711c 100755 --- a/sdk/basyx/aas/examples/tutorial_serialization_deserialization.py +++ b/sdk/basyx/aas/examples/tutorial_serialization_deserialization.py @@ -93,18 +93,18 @@ # step 4.1: creating an ObjectStore containing the objects to be serialized # For more information, take a look into `tutorial_storage.py` -obj_store: model.DictObjectStore[model.Identifiable] = model.DictObjectStore() -obj_store.add(submodel) -obj_store.add(aashell) +id_store: model.DictIdentifiableStore[model.Identifiable] = model.DictIdentifiableStore() +id_store.add(submodel) +id_store.add(aashell) # step 4.2: writing the contents of the ObjectStore to a JSON file -basyx.aas.adapter.json.write_aas_json_file('data.json', obj_store) +basyx.aas.adapter.json.write_aas_json_file('data.json', id_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 ObjectStore to an XML file -basyx.aas.adapter.xml.write_aas_xml_file('data.xml', obj_store) +basyx.aas.adapter.xml.write_aas_xml_file('data.xml', id_store) ################################################################## @@ -124,5 +124,5 @@ # step 5.3: Retrieving the objects from the ObjectStore # For more information on the available techniques, see `tutorial_storage.py`. -submodel_from_xml = xml_file_data.get_identifiable('https://acplt.org/Simple_Submodel') +submodel_from_xml = xml_file_data.get_item('https://acplt.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 fe978b11b..81ce8703d 100755 --- a/sdk/basyx/aas/examples/tutorial_storage.py +++ b/sdk/basyx/aas/examples/tutorial_storage.py @@ -69,18 +69,18 @@ # `aas.backends.couchdb` to use a CouchDB database server as persistent storage. Both ObjectStore implementations # provide the same interface. In addition, the CouchDBObjectStores allows synchronizing the local object with the # database via a Backend. See the `tutorial_backend_couchdb.py` for more information. -obj_store: model.DictObjectStore[model.Identifiable] = model.DictObjectStore() +id_store: model.DictIdentifiableStore[model.Identifiable] = model.DictIdentifiableStore() # step 2.2: add submodel and asset administration shell to store -obj_store.add(submodel) -obj_store.add(aas) +id_store.add(submodel) +id_store.add(aas) ################################################################# # Step 3: Retrieving Objects From the Store by Their Identifier # ################################################################# -tmp_submodel = obj_store.get_identifiable( +tmp_submodel = id_store.get_item( 'https://acplt.org/Simple_Submodel') assert submodel is tmp_submodel @@ -92,7 +92,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(obj_store) +submodels = [reference.resolve(id_store) for reference in aas.submodel] # The first (and only) element of this list should be our example submodel: @@ -115,5 +115,5 @@ # Now, we can resolve this new reference. # The `resolve()` method will fetch the Submodel object from the ObjectStore, traverse down to the included Property # object and return this object. -tmp_property = property_reference.resolve(obj_store) +tmp_property = property_reference.resolve(id_store) assert prop is tmp_property diff --git a/sdk/basyx/aas/model/base.py b/sdk/basyx/aas/model/base.py index 35ccad5a1..8386199f1 100644 --- a/sdk/basyx/aas/model/base.py +++ b/sdk/basyx/aas/model/base.py @@ -18,6 +18,7 @@ import re from . import datatypes, _string_constraints +from .. import model if TYPE_CHECKING: from . import provider @@ -1047,7 +1048,7 @@ def resolve(self, provider_: "provider.AbstractObjectProvider") -> _RT: raise AssertionError(f"Retrieving the identifier of the first {self.key[0]!r} failed.") try: - item: Referable = provider_.get_identifiable(identifier) + item: Referable = provider_.get_item(identifier) except KeyError as e: raise KeyError("Could not resolve identifier {}".format(identifier)) from e @@ -1274,26 +1275,38 @@ def __repr__(self) -> str: @_string_constraints.constrain_identifier("id") -class Identifiable(Referable, metaclass=abc.ABCMeta): +class HasIdentifier(metaclass=abc.ABCMeta): """ - An element that has a globally unique :class:`Identifier`. + Abstract base class for entities characterised by a globally unique :class:`Identifier`. <> - :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__() - self.administration: Optional[AdministrativeInformation] = None - # The id attribute is set by all inheriting classes __init__ functions. self.id: Identifier def __repr__(self) -> str: return "{}[{}]".format(self.__class__.__name__, self.id) +class Identifiable(HasIdentifier, Referable, metaclass=abc.ABCMeta): + """ + Identifiable element with a globally unique :class:`Identifier` and, optionally, additional + :class:`~.AdministrativeInformation`. + + <> + + :ivar administration: :class:`~.AdministrativeInformation` of an identifiable element. + """ + @abc.abstractmethod + def __init__(self) -> None: + super().__init__() + self.administration: Optional[AdministrativeInformation] = None + + _T = TypeVar("_T") diff --git a/sdk/basyx/aas/model/provider.py b/sdk/basyx/aas/model/provider.py index d13758308..03fcfb07e 100644 --- a/sdk/basyx/aas/model/provider.py +++ b/sdk/basyx/aas/model/provider.py @@ -11,55 +11,35 @@ """ import abc -from typing import MutableSet, Iterator, Generic, TypeVar, Dict, List, Optional, Iterable, Set, Tuple, cast +import warnings +from typing import MutableSet, Iterator, Generic, TypeVar, Dict, List, Optional, Iterable, Set, Tuple from .base import Identifier, Identifiable -class AbstractObjectProvider(metaclass=abc.ABCMeta): - """ - Abstract baseclass for all objects, that allow to retrieve :class:`~basyx.aas.model.base.Identifiable` objects - (resp. proxy objects for remote :class:`~basyx.aas.model.base.Identifiable` objects) by their - :class:`~basyx.aas.model.base.Identifier`. +_K = TypeVar('_K') +_V = TypeVar('_V') - This includes local object stores, database clients and AAS API clients. - """ - @abc.abstractmethod - def get_identifiable(self, identifier: Identifier) -> Identifiable: - """ - Find an :class:`~basyx.aas.model.base.Identifiable` by its :class:`~basyx.aas.model.base.Identifier` - This may include looking up the object's endpoint in a registry and fetching it from an HTTP server or a - database. +class AbstractObjectProvider(Generic[_K, _V], metaclass=abc.ABCMeta): + """ + Documentation when we agree on this solution. + """ - :param identifier: :class:`~basyx.aas.model.base.Identifier` of the object to return - :return: The :class:`~basyx.aas.model.base.Identifiable` object (or a proxy object for a remote - :class:`~basyx.aas.model.base.Identifiable` object) - :raises KeyError: If no such :class:`~.basyx.aas.model.base.Identifiable` can be found - """ + @abc.abstractmethod + def get_item(self, key: _K) -> _V: + """Retrieve the item or raise a KeyError.""" pass - def get(self, identifier: Identifier, default: Optional[Identifiable] = None) -> Optional[Identifiable]: - """ - Find an object in this set by its :class:`id `, with fallback parameter - - :param identifier: :class:`~basyx.aas.model.base.Identifier` of the object to return - :param default: An object to be returned, if no object with the given - :class:`id ` is found - :return: The :class:`~basyx.aas.model.base.Identifiable` object with the given - :class:`id ` in the provider. Otherwise, the ``default`` object - or None, if none is given. - """ + def get(self, key: _K, default: Optional[_V] = None) -> Optional[_V]: + """Retrieve the item or return a default value.""" try: - return self.get_identifiable(identifier) + return self.get_item(key) except KeyError: return default -_IT = TypeVar('_IT', bound=Identifiable) - - -class AbstractObjectStore(AbstractObjectProvider, MutableSet[_IT], Generic[_IT], metaclass=abc.ABCMeta): +class AbstractObjectStore(AbstractObjectProvider[_K, _V], MutableSet[_V]): """ Abstract baseclass of for container-like objects for storage of :class:`~basyx.aas.model.base.Identifiable` objects. @@ -72,15 +52,16 @@ class AbstractObjectStore(AbstractObjectProvider, MutableSet[_IT], Generic[_IT], The AbstractObjectStore inherits from the :class:`~collections.abc.MutableSet` abstract collections class and therefore implements all the functions of this class. """ + @abc.abstractmethod def __init__(self): pass - def update(self, other: Iterable[_IT]) -> None: + def update(self, other: Iterable[_V]) -> None: for x in other: self.add(x) - def sync(self, other: Iterable[_IT], overwrite: bool) -> Tuple[int, int, int]: + def sync(self, other: Iterable[_V], overwrite: bool) -> Tuple[int, int, int]: """ Merge :class:`Identifiables ` from an :class:`~collections.abc.Iterable` into this :class:`~basyx.aas.model.provider.AbstractObjectStore`. @@ -93,25 +74,39 @@ def sync(self, other: Iterable[_IT], overwrite: bool) -> Tuple[int, int, int]: :return: Counts of processed :class:`Identifiables ` as ``(added, overwritten, skipped)`` """ - added, overwritten, skipped = 0, 0, 0 - for identifiable in other: - identifiable_id = identifiable.id - if identifiable_id in self: + for value in other: + if value in self: if overwrite: - existing = self.get_identifiable(identifiable_id) - self.discard(cast(_IT, existing)) - self.add(identifiable) + + # TODO: This is a quick fix. Yes it works. The underlying problem with the subclass + # `LocalFileIdentifiableStore` will be solved in a separate issue + # (https://github.com/eclipse-basyx/basyx-python-sdk/issues/438). + # Think of this as pythonic duct tape. + # + # The problem is that the `_object_cache` isn't initialised together with the + # `LocalFileIdentifiableStore`, leading to an error when `discard()` is called on the empty cache. + # The for-loop calls `__iter__` calls `get_identifiable_by_hash()` calls + # `self._object_cache[obj.id] = obj`, adding all identifiables to the cache and therefore avoiding + # the error. + for element in self: + pass + + self.discard(value) + self.add(value) overwritten += 1 else: skipped += 1 else: - self.add(identifiable) + self.add(value) added += 1 return added, overwritten, skipped -class DictObjectStore(AbstractObjectStore[_IT], Generic[_IT]): +_IT = TypeVar('_IT', bound=Identifiable) + + +class DictIdentifiableStore(AbstractObjectStore[Identifier, _IT]): """ A local in-memory object store for :class:`~basyx.aas.model.base.Identifiable` objects, backed by a dict, mapping :class:`~basyx.aas.model.base.Identifier` → :class:`~basyx.aas.model.base.Identifiable` @@ -125,12 +120,13 @@ class DictObjectStore(AbstractObjectStore[_IT], Generic[_IT]): :class:`~basyx.aas.model.base.Identifier` may change. In such cases, consider using a :class:`~.SetObjectStore` instead. """ - def __init__(self, objects: Iterable[_IT] = ()) -> None: + + def __init__(self, iterables: Iterable[_IT] = ()) -> None: self._backend: Dict[Identifier, _IT] = {} - for x in objects: + for x in iterables: self.add(x) - def get_identifiable(self, identifier: Identifier) -> _IT: + def get_item(self, identifier: Identifier) -> _IT: return self._backend[identifier] def add(self, x: _IT) -> None: @@ -157,7 +153,23 @@ def __iter__(self) -> Iterator[_IT]: return iter(self._backend.values()) -class SetObjectStore(AbstractObjectStore[_IT], Generic[_IT]): +class DictObjectStore(DictIdentifiableStore): + """ + `DictObjectStore` has been renamed to :class:`~.DictIdentifiableStore` and will be removed in a future release. + Please migrate to :class:`~.DictIdentifiableStore`. + """ + + def __init__(self, iterables: Iterable[_IT] = ()) -> None: + warnings.warn( + "`DictObjectStore` is deprecated and will be removed in a future release. Use " + "`DictIdentifiableStore` instead.", + DeprecationWarning, + stacklevel=2, + ) + super().__init__(iterables) + + +class SetIdentifiableStore(AbstractObjectStore[Identifier, _IT]): """ A local in-memory object store for :class:`~basyx.aas.model.base.Identifiable` objects, backed by a set @@ -169,12 +181,13 @@ class SetObjectStore(AbstractObjectStore[_IT], Generic[_IT]): Therefore, the `SetObjectStore` is suitable for storing objects whose :class:`~basyx.aas.model.base.Identifier` may change. """ + def __init__(self, objects: Iterable[_IT] = ()) -> None: self._backend: Set[_IT] = set() for x in objects: self.add(x) - def get_identifiable(self, identifier: Identifier) -> _IT: + def get_item(self, identifier: Identifier) -> _IT: for x in self._backend: if x.id == identifier: return x @@ -185,7 +198,7 @@ def add(self, x: _IT) -> None: # Object is already in store return try: - self.get_identifiable(x.id) + self.get_item(x.id) except KeyError: self._backend.add(x) else: @@ -200,7 +213,7 @@ def remove(self, x: _IT) -> None: def __contains__(self, x: object) -> bool: if isinstance(x, Identifier): try: - self.get_identifiable(x) + self.get_item(x) return True except KeyError: return False @@ -215,7 +228,23 @@ def __iter__(self) -> Iterator[_IT]: return iter(self._backend) -class ObjectProviderMultiplexer(AbstractObjectProvider): +class SetObjectStore(SetIdentifiableStore): + """ + `SetObjectStore` has been renamed to :class:`~.SetIdentifiableStore` and will be removed in a future release. + Please migrate to :class:`~.SetIdentifiableStore`. + """ + + def __init__(self, objects: Iterable[_IT] = ()) -> None: + warnings.warn( + "`SetObjectStore` is deprecated and will be removed in a future release. Use `SetIdentifiableStore`" + "instead.", + DeprecationWarning, + stacklevel=2, + ) + super().__init__(objects) + + +class ObjectProviderMultiplexer(AbstractObjectProvider[_K, _V]): """ A multiplexer for Providers of :class:`~basyx.aas.model.base.Identifiable` objects. @@ -226,14 +255,15 @@ class ObjectProviderMultiplexer(AbstractObjectProvider): :param registries: A list of :class:`AbstractObjectProviders <.AbstractObjectProvider>` to query when looking up an object """ - def __init__(self, registries: Optional[List[AbstractObjectProvider]] = None): - self.providers: List[AbstractObjectProvider] = registries if registries is not None else [] - def get_identifiable(self, identifier: Identifier) -> Identifiable: + def __init__(self, registries: Optional[List[AbstractObjectProvider[_K, _V]]] = None) -> None: + self.providers: List[AbstractObjectProvider[_K, _V]] = registries if registries is not None else [] + + def get_item(self, key: _K) -> _V: for provider in self.providers: try: - return provider.get_identifiable(identifier) + return provider.get_item(key) except KeyError: pass - raise KeyError("Identifier could not be found in any of the {} consulted registries." + raise KeyError("Key could not be found in any of the {} consulted registries." .format(len(self.providers))) diff --git a/sdk/basyx/aas/util/identification.py b/sdk/basyx/aas/util/identification.py index 74cccd99a..7b88d6818 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, Set +from typing import Optional, Dict, Union from .. import model @@ -70,7 +70,7 @@ 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): + 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 @@ -100,7 +100,7 @@ def generate_id(self, proposal: Optional[str] = None) -> model.Identifier: iri = "{}{}".format(self._namespace, proposal) # Try to find iri in provider. If it does not exist (KeyError), we found a unique one to return try: - self.provider.get_identifiable(iri) + self.provider.get_item(iri) except KeyError: self._counter_cache[proposal] = counter return iri diff --git a/sdk/docs/source/model/provider.rst b/sdk/docs/source/model/provider.rst index a9d990154..9d9072f6f 100644 --- a/sdk/docs/source/model/provider.rst +++ b/sdk/docs/source/model/provider.rst @@ -3,4 +3,6 @@ provider - Providers for storing and retrieving AAS-objects .. automodule:: basyx.aas.model.provider +.. autoclass:: _K +.. autoclass:: _V .. autoclass:: _IT diff --git a/sdk/test/adapter/aasx/test_aasx.py b/sdk/test/adapter/aasx/test_aasx.py index a83c60186..ceeb6ea96 100644 --- a/sdk/test/adapter/aasx/test_aasx.py +++ b/sdk/test/adapter/aasx/test_aasx.py @@ -100,7 +100,7 @@ def test_writing_reading_example_aas(self) -> None: f"{[warning.message for warning in w]}") # Read AASX file - new_data: model.DictObjectStore[model.Identifiable] = model.DictObjectStore() + 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) diff --git a/sdk/test/adapter/json/test_json_deserialization.py b/sdk/test/adapter/json/test_json_deserialization.py index 0dba6dbdb..bc0aaea46 100644 --- a/sdk/test/adapter/json/test_json_deserialization.py +++ b/sdk/test/adapter/json/test_json_deserialization.py @@ -165,8 +165,8 @@ def test_duplicate_identifier(self) -> None: def test_duplicate_identifier_object_store(self) -> None: sm_id = "http://acplt.org/test_submodel" - def get_clean_store() -> model.DictObjectStore: - store: model.DictObjectStore = model.DictObjectStore() + def get_clean_store() -> model.DictIdentifiableStore: + store: model.DictIdentifiableStore[model.Identifiable] = model.DictIdentifiableStore() submodel_ = model.Submodel(sm_id, id_short="test123") store.add(submodel_) return store diff --git a/sdk/test/adapter/json/test_json_serialization.py b/sdk/test/adapter/json/test_json_serialization.py index 8e9bc8d01..8a3aa370e 100644 --- a/sdk/test/adapter/json/test_json_serialization.py +++ b/sdk/test/adapter/json/test_json_serialization.py @@ -96,7 +96,7 @@ def test_aas_example_serialization(self) -> None: validate(instance=json_data, schema=aas_json_schema) def test_submodel_template_serialization(self) -> None: - data: model.DictObjectStore[model.Identifiable] = model.DictObjectStore() + 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) @@ -139,7 +139,7 @@ def test_missing_serialization(self) -> None: validate(instance=json_data, schema=aas_json_schema) def test_concept_description_serialization(self) -> None: - data: model.DictObjectStore[model.Identifiable] = model.DictObjectStore() + 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) diff --git a/sdk/test/adapter/json/test_json_serialization_deserialization.py b/sdk/test/adapter/json/test_json_serialization_deserialization.py index e33921a21..ffa99bf59 100644 --- a/sdk/test/adapter/json/test_json_serialization_deserialization.py +++ b/sdk/test/adapter/json/test_json_serialization_deserialization.py @@ -83,7 +83,7 @@ def test_example_missing_attributes_serialization_deserialization(self) -> None: class JsonSerializationDeserializationTest4(unittest.TestCase): def test_example_submodel_template_serialization_deserialization(self) -> None: - data: model.DictObjectStore[model.Identifiable] = model.DictObjectStore() + 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) @@ -96,11 +96,11 @@ def test_example_submodel_template_serialization_deserialization(self) -> None: class JsonSerializationDeserializationTest5(unittest.TestCase): def test_example_all_examples_serialization_deserialization(self) -> None: - data: model.DictObjectStore[model.Identifiable] = create_example() + data: model.DictIdentifiableStore[model.Identifiable] = create_example() file = io.StringIO() write_aas_json_file(file=file, data=data) # try deserializing the json string into a DictObjectStore of AAS objects with help of the json module file.seek(0) json_object_store = read_aas_json_file(file, failsafe=False) checker = AASDataChecker(raise_immediately=True) - checker.check_object_store(json_object_store, data) + checker.check_identifiable_store(json_object_store, data) diff --git a/sdk/test/adapter/xml/test_xml_deserialization.py b/sdk/test/adapter/xml/test_xml_deserialization.py index 331ad98c5..822beed86 100644 --- a/sdk/test/adapter/xml/test_xml_deserialization.py +++ b/sdk/test/adapter/xml/test_xml_deserialization.py @@ -274,8 +274,8 @@ def test_duplicate_identifier(self) -> None: def test_duplicate_identifier_object_store(self) -> None: sm_id = "http://acplt.org/test_submodel" - def get_clean_store() -> model.DictObjectStore: - store: model.DictObjectStore = model.DictObjectStore() + def get_clean_store() -> model.DictIdentifiableStore: + store: model.DictIdentifiableStore[model.Identifiable] = model.DictIdentifiableStore() submodel_ = model.Submodel(sm_id, id_short="test123") store.add(submodel_) return store diff --git a/sdk/test/adapter/xml/test_xml_serialization.py b/sdk/test/adapter/xml/test_xml_serialization.py index e07e10255..e9f23b688 100644 --- a/sdk/test/adapter/xml/test_xml_serialization.py +++ b/sdk/test/adapter/xml/test_xml_serialization.py @@ -39,7 +39,7 @@ def test_random_object_serialization(self) -> None: test_aas = model.AssetAdministrationShell(model.AssetInformation(global_asset_id="Test"), aas_identifier, submodel={submodel_reference}) - test_data: model.DictObjectStore[model.Identifiable] = model.DictObjectStore() + test_data: model.DictIdentifiableStore[model.Identifiable] = model.DictIdentifiableStore() test_data.add(test_aas) test_data.add(submodel) @@ -65,7 +65,7 @@ def test_random_object_serialization(self) -> None: test_aas = model.AssetAdministrationShell(model.AssetInformation(global_asset_id="Test"), aas_identifier, submodel={submodel_reference}) # serialize object to xml - test_data: model.DictObjectStore[model.Identifiable] = model.DictObjectStore() + test_data: model.DictIdentifiableStore[model.Identifiable] = model.DictIdentifiableStore() test_data.add(test_aas) test_data.add(submodel) @@ -94,7 +94,7 @@ def test_full_example_serialization(self) -> None: root = etree.parse(file, parser=parser) def test_submodel_template_serialization(self) -> None: - data: model.DictObjectStore[model.Identifiable] = model.DictObjectStore() + 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) @@ -134,7 +134,7 @@ def test_missing_serialization(self) -> None: root = etree.parse(file, parser=parser) def test_concept_description(self) -> None: - data: model.DictObjectStore[model.Identifiable] = model.DictObjectStore() + 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 7a4f5c12d..057003e13 100644 --- a/sdk/test/adapter/xml/test_xml_serialization_deserialization.py +++ b/sdk/test/adapter/xml/test_xml_serialization_deserialization.py @@ -17,7 +17,7 @@ from basyx.aas.examples.data._helper import AASDataChecker -def _serialize_and_deserialize(data: model.DictObjectStore) -> model.DictObjectStore: +def _serialize_and_deserialize(data: model.DictIdentifiableStore) -> model.DictIdentifiableStore: file = io.BytesIO() write_aas_xml_file(file=file, data=data) @@ -43,17 +43,17 @@ def test_example_missing_attributes_serialization_deserialization(self) -> None: example_aas_missing_attributes.check_full_example(checker, object_store) def test_example_submodel_template_serialization_deserialization(self) -> None: - data: model.DictObjectStore[model.Identifiable] = model.DictObjectStore() + data: model.DictIdentifiableStore[model.Identifiable] = model.DictIdentifiableStore() data.add(example_submodel_template.create_example_submodel_template()) object_store = _serialize_and_deserialize(data) checker = AASDataChecker(raise_immediately=True) example_submodel_template.check_full_example(checker, object_store) def test_example_all_examples_serialization_deserialization(self) -> None: - data: model.DictObjectStore[model.Identifiable] = create_example() + data: model.DictIdentifiableStore[model.Identifiable] = create_example() object_store = _serialize_and_deserialize(data) checker = AASDataChecker(raise_immediately=True) - checker.check_object_store(object_store, data) + checker.check_identifiable_store(object_store, data) class XMLSerializationDeserializationSingleObjectTest(unittest.TestCase): diff --git a/sdk/test/backend/test_couchdb.py b/sdk/test/backend/test_couchdb.py index 36e5ef039..1e0e5d33d 100644 --- a/sdk/test/backend/test_couchdb.py +++ b/sdk/test/backend/test_couchdb.py @@ -23,65 +23,67 @@ COUCHDB_ERROR)) class CouchDBBackendTest(unittest.TestCase): def setUp(self) -> None: - self.object_store = couchdb.CouchDBObjectStore(TEST_CONFIG['couchdb']['url'], - TEST_CONFIG['couchdb']['database']) + self.coch_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.object_store.check_database() + self.coch_identifiable_store.check_database() def tearDown(self) -> None: - self.object_store.clear() + self.coch_identifiable_store.clear() def test_object_store_add(self): test_object = create_example_submodel() - self.object_store.add(test_object) + self.coch_identifiable_store.add(test_object) # Note that this test is only checking that there are no errors during adding. # The actual logic is tested together with retrieval in `test_retrieval`. def test_retrieval(self): test_object = create_example_submodel() - self.object_store.add(test_object) + self.coch_identifiable_store.add(test_object) # When retrieving the object, we should get the *same* instance as we added - test_object_retrieved = self.object_store.get_identifiable('https://acplt.org/Test_Submodel') + test_object_retrieved = self.coch_identifiable_store.get_item('https://acplt.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.object_store.get_identifiable('https://acplt.org/Test_Submodel') + test_object_retrieved_again = self.coch_identifiable_store.get_item('https://acplt.org/Test_Submodel') self.assertIs(test_object_retrieved, test_object_retrieved_again) def test_example_submodel_storing(self) -> None: example_submodel = create_example_submodel() # Add exmaple submodel - self.object_store.add(example_submodel) - self.assertEqual(1, len(self.object_store)) - self.assertIn(example_submodel, self.object_store) + self.coch_identifiable_store.add(example_submodel) + self.assertEqual(1, len(self.coch_identifiable_store)) + self.assertIn(example_submodel, self.coch_identifiable_store) # Restore example submodel and check data - submodel_restored = self.object_store.get_identifiable('https://acplt.org/Test_Submodel') + submodel_restored = self.coch_identifiable_store.get_item('https://acplt.org/Test_Submodel') assert (isinstance(submodel_restored, model.Submodel)) checker = AASDataChecker(raise_immediately=True) check_example_submodel(checker, submodel_restored) # Delete example submodel - self.object_store.discard(submodel_restored) - self.assertNotIn(example_submodel, self.object_store) + self.coch_identifiable_store.discard(submodel_restored) + self.assertNotIn(example_submodel, self.coch_identifiable_store) def test_iterating(self) -> None: example_data = create_full_example() # Add all objects for item in example_data: - self.object_store.add(item) + self.coch_identifiable_store.add(item) - self.assertEqual(5, len(self.object_store)) + self.assertEqual(5, len(self.coch_identifiable_store)) # Iterate objects, add them to a DictObjectStore and check them - retrieved_data_store: model.provider.DictObjectStore[model.Identifiable] = model.provider.DictObjectStore() - for item in self.object_store: + retrieved_data_store: model.provider.DictIdentifiableStore[model.Identifiable] = ( + model.provider.DictIdentifiableStore() + ) + for item in self.coch_identifiable_store: retrieved_data_store.add(item) checker = AASDataChecker(raise_immediately=True) check_full_example(checker, retrieved_data_store) @@ -89,22 +91,22 @@ def test_iterating(self) -> None: def test_key_errors(self) -> None: # Double adding an object should raise a KeyError example_submodel = create_example_submodel() - self.object_store.add(example_submodel) + self.coch_identifiable_store.add(example_submodel) with self.assertRaises(KeyError) as cm: - self.object_store.add(example_submodel) + self.coch_identifiable_store.add(example_submodel) self.assertEqual("'Identifiable with id https://acplt.org/Test_Submodel already exists in " "CouchDB database'", str(cm.exception)) # Querying a deleted object should raise a KeyError - retrieved_submodel = self.object_store.get_identifiable('https://acplt.org/Test_Submodel') - self.object_store.discard(example_submodel) + retrieved_submodel = self.coch_identifiable_store.get_item('https://acplt.org/Test_Submodel') + self.coch_identifiable_store.discard(example_submodel) with self.assertRaises(KeyError) as cm: - self.object_store.get_identifiable('https://acplt.org/Test_Submodel') + self.coch_identifiable_store.get_item('https://acplt.org/Test_Submodel') self.assertEqual("'No Identifiable with id https://acplt.org/Test_Submodel found in CouchDB database'", str(cm.exception)) # Double deleting should also raise a KeyError with self.assertRaises(KeyError) as cm: - self.object_store.discard(retrieved_submodel) + self.coch_identifiable_store.discard(retrieved_submodel) self.assertEqual("'No AAS object with id https://acplt.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 7d96d8713..0c019ccfa 100644 --- a/sdk/test/backend/test_local_file.py +++ b/sdk/test/backend/test_local_file.py @@ -6,7 +6,6 @@ # SPDX-License-Identifier: MIT import os.path import shutil -import unittest import unittest.mock from basyx.aas.backend import local_file @@ -19,64 +18,66 @@ class LocalFileBackendTest(unittest.TestCase): def setUp(self) -> None: - self.object_store = local_file.LocalFileObjectStore(store_path) - self.object_store.check_directory(create=True) + self.identifiable_store = local_file.LocalFileIdentifiableStore(store_path) + self.identifiable_store.check_directory(create=True) def tearDown(self) -> None: try: - self.object_store.clear() + self.identifiable_store.clear() finally: shutil.rmtree(store_path) def test_object_store_add(self): test_object = create_example_submodel() - self.object_store.add(test_object) + self.identifiable_store.add(test_object) # Note that this test is only checking that there are no errors during adding. # The actual logic is tested together with retrieval in `test_retrieval`. def test_retrieval(self): test_object = create_example_submodel() - self.object_store.add(test_object) + self.identifiable_store.add(test_object) # When retrieving the object, we should get the *same* instance as we added - test_object_retrieved = self.object_store.get_identifiable('https://acplt.org/Test_Submodel') + test_object_retrieved = self.identifiable_store.get_item('https://acplt.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.object_store.get_identifiable('https://acplt.org/Test_Submodel') + test_object_retrieved_again = self.identifiable_store.get_item('https://acplt.org/Test_Submodel') self.assertIs(test_object_retrieved, test_object_retrieved_again) def test_example_submodel_storing(self) -> None: example_submodel = create_example_submodel() # Add exmaple submodel - self.object_store.add(example_submodel) - self.assertEqual(1, len(self.object_store)) - self.assertIn(example_submodel, self.object_store) + self.identifiable_store.add(example_submodel) + self.assertEqual(1, len(self.identifiable_store)) + self.assertIn(example_submodel, self.identifiable_store) # Restore example submodel and check data - submodel_restored = self.object_store.get_identifiable('https://acplt.org/Test_Submodel') + submodel_restored = self.identifiable_store.get_item('https://acplt.org/Test_Submodel') assert (isinstance(submodel_restored, model.Submodel)) checker = AASDataChecker(raise_immediately=True) check_example_submodel(checker, submodel_restored) # Delete example submodel - self.object_store.discard(submodel_restored) - self.assertNotIn(example_submodel, self.object_store) + self.identifiable_store.discard(submodel_restored) + self.assertNotIn(example_submodel, self.identifiable_store) def test_iterating(self) -> None: example_data = create_full_example() # Add all objects for item in example_data: - self.object_store.add(item) + self.identifiable_store.add(item) - self.assertEqual(5, len(self.object_store)) + self.assertEqual(5, len(self.identifiable_store)) # Iterate objects, add them to a DictObjectStore and check them - retrieved_data_store: model.provider.DictObjectStore[model.Identifiable] = model.provider.DictObjectStore() - for item in self.object_store: + 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) check_full_example(checker, retrieved_data_store) @@ -84,23 +85,23 @@ def test_iterating(self) -> None: def test_key_errors(self) -> None: # Double adding an object should raise a KeyError example_submodel = create_example_submodel() - self.object_store.add(example_submodel) + self.identifiable_store.add(example_submodel) with self.assertRaises(KeyError) as cm: - self.object_store.add(example_submodel) + self.identifiable_store.add(example_submodel) self.assertEqual("'Identifiable with id https://acplt.org/Test_Submodel already exists in " "local file database'", str(cm.exception)) # Querying a deleted object should raise a KeyError - retrieved_submodel = self.object_store.get_identifiable('https://acplt.org/Test_Submodel') - self.object_store.discard(example_submodel) + retrieved_submodel = self.identifiable_store.get_item('https://acplt.org/Test_Submodel') + self.identifiable_store.discard(example_submodel) with self.assertRaises(KeyError) as cm: - self.object_store.get_identifiable('https://acplt.org/Test_Submodel') + self.identifiable_store.get_item('https://acplt.org/Test_Submodel') self.assertEqual("'No Identifiable with id https://acplt.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.object_store.discard(retrieved_submodel) + self.identifiable_store.discard(retrieved_submodel) self.assertEqual("'No AAS object with id https://acplt.org/Test_Submodel exists in " "local file database'", str(cm.exception)) diff --git a/sdk/test/examples/test__init__.py b/sdk/test/examples/test__init__.py index ea5eba30d..a2154c47e 100644 --- a/sdk/test/examples/test__init__.py +++ b/sdk/test/examples/test__init__.py @@ -1,4 +1,4 @@ -# Copyright (c) 2024 the Eclipse BaSyx Authors +# Copyright (c) 2025 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. @@ -13,10 +13,10 @@ class TestExampleFunctions(unittest.TestCase): def test_create_example(self): - obj_store = create_example() + id_store = create_example() # Check that the object store is not empty - self.assertGreater(len(obj_store), 0) + self.assertGreater(len(id_store), 0) # Check that the object store contains expected elements expected_ids = [ @@ -25,7 +25,7 @@ def test_create_example(self): 'https://acplt.org/Test_ConceptDescription_Mandatory' ] for id in expected_ids: - self.assertIsNotNone(obj_store.get_identifiable(id)) + self.assertIsNotNone(id_store.get_item(id)) def test_create_example_aas_binding(self): obj_store = create_example_aas_binding() @@ -38,9 +38,9 @@ def test_create_example_aas_binding(self): sm_id = 'https://acplt.org/Test_Submodel_Template' cd_id = 'https://acplt.org/Test_ConceptDescription_Mandatory' - aas = obj_store.get_identifiable(aas_id) - sm = obj_store.get_identifiable(sm_id) - cd = obj_store.get_identifiable(cd_id) + aas = obj_store.get_item(aas_id) + sm = obj_store.get_item(sm_id) + cd = obj_store.get_item(cd_id) self.assertIsNotNone(aas) self.assertIsNotNone(sm) diff --git a/sdk/test/examples/test_examples.py b/sdk/test/examples/test_examples.py index 5695e3b94..b6e786512 100644 --- a/sdk/test/examples/test_examples.py +++ b/sdk/test/examples/test_examples.py @@ -40,50 +40,50 @@ def test_example_submodel(self): def test_full_example(self): checker = AASDataChecker(raise_immediately=True) - obj_store = model.DictObjectStore() + id_store = model.DictIdentifiableStore() with self.assertRaises(AssertionError) as cm: - example_aas.check_full_example(checker, obj_store) + example_aas.check_full_example(checker, id_store) self.assertIn("AssetAdministrationShell[https://acplt.org/Test_AssetAdministrationShell]", str(cm.exception)) - obj_store = example_aas.create_full_example() - example_aas.check_full_example(checker, obj_store) + id_store = example_aas.create_full_example() + example_aas.check_full_example(checker, id_store) failed_shell = model.AssetAdministrationShell( asset_information=model.AssetInformation(global_asset_id='test'), id_='test' ) - obj_store.add(failed_shell) + id_store.add(failed_shell) with self.assertRaises(AssertionError) as cm: - example_aas.check_full_example(checker, obj_store) + example_aas.check_full_example(checker, id_store) self.assertIn("AssetAdministrationShell[test]", str(cm.exception)) - obj_store.discard(failed_shell) + id_store.discard(failed_shell) failed_submodel = model.Submodel(id_='test') - obj_store.add(failed_submodel) + id_store.add(failed_submodel) with self.assertRaises(AssertionError) as cm: - example_aas.check_full_example(checker, obj_store) + example_aas.check_full_example(checker, id_store) self.assertIn("Submodel[test]", str(cm.exception)) - obj_store.discard(failed_submodel) + id_store.discard(failed_submodel) failed_cd = model.ConceptDescription(id_='test') - obj_store.add(failed_cd) + id_store.add(failed_cd) with self.assertRaises(AssertionError) as cm: - example_aas.check_full_example(checker, obj_store) + example_aas.check_full_example(checker, id_store) self.assertIn("ConceptDescription[test]", str(cm.exception)) - obj_store.discard(failed_cd) + id_store.discard(failed_cd) class DummyIdentifiable(model.Identifiable): def __init__(self, id_: model.Identifier): super().__init__() self.id = id_ failed_identifiable = DummyIdentifiable(id_='test') - obj_store.add(failed_identifiable) + id_store.add(failed_identifiable) with self.assertRaises(KeyError) as cm: - example_aas.check_full_example(checker, obj_store) + example_aas.check_full_example(checker, id_store) self.assertIn("Check for DummyIdentifiable[test] not implemented", str(cm.exception)) - obj_store.discard(failed_identifiable) - example_aas.check_full_example(checker, obj_store) + id_store.discard(failed_identifiable) + example_aas.check_full_example(checker, id_store) class ExampleAASMandatoryTest(unittest.TestCase): @@ -161,16 +161,16 @@ def test_example_submodel_template(self): def test_full_example(self): checker = AASDataChecker(raise_immediately=True) - obj_store: model.DictObjectStore[model.Identifiable] = model.DictObjectStore() - obj_store.add(example_submodel_template.create_example_submodel_template()) - example_submodel_template.check_full_example(checker, obj_store) + id_store: model.DictIdentifiableStore[model.Identifiable] = model.DictIdentifiableStore() + id_store.add(example_submodel_template.create_example_submodel_template()) + example_submodel_template.check_full_example(checker, id_store) failed_submodel = model.Submodel(id_='test') - obj_store.add(failed_submodel) + id_store.add(failed_submodel) with self.assertRaises(AssertionError) as cm: - example_submodel_template.check_full_example(checker, obj_store) + example_submodel_template.check_full_example(checker, id_store) self.assertIn("Given submodel list must not have extra submodels", str(cm.exception)) self.assertIn("Submodel[test]", str(cm.exception)) - obj_store.discard(failed_submodel) + id_store.discard(failed_submodel) - example_submodel_template.check_full_example(checker, obj_store) + example_submodel_template.check_full_example(checker, id_store) diff --git a/sdk/test/examples/test_tutorials.py b/sdk/test/examples/test_tutorials.py index 2b5dbe54e..dcec6b3bc 100644 --- a/sdk/test/examples/test_tutorials.py +++ b/sdk/test/examples/test_tutorials.py @@ -22,7 +22,7 @@ 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') - store = model.DictObjectStore({tutorial_create_simple_aas.submodel}) + store = model.DictIdentifiableStore({tutorial_create_simple_aas.submodel}) next(iter(tutorial_create_simple_aas.aas.submodel)).resolve(store) def test_tutorial_storage(self): diff --git a/sdk/test/model/test_base.py b/sdk/test/model/test_base.py index 460bce563..631616b19 100644 --- a/sdk/test/model/test_base.py +++ b/sdk/test/model/test_base.py @@ -854,12 +854,12 @@ def test_equality(self): def test_reference_typing(self) -> None: dummy_submodel = model.Submodel("urn:x-test:x") - class DummyObjectProvider(model.AbstractObjectProvider): - def get_identifiable(self, identifier: Identifier) -> 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) - submodel: model.Submodel = x.resolve(DummyObjectProvider()) + submodel: model.Submodel = x.resolve(DummyIdentifiableProvider()) self.assertIs(submodel, submodel) def test_resolve(self) -> None: @@ -868,8 +868,8 @@ def test_resolve(self) -> None: list_ = model.SubmodelElementList("list", model.SubmodelElementCollection, {collection}) submodel = model.Submodel("urn:x-test:submodel", {list_}) - class DummyObjectProvider(model.AbstractObjectProvider): - def get_identifiable(self, identifier: Identifier) -> Identifiable: + class DummyIdentifiableProvider(model.AbstractObjectProvider[model.Identifier, model.Identifiable]): + def get_item(self, identifier: Identifier) -> Identifiable: if identifier == submodel.id: return submodel else: @@ -881,7 +881,7 @@ def get_identifiable(self, identifier: Identifier) -> Identifiable: model.Key(model.KeyTypes.PROPERTY, "prop")), model.Property) with self.assertRaises(KeyError) as cm: - ref1.resolve(DummyObjectProvider()) + 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"), @@ -890,7 +890,7 @@ def get_identifiable(self, identifier: Identifier) -> Identifiable: model.Key(model.KeyTypes.PROPERTY, "prop")), model.Property) with self.assertRaises(KeyError) as cm_2: - ref2.resolve(DummyObjectProvider()) + ref2.resolve(DummyIdentifiableProvider()) self.assertEqual("'Referable with index 99 not found in SubmodelElementList[urn:x-test:submodel / list]'", str(cm_2.exception)) @@ -899,7 +899,7 @@ def get_identifiable(self, identifier: Identifier) -> Identifiable: model.Key(model.KeyTypes.SUBMODEL_ELEMENT_COLLECTION, "0"), model.Key(model.KeyTypes.PROPERTY, "prop")), model.Property) - self.assertIs(prop, ref3.resolve(DummyObjectProvider())) + 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"), @@ -908,7 +908,7 @@ def get_identifiable(self, identifier: Identifier) -> Identifiable: model.Key(model.KeyTypes.PROPERTY, "prop")), model.Property) with self.assertRaises(TypeError) as cm_3: - ref4.resolve(DummyObjectProvider()) + 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)) @@ -919,14 +919,14 @@ def get_identifiable(self, identifier: Identifier) -> Identifiable: 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(DummyObjectProvider()) + ref5.resolve(DummyIdentifiableProvider()) 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) # 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: - ref6.resolve(DummyObjectProvider()) + ref6.resolve(DummyIdentifiableProvider()) self.assertIs(submodel, cm_6.exception.value) with self.assertRaises(ValueError) as cm_7: @@ -939,7 +939,7 @@ def get_identifiable(self, identifier: Identifier) -> Identifiable: model.Key(model.KeyTypes.PROPERTY, "prop_false")), model.Property) with self.assertRaises(KeyError) as cm_8: - ref8.resolve(DummyObjectProvider()) + 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)) @@ -949,7 +949,7 @@ def get_identifiable(self, identifier: Identifier) -> Identifiable: model.SubmodelElementCollection) with self.assertRaises(ValueError) as cm_9: - ref9.resolve(DummyObjectProvider()) + 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)) diff --git a/sdk/test/model/test_provider.py b/sdk/test/model/test_provider.py index 68fb01cff..36a272dc9 100644 --- a/sdk/test/model/test_provider.py +++ b/sdk/test/model/test_provider.py @@ -20,52 +20,56 @@ def setUp(self) -> None: self.submodel2 = model.Submodel("urn:x-test:submodel2") def test_store_retrieve(self) -> None: - object_store: model.DictObjectStore[model.AssetAdministrationShell] = model.DictObjectStore() - object_store.add(self.aas1) - object_store.add(self.aas2) - self.assertIn(self.aas1, object_store) + identifiable_store: model.DictIdentifiableStore[model.AssetAdministrationShell] = model.DictIdentifiableStore() + identifiable_store.add(self.aas1) + identifiable_store.add(self.aas2) + self.assertIn(self.aas1, identifiable_store) property = model.Property('test', model.datatypes.String) - self.assertFalse(property in object_store) + self.assertFalse(property in identifiable_store) aas3 = model.AssetAdministrationShell(model.AssetInformation(global_asset_id="http://acplt.org/TestAsset/"), "urn:x-test:aas1") with self.assertRaises(KeyError) as cm: - object_store.add(aas3) + identifiable_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(2, len(object_store)) + self.assertEqual(2, len(identifiable_store)) self.assertIs(self.aas1, - object_store.get_identifiable("urn:x-test:aas1")) + identifiable_store.get_item("urn:x-test:aas1")) self.assertIs(self.aas1, - object_store.get("urn:x-test:aas1")) - object_store.discard(self.aas1) - object_store.discard(self.aas1) + identifiable_store.get("urn:x-test:aas1")) + identifiable_store.discard(self.aas1) + identifiable_store.discard(self.aas1) with self.assertRaises(KeyError) as cm: - object_store.get_identifiable("urn:x-test:aas1") - self.assertIsNone(object_store.get("urn:x-test:aas1")) + identifiable_store.get_item("urn:x-test:aas1") + self.assertIsNone(identifiable_store.get("urn:x-test:aas1")) self.assertEqual("'urn:x-test:aas1'", str(cm.exception)) - self.assertIs(self.aas2, object_store.pop()) - self.assertEqual(0, len(object_store)) + self.assertIs(self.aas2, identifiable_store.pop()) + self.assertEqual(0, len(identifiable_store)) def test_store_update(self) -> None: - object_store1: model.DictObjectStore[model.AssetAdministrationShell] = model.DictObjectStore() - object_store1.add(self.aas1) - object_store2: model.DictObjectStore[model.AssetAdministrationShell] = model.DictObjectStore() - object_store2.add(self.aas2) - object_store1.update(object_store2) - self.assertIsInstance(object_store1, model.DictObjectStore) - self.assertIn(self.aas2, object_store1) + identifiable_store1: model.DictIdentifiableStore[model.AssetAdministrationShell] = model.DictIdentifiableStore() + identifiable_store1.add(self.aas1) + identifiable_store2: model.DictIdentifiableStore[model.AssetAdministrationShell] = model.DictIdentifiableStore() + identifiable_store2.add(self.aas2) + identifiable_store1.update(identifiable_store2) + self.assertIsInstance(identifiable_store1, model.DictIdentifiableStore) + self.assertIn(self.aas2, identifiable_store1) def test_provider_multiplexer(self) -> None: - aas_object_store: model.DictObjectStore[model.AssetAdministrationShell] = model.DictObjectStore() - aas_object_store.add(self.aas1) - aas_object_store.add(self.aas2) - submodel_object_store: model.DictObjectStore[model.Submodel] = model.DictObjectStore() - submodel_object_store.add(self.submodel1) - submodel_object_store.add(self.submodel2) + aas_identifiable_store: model.DictIdentifiableStore[model.Identifiable] = ( + model.DictIdentifiableStore() + ) + aas_identifiable_store.add(self.aas1) + aas_identifiable_store.add(self.aas2) + submodel_identifiable_store: model.DictIdentifiableStore[model.Identifiable] = model.DictIdentifiableStore() + submodel_identifiable_store.add(self.submodel1) + submodel_identifiable_store.add(self.submodel2) - multiplexer = model.ObjectProviderMultiplexer([aas_object_store, submodel_object_store]) - self.assertIs(self.aas1, multiplexer.get_identifiable("urn:x-test:aas1")) - self.assertIs(self.submodel1, multiplexer.get_identifiable("urn:x-test:submodel1")) + 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_identifiable("urn:x-test:submodel3") - self.assertEqual("'Identifier could not be found in any of the 2 consulted registries.'", str(cm.exception)) + multiplexer.get_item("urn:x-test:submodel3") + self.assertEqual("'Key could not be found in any of the 2 consulted registries.'", str(cm.exception)) diff --git a/sdk/test/util/test_identification.py b/sdk/test/util/test_identification.py index ebfed8606..0fa640c57 100644 --- a/sdk/test/util/test_identification.py +++ b/sdk/test/util/test_identification.py @@ -24,7 +24,7 @@ def test_generate_uuid_identifier(self): ids.add(identification) def test_generate_iri_identifier(self): - provider = model.DictObjectStore() + provider = model.DictIdentifiableStore() # Check expected Errors when Namespaces are not valid with self.assertRaises(ValueError) as cm: diff --git a/server/app/main.py b/server/app/main.py index 49920f628..276b1297c 100644 --- a/server/app/main.py +++ b/server/app/main.py @@ -12,8 +12,8 @@ import os from basyx.aas.adapter import load_directory from basyx.aas.adapter.aasx import DictSupplementaryFileContainer -from basyx.aas.backend.local_file import LocalFileObjectStore -from basyx.aas.model.provider import DictObjectStore +from basyx.aas.backend.local_file import LocalFileIdentifiableStore +from basyx.aas.model.provider import DictIdentifiableStore from interfaces.repository import WSGIApp from typing import Tuple, Union @@ -44,7 +44,7 @@ def build_storage( env_storage_persistency: bool, env_storage_overwrite: bool, logger: logging.Logger -) -> Tuple[Union[DictObjectStore, LocalFileObjectStore], DictSupplementaryFileContainer]: +) -> Tuple[Union[DictIdentifiableStore, LocalFileIdentifiableStore], DictSupplementaryFileContainer]: """ Configure the server's storage according to the given start-up settings. @@ -62,7 +62,7 @@ def build_storage( """ if env_storage_persistency: - storage_files = LocalFileObjectStore(env_storage) + storage_files = LocalFileIdentifiableStore(env_storage) storage_files.check_directory(create=True) if os.path.isdir(env_input): input_files, input_supp_files = load_directory(env_input) @@ -91,7 +91,7 @@ def build_storage( return input_files, input_supp_files else: logger.warning("INPUT directory \"%s\" not found, starting empty", env_input) - return DictObjectStore(), DictSupplementaryFileContainer() + return DictIdentifiableStore(), DictSupplementaryFileContainer() # -------- WSGI entrypoint -------- diff --git a/server/test/interfaces/test_repository.py b/server/test/interfaces/test_repository.py index 5177dfacb..b3128105e 100644 --- a/server/test/interfaces/test_repository.py +++ b/server/test/interfaces/test_repository.py @@ -91,7 +91,7 @@ def _check_transformed(response, case): class APIWorkflowAAS(AAS_SCHEMA.as_state_machine()): # type: ignore def setup(self): - self.schema.app.object_store = create_full_example() + self.schema.app.coch_identifiable_store = create_full_example() # select random identifier for each test scenario self.schema.base_url = BASE_URL + "/aas/" + random.choice(tuple(IDENTIFIER_AAS)) @@ -108,7 +108,7 @@ def validate_response(self, response, case, additional_checks=()) -> None: class APIWorkflowSubmodel(SUBMODEL_SCHEMA.as_state_machine()): # type: ignore def setup(self): - self.schema.app.object_store = create_full_example() + self.schema.app.coch_identifiable_store = create_full_example() self.schema.base_url = BASE_URL + "/submodels/" + random.choice(tuple(IDENTIFIER_SUBMODEL)) def transform(self, result, direction, case):