diff --git a/compliance_tool/aas_compliance_tool/cli.py b/compliance_tool/aas_compliance_tool/cli.py index 1c10bed91..d09f45447 100644 --- a/compliance_tool/aas_compliance_tool/cli.py +++ b/compliance_tool/aas_compliance_tool/cli.py @@ -10,6 +10,7 @@ "Details of the Asset Administration Shell" specification of Plattform Industrie 4.0. It uses the create_example() from examples.data.__init__.py """ + import argparse import datetime @@ -19,9 +20,11 @@ from basyx.aas.adapter import aasx from basyx.aas.adapter.xml import write_aas_xml_file -from aas_compliance_tool import compliance_check_xml as compliance_tool_xml, \ - compliance_check_json as compliance_tool_json, \ - compliance_check_aasx as compliance_tool_aasx +from aas_compliance_tool import ( + compliance_check_xml as compliance_tool_xml, + compliance_check_json as compliance_tool_json, + compliance_check_aasx as compliance_tool_aasx, +) from basyx.aas.adapter.json import write_aas_json_file from basyx.aas.examples.data import create_example, create_example_aas_binding, TEST_PDF_FILE from aas_compliance_tool.state_manager import ComplianceToolStateManager, Status @@ -32,56 +35,66 @@ def parse_cli_arguments() -> argparse.ArgumentParser: This function returns the argument-parser for the cli """ parser = argparse.ArgumentParser( - prog='compliance_tool', + prog="compliance_tool", description='Compliance tool for creating and checking json and xml files in compliance with "Details of the ' - 'Asset Administration Shell" specification of Plattform Industrie 4.0. \n\n' - 'This tool has five features: \n' - '1. create a xml or json file or an AASX file using xml or json files with example aas elements\n' - '2. check if a given xml or json file is deserializable and therefore compliant with the schema\n' - '3. check if the data in a given xml, json or aasx file is the same as the example data\n' - '4. check if two given xml, json or aasx files contain the same aas elements in any order\n\n' - 'As a first argument, the feature must be specified (create, deserialization, example, ' - 'files) or in short (c, d, e or f).\n' - 'Depending the chosen feature, different additional arguments must be specified:\n' - 'create or c: path to the file which shall be created (file_1)\n' - 'deseriable or d: file to be checked (file_1)\n' - 'example or e: file to be checked (file_1)\n' - 'file_compare or f: files to compare (file_1, file_2)\n,' - 'In any case, it must be specified whether the (given or created) files are json (--json) or ' - 'xml (--xml).\n' - 'All features support reading/writing AASX packages instead of plain XML or JSON ' - 'files via the --aasx option.\n\n' - 'Additionally, the tool offers some extra features for more convenient usage:\n\n' - 'a. Different levels of verbosity:\n' - ' Default output is just the status for each step performed. With -v or --verbose, additional ' - 'information in case of status = FAILED will be provided. With one more -v or --verbose, additional' - ' information even in case of status = SUCCESS or WARNINGS will be provided.\n' - 'b. Suppressing output on success:\n' - ' With -q or --quite no output will be generated if the status = SUCCESS.\n' - 'c. Save log additionally in a logfile:\n' - ' With -l or --logfile, a path to the file where the logfiles shall be created can be specified.', - formatter_class=argparse.RawTextHelpFormatter) - - parser.add_argument('action', choices=['create', 'c', 'deserialization', 'd', 'example', 'e', - 'files', 'f'], - help='c or create: creates a file with example data\n' - 'd or deserialization: checks if a given file is compliance with the official schema and ' - 'is deserializable\n' - 'e or example: checks if a given file contains the example aas elements\n' - 'f or file_compare: checks if two given files contain the same aas elements in any order') - parser.add_argument('file_1', help="path to file 1") - parser.add_argument('file_2', nargs='?', default=None, help="path to file 2: is required if action f or files is " - "chosen") - parser.add_argument('-v', '--verbose', help="Print detailed information for each check. Multiple -v options " - "increase the verbosity. 1: Detailed error information, 2: Additional " - "detailed success information", action='count', default=0) - parser.add_argument('-q', '--quiet', help="no information output if successful", action='store_true') + 'Asset Administration Shell" specification of Plattform Industrie 4.0. \n\n' + "This tool has five features: \n" + "1. create a xml or json file or an AASX file using xml or json files with example aas elements\n" + "2. check if a given xml or json file is deserializable and therefore compliant with the schema\n" + "3. check if the data in a given xml, json or aasx file is the same as the example data\n" + "4. check if two given xml, json or aasx files contain the same aas elements in any order\n\n" + "As a first argument, the feature must be specified (create, deserialization, example, " + "files) or in short (c, d, e or f).\n" + "Depending the chosen feature, different additional arguments must be specified:\n" + "create or c: path to the file which shall be created (file_1)\n" + "deseriable or d: file to be checked (file_1)\n" + "example or e: file to be checked (file_1)\n" + "file_compare or f: files to compare (file_1, file_2)\n," + "In any case, it must be specified whether the (given or created) files are json (--json) or " + "xml (--xml).\n" + "All features support reading/writing AASX packages instead of plain XML or JSON " + "files via the --aasx option.\n\n" + "Additionally, the tool offers some extra features for more convenient usage:\n\n" + "a. Different levels of verbosity:\n" + " Default output is just the status for each step performed. With -v or --verbose, additional " + "information in case of status = FAILED will be provided. With one more -v or --verbose, additional" + " information even in case of status = SUCCESS or WARNINGS will be provided.\n" + "b. Suppressing output on success:\n" + " With -q or --quite no output will be generated if the status = SUCCESS.\n" + "c. Save log additionally in a logfile:\n" + " With -l or --logfile, a path to the file where the logfiles shall be created can be specified.", + formatter_class=argparse.RawTextHelpFormatter, + ) + + parser.add_argument( + "action", + choices=["create", "c", "deserialization", "d", "example", "e", "files", "f"], + help="c or create: creates a file with example data\n" + "d or deserialization: checks if a given file is compliance with the official schema and " + "is deserializable\n" + "e or example: checks if a given file contains the example aas elements\n" + "f or file_compare: checks if two given files contain the same aas elements in any order", + ) + parser.add_argument("file_1", help="path to file 1") + parser.add_argument( + "file_2", nargs="?", default=None, help="path to file 2: is required if action f or files is chosen" + ) + parser.add_argument( + "-v", + "--verbose", + help="Print detailed information for each check. Multiple -v options " + "increase the verbosity. 1: Detailed error information, 2: Additional " + "detailed success information", + action="count", + default=0, + ) + parser.add_argument("-q", "--quiet", help="no information output if successful", action="store_true") group = parser.add_mutually_exclusive_group(required=True) - group.add_argument('--json', help="Use AAS json format when checking or creating files", action='store_true') - group.add_argument('--xml', help="Use AAS xml format when checking or creating files", action='store_true') - parser.add_argument('-l', '--logfile', help="Log file to be created in addition to output to stdout", default=None) - parser.add_argument('--aasx', help="Create or read AASX files", action='store_true') - parser.add_argument('--dont-check-extensions', help="Don't compare Extensions", action='store_false') + group.add_argument("--json", help="Use AAS json format when checking or creating files", action="store_true") + group.add_argument("--xml", help="Use AAS xml format when checking or creating files", action="store_true") + parser.add_argument("-l", "--logfile", help="Log file to be created in addition to output to stdout", default=None) + parser.add_argument("--aasx", help="Create or read AASX files", action="store_true") + parser.add_argument("--dont-check-extensions", help="Don't compare Extensions", action="store_false") return parser @@ -96,26 +109,24 @@ def main(): logger.propagate = False logger.addHandler(manager) - data_checker_kwargs = { - 'check_extensions': args.dont_check_extensions - } + data_checker_kwargs = {"check_extensions": args.dont_check_extensions} - if args.action == 'create' or args.action == 'c': - manager.add_step('Create example data') + if args.action == "create" or args.action == "c": + manager.add_step("Create example data") if args.aasx: data = create_example_aas_binding() else: data = create_example() manager.set_step_status(Status.SUCCESS) try: - manager.add_step('Open file') + manager.add_step("Open file") if args.aasx: with aasx.AASXWriter(args.file_1) as writer: manager.set_step_status(Status.SUCCESS) - manager.add_step('Write data to file') + manager.add_step("Write data to file") files = aasx.DictSupplementaryFileContainer() - with open(TEST_PDF_FILE, 'rb') as f: + with open(TEST_PDF_FILE, "rb") as f: files.add_file("/TestFile.pdf", f, "application/pdf") # Create OPC/AASX core properties @@ -129,51 +140,58 @@ def main(): cp.version = "2.0.1" cp.title = "Test Title" - writer.write_aas_objects("/aasx/data.json" if args.json else "/aasx/data.xml", - [obj.id for obj in data], data, files, - write_json=args.json) + writer.write_aas_objects( + "/aasx/data.json" if args.json else "/aasx/data.xml", + [obj.id for obj in data], + data, + files, + write_json=args.json, + ) writer.write_core_properties(cp) manager.set_step_status(Status.SUCCESS) elif args.json: - with open(args.file_1, 'w', encoding='utf-8-sig') as file: + with open(args.file_1, "w", encoding="utf-8-sig") as file: manager.set_step_status(Status.SUCCESS) - manager.add_step('Write data to file') + manager.add_step("Write data to file") write_aas_json_file(file=file, data=data, indent=4) manager.set_step_status(Status.SUCCESS) elif args.xml: - with open(args.file_1, 'wb') as file: + with open(args.file_1, "wb") as file: manager.set_step_status(Status.SUCCESS) - manager.add_step('Write data to file') + manager.add_step("Write data to file") write_aas_xml_file(file=file, data=data, pretty_print=True) manager.set_step_status(Status.SUCCESS) except IOError as error: logger.error(error) manager.set_step_status(Status.FAILED) - elif args.action == 'deserialization' or args.action == 'd': + elif args.action == "deserialization" or args.action == "d": if args.aasx: compliance_tool_aasx.check_deserialization(args.file_1, manager) elif args.json: compliance_tool_json.check_deserialization(args.file_1, manager) elif args.xml: compliance_tool_xml.check_deserialization(args.file_1, manager) - elif args.action == 'example' or args.action == 'e': + elif args.action == "example" or args.action == "e": if args.aasx: compliance_tool_aasx.check_aas_example(args.file_1, manager, **data_checker_kwargs) elif args.json: compliance_tool_json.check_aas_example(args.file_1, manager, **data_checker_kwargs) elif args.xml: compliance_tool_xml.check_aas_example(args.file_1, manager, **data_checker_kwargs) - elif args.action == 'files' or args.action == 'f': + elif args.action == "files" or args.action == "f": if args.file_2: if args.aasx: - compliance_tool_aasx.check_aasx_files_equivalence(args.file_1, args.file_2, manager, - **data_checker_kwargs) + compliance_tool_aasx.check_aasx_files_equivalence( + args.file_1, args.file_2, manager, **data_checker_kwargs + ) elif args.json: - compliance_tool_json.check_json_files_equivalence(args.file_1, args.file_2, manager, - **data_checker_kwargs) + compliance_tool_json.check_json_files_equivalence( + args.file_1, args.file_2, manager, **data_checker_kwargs + ) elif args.xml: - compliance_tool_xml.check_xml_files_equivalence(args.file_1, args.file_2, manager, - **data_checker_kwargs) + compliance_tool_xml.check_xml_files_equivalence( + args.file_1, args.file_2, manager, **data_checker_kwargs + ) else: parser.error("f or files requires two file path.") @@ -184,10 +202,10 @@ def main(): if args.logfile: try: - with open(args.logfile, 'w', encoding='utf-8-sig') as file: + with open(args.logfile, "w", encoding="utf-8-sig") as file: file.write(manager.format_state_manager(args.verbose)) except IOError as error: - print('Could not open logfile: \n{}'.format(error)) + print("Could not open logfile: \n{}".format(error)) if __name__ == "__main__": diff --git a/compliance_tool/aas_compliance_tool/compliance_check_aasx.py b/compliance_tool/aas_compliance_tool/compliance_check_aasx.py index 82b25766d..9febffbaf 100644 --- a/compliance_tool/aas_compliance_tool/compliance_check_aasx.py +++ b/compliance_tool/aas_compliance_tool/compliance_check_aasx.py @@ -11,6 +11,7 @@ :class:`~basyx.aas.compliance_tool.state_manager.ComplianceToolStateManager` by adding new steps and associated :class:`LogRecords ` """ + import datetime import logging from typing import Optional, Tuple, cast @@ -29,9 +30,9 @@ from aas_compliance_tool.state_manager import ComplianceToolStateManager, Status -def check_deserialization(file_path: str, state_manager: ComplianceToolStateManager, - file_info: Optional[str] = None) \ - -> Tuple[model.DictIdentifiableStore, aasx.DictSupplementaryFileContainer, pyecma376_2.OPCCoreProperties]: +def check_deserialization( + file_path: str, state_manager: ComplianceToolStateManager, file_info: Optional[str] = None +) -> 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` @@ -44,7 +45,7 @@ def check_deserialization(file_path: str, state_manager: ComplianceToolStateMana :return: The read object store """ logger_names = [ - 'compliance_check', + "compliance_check", aasx.__name__, xml_deserialization.__name__, json_deserialization.__name__, @@ -56,9 +57,9 @@ def check_deserialization(file_path: str, state_manager: ComplianceToolStateMana logger.setLevel(logging.INFO) if file_info: - state_manager.add_step('Open {} file'.format(file_info)) + state_manager.add_step("Open {} file".format(file_info)) else: - state_manager.add_step('Open file') + state_manager.add_step("Open file") try: # open given file reader = aasx.AASXReader(file_path) @@ -66,13 +67,13 @@ def check_deserialization(file_path: str, state_manager: ComplianceToolStateMana except (FileNotFoundError, ValueError) as error: logger.error(error) state_manager.set_step_status_from_log() - state_manager.add_step('Read file') + state_manager.add_step("Read file") state_manager.set_step_status(Status.NOT_EXECUTED) return model.DictIdentifiableStore(), aasx.DictSupplementaryFileContainer(), pyecma376_2.OPCCoreProperties() try: # read given file - state_manager.add_step('Read file') + state_manager.add_step("Read file") identifiable_store: model.DictIdentifiableStore[model.Identifiable] = model.DictIdentifiableStore() files = aasx.DictSupplementaryFileContainer() reader.read_into(identifiable_store, files) @@ -100,7 +101,7 @@ def check_aas_example(file_path: str, state_manager: ComplianceToolStateManager, :param state_manager: :class:`~basyx.aas.compliance_tool.state_manager.ComplianceToolStateManager` to log the steps :param kwargs: Additional arguments to pass to :class:`~basyx.aas.examples.data._helper.AASDataChecker` """ - logger = logging.getLogger('compliance_check') + logger = logging.getLogger("compliance_check") logger.addHandler(state_manager) logger.propagate = False logger.setLevel(logging.INFO) @@ -114,29 +115,29 @@ def check_aas_example(file_path: str, state_manager: ComplianceToolStateManager, identifiable_store, files, cp_new = check_deserialization(file_path, state_manager) if state_manager.status in (Status.FAILED, Status.NOT_EXECUTED): - state_manager.add_step('Check if data is equal to example data') + state_manager.add_step("Check if data is equal to example data") state_manager.set_step_status(Status.NOT_EXECUTED) - state_manager.add_step('Check if core properties are equal') + state_manager.add_step("Check if core properties are equal") state_manager.set_step_status(Status.NOT_EXECUTED) - state_manager.add_step('Check if supplementary files are equal') + state_manager.add_step("Check if supplementary files are equal") state_manager.set_step_status(Status.NOT_EXECUTED) return checker = AASDataChecker(raise_immediately=False, **kwargs) - state_manager.add_step('Check if data is equal to example data') + state_manager.add_step("Check if data is equal to example data") example_data = create_example_aas_binding() checker.check_identifiable_store(identifiable_store, example_data) state_manager.add_log_records_from_data_checker(checker) if state_manager.status in (Status.FAILED, Status.NOT_EXECUTED): - state_manager.add_step('Check if core properties are equal') + state_manager.add_step("Check if core properties are equal") state_manager.set_step_status(Status.NOT_EXECUTED) - state_manager.add_step('Check if supplementary files are equal') + state_manager.add_step("Check if supplementary files are equal") state_manager.set_step_status(Status.NOT_EXECUTED) return - state_manager.add_step('Check if core properties are equal') + state_manager.add_step("Check if core properties are equal") # Create OPC/AASX core properties cp = pyecma376_2.OPCCoreProperties() cp.created = datetime.datetime(2020, 1, 1, 0, 0, 0) @@ -149,19 +150,31 @@ def check_aas_example(file_path: str, state_manager: ComplianceToolStateManager, cp.title = "Test Title" checker2 = DataChecker(raise_immediately=False) - if checker2.check(isinstance(cp_new.created, datetime.datetime), "core property created must be of type datetime", - created=type(cp_new.created)): + if checker2.check( + isinstance(cp_new.created, datetime.datetime), + "core property created must be of type datetime", + created=type(cp_new.created), + ): duration = cast(datetime.datetime, cp_new.created) - cp.created checker2.check(duration.microseconds < 20, "created must be {}".format(cp.created), created=cp_new.created) checker2.check(cp_new.creator == cp.creator, "creator must be {}".format(cp.creator), creator=cp_new.creator) - checker2.check(cp_new.description == cp.description, "description must be {}".format(cp.description), - description=cp_new.description) - checker2.check(cp_new.lastModifiedBy == cp.lastModifiedBy, "lastModifiedBy must be {}".format(cp.lastModifiedBy), - lastModifiedBy=cp_new.lastModifiedBy) - - if checker2.check(isinstance(cp_new.modified, datetime.datetime), "modified must be of type datetime", - modified=type(cp_new.modified)): + checker2.check( + cp_new.description == cp.description, + "description must be {}".format(cp.description), + description=cp_new.description, + ) + checker2.check( + cp_new.lastModifiedBy == cp.lastModifiedBy, + "lastModifiedBy must be {}".format(cp.lastModifiedBy), + lastModifiedBy=cp_new.lastModifiedBy, + ) + + if checker2.check( + isinstance(cp_new.modified, datetime.datetime), + "modified must be of type datetime", + modified=type(cp_new.modified), + ): duration = cast(datetime.datetime, cp_new.modified) - cp.modified checker2.check(duration.microseconds < 20, "modified must be {}".format(cp.modified), modified=cp_new.modified) @@ -172,7 +185,7 @@ def check_aas_example(file_path: str, state_manager: ComplianceToolStateManager, state_manager.add_log_records_from_data_checker(checker2) # Check if file in file object is the same - state_manager.add_step('Check if supplementary files are equal') + state_manager.add_step("Check if supplementary files are equal") file_checker = DataChecker(raise_immediately=False) list_of_id_shorts = ["ExampleSubmodelCollection", "ExampleFile"] @@ -181,18 +194,19 @@ def check_aas_example(file_path: str, state_manager: ComplianceToolStateManager, identifiable = identifiable.get_referable(id_short) file_name = identifiable.value if file_checker.check(file_name in files, f"Supplementary File {file_name} must exist"): - test_file_checksum = 'b18229b24a4ee92c6c2b6bc6a8018563b17472f1150d35d5a5945afeb447ed44' + test_file_checksum = "b18229b24a4ee92c6c2b6bc6a8018563b17472f1150d35d5a5945afeb447ed44" file_checker.check( files.get_sha256(file_name).hex() == test_file_checksum, f"Supplementary File {file_name} checksum must be '{test_file_checksum}'.", - value=files.get_sha256(file_name) + value=files.get_sha256(file_name), ) state_manager.add_log_records_from_data_checker(file_checker) -def check_aasx_files_equivalence(file_path_1: str, file_path_2: str, state_manager: ComplianceToolStateManager, - **kwargs) -> None: +def check_aasx_files_equivalence( + file_path_1: str, file_path_2: str, state_manager: ComplianceToolStateManager, **kwargs +) -> None: """ Checks if two aasx files contain the same elements in any order and reports any issues using the given :class:`~basyx.aas.compliance_tool.state_manager.ComplianceToolStateManager` @@ -205,54 +219,58 @@ def check_aasx_files_equivalence(file_path_1: str, file_path_2: str, state_manag :param state_manager: :class:`~basyx.aas.compliance_tool.state_manager.ComplianceToolStateManager` to log the steps :param kwargs: Additional arguments to pass to :class:`~basyx.aas.examples.data._helper.AASDataChecker` """ - logger = logging.getLogger('compliance_check') + logger = logging.getLogger("compliance_check") logger.addHandler(state_manager) logger.propagate = False logger.setLevel(logging.INFO) - identifiable_store_1, files_1, cp_1 = check_deserialization(file_path_1, state_manager, 'first') + identifiable_store_1, files_1, cp_1 = check_deserialization(file_path_1, state_manager, "first") - identifiable_store_2, files_2, cp_2 = check_deserialization(file_path_2, state_manager, 'second') + identifiable_store_2, files_2, cp_2 = check_deserialization(file_path_2, state_manager, "second") if state_manager.status >= Status.FAILED: - state_manager.add_step('Check if data in files are equal') + state_manager.add_step("Check if data in files are equal") state_manager.set_step_status(Status.NOT_EXECUTED) - state_manager.add_step('Check if core properties are equal') + state_manager.add_step("Check if core properties are equal") state_manager.set_step_status(Status.NOT_EXECUTED) - state_manager.add_step('Check if supplementary files are equal') + state_manager.add_step("Check if supplementary files are equal") state_manager.set_step_status(Status.NOT_EXECUTED) return checker = AASDataChecker(raise_immediately=False, **kwargs) try: - state_manager.add_step('Check if data in files are equal') + state_manager.add_step("Check if data in files are equal") checker.check_identifiable_store(identifiable_store_1, identifiable_store_2) except (KeyError, AssertionError) as error: state_manager.set_step_status(Status.FAILED) logger.error(error) - state_manager.add_step('Check if core properties are equal') + state_manager.add_step("Check if core properties are equal") state_manager.set_step_status(Status.NOT_EXECUTED) - state_manager.add_step('Check if supplementary files are equal') + state_manager.add_step("Check if supplementary files are equal") state_manager.set_step_status(Status.NOT_EXECUTED) return state_manager.add_log_records_from_data_checker(checker) if state_manager.status >= Status.FAILED: - state_manager.add_step('Check if core properties are equal') + state_manager.add_step("Check if core properties are equal") state_manager.set_step_status(Status.NOT_EXECUTED) - state_manager.add_step('Check if supplementary files are equal') + state_manager.add_step("Check if supplementary files are equal") state_manager.set_step_status(Status.NOT_EXECUTED) return - state_manager.add_step('Check if core properties are equal') + state_manager.add_step("Check if core properties are equal") checker2 = DataChecker(raise_immediately=False) - checker2.check(isinstance(cp_1.created, datetime.datetime), - "core property created of first file must be of type datetime", - created=type(cp_1.created)) - checker2.check(isinstance(cp_2.created, datetime.datetime), - "core property created of second file must be of type datetime", - created=type(cp_2.created)) + checker2.check( + isinstance(cp_1.created, datetime.datetime), + "core property created of first file must be of type datetime", + created=type(cp_1.created), + ) + checker2.check( + isinstance(cp_2.created, datetime.datetime), + "core property created of second file must be of type datetime", + created=type(cp_2.created), + ) if any(True for _ in checker2.failed_checks): state_manager.add_log_records_from_data_checker(checker2) @@ -261,33 +279,38 @@ def check_aasx_files_equivalence(file_path_1: str, file_path_2: str, state_manag duration = cast(datetime.datetime, cp_1.created) - cast(datetime.datetime, cp_2.created) checker2.check(duration.microseconds < 20, "created must be {}".format(cp_1.created), value=cp_2.created) checker2.check(cp_1.creator == cp_2.creator, "creator must be {}".format(cp_1.creator), value=cp_2.creator) - checker2.check(cp_1.lastModifiedBy == cp_2.lastModifiedBy, "lastModifiedBy must be {}".format(cp_1.lastModifiedBy), - value=cp_2.lastModifiedBy) + checker2.check( + cp_1.lastModifiedBy == cp_2.lastModifiedBy, + "lastModifiedBy must be {}".format(cp_1.lastModifiedBy), + value=cp_2.lastModifiedBy, + ) checker2.check(cp_1.revision == cp_2.revision, "revision must be {}".format(cp_2.revision), revision=cp_1.revision) checker2.check(cp_1.version == cp_2.version, "version must be {}".format(cp_2.version), version=cp_1.version) checker2.check(cp_1.title == cp_2.title, "title must be {}".format(cp_2.title), title=cp_1.title) state_manager.add_log_records_from_data_checker(checker2) - state_manager.add_step('Check if supplementary files are equal') + state_manager.add_step("Check if supplementary files are equal") file_checker = DataChecker(raise_immediately=False) for file_name in files_1: - both_contain = file_checker.check(file_name in files_2, - "second file must contain supplementary file {}".format(file_name)) + both_contain = file_checker.check( + file_name in files_2, "second file must contain supplementary file {}".format(file_name) + ) if both_contain: expected_type = files_1.get_content_type(file_name) - file_checker.check(expected_type == files_2.get_content_type(file_name), - f"second file must contain supplementary file {file_name}" - " with content-type {expected_type}", - content_type=files_2.get_content_type(file_name)) + file_checker.check( + expected_type == files_2.get_content_type(file_name), + f"second file must contain supplementary file {file_name} with content-type {{expected_type}}", + content_type=files_2.get_content_type(file_name), + ) expected_checksum = files_1.get_sha256(file_name) - file_checker.check(expected_checksum == files_2.get_sha256(file_name), - f"second file must contain supplementary file {file_name}" - f" with sha256 {expected_checksum.hex()}", - checksum=files_2.get_sha256(file_name).hex()) + file_checker.check( + expected_checksum == files_2.get_sha256(file_name), + f"second file must contain supplementary file {file_name} with sha256 {expected_checksum.hex()}", + checksum=files_2.get_sha256(file_name).hex(), + ) for file_name in files_2: - file_checker.check(file_name in files_1, - "first file must contain supplementary file {}".format(file_name)) + file_checker.check(file_name in files_1, "first file must contain supplementary file {}".format(file_name)) state_manager.add_log_records_from_data_checker(file_checker) diff --git a/compliance_tool/aas_compliance_tool/compliance_check_json.py b/compliance_tool/aas_compliance_tool/compliance_check_json.py index e50332ecc..3076183e0 100644 --- a/compliance_tool/aas_compliance_tool/compliance_check_json.py +++ b/compliance_tool/aas_compliance_tool/compliance_check_json.py @@ -11,20 +11,22 @@ :class:`~basyx.aas.compliance_tool.state_manager.ComplianceToolStateManager` by adding new steps and associated :class:`LogRecords ` """ + import os import json import logging from typing import Optional, IO -from basyx.aas import (model) +from basyx.aas import model from basyx.aas.adapter.json import json_deserialization from basyx.aas.examples.data import example_aas, create_example from basyx.aas.examples.data._helper import AASDataChecker from aas_compliance_tool.state_manager import ComplianceToolStateManager, Status -def check_deserialization(file_path: str, state_manager: ComplianceToolStateManager, - file_info: Optional[str] = None) -> model.DictIdentifiableStore: +def check_deserialization( + file_path: str, state_manager: ComplianceToolStateManager, 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` @@ -36,7 +38,7 @@ def check_deserialization(file_path: str, state_manager: ComplianceToolStateMana :param file_info: Additional information about the file for name of the steps :return: The deserialized :class:`~basyx.aas.model.provider.DictIdentifiableStore` """ - logger = logging.getLogger('compliance_check') + logger = logging.getLogger("compliance_check") logger.addHandler(state_manager) logger.propagate = False logger.setLevel(logging.INFO) @@ -48,19 +50,19 @@ def check_deserialization(file_path: str, state_manager: ComplianceToolStateMana logger_deserialization.setLevel(logging.INFO) if file_info: - state_manager.add_step('Open {} file'.format(file_info)) + state_manager.add_step("Open {} file".format(file_info)) else: - state_manager.add_step('Open file') + state_manager.add_step("Open file") try: # open given file - file_to_be_checked = open(file_path, 'r', encoding='utf-8-sig') + file_to_be_checked = open(file_path, "r", encoding="utf-8-sig") except IOError as error: state_manager.set_step_status(Status.FAILED) logger.error(error) if file_info: - state_manager.add_step('Read file {} and check if it is deserializable'.format(file_info)) + state_manager.add_step("Read file {} and check if it is deserializable".format(file_info)) else: - state_manager.add_step('Read file and check if it is deserializable') + state_manager.add_step("Read file and check if it is deserializable") state_manager.set_step_status(Status.NOT_EXECUTED) return model.DictIdentifiableStore() @@ -68,9 +70,9 @@ def check_deserialization(file_path: str, state_manager: ComplianceToolStateMana state_manager.set_step_status(Status.SUCCESS) # read given file and check if it is conform to the official json schema if file_info: - state_manager.add_step('Read file {} and check if it is deserializable'.format(file_info)) + state_manager.add_step("Read file {} and check if it is deserializable".format(file_info)) else: - state_manager.add_step('Read file and check if it is deserializable') + state_manager.add_step("Read file and check if it is deserializable") identifiable_store = json_deserialization.read_aas_json_file(file_to_be_checked, failsafe=True) state_manager.set_step_status_from_log() @@ -99,20 +101,21 @@ def check_aas_example(file_path: str, state_manager: ComplianceToolStateManager, identifiable_store = check_deserialization(file_path, state_manager) if state_manager.status in (Status.FAILED, Status.NOT_EXECUTED): - state_manager.add_step('Check if data is equal to example data') + state_manager.add_step("Check if data is equal to example data") state_manager.set_step_status(Status.NOT_EXECUTED) return checker = AASDataChecker(raise_immediately=False, **kwargs) - state_manager.add_step('Check if data is equal to example data') + state_manager.add_step("Check if data is equal to example data") checker.check_identifiable_store(identifiable_store, create_example()) state_manager.add_log_records_from_data_checker(checker) -def check_json_files_equivalence(file_path_1: str, file_path_2: str, state_manager: ComplianceToolStateManager, - **kwargs) -> None: +def check_json_files_equivalence( + file_path_1: str, file_path_2: str, state_manager: ComplianceToolStateManager, **kwargs +) -> None: """ Checks if two json files contain the same elements in any order and reports any issues using the given :class:`~basyx.aas.compliance_tool.state_manager.ComplianceToolStateManager` @@ -125,23 +128,23 @@ def check_json_files_equivalence(file_path_1: str, file_path_2: str, state_manag :param state_manager: :class:`~basyx.aas.compliance_tool.state_manager.ComplianceToolStateManager` to log the steps :param kwargs: Additional arguments to pass to :class:`~basyx.aas.examples.data._helper.AASDataChecker` """ - logger = logging.getLogger('compliance_check') + logger = logging.getLogger("compliance_check") logger.addHandler(state_manager) logger.propagate = False logger.setLevel(logging.INFO) - identifiable_store_1 = check_deserialization(file_path_1, state_manager, 'first') + identifiable_store_1 = check_deserialization(file_path_1, state_manager, "first") - identifiable_store_2 = check_deserialization(file_path_2, state_manager, 'second') + identifiable_store_2 = check_deserialization(file_path_2, state_manager, "second") if state_manager.status is Status.FAILED: - state_manager.add_step('Check if data in files are equal') + state_manager.add_step("Check if data in files are equal") state_manager.set_step_status(Status.NOT_EXECUTED) return checker = AASDataChecker(raise_immediately=False, **kwargs) try: - state_manager.add_step('Check if data in files are equal') + state_manager.add_step("Check if data in files are equal") checker.check_identifiable_store(identifiable_store_1, identifiable_store_2) except (KeyError, AssertionError) as error: state_manager.set_step_status(Status.FAILED) diff --git a/compliance_tool/aas_compliance_tool/compliance_check_xml.py b/compliance_tool/aas_compliance_tool/compliance_check_xml.py index eeb9924c6..75ca619ec 100644 --- a/compliance_tool/aas_compliance_tool/compliance_check_xml.py +++ b/compliance_tool/aas_compliance_tool/compliance_check_xml.py @@ -11,6 +11,7 @@ :class:`~basyx.aas.compliance_tool.state_manager.ComplianceToolStateManager` by adding new steps and associated :class:`LogRecords ` """ + import os from lxml import etree # type: ignore import logging @@ -23,8 +24,9 @@ from aas_compliance_tool.state_manager import ComplianceToolStateManager, Status -def check_deserialization(file_path: str, state_manager: ComplianceToolStateManager, - file_info: Optional[str] = None) -> model.DictIdentifiableStore: +def check_deserialization( + file_path: str, state_manager: ComplianceToolStateManager, 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` @@ -36,7 +38,7 @@ def check_deserialization(file_path: str, state_manager: ComplianceToolStateMana :param file_info: Additional information about the file for name of the steps :return: The deserialized object store """ - logger = logging.getLogger('compliance_check') + logger = logging.getLogger("compliance_check") logger.addHandler(state_manager) logger.propagate = False logger.setLevel(logging.INFO) @@ -48,19 +50,19 @@ def check_deserialization(file_path: str, state_manager: ComplianceToolStateMana logger_deserialization.setLevel(logging.INFO) if file_info: - state_manager.add_step('Open {} file'.format(file_info)) + state_manager.add_step("Open {} file".format(file_info)) else: - state_manager.add_step('Open file') + state_manager.add_step("Open file") try: # open given file - file_to_be_checked = open(file_path, 'rb') + file_to_be_checked = open(file_path, "rb") except IOError as error: state_manager.set_step_status(Status.FAILED) logger.error(error) if file_info: - state_manager.add_step('Read file {} and check if it is deserializable'.format(file_info)) + state_manager.add_step("Read file {} and check if it is deserializable".format(file_info)) else: - state_manager.add_step('Read file and check if it is deserializable') + state_manager.add_step("Read file and check if it is deserializable") state_manager.set_step_status(Status.NOT_EXECUTED) return model.DictIdentifiableStore() @@ -68,9 +70,9 @@ def check_deserialization(file_path: str, state_manager: ComplianceToolStateMana state_manager.set_step_status(Status.SUCCESS) # read given file and check if it is conform to the official xml schema if file_info: - state_manager.add_step('Read file {} and check if it is deserializable'.format(file_info)) + state_manager.add_step("Read file {} and check if it is deserializable".format(file_info)) else: - state_manager.add_step('Read file and check if it is deserializable') + state_manager.add_step("Read file and check if it is deserializable") identifiable_store = xml_deserialization.read_aas_xml_file(file_to_be_checked, failsafe=True) state_manager.set_step_status_from_log() @@ -99,20 +101,21 @@ def check_aas_example(file_path: str, state_manager: ComplianceToolStateManager, identifiable_store = check_deserialization(file_path, state_manager) if state_manager.status in (Status.FAILED, Status.NOT_EXECUTED): - state_manager.add_step('Check if data is equal to example data') + state_manager.add_step("Check if data is equal to example data") state_manager.set_step_status(Status.NOT_EXECUTED) return checker = AASDataChecker(raise_immediately=False, **kwargs) - state_manager.add_step('Check if data is equal to example data') + state_manager.add_step("Check if data is equal to example data") checker.check_identifiable_store(identifiable_store, create_example()) state_manager.add_log_records_from_data_checker(checker) -def check_xml_files_equivalence(file_path_1: str, file_path_2: str, state_manager: ComplianceToolStateManager, - **kwargs) -> None: +def check_xml_files_equivalence( + file_path_1: str, file_path_2: str, state_manager: ComplianceToolStateManager, **kwargs +) -> None: """ Checks if two xml files contain the same elements in any order and reports any issues using the given :class:`~basyx.aas.compliance_tool.state_manager.ComplianceToolStateManager` @@ -125,23 +128,23 @@ def check_xml_files_equivalence(file_path_1: str, file_path_2: str, state_manage :param state_manager: :class:`~basyx.aas.compliance_tool.state_manager.ComplianceToolStateManager` to log the steps :param kwargs: Additional arguments to pass to :class:`~basyx.aas.examples.data._helper.AASDataChecker` """ - logger = logging.getLogger('compliance_check') + logger = logging.getLogger("compliance_check") logger.addHandler(state_manager) logger.propagate = False logger.setLevel(logging.INFO) - identifiable_store_1 = check_deserialization(file_path_1, state_manager, 'first') + identifiable_store_1 = check_deserialization(file_path_1, state_manager, "first") - identifiable_store_2 = check_deserialization(file_path_2, state_manager, 'second') + identifiable_store_2 = check_deserialization(file_path_2, state_manager, "second") if state_manager.status is Status.FAILED: - state_manager.add_step('Check if data in files are equal') + state_manager.add_step("Check if data in files are equal") state_manager.set_step_status(Status.NOT_EXECUTED) return checker = AASDataChecker(raise_immediately=False, **kwargs) try: - state_manager.add_step('Check if data in files are equal') + state_manager.add_step("Check if data in files are equal") checker.check_identifiable_store(identifiable_store_1, identifiable_store_2) except (KeyError, AssertionError) as error: state_manager.set_step_status(Status.FAILED) diff --git a/compliance_tool/aas_compliance_tool/state_manager.py b/compliance_tool/aas_compliance_tool/state_manager.py index 1efe51991..a0bc38496 100644 --- a/compliance_tool/aas_compliance_tool/state_manager.py +++ b/compliance_tool/aas_compliance_tool/state_manager.py @@ -8,6 +8,7 @@ This module defines a :class:`~.ComplianceToolStateManager` to store :class:`LogRecords ` for single steps in a compliance check of the compliance tool """ + import logging import enum import pprint @@ -25,6 +26,7 @@ class Status(enum.IntEnum): :cvar FAILED: :cvar NOT_EXECUTED: """ + SUCCESS = 0 SUCCESS_WITH_WARNINGS = 1 # never used FAILED = 2 @@ -39,6 +41,7 @@ class Step: :ivar status: Status of the step from type Status :ivar log_list: List of :class:`LogRecords ` which belong to this step """ + def __init__(self, name: str, status: Status, log_list: List[logging.LogRecord]): self.name = name self.status = status @@ -65,6 +68,7 @@ class ComplianceToolStateManager(logging.Handler): :ivar steps: List of :class:`Steps <.Step>` """ + def __init__(self): """ steps: List of steps. Each step consist of a step name, a step status and LogRecords belong to to this step. @@ -134,18 +138,23 @@ def add_log_records_from_data_checker(self, data_checker: DataChecker) -> None: """ self.steps[-1].status = Status.SUCCESS if not any(True for _ in data_checker.failed_checks) else Status.FAILED for check in data_checker.checks: - self.steps[-1].log_list.append(logging.LogRecord(name=__name__, - level=logging.INFO if check.result else logging.ERROR, - pathname='', - lineno=0, - msg="{} ({})".format( - check.expectation, - ", ".join("{}={}".format( - k, pprint.pformat( - v, depth=2, width=2 ** 14, compact=True)) - for k, v in check.data.items())), - args=(), - exc_info=None)) + self.steps[-1].log_list.append( + logging.LogRecord( + name=__name__, + level=logging.INFO if check.result else logging.ERROR, + pathname="", + lineno=0, + msg="{} ({})".format( + check.expectation, + ", ".join( + "{}={}".format(k, pprint.pformat(v, depth=2, width=2**14, compact=True)) + for k, v in check.data.items() + ), + ), + args=(), + exc_info=None, + ) + ) def get_error_logs_from_step(self, index: int) -> List[logging.LogRecord]: """ @@ -172,11 +181,11 @@ def format_step(self, index: int, verbose_level: int = 0) -> str: :return: formatted string of the step """ STEP_STATUS: Dict[Status, str] = { - Status.SUCCESS: '{:14}'.format('SUCCESS:'), - Status.SUCCESS_WITH_WARNINGS: '{:14}'.format('WARNINGS:'), - Status.FAILED: '{:14}'.format('FAILED:'), - Status.NOT_EXECUTED: '{:14}'.format('NOT_EXECUTED:'), - } + Status.SUCCESS: "{:14}".format("SUCCESS:"), + Status.SUCCESS_WITH_WARNINGS: "{:14}".format("WARNINGS:"), + Status.FAILED: "{:14}".format("FAILED:"), + Status.NOT_EXECUTED: "{:14}".format("NOT_EXECUTED:"), + } if self.steps[index].status not in STEP_STATUS: raise NotImplementedError string = STEP_STATUS[self.steps[index].status] @@ -187,7 +196,7 @@ def format_step(self, index: int, verbose_level: int = 0) -> str: if log.levelno < logging.WARNING: if verbose_level == 1: continue - string += '\n'+' - {:6} {}'.format(log.levelname + ':', log.getMessage()) + string += "\n" + " - {:6} {}".format(log.levelname + ":", log.getMessage()) return string def format_state_manager(self, verbose_level: int = 0) -> str: @@ -203,7 +212,7 @@ def format_state_manager(self, verbose_level: int = 0) -> str: :return: formatted report """ - string = 'Compliance Test executed:\n' + string = "Compliance Test executed:\n" string += "\n".join(self.format_step(x, verbose_level) for x in range(len(self.steps))) return string diff --git a/compliance_tool/test/_test_helper.py b/compliance_tool/test/_test_helper.py index bfcac3fd4..9c1b26e40 100644 --- a/compliance_tool/test/_test_helper.py +++ b/compliance_tool/test/_test_helper.py @@ -23,33 +23,34 @@ def create_example_aas_core_properties() -> pyecma376_2.OPCCoreProperties: return cp -def create_read_into_mock(file: Literal['TestFile', 'TestFileWrong', None]): - """"Creates side effect function for the AASXReader.read_into mock""" +def create_read_into_mock(file: Literal["TestFile", "TestFileWrong", None]): + """ "Creates side effect function for the AASXReader.read_into mock""" def fill_stores(store, file_store, **kwargs) -> None: for item in create_example_aas_binding(): store.add(item) - if file == 'TestFile': - with open(TEST_PDF_FILE, 'rb') as f: + if file == "TestFile": + with open(TEST_PDF_FILE, "rb") as f: file_store.add_file("/TestFile.pdf", f, "application/pdf") - elif file == 'TestFileWrong': + elif file == "TestFileWrong": file_store.add_file("/TestFile.pdf", io.BytesIO(b"dummy"), "application/pdf") + return fill_stores def create_mock_effect( - module: str, - level: Literal['error', 'warning', 'info', 'debug'], - error_cls: Type[Exception] = ValueError, - error_msg: Optional[str] = None + module: str, + level: Literal["error", "warning", "info", "debug"], + error_cls: Type[Exception] = ValueError, + error_msg: Optional[str] = None, ): """Create mock function, that raises or logs error (based on `failsafe` argument)""" error_msg = error_msg or f"Test {level}!" def mock_error(*args, **kwargs): - if kwargs.get('failsafe', True): + if kwargs.get("failsafe", True): getattr(logging.getLogger(module), level)(error_msg) else: raise error_cls(error_msg) diff --git a/compliance_tool/test/test_aas_compliance_tool.py b/compliance_tool/test/test_aas_compliance_tool.py index df314888d..f53c9a003 100644 --- a/compliance_tool/test/test_aas_compliance_tool.py +++ b/compliance_tool/test/test_aas_compliance_tool.py @@ -34,9 +34,9 @@ def test_json_xml_mutually_exclusive(self): class ComplianceToolActionTest(unittest.TestCase): def setUp(self): - self._json_patcher = patch('aas_compliance_tool.cli.compliance_tool_json') - self._xml_patcher = patch('aas_compliance_tool.cli.compliance_tool_xml') - self._aasx_patcher = patch('aas_compliance_tool.cli.compliance_tool_aasx') + self._json_patcher = patch("aas_compliance_tool.cli.compliance_tool_json") + self._xml_patcher = patch("aas_compliance_tool.cli.compliance_tool_xml") + self._aasx_patcher = patch("aas_compliance_tool.cli.compliance_tool_aasx") self.mock_json = self._json_patcher.start() self.mock_xml = self._xml_patcher.start() self.mock_aasx = self._aasx_patcher.start() @@ -47,53 +47,54 @@ def tearDown(self): self._aasx_patcher.stop() def _call_main(self, args): - with patch('sys.argv', ['compliance_tool'] + args): + with patch("sys.argv", ["compliance_tool"] + args): with redirect_stdout(StringIO()): main() def test_route_d_json(self): - self._call_main(['d', 'f.json', '--json']) - self.mock_json.check_deserialization.assert_called_once_with('f.json', ANY) + self._call_main(["d", "f.json", "--json"]) + self.mock_json.check_deserialization.assert_called_once_with("f.json", ANY) def test_route_d_xml(self): - self._call_main(['d', 'f.xml', '--xml']) - self.mock_xml.check_deserialization.assert_called_once_with('f.xml', ANY) + self._call_main(["d", "f.xml", "--xml"]) + self.mock_xml.check_deserialization.assert_called_once_with("f.xml", ANY) def test_route_d_aasx(self): - self._call_main(['d', 'f.aasx', '--json', '--aasx']) - self.mock_aasx.check_deserialization.assert_called_once_with('f.aasx', ANY) + self._call_main(["d", "f.aasx", "--json", "--aasx"]) + self.mock_aasx.check_deserialization.assert_called_once_with("f.aasx", ANY) def test_route_e_json(self): - self._call_main(['e', 'f.json', '--json']) - self.mock_json.check_aas_example.assert_called_once_with('f.json', ANY, check_extensions=True) + self._call_main(["e", "f.json", "--json"]) + self.mock_json.check_aas_example.assert_called_once_with("f.json", ANY, check_extensions=True) def test_route_e_xml(self): - self._call_main(['e', 'f.xml', '--xml']) - self.mock_xml.check_aas_example.assert_called_once_with('f.xml', ANY, check_extensions=True) + self._call_main(["e", "f.xml", "--xml"]) + self.mock_xml.check_aas_example.assert_called_once_with("f.xml", ANY, check_extensions=True) def test_route_e_aasx(self): - self._call_main(['e', 'f.aasx', '--json', '--aasx']) - self.mock_aasx.check_aas_example.assert_called_once_with('f.aasx', ANY, check_extensions=True) + self._call_main(["e", "f.aasx", "--json", "--aasx"]) + self.mock_aasx.check_aas_example.assert_called_once_with("f.aasx", ANY, check_extensions=True) def test_route_f_json(self): - self._call_main(['f', 'a.json', 'b.json', '--json']) - self.mock_json.check_json_files_equivalence.assert_called_once_with('a.json', 'b.json', ANY, - check_extensions=True) + self._call_main(["f", "a.json", "b.json", "--json"]) + self.mock_json.check_json_files_equivalence.assert_called_once_with( + "a.json", "b.json", ANY, check_extensions=True + ) def test_route_f_xml(self): - self._call_main(['f', 'a.xml', 'b.xml', '--xml']) - self.mock_xml.check_xml_files_equivalence.assert_called_once_with('a.xml', 'b.xml', ANY, - check_extensions=True) + self._call_main(["f", "a.xml", "b.xml", "--xml"]) + self.mock_xml.check_xml_files_equivalence.assert_called_once_with("a.xml", "b.xml", ANY, check_extensions=True) def test_route_f_aasx(self): - self._call_main(['f', 'a.aasx', 'b.aasx', '--json', '--aasx']) - self.mock_aasx.check_aasx_files_equivalence.assert_called_once_with('a.aasx', 'b.aasx', ANY, - check_extensions=True) + self._call_main(["f", "a.aasx", "b.aasx", "--json", "--aasx"]) + self.mock_aasx.check_aasx_files_equivalence.assert_called_once_with( + "a.aasx", "b.aasx", ANY, check_extensions=True + ) def test_route_f_missing_file2(self): with self.assertRaises(SystemExit) as cm: with redirect_stderr(StringIO()): - self._call_main(['f', 'f.json', '--json']) + self._call_main(["f", "f.json", "--json"]) self.assertEqual(2, cm.exception.code) @@ -101,19 +102,18 @@ def test_route_f_missing_file2(self): class ComplianceToolCreateTests(unittest.TestCase): def _call_main(self, args) -> str: buf = StringIO() - with patch('sys.argv', ['compliance_tool'] + args): + with patch("sys.argv", ["compliance_tool"] + args): with redirect_stdout(buf): main() return buf.getvalue() def test_create_json(self): with tempfile.NamedTemporaryFile(suffix=".json") as tf: + output = self._call_main(["c", tf.name, "--json"]) - output = self._call_main(['c', tf.name, '--json']) - - self.assertIn('SUCCESS: Create example data', output) - self.assertIn('SUCCESS: Open file', output) - self.assertIn('SUCCESS: Write data to file', output) + self.assertIn("SUCCESS: Create example data", output) + self.assertIn("SUCCESS: Open file", output) + self.assertIn("SUCCESS: Write data to file", output) json_store = read_aas_json_file(tf, failsafe=False) checker = AASDataChecker(raise_immediately=True) @@ -121,22 +121,22 @@ def test_create_json(self): def test_create_xml(self): with tempfile.NamedTemporaryFile(suffix=".json") as tf: - output = self._call_main(['c', tf.name, '--xml']) + output = self._call_main(["c", tf.name, "--xml"]) - self.assertIn('SUCCESS: Create example data', output) - self.assertIn('SUCCESS: Open file', output) - self.assertIn('SUCCESS: Write data to file', output) + self.assertIn("SUCCESS: Create example data", output) + self.assertIn("SUCCESS: Open file", output) + self.assertIn("SUCCESS: Write data to file", output) xml_store = read_aas_xml_file(tf, failsafe=False) checker = AASDataChecker(raise_immediately=True) checker.check_identifiable_store(xml_store, create_example()) def test_create_aasx_xml(self): with tempfile.NamedTemporaryFile(suffix=".json") as tf: - output = self._call_main(['c', tf.name, '--xml', '--aasx']) + output = self._call_main(["c", tf.name, "--xml", "--aasx"]) - self.assertIn('SUCCESS: Create example data', output) - self.assertIn('SUCCESS: Open file', output) - self.assertIn('SUCCESS: Write data to file', output) + self.assertIn("SUCCESS: Create example data", output) + self.assertIn("SUCCESS: Open file", output) + self.assertIn("SUCCESS: Write data to file", output) new_data: model.DictIdentifiableStore = model.DictIdentifiableStore() new_files = aasx.DictSupplementaryFileContainer() @@ -145,29 +145,29 @@ def test_create_aasx_xml(self): new_cp = reader.get_core_properties() self.assertIsInstance(new_cp.created, datetime.datetime) - self.assertAlmostEqual(new_cp.created, datetime.datetime(2020, 1, 1, 0, 0, 0), - delta=datetime.timedelta(milliseconds=20)) + self.assertAlmostEqual( + new_cp.created, datetime.datetime(2020, 1, 1, 0, 0, 0), delta=datetime.timedelta(milliseconds=20) + ) self.assertEqual(new_cp.creator, "Eclipse BaSyx Python Testing Framework") self.assertEqual(new_cp.description, "Test_Description") self.assertEqual(new_cp.lastModifiedBy, "Eclipse BaSyx Python Testing Framework Compliance Tool") self.assertIsInstance(new_cp.modified, datetime.datetime) - self.assertAlmostEqual(new_cp.modified, datetime.datetime(2020, 1, 1, 0, 0, 1), - delta=datetime.timedelta(milliseconds=20)) + self.assertAlmostEqual( + new_cp.modified, datetime.datetime(2020, 1, 1, 0, 0, 1), delta=datetime.timedelta(milliseconds=20) + ) self.assertEqual(new_cp.revision, "1.0") self.assertEqual(new_cp.version, "2.0.1") self.assertEqual(new_cp.title, "Test Title") self.assertEqual(new_files.get_content_type("/TestFile.pdf"), "application/pdf") file_content = io.BytesIO() new_files.write_file("/TestFile.pdf", file_content) - self.assertEqual(hashlib.sha1(file_content.getvalue()).hexdigest(), - "78450a66f59d74c073bf6858db340090ea72a8b1") + self.assertEqual(hashlib.sha1(file_content.getvalue()).hexdigest(), "78450a66f59d74c073bf6858db340090ea72a8b1") def test_logfile(self): with tempfile.NamedTemporaryFile(suffix=".json") as tf: - with tempfile.NamedTemporaryFile("w+", encoding='utf-8-sig', suffix=".log") as lf: - - output = self._call_main(['c', tf.name, '--json', '-l', lf.name]) + with tempfile.NamedTemporaryFile("w+", encoding="utf-8-sig", suffix=".log") as lf: + output = self._call_main(["c", tf.name, "--json", "-l", lf.name]) logfile_content = lf.read() # print() appends a newline that file.write() does not - self.assertEqual(logfile_content, output.rstrip('\n')) + self.assertEqual(logfile_content, output.rstrip("\n")) diff --git a/compliance_tool/test/test_compliance_check_aasx.py b/compliance_tool/test/test_compliance_check_aasx.py index b6e3ffbeb..c1ce19691 100644 --- a/compliance_tool/test/test_compliance_check_aasx.py +++ b/compliance_tool/test/test_compliance_check_aasx.py @@ -15,7 +15,6 @@ class ComplianceToolAASXTest(unittest.TestCase): - def test_check_deserialization_no_file(self) -> None: manager = ComplianceToolStateManager() @@ -59,8 +58,9 @@ def test_check_deserialization_success(self, mock_aasx_reader: mock.MagicMock) - @mock.patch("basyx.aas.adapter.aasx.AASXReader", autospec=True) @mock.patch("aas_compliance_tool.compliance_check_aasx.AASDataChecker", autospec=True) - def test_check_aas_example_fail_on_open(self, mock_data_checker: mock.MagicMock, - mock_aasx_reader: mock.MagicMock) -> None: + def test_check_aas_example_fail_on_open( + self, mock_data_checker: mock.MagicMock, mock_aasx_reader: mock.MagicMock + ) -> None: manager = ComplianceToolStateManager() mock_aasx_reader.side_effect = ValueError("Test error!") @@ -75,8 +75,9 @@ def test_check_aas_example_fail_on_open(self, mock_data_checker: mock.MagicMock, @mock.patch("basyx.aas.adapter.aasx.AASXReader", autospec=True) @mock.patch("aas_compliance_tool.compliance_check_aasx.AASDataChecker", autospec=True) - def test_check_aas_example_fail_on_read(self, mock_data_checker: mock.MagicMock, - mock_aasx_reader: mock.MagicMock) -> None: + def test_check_aas_example_fail_on_read( + self, mock_data_checker: mock.MagicMock, mock_aasx_reader: mock.MagicMock + ) -> None: manager = ComplianceToolStateManager() mock_aasx_reader.return_value.read_into.side_effect = ValueError("Test error!") @@ -91,8 +92,9 @@ def test_check_aas_example_fail_on_read(self, mock_data_checker: mock.MagicMock, @mock.patch("basyx.aas.adapter.aasx.AASXReader", autospec=True) @mock.patch("aas_compliance_tool.compliance_check_aasx.AASDataChecker", autospec=True) - def test_check_aas_example_fail_on_data_check(self, mock_data_checker: mock.MagicMock, - mock_aasx_reader: mock.MagicMock) -> None: + def test_check_aas_example_fail_on_data_check( + self, mock_data_checker: mock.MagicMock, mock_aasx_reader: mock.MagicMock + ) -> None: manager = ComplianceToolStateManager() failed = [CheckResult("Expected Behavior", False, dict())] @@ -110,13 +112,14 @@ def test_check_aas_example_fail_on_data_check(self, mock_data_checker: mock.Magi @mock.patch("basyx.aas.adapter.aasx.AASXReader", autospec=True) @mock.patch("aas_compliance_tool.compliance_check_aasx.AASDataChecker", autospec=True) - def test_check_aas_example_fail_on_core_properties(self, mock_data_checker: mock.MagicMock, - mock_aasx_reader: mock.MagicMock) -> None: + def test_check_aas_example_fail_on_core_properties( + self, mock_data_checker: mock.MagicMock, mock_aasx_reader: mock.MagicMock + ) -> None: manager = ComplianceToolStateManager() mock_data_checker.return_value.checks = [] type(mock_data_checker.return_value).failed_checks = mock.PropertyMock(side_effect=lambda: iter([])) - mock_aasx_reader.return_value.read_into.side_effect = create_read_into_mock(file='TestFile') + mock_aasx_reader.return_value.read_into.side_effect = create_read_into_mock(file="TestFile") wrong_cp = create_example_aas_core_properties() wrong_cp.creator = "Wrong Creator" mock_aasx_reader.return_value.get_core_properties.return_value = wrong_cp @@ -132,8 +135,9 @@ def test_check_aas_example_fail_on_core_properties(self, mock_data_checker: mock @mock.patch("basyx.aas.adapter.aasx.AASXReader", autospec=True) @mock.patch("aas_compliance_tool.compliance_check_aasx.AASDataChecker", autospec=True) - def test_check_aas_example_fail_on_file_missing(self, mock_data_checker: mock.MagicMock, - mock_aasx_reader: mock.MagicMock) -> None: + def test_check_aas_example_fail_on_file_missing( + self, mock_data_checker: mock.MagicMock, mock_aasx_reader: mock.MagicMock + ) -> None: manager = ComplianceToolStateManager() mock_data_checker.return_value.checks = [] @@ -152,13 +156,14 @@ def test_check_aas_example_fail_on_file_missing(self, mock_data_checker: mock.Ma @mock.patch("basyx.aas.adapter.aasx.AASXReader", autospec=True) @mock.patch("aas_compliance_tool.compliance_check_aasx.AASDataChecker", autospec=True) - def test_check_aas_example_fail_on_file_check(self, mock_data_checker: mock.MagicMock, - mock_aasx_reader: mock.MagicMock) -> None: + def test_check_aas_example_fail_on_file_check( + self, mock_data_checker: mock.MagicMock, mock_aasx_reader: mock.MagicMock + ) -> None: manager = ComplianceToolStateManager() mock_data_checker.return_value.checks = [] type(mock_data_checker.return_value).failed_checks = mock.PropertyMock(side_effect=lambda: iter([])) - mock_aasx_reader.return_value.read_into.side_effect = create_read_into_mock(file='TestFileWrong') + mock_aasx_reader.return_value.read_into.side_effect = create_read_into_mock(file="TestFileWrong") mock_aasx_reader.return_value.get_core_properties.return_value = create_example_aas_core_properties() compliance_tool.check_aas_example("", manager) @@ -172,11 +177,12 @@ def test_check_aas_example_fail_on_file_check(self, mock_data_checker: mock.Magi @mock.patch("basyx.aas.adapter.aasx.AASXReader", autospec=True) @mock.patch("aas_compliance_tool.compliance_check_aasx.AASDataChecker", autospec=True) - def test_check_aas_example_success(self, mock_data_checker: mock.MagicMock, - mock_aasx_reader: mock.MagicMock) -> None: + def test_check_aas_example_success( + self, mock_data_checker: mock.MagicMock, mock_aasx_reader: mock.MagicMock + ) -> None: manager = ComplianceToolStateManager() - mock_aasx_reader.return_value.read_into.side_effect = create_read_into_mock(file='TestFile') + mock_aasx_reader.return_value.read_into.side_effect = create_read_into_mock(file="TestFile") mock_aasx_reader.return_value.get_core_properties.return_value = create_example_aas_core_properties() mock_data_checker.return_value.checks = [] type(mock_data_checker.return_value).failed_checks = mock.PropertyMock(side_effect=lambda: iter([])) @@ -191,8 +197,9 @@ def test_check_aas_example_success(self, mock_data_checker: mock.MagicMock, @mock.patch("basyx.aas.adapter.aasx.AASXReader", autospec=True) @mock.patch("aas_compliance_tool.compliance_check_aasx.AASDataChecker", autospec=True) - def test_check_aasx_files_equivalence_file1_fail_on_open(self, mock_data_checker: mock.MagicMock, - mock_aasx_reader: mock.MagicMock) -> None: + def test_check_aasx_files_equivalence_file1_fail_on_open( + self, mock_data_checker: mock.MagicMock, mock_aasx_reader: mock.MagicMock + ) -> None: manager = ComplianceToolStateManager() mock_aasx_reader.side_effect = [ValueError("Test error!"), mock_aasx_reader.return_value] @@ -212,8 +219,9 @@ def test_check_aasx_files_equivalence_file1_fail_on_open(self, mock_data_checker @mock.patch("basyx.aas.adapter.aasx.AASXReader", autospec=True) @mock.patch("aas_compliance_tool.compliance_check_aasx.AASDataChecker", autospec=True) - def test_check_aasx_files_equivalence_file2_fail_on_open(self, mock_data_checker: mock.MagicMock, - mock_aasx_reader: mock.MagicMock) -> None: + def test_check_aasx_files_equivalence_file2_fail_on_open( + self, mock_data_checker: mock.MagicMock, mock_aasx_reader: mock.MagicMock + ) -> None: manager = ComplianceToolStateManager() mock_aasx_reader.side_effect = [mock_aasx_reader.return_value, ValueError("Test error!")] @@ -233,8 +241,9 @@ def test_check_aasx_files_equivalence_file2_fail_on_open(self, mock_data_checker @mock.patch("basyx.aas.adapter.aasx.AASXReader", autospec=True) @mock.patch("aas_compliance_tool.compliance_check_aasx.AASDataChecker", autospec=True) - def test_check_aasx_files_equivalence_fail_on_data_check(self, mock_data_checker: mock.MagicMock, - mock_aasx_reader: mock.MagicMock) -> None: + def test_check_aasx_files_equivalence_fail_on_data_check( + self, mock_data_checker: mock.MagicMock, mock_aasx_reader: mock.MagicMock + ) -> None: manager = ComplianceToolStateManager() failed = [CheckResult("Expected Behavior", False, dict())] @@ -255,19 +264,19 @@ def test_check_aasx_files_equivalence_fail_on_data_check(self, mock_data_checker @mock.patch("basyx.aas.adapter.aasx.AASXReader", autospec=True) @mock.patch("aas_compliance_tool.compliance_check_aasx.AASDataChecker", autospec=True) - def test_check_aasx_files_equivalence_fail_on_core_properties(self, mock_data_checker: mock.MagicMock, - mock_aasx_reader: mock.MagicMock) -> None: + def test_check_aasx_files_equivalence_fail_on_core_properties( + self, mock_data_checker: mock.MagicMock, mock_aasx_reader: mock.MagicMock + ) -> None: manager = ComplianceToolStateManager() - mock_aasx_reader.return_value.read_into.side_effect = create_read_into_mock(file='TestFile') + mock_aasx_reader.return_value.read_into.side_effect = create_read_into_mock(file="TestFile") mock_data_checker.return_value.checks = [] mock_aasx_reader.return_value.get_core_properties.return_value = create_example_aas_core_properties() type(mock_data_checker.return_value).failed_checks = mock.PropertyMock(side_effect=lambda: iter([])) wrong_cp = create_example_aas_core_properties() wrong_cp.creator = "Wrong Creator" - mock_aasx_reader.return_value.get_core_properties.side_effect = \ - [create_example_aas_core_properties(), wrong_cp] + mock_aasx_reader.return_value.get_core_properties.side_effect = [create_example_aas_core_properties(), wrong_cp] compliance_tool.check_aasx_files_equivalence("", "", manager) @@ -283,8 +292,9 @@ def test_check_aasx_files_equivalence_fail_on_core_properties(self, mock_data_ch @mock.patch("basyx.aas.adapter.aasx.AASXReader", autospec=True) @mock.patch("aas_compliance_tool.compliance_check_aasx.AASDataChecker", autospec=True) - def test_check_aasx_files_equivalence_fail_on_file_missing(self, mock_data_checker: mock.MagicMock, - mock_aasx_reader: mock.MagicMock) -> None: + def test_check_aasx_files_equivalence_fail_on_file_missing( + self, mock_data_checker: mock.MagicMock, mock_aasx_reader: mock.MagicMock + ) -> None: manager = ComplianceToolStateManager() mock_data_checker.return_value.checks = [] @@ -296,7 +306,7 @@ def test_check_aasx_files_equivalence_fail_on_file_missing(self, mock_data_check def setup_file_stores(*args, **kwargs): call_count[0] += 1 if call_count[0] == 1: - return create_read_into_mock(file='TestFile')(*args, **kwargs) + return create_read_into_mock(file="TestFile")(*args, **kwargs) else: return create_read_into_mock(file=None)(*args, **kwargs) @@ -311,13 +321,15 @@ def setup_file_stores(*args, **kwargs): self.assertEqual(Status.SUCCESS, manager.steps[4].status) self.assertEqual(Status.SUCCESS, manager.steps[5].status) self.assertEqual(Status.FAILED, manager.steps[6].status) - self.assertIn("second file must contain supplementary file /TestFile.pdf", - manager.format_step(6, verbose_level=1)) + self.assertIn( + "second file must contain supplementary file /TestFile.pdf", manager.format_step(6, verbose_level=1) + ) @mock.patch("basyx.aas.adapter.aasx.AASXReader", autospec=True) @mock.patch("aas_compliance_tool.compliance_check_aasx.AASDataChecker", autospec=True) - def test_check_aasx_files_equivalence_fail_on_file_check(self, mock_data_checker: mock.MagicMock, - mock_aasx_reader: mock.MagicMock) -> None: + def test_check_aasx_files_equivalence_fail_on_file_check( + self, mock_data_checker: mock.MagicMock, mock_aasx_reader: mock.MagicMock + ) -> None: manager = ComplianceToolStateManager() mock_data_checker.return_value.checks = [] @@ -329,9 +341,9 @@ def test_check_aasx_files_equivalence_fail_on_file_check(self, mock_data_checker def setup_file_stores(*args, **kwargs): call_count[0] += 1 if call_count[0] == 1: - return create_read_into_mock(file='TestFile')(*args, **kwargs) + return create_read_into_mock(file="TestFile")(*args, **kwargs) else: - return create_read_into_mock(file='TestFileWrong')(*args, **kwargs) + return create_read_into_mock(file="TestFileWrong")(*args, **kwargs) mock_aasx_reader.return_value.read_into.side_effect = setup_file_stores compliance_tool.check_aasx_files_equivalence("", "", manager) @@ -344,16 +356,19 @@ def setup_file_stores(*args, **kwargs): self.assertEqual(Status.SUCCESS, manager.steps[4].status) self.assertEqual(Status.SUCCESS, manager.steps[5].status) self.assertEqual(Status.FAILED, manager.steps[6].status) - self.assertIn("second file must contain supplementary file /TestFile.pdf with sha256", - manager.format_step(6, verbose_level=1)) + self.assertIn( + "second file must contain supplementary file /TestFile.pdf with sha256", + manager.format_step(6, verbose_level=1), + ) @mock.patch("basyx.aas.adapter.aasx.AASXReader", autospec=True) @mock.patch("aas_compliance_tool.compliance_check_aasx.AASDataChecker", autospec=True) - def test_check_aasx_files_equivalence_success(self, mock_data_checker: mock.MagicMock, - mock_aasx_reader: mock.MagicMock) -> None: + def test_check_aasx_files_equivalence_success( + self, mock_data_checker: mock.MagicMock, mock_aasx_reader: mock.MagicMock + ) -> None: manager = ComplianceToolStateManager() - mock_aasx_reader.return_value.read_into.side_effect = create_read_into_mock(file='TestFile') + mock_aasx_reader.return_value.read_into.side_effect = create_read_into_mock(file="TestFile") mock_aasx_reader.return_value.get_core_properties.return_value = create_example_aas_core_properties() mock_data_checker.return_value.checks = [] type(mock_data_checker.return_value).failed_checks = mock.PropertyMock(side_effect=lambda: iter([])) diff --git a/compliance_tool/test/test_compliance_check_json.py b/compliance_tool/test/test_compliance_check_json.py index e889bc394..82e6aec45 100644 --- a/compliance_tool/test/test_compliance_check_json.py +++ b/compliance_tool/test/test_compliance_check_json.py @@ -15,7 +15,6 @@ class ComplianceToolJsonTest(unittest.TestCase): - def test_check_deserialization_no_file(self) -> None: manager = ComplianceToolStateManager() @@ -30,7 +29,7 @@ def test_check_deserialization_no_file(self) -> None: def test_check_deserialization_fail_on_error(self, mock_read_json_file, mock_open) -> None: manager = ComplianceToolStateManager() - mock_read_json_file.side_effect = create_mock_effect('basyx.aas.adapter.json.json_deserialization', 'error') + mock_read_json_file.side_effect = create_mock_effect("basyx.aas.adapter.json.json_deserialization", "error") compliance_tool.check_deserialization("", manager) self.assertEqual(2, len(manager.steps)) @@ -43,7 +42,7 @@ def test_check_deserialization_fail_on_error(self, mock_read_json_file, mock_ope def test_check_deserialization_fail_on_warning(self, mock_read_json_file, mock_open) -> None: manager = ComplianceToolStateManager() - mock_read_json_file.side_effect = create_mock_effect('basyx.aas.adapter.json.json_deserialization', 'warning') + mock_read_json_file.side_effect = create_mock_effect("basyx.aas.adapter.json.json_deserialization", "warning") compliance_tool.check_deserialization("", manager) self.assertEqual(2, len(manager.steps)) @@ -56,7 +55,7 @@ def test_check_deserialization_fail_on_warning(self, mock_read_json_file, mock_o def test_check_deserialization_success(self, mock_read_json_file, mock_open) -> None: manager = ComplianceToolStateManager() - mock_read_json_file.side_effect = create_mock_effect('basyx.aas.adapter.json.json_deserialization', 'debug') + mock_read_json_file.side_effect = create_mock_effect("basyx.aas.adapter.json.json_deserialization", "debug") compliance_tool.check_deserialization("", manager) self.assertEqual(2, len(manager.steps)) @@ -66,8 +65,9 @@ def test_check_deserialization_success(self, mock_read_json_file, mock_open) -> @mock.patch("builtins.open") @mock.patch("basyx.aas.adapter.json.json_deserialization.read_aas_json_file", autospec=True) @mock.patch("aas_compliance_tool.compliance_check_json.AASDataChecker", autospec=True) - def test_check_example_success(self, mock_data_checker: mock.MagicMock, mock_read_json_file: mock.MagicMock, - mock_open: mock.MagicMock) -> None: + def test_check_example_success( + self, mock_data_checker: mock.MagicMock, mock_read_json_file: mock.MagicMock, mock_open: mock.MagicMock + ) -> None: manager = ComplianceToolStateManager() mock_data_checker.return_value.checks = [] @@ -82,12 +82,14 @@ def test_check_example_success(self, mock_data_checker: mock.MagicMock, mock_rea @mock.patch("builtins.open") @mock.patch("basyx.aas.adapter.json.json_deserialization.read_aas_json_file", autospec=True) @mock.patch("aas_compliance_tool.compliance_check_json.AASDataChecker", autospec=True) - def test_check_example_fail_on_read(self, mock_data_checker: mock.MagicMock, mock_read_json_file: mock.MagicMock, - mock_open: mock.MagicMock) -> None: + def test_check_example_fail_on_read( + self, mock_data_checker: mock.MagicMock, mock_read_json_file: mock.MagicMock, mock_open: mock.MagicMock + ) -> None: manager = ComplianceToolStateManager() - mock_read_json_file.side_effect = create_mock_effect('basyx.aas.adapter.json.json_deserialization', 'error', - error_msg="Error on reading aas json file!") + mock_read_json_file.side_effect = create_mock_effect( + "basyx.aas.adapter.json.json_deserialization", "error", error_msg="Error on reading aas json file!" + ) compliance_tool.check_aas_example("", manager) self.assertEqual(3, len(manager.steps)) @@ -99,8 +101,9 @@ def test_check_example_fail_on_read(self, mock_data_checker: mock.MagicMock, moc @mock.patch("builtins.open") @mock.patch("basyx.aas.adapter.json.json_deserialization.read_aas_json_file", autospec=True) @mock.patch("aas_compliance_tool.compliance_check_json.AASDataChecker", autospec=True) - def test_check_example_fail_on_check(self, mock_data_checker: mock.MagicMock, mock_read_json_file: mock.MagicMock, - mock_open: mock.MagicMock) -> None: + def test_check_example_fail_on_check( + self, mock_data_checker: mock.MagicMock, mock_read_json_file: mock.MagicMock, mock_open: mock.MagicMock + ) -> None: manager = ComplianceToolStateManager() failed = [CheckResult("Expected Behavior", False, dict())] mock_data_checker.return_value.checks = failed @@ -117,8 +120,9 @@ def test_check_example_fail_on_check(self, mock_data_checker: mock.MagicMock, mo @mock.patch("builtins.open") @mock.patch("basyx.aas.adapter.json.json_deserialization.read_aas_json_file", autospec=True) @mock.patch("aas_compliance_tool.compliance_check_json.AASDataChecker", autospec=True) - def test_check_json_files_equivalence_file1_fail_on_deserialization(self, mock_data_checker, mock_read_json_file, - mock_open) -> None: + def test_check_json_files_equivalence_file1_fail_on_deserialization( + self, mock_data_checker, mock_read_json_file, mock_open + ) -> None: manager = ComplianceToolStateManager() call_count = [0] @@ -126,7 +130,7 @@ def test_check_json_files_equivalence_file1_fail_on_deserialization(self, mock_d def mock_first_fails(*args, **kwargs): call_count[0] += 1 if call_count[0] == 1: - create_mock_effect('basyx.aas.adapter.json.json_deserialization', 'error')(*args, **kwargs) + create_mock_effect("basyx.aas.adapter.json.json_deserialization", "error")(*args, **kwargs) mock_read_json_file.side_effect = mock_first_fails compliance_tool.check_json_files_equivalence("", "", manager) @@ -142,8 +146,9 @@ def mock_first_fails(*args, **kwargs): @mock.patch("builtins.open") @mock.patch("basyx.aas.adapter.json.json_deserialization.read_aas_json_file", autospec=True) @mock.patch("aas_compliance_tool.compliance_check_json.AASDataChecker", autospec=True) - def test_check_json_files_equivalence_file2_fail_on_deserialization(self, mock_data_checker, mock_read_json_file, - mock_open) -> None: + def test_check_json_files_equivalence_file2_fail_on_deserialization( + self, mock_data_checker, mock_read_json_file, mock_open + ) -> None: manager = ComplianceToolStateManager() call_count = [0] @@ -151,7 +156,7 @@ def test_check_json_files_equivalence_file2_fail_on_deserialization(self, mock_d def mock_second_fails(*args, **kwargs): call_count[0] += 1 if call_count[0] == 2: - create_mock_effect('basyx.aas.adapter.json.json_deserialization', 'error')(*args, **kwargs) + create_mock_effect("basyx.aas.adapter.json.json_deserialization", "error")(*args, **kwargs) mock_read_json_file.side_effect = mock_second_fails compliance_tool.check_json_files_equivalence("", "", manager) @@ -184,8 +189,9 @@ def test_check_json_files_equivalence_success(self, mock_data_checker, mock_read @mock.patch("builtins.open") @mock.patch("basyx.aas.adapter.json.json_deserialization.read_aas_json_file", autospec=True) @mock.patch("aas_compliance_tool.compliance_check_json.AASDataChecker", autospec=True) - def test_check_json_files_equivalence_fail_on_check(self, mock_data_checker: mock.MagicMock, mock_read_json_file, - mock_open) -> None: + def test_check_json_files_equivalence_fail_on_check( + self, mock_data_checker: mock.MagicMock, mock_read_json_file, mock_open + ) -> None: manager = ComplianceToolStateManager() failed = [CheckResult("Expected Behavior", False, dict())] diff --git a/compliance_tool/test/test_compliance_check_xml.py b/compliance_tool/test/test_compliance_check_xml.py index 0a85e330a..1e848a4d9 100644 --- a/compliance_tool/test/test_compliance_check_xml.py +++ b/compliance_tool/test/test_compliance_check_xml.py @@ -15,7 +15,6 @@ class ComplianceToolXmlTest(unittest.TestCase): - def test_check_deserialization_no_file(self) -> None: manager = ComplianceToolStateManager() @@ -30,7 +29,7 @@ def test_check_deserialization_no_file(self) -> None: def test_check_deserialization_fail_on_error(self, mock_read_xml_file, mock_open) -> None: manager = ComplianceToolStateManager() - mock_read_xml_file.side_effect = create_mock_effect('basyx.aas.adapter.xml.xml_deserialization', 'error') + mock_read_xml_file.side_effect = create_mock_effect("basyx.aas.adapter.xml.xml_deserialization", "error") compliance_tool.check_deserialization("", manager) self.assertEqual(2, len(manager.steps)) @@ -43,7 +42,7 @@ def test_check_deserialization_fail_on_error(self, mock_read_xml_file, mock_open def test_check_deserialization_fail_on_warning(self, mock_read_xml_file, mock_open) -> None: manager = ComplianceToolStateManager() - mock_read_xml_file.side_effect = create_mock_effect('basyx.aas.adapter.xml.xml_deserialization', 'warning') + mock_read_xml_file.side_effect = create_mock_effect("basyx.aas.adapter.xml.xml_deserialization", "warning") compliance_tool.check_deserialization("", manager) self.assertEqual(2, len(manager.steps)) @@ -56,7 +55,7 @@ def test_check_deserialization_fail_on_warning(self, mock_read_xml_file, mock_op def test_check_deserialization_success(self, mock_read_xml_file, mock_open) -> None: manager = ComplianceToolStateManager() - mock_read_xml_file.side_effect = create_mock_effect('basyx.aas.adapter.xml.xml_deserialization', 'debug') + mock_read_xml_file.side_effect = create_mock_effect("basyx.aas.adapter.xml.xml_deserialization", "debug") compliance_tool.check_deserialization("", manager) self.assertEqual(2, len(manager.steps)) @@ -66,8 +65,9 @@ def test_check_deserialization_success(self, mock_read_xml_file, mock_open) -> N @mock.patch("builtins.open") @mock.patch("basyx.aas.adapter.xml.xml_deserialization.read_aas_xml_file", autospec=True) @mock.patch("aas_compliance_tool.compliance_check_xml.AASDataChecker", autospec=True) - def test_check_example_success(self, mock_data_checker: mock.MagicMock, mock_read_xml_file: mock.MagicMock, - mock_open: mock.MagicMock) -> None: + def test_check_example_success( + self, mock_data_checker: mock.MagicMock, mock_read_xml_file: mock.MagicMock, mock_open: mock.MagicMock + ) -> None: manager = ComplianceToolStateManager() mock_data_checker.return_value.checks = [] @@ -82,12 +82,14 @@ def test_check_example_success(self, mock_data_checker: mock.MagicMock, mock_rea @mock.patch("builtins.open") @mock.patch("basyx.aas.adapter.xml.xml_deserialization.read_aas_xml_file", autospec=True) @mock.patch("aas_compliance_tool.compliance_check_xml.AASDataChecker", autospec=True) - def test_check_example_fail_on_read(self, mock_data_checker: mock.MagicMock, mock_read_xml_file: mock.MagicMock, - mock_open: mock.MagicMock) -> None: + def test_check_example_fail_on_read( + self, mock_data_checker: mock.MagicMock, mock_read_xml_file: mock.MagicMock, mock_open: mock.MagicMock + ) -> None: manager = ComplianceToolStateManager() - mock_read_xml_file.side_effect = create_mock_effect('basyx.aas.adapter.xml.xml_deserialization', 'error', - error_msg="Error on reading aas xml file!") + mock_read_xml_file.side_effect = create_mock_effect( + "basyx.aas.adapter.xml.xml_deserialization", "error", error_msg="Error on reading aas xml file!" + ) compliance_tool.check_aas_example("", manager) self.assertEqual(3, len(manager.steps)) @@ -99,8 +101,9 @@ def test_check_example_fail_on_read(self, mock_data_checker: mock.MagicMock, moc @mock.patch("builtins.open") @mock.patch("basyx.aas.adapter.xml.xml_deserialization.read_aas_xml_file", autospec=True) @mock.patch("aas_compliance_tool.compliance_check_xml.AASDataChecker", autospec=True) - def test_check_example_fail_on_check(self, mock_data_checker: mock.MagicMock, mock_read_xml_file: mock.MagicMock, - mock_open: mock.MagicMock) -> None: + def test_check_example_fail_on_check( + self, mock_data_checker: mock.MagicMock, mock_read_xml_file: mock.MagicMock, mock_open: mock.MagicMock + ) -> None: manager = ComplianceToolStateManager() failed = [CheckResult("Expected Behavior", False, dict())] mock_data_checker.return_value.checks = failed @@ -117,8 +120,9 @@ def test_check_example_fail_on_check(self, mock_data_checker: mock.MagicMock, mo @mock.patch("builtins.open") @mock.patch("basyx.aas.adapter.xml.xml_deserialization.read_aas_xml_file", autospec=True) @mock.patch("aas_compliance_tool.compliance_check_xml.AASDataChecker", autospec=True) - def test_check_xml_files_equivalence_file1_fail_on_deserialization(self, mock_data_checker, mock_read_xml_file, - mock_open) -> None: + def test_check_xml_files_equivalence_file1_fail_on_deserialization( + self, mock_data_checker, mock_read_xml_file, mock_open + ) -> None: manager = ComplianceToolStateManager() call_count = [0] @@ -126,7 +130,7 @@ def test_check_xml_files_equivalence_file1_fail_on_deserialization(self, mock_da def mock_first_fails(*args, **kwargs): call_count[0] += 1 if call_count[0] == 1: - create_mock_effect('basyx.aas.adapter.xml.xml_deserialization', 'error')(*args, **kwargs) + create_mock_effect("basyx.aas.adapter.xml.xml_deserialization", "error")(*args, **kwargs) mock_read_xml_file.side_effect = mock_first_fails compliance_tool.check_xml_files_equivalence("", "", manager) @@ -142,8 +146,9 @@ def mock_first_fails(*args, **kwargs): @mock.patch("builtins.open") @mock.patch("basyx.aas.adapter.xml.xml_deserialization.read_aas_xml_file", autospec=True) @mock.patch("aas_compliance_tool.compliance_check_xml.AASDataChecker", autospec=True) - def test_check_xml_files_equivalence_file2_fail_on_deserialization(self, mock_data_checker, mock_read_xml_file, - mock_open) -> None: + def test_check_xml_files_equivalence_file2_fail_on_deserialization( + self, mock_data_checker, mock_read_xml_file, mock_open + ) -> None: manager = ComplianceToolStateManager() call_count = [0] @@ -151,7 +156,7 @@ def test_check_xml_files_equivalence_file2_fail_on_deserialization(self, mock_da def mock_second_fails(*args, **kwargs): call_count[0] += 1 if call_count[0] == 2: - create_mock_effect('basyx.aas.adapter.xml.xml_deserialization', 'error')(*args, **kwargs) + create_mock_effect("basyx.aas.adapter.xml.xml_deserialization", "error")(*args, **kwargs) mock_read_xml_file.side_effect = mock_second_fails compliance_tool.check_xml_files_equivalence("", "", manager) @@ -184,8 +189,9 @@ def test_check_xml_files_equivalence_success(self, mock_data_checker, mock_read_ @mock.patch("builtins.open") @mock.patch("basyx.aas.adapter.xml.xml_deserialization.read_aas_xml_file", autospec=True) @mock.patch("aas_compliance_tool.compliance_check_xml.AASDataChecker", autospec=True) - def test_check_xml_files_equivalence_fail_on_check(self, mock_data_checker: mock.MagicMock, mock_read_xml_file, - mock_open) -> None: + def test_check_xml_files_equivalence_fail_on_check( + self, mock_data_checker: mock.MagicMock, mock_read_xml_file, mock_open + ) -> None: manager = ComplianceToolStateManager() failed = [CheckResult("Expected Behavior", False, dict())] diff --git a/compliance_tool/test/test_compliance_tool_integration.py b/compliance_tool/test/test_compliance_tool_integration.py index 512c312e8..471646e6c 100644 --- a/compliance_tool/test/test_compliance_tool_integration.py +++ b/compliance_tool/test/test_compliance_tool_integration.py @@ -8,6 +8,7 @@ Integration tests that exercise the full compliance check pipeline without mocking AASDataChecker. Each test creates a real file on disk and runs the compliance tool CLI against it. """ + import tempfile import unittest from contextlib import redirect_stdout @@ -25,10 +26,9 @@ class ComplianceToolIntegrationTest(unittest.TestCase): - def _call_main(self, args) -> str: buf = StringIO() - with patch('sys.argv', ['compliance_tool'] + args): + with patch("sys.argv", ["compliance_tool"] + args): with redirect_stdout(buf): main() return buf.getvalue() @@ -43,90 +43,87 @@ def _modified_example(self) -> model.DictIdentifiableStore: def test_json_example_success(self) -> None: with tempfile.NamedTemporaryFile(suffix=".json") as tf: - self._call_main(['c', tf.name, '--json']) - output = self._call_main(['e', tf.name, '--json']) - self.assertNotIn('FAILED:', output) + self._call_main(["c", tf.name, "--json"]) + output = self._call_main(["e", tf.name, "--json"]) + self.assertNotIn("FAILED:", output) def test_json_example_fail(self) -> None: - with tempfile.NamedTemporaryFile(suffix=".json", mode='w', encoding='utf-8-sig') as tf: + with tempfile.NamedTemporaryFile(suffix=".json", mode="w", encoding="utf-8-sig") as tf: write_aas_json_file(tf, self._modified_example()) tf.flush() - output = self._call_main(['e', tf.name, '--json']) - self.assertIn('FAILED:', output) + output = self._call_main(["e", tf.name, "--json"]) + self.assertIn("FAILED:", output) def test_json_files_equivalence_success(self) -> None: - with tempfile.NamedTemporaryFile(suffix=".json") as tf1, \ - tempfile.NamedTemporaryFile(suffix=".json") as tf2: - self._call_main(['c', tf1.name, '--json']) - self._call_main(['c', tf2.name, '--json']) - output = self._call_main(['f', tf1.name, tf2.name, '--json']) - self.assertNotIn('FAILED:', output) + with tempfile.NamedTemporaryFile(suffix=".json") as tf1, tempfile.NamedTemporaryFile(suffix=".json") as tf2: + self._call_main(["c", tf1.name, "--json"]) + self._call_main(["c", tf2.name, "--json"]) + output = self._call_main(["f", tf1.name, tf2.name, "--json"]) + self.assertNotIn("FAILED:", output) def test_json_files_equivalence_fail(self) -> None: - with tempfile.NamedTemporaryFile(suffix=".json") as tf1, \ - tempfile.NamedTemporaryFile(suffix=".json", mode='w', encoding='utf-8-sig') as tf2: - self._call_main(['c', tf1.name, '--json']) + with ( + tempfile.NamedTemporaryFile(suffix=".json") as tf1, + tempfile.NamedTemporaryFile(suffix=".json", mode="w", encoding="utf-8-sig") as tf2, + ): + self._call_main(["c", tf1.name, "--json"]) write_aas_json_file(tf2, self._modified_example()) tf2.flush() - output = self._call_main(['f', tf1.name, tf2.name, '--json']) - self.assertIn('FAILED:', output) + output = self._call_main(["f", tf1.name, tf2.name, "--json"]) + self.assertIn("FAILED:", output) # --- XML --- def test_xml_example_success(self) -> None: with tempfile.NamedTemporaryFile(suffix=".xml") as tf: - self._call_main(['c', tf.name, '--xml']) - output = self._call_main(['e', tf.name, '--xml']) - self.assertNotIn('FAILED:', output) + self._call_main(["c", tf.name, "--xml"]) + output = self._call_main(["e", tf.name, "--xml"]) + self.assertNotIn("FAILED:", output) def test_xml_example_fail(self) -> None: with tempfile.NamedTemporaryFile(suffix=".xml") as tf: write_aas_xml_file(tf, self._modified_example()) tf.flush() - output = self._call_main(['e', tf.name, '--xml']) - self.assertIn('FAILED:', output) + output = self._call_main(["e", tf.name, "--xml"]) + self.assertIn("FAILED:", output) def test_xml_files_equivalence_success(self) -> None: - with tempfile.NamedTemporaryFile(suffix=".xml") as tf1, \ - tempfile.NamedTemporaryFile(suffix=".xml") as tf2: - self._call_main(['c', tf1.name, '--xml']) - self._call_main(['c', tf2.name, '--xml']) - output = self._call_main(['f', tf1.name, tf2.name, '--xml']) - self.assertNotIn('FAILED:', output) + with tempfile.NamedTemporaryFile(suffix=".xml") as tf1, tempfile.NamedTemporaryFile(suffix=".xml") as tf2: + self._call_main(["c", tf1.name, "--xml"]) + self._call_main(["c", tf2.name, "--xml"]) + output = self._call_main(["f", tf1.name, tf2.name, "--xml"]) + self.assertNotIn("FAILED:", output) def test_xml_files_equivalence_fail(self) -> None: - with tempfile.NamedTemporaryFile(suffix=".xml") as tf1, \ - tempfile.NamedTemporaryFile(suffix=".xml") as tf2: - self._call_main(['c', tf1.name, '--xml']) + with tempfile.NamedTemporaryFile(suffix=".xml") as tf1, tempfile.NamedTemporaryFile(suffix=".xml") as tf2: + self._call_main(["c", tf1.name, "--xml"]) write_aas_xml_file(tf2, self._modified_example()) tf2.flush() - output = self._call_main(['f', tf1.name, tf2.name, '--xml']) - self.assertIn('FAILED:', output) + output = self._call_main(["f", tf1.name, tf2.name, "--xml"]) + self.assertIn("FAILED:", output) # --- AASX --- def test_aasx_example_success(self) -> None: with tempfile.NamedTemporaryFile(suffix=".aasx") as tf: - self._call_main(['c', tf.name, '--xml', '--aasx']) - output = self._call_main(['e', tf.name, '--xml', '--aasx']) - self.assertNotIn('FAILED:', output) + self._call_main(["c", tf.name, "--xml", "--aasx"]) + output = self._call_main(["e", tf.name, "--xml", "--aasx"]) + self.assertNotIn("FAILED:", output) def test_aasx_files_equivalence_success(self) -> None: - with tempfile.NamedTemporaryFile(suffix=".aasx") as tf1, \ - tempfile.NamedTemporaryFile(suffix=".aasx") as tf2: - self._call_main(['c', tf1.name, '--xml', '--aasx']) - self._call_main(['c', tf2.name, '--xml', '--aasx']) - output = self._call_main(['f', tf1.name, tf2.name, '--xml', '--aasx']) - self.assertNotIn('FAILED:', output) + with tempfile.NamedTemporaryFile(suffix=".aasx") as tf1, tempfile.NamedTemporaryFile(suffix=".aasx") as tf2: + self._call_main(["c", tf1.name, "--xml", "--aasx"]) + self._call_main(["c", tf2.name, "--xml", "--aasx"]) + output = self._call_main(["f", tf1.name, tf2.name, "--xml", "--aasx"]) + self.assertNotIn("FAILED:", output) def test_aasx_files_equivalence_fail(self) -> None: - with tempfile.NamedTemporaryFile(suffix=".aasx") as tf1, \ - tempfile.NamedTemporaryFile(suffix=".aasx") as tf2: - self._call_main(['c', tf1.name, '--xml', '--aasx']) + with tempfile.NamedTemporaryFile(suffix=".aasx") as tf1, tempfile.NamedTemporaryFile(suffix=".aasx") as tf2: + self._call_main(["c", tf1.name, "--xml", "--aasx"]) with aasx.AASXWriter(tf2.name) as writer: writer.write_aas([], model.DictIdentifiableStore(), aasx.DictSupplementaryFileContainer()) - output = self._call_main(['f', tf1.name, tf2.name, '--xml', '--aasx']) + output = self._call_main(["f", tf1.name, tf2.name, "--xml", "--aasx"]) - self.assertIn('FAILED:', output) + self.assertIn("FAILED:", output) diff --git a/compliance_tool/test/test_state_manager.py b/compliance_tool/test/test_state_manager.py index 7654203d1..fc13369ff 100644 --- a/compliance_tool/test/test_state_manager.py +++ b/compliance_tool/test/test_state_manager.py @@ -14,7 +14,7 @@ class ComplianceToolStateManagerTest(unittest.TestCase): def test_state(self) -> None: manager = ComplianceToolStateManager() - manager.add_step('test') + manager.add_step("test") self.assertEqual(Status.NOT_EXECUTED, manager.status) manager.set_step_status(Status.SUCCESS) self.assertEqual(Status.SUCCESS, manager.status) @@ -23,25 +23,25 @@ def test_state(self) -> None: def test_logs(self) -> None: manager = ComplianceToolStateManager() - manager.add_step('test') - manager.add_log_record(logging.LogRecord('x', logging.INFO, '', 0, 'test_msg', (), None)) + manager.add_step("test") + manager.add_log_record(logging.LogRecord("x", logging.INFO, "", 0, "test_msg", (), None)) self.assertEqual(0, len(manager.get_error_logs_from_step(0))) - manager.add_log_record(logging.LogRecord('x', logging.ERROR, '', 0, 'test_msg_2', (), None)) + manager.add_log_record(logging.LogRecord("x", logging.ERROR, "", 0, "test_msg_2", (), None)) self.assertEqual(1, len(manager.get_error_logs_from_step(0))) - manager.add_step('test 2') + manager.add_step("test 2") checker = DataChecker(raise_immediately=False) - checker.check(2 == 2, 'Assertion test') + checker.check(2 == 2, "Assertion test") manager.add_log_records_from_data_checker(checker) self.assertEqual(0, len(manager.get_error_logs_from_step(1))) - self.assertEqual('SUCCESS: test 2', manager.format_step(1, 1)) - self.assertEqual('SUCCESS: test 2', manager.format_step(1, 1)) - self.assertIn('INFO: Assertion test ()', manager.format_step(1, 2)) + self.assertEqual("SUCCESS: test 2", manager.format_step(1, 1)) + self.assertEqual("SUCCESS: test 2", manager.format_step(1, 1)) + self.assertIn("INFO: Assertion test ()", manager.format_step(1, 2)) - checker.check(2 == 1, 'Assertion test 2') + checker.check(2 == 1, "Assertion test 2") manager.add_log_records_from_data_checker(checker) self.assertEqual(1, len(manager.get_error_logs_from_step(1))) - self.assertEqual('FAILED: test 2', manager.format_step(1)) - self.assertIn('ERROR: Assertion test 2 ()', manager.format_step(1, 1)) - self.assertIn('INFO: Assertion test ()', manager.format_step(1, 2)) + self.assertEqual("FAILED: test 2", manager.format_step(1)) + self.assertIn("ERROR: Assertion test 2 ()", manager.format_step(1, 1)) + self.assertIn("INFO: Assertion test ()", manager.format_step(1, 2)) diff --git a/etc/scripts/check_python_versions_coincide.py b/etc/scripts/check_python_versions_coincide.py index 774b67db8..aa03b6eb0 100644 --- a/etc/scripts/check_python_versions_coincide.py +++ b/etc/scripts/check_python_versions_coincide.py @@ -2,11 +2,13 @@ This helper script checks if the Python versions defined in a `pyproject.toml` coincide with the given `min_version` and `max_version` and returns an error if they don't. """ + import re import argparse import sys from packaging.version import Version, InvalidVersion + def main(pyproject_toml_path: str, min_version: str, max_version: str) -> None: # Load and check `requires-python` version from `pyproject.toml` try: @@ -20,16 +22,20 @@ def main(pyproject_toml_path: str, min_version: str, max_version: str) -> None: pyproject_version = match.group(1) if Version(pyproject_version) < Version(min_version): - print(f"Error: Python version in `{pyproject_toml_path}` `requires-python` ({pyproject_version}) " - f"is smaller than `min_version` ({min_version}).") + print( + f"Error: Python version in `{pyproject_toml_path}` `requires-python` ({pyproject_version}) " + f"is smaller than `min_version` ({min_version})." + ) sys.exit(1) except FileNotFoundError: print(f"Error: File not found: `{pyproject_toml_path}`.") sys.exit(1) - print(f"Success: Version in pyproject.toml `requires-python` (>={pyproject_version}) " - f"matches expected versions ([{min_version} to {max_version}]).") + print( + f"Success: Version in pyproject.toml `requires-python` (>={pyproject_version}) " + f"matches expected versions ([{min_version} to {max_version}])." + ) if __name__ == "__main__": diff --git a/etc/scripts/check_python_versions_supported.py b/etc/scripts/check_python_versions_supported.py index 5980102f6..460e4165d 100644 --- a/etc/scripts/check_python_versions_supported.py +++ b/etc/scripts/check_python_versions_supported.py @@ -2,12 +2,14 @@ This helper script checks that the provided `min_version` and `max_version` are supported and released, respectively, using the API from the great https://github.com/endoflife-date/endoflife.date project. """ + import argparse import sys import requests from packaging.version import InvalidVersion from datetime import datetime + def main(min_version: str, max_version: str) -> None: # Fetch supported Python versions and check min/max versions try: @@ -47,8 +49,10 @@ def main(min_version: str, max_version: str) -> None: print("Error: Failed to fetch Python version support data.") sys.exit(1) - print(f"Version check passed: min_version [{min_version}] is supported " - f"and max_version [{max_version}] is released.") + print( + f"Version check passed: min_version [{min_version}] is supported and max_version [{max_version}] is released." + ) + if __name__ == "__main__": parser = argparse.ArgumentParser(description="Check Python version support and alignment with pyproject.toml.") diff --git a/ruff.toml b/ruff.toml new file mode 100644 index 000000000..3e683e8ef --- /dev/null +++ b/ruff.toml @@ -0,0 +1,33 @@ +line-length = 120 # matches the current pycodestyle --max-line-length 120 +target-version = "py310" # matches X_PYTHON_MIN_VERSION in ci.yml + +[format] +quote-style = "double" +indent-style = "space" +docstring-code-format = true + + +[lint] +select = [ + "E", # pycodestyle errors + "W", # pycodestyle warnings + "I", # isort + "N", # pep8-naming + "F4", # pyflakes: imports/__future__ + "F5", # pyflakes: format strings + "F7", # pyflakes: control flow (return/break/continue outside loop/func) + "F8", # pyflakes: undefined/unused names + "RUF100", # unused noqa + "COM818", # enforce trailing comma + "B", # flake8-bugbear + "D204", "D211", "D201", "D202", "D300", # basic docstring formatting + "T20", # prevent native print() statements + "A", # prevent shadowing of python builtins +] + +# TODO: Revisit these +ignore = [ + "F403", # star imports - high existing usage + "F405", # may-be-undefined from star imports + "N818", # Exception name should be named with Error suffix +] diff --git a/sdk/basyx/aas/adapter/_generic.py b/sdk/basyx/aas/adapter/_generic.py index aa4f9d692..cbb586ed5 100644 --- a/sdk/basyx/aas/adapter/_generic.py +++ b/sdk/basyx/aas/adapter/_generic.py @@ -8,6 +8,7 @@ The dicts defined in this module are used in the json and xml modules to translate enum members of our implementation to the respective string and vice versa. """ + import os from typing import BinaryIO, Dict, IO, Type, Union @@ -21,9 +22,9 @@ # JSON top-level keys and their corresponding model classes JSON_AAS_TOP_LEVEL_KEYS_TO_TYPES = ( - ('assetAdministrationShells', model.AssetAdministrationShell), - ('submodels', model.Submodel), - ('conceptDescriptions', model.ConceptDescription), + ("assetAdministrationShells", model.AssetAdministrationShell), + ("submodels", model.Submodel), + ("conceptDescriptions", model.ConceptDescription), ) # XML Namespace definition @@ -31,87 +32,89 @@ XML_NS_AAS = "{" + XML_NS_MAP["aas"] + "}" MODELLING_KIND: Dict[model.ModellingKind, str] = { - model.ModellingKind.TEMPLATE: 'Template', - model.ModellingKind.INSTANCE: 'Instance'} + model.ModellingKind.TEMPLATE: "Template", + model.ModellingKind.INSTANCE: "Instance", +} ASSET_KIND: Dict[model.AssetKind, str] = { - model.AssetKind.TYPE: 'Type', - model.AssetKind.INSTANCE: 'Instance', - model.AssetKind.NOT_APPLICABLE: 'NotApplicable', - model.AssetKind.ROLE: 'Role'} + model.AssetKind.TYPE: "Type", + model.AssetKind.INSTANCE: "Instance", + model.AssetKind.NOT_APPLICABLE: "NotApplicable", + model.AssetKind.ROLE: "Role", +} QUALIFIER_KIND: Dict[model.QualifierKind, str] = { - model.QualifierKind.CONCEPT_QUALIFIER: 'ConceptQualifier', - model.QualifierKind.TEMPLATE_QUALIFIER: 'TemplateQualifier', - model.QualifierKind.VALUE_QUALIFIER: 'ValueQualifier'} + model.QualifierKind.CONCEPT_QUALIFIER: "ConceptQualifier", + model.QualifierKind.TEMPLATE_QUALIFIER: "TemplateQualifier", + model.QualifierKind.VALUE_QUALIFIER: "ValueQualifier", +} -DIRECTION: Dict[model.Direction, str] = { - model.Direction.INPUT: 'input', - model.Direction.OUTPUT: 'output'} +DIRECTION: Dict[model.Direction, str] = {model.Direction.INPUT: "input", model.Direction.OUTPUT: "output"} -STATE_OF_EVENT: Dict[model.StateOfEvent, str] = { - model.StateOfEvent.ON: 'on', - model.StateOfEvent.OFF: 'off'} +STATE_OF_EVENT: Dict[model.StateOfEvent, str] = {model.StateOfEvent.ON: "on", model.StateOfEvent.OFF: "off"} REFERENCE_TYPES: Dict[Type[model.Reference], str] = { - model.ExternalReference: 'ExternalReference', - model.ModelReference: 'ModelReference'} + model.ExternalReference: "ExternalReference", + model.ModelReference: "ModelReference", +} KEY_TYPES: Dict[model.KeyTypes, str] = { - model.KeyTypes.ASSET_ADMINISTRATION_SHELL: 'AssetAdministrationShell', - model.KeyTypes.CONCEPT_DESCRIPTION: 'ConceptDescription', - model.KeyTypes.SUBMODEL: 'Submodel', - model.KeyTypes.ANNOTATED_RELATIONSHIP_ELEMENT: 'AnnotatedRelationshipElement', - model.KeyTypes.BASIC_EVENT_ELEMENT: 'BasicEventElement', - model.KeyTypes.BLOB: 'Blob', - model.KeyTypes.CAPABILITY: 'Capability', - model.KeyTypes.DATA_ELEMENT: 'DataElement', - model.KeyTypes.ENTITY: 'Entity', - model.KeyTypes.EVENT_ELEMENT: 'EventElement', - model.KeyTypes.FILE: 'File', - model.KeyTypes.MULTI_LANGUAGE_PROPERTY: 'MultiLanguageProperty', - model.KeyTypes.OPERATION: 'Operation', - model.KeyTypes.PROPERTY: 'Property', - model.KeyTypes.RANGE: 'Range', - model.KeyTypes.REFERENCE_ELEMENT: 'ReferenceElement', - model.KeyTypes.RELATIONSHIP_ELEMENT: 'RelationshipElement', - model.KeyTypes.SUBMODEL_ELEMENT: 'SubmodelElement', - model.KeyTypes.SUBMODEL_ELEMENT_COLLECTION: 'SubmodelElementCollection', - model.KeyTypes.SUBMODEL_ELEMENT_LIST: 'SubmodelElementList', - model.KeyTypes.GLOBAL_REFERENCE: 'GlobalReference', - model.KeyTypes.FRAGMENT_REFERENCE: 'FragmentReference'} + model.KeyTypes.ASSET_ADMINISTRATION_SHELL: "AssetAdministrationShell", + model.KeyTypes.CONCEPT_DESCRIPTION: "ConceptDescription", + model.KeyTypes.SUBMODEL: "Submodel", + model.KeyTypes.ANNOTATED_RELATIONSHIP_ELEMENT: "AnnotatedRelationshipElement", + model.KeyTypes.BASIC_EVENT_ELEMENT: "BasicEventElement", + model.KeyTypes.BLOB: "Blob", + model.KeyTypes.CAPABILITY: "Capability", + model.KeyTypes.DATA_ELEMENT: "DataElement", + model.KeyTypes.ENTITY: "Entity", + model.KeyTypes.EVENT_ELEMENT: "EventElement", + model.KeyTypes.FILE: "File", + model.KeyTypes.MULTI_LANGUAGE_PROPERTY: "MultiLanguageProperty", + model.KeyTypes.OPERATION: "Operation", + model.KeyTypes.PROPERTY: "Property", + model.KeyTypes.RANGE: "Range", + model.KeyTypes.REFERENCE_ELEMENT: "ReferenceElement", + model.KeyTypes.RELATIONSHIP_ELEMENT: "RelationshipElement", + model.KeyTypes.SUBMODEL_ELEMENT: "SubmodelElement", + model.KeyTypes.SUBMODEL_ELEMENT_COLLECTION: "SubmodelElementCollection", + model.KeyTypes.SUBMODEL_ELEMENT_LIST: "SubmodelElementList", + model.KeyTypes.GLOBAL_REFERENCE: "GlobalReference", + model.KeyTypes.FRAGMENT_REFERENCE: "FragmentReference", +} ENTITY_TYPES: Dict[model.EntityType, str] = { - model.EntityType.CO_MANAGED_ENTITY: 'CoManagedEntity', - model.EntityType.SELF_MANAGED_ENTITY: 'SelfManagedEntity'} + model.EntityType.CO_MANAGED_ENTITY: "CoManagedEntity", + model.EntityType.SELF_MANAGED_ENTITY: "SelfManagedEntity", +} IEC61360_DATA_TYPES: Dict[model.base.DataTypeIEC61360, str] = { - model.base.DataTypeIEC61360.DATE: 'DATE', - model.base.DataTypeIEC61360.STRING: 'STRING', - model.base.DataTypeIEC61360.STRING_TRANSLATABLE: 'STRING_TRANSLATABLE', - model.base.DataTypeIEC61360.INTEGER_MEASURE: 'INTEGER_MEASURE', - model.base.DataTypeIEC61360.INTEGER_COUNT: 'INTEGER_COUNT', - model.base.DataTypeIEC61360.INTEGER_CURRENCY: 'INTEGER_CURRENCY', - model.base.DataTypeIEC61360.REAL_MEASURE: 'REAL_MEASURE', - model.base.DataTypeIEC61360.REAL_COUNT: 'REAL_COUNT', - model.base.DataTypeIEC61360.REAL_CURRENCY: 'REAL_CURRENCY', - model.base.DataTypeIEC61360.BOOLEAN: 'BOOLEAN', - model.base.DataTypeIEC61360.IRI: 'IRI', - model.base.DataTypeIEC61360.IRDI: 'IRDI', - model.base.DataTypeIEC61360.RATIONAL: 'RATIONAL', - model.base.DataTypeIEC61360.RATIONAL_MEASURE: 'RATIONAL_MEASURE', - model.base.DataTypeIEC61360.TIME: 'TIME', - model.base.DataTypeIEC61360.TIMESTAMP: 'TIMESTAMP', - model.base.DataTypeIEC61360.HTML: 'HTML', - model.base.DataTypeIEC61360.BLOB: 'BLOB', - model.base.DataTypeIEC61360.FILE: 'FILE', + model.base.DataTypeIEC61360.DATE: "DATE", + model.base.DataTypeIEC61360.STRING: "STRING", + model.base.DataTypeIEC61360.STRING_TRANSLATABLE: "STRING_TRANSLATABLE", + model.base.DataTypeIEC61360.INTEGER_MEASURE: "INTEGER_MEASURE", + model.base.DataTypeIEC61360.INTEGER_COUNT: "INTEGER_COUNT", + model.base.DataTypeIEC61360.INTEGER_CURRENCY: "INTEGER_CURRENCY", + model.base.DataTypeIEC61360.REAL_MEASURE: "REAL_MEASURE", + model.base.DataTypeIEC61360.REAL_COUNT: "REAL_COUNT", + model.base.DataTypeIEC61360.REAL_CURRENCY: "REAL_CURRENCY", + model.base.DataTypeIEC61360.BOOLEAN: "BOOLEAN", + model.base.DataTypeIEC61360.IRI: "IRI", + model.base.DataTypeIEC61360.IRDI: "IRDI", + model.base.DataTypeIEC61360.RATIONAL: "RATIONAL", + model.base.DataTypeIEC61360.RATIONAL_MEASURE: "RATIONAL_MEASURE", + model.base.DataTypeIEC61360.TIME: "TIME", + model.base.DataTypeIEC61360.TIMESTAMP: "TIMESTAMP", + model.base.DataTypeIEC61360.HTML: "HTML", + model.base.DataTypeIEC61360.BLOB: "BLOB", + model.base.DataTypeIEC61360.FILE: "FILE", } IEC61360_LEVEL_TYPES: Dict[model.base.IEC61360LevelType, str] = { - model.base.IEC61360LevelType.MIN: 'min', - model.base.IEC61360LevelType.NOM: 'nom', - model.base.IEC61360LevelType.TYP: 'typ', - model.base.IEC61360LevelType.MAX: 'max', + model.base.IEC61360LevelType.MIN: "min", + model.base.IEC61360LevelType.NOM: "nom", + model.base.IEC61360LevelType.TYP: "typ", + model.base.IEC61360LevelType.MAX: "max", } MODELLING_KIND_INVERSE: Dict[str, model.ModellingKind] = {v: k for k, v in MODELLING_KIND.items()} @@ -123,8 +126,8 @@ KEY_TYPES_INVERSE: Dict[str, model.KeyTypes] = {v: k for k, v in KEY_TYPES.items()} ENTITY_TYPES_INVERSE: Dict[str, model.EntityType] = {v: k for k, v in ENTITY_TYPES.items()} IEC61360_DATA_TYPES_INVERSE: Dict[str, model.base.DataTypeIEC61360] = {v: k for k, v in IEC61360_DATA_TYPES.items()} -IEC61360_LEVEL_TYPES_INVERSE: Dict[str, model.base.IEC61360LevelType] = \ - {v: k for k, v in IEC61360_LEVEL_TYPES.items()} +IEC61360_LEVEL_TYPES_INVERSE: Dict[str, model.base.IEC61360LevelType] = {v: k for k, v in IEC61360_LEVEL_TYPES.items()} -KEY_TYPES_CLASSES_INVERSE: Dict[model.KeyTypes, Type[model.Referable]] = \ - {v: k for k, v in model.KEY_TYPES_CLASSES.items()} +KEY_TYPES_CLASSES_INVERSE: Dict[model.KeyTypes, Type[model.Referable]] = { + v: k for k, v in model.KEY_TYPES_CLASSES.items() +} diff --git a/sdk/basyx/aas/adapter/aasx.py b/sdk/basyx/aas/adapter/aasx.py index 82fe4b76f..be0416e1a 100644 --- a/sdk/basyx/aas/adapter/aasx.py +++ b/sdk/basyx/aas/adapter/aasx.py @@ -59,6 +59,7 @@ class AASXReader: reader.read_into(objects, files) """ + def __init__(self, file: Union[os.PathLike, str, IO], failsafe: bool = True): """ Open an AASX reader for the given filename or file handle @@ -103,6 +104,7 @@ def get_thumbnail(self) -> Optional[bytes]: import io from PIL import Image + thumbnail = Image.open(io.BytesIO(reader.get_thumbnail())) :return: The AASX package thumbnail's file contents or None if no thumbnail is provided @@ -115,9 +117,13 @@ def get_thumbnail(self) -> Optional[bytes]: with self.reader.open_part(thumbnail_part) as p: return p.read() - def read_into(self, object_store: model.AbstractObjectStore, - file_store: "AbstractSupplementaryFileContainer", - override_existing: bool = False, **kwargs) -> Set[model.Identifier]: + def read_into( + self, + object_store: model.AbstractObjectStore, + file_store: "AbstractSupplementaryFileContainer", + override_existing: bool = False, + **kwargs, + ) -> Set[model.Identifier]: """ Read the contents of the AASX package and add them into a given :class:`ObjectStore ` @@ -174,13 +180,15 @@ def read_into(self, object_store: model.AbstractObjectStore, # Iterate AAS files for aas_part in self.reader.get_related_parts_by_type(aasx_origin_part)[RELATIONSHIP_TYPE_AAS_SPEC]: no_aas_files_found = False - self._read_aas_part_into(aas_part, object_store, file_store, - read_identifiables, override_existing, **kwargs) + self._read_aas_part_into( + aas_part, object_store, file_store, read_identifiables, override_existing, **kwargs + ) # Iterate split parts of AAS file for split_part in self.reader.get_related_parts_by_type(aas_part)[RELATIONSHIP_TYPE_AAS_SPEC_SPLIT]: - self._read_aas_part_into(split_part, object_store, file_store, - read_identifiables, override_existing, **kwargs) + self._read_aas_part_into( + split_part, object_store, file_store, read_identifiables, override_existing, **kwargs + ) if no_aas_files_found: if self.failsafe: logger.warning("No AAS files found in AASX package") @@ -201,11 +209,15 @@ def __enter__(self) -> "AASXReader": def __exit__(self, exc_type, exc_val, exc_tb) -> None: self.close() - def _read_aas_part_into(self, part_name: str, - object_store: model.AbstractObjectStore, - file_store: "AbstractSupplementaryFileContainer", - read_identifiables: Set[model.Identifier], - override_existing: bool, **kwargs) -> None: + def _read_aas_part_into( + self, + part_name: str, + object_store: model.AbstractObjectStore, + file_store: "AbstractSupplementaryFileContainer", + read_identifiables: Set[model.Identifier], + override_existing: bool, + **kwargs, + ) -> None: """ Helper function for :meth:`read_into()` to read and process the contents of an AAS-spec part of the AASX file. @@ -230,8 +242,9 @@ def _read_aas_part_into(self, part_name: str, object_store.discard(obj) else: if self.failsafe: - logger.warning(f"Skipping {obj}, since an object with the same id is already contained in the " - "ObjectStore") + logger.warning( + f"Skipping {obj}, since an object with the same id is already contained in the ObjectStore" + ) continue else: raise ValueError(f"Object with id {obj} is already contained in the ObjectStore") @@ -257,23 +270,31 @@ def _parse_aas_part(self, part_name: str, **kwargs) -> model.DictIdentifiableSto logger.debug(f"Parsing AAS objects from XML stream in OPC part {part_name} ...") with self.reader.open_part(part_name) as p: return read_aas_xml_file(p, failsafe=self.failsafe, **kwargs) - elif content_type.split(";")[0] in ("text/json", "application/json") \ - or content_type == "" and extension == "json": + elif ( + content_type.split(";")[0] in ("text/json", "application/json") + or content_type == "" + and extension == "json" + ): logger.debug(f"Parsing AAS objects from JSON stream in OPC part {part_name} ...") with self.reader.open_part(part_name) as p: - return read_aas_json_file(io.TextIOWrapper(p, encoding='utf-8-sig'), failsafe=self.failsafe, **kwargs) + return read_aas_json_file(io.TextIOWrapper(p, encoding="utf-8-sig"), failsafe=self.failsafe, **kwargs) else: - error_message = (f"Could not determine part format of AASX part {part_name} (Content Type: {content_type}," - f" extension: {extension}") + error_message = ( + f"Could not determine part format of AASX part {part_name} (Content Type: {content_type}," + f" extension: {extension}" + ) if self.failsafe: logger.error(error_message) else: raise ValueError(error_message) return model.DictIdentifiableStore() - def _collect_supplementary_files(self, part_name: str, - root_element: Union[model.AssetAdministrationShell, model.Submodel], - file_store: "AbstractSupplementaryFileContainer") -> None: + def _collect_supplementary_files( + self, + part_name: str, + root_element: Union[model.AssetAdministrationShell, model.Submodel], + file_store: "AbstractSupplementaryFileContainer", + ) -> None: """ Helper function to search File objects within a single parsed AssetAdministrationShell or Submodel. Resolve their absolute paths, and update the corresponding File/Thumbnail objects with the absolute path. @@ -284,11 +305,13 @@ def _collect_supplementary_files(self, part_name: str, :param file_store: The SupplementaryFileContainer to add the extracted supplementary files to """ if isinstance(root_element, model.AssetAdministrationShell): - if (root_element.asset_information.default_thumbnail and - root_element.asset_information.default_thumbnail.path): - file_name = self._add_supplementary_file(part_name, - root_element.asset_information.default_thumbnail.path, - file_store) + if ( + root_element.asset_information.default_thumbnail + and root_element.asset_information.default_thumbnail.path + ): + file_name = self._add_supplementary_file( + part_name, root_element.asset_information.default_thumbnail.path, file_store + ) if file_name: root_element.asset_information.default_thumbnail.path = file_name if isinstance(root_element, model.Submodel): @@ -300,8 +323,9 @@ def _collect_supplementary_files(self, part_name: str, if final_name: element.value = final_name - def _add_supplementary_file(self, part_name: str, file_path: str, - file_store: "AbstractSupplementaryFileContainer") -> Optional[str]: + def _add_supplementary_file( + self, part_name: str, file_path: str, file_store: "AbstractSupplementaryFileContainer" + ) -> Optional[str]: """ Helper function to extract a single referenced supplementary file and return the absolute path within the AASX package. @@ -315,9 +339,11 @@ def _add_supplementary_file(self, part_name: str, file_path: str, # Only absolute-path references and relative-path URI references (see RFC 3986, sec. 4.2) are considered # to refer to files within the AASX package. Thus, we must skip all other types of URIs (esp. absolute # URIs and network-path references) - if file_path.startswith('//') or ':' in file_path.split('/')[0]: - logger.info(f"Skipping supplementary file {file_path}, since it seems to be an absolute URI or " - f"network-path URI reference") + if file_path.startswith("//") or ":" in file_path.split("/")[0]: + logger.info( + f"Skipping supplementary file {file_path}, since it seems to be an absolute URI or " + f"network-path URI reference" + ) return None absolute_name = pyecma376_2.package_model.part_realpath(file_path, part_name) logger.debug(f"Reading supplementary file {absolute_name} from AASX package ...") @@ -340,12 +366,8 @@ class AASXWriter: cp.created = datetime.datetime.now() with AASXWriter("filename.aasx") as writer: - writer.write_aas("https://example.org/AssetAdministrationShell", - object_store, - file_store) - writer.write_aas("https://example.org/AssetAdministrationShell2", - object_store, - file_store) + writer.write_aas("https://example.org/AssetAdministrationShell", object_store, file_store) + writer.write_aas("https://example.org/AssetAdministrationShell2", object_store, file_store) writer.write_core_properties(cp) .. attention:: @@ -354,6 +376,7 @@ class AASXWriter: functionality (as shown above). Otherwise, the resulting AASX file will lack important data structures and will not be readable. """ + AASX_ORIGIN_PART_NAME = "/aasx/aasx-origin" def __init__(self, file: Union[os.PathLike, str, IO], failsafe: bool = True): @@ -386,11 +409,13 @@ def __init__(self, file: Union[os.PathLike, str, IO], failsafe: bool = True): p = self.writer.open_part(self.AASX_ORIGIN_PART_NAME, "text/plain") p.close() - def write_aas(self, - aas_ids: Union[model.Identifier, Iterable[model.Identifier]], - object_store: model.AbstractObjectStore[model.Identifier, model.Identifiable], - file_store: "AbstractSupplementaryFileContainer", - write_json: bool = False) -> None: + def write_aas( + self, + aas_ids: Union[model.Identifier, Iterable[model.Identifier]], + object_store: model.AbstractObjectStore[model.Identifier, model.Identifiable], + file_store: "AbstractSupplementaryFileContainer", + write_json: bool = False, + ) -> None: """ Convenience method to write one or more :class:`AssetAdministrationShells ` with all included @@ -443,8 +468,9 @@ def write_aas(self, try: aas = object_store.get_item(aas_id) if not isinstance(aas, model.AssetAdministrationShell): - raise TypeError(f"Identifier {aas_id} does not belong to an AssetAdministrationShell object but to " - f"{aas!r}") + raise TypeError( + f"Identifier {aas_id} does not belong to an AssetAdministrationShell object but to {aas!r}" + ) except (KeyError, TypeError) as e: if self.failsafe: logger.error(f"Skipping AAS {aas_id}: {e}") @@ -474,43 +500,52 @@ def write_aas(self, for semantic_id in traversal.walk_semantic_ids_recursive(identifiable): if isinstance(semantic_id, model.ExternalReference): continue - if not isinstance(semantic_id, model.ModelReference) \ - or semantic_id.type is not model.ConceptDescription: + if ( + not isinstance(semantic_id, model.ModelReference) + or semantic_id.type is not model.ConceptDescription + ): continue try: cd = semantic_id.resolve(object_store) except KeyError: if self.failsafe: - logger.warning(f"ConceptDescription for semanticId {semantic_id} not found in ObjectStore. " - f"Skipping it.") + logger.warning( + f"ConceptDescription for semanticId {semantic_id} not found in ObjectStore. Skipping it." + ) continue else: raise KeyError(f"ConceptDescription for semanticId {semantic_id!r} not found in ObjectStore.") except model.UnexpectedTypeError as e: if self.failsafe: - logger.error(f"semanticId {semantic_id} resolves to {e.value}, " - f"which is not a ConceptDescription. Skipping it.") + logger.error( + f"semanticId {semantic_id} resolves to {e.value}, " + f"which is not a ConceptDescription. Skipping it." + ) continue else: - raise TypeError(f"semanticId {semantic_id!r} resolves to {e.value!r}, which is not a" - f" ConceptDescription.") from e + raise TypeError( + f"semanticId {semantic_id!r} resolves to {e.value!r}, which is not a ConceptDescription." + ) from e concept_descriptions.append(cd) objects_to_be_written.update(concept_descriptions) # Write AAS data part - self.write_all_aas_objects("/aasx/data.{}".format("json" if write_json else "xml"), - objects_to_be_written, file_store, write_json) + self.write_all_aas_objects( + "/aasx/data.{}".format("json" if write_json else "xml"), objects_to_be_written, file_store, write_json + ) # TODO remove `method` parameter in future version. # Not actually required since you can always create a local dict - def write_aas_objects(self, - part_name: str, - object_ids: Iterable[model.Identifier], - object_store: model.AbstractObjectStore, - file_store: "AbstractSupplementaryFileContainer", - write_json: bool = False, - split_part: bool = False, - additional_relationships: Iterable[pyecma376_2.OPCRelationship] = ()) -> None: + def write_aas_objects( + self, + part_name: str, + object_ids: Iterable[model.Identifier], + object_store: model.AbstractObjectStore, + file_store: "AbstractSupplementaryFileContainer", + write_json: bool = False, + split_part: bool = False, + additional_relationships: Iterable[pyecma376_2.OPCRelationship] = (), + ) -> None: """ A thin wrapper around :meth:`write_all_aas_objects` to ensure backward compatibility @@ -566,13 +601,15 @@ def write_aas_objects(self, # TODO remove `split_part` parameter in future version. # Not required anymore since changes from DotAAS version 2.0.1 to 3.0RC01 - def write_all_aas_objects(self, - part_name: str, - objects: model.AbstractObjectStore[model.Identifier, model.Identifiable], - file_store: "AbstractSupplementaryFileContainer", - write_json: bool = False, - split_part: bool = False, - additional_relationships: Iterable[pyecma376_2.OPCRelationship] = ()) -> None: + def write_all_aas_objects( + self, + part_name: str, + objects: model.AbstractObjectStore[model.Identifier, model.Identifiable], + file_store: "AbstractSupplementaryFileContainer", + write_json: bool = False, + split_part: bool = False, + additional_relationships: Iterable[pyecma376_2.OPCRelationship] = (), + ) -> None: """ Write all AAS objects in a given :class:`ObjectStore ` to an XML or JSON part in the AASX package and add the referenced supplementary files to the package. @@ -607,15 +644,17 @@ def write_all_aas_objects(self, def _collect_supplementary_file(file_name: str) -> None: # Skip File objects with empty value URI references that are considered to be no local file # (absolute URIs or network-path URI references) - if file_name is None or file_name.startswith('//') or ':' in file_name.split('/')[0]: + if file_name is None or file_name.startswith("//") or ":" in file_name.split("/")[0]: return supplementary_files.append(file_name) # Retrieve objects and scan for referenced supplementary files for the_object in objects: if isinstance(the_object, model.AssetAdministrationShell): - if (the_object.asset_information.default_thumbnail and - the_object.asset_information.default_thumbnail.path): + if ( + the_object.asset_information.default_thumbnail + and the_object.asset_information.default_thumbnail.path + ): _collect_supplementary_file(the_object.asset_information.default_thumbnail.path) if isinstance(the_object, model.Submodel): for element in traversal.walk_submodel(the_object): @@ -631,7 +670,7 @@ def _collect_supplementary_file(file_name: str) -> None: # TODO allow writing xml *and* JSON part with self.writer.open_part(part_name, "application/json" if write_json else "application/xml") as p: if write_json: - write_aas_json_file(io.TextIOWrapper(p, encoding='utf-8'), objects) + write_aas_json_file(io.TextIOWrapper(p, encoding="utf-8"), objects) else: write_aas_xml_file(p, objects) @@ -652,11 +691,13 @@ def _collect_supplementary_file(file_name: str) -> None: continue elif file_name in self._supplementary_part_names: if self.failsafe: - logger.error(f"Trying to write supplementary file {file_name} to AASX " - f"twice with different contents") + logger.error( + f"Trying to write supplementary file {file_name} to AASX twice with different contents" + ) else: - raise ValueError(f"Trying to write supplementary file {file_name} to AASX twice with" - f" different contents") + raise ValueError( + f"Trying to write supplementary file {file_name} to AASX twice with different contents" + ) logger.debug(f"Writing supplementary file {file_name} to AASX package ...") with self.writer.open_part(file_name, content_type) as p: file_store.write_file(file_name, p) @@ -667,13 +708,19 @@ def _collect_supplementary_file(file_name: str) -> None: logger.debug(f"Writing aas-suppl relationships for AAS object part {part_name} to AASX package ...") self.writer.write_relationships( itertools.chain( - (pyecma376_2.OPCRelationship("r{}".format(i), - RELATIONSHIP_TYPE_AAS_SUPL, - submodel_file_name, - pyecma376_2.OPCTargetMode.INTERNAL) - for i, submodel_file_name in enumerate(supplementary_file_names)), - additional_relationships), - part_name) + ( + pyecma376_2.OPCRelationship( + "r{}".format(i), + RELATIONSHIP_TYPE_AAS_SUPL, + submodel_file_name, + pyecma376_2.OPCTargetMode.INTERNAL, + ) + for i, submodel_file_name in enumerate(supplementary_file_names) + ), + additional_relationships, + ), + part_name, + ) def write_core_properties(self, core_properties: pyecma376_2.OPCCoreProperties): """ @@ -732,11 +779,14 @@ def _write_aasx_origin_relationships(self): # Add relationships from AASX-origin part to AAS parts logger.debug("Writing aas-spec relationships to AASX package ...") self.writer.write_relationships( - (pyecma376_2.OPCRelationship("r{}".format(i), RELATIONSHIP_TYPE_AAS_SPEC, - aas_part_name, - pyecma376_2.OPCTargetMode.INTERNAL) - for i, aas_part_name in enumerate(self._aas_part_names)), - self.AASX_ORIGIN_PART_NAME) + ( + pyecma376_2.OPCRelationship( + "r{}".format(i), RELATIONSHIP_TYPE_AAS_SPEC, aas_part_name, pyecma376_2.OPCTargetMode.INTERNAL + ) + for i, aas_part_name in enumerate(self._aas_part_names) + ), + self.AASX_ORIGIN_PART_NAME, + ) def _write_package_relationships(self): """ @@ -750,18 +800,28 @@ def _write_package_relationships(self): """ logger.debug("Writing package relationships to AASX package ...") package_relationships: List[pyecma376_2.OPCRelationship] = [ - pyecma376_2.OPCRelationship("r1", RELATIONSHIP_TYPE_AASX_ORIGIN, - self.AASX_ORIGIN_PART_NAME, - pyecma376_2.OPCTargetMode.INTERNAL), + pyecma376_2.OPCRelationship( + "r1", RELATIONSHIP_TYPE_AASX_ORIGIN, self.AASX_ORIGIN_PART_NAME, pyecma376_2.OPCTargetMode.INTERNAL + ), ] if self._properties_part is not None: - package_relationships.append(pyecma376_2.OPCRelationship( - "r2", pyecma376_2.RELATIONSHIP_TYPE_CORE_PROPERTIES, self._properties_part, - pyecma376_2.OPCTargetMode.INTERNAL)) + package_relationships.append( + pyecma376_2.OPCRelationship( + "r2", + pyecma376_2.RELATIONSHIP_TYPE_CORE_PROPERTIES, + self._properties_part, + pyecma376_2.OPCTargetMode.INTERNAL, + ) + ) if self._thumbnail_part is not None: - package_relationships.append(pyecma376_2.OPCRelationship( - "r3", pyecma376_2.RELATIONSHIP_TYPE_THUMBNAIL, self._thumbnail_part, - pyecma376_2.OPCTargetMode.INTERNAL)) + package_relationships.append( + pyecma376_2.OPCRelationship( + "r3", + pyecma376_2.RELATIONSHIP_TYPE_THUMBNAIL, + self._thumbnail_part, + pyecma376_2.OPCTargetMode.INTERNAL, + ) + ) self.writer.write_relationships(package_relationships) @@ -778,6 +838,7 @@ class AbstractSupplementaryFileContainer(metaclass=abc.ABCMeta): new file. It also provides each files sha256 hash sum to allow name conflict checking in other classes (e.g. when writing AASX files). """ + @abc.abstractmethod def add_file(self, name: str, file: IO[bytes], content_type: str) -> str: """ @@ -857,6 +918,7 @@ class DictSupplementaryFileContainer(AbstractSupplementaryFileContainer): """ SupplementaryFileContainer implementation using a dict to store the file contents in-memory. """ + def __init__(self): # Stores the files' contents, identified by their sha256 hash self._store: Dict[bytes, bytes] = {} @@ -899,8 +961,8 @@ def _assign_unique_name(self, name: str, sha: bytes, content_type: str) -> str: @staticmethod def _append_counter(name: str, i: int) -> str: - split1 = name.split('/') - split2 = split1[-1].split('.') + split1 = name.split("/") + split2 = split1[-1].split(".") index = -2 if len(split2) > 1 else -1 new_basename = "{}_{:04d}".format(split2[index], i) split2[index] = new_basename diff --git a/sdk/basyx/aas/adapter/json/__init__.py b/sdk/basyx/aas/adapter/json/__init__.py index 04b780566..514f83bdf 100644 --- a/sdk/basyx/aas/adapter/json/__init__.py +++ b/sdk/basyx/aas/adapter/json/__init__.py @@ -18,5 +18,11 @@ """ from .json_serialization import AASToJsonEncoder, StrippedAASToJsonEncoder, write_aas_json_file, object_store_to_json -from .json_deserialization import AASFromJsonDecoder, StrictAASFromJsonDecoder, StrippedAASFromJsonDecoder, \ - StrictStrippedAASFromJsonDecoder, read_aas_json_file, read_aas_json_file_into +from .json_deserialization import ( + AASFromJsonDecoder, + StrictAASFromJsonDecoder, + StrippedAASFromJsonDecoder, + StrictStrippedAASFromJsonDecoder, + read_aas_json_file, + read_aas_json_file_into, +) diff --git a/sdk/basyx/aas/adapter/json/json_deserialization.py b/sdk/basyx/aas/adapter/json/json_deserialization.py index e6fc9b448..3a030b652 100644 --- a/sdk/basyx/aas/adapter/json/json_deserialization.py +++ b/sdk/basyx/aas/adapter/json/json_deserialization.py @@ -29,18 +29,45 @@ Embedded objects that should have a ``modelType`` themselves are expected to be converted already. Other embedded objects are converted using a number of helper constructor methods. """ + import base64 import contextlib import json import logging import pprint -from typing import (Dict, Callable, ContextManager, TypeVar, Type, - List, IO, Optional, Set, get_args, Tuple, Iterable, Any) +from typing import ( + Dict, + Callable, + ContextManager, + TypeVar, + Type, + List, + IO, + Optional, + Set, + get_args, + Tuple, + Iterable, + Any, +) from basyx.aas import model -from .._generic import MODELLING_KIND_INVERSE, ASSET_KIND_INVERSE, KEY_TYPES_INVERSE, ENTITY_TYPES_INVERSE, \ - IEC61360_DATA_TYPES_INVERSE, IEC61360_LEVEL_TYPES_INVERSE, KEY_TYPES_CLASSES_INVERSE, REFERENCE_TYPES_INVERSE, \ - DIRECTION_INVERSE, STATE_OF_EVENT_INVERSE, QUALIFIER_KIND_INVERSE, PathOrIO, Path, JSON_AAS_TOP_LEVEL_KEYS_TO_TYPES +from .._generic import ( + MODELLING_KIND_INVERSE, + ASSET_KIND_INVERSE, + KEY_TYPES_INVERSE, + ENTITY_TYPES_INVERSE, + IEC61360_DATA_TYPES_INVERSE, + IEC61360_LEVEL_TYPES_INVERSE, + KEY_TYPES_CLASSES_INVERSE, + REFERENCE_TYPES_INVERSE, + DIRECTION_INVERSE, + STATE_OF_EVENT_INVERSE, + QUALIFIER_KIND_INVERSE, + PathOrIO, + Path, + JSON_AAS_TOP_LEVEL_KEYS_TO_TYPES, +) logger = logging.getLogger(__name__) @@ -49,8 +76,8 @@ # Helper functions (for simplifying implementation of constructor functions) # ############################################################################# -T = TypeVar('T') -LSS = TypeVar('LSS', bound=model.LangStringSet) +T = TypeVar("T") +LSS = TypeVar("LSS", bound=model.LangStringSet) def _get_ts(dct: Dict[str, object], key: str, type_: Type[T]) -> T: @@ -135,6 +162,7 @@ class AASFromJsonDecoder(json.JSONDecoder): class EnhancedSubmodel(model.Submodel): pass + class EnhancedAASDecoder(StrictAASFromJsonDecoder): @classmethod def _construct_submodel(cls, dct, object_class=EnhancedSubmodel): @@ -148,6 +176,7 @@ def _construct_submodel(cls, dct, object_class=EnhancedSubmodel): Defaults to ``False``. See https://git.rwth-aachen.de/acplt/pyi40aas/-/issues/91 """ + failsafe = True stripped = False @@ -169,27 +198,27 @@ def _get_aas_class_parsers(cls) -> Dict[str, Callable[[Dict[str, object]], objec :return: The dictionary of AAS class parsers """ aas_class_parsers: Dict[str, Callable[[Dict[str, object]], object]] = { - 'AssetAdministrationShell': cls._construct_asset_administration_shell, - 'AssetInformation': cls._construct_asset_information, - 'SpecificAssetId': cls._construct_specific_asset_id, - 'ConceptDescription': cls._construct_concept_description, - 'Extension': cls._construct_extension, - 'Submodel': cls._construct_submodel, - 'Capability': cls._construct_capability, - 'Entity': cls._construct_entity, - 'BasicEventElement': cls._construct_basic_event_element, - 'Operation': cls._construct_operation, - 'RelationshipElement': cls._construct_relationship_element, - 'AnnotatedRelationshipElement': cls._construct_annotated_relationship_element, - 'SubmodelElementCollection': cls._construct_submodel_element_collection, - 'SubmodelElementList': cls._construct_submodel_element_list, - 'Blob': cls._construct_blob, - 'File': cls._construct_file, - 'MultiLanguageProperty': cls._construct_multi_language_property, - 'Property': cls._construct_property, - 'Range': cls._construct_range, - 'ReferenceElement': cls._construct_reference_element, - 'DataSpecificationIec61360': cls._construct_data_specification_iec61360, + "AssetAdministrationShell": cls._construct_asset_administration_shell, + "AssetInformation": cls._construct_asset_information, + "SpecificAssetId": cls._construct_specific_asset_id, + "ConceptDescription": cls._construct_concept_description, + "Extension": cls._construct_extension, + "Submodel": cls._construct_submodel, + "Capability": cls._construct_capability, + "Entity": cls._construct_entity, + "BasicEventElement": cls._construct_basic_event_element, + "Operation": cls._construct_operation, + "RelationshipElement": cls._construct_relationship_element, + "AnnotatedRelationshipElement": cls._construct_annotated_relationship_element, + "SubmodelElementCollection": cls._construct_submodel_element_collection, + "SubmodelElementList": cls._construct_submodel_element_list, + "Blob": cls._construct_blob, + "File": cls._construct_file, + "MultiLanguageProperty": cls._construct_multi_language_property, + "Property": cls._construct_property, + "Range": cls._construct_range, + "ReferenceElement": cls._construct_reference_element, + "DataSpecificationIec61360": cls._construct_data_specification_iec61360, } return aas_class_parsers @@ -197,23 +226,23 @@ def _get_aas_class_parsers(cls) -> Dict[str, Callable[[Dict[str, object]], objec def object_hook(cls, dct: Dict[str, object]) -> object: # Check if JSON object seems to be a deserializable AAS object (i.e. it has a modelType). Otherwise, the JSON # object is returned as is, so it's possible to mix AAS objects with other data within a JSON structure. - if 'modelType' not in dct: + if "modelType" not in dct: return dct AAS_CLASS_PARSERS = cls._get_aas_class_parsers() # Get modelType and constructor function - if not isinstance(dct['modelType'], str): - logger.warning("JSON object has unexpected format of modelType: %s", dct['modelType']) + if not isinstance(dct["modelType"], str): + logger.warning("JSON object has unexpected format of modelType: %s", dct["modelType"]) # Even in strict mode, we consider 'modelType' attributes of wrong type as non-AAS objects instead of # raising an exception. However, the object's type will probably checked later by read_json_aas_file() or # _expect_type() return dct - model_type = dct['modelType'] + model_type = dct["modelType"] if model_type not in AAS_CLASS_PARSERS: if not cls.failsafe: - raise TypeError("Found JSON object with modelType=\"%s\", which is not a known AAS class" % model_type) - logger.error("Found JSON object with modelType=\"%s\", which is not a known AAS class", model_type) + raise TypeError('Found JSON object with modelType="%s", which is not a known AAS class' % model_type) + logger.error('Found JSON object with modelType="%s", which is not a known AAS class', model_type) return dct # Use constructor function to transform JSON representation into BaSyx Python SDK model object @@ -221,7 +250,8 @@ def object_hook(cls, dct: Dict[str, object]) -> object: return AAS_CLASS_PARSERS[model_type](dct) except (KeyError, TypeError, model.AASConstraintViolation) as e: error_message = "Error while trying to convert JSON object into {}: {} >>> {}".format( - model_type, e, pprint.pformat(dct, depth=2, width=2**14, compact=True)) + model_type, e, pprint.pformat(dct, depth=2, width=2**14, compact=True) + ) if cls.failsafe: logger.error(error_message, exc_info=e) # In failsafe mode, we return the raw JSON object dict, if there were errors while parsing an object, so @@ -245,48 +275,52 @@ def _amend_abstract_attributes(cls, obj: object, dct: Dict[str, object]) -> None :param dct: The object's dict representation from JSON """ if isinstance(obj, model.Referable): - if 'idShort' in dct: - obj.id_short = _get_ts(dct, 'idShort', str) - if 'category' in dct: - obj.category = _get_ts(dct, 'category', str) - if 'displayName' in dct: - obj.display_name = cls._construct_lang_string_set(_get_ts(dct, 'displayName', list), - model.MultiLanguageNameType) - if 'description' in dct: - obj.description = cls._construct_lang_string_set(_get_ts(dct, 'description', list), - model.MultiLanguageTextType) + if "idShort" in dct: + obj.id_short = _get_ts(dct, "idShort", str) + if "category" in dct: + obj.category = _get_ts(dct, "category", str) + if "displayName" in dct: + obj.display_name = cls._construct_lang_string_set( + _get_ts(dct, "displayName", list), model.MultiLanguageNameType + ) + if "description" in dct: + obj.description = cls._construct_lang_string_set( + _get_ts(dct, "description", list), model.MultiLanguageTextType + ) if isinstance(obj, model.Identifiable): - if 'administration' in dct: - obj.administration = cls._construct_administrative_information(_get_ts(dct, 'administration', dict)) + if "administration" in dct: + obj.administration = cls._construct_administrative_information(_get_ts(dct, "administration", dict)) if isinstance(obj, model.HasSemantics): - if 'semanticId' in dct: - obj.semantic_id = cls._construct_reference(_get_ts(dct, 'semanticId', dict)) - if 'supplementalSemanticIds' in dct: - for ref in _get_ts(dct, 'supplementalSemanticIds', list): + if "semanticId" in dct: + obj.semantic_id = cls._construct_reference(_get_ts(dct, "semanticId", dict)) + if "supplementalSemanticIds" in dct: + for ref in _get_ts(dct, "supplementalSemanticIds", list): obj.supplemental_semantic_id.append(cls._construct_reference(ref)) # `HasKind` provides only mandatory, immutable attributes; so we cannot do anything here, after object creation. # However, the `cls._get_kind()` function may assist by retrieving them from the JSON object if isinstance(obj, model.Qualifiable) and not cls.stripped: - if 'qualifiers' in dct: - for constraint_dct in _get_ts(dct, 'qualifiers', list): + if "qualifiers" in dct: + for constraint_dct in _get_ts(dct, "qualifiers", list): constraint = cls._construct_qualifier(constraint_dct) obj.qualifier.add(constraint) if isinstance(obj, model.HasDataSpecification) and not cls.stripped: - if 'embeddedDataSpecifications' in dct: - for dspec in _get_ts(dct, 'embeddedDataSpecifications', list): + if "embeddedDataSpecifications" in dct: + for dspec in _get_ts(dct, "embeddedDataSpecifications", list): obj.embedded_data_specifications.append( # TODO: remove the following type: ignore comment when mypy supports abstract types for Type[T] # see https://github.com/python/mypy/issues/5374 model.EmbeddedDataSpecification( data_specification=cls._construct_external_reference( - _get_ts(dspec, 'dataSpecification', dict)), - data_specification_content=_get_ts(dspec, 'dataSpecificationContent', - model.DataSpecificationContent) # type: ignore + _get_ts(dspec, "dataSpecification", dict) + ), + data_specification_content=_get_ts( + dspec, "dataSpecificationContent", model.DataSpecificationContent + ), # type: ignore ) ) if isinstance(obj, model.HasExtension) and not cls.stripped: - if 'extensions' in dct: - for extension in _get_ts(dct, 'extensions', list): + if "extensions" in dct: + for extension in _get_ts(dct, "extensions", list): obj.extension.add(cls._construct_extension(extension)) @classmethod @@ -297,7 +331,7 @@ def _get_kind(cls, dct: Dict[str, object]) -> model.ModellingKind: :param dct: The object's dict representation from JSON :return: The object's ``kind`` value """ - return MODELLING_KIND_INVERSE[_get_ts(dct, "kind", str)] if 'kind' in dct else model.ModellingKind.INSTANCE + return MODELLING_KIND_INVERSE[_get_ts(dct, "kind", str)] if "kind" in dct else model.ModellingKind.INSTANCE # ############################################################################# # Helper Constructor Methods starting from here @@ -309,27 +343,30 @@ def _get_kind(cls, dct: Dict[str, object]) -> model.ModellingKind: @classmethod def _construct_key(cls, dct: Dict[str, object], object_class=model.Key) -> model.Key: - return object_class(type_=KEY_TYPES_INVERSE[_get_ts(dct, 'type', str)], - value=_get_ts(dct, 'value', str)) + return object_class(type_=KEY_TYPES_INVERSE[_get_ts(dct, "type", str)], value=_get_ts(dct, "value", str)) @classmethod - def _construct_specific_asset_id(cls, dct: Dict[str, object], object_class=model.SpecificAssetId) \ - -> model.SpecificAssetId: + def _construct_specific_asset_id( + cls, dct: Dict[str, object], object_class=model.SpecificAssetId + ) -> model.SpecificAssetId: # semantic_id can't be applied by _amend_abstract_attributes because specificAssetId is immutable - return object_class(name=_get_ts(dct, 'name', str), - value=_get_ts(dct, 'value', str), - external_subject_id=cls._construct_external_reference( - _get_ts(dct, 'externalSubjectId', dict)) if 'externalSubjectId' in dct else None, - semantic_id=cls._construct_reference(_get_ts(dct, 'semanticId', dict)) - if 'semanticId' in dct else None, - supplemental_semantic_id=[ - cls._construct_reference(ref) for ref in - _get_ts(dct, 'supplementalSemanticIds', list)] - if 'supplementalSemanticIds' in dct else ()) + return object_class( + name=_get_ts(dct, "name", str), + value=_get_ts(dct, "value", str), + external_subject_id=cls._construct_external_reference(_get_ts(dct, "externalSubjectId", dict)) + if "externalSubjectId" in dct + else None, + semantic_id=cls._construct_reference(_get_ts(dct, "semanticId", dict)) if "semanticId" in dct else None, + supplemental_semantic_id=[ + cls._construct_reference(ref) for ref in _get_ts(dct, "supplementalSemanticIds", list) + ] + if "supplementalSemanticIds" in dct + else (), + ) @classmethod def _construct_reference(cls, dct: Dict[str, object]) -> model.Reference: - reference_type: Type[model.Reference] = REFERENCE_TYPES_INVERSE[_get_ts(dct, 'type', str)] + reference_type: Type[model.Reference] = REFERENCE_TYPES_INVERSE[_get_ts(dct, "type", str)] if reference_type is model.ModelReference: return cls._construct_model_reference(dct, model.Referable) # type: ignore elif reference_type is model.ExternalReference: @@ -337,50 +374,60 @@ def _construct_reference(cls, dct: Dict[str, object]) -> model.Reference: raise ValueError(f"Unsupported reference type {reference_type}!") @classmethod - def _construct_external_reference(cls, dct: Dict[str, object], object_class=model.ExternalReference)\ - -> model.ExternalReference: - reference_type: Type[model.Reference] = REFERENCE_TYPES_INVERSE[_get_ts(dct, 'type', str)] + def _construct_external_reference( + cls, dct: Dict[str, object], object_class=model.ExternalReference + ) -> model.ExternalReference: + reference_type: Type[model.Reference] = REFERENCE_TYPES_INVERSE[_get_ts(dct, "type", str)] if reference_type is not model.ExternalReference: raise ValueError(f"Expected a reference of type {model.ExternalReference}, got {reference_type}!") keys = [cls._construct_key(key_data) for key_data in _get_ts(dct, "keys", list)] - return object_class(tuple(keys), cls._construct_reference(_get_ts(dct, 'referredSemanticId', dict)) - if 'referredSemanticId' in dct else None) + return object_class( + tuple(keys), + cls._construct_reference(_get_ts(dct, "referredSemanticId", dict)) if "referredSemanticId" in dct else None, + ) @classmethod - def _construct_model_reference(cls, dct: Dict[str, object], type_: Type[T], object_class=model.ModelReference)\ - -> model.ModelReference: - reference_type: Type[model.Reference] = REFERENCE_TYPES_INVERSE[_get_ts(dct, 'type', str)] + def _construct_model_reference( + cls, dct: Dict[str, object], type_: Type[T], object_class=model.ModelReference + ) -> model.ModelReference: + reference_type: Type[model.Reference] = REFERENCE_TYPES_INVERSE[_get_ts(dct, "type", str)] if reference_type is not model.ModelReference: raise ValueError(f"Expected a reference of type {model.ModelReference}, got {reference_type}!") keys = [cls._construct_key(key_data) for key_data in _get_ts(dct, "keys", list)] last_key_type = KEY_TYPES_CLASSES_INVERSE.get(keys[-1].type, type(None)) if keys and not issubclass(last_key_type, type_): - logger.warning("type %s of last key of reference to %s does not match reference type %s", - keys[-1].type.name, " / ".join(str(k) for k in keys), type_.__name__) + logger.warning( + "type %s of last key of reference to %s does not match reference type %s", + keys[-1].type.name, + " / ".join(str(k) for k in keys), + type_.__name__, + ) # Infer type the model refence points to using `last_key_type` instead of `type_`. # `type_` is often a `model.Referable`, which is more abstract than e.g. a `model.ConceptDescription`, # leading to information loss while deserializing. # TODO Remove this fix, when this function is called with correct `type_` - return object_class(tuple(keys), last_key_type, - cls._construct_reference(_get_ts(dct, 'referredSemanticId', dict)) - if 'referredSemanticId' in dct else None) + return object_class( + tuple(keys), + last_key_type, + cls._construct_reference(_get_ts(dct, "referredSemanticId", dict)) if "referredSemanticId" in dct else None, + ) @classmethod def _construct_administrative_information( - cls, dct: Dict[str, object], object_class=model.AdministrativeInformation)\ - -> model.AdministrativeInformation: + cls, dct: Dict[str, object], object_class=model.AdministrativeInformation + ) -> model.AdministrativeInformation: ret = object_class() cls._amend_abstract_attributes(ret, dct) - if 'version' in dct: - ret.version = _get_ts(dct, 'version', str) - if 'revision' in dct: - ret.revision = _get_ts(dct, 'revision', str) - elif 'revision' in dct: + if "version" in dct: + ret.version = _get_ts(dct, "version", str) + if "revision" in dct: + ret.revision = _get_ts(dct, "revision", str) + elif "revision" in dct: logger.warning("Ignoring 'revision' attribute of AdministrativeInformation object due to missing 'version'") - if 'creator' in dct: - ret.creator = cls._construct_reference(_get_ts(dct, 'creator', dict)) - if 'templateId' in dct: - ret.template_id = _get_ts(dct, 'templateId', str) + if "creator" in dct: + ret.creator = cls._construct_reference(_get_ts(dct, "creator", dict)) + if "templateId" in dct: + ret.template_id = _get_ts(dct, "templateId", str) return ret @classmethod @@ -391,17 +438,18 @@ def _construct_operation_variable(cls, dct: Dict[str, object]) -> model.Submodel """ # TODO: remove the following type: ignore comments when mypy supports abstract types for Type[T] # see https://github.com/python/mypy/issues/5374 - return _get_ts(dct, 'value', model.SubmodelElement) # type: ignore + return _get_ts(dct, "value", model.SubmodelElement) # type: ignore @classmethod def _construct_lang_string_set(cls, lst: List[Dict[str, object]], object_class: Type[LSS]) -> LSS: ret = {} for desc in lst: try: - ret[_get_ts(desc, 'language', str)] = _get_ts(desc, 'text', str) + ret[_get_ts(desc, "language", str)] = _get_ts(desc, "text", str) except (KeyError, TypeError) as e: error_message = "Error while trying to convert JSON object into {}: {} >>> {}".format( - object_class.__name__, e, pprint.pformat(desc, depth=2, width=2 ** 14, compact=True)) + object_class.__name__, e, pprint.pformat(desc, depth=2, width=2**14, compact=True) + ) if cls.failsafe: logger.error(error_message, exc_info=e) else: @@ -411,12 +459,13 @@ def _construct_lang_string_set(cls, lst: List[Dict[str, object]], object_class: @classmethod def _construct_value_list(cls, dct: Dict[str, object]) -> model.ValueList: ret: model.ValueList = set() - for element in _get_ts(dct, 'valueReferencePairs', list): + for element in _get_ts(dct, "valueReferencePairs", list): try: ret.add(cls._construct_value_reference_pair(element)) except (KeyError, TypeError) as e: error_message = "Error while trying to convert JSON object into ValueReferencePair: {} >>> {}".format( - e, pprint.pformat(element, depth=2, width=2 ** 14, compact=True)) + e, pprint.pformat(element, depth=2, width=2**14, compact=True) + ) if cls.failsafe: logger.error(error_message, exc_info=e) else: @@ -424,11 +473,13 @@ def _construct_value_list(cls, dct: Dict[str, object]) -> model.ValueList: return ret @classmethod - def _construct_value_reference_pair(cls, dct: Dict[str, object], - object_class=model.ValueReferencePair) -> model.ValueReferencePair: - return object_class(value=_get_ts(dct, 'value', str), - value_id=cls._construct_reference(_get_ts(dct, 'valueId', dict)) - if 'valueId' in dct else None) + def _construct_value_reference_pair( + cls, dct: Dict[str, object], object_class=model.ValueReferencePair + ) -> model.ValueReferencePair: + return object_class( + value=_get_ts(dct, "value", str), + value_id=cls._construct_reference(_get_ts(dct, "valueId", dict)) if "valueId" in dct else None, + ) # ############################################################################# # Direct Constructor Methods (for classes with `modelType`) starting from here @@ -438,83 +489,96 @@ def _construct_value_reference_pair(cls, dct: Dict[str, object], # be called from the object_hook() method directly. @classmethod - def _construct_asset_information(cls, dct: Dict[str, object], object_class=model.AssetInformation)\ - -> model.AssetInformation: + def _construct_asset_information( + cls, dct: Dict[str, object], object_class=model.AssetInformation + ) -> model.AssetInformation: global_asset_id = None - if 'globalAssetId' in dct: - global_asset_id = _get_ts(dct, 'globalAssetId', str) + if "globalAssetId" in dct: + global_asset_id = _get_ts(dct, "globalAssetId", str) specific_asset_id = set() - if 'specificAssetIds' in dct: + if "specificAssetIds" in dct: for desc_data in _get_ts(dct, "specificAssetIds", list): specific_asset_id.add(cls._construct_specific_asset_id(desc_data, model.SpecificAssetId)) - ret = object_class(asset_kind=ASSET_KIND_INVERSE[_get_ts(dct, 'assetKind', str)], - global_asset_id=global_asset_id, - specific_asset_id=specific_asset_id) + ret = object_class( + asset_kind=ASSET_KIND_INVERSE[_get_ts(dct, "assetKind", str)], + global_asset_id=global_asset_id, + specific_asset_id=specific_asset_id, + ) cls._amend_abstract_attributes(ret, dct) - if 'assetType' in dct: - ret.asset_type = _get_ts(dct, 'assetType', str) - if 'defaultThumbnail' in dct: - ret.default_thumbnail = cls._construct_resource(_get_ts(dct, 'defaultThumbnail', dict)) + if "assetType" in dct: + ret.asset_type = _get_ts(dct, "assetType", str) + if "defaultThumbnail" in dct: + ret.default_thumbnail = cls._construct_resource(_get_ts(dct, "defaultThumbnail", dict)) return ret @classmethod def _construct_asset_administration_shell( - cls, dct: Dict[str, object], object_class=model.AssetAdministrationShell) -> model.AssetAdministrationShell: + cls, dct: Dict[str, object], object_class=model.AssetAdministrationShell + ) -> model.AssetAdministrationShell: ret = object_class( - asset_information=cls._construct_asset_information(_get_ts(dct, 'assetInformation', dict), - model.AssetInformation), - id_=_get_ts(dct, 'id', str)) + asset_information=cls._construct_asset_information( + _get_ts(dct, "assetInformation", dict), model.AssetInformation + ), + id_=_get_ts(dct, "id", str), + ) cls._amend_abstract_attributes(ret, dct) - if not cls.stripped and 'submodels' in dct: - for sm_data in _get_ts(dct, 'submodels', list): + if not cls.stripped and "submodels" in dct: + for sm_data in _get_ts(dct, "submodels", list): ret.submodel.add(cls._construct_model_reference(sm_data, model.Submodel)) - if 'derivedFrom' in dct: - ret.derived_from = cls._construct_model_reference(_get_ts(dct, 'derivedFrom', dict), - model.AssetAdministrationShell) + if "derivedFrom" in dct: + ret.derived_from = cls._construct_model_reference( + _get_ts(dct, "derivedFrom", dict), model.AssetAdministrationShell + ) return ret @classmethod - def _construct_concept_description(cls, dct: Dict[str, object], object_class=model.ConceptDescription)\ - -> model.ConceptDescription: - ret = object_class(id_=_get_ts(dct, 'id', str)) + def _construct_concept_description( + cls, dct: Dict[str, object], object_class=model.ConceptDescription + ) -> model.ConceptDescription: + ret = object_class(id_=_get_ts(dct, "id", str)) cls._amend_abstract_attributes(ret, dct) - if 'isCaseOf' in dct: + if "isCaseOf" in dct: for case_data in _get_ts(dct, "isCaseOf", list): ret.is_case_of.add(cls._construct_reference(case_data)) return ret @classmethod - def _construct_data_specification_iec61360(cls, dct: Dict[str, object], - object_class=model.base.DataSpecificationIEC61360)\ - -> model.base.DataSpecificationIEC61360: - ret = object_class(preferred_name=cls._construct_lang_string_set(_get_ts(dct, 'preferredName', list), - model.PreferredNameTypeIEC61360)) - if 'dataType' in dct: - ret.data_type = IEC61360_DATA_TYPES_INVERSE[_get_ts(dct, 'dataType', str)] - if 'definition' in dct: - ret.definition = cls._construct_lang_string_set(_get_ts(dct, 'definition', list), - model.DefinitionTypeIEC61360) - if 'shortName' in dct: - ret.short_name = cls._construct_lang_string_set(_get_ts(dct, 'shortName', list), - model.ShortNameTypeIEC61360) - if 'unit' in dct: - ret.unit = _get_ts(dct, 'unit', str) - if 'unitId' in dct: - ret.unit_id = cls._construct_reference(_get_ts(dct, 'unitId', dict)) - if 'sourceOfDefinition' in dct: - ret.source_of_definition = _get_ts(dct, 'sourceOfDefinition', str) - if 'symbol' in dct: - ret.symbol = _get_ts(dct, 'symbol', str) - if 'valueFormat' in dct: - ret.value_format = _get_ts(dct, 'valueFormat', str) - if 'valueList' in dct: - ret.value_list = cls._construct_value_list(_get_ts(dct, 'valueList', dict)) - if 'value' in dct: - ret.value = _get_ts(dct, 'value', str) - if 'levelType' in dct: - for k, v in _get_ts(dct, 'levelType', dict).items(): + def _construct_data_specification_iec61360( + cls, dct: Dict[str, object], object_class=model.base.DataSpecificationIEC61360 + ) -> model.base.DataSpecificationIEC61360: + ret = object_class( + preferred_name=cls._construct_lang_string_set( + _get_ts(dct, "preferredName", list), model.PreferredNameTypeIEC61360 + ) + ) + if "dataType" in dct: + ret.data_type = IEC61360_DATA_TYPES_INVERSE[_get_ts(dct, "dataType", str)] + if "definition" in dct: + ret.definition = cls._construct_lang_string_set( + _get_ts(dct, "definition", list), model.DefinitionTypeIEC61360 + ) + if "shortName" in dct: + ret.short_name = cls._construct_lang_string_set( + _get_ts(dct, "shortName", list), model.ShortNameTypeIEC61360 + ) + if "unit" in dct: + ret.unit = _get_ts(dct, "unit", str) + if "unitId" in dct: + ret.unit_id = cls._construct_reference(_get_ts(dct, "unitId", dict)) + if "sourceOfDefinition" in dct: + ret.source_of_definition = _get_ts(dct, "sourceOfDefinition", str) + if "symbol" in dct: + ret.symbol = _get_ts(dct, "symbol", str) + if "valueFormat" in dct: + ret.value_format = _get_ts(dct, "valueFormat", str) + if "valueList" in dct: + ret.value_list = cls._construct_value_list(_get_ts(dct, "valueList", dict)) + if "value" in dct: + ret.value = _get_ts(dct, "value", str) + if "levelType" in dct: + for k, v in _get_ts(dct, "levelType", dict).items(): if v: ret.level_types.add(IEC61360_LEVEL_TYPES_INVERSE[k]) return ret @@ -522,22 +586,21 @@ def _construct_data_specification_iec61360(cls, dct: Dict[str, object], @classmethod def _construct_entity(cls, dct: Dict[str, object], object_class=model.Entity) -> model.Entity: global_asset_id = None - if 'globalAssetId' in dct: - global_asset_id = _get_ts(dct, 'globalAssetId', str) + if "globalAssetId" in dct: + global_asset_id = _get_ts(dct, "globalAssetId", str) specific_asset_id = set() - if 'specificAssetIds' in dct: + if "specificAssetIds" in dct: for desc_data in _get_ts(dct, "specificAssetIds", list): specific_asset_id.add(cls._construct_specific_asset_id(desc_data, model.SpecificAssetId)) - if 'entityType' in dct: - entity_type = ENTITY_TYPES_INVERSE[_get_ts(dct, 'entityType', str)] + if "entityType" in dct: + entity_type = ENTITY_TYPES_INVERSE[_get_ts(dct, "entityType", str)] else: entity_type = None - ret = object_class(id_short=None, - entity_type=entity_type, - global_asset_id=global_asset_id, - specific_asset_id=specific_asset_id) + ret = object_class( + id_short=None, entity_type=entity_type, global_asset_id=global_asset_id, specific_asset_id=specific_asset_id + ) cls._amend_abstract_attributes(ret, dct) - if not cls.stripped and 'statements' in dct: + if not cls.stripped and "statements" in dct: for element in _get_ts(dct, "statements", list): if _expect_type(element, model.SubmodelElement, str(ret), cls.failsafe): ret.statement.add(element) @@ -545,36 +608,38 @@ def _construct_entity(cls, dct: Dict[str, object], object_class=model.Entity) -> @classmethod def _construct_qualifier(cls, dct: Dict[str, object], object_class=model.Qualifier) -> model.Qualifier: - ret = object_class(type_=_get_ts(dct, 'type', str), - value_type=model.datatypes.XSD_TYPE_CLASSES[_get_ts(dct, 'valueType', str)]) + ret = object_class( + type_=_get_ts(dct, "type", str), value_type=model.datatypes.XSD_TYPE_CLASSES[_get_ts(dct, "valueType", str)] + ) cls._amend_abstract_attributes(ret, dct) - if 'value' in dct: - ret.value = model.datatypes.from_xsd(_get_ts(dct, 'value', str), ret.value_type) - if 'valueId' in dct: - ret.value_id = cls._construct_reference(_get_ts(dct, 'valueId', dict)) - if 'kind' in dct: - ret.kind = QUALIFIER_KIND_INVERSE[_get_ts(dct, 'kind', str)] + if "value" in dct: + ret.value = model.datatypes.from_xsd(_get_ts(dct, "value", str), ret.value_type) + if "valueId" in dct: + ret.value_id = cls._construct_reference(_get_ts(dct, "valueId", dict)) + if "kind" in dct: + ret.kind = QUALIFIER_KIND_INVERSE[_get_ts(dct, "kind", str)] return ret @classmethod def _construct_extension(cls, dct: Dict[str, object], object_class=model.Extension) -> model.Extension: - ret = object_class(name=_get_ts(dct, 'name', str)) + ret = object_class(name=_get_ts(dct, "name", str)) cls._amend_abstract_attributes(ret, dct) - if 'valueType' in dct: - ret.value_type = model.datatypes.XSD_TYPE_CLASSES[_get_ts(dct, 'valueType', str)] - if 'value' in dct: - ret.value = model.datatypes.from_xsd(_get_ts(dct, 'value', str), ret.value_type) - if 'refersTo' in dct: - ret.refers_to = {cls._construct_model_reference(refers_to, model.Referable) # type: ignore - for refers_to in _get_ts(dct, 'refersTo', list)} + if "valueType" in dct: + ret.value_type = model.datatypes.XSD_TYPE_CLASSES[_get_ts(dct, "valueType", str)] + if "value" in dct: + ret.value = model.datatypes.from_xsd(_get_ts(dct, "value", str), ret.value_type) + if "refersTo" in dct: + ret.refers_to = { + cls._construct_model_reference(refers_to, model.Referable) # type: ignore + for refers_to in _get_ts(dct, "refersTo", list) + } return ret @classmethod def _construct_submodel(cls, dct: Dict[str, object], object_class=model.Submodel) -> model.Submodel: - ret = object_class(id_=_get_ts(dct, 'id', str), - kind=cls._get_kind(dct)) + ret = object_class(id_=_get_ts(dct, "id", str), kind=cls._get_kind(dct)) cls._amend_abstract_attributes(ret, dct) - if not cls.stripped and 'submodelElements' in dct: + if not cls.stripped and "submodelElements" in dct: for element in _get_ts(dct, "submodelElements", list): if _expect_type(element, model.SubmodelElement, str(ret), cls.failsafe): ret.submodel_element.add(element) @@ -587,26 +652,28 @@ def _construct_capability(cls, dct: Dict[str, object], object_class=model.Capabi return ret @classmethod - def _construct_basic_event_element(cls, dct: Dict[str, object], object_class=model.BasicEventElement) \ - -> model.BasicEventElement: + def _construct_basic_event_element( + cls, dct: Dict[str, object], object_class=model.BasicEventElement + ) -> model.BasicEventElement: # TODO: remove the following type: ignore comments when mypy supports abstract types for Type[T] # see https://github.com/python/mypy/issues/5374 - ret = object_class(id_short=None, - observed=cls._construct_model_reference(_get_ts(dct, 'observed', dict), - model.Referable), # type: ignore - direction=DIRECTION_INVERSE[_get_ts(dct, "direction", str)], - state=STATE_OF_EVENT_INVERSE[_get_ts(dct, "state", str)]) + ret = object_class( + id_short=None, + observed=cls._construct_model_reference(_get_ts(dct, "observed", dict), model.Referable), # type: ignore + direction=DIRECTION_INVERSE[_get_ts(dct, "direction", str)], + state=STATE_OF_EVENT_INVERSE[_get_ts(dct, "state", str)], + ) cls._amend_abstract_attributes(ret, dct) - if 'messageTopic' in dct: - ret.message_topic = _get_ts(dct, 'messageTopic', str) - if 'messageBroker' in dct: - ret.message_broker = cls._construct_reference(_get_ts(dct, 'messageBroker', dict)) - if 'lastUpdate' in dct: - ret.last_update = model.datatypes.from_xsd(_get_ts(dct, 'lastUpdate', str), model.datatypes.DateTime) - if 'minInterval' in dct: - ret.min_interval = model.datatypes.from_xsd(_get_ts(dct, 'minInterval', str), model.datatypes.Duration) - if 'maxInterval' in dct: - ret.max_interval = model.datatypes.from_xsd(_get_ts(dct, 'maxInterval', str), model.datatypes.Duration) + if "messageTopic" in dct: + ret.message_topic = _get_ts(dct, "messageTopic", str) + if "messageBroker" in dct: + ret.message_broker = cls._construct_reference(_get_ts(dct, "messageBroker", dict)) + if "lastUpdate" in dct: + ret.last_update = model.datatypes.from_xsd(_get_ts(dct, "lastUpdate", str), model.datatypes.DateTime) + if "minInterval" in dct: + ret.min_interval = model.datatypes.from_xsd(_get_ts(dct, "minInterval", str), model.datatypes.Duration) + if "maxInterval" in dct: + ret.max_interval = model.datatypes.from_xsd(_get_ts(dct, "maxInterval", str), model.datatypes.Duration) return ret @classmethod @@ -615,16 +682,19 @@ def _construct_operation(cls, dct: Dict[str, object], object_class=model.Operati cls._amend_abstract_attributes(ret, dct) # Deserialize variables (they are not Referable, thus we don't - for json_name, target in (('inputVariables', ret.input_variable), - ('outputVariables', ret.output_variable), - ('inoutputVariables', ret.in_output_variable)): + for json_name, target in ( + ("inputVariables", ret.input_variable), + ("outputVariables", ret.output_variable), + ("inoutputVariables", ret.in_output_variable), + ): if json_name in dct: for variable_data in _get_ts(dct, json_name, list): try: target.add(cls._construct_operation_variable(variable_data)) except (KeyError, TypeError) as e: error_message = "Error while trying to convert JSON object into {} of {}: {}".format( - json_name, ret, pprint.pformat(variable_data, depth=2, width=2 ** 14, compact=True)) + json_name, ret, pprint.pformat(variable_data, depth=2, width=2**14, compact=True) + ) if cls.failsafe: logger.error(error_message, exc_info=e) else: @@ -633,61 +703,77 @@ def _construct_operation(cls, dct: Dict[str, object], object_class=model.Operati @classmethod def _construct_relationship_element( - cls, dct: Dict[str, object], object_class=model.RelationshipElement) -> model.RelationshipElement: - ret = object_class(id_short=None, - first=cls._construct_reference(_get_ts(dct, 'first', dict)) if 'first' in dct else None, - second=cls._construct_reference(_get_ts(dct, 'second', dict)) if 'second' in dct else None) + cls, dct: Dict[str, object], object_class=model.RelationshipElement + ) -> model.RelationshipElement: + ret = object_class( + id_short=None, + first=cls._construct_reference(_get_ts(dct, "first", dict)) if "first" in dct else None, + second=cls._construct_reference(_get_ts(dct, "second", dict)) if "second" in dct else None, + ) cls._amend_abstract_attributes(ret, dct) return ret @classmethod def _construct_annotated_relationship_element( - cls, dct: Dict[str, object], object_class=model.AnnotatedRelationshipElement)\ - -> model.AnnotatedRelationshipElement: + cls, dct: Dict[str, object], object_class=model.AnnotatedRelationshipElement + ) -> model.AnnotatedRelationshipElement: ret = object_class( id_short=None, - first=cls._construct_reference(_get_ts(dct, 'first', dict)) if 'first' in dct else None, - second=cls._construct_reference(_get_ts(dct, 'second', dict)) if 'second' in dct else None) + first=cls._construct_reference(_get_ts(dct, "first", dict)) if "first" in dct else None, + second=cls._construct_reference(_get_ts(dct, "second", dict)) if "second" in dct else None, + ) cls._amend_abstract_attributes(ret, dct) - if not cls.stripped and 'annotations' in dct: - for element in _get_ts(dct, 'annotations', list): + if not cls.stripped and "annotations" in dct: + for element in _get_ts(dct, "annotations", list): if _expect_type(element, model.DataElement, str(ret), cls.failsafe): ret.annotation.add(element) return ret @classmethod - def _construct_submodel_element_collection(cls, dct: Dict[str, object], - object_class=model.SubmodelElementCollection)\ - -> model.SubmodelElementCollection: + def _construct_submodel_element_collection( + cls, dct: Dict[str, object], object_class=model.SubmodelElementCollection + ) -> model.SubmodelElementCollection: ret = object_class(id_short=None) cls._amend_abstract_attributes(ret, dct) - if not cls.stripped and 'value' in dct: + if not cls.stripped and "value" in dct: for element in _get_ts(dct, "value", list): if _expect_type(element, model.SubmodelElement, str(ret), cls.failsafe): ret.value.add(element) return ret @classmethod - def _construct_submodel_element_list(cls, dct: Dict[str, object], object_class=model.SubmodelElementList)\ - -> model.SubmodelElementList: + def _construct_submodel_element_list( + cls, dct: Dict[str, object], object_class=model.SubmodelElementList + ) -> model.SubmodelElementList: type_value_list_element = KEY_TYPES_CLASSES_INVERSE[ - KEY_TYPES_INVERSE[_get_ts(dct, 'typeValueListElement', str)]] + KEY_TYPES_INVERSE[_get_ts(dct, "typeValueListElement", str)] + ] if not issubclass(type_value_list_element, model.SubmodelElement): - raise ValueError("Expected a SubmodelElementList with a typeValueListElement that is a subclass of" - f"{model.SubmodelElement}, got {type_value_list_element}!") - order_relevant = _get_ts(dct, 'orderRelevant', bool) if 'orderRelevant' in dct else True - semantic_id_list_element = cls._construct_reference(_get_ts(dct, 'semanticIdListElement', dict))\ - if 'semanticIdListElement' in dct else None - value_type_list_element = model.datatypes.XSD_TYPE_CLASSES[_get_ts(dct, 'valueTypeListElement', str)]\ - if 'valueTypeListElement' in dct else None - ret = object_class(id_short=None, - type_value_list_element=type_value_list_element, - order_relevant=order_relevant, - semantic_id_list_element=semantic_id_list_element, - value_type_list_element=value_type_list_element) + raise ValueError( + "Expected a SubmodelElementList with a typeValueListElement that is a subclass of" + f"{model.SubmodelElement}, got {type_value_list_element}!" + ) + order_relevant = _get_ts(dct, "orderRelevant", bool) if "orderRelevant" in dct else True + semantic_id_list_element = ( + cls._construct_reference(_get_ts(dct, "semanticIdListElement", dict)) + if "semanticIdListElement" in dct + else None + ) + value_type_list_element = ( + model.datatypes.XSD_TYPE_CLASSES[_get_ts(dct, "valueTypeListElement", str)] + if "valueTypeListElement" in dct + else None + ) + ret = object_class( + id_short=None, + type_value_list_element=type_value_list_element, + order_relevant=order_relevant, + semantic_id_list_element=semantic_id_list_element, + value_type_list_element=value_type_list_element, + ) cls._amend_abstract_attributes(ret, dct) - if not cls.stripped and 'value' in dct: - for element in _get_ts(dct, 'value', list): + if not cls.stripped and "value" in dct: + for element in _get_ts(dct, "value", list): if _expect_type(element, type_value_list_element, str(ret), cls.failsafe): ret.value.add(element) return ret @@ -695,74 +781,78 @@ def _construct_submodel_element_list(cls, dct: Dict[str, object], object_class=m @classmethod def _construct_blob(cls, dct: Dict[str, object], object_class=model.Blob) -> model.Blob: ret = object_class( - id_short=None, - content_type=_get_ts(dct, "contentType", str) if 'contentType' in dct else None + id_short=None, content_type=_get_ts(dct, "contentType", str) if "contentType" in dct else None ) cls._amend_abstract_attributes(ret, dct) - if 'value' in dct: - ret.value = base64.b64decode(_get_ts(dct, 'value', str)) + if "value" in dct: + ret.value = base64.b64decode(_get_ts(dct, "value", str)) return ret @classmethod def _construct_file(cls, dct: Dict[str, object], object_class=model.File) -> model.File: - content_type = _get_ts(dct, "contentType", str) if 'contentType' in dct else None - ret = object_class(id_short=None, - value=None, - content_type=_get_ts(dct, "contentType", str) if 'contentType' in dct else None) + content_type = _get_ts(dct, "contentType", str) if "contentType" in dct else None + ret = object_class( + id_short=None, value=None, content_type=_get_ts(dct, "contentType", str) if "contentType" in dct else None + ) cls._amend_abstract_attributes(ret, dct) - if 'value' in dct and dct['value'] is not None: - ret.value = _get_ts(dct, 'value', str) + if "value" in dct and dct["value"] is not None: + ret.value = _get_ts(dct, "value", str) return ret @classmethod def _construct_resource(cls, dct: Dict[str, object], object_class=model.Resource) -> model.Resource: ret = object_class(path=_get_ts(dct, "path", str)) cls._amend_abstract_attributes(ret, dct) - if 'contentType' in dct and dct['contentType'] is not None: - ret.content_type = _get_ts(dct, 'contentType', str) + if "contentType" in dct and dct["contentType"] is not None: + ret.content_type = _get_ts(dct, "contentType", str) return ret @classmethod def _construct_multi_language_property( - cls, dct: Dict[str, object], object_class=model.MultiLanguageProperty) -> model.MultiLanguageProperty: + cls, dct: Dict[str, object], object_class=model.MultiLanguageProperty + ) -> model.MultiLanguageProperty: ret = object_class(id_short=None) cls._amend_abstract_attributes(ret, dct) - if 'value' in dct and dct['value'] is not None: - ret.value = cls._construct_lang_string_set(_get_ts(dct, 'value', list), model.MultiLanguageTextType) - if 'valueId' in dct: - ret.value_id = cls._construct_reference(_get_ts(dct, 'valueId', dict)) + if "value" in dct and dct["value"] is not None: + ret.value = cls._construct_lang_string_set(_get_ts(dct, "value", list), model.MultiLanguageTextType) + if "valueId" in dct: + ret.value_id = cls._construct_reference(_get_ts(dct, "valueId", dict)) return ret @classmethod def _construct_property(cls, dct: Dict[str, object], object_class=model.Property) -> model.Property: - ret = object_class(id_short=None, - value_type=model.datatypes.XSD_TYPE_CLASSES[_get_ts(dct, 'valueType', str)],) + ret = object_class( + id_short=None, + value_type=model.datatypes.XSD_TYPE_CLASSES[_get_ts(dct, "valueType", str)], + ) cls._amend_abstract_attributes(ret, dct) - if 'value' in dct and dct['value'] is not None: - ret.value = model.datatypes.from_xsd(_get_ts(dct, 'value', str), ret.value_type) - if 'valueId' in dct: - ret.value_id = cls._construct_reference(_get_ts(dct, 'valueId', dict)) + if "value" in dct and dct["value"] is not None: + ret.value = model.datatypes.from_xsd(_get_ts(dct, "value", str), ret.value_type) + if "valueId" in dct: + ret.value_id = cls._construct_reference(_get_ts(dct, "valueId", dict)) return ret @classmethod def _construct_range(cls, dct: Dict[str, object], object_class=model.Range) -> model.Range: - ret = object_class(id_short=None, - value_type=model.datatypes.XSD_TYPE_CLASSES[_get_ts(dct, 'valueType', str)],) + ret = object_class( + id_short=None, + value_type=model.datatypes.XSD_TYPE_CLASSES[_get_ts(dct, "valueType", str)], + ) cls._amend_abstract_attributes(ret, dct) - if 'min' in dct and dct['min'] is not None: - ret.min = model.datatypes.from_xsd(_get_ts(dct, 'min', str), ret.value_type) - if 'max' in dct and dct['max'] is not None: - ret.max = model.datatypes.from_xsd(_get_ts(dct, 'max', str), ret.value_type) + if "min" in dct and dct["min"] is not None: + ret.min = model.datatypes.from_xsd(_get_ts(dct, "min", str), ret.value_type) + if "max" in dct and dct["max"] is not None: + ret.max = model.datatypes.from_xsd(_get_ts(dct, "max", str), ret.value_type) return ret @classmethod def _construct_reference_element( - cls, dct: Dict[str, object], object_class=model.ReferenceElement) -> model.ReferenceElement: - ret = object_class(id_short=None, - value=None) + cls, dct: Dict[str, object], object_class=model.ReferenceElement + ) -> model.ReferenceElement: + ret = object_class(id_short=None, value=None) cls._amend_abstract_attributes(ret, dct) - if 'value' in dct and dct['value'] is not None: - ret.value = cls._construct_reference(_get_ts(dct, 'value', dict)) + if "value" in dct and dct["value"] is not None: + ret.value = cls._construct_reference(_get_ts(dct, "value", dict)) return ret @@ -774,6 +864,7 @@ class StrictAASFromJsonDecoder(AASFromJsonDecoder): This version has set ``failsafe = False``, which will lead to Exceptions raised for every missing attribute or wrong object type. """ + failsafe = False @@ -781,6 +872,7 @@ class StrippedAASFromJsonDecoder(AASFromJsonDecoder): """ Decoder for stripped JSON objects. Used in the HTTP adapter. """ + stripped = True @@ -788,11 +880,13 @@ class StrictStrippedAASFromJsonDecoder(StrictAASFromJsonDecoder, StrippedAASFrom """ Non-failsafe decoder for stripped JSON objects. """ + pass -def _select_decoder(failsafe: bool, stripped: bool, decoder: Optional[Type[AASFromJsonDecoder]]) \ - -> Type[AASFromJsonDecoder]: +def _select_decoder( + failsafe: bool, stripped: bool, decoder: Optional[Type[AASFromJsonDecoder]] +) -> Type[AASFromJsonDecoder]: """ Returns the correct decoder based on the parameters failsafe and stripped. If a decoder class is given, failsafe and stripped are ignored. @@ -815,11 +909,16 @@ def _select_decoder(failsafe: bool, stripped: bool, decoder: Optional[Type[AASFr return StrictAASFromJsonDecoder -def read_aas_json_file_into(object_store: model.AbstractObjectStore, file: PathOrIO, replace_existing: bool = False, - ignore_existing: bool = False, failsafe: bool = True, stripped: bool = False, - decoder: Optional[Type[AASFromJsonDecoder]] = None, - keys_to_types: Iterable[Tuple[str, Any]] = JSON_AAS_TOP_LEVEL_KEYS_TO_TYPES) \ - -> Set[model.Identifier]: +def read_aas_json_file_into( + object_store: model.AbstractObjectStore, + file: PathOrIO, + replace_existing: bool = False, + ignore_existing: bool = False, + failsafe: bool = True, + stripped: bool = False, + decoder: Optional[Type[AASFromJsonDecoder]] = None, + keys_to_types: Iterable[Tuple[str, Any]] = JSON_AAS_TOP_LEVEL_KEYS_TO_TYPES, +) -> Set[model.Identifier]: """ Read an Asset Administration Shell JSON file according to 'Details of the Asset Administration Shell', chapter 5.5 into a given ObjectStore. diff --git a/sdk/basyx/aas/adapter/json/json_serialization.py b/sdk/basyx/aas/adapter/json/json_serialization.py index d981f2283..76cd7966b 100644 --- a/sdk/basyx/aas/adapter/json/json_serialization.py +++ b/sdk/basyx/aas/adapter/json/json_serialization.py @@ -26,6 +26,7 @@ later on. The special helper function ``_abstract_classes_to_json`` is called by most of the conversion functions to handle all the attributes of abstract base classes. """ + import base64 import contextlib import inspect @@ -56,6 +57,7 @@ class AASToJsonEncoder(json.JSONEncoder): Defaults to ``False``. See https://git.rwth-aachen.de/acplt/pyi40aas/-/issues/91 """ + stripped = False @classmethod @@ -117,45 +119,50 @@ def _abstract_classes_to_json(cls, obj: object) -> Dict[str, object]: data: Dict[str, object] = {} if isinstance(obj, model.HasExtension) and not cls.stripped: if obj.extension: - data['extensions'] = list(obj.extension) + data["extensions"] = list(obj.extension) if isinstance(obj, model.HasDataSpecification) and not cls.stripped: if obj.embedded_data_specifications: - data['embeddedDataSpecifications'] = [ - {'dataSpecification': spec.data_specification, - 'dataSpecificationContent': spec.data_specification_content} + data["embeddedDataSpecifications"] = [ + { + "dataSpecification": spec.data_specification, + "dataSpecificationContent": spec.data_specification_content, + } for spec in obj.embedded_data_specifications ] if isinstance(obj, model.Referable): if obj.id_short and not isinstance(obj.parent, model.SubmodelElementList): - data['idShort'] = obj.id_short + data["idShort"] = obj.id_short if obj.display_name: - data['displayName'] = obj.display_name + data["displayName"] = obj.display_name if obj.category: - data['category'] = obj.category + data["category"] = obj.category if obj.description: - data['description'] = obj.description + data["description"] = obj.description try: ref_type = model.resolve_referable_class_in_key_types(obj) except StopIteration as e: - raise TypeError("Object of type {} is Referable but does not inherit from a known AAS type" - .format(obj.__class__.__name__)) from e - data['modelType'] = ref_type.__name__ + raise TypeError( + "Object of type {} is Referable but does not inherit from a known AAS type".format( + obj.__class__.__name__ + ) + ) from e + data["modelType"] = ref_type.__name__ if isinstance(obj, model.Identifiable): - data['id'] = obj.id + data["id"] = obj.id if obj.administration: - data['administration'] = obj.administration + data["administration"] = obj.administration if isinstance(obj, model.HasSemantics): if obj.semantic_id: - data['semanticId'] = obj.semantic_id + data["semanticId"] = obj.semantic_id if obj.supplemental_semantic_id: - data['supplementalSemanticIds'] = list(obj.supplemental_semantic_id) + data["supplementalSemanticIds"] = list(obj.supplemental_semantic_id) if isinstance(obj, model.HasKind): if obj.kind is model.ModellingKind.TEMPLATE: - data['kind'] = _generic.MODELLING_KIND[obj.kind] + data["kind"] = _generic.MODELLING_KIND[obj.kind] if isinstance(obj, model.Qualifiable) and not cls.stripped: if obj.qualifier: - data['qualifiers'] = list(obj.qualifier) + data["qualifiers"] = list(obj.qualifier) return data # ############################################################# @@ -164,8 +171,7 @@ def _abstract_classes_to_json(cls, obj: object) -> Dict[str, object]: @classmethod def _lang_string_set_to_json(cls, obj: model.LangStringSet) -> List[Dict[str, object]]: - return [{'language': k, 'text': v} - for k, v in obj.items()] + return [{"language": k, "text": v} for k, v in obj.items()] @classmethod def _key_to_json(cls, obj: model.Key) -> Dict[str, object]: @@ -176,8 +182,7 @@ def _key_to_json(cls, obj: model.Key) -> Dict[str, object]: :return: dict with the serialized attributes of this object """ data = cls._abstract_classes_to_json(obj) - data.update({'type': _generic.KEY_TYPES[obj.type], - 'value': obj.value}) + data.update({"type": _generic.KEY_TYPES[obj.type], "value": obj.value}) return data @classmethod @@ -190,13 +195,13 @@ def _administrative_information_to_json(cls, obj: model.AdministrativeInformatio """ data = cls._abstract_classes_to_json(obj) if obj.version: - data['version'] = obj.version + data["version"] = obj.version if obj.revision: - data['revision'] = obj.revision + data["revision"] = obj.revision if obj.creator: - data['creator'] = obj.creator + data["creator"] = obj.creator if obj.template_id: - data['templateId'] = obj.template_id + data["templateId"] = obj.template_id return data @classmethod @@ -208,10 +213,10 @@ def _reference_to_json(cls, obj: model.Reference) -> Dict[str, object]: :return: dict with the serialized attributes of this object """ data = cls._abstract_classes_to_json(obj) - data['type'] = _generic.REFERENCE_TYPES[obj.__class__] - data['keys'] = list(obj.key) + data["type"] = _generic.REFERENCE_TYPES[obj.__class__] + data["keys"] = list(obj.key) if obj.referred_semantic_id is not None: - data['referredSemanticId'] = cls._reference_to_json(obj.referred_semantic_id) + data["referredSemanticId"] = cls._reference_to_json(obj.referred_semantic_id) return data @classmethod @@ -235,14 +240,14 @@ def _qualifier_to_json(cls, obj: model.Qualifier) -> Dict[str, object]: """ data = cls._abstract_classes_to_json(obj) if obj.value: - data['value'] = model.datatypes.xsd_repr(obj.value) if obj.value is not None else None + data["value"] = model.datatypes.xsd_repr(obj.value) if obj.value is not None else None if obj.value_id: - data['valueId'] = obj.value_id + data["valueId"] = obj.value_id # Even though kind is optional in the schema, it's better to always serialize it instead of specifying # the default value in multiple locations. - data['kind'] = _generic.QUALIFIER_KIND[obj.kind] - data['valueType'] = model.datatypes.XSD_TYPE_NAMES[obj.value_type] - data['type'] = obj.type + data["kind"] = _generic.QUALIFIER_KIND[obj.kind] + data["valueType"] = model.datatypes.XSD_TYPE_NAMES[obj.value_type] + data["type"] = obj.type return data @classmethod @@ -255,12 +260,12 @@ def _extension_to_json(cls, obj: model.Extension) -> Dict[str, object]: """ data = cls._abstract_classes_to_json(obj) if obj.value: - data['value'] = model.datatypes.xsd_repr(obj.value) if obj.value is not None else None + data["value"] = model.datatypes.xsd_repr(obj.value) if obj.value is not None else None if obj.refers_to: - data['refersTo'] = list(obj.refers_to) + data["refersTo"] = list(obj.refers_to) if obj.value_type: - data['valueType'] = model.datatypes.XSD_TYPE_NAMES[obj.value_type] - data['name'] = obj.name + data["valueType"] = model.datatypes.XSD_TYPE_NAMES[obj.value_type] + data["name"] = obj.name return data @classmethod @@ -272,8 +277,7 @@ def _value_reference_pair_to_json(cls, obj: model.ValueReferencePair) -> Dict[st :return: dict with the serialized attributes of this object """ data = cls._abstract_classes_to_json(obj) - data.update({'value': model.datatypes.xsd_repr(obj.value), - 'valueId': obj.value_id}) + data.update({"value": model.datatypes.xsd_repr(obj.value), "valueId": obj.value_id}) return data @classmethod @@ -284,7 +288,7 @@ def _value_list_to_json(cls, obj: model.ValueList) -> Dict[str, object]: :param obj: object of class ValueList :return: dict with the serialized attributes of this object """ - return {'valueReferencePairs': list(obj)} + return {"valueReferencePairs": list(obj)} # ############################################################ # transformation functions to serialize classes from model.aas @@ -299,10 +303,10 @@ def _specific_asset_id_to_json(cls, obj: model.SpecificAssetId) -> Dict[str, obj :return: dict with the serialized attributes of this object """ data = cls._abstract_classes_to_json(obj) - data['name'] = obj.name - data['value'] = obj.value + data["name"] = obj.name + data["value"] = obj.value if obj.external_subject_id: - data['externalSubjectId'] = obj.external_subject_id + data["externalSubjectId"] = obj.external_subject_id return data @classmethod @@ -314,15 +318,15 @@ def _asset_information_to_json(cls, obj: model.AssetInformation) -> Dict[str, ob :return: dict with the serialized attributes of this object """ data = cls._abstract_classes_to_json(obj) - data['assetKind'] = _generic.ASSET_KIND[obj.asset_kind] + data["assetKind"] = _generic.ASSET_KIND[obj.asset_kind] if obj.global_asset_id: - data['globalAssetId'] = obj.global_asset_id + data["globalAssetId"] = obj.global_asset_id if obj.specific_asset_id: - data['specificAssetIds'] = list(obj.specific_asset_id) + data["specificAssetIds"] = list(obj.specific_asset_id) if obj.asset_type: - data['assetType'] = obj.asset_type + data["assetType"] = obj.asset_type if obj.default_thumbnail: - data['defaultThumbnail'] = obj.default_thumbnail + data["defaultThumbnail"] = obj.default_thumbnail return data @classmethod @@ -335,44 +339,40 @@ def _concept_description_to_json(cls, obj: model.ConceptDescription) -> Dict[str """ data = cls._abstract_classes_to_json(obj) if obj.is_case_of: - data['isCaseOf'] = list(obj.is_case_of) + data["isCaseOf"] = list(obj.is_case_of) return data @classmethod - def _data_specification_iec61360_to_json( - cls, obj: model.base.DataSpecificationIEC61360) -> Dict[str, object]: + def _data_specification_iec61360_to_json(cls, obj: model.base.DataSpecificationIEC61360) -> Dict[str, object]: """ serialization of an object from class DataSpecificationIEC61360 to json :param obj: object of class DataSpecificationIEC61360 :return: dict with the serialized attributes of this object """ - data_spec: Dict[str, object] = { - 'modelType': 'DataSpecificationIec61360', - 'preferredName': obj.preferred_name - } + data_spec: Dict[str, object] = {"modelType": "DataSpecificationIec61360", "preferredName": obj.preferred_name} if obj.data_type is not None: - data_spec['dataType'] = _generic.IEC61360_DATA_TYPES[obj.data_type] + data_spec["dataType"] = _generic.IEC61360_DATA_TYPES[obj.data_type] if obj.definition is not None: - data_spec['definition'] = obj.definition + data_spec["definition"] = obj.definition if obj.short_name is not None: - data_spec['shortName'] = obj.short_name + data_spec["shortName"] = obj.short_name if obj.unit is not None: - data_spec['unit'] = obj.unit + data_spec["unit"] = obj.unit if obj.unit_id is not None: - data_spec['unitId'] = obj.unit_id + data_spec["unitId"] = obj.unit_id if obj.source_of_definition is not None: - data_spec['sourceOfDefinition'] = obj.source_of_definition + data_spec["sourceOfDefinition"] = obj.source_of_definition if obj.symbol is not None: - data_spec['symbol'] = obj.symbol + data_spec["symbol"] = obj.symbol if obj.value_format is not None: - data_spec['valueFormat'] = obj.value_format + data_spec["valueFormat"] = obj.value_format if obj.value_list is not None: - data_spec['valueList'] = cls._value_list_to_json(obj.value_list) + data_spec["valueList"] = cls._value_list_to_json(obj.value_list) if obj.value is not None: - data_spec['value'] = obj.value + data_spec["value"] = obj.value if obj.level_types: - data_spec['levelType'] = {v: k in obj.level_types for k, v in _generic.IEC61360_LEVEL_TYPES.items()} + data_spec["levelType"] = {v: k in obj.level_types for k, v in _generic.IEC61360_LEVEL_TYPES.items()} return data_spec @classmethod @@ -407,7 +407,7 @@ def _submodel_to_json(cls, obj: model.Submodel) -> Dict[str, object]: # TODO ma """ data = cls._abstract_classes_to_json(obj) if not cls.stripped and obj.submodel_element != set(): - data['submodelElements'] = list(obj.submodel_element) + data["submodelElements"] = list(obj.submodel_element) return data @classmethod @@ -430,10 +430,10 @@ def _property_to_json(cls, obj: model.Property) -> Dict[str, object]: """ data = cls._abstract_classes_to_json(obj) if obj.value is not None: - data['value'] = model.datatypes.xsd_repr(obj.value) + data["value"] = model.datatypes.xsd_repr(obj.value) if obj.value_id: - data['valueId'] = obj.value_id - data['valueType'] = model.datatypes.XSD_TYPE_NAMES[obj.value_type] + data["valueId"] = obj.value_id + data["valueType"] = model.datatypes.XSD_TYPE_NAMES[obj.value_type] return data @classmethod @@ -446,9 +446,9 @@ def _multi_language_property_to_json(cls, obj: model.MultiLanguageProperty) -> D """ data = cls._abstract_classes_to_json(obj) if obj.value: - data['value'] = obj.value + data["value"] = obj.value if obj.value_id: - data['valueId'] = obj.value_id + data["valueId"] = obj.value_id return data @classmethod @@ -460,11 +460,11 @@ def _range_to_json(cls, obj: model.Range) -> Dict[str, object]: :return: dict with the serialized attributes of this object """ data = cls._abstract_classes_to_json(obj) - data['valueType'] = model.datatypes.XSD_TYPE_NAMES[obj.value_type] + data["valueType"] = model.datatypes.XSD_TYPE_NAMES[obj.value_type] if obj.min is not None: - data['min'] = model.datatypes.xsd_repr(obj.min) + data["min"] = model.datatypes.xsd_repr(obj.min) if obj.max is not None: - data['max'] = model.datatypes.xsd_repr(obj.max) + data["max"] = model.datatypes.xsd_repr(obj.max) return data @classmethod @@ -477,9 +477,9 @@ def _blob_to_json(cls, obj: model.Blob) -> Dict[str, object]: """ data = cls._abstract_classes_to_json(obj) if obj.content_type is not None: - data['contentType'] = obj.content_type + data["contentType"] = obj.content_type if obj.value is not None: - data['value'] = base64.b64encode(obj.value).decode() + data["value"] = base64.b64encode(obj.value).decode() return data @classmethod @@ -492,9 +492,9 @@ def _file_to_json(cls, obj: model.File) -> Dict[str, object]: """ data = cls._abstract_classes_to_json(obj) if obj.content_type is not None: - data['contentType'] = obj.content_type + data["contentType"] = obj.content_type if obj.value is not None: - data['value'] = obj.value + data["value"] = obj.value return data @classmethod @@ -506,9 +506,9 @@ def _resource_to_json(cls, obj: model.Resource) -> Dict[str, object]: :return: dict with the serialized attributes of this object """ data = cls._abstract_classes_to_json(obj) - data['path'] = obj.path + data["path"] = obj.path if obj.content_type is not None: - data['contentType'] = obj.content_type + data["contentType"] = obj.content_type return data @classmethod @@ -521,7 +521,7 @@ def _reference_element_to_json(cls, obj: model.ReferenceElement) -> Dict[str, ob """ data = cls._abstract_classes_to_json(obj) if obj.value: - data['value'] = obj.value + data["value"] = obj.value return data @classmethod @@ -534,7 +534,7 @@ def _submodel_element_collection_to_json(cls, obj: model.SubmodelElementCollecti """ data = cls._abstract_classes_to_json(obj) if not cls.stripped and len(obj.value) > 0: - data['value'] = list(obj.value) + data["value"] = list(obj.value) return data @classmethod @@ -548,14 +548,14 @@ def _submodel_element_list_to_json(cls, obj: model.SubmodelElementList) -> Dict[ data = cls._abstract_classes_to_json(obj) # Even though orderRelevant is optional in the schema, it's better to always serialize it instead of specifying # the default value in multiple locations. - data['orderRelevant'] = obj.order_relevant - data['typeValueListElement'] = _generic.KEY_TYPES[model.KEY_TYPES_CLASSES[obj.type_value_list_element]] + data["orderRelevant"] = obj.order_relevant + data["typeValueListElement"] = _generic.KEY_TYPES[model.KEY_TYPES_CLASSES[obj.type_value_list_element]] if obj.semantic_id_list_element is not None: - data['semanticIdListElement'] = obj.semantic_id_list_element + data["semanticIdListElement"] = obj.semantic_id_list_element if obj.value_type_list_element is not None: - data['valueTypeListElement'] = model.datatypes.XSD_TYPE_NAMES[obj.value_type_list_element] + data["valueTypeListElement"] = model.datatypes.XSD_TYPE_NAMES[obj.value_type_list_element] if not cls.stripped and len(obj.value) > 0: - data['value'] = list(obj.value) + data["value"] = list(obj.value) return data @classmethod @@ -568,9 +568,9 @@ def _relationship_element_to_json(cls, obj: model.RelationshipElement) -> Dict[s """ data = cls._abstract_classes_to_json(obj) if obj.first is not None: - data.update({'first': obj.first}) + data.update({"first": obj.first}) if obj.second is not None: - data.update({'second': obj.second}) + data.update({"second": obj.second}) return data @classmethod @@ -583,7 +583,7 @@ def _annotated_relationship_element_to_json(cls, obj: model.AnnotatedRelationshi """ data = cls._relationship_element_to_json(obj) if not cls.stripped and obj.annotation: - data['annotations'] = list(obj.annotation) + data["annotations"] = list(obj.annotation) return data @classmethod @@ -598,7 +598,7 @@ def _operation_variable_to_json(cls, obj: model.SubmodelElement) -> Dict[str, ob :return: ``OperationVariable`` wrapper containing the serialized :class:`~basyx.aas.model.submodel.SubmodelElement` """ - return {'value': obj} + return {"value": obj} @classmethod def _operation_to_json(cls, obj: model.Operation) -> Dict[str, object]: @@ -609,9 +609,11 @@ def _operation_to_json(cls, obj: model.Operation) -> Dict[str, object]: :return: dict with the serialized attributes of this object """ data = cls._abstract_classes_to_json(obj) - for tag, nss in (('inputVariables', obj.input_variable), - ('outputVariables', obj.output_variable), - ('inoutputVariables', obj.in_output_variable)): + for tag, nss in ( + ("inputVariables", obj.input_variable), + ("outputVariables", obj.output_variable), + ("inoutputVariables", obj.in_output_variable), + ): if nss: data[tag] = [cls._operation_variable_to_json(obj) for obj in nss] return data @@ -638,13 +640,13 @@ def _entity_to_json(cls, obj: model.Entity) -> Dict[str, object]: """ data = cls._abstract_classes_to_json(obj) if not cls.stripped and obj.statement: - data['statements'] = list(obj.statement) + data["statements"] = list(obj.statement) if obj.entity_type is not None: - data['entityType'] = _generic.ENTITY_TYPES[obj.entity_type] + data["entityType"] = _generic.ENTITY_TYPES[obj.entity_type] if obj.global_asset_id is not None: - data['globalAssetId'] = obj.global_asset_id + data["globalAssetId"] = obj.global_asset_id if obj.specific_asset_id is not None: - data['specificAssetIds'] = list(obj.specific_asset_id) + data["specificAssetIds"] = list(obj.specific_asset_id) return data @classmethod @@ -666,19 +668,19 @@ def _basic_event_element_to_json(cls, obj: model.BasicEventElement) -> Dict[str, :return: dict with the serialized attributes of this object """ data = cls._abstract_classes_to_json(obj) - data['observed'] = obj.observed - data['direction'] = _generic.DIRECTION[obj.direction] - data['state'] = _generic.STATE_OF_EVENT[obj.state] + data["observed"] = obj.observed + data["direction"] = _generic.DIRECTION[obj.direction] + data["state"] = _generic.STATE_OF_EVENT[obj.state] if obj.message_topic is not None: - data['messageTopic'] = obj.message_topic + data["messageTopic"] = obj.message_topic if obj.message_broker is not None: - data['messageBroker'] = cls._reference_to_json(obj.message_broker) + data["messageBroker"] = cls._reference_to_json(obj.message_broker) if obj.last_update is not None: - data['lastUpdate'] = model.datatypes.xsd_repr(obj.last_update) + data["lastUpdate"] = model.datatypes.xsd_repr(obj.last_update) if obj.min_interval is not None: - data['minInterval'] = model.datatypes.xsd_repr(obj.min_interval) + data["minInterval"] = model.datatypes.xsd_repr(obj.min_interval) if obj.max_interval is not None: - data['maxInterval'] = model.datatypes.xsd_repr(obj.max_interval) + data["maxInterval"] = model.datatypes.xsd_repr(obj.max_interval) return data @@ -687,6 +689,7 @@ class StrippedAASToJsonEncoder(AASToJsonEncoder): AASToJsonEncoder for stripped objects. Used in the HTTP API. See https://git.rwth-aachen.de/acplt/pyi40aas/-/issues/91 """ + stripped = True @@ -704,9 +707,9 @@ def _select_encoder(stripped: bool, encoder: Optional[Type[AASToJsonEncoder]] = return AASToJsonEncoder if not stripped else StrippedAASToJsonEncoder -def _create_dict(data: model.AbstractObjectStore, - keys_to_types: Iterable[Tuple[str, Type]] = JSON_AAS_TOP_LEVEL_KEYS_TO_TYPES) \ - -> Dict[str, List[model.Identifiable]]: +def _create_dict( + data: model.AbstractObjectStore, keys_to_types: Iterable[Tuple[str, Type]] = JSON_AAS_TOP_LEVEL_KEYS_TO_TYPES +) -> Dict[str, List[model.Identifiable]]: """ Categorizes objects from an AbstractObjectStore into a dictionary based on their types. @@ -734,8 +737,9 @@ def _create_dict(data: model.AbstractObjectStore, return objects -def object_store_to_json(data: model.AbstractObjectStore, stripped: bool = False, - encoder: Optional[Type[AASToJsonEncoder]] = None, **kwargs) -> str: +def object_store_to_json( + data: model.AbstractObjectStore, stripped: bool = False, encoder: Optional[Type[AASToJsonEncoder]] = None, **kwargs +) -> str: """ Create a json serialization of a set of AAS objects according to 'Details of the Asset Administration Shell', chapter 5.5 @@ -757,12 +761,18 @@ class _DetachingTextIOWrapper(io.TextIOWrapper): """ Like :class:`io.TextIOWrapper`, but detaches on context exit instead of closing the wrapped buffer. """ + def __exit__(self, exc_type, exc_val, exc_tb): self.detach() -def write_aas_json_file(file: _generic.PathOrIO, data: model.AbstractObjectStore, stripped: bool = False, - encoder: Optional[Type[AASToJsonEncoder]] = None, **kwargs) -> None: +def write_aas_json_file( + file: _generic.PathOrIO, + data: model.AbstractObjectStore, + stripped: bool = False, + encoder: Optional[Type[AASToJsonEncoder]] = None, + **kwargs, +) -> None: """ Write a set of AAS objects to an Asset Administration Shell JSON file according to 'Details of the Asset Administration Shell', chapter 5.5 diff --git a/sdk/basyx/aas/adapter/xml/__init__.py b/sdk/basyx/aas/adapter/xml/__init__.py index aa08288a0..15ce534bc 100644 --- a/sdk/basyx/aas/adapter/xml/__init__.py +++ b/sdk/basyx/aas/adapter/xml/__init__.py @@ -10,7 +10,19 @@ :class:`ObjectStore ` from a given xml document. """ -from .xml_serialization import object_store_to_xml_element, write_aas_xml_file, object_to_xml_element, \ - write_aas_xml_element -from .xml_deserialization import AASFromXmlDecoder, StrictAASFromXmlDecoder, StrippedAASFromXmlDecoder, \ - StrictStrippedAASFromXmlDecoder, XMLConstructables, read_aas_xml_file, read_aas_xml_file_into, read_aas_xml_element +from .xml_serialization import ( + object_store_to_xml_element, + write_aas_xml_file, + object_to_xml_element, + write_aas_xml_element, +) +from .xml_deserialization import ( + AASFromXmlDecoder, + StrictAASFromXmlDecoder, + StrippedAASFromXmlDecoder, + StrictStrippedAASFromXmlDecoder, + XMLConstructables, + read_aas_xml_file, + read_aas_xml_file_into, + read_aas_xml_element, +) diff --git a/sdk/basyx/aas/adapter/xml/xml_deserialization.py b/sdk/basyx/aas/adapter/xml/xml_deserialization.py index 6bfb67bce..a4f409754 100644 --- a/sdk/basyx/aas/adapter/xml/xml_deserialization.py +++ b/sdk/basyx/aas/adapter/xml/xml_deserialization.py @@ -48,9 +48,22 @@ import enum from typing import Any, Callable, Dict, Iterable, Optional, Set, Tuple, Type, TypeVar -from .._generic import XML_NS_MAP, XML_NS_AAS, MODELLING_KIND_INVERSE, ASSET_KIND_INVERSE, KEY_TYPES_INVERSE, \ - ENTITY_TYPES_INVERSE, IEC61360_DATA_TYPES_INVERSE, IEC61360_LEVEL_TYPES_INVERSE, KEY_TYPES_CLASSES_INVERSE, \ - REFERENCE_TYPES_INVERSE, DIRECTION_INVERSE, STATE_OF_EVENT_INVERSE, QUALIFIER_KIND_INVERSE, PathOrIO +from .._generic import ( + XML_NS_MAP, + XML_NS_AAS, + MODELLING_KIND_INVERSE, + ASSET_KIND_INVERSE, + KEY_TYPES_INVERSE, + ENTITY_TYPES_INVERSE, + IEC61360_DATA_TYPES_INVERSE, + IEC61360_LEVEL_TYPES_INVERSE, + KEY_TYPES_CLASSES_INVERSE, + REFERENCE_TYPES_INVERSE, + DIRECTION_INVERSE, + STATE_OF_EVENT_INVERSE, + QUALIFIER_KIND_INVERSE, + PathOrIO, +) NS_AAS = XML_NS_AAS REQUIRED_NAMESPACES: Set[str] = {XML_NS_MAP["aas"]} @@ -140,8 +153,9 @@ def _get_child_mandatory(parent: etree._Element, child_tag: str) -> etree._Eleme """ child = parent.find(child_tag) if child is None: - raise KeyError(_element_pretty_identifier(parent) - + f" has no child {_tag_replace_namespace(child_tag, parent.nsmap)}!") + raise KeyError( + _element_pretty_identifier(parent) + f" has no child {_tag_replace_namespace(child_tag, parent.nsmap)}!" + ) return child @@ -159,8 +173,10 @@ def _get_all_children_expect_tag(parent: etree._Element, expected_tag: str, fail """ for child in parent: if child.tag != expected_tag: - error_message = f"{_element_pretty_identifier(child)}, child of {_element_pretty_identifier(parent)}, " \ - f"doesn't match the expected tag {_tag_replace_namespace(expected_tag, child.nsmap)}!" + error_message = ( + f"{_element_pretty_identifier(child)}, child of {_element_pretty_identifier(parent)}, " + f"doesn't match the expected tag {_tag_replace_namespace(expected_tag, child.nsmap)}!" + ) if not failsafe: raise KeyError(error_message) logger.warning(error_message) @@ -198,8 +214,9 @@ def _get_attrib_mandatory_mapped(element: etree._Element, attrib: str, dct: Dict """ attrib_value = _get_attrib_mandatory(element, attrib) if attrib_value not in dct: - raise ValueError(f"Attribute {attrib} of {_element_pretty_identifier(element)} " - f"has invalid value: {attrib_value}") + raise ValueError( + f"Attribute {attrib} of {_element_pretty_identifier(element)} has invalid value: {attrib_value}" + ) return dct[attrib_value] @@ -281,8 +298,9 @@ def _get_text_mandatory_mapped(element: etree._Element, dct: Dict[str, T]) -> T: return dct[text] -def _failsafe_construct(element: Optional[etree._Element], constructor: Callable[..., T], failsafe: bool, - **kwargs: Any) -> Optional[T]: +def _failsafe_construct( + element: Optional[etree._Element], constructor: Callable[..., T], failsafe: bool, **kwargs: Any +) -> Optional[T]: """ A wrapper function that is used to handle exceptions raised in constructor functions. @@ -330,13 +348,16 @@ def _failsafe_construct_mandatory(element: etree._Element, constructor: Callable """ constructed = _failsafe_construct(element, constructor, False, **kwargs) if constructed is None: - raise AssertionError("The result of a non-failsafe _failsafe_construct() call was None! " - "This is a bug in the Eclipse BaSyx Python SDK XML deserialization, please report it!") + raise AssertionError( + "The result of a non-failsafe _failsafe_construct() call was None! " + "This is a bug in the Eclipse BaSyx Python SDK XML deserialization, please report it!" + ) return constructed -def _failsafe_construct_multiple(elements: Iterable[etree._Element], constructor: Callable[..., T], failsafe: bool, - **kwargs: Any) -> Iterable[T]: +def _failsafe_construct_multiple( + elements: Iterable[etree._Element], constructor: Callable[..., T], failsafe: bool, **kwargs: Any +) -> Iterable[T]: """ A generator function that applies _failsafe_construct() to multiple elements. @@ -354,8 +375,9 @@ def _failsafe_construct_multiple(elements: Iterable[etree._Element], constructor yield parsed -def _child_construct_mandatory(parent: etree._Element, child_tag: str, constructor: Callable[..., T], **kwargs: Any) \ - -> T: +def _child_construct_mandatory( + parent: etree._Element, child_tag: str, constructor: Callable[..., T], **kwargs: Any +) -> T: """ Shorthand for _failsafe_construct_mandatory() in combination with _get_child_mandatory(). @@ -368,8 +390,9 @@ def _child_construct_mandatory(parent: etree._Element, child_tag: str, construct return _failsafe_construct_mandatory(_get_child_mandatory(parent, child_tag), constructor, **kwargs) -def _child_construct_multiple(parent: etree._Element, expected_tag: str, constructor: Callable[..., T], - failsafe: bool, **kwargs: Any) -> Iterable[T]: +def _child_construct_multiple( + parent: etree._Element, expected_tag: str, constructor: Callable[..., T], failsafe: bool, **kwargs: Any +) -> Iterable[T]: """ Shorthand for _failsafe_construct_multiple() in combination with _get_child_multiple(). @@ -381,8 +404,9 @@ def _child_construct_multiple(parent: etree._Element, expected_tag: str, constru If an error occurred while constructing an element and while in failsafe mode, the respective element will be skipped. """ - return _failsafe_construct_multiple(_get_all_children_expect_tag(parent, expected_tag, failsafe), constructor, - failsafe, **kwargs) + return _failsafe_construct_multiple( + _get_all_children_expect_tag(parent, expected_tag, failsafe), constructor, failsafe, **kwargs + ) def _child_text_mandatory(parent: etree._Element, child_tag: str) -> str: @@ -441,6 +465,7 @@ class AASFromXmlDecoder: Most member functions support the ``object_class`` parameter. It was introduced, so they can be overwritten in subclasses, which allows constructing instances of subtypes. """ + failsafe = True stripped = False @@ -459,31 +484,35 @@ def _amend_abstract_attributes(cls, obj: object, element: etree._Element) -> Non if id_short is not None: obj.id_short = id_short category = _get_text_or_none(element.find(NS_AAS + "category")) - display_name = _failsafe_construct(element.find(NS_AAS + "displayName"), - cls.construct_multi_language_name_type, cls.failsafe) + display_name = _failsafe_construct( + element.find(NS_AAS + "displayName"), cls.construct_multi_language_name_type, cls.failsafe + ) if display_name is not None: obj.display_name = display_name if category is not None: obj.category = category - description = _failsafe_construct(element.find(NS_AAS + "description"), - cls.construct_multi_language_text_type, cls.failsafe) + description = _failsafe_construct( + element.find(NS_AAS + "description"), cls.construct_multi_language_text_type, cls.failsafe + ) if description is not None: obj.description = description if isinstance(obj, model.Identifiable): - administration = _failsafe_construct(element.find(NS_AAS + "administration"), - cls.construct_administrative_information, cls.failsafe) + administration = _failsafe_construct( + element.find(NS_AAS + "administration"), cls.construct_administrative_information, cls.failsafe + ) if administration: obj.administration = administration if isinstance(obj, model.HasSemantics): - semantic_id = _failsafe_construct(element.find(NS_AAS + "semanticId"), cls.construct_reference, - cls.failsafe) + semantic_id = _failsafe_construct( + element.find(NS_AAS + "semanticId"), cls.construct_reference, cls.failsafe + ) if semantic_id is not None: obj.semantic_id = semantic_id supplemental_semantic_ids = element.find(NS_AAS + "supplementalSemanticIds") if supplemental_semantic_ids is not None: - for supplemental_semantic_id in _child_construct_multiple(supplemental_semantic_ids, - NS_AAS + "reference", cls.construct_reference, - cls.failsafe): + for supplemental_semantic_id in _child_construct_multiple( + supplemental_semantic_ids, NS_AAS + "reference", cls.construct_reference, cls.failsafe + ): obj.supplemental_semantic_id.append(supplemental_semantic_id) if isinstance(obj, model.Qualifiable) and not cls.stripped: qualifiers_elem = element.find(NS_AAS + "qualifiers") @@ -493,19 +522,22 @@ def _amend_abstract_attributes(cls, obj: object, element: etree._Element) -> Non if isinstance(obj, model.HasDataSpecification) and not cls.stripped: embedded_data_specifications_elem = element.find(NS_AAS + "embeddedDataSpecifications") if embedded_data_specifications_elem is not None: - for eds in _failsafe_construct_multiple(embedded_data_specifications_elem, - cls.construct_embedded_data_specification, cls.failsafe): + for eds in _failsafe_construct_multiple( + embedded_data_specifications_elem, cls.construct_embedded_data_specification, cls.failsafe + ): obj.embedded_data_specifications.append(eds) if isinstance(obj, model.HasExtension) and not cls.stripped: extension_elem = element.find(NS_AAS + "extensions") if extension_elem is not None: - for extension in _child_construct_multiple(extension_elem, NS_AAS + "extension", - cls.construct_extension, cls.failsafe): + for extension in _child_construct_multiple( + extension_elem, NS_AAS + "extension", cls.construct_extension, cls.failsafe + ): obj.extension.add(extension) @classmethod - def _construct_relationship_element_internal(cls, element: etree._Element, object_class: Type[RE], **_kwargs: Any) \ - -> RE: + def _construct_relationship_element_internal( + cls, element: etree._Element, object_class: Type[RE], **_kwargs: Any + ) -> RE: """ Helper function used by construct_relationship_element() and construct_annotated_relationship_element() to reduce duplicate code @@ -513,14 +545,15 @@ def _construct_relationship_element_internal(cls, element: etree._Element, objec relationship_element = object_class( None, _failsafe_construct(element.find(NS_AAS + "first"), cls.construct_reference, cls.failsafe), - _failsafe_construct(element.find(NS_AAS + "second"), cls.construct_reference, cls.failsafe) + _failsafe_construct(element.find(NS_AAS + "second"), cls.construct_reference, cls.failsafe), ) cls._amend_abstract_attributes(relationship_element, element) return relationship_element @classmethod - def _construct_key_tuple(cls, element: etree._Element, namespace: str = NS_AAS, **_kwargs: Any) \ - -> Tuple[model.Key, ...]: + def _construct_key_tuple( + cls, element: etree._Element, namespace: str = NS_AAS, **_kwargs: Any + ) -> Tuple[model.Key, ...]: """ Helper function used by construct_reference() and construct_aas_reference() to reduce duplicate code """ @@ -528,24 +561,27 @@ def _construct_key_tuple(cls, element: etree._Element, namespace: str = NS_AAS, return tuple(_child_construct_multiple(keys, namespace + "key", cls.construct_key, cls.failsafe)) @classmethod - def _construct_submodel_reference(cls, element: etree._Element, **kwargs: Any) \ - -> model.ModelReference[model.Submodel]: + def _construct_submodel_reference( + cls, element: etree._Element, **kwargs: Any + ) -> model.ModelReference[model.Submodel]: """ Helper function. Doesn't support the object_class parameter. Overwrite construct_aas_reference instead. """ return cls.construct_model_reference_expect_type(element, model.Submodel, **kwargs) @classmethod - def _construct_asset_administration_shell_reference(cls, element: etree._Element, **kwargs: Any) \ - -> model.ModelReference[model.AssetAdministrationShell]: + def _construct_asset_administration_shell_reference( + cls, element: etree._Element, **kwargs: Any + ) -> model.ModelReference[model.AssetAdministrationShell]: """ Helper function. Doesn't support the object_class parameter. Overwrite construct_aas_reference instead. """ return cls.construct_model_reference_expect_type(element, model.AssetAdministrationShell, **kwargs) @classmethod - def _construct_referable_reference(cls, element: etree._Element, **kwargs: Any) \ - -> model.ModelReference[model.Referable]: + def _construct_referable_reference( + cls, element: etree._Element, **kwargs: Any + ) -> model.ModelReference[model.Referable]: """ Helper function. Doesn't support the object_class parameter. Overwrite construct_aas_reference instead. """ @@ -563,42 +599,47 @@ def _construct_operation_variable(cls, element: etree._Element, **kwargs: Any) - if len(value) == 0: raise KeyError(f"{_element_pretty_identifier(value)} has no submodel element!") if len(value) > 1: - logger.warning(f"{_element_pretty_identifier(value)} has more than one submodel element, " - "using the first one...") + logger.warning( + f"{_element_pretty_identifier(value)} has more than one submodel element, using the first one..." + ) return cls.construct_submodel_element(value[0], **kwargs) @classmethod - def construct_key(cls, element: etree._Element, object_class=model.Key, **_kwargs: Any) \ - -> model.Key: + def construct_key(cls, element: etree._Element, object_class=model.Key, **_kwargs: Any) -> model.Key: return object_class( _child_text_mandatory_mapped(element, NS_AAS + "type", KEY_TYPES_INVERSE), - _child_text_mandatory(element, NS_AAS + "value") + _child_text_mandatory(element, NS_AAS + "value"), ) @classmethod def construct_reference(cls, element: etree._Element, namespace: str = NS_AAS, **kwargs: Any) -> model.Reference: - reference_type: Type[model.Reference] = _child_text_mandatory_mapped(element, NS_AAS + "type", - REFERENCE_TYPES_INVERSE) + reference_type: Type[model.Reference] = _child_text_mandatory_mapped( + element, NS_AAS + "type", REFERENCE_TYPES_INVERSE + ) references: Dict[Type[model.Reference], Callable[..., model.Reference]] = { model.ExternalReference: cls.construct_external_reference, - model.ModelReference: cls.construct_model_reference + model.ModelReference: cls.construct_model_reference, } if reference_type not in references: raise KeyError(_element_pretty_identifier(element) + f" is of unsupported Reference type {reference_type}!") return references[reference_type](element, namespace=namespace, **kwargs) @classmethod - def construct_external_reference(cls, element: etree._Element, namespace: str = NS_AAS, - object_class=model.ExternalReference, **_kwargs: Any) \ - -> model.ExternalReference: + def construct_external_reference( + cls, element: etree._Element, namespace: str = NS_AAS, object_class=model.ExternalReference, **_kwargs: Any + ) -> model.ExternalReference: _expect_reference_type(element, model.ExternalReference) - return object_class(cls._construct_key_tuple(element, namespace=namespace), - _failsafe_construct(element.find(NS_AAS + "referredSemanticId"), cls.construct_reference, - cls.failsafe, namespace=namespace)) + return object_class( + cls._construct_key_tuple(element, namespace=namespace), + _failsafe_construct( + element.find(NS_AAS + "referredSemanticId"), cls.construct_reference, cls.failsafe, namespace=namespace + ), + ) @classmethod - def construct_model_reference(cls, element: etree._Element, object_class=model.ModelReference, **_kwargs: Any) \ - -> model.ModelReference: + def construct_model_reference( + cls, element: etree._Element, object_class=model.ModelReference, **_kwargs: Any + ) -> model.ModelReference: """ This constructor for ModelReference determines the type of the ModelReference by its keys. If no keys are present, it will default to the type Referable. This behaviour is wanted in read_aas_xml_element(). @@ -610,13 +651,16 @@ def construct_model_reference(cls, element: etree._Element, object_class=model.M type_: Type[model.Referable] = model.Referable # type: ignore if len(keys) > 0: type_ = KEY_TYPES_CLASSES_INVERSE.get(keys[-1].type, model.Referable) # type: ignore - return object_class(keys, type_, _failsafe_construct(element.find(NS_AAS + "referredSemanticId"), - cls.construct_reference, cls.failsafe)) + return object_class( + keys, + type_, + _failsafe_construct(element.find(NS_AAS + "referredSemanticId"), cls.construct_reference, cls.failsafe), + ) @classmethod - def construct_model_reference_expect_type(cls, element: etree._Element, type_: Type[model.base._RT], - object_class=model.ModelReference, **_kwargs: Any) \ - -> model.ModelReference[model.base._RT]: + def construct_model_reference_expect_type( + cls, element: etree._Element, type_: Type[model.base._RT], object_class=model.ModelReference, **_kwargs: Any + ) -> model.ModelReference[model.base._RT]: """ This constructor for ModelReference allows passing an expected type, which is checked against the type of the last key of the reference. This constructor function is used by other constructor functions, since all expect a @@ -625,18 +669,26 @@ def construct_model_reference_expect_type(cls, element: etree._Element, type_: T _expect_reference_type(element, model.ModelReference) keys = cls._construct_key_tuple(element) if keys and not issubclass(KEY_TYPES_CLASSES_INVERSE.get(keys[-1].type, type(None)), type_): - logger.warning("type %s of last key of reference to %s does not match reference type %s", - keys[-1].type.name, " / ".join(str(k) for k in keys), type_.__name__) - return object_class(keys, type_, _failsafe_construct(element.find(NS_AAS + "referredSemanticId"), - cls.construct_reference, cls.failsafe)) + logger.warning( + "type %s of last key of reference to %s does not match reference type %s", + keys[-1].type.name, + " / ".join(str(k) for k in keys), + type_.__name__, + ) + return object_class( + keys, + type_, + _failsafe_construct(element.find(NS_AAS + "referredSemanticId"), cls.construct_reference, cls.failsafe), + ) @classmethod - def construct_administrative_information(cls, element: etree._Element, object_class=model.AdministrativeInformation, - **_kwargs: Any) -> model.AdministrativeInformation: + def construct_administrative_information( + cls, element: etree._Element, object_class=model.AdministrativeInformation, **_kwargs: Any + ) -> model.AdministrativeInformation: administrative_information = object_class( revision=_get_text_or_none(element.find(NS_AAS + "revision")), version=_get_text_or_none(element.find(NS_AAS + "version")), - template_id=_get_text_or_none(element.find(NS_AAS + "templateId")) + template_id=_get_text_or_none(element.find(NS_AAS + "templateId")), ) creator = _failsafe_construct(element.find(NS_AAS + "creator"), cls.construct_reference, cls.failsafe) if creator is not None: @@ -645,49 +697,59 @@ def construct_administrative_information(cls, element: etree._Element, object_cl return administrative_information @classmethod - def construct_lang_string_set(cls, element: etree._Element, expected_tag: str, object_class: Type[LSS], - **_kwargs: Any) -> LSS: + def construct_lang_string_set( + cls, element: etree._Element, expected_tag: str, object_class: Type[LSS], **_kwargs: Any + ) -> LSS: collected_lang_strings: Dict[str, str] = {} for lang_string_elem in _get_all_children_expect_tag(element, expected_tag, cls.failsafe): - collected_lang_strings[_child_text_mandatory(lang_string_elem, NS_AAS + "language")] = \ + collected_lang_strings[_child_text_mandatory(lang_string_elem, NS_AAS + "language")] = ( _child_text_mandatory(lang_string_elem, NS_AAS + "text") + ) return object_class(collected_lang_strings) @classmethod - def construct_multi_language_name_type(cls, element: etree._Element, object_class=model.MultiLanguageNameType, - **kwargs: Any) -> model.MultiLanguageNameType: + def construct_multi_language_name_type( + cls, element: etree._Element, object_class=model.MultiLanguageNameType, **kwargs: Any + ) -> model.MultiLanguageNameType: return cls.construct_lang_string_set(element, NS_AAS + "langStringNameType", object_class, **kwargs) @classmethod - def construct_multi_language_text_type(cls, element: etree._Element, object_class=model.MultiLanguageTextType, - **kwargs: Any) -> model.MultiLanguageTextType: + def construct_multi_language_text_type( + cls, element: etree._Element, object_class=model.MultiLanguageTextType, **kwargs: Any + ) -> model.MultiLanguageTextType: return cls.construct_lang_string_set(element, NS_AAS + "langStringTextType", object_class, **kwargs) @classmethod - def construct_definition_type_iec61360(cls, element: etree._Element, object_class=model.DefinitionTypeIEC61360, - **kwargs: Any) -> model.DefinitionTypeIEC61360: - return cls.construct_lang_string_set(element, NS_AAS + "langStringDefinitionTypeIec61360", object_class, - **kwargs) + def construct_definition_type_iec61360( + cls, element: etree._Element, object_class=model.DefinitionTypeIEC61360, **kwargs: Any + ) -> model.DefinitionTypeIEC61360: + return cls.construct_lang_string_set( + element, NS_AAS + "langStringDefinitionTypeIec61360", object_class, **kwargs + ) @classmethod - def construct_preferred_name_type_iec61360(cls, element: etree._Element, - object_class=model.PreferredNameTypeIEC61360, - **kwargs: Any) -> model.PreferredNameTypeIEC61360: - return cls.construct_lang_string_set(element, NS_AAS + "langStringPreferredNameTypeIec61360", object_class, - **kwargs) + def construct_preferred_name_type_iec61360( + cls, element: etree._Element, object_class=model.PreferredNameTypeIEC61360, **kwargs: Any + ) -> model.PreferredNameTypeIEC61360: + return cls.construct_lang_string_set( + element, NS_AAS + "langStringPreferredNameTypeIec61360", object_class, **kwargs + ) @classmethod - def construct_short_name_type_iec61360(cls, element: etree._Element, object_class=model.ShortNameTypeIEC61360, - **kwargs: Any) -> model.ShortNameTypeIEC61360: - return cls.construct_lang_string_set(element, NS_AAS + "langStringShortNameTypeIec61360", object_class, - **kwargs) + def construct_short_name_type_iec61360( + cls, element: etree._Element, object_class=model.ShortNameTypeIEC61360, **kwargs: Any + ) -> model.ShortNameTypeIEC61360: + return cls.construct_lang_string_set( + element, NS_AAS + "langStringShortNameTypeIec61360", object_class, **kwargs + ) @classmethod - def construct_qualifier(cls, element: etree._Element, object_class=model.Qualifier, **_kwargs: Any) \ - -> model.Qualifier: + def construct_qualifier( + cls, element: etree._Element, object_class=model.Qualifier, **_kwargs: Any + ) -> model.Qualifier: qualifier = object_class( _child_text_mandatory(element, NS_AAS + "type"), - _child_text_mandatory_mapped(element, NS_AAS + "valueType", model.datatypes.XSD_TYPE_CLASSES) + _child_text_mandatory_mapped(element, NS_AAS + "valueType", model.datatypes.XSD_TYPE_CLASSES), ) kind = _get_text_mapped_or_none(element.find(NS_AAS + "kind"), QUALIFIER_KIND_INVERSE) if kind is not None: @@ -702,10 +764,10 @@ def construct_qualifier(cls, element: etree._Element, object_class=model.Qualifi return qualifier @classmethod - def construct_extension(cls, element: etree._Element, object_class=model.Extension, **_kwargs: Any) \ - -> model.Extension: - extension = object_class( - _child_text_mandatory(element, NS_AAS + "name")) + def construct_extension( + cls, element: etree._Element, object_class=model.Extension, **_kwargs: Any + ) -> model.Extension: + extension = object_class(_child_text_mandatory(element, NS_AAS + "name")) value_type = _get_text_or_none(element.find(NS_AAS + "valueType")) if value_type is not None: extension.value_type = model.datatypes.XSD_TYPE_CLASSES[value_type] @@ -714,8 +776,9 @@ def construct_extension(cls, element: etree._Element, object_class=model.Extensi extension.value = model.datatypes.from_xsd(value, extension.value_type) refers_to = element.find(NS_AAS + "refersTo") if refers_to is not None: - for ref in _child_construct_multiple(refers_to, NS_AAS + "reference", cls._construct_referable_reference, - cls.failsafe): + for ref in _child_construct_multiple( + refers_to, NS_AAS + "reference", cls._construct_referable_reference, cls.failsafe + ): extension.refers_to.add(ref) cls._amend_abstract_attributes(extension, element) return extension @@ -726,68 +789,76 @@ def construct_submodel_element(cls, element: etree._Element, **kwargs: Any) -> m This function doesn't support the object_class parameter. Overwrite each individual SubmodelElement/DataElement constructor function instead. """ - submodel_elements: Dict[str, Callable[..., model.SubmodelElement]] = {NS_AAS + k: v for k, v in { - "annotatedRelationshipElement": cls.construct_annotated_relationship_element, - "basicEventElement": cls.construct_basic_event_element, - "capability": cls.construct_capability, - "entity": cls.construct_entity, - "operation": cls.construct_operation, - "relationshipElement": cls.construct_relationship_element, - "submodelElementCollection": cls.construct_submodel_element_collection, - "submodelElementList": cls.construct_submodel_element_list - }.items()} + submodel_elements: Dict[str, Callable[..., model.SubmodelElement]] = { + NS_AAS + k: v + for k, v in { + "annotatedRelationshipElement": cls.construct_annotated_relationship_element, + "basicEventElement": cls.construct_basic_event_element, + "capability": cls.construct_capability, + "entity": cls.construct_entity, + "operation": cls.construct_operation, + "relationshipElement": cls.construct_relationship_element, + "submodelElementCollection": cls.construct_submodel_element_collection, + "submodelElementList": cls.construct_submodel_element_list, + }.items() + } if element.tag not in submodel_elements: return cls.construct_data_element(element, abstract_class_name="SubmodelElement", **kwargs) return submodel_elements[element.tag](element, **kwargs) @classmethod - def construct_data_element(cls, element: etree._Element, abstract_class_name: str = "DataElement", **kwargs: Any) \ - -> model.DataElement: + def construct_data_element( + cls, element: etree._Element, abstract_class_name: str = "DataElement", **kwargs: Any + ) -> model.DataElement: """ This function does not support the object_class parameter. Overwrite each individual DataElement constructor function instead. """ - data_elements: Dict[str, Callable[..., model.DataElement]] = {NS_AAS + k: v for k, v in { - "blob": cls.construct_blob, - "file": cls.construct_file, - "multiLanguageProperty": cls.construct_multi_language_property, - "property": cls.construct_property, - "range": cls.construct_range, - "referenceElement": cls.construct_reference_element, - }.items()} + data_elements: Dict[str, Callable[..., model.DataElement]] = { + NS_AAS + k: v + for k, v in { + "blob": cls.construct_blob, + "file": cls.construct_file, + "multiLanguageProperty": cls.construct_multi_language_property, + "property": cls.construct_property, + "range": cls.construct_range, + "referenceElement": cls.construct_reference_element, + }.items() + } if element.tag not in data_elements: raise KeyError(_element_pretty_identifier(element) + f" is not a valid {abstract_class_name}!") return data_elements[element.tag](element, **kwargs) @classmethod - def construct_annotated_relationship_element(cls, element: etree._Element, - object_class=model.AnnotatedRelationshipElement, **_kwargs: Any) \ - -> model.AnnotatedRelationshipElement: + def construct_annotated_relationship_element( + cls, element: etree._Element, object_class=model.AnnotatedRelationshipElement, **_kwargs: Any + ) -> model.AnnotatedRelationshipElement: annotated_relationship_element = cls._construct_relationship_element_internal(element, object_class) if not cls.stripped: annotations = element.find(NS_AAS + "annotations") if annotations is not None: - for data_element in _failsafe_construct_multiple(annotations, cls.construct_data_element, - cls.failsafe): + for data_element in _failsafe_construct_multiple(annotations, cls.construct_data_element, cls.failsafe): annotated_relationship_element.annotation.add(data_element) return annotated_relationship_element @classmethod - def construct_basic_event_element(cls, element: etree._Element, object_class=model.BasicEventElement, - **_kwargs: Any) -> model.BasicEventElement: + def construct_basic_event_element( + cls, element: etree._Element, object_class=model.BasicEventElement, **_kwargs: Any + ) -> model.BasicEventElement: basic_event_element = object_class( None, _child_construct_mandatory(element, NS_AAS + "observed", cls._construct_referable_reference), _child_text_mandatory_mapped(element, NS_AAS + "direction", DIRECTION_INVERSE), - _child_text_mandatory_mapped(element, NS_AAS + "state", STATE_OF_EVENT_INVERSE) + _child_text_mandatory_mapped(element, NS_AAS + "state", STATE_OF_EVENT_INVERSE), ) message_topic = _get_text_or_none(element.find(NS_AAS + "messageTopic")) if message_topic is not None: basic_event_element.message_topic = message_topic message_broker = element.find(NS_AAS + "messageBroker") if message_broker is not None: - basic_event_element.message_broker = _failsafe_construct(message_broker, cls.construct_reference, - cls.failsafe) + basic_event_element.message_broker = _failsafe_construct( + message_broker, cls.construct_reference, cls.failsafe + ) last_update = _get_text_or_none(element.find(NS_AAS + "lastUpdate")) if last_update is not None: basic_event_element.last_update = model.datatypes.from_xsd(last_update, model.datatypes.DateTime) @@ -802,10 +873,7 @@ def construct_basic_event_element(cls, element: etree._Element, object_class=mod @classmethod def construct_blob(cls, element: etree._Element, object_class=model.Blob, **_kwargs: Any) -> model.Blob: - blob = object_class( - None, - _get_text_or_none(element.find(NS_AAS + "contentType")) - ) + blob = object_class(None, _get_text_or_none(element.find(NS_AAS + "contentType"))) value = _get_text_or_none(element.find(NS_AAS + "value")) if value is not None: blob.value = base64.b64decode(value) @@ -813,8 +881,9 @@ def construct_blob(cls, element: etree._Element, object_class=model.Blob, **_kwa return blob @classmethod - def construct_capability(cls, element: etree._Element, object_class=model.Capability, **_kwargs: Any) \ - -> model.Capability: + def construct_capability( + cls, element: etree._Element, object_class=model.Capability, **_kwargs: Any + ) -> model.Capability: capability = object_class(None) cls._amend_abstract_attributes(capability, element) return capability @@ -824,8 +893,9 @@ def construct_entity(cls, element: etree._Element, object_class=model.Entity, ** specific_asset_id = set() specific_asset_ids = element.find(NS_AAS + "specificAssetIds") if specific_asset_ids is not None: - for id in _child_construct_multiple(specific_asset_ids, NS_AAS + "specificAssetId", - cls.construct_specific_asset_id, cls.failsafe): + for id in _child_construct_multiple( + specific_asset_ids, NS_AAS + "specificAssetId", cls.construct_specific_asset_id, cls.failsafe + ): specific_asset_id.add(id) entity_type_text = _get_text_or_none(element.find(NS_AAS + "entityType")) if entity_type_text is not None: @@ -836,23 +906,22 @@ def construct_entity(cls, element: etree._Element, object_class=model.Entity, ** id_short=None, entity_type=entity_type, global_asset_id=_get_text_or_none(element.find(NS_AAS + "globalAssetId")), - specific_asset_id=specific_asset_id) + specific_asset_id=specific_asset_id, + ) if not cls.stripped: statements = element.find(NS_AAS + "statements") if statements is not None: - for submodel_element in _failsafe_construct_multiple(statements, cls.construct_submodel_element, - cls.failsafe): + for submodel_element in _failsafe_construct_multiple( + statements, cls.construct_submodel_element, cls.failsafe + ): entity.statement.add(submodel_element) cls._amend_abstract_attributes(entity, element) return entity @classmethod def construct_file(cls, element: etree._Element, object_class=model.File, **_kwargs: Any) -> model.File: - file = object_class( - None, - _get_text_or_none(element.find(NS_AAS + "contentType")) - ) + file = object_class(None, _get_text_or_none(element.find(NS_AAS + "contentType"))) value = _get_text_or_none(element.find(NS_AAS + "value")) if value is not None: file.value = value @@ -861,9 +930,7 @@ def construct_file(cls, element: etree._Element, object_class=model.File, **_kwa @classmethod def construct_resource(cls, element: etree._Element, object_class=model.Resource, **_kwargs: Any) -> model.Resource: - resource = object_class( - _child_text_mandatory(element, NS_AAS + "path") - ) + resource = object_class(_child_text_mandatory(element, NS_AAS + "path")) content_type = _get_text_or_none(element.find(NS_AAS + "contentType")) if content_type is not None: resource.content_type = content_type @@ -871,11 +938,13 @@ def construct_resource(cls, element: etree._Element, object_class=model.Resource return resource @classmethod - def construct_multi_language_property(cls, element: etree._Element, object_class=model.MultiLanguageProperty, - **_kwargs: Any) -> model.MultiLanguageProperty: + def construct_multi_language_property( + cls, element: etree._Element, object_class=model.MultiLanguageProperty, **_kwargs: Any + ) -> model.MultiLanguageProperty: multi_language_property = object_class(None) - value = _failsafe_construct(element.find(NS_AAS + "value"), cls.construct_multi_language_text_type, - cls.failsafe) + value = _failsafe_construct( + element.find(NS_AAS + "value"), cls.construct_multi_language_text_type, cls.failsafe + ) if value is not None: multi_language_property.value = value value_id = _failsafe_construct(element.find(NS_AAS + "valueId"), cls.construct_reference, cls.failsafe) @@ -885,16 +954,20 @@ def construct_multi_language_property(cls, element: etree._Element, object_class return multi_language_property @classmethod - def construct_operation(cls, element: etree._Element, object_class=model.Operation, **_kwargs: Any) \ - -> model.Operation: + def construct_operation( + cls, element: etree._Element, object_class=model.Operation, **_kwargs: Any + ) -> model.Operation: operation = object_class(None) - for tag, target in ((NS_AAS + "inputVariables", operation.input_variable), - (NS_AAS + "outputVariables", operation.output_variable), - (NS_AAS + "inoutputVariables", operation.in_output_variable)): + for tag, target in ( + (NS_AAS + "inputVariables", operation.input_variable), + (NS_AAS + "outputVariables", operation.output_variable), + (NS_AAS + "inoutputVariables", operation.in_output_variable), + ): variables = element.find(tag) if variables is not None: - for var in _child_construct_multiple(variables, NS_AAS + "operationVariable", - cls._construct_operation_variable, cls.failsafe): + for var in _child_construct_multiple( + variables, NS_AAS + "operationVariable", cls._construct_operation_variable, cls.failsafe + ): target.add(var) cls._amend_abstract_attributes(operation, element) return operation @@ -903,7 +976,7 @@ def construct_operation(cls, element: etree._Element, object_class=model.Operati def construct_property(cls, element: etree._Element, object_class=model.Property, **_kwargs: Any) -> model.Property: property_ = object_class( None, - value_type=_child_text_mandatory_mapped(element, NS_AAS + "valueType", model.datatypes.XSD_TYPE_CLASSES) + value_type=_child_text_mandatory_mapped(element, NS_AAS + "valueType", model.datatypes.XSD_TYPE_CLASSES), ) value = _get_text_or_empty_string_or_none(element.find(NS_AAS + "value")) if value is not None: @@ -918,7 +991,7 @@ def construct_property(cls, element: etree._Element, object_class=model.Property def construct_range(cls, element: etree._Element, object_class=model.Range, **_kwargs: Any) -> model.Range: range_ = object_class( None, - value_type=_child_text_mandatory_mapped(element, NS_AAS + "valueType", model.datatypes.XSD_TYPE_CLASSES) + value_type=_child_text_mandatory_mapped(element, NS_AAS + "valueType", model.datatypes.XSD_TYPE_CLASSES), ) max_ = _get_text_or_empty_string_or_none(element.find(NS_AAS + "max")) if max_ is not None: @@ -930,8 +1003,9 @@ def construct_range(cls, element: etree._Element, object_class=model.Range, **_k return range_ @classmethod - def construct_reference_element(cls, element: etree._Element, object_class=model.ReferenceElement, **_kwargs: Any) \ - -> model.ReferenceElement: + def construct_reference_element( + cls, element: etree._Element, object_class=model.ReferenceElement, **_kwargs: Any + ) -> model.ReferenceElement: reference_element = object_class(None) value = _failsafe_construct(element.find(NS_AAS + "value"), cls.construct_reference, cls.failsafe) if value is not None: @@ -940,42 +1014,49 @@ def construct_reference_element(cls, element: etree._Element, object_class=model return reference_element @classmethod - def construct_relationship_element(cls, element: etree._Element, object_class=model.RelationshipElement, - **_kwargs: Any) -> model.RelationshipElement: + def construct_relationship_element( + cls, element: etree._Element, object_class=model.RelationshipElement, **_kwargs: Any + ) -> model.RelationshipElement: return cls._construct_relationship_element_internal(element, object_class=object_class, **_kwargs) @classmethod - def construct_submodel_element_collection(cls, element: etree._Element, - object_class=model.SubmodelElementCollection, - **_kwargs: Any) -> model.SubmodelElementCollection: + def construct_submodel_element_collection( + cls, element: etree._Element, object_class=model.SubmodelElementCollection, **_kwargs: Any + ) -> model.SubmodelElementCollection: collection = object_class(None) if not cls.stripped: value = element.find(NS_AAS + "value") if value is not None: - for submodel_element in _failsafe_construct_multiple(value, cls.construct_submodel_element, - cls.failsafe): + for submodel_element in _failsafe_construct_multiple( + value, cls.construct_submodel_element, cls.failsafe + ): collection.value.add(submodel_element) cls._amend_abstract_attributes(collection, element) return collection @classmethod - def construct_submodel_element_list(cls, element: etree._Element, object_class=model.SubmodelElementList, - **_kwargs: Any) -> model.SubmodelElementList: + def construct_submodel_element_list( + cls, element: etree._Element, object_class=model.SubmodelElementList, **_kwargs: Any + ) -> model.SubmodelElementList: type_value_list_element = KEY_TYPES_CLASSES_INVERSE[ - _child_text_mandatory_mapped(element, NS_AAS + "typeValueListElement", KEY_TYPES_INVERSE)] + _child_text_mandatory_mapped(element, NS_AAS + "typeValueListElement", KEY_TYPES_INVERSE) + ] if not issubclass(type_value_list_element, model.SubmodelElement): - raise ValueError("Expected a SubmodelElementList with a typeValueListElement that is a subclass of" - f"{model.SubmodelElement}, got {type_value_list_element}!") + raise ValueError( + "Expected a SubmodelElementList with a typeValueListElement that is a subclass of" + f"{model.SubmodelElement}, got {type_value_list_element}!" + ) order_relevant = element.find(NS_AAS + "orderRelevant") list_ = object_class( None, type_value_list_element, - semantic_id_list_element=_failsafe_construct(element.find(NS_AAS + "semanticIdListElement"), - cls.construct_reference, cls.failsafe), - value_type_list_element=_get_text_mapped_or_none(element.find(NS_AAS + "valueTypeListElement"), - model.datatypes.XSD_TYPE_CLASSES), - order_relevant=_str_to_bool(_get_text_mandatory(order_relevant)) - if order_relevant is not None else True + semantic_id_list_element=_failsafe_construct( + element.find(NS_AAS + "semanticIdListElement"), cls.construct_reference, cls.failsafe + ), + value_type_list_element=_get_text_mapped_or_none( + element.find(NS_AAS + "valueTypeListElement"), model.datatypes.XSD_TYPE_CLASSES + ), + order_relevant=_str_to_bool(_get_text_mandatory(order_relevant)) if order_relevant is not None else True, ) if not cls.stripped: value = element.find(NS_AAS + "value") @@ -985,46 +1066,54 @@ def construct_submodel_element_list(cls, element: etree._Element, object_class=m return list_ @classmethod - def construct_asset_administration_shell(cls, element: etree._Element, object_class=model.AssetAdministrationShell, - **_kwargs: Any) -> model.AssetAdministrationShell: + def construct_asset_administration_shell( + cls, element: etree._Element, object_class=model.AssetAdministrationShell, **_kwargs: Any + ) -> model.AssetAdministrationShell: aas = object_class( id_=_child_text_mandatory(element, NS_AAS + "id"), - asset_information=_child_construct_mandatory(element, NS_AAS + "assetInformation", - cls.construct_asset_information) + asset_information=_child_construct_mandatory( + element, NS_AAS + "assetInformation", cls.construct_asset_information + ), ) if not cls.stripped: submodels = element.find(NS_AAS + "submodels") if submodels is not None: - for ref in _child_construct_multiple(submodels, NS_AAS + "reference", - cls._construct_submodel_reference, cls.failsafe): + for ref in _child_construct_multiple( + submodels, NS_AAS + "reference", cls._construct_submodel_reference, cls.failsafe + ): aas.submodel.add(ref) - derived_from = _failsafe_construct(element.find(NS_AAS + "derivedFrom"), - cls._construct_asset_administration_shell_reference, cls.failsafe) + derived_from = _failsafe_construct( + element.find(NS_AAS + "derivedFrom"), cls._construct_asset_administration_shell_reference, cls.failsafe + ) if derived_from is not None: aas.derived_from = derived_from cls._amend_abstract_attributes(aas, element) return aas @classmethod - def construct_specific_asset_id(cls, element: etree._Element, object_class=model.SpecificAssetId, - **_kwargs: Any) -> model.SpecificAssetId: + def construct_specific_asset_id( + cls, element: etree._Element, object_class=model.SpecificAssetId, **_kwargs: Any + ) -> model.SpecificAssetId: # semantic_id can't be applied by _amend_abstract_attributes because specificAssetId is immutable return object_class( name=_get_text_or_none(element.find(NS_AAS + "name")), value=_get_text_or_none(element.find(NS_AAS + "value")), - external_subject_id=_failsafe_construct(element.find(NS_AAS + "externalSubjectId"), - cls.construct_external_reference, cls.failsafe), - semantic_id=_failsafe_construct(element.find(NS_AAS + "semanticId"), cls.construct_reference, cls.failsafe) + external_subject_id=_failsafe_construct( + element.find(NS_AAS + "externalSubjectId"), cls.construct_external_reference, cls.failsafe + ), + semantic_id=_failsafe_construct(element.find(NS_AAS + "semanticId"), cls.construct_reference, cls.failsafe), ) @classmethod - def construct_asset_information(cls, element: etree._Element, object_class=model.AssetInformation, **_kwargs: Any) \ - -> model.AssetInformation: + def construct_asset_information( + cls, element: etree._Element, object_class=model.AssetInformation, **_kwargs: Any + ) -> model.AssetInformation: specific_asset_id = set() specific_asset_ids = element.find(NS_AAS + "specificAssetIds") if specific_asset_ids is not None: - for id in _child_construct_multiple(specific_asset_ids, NS_AAS + "specificAssetId", - cls.construct_specific_asset_id, cls.failsafe): + for id in _child_construct_multiple( + specific_asset_ids, NS_AAS + "specificAssetId", cls.construct_specific_asset_id, cls.failsafe + ): specific_asset_id.add(id) asset_information = object_class( @@ -1036,8 +1125,7 @@ def construct_asset_information(cls, element: etree._Element, object_class=model asset_type = _get_text_or_none(element.find(NS_AAS + "assetType")) if asset_type is not None: asset_information.asset_type = asset_type - thumbnail = _failsafe_construct(element.find(NS_AAS + "defaultThumbnail"), - cls.construct_resource, cls.failsafe) + thumbnail = _failsafe_construct(element.find(NS_AAS + "defaultThumbnail"), cls.construct_resource, cls.failsafe) if thumbnail is not None: asset_information.default_thumbnail = thumbnail @@ -1045,28 +1133,25 @@ def construct_asset_information(cls, element: etree._Element, object_class=model return asset_information @classmethod - def construct_submodel(cls, element: etree._Element, object_class=model.Submodel, **_kwargs: Any) \ - -> model.Submodel: - submodel = object_class( - _child_text_mandatory(element, NS_AAS + "id"), - kind=_get_kind(element) - ) + def construct_submodel(cls, element: etree._Element, object_class=model.Submodel, **_kwargs: Any) -> model.Submodel: + submodel = object_class(_child_text_mandatory(element, NS_AAS + "id"), kind=_get_kind(element)) if not cls.stripped: submodel_elements = element.find(NS_AAS + "submodelElements") if submodel_elements is not None: - for submodel_element in _failsafe_construct_multiple(submodel_elements, cls.construct_submodel_element, - cls.failsafe): + for submodel_element in _failsafe_construct_multiple( + submodel_elements, cls.construct_submodel_element, cls.failsafe + ): submodel.submodel_element.add(submodel_element) cls._amend_abstract_attributes(submodel, element) return submodel @classmethod - def construct_value_reference_pair(cls, element: etree._Element, object_class=model.ValueReferencePair, - **_kwargs: Any) -> model.ValueReferencePair: + def construct_value_reference_pair( + cls, element: etree._Element, object_class=model.ValueReferencePair, **_kwargs: Any + ) -> model.ValueReferencePair: value_id_element = element.find(NS_AAS + "valueId") value_id = cls.construct_reference(value_id_element, **_kwargs) if value_id_element is not None else None - return object_class(_child_text_mandatory(element, NS_AAS + "value"), - value_id) + return object_class(_child_text_mandatory(element, NS_AAS + "value"), value_id) @classmethod def construct_value_list(cls, element: etree._Element, **_kwargs: Any) -> model.ValueList: @@ -1075,63 +1160,75 @@ def construct_value_list(cls, element: etree._Element, **_kwargs: Any) -> model. """ return set( - _child_construct_multiple(_get_child_mandatory(element, NS_AAS + "valueReferencePairs"), - NS_AAS + "valueReferencePair", cls.construct_value_reference_pair, - cls.failsafe) + _child_construct_multiple( + _get_child_mandatory(element, NS_AAS + "valueReferencePairs"), + NS_AAS + "valueReferencePair", + cls.construct_value_reference_pair, + cls.failsafe, + ) ) @classmethod - def construct_concept_description(cls, element: etree._Element, object_class=model.ConceptDescription, - **_kwargs: Any) -> model.ConceptDescription: + def construct_concept_description( + cls, element: etree._Element, object_class=model.ConceptDescription, **_kwargs: Any + ) -> model.ConceptDescription: cd = object_class(_child_text_mandatory(element, NS_AAS + "id")) is_case_of = element.find(NS_AAS + "isCaseOf") if is_case_of is not None: - for ref in _child_construct_multiple(is_case_of, NS_AAS + "reference", cls.construct_reference, - cls.failsafe): + for ref in _child_construct_multiple( + is_case_of, NS_AAS + "reference", cls.construct_reference, cls.failsafe + ): cd.is_case_of.add(ref) cls._amend_abstract_attributes(cd, element) return cd @classmethod - def construct_embedded_data_specification(cls, element: etree._Element, - object_class=model.EmbeddedDataSpecification, - **_kwargs: Any) -> model.EmbeddedDataSpecification: + def construct_embedded_data_specification( + cls, element: etree._Element, object_class=model.EmbeddedDataSpecification, **_kwargs: Any + ) -> model.EmbeddedDataSpecification: data_specification_content = _get_child_mandatory(element, NS_AAS + "dataSpecificationContent") if len(data_specification_content) == 0: raise KeyError(f"{_element_pretty_identifier(data_specification_content)} has no data specification!") if len(data_specification_content) > 1: - logger.warning(f"{_element_pretty_identifier(data_specification_content)} has more than one " - "data specification, using the first one...") + logger.warning( + f"{_element_pretty_identifier(data_specification_content)} has more than one " + "data specification, using the first one..." + ) embedded_data_specification = object_class( _child_construct_mandatory(element, NS_AAS + "dataSpecification", cls.construct_external_reference), - _failsafe_construct_mandatory(data_specification_content[0], cls.construct_data_specification_content) + _failsafe_construct_mandatory(data_specification_content[0], cls.construct_data_specification_content), ) cls._amend_abstract_attributes(embedded_data_specification, element) return embedded_data_specification @classmethod - def construct_data_specification_content(cls, element: etree._Element, **kwargs: Any) \ - -> model.DataSpecificationContent: + def construct_data_specification_content( + cls, element: etree._Element, **kwargs: Any + ) -> model.DataSpecificationContent: """ This function doesn't support the object_class parameter. Overwrite each individual DataSpecificationContent constructor function instead. """ - data_specification_contents: Dict[str, Callable[..., model.DataSpecificationContent]] = \ - {NS_AAS + k: v for k, v in { + data_specification_contents: Dict[str, Callable[..., model.DataSpecificationContent]] = { + NS_AAS + k: v + for k, v in { "dataSpecificationIec61360": cls.construct_data_specification_iec61360, - }.items()} + }.items() + } if element.tag not in data_specification_contents: raise KeyError(f"{_element_pretty_identifier(element)} is not a valid DataSpecificationContent!") return data_specification_contents[element.tag](element, **kwargs) @classmethod - def construct_data_specification_iec61360(cls, element: etree._Element, - object_class=model.DataSpecificationIEC61360, - **_kwargs: Any) -> model.DataSpecificationIEC61360: - ds_iec = object_class(_child_construct_mandatory(element, NS_AAS + "preferredName", - cls.construct_preferred_name_type_iec61360)) - short_name = _failsafe_construct(element.find(NS_AAS + "shortName"), cls.construct_short_name_type_iec61360, - cls.failsafe) + def construct_data_specification_iec61360( + cls, element: etree._Element, object_class=model.DataSpecificationIEC61360, **_kwargs: Any + ) -> model.DataSpecificationIEC61360: + ds_iec = object_class( + _child_construct_mandatory(element, NS_AAS + "preferredName", cls.construct_preferred_name_type_iec61360) + ) + short_name = _failsafe_construct( + element.find(NS_AAS + "shortName"), cls.construct_short_name_type_iec61360, cls.failsafe + ) if short_name is not None: ds_iec.short_name = short_name unit = _get_text_or_none(element.find(NS_AAS + "unit")) @@ -1149,8 +1246,9 @@ def construct_data_specification_iec61360(cls, element: etree._Element, data_type = _get_text_mapped_or_none(element.find(NS_AAS + "dataType"), IEC61360_DATA_TYPES_INVERSE) if data_type is not None: ds_iec.data_type = data_type - definition = _failsafe_construct(element.find(NS_AAS + "definition"), cls.construct_definition_type_iec61360, - cls.failsafe) + definition = _failsafe_construct( + element.find(NS_AAS + "definition"), cls.construct_definition_type_iec61360, cls.failsafe + ) if definition is not None: ds_iec.definition = definition value_format = _get_text_or_none(element.find(NS_AAS + "valueFormat")) @@ -1177,8 +1275,10 @@ def construct_data_specification_iec61360(cls, element: etree._Element, raise ValueError level_type_value = _str_to_bool(child.text) except ValueError: - error_message = f"levelType {tag} of {_element_pretty_identifier(element)} has invalid boolean: " \ - + str(child.text) + error_message = ( + f"levelType {tag} of {_element_pretty_identifier(element)} has invalid boolean: " + + str(child.text) + ) if not cls.failsafe: raise ValueError(error_message) logger.warning(error_message) @@ -1193,6 +1293,7 @@ class StrictAASFromXmlDecoder(AASFromXmlDecoder): """ Non-failsafe XML decoder. Encountered errors won't be caught and abort parsing. """ + failsafe = False @@ -1200,6 +1301,7 @@ class StrippedAASFromXmlDecoder(AASFromXmlDecoder): """ Decoder for stripped XML elements. Used in the HTTP adapter. """ + stripped = True @@ -1207,6 +1309,7 @@ class StrictStrippedAASFromXmlDecoder(StrictAASFromXmlDecoder, StrippedAASFromXm """ Non-failsafe decoder for stripped XML elements. """ + pass @@ -1235,16 +1338,19 @@ def _parse_xml_document(file: PathOrIO, failsafe: bool = True, **parser_kwargs: missing_namespaces: Set[str] = REQUIRED_NAMESPACES - set(root.nsmap.values()) if missing_namespaces: - error_message = f"The following required namespaces are not declared: {' | '.join(missing_namespaces)}" \ - + " - Is the input document of an older version?" + error_message = ( + f"The following required namespaces are not declared: {' | '.join(missing_namespaces)}" + + " - Is the input document of an older version?" + ) if not failsafe: raise KeyError(error_message) logger.error(error_message) return root -def _select_decoder(failsafe: bool, stripped: bool, decoder: Optional[Type[AASFromXmlDecoder]]) \ - -> Type[AASFromXmlDecoder]: +def _select_decoder( + failsafe: bool, stripped: bool, decoder: Optional[Type[AASFromXmlDecoder]] +) -> Type[AASFromXmlDecoder]: """ Returns the correct decoder based on the parameters failsafe and stripped. If a decoder class is given, failsafe and stripped are ignored. @@ -1272,6 +1378,7 @@ class XMLConstructables(enum.Enum): """ This enum is used to specify which type to construct in read_aas_xml_element(). """ + KEY = enum.auto() REFERENCE = enum.auto() MODEL_REFERENCE = enum.auto() @@ -1315,8 +1422,14 @@ class XMLConstructables(enum.Enum): DATA_SPECIFICATION_IEC61360 = enum.auto() -def read_aas_xml_element(file: PathOrIO, construct: XMLConstructables, failsafe: bool = True, stripped: bool = False, - decoder: Optional[Type[AASFromXmlDecoder]] = None, **constructor_kwargs) -> Optional[object]: +def read_aas_xml_element( + file: PathOrIO, + construct: XMLConstructables, + failsafe: bool = True, + stripped: bool = False, + decoder: Optional[Type[AASFromXmlDecoder]] = None, + **constructor_kwargs, +) -> Optional[object]: """ Construct a single object from an XML string. The namespaces have to be declared on the object itself, since there is no surrounding environment element. @@ -1428,14 +1541,14 @@ def read_aas_xml_element(file: PathOrIO, construct: XMLConstructables, failsafe: def read_aas_xml_file_into( - object_store: model.AbstractObjectStore[model.Identifier, model.Identifiable], - file: PathOrIO, - replace_existing: bool = False, - ignore_existing: bool = False, - failsafe: bool = True, - stripped: bool = False, - decoder: Optional[Type[AASFromXmlDecoder]] = None, - **parser_kwargs: Any + object_store: model.AbstractObjectStore[model.Identifier, model.Identifiable], + file: PathOrIO, + replace_existing: bool = False, + ignore_existing: bool = False, + failsafe: bool = True, + stripped: bool = False, + decoder: Optional[Type[AASFromXmlDecoder]] = None, + **parser_kwargs: Any, ) -> Set[model.Identifier]: """ Read an Asset Administration Shell XML file according to 'Details of the Asset Administration Shell', chapter 5.4 @@ -1472,7 +1585,7 @@ def read_aas_xml_file_into( element_constructors: Dict[str, Callable[..., model.Identifiable]] = { "assetAdministrationShell": decoder_.construct_asset_administration_shell, "conceptDescription": decoder_.construct_concept_description, - "submodel": decoder_.construct_submodel + "submodel": decoder_.construct_submodel, } element_constructors = {NS_AAS + k: v for k, v in element_constructors.items()} @@ -1502,8 +1615,9 @@ def read_aas_xml_file_into( existing_element = object_store.get(element.id) if existing_element is not None: if not replace_existing: - error_message = f"object with identifier {element.id} already exists " \ - f"in the object store: {existing_element}!" + error_message = ( + f"object with identifier {element.id} already exists in the object store: {existing_element}!" + ) if not ignore_existing: raise KeyError(error_message + f" failed to insert {element}!") logger.info(error_message + f" skipping insertion of {element}...") @@ -1514,8 +1628,7 @@ def read_aas_xml_file_into( return ret -def read_aas_xml_file(file: PathOrIO, failsafe: bool = True, **kwargs: Any)\ - -> model.DictIdentifiableStore: +def read_aas_xml_file(file: PathOrIO, failsafe: bool = True, **kwargs: Any) -> model.DictIdentifiableStore: """ A wrapper of :meth:`~basyx.aas.adapter.xml.xml_deserialization.read_aas_xml_file_into`, that reads all objects in an empty :class:`~basyx.aas.model.provider.DictIdentifiableStore`. This function supports diff --git a/sdk/basyx/aas/adapter/xml/xml_serialization.py b/sdk/basyx/aas/adapter/xml/xml_serialization.py index 4b80e5954..1cf6dd45a 100644 --- a/sdk/basyx/aas/adapter/xml/xml_serialization.py +++ b/sdk/basyx/aas/adapter/xml/xml_serialization.py @@ -45,9 +45,8 @@ # functions to manipulate etree.Elements more effectively # ############################################################## -def _generate_element(name: str, - text: Optional[str] = None, - attributes: Optional[Dict] = None) -> etree._Element: + +def _generate_element(name: str, text: Optional[str] = None, attributes: Optional[Dict] = None) -> etree._Element: """ generate an :class:`~lxml.etree._Element` object @@ -123,17 +122,17 @@ def abstract_classes_to_xml(tag: str, obj: object) -> etree._Element: elm.append(_generate_element(name=NS_AAS + "kind", text="Instance")) if isinstance(obj, model.HasSemantics): if obj.semantic_id: - elm.append(reference_to_xml(obj.semantic_id, tag=NS_AAS+"semanticId")) + elm.append(reference_to_xml(obj.semantic_id, tag=NS_AAS + "semanticId")) if obj.supplemental_semantic_id: et_supplemental_semantic_ids = _generate_element(NS_AAS + "supplementalSemanticIds") for supplemental_semantic_id in obj.supplemental_semantic_id: - et_supplemental_semantic_ids.append(reference_to_xml(supplemental_semantic_id, NS_AAS+"reference")) + et_supplemental_semantic_ids.append(reference_to_xml(supplemental_semantic_id, NS_AAS + "reference")) elm.append(et_supplemental_semantic_ids) if isinstance(obj, model.Qualifiable): if obj.qualifier: et_qualifier = _generate_element(NS_AAS + "qualifiers") for qualifier in obj.qualifier: - et_qualifier.append(qualifier_to_xml(qualifier, tag=NS_AAS+"qualifier")) + et_qualifier.append(qualifier_to_xml(qualifier, tag=NS_AAS + "qualifier")) elm.append(et_qualifier) if isinstance(obj, model.HasDataSpecification): if obj.embedded_data_specifications: @@ -149,9 +148,9 @@ def abstract_classes_to_xml(tag: str, obj: object) -> etree._Element: # ############################################################## -def _value_to_xml(value: model.ValueDataType, - value_type: model.DataTypeDefXsd, - tag: str = NS_AAS+"value") -> etree._Element: +def _value_to_xml( + value: model.ValueDataType, value_type: model.DataTypeDefXsd, tag: str = NS_AAS + "value" +) -> etree._Element: """ Serialization of objects of :class:`~basyx.aas.model.base.ValueDataType` to XML @@ -163,8 +162,7 @@ def _value_to_xml(value: model.ValueDataType, # todo: add "{NS_XSI+"type": "xs:"+model.datatypes.XSD_TYPE_NAMES[value_type]}" as attribute, if the schema allows # it # TODO: if this is ever changed, check value_reference_pair_to_xml() - return _generate_element(tag, - text=model.datatypes.xsd_repr(value)) + return _generate_element(tag, text=model.datatypes.xsd_repr(value)) def lang_string_set_to_xml(obj: model.LangStringSet, tag: str) -> etree._Element: @@ -175,13 +173,16 @@ def lang_string_set_to_xml(obj: model.LangStringSet, tag: str) -> etree._Element :param tag: Namespace+Tag name of the returned XML element. :return: Serialized :class:`~lxml.etree._Element` object """ - LANG_STRING_SET_TAGS: Dict[Type[model.LangStringSet], str] = {k: NS_AAS + v for k, v in { - model.MultiLanguageNameType: "langStringNameType", - model.MultiLanguageTextType: "langStringTextType", - model.DefinitionTypeIEC61360: "langStringDefinitionTypeIec61360", - model.PreferredNameTypeIEC61360: "langStringPreferredNameTypeIec61360", - model.ShortNameTypeIEC61360: "langStringShortNameTypeIec61360" - }.items()} + LANG_STRING_SET_TAGS: Dict[Type[model.LangStringSet], str] = { + k: NS_AAS + v + for k, v in { + model.MultiLanguageNameType: "langStringNameType", + model.MultiLanguageTextType: "langStringTextType", + model.DefinitionTypeIEC61360: "langStringDefinitionTypeIec61360", + model.PreferredNameTypeIEC61360: "langStringPreferredNameTypeIec61360", + model.ShortNameTypeIEC61360: "langStringShortNameTypeIec61360", + }.items() + } et_lss = _generate_element(name=tag) for language, text in obj.items(): et_ls = _generate_element(name=LANG_STRING_SET_TAGS[type(obj)]) @@ -191,8 +192,9 @@ def lang_string_set_to_xml(obj: model.LangStringSet, tag: str) -> etree._Element return et_lss -def administrative_information_to_xml(obj: model.AdministrativeInformation, - tag: str = NS_AAS+"administration") -> etree._Element: +def administrative_information_to_xml( + obj: model.AdministrativeInformation, tag: str = NS_AAS + "administration" +) -> etree._Element: """ Serialization of objects of class :class:`~basyx.aas.model.base.AdministrativeInformation` to XML @@ -234,7 +236,7 @@ def data_element_to_xml(obj: model.DataElement) -> etree._Element: raise AssertionError(f"Type {obj.__class__.__name__} is not yet supported by the XML serialization!") -def key_to_xml(obj: model.Key, tag: str = NS_AAS+"key") -> etree._Element: +def key_to_xml(obj: model.Key, tag: str = NS_AAS + "key") -> etree._Element: """ Serialization of objects of class :class:`~basyx.aas.model.base.Key` to XML @@ -248,7 +250,7 @@ def key_to_xml(obj: model.Key, tag: str = NS_AAS+"key") -> etree._Element: return et_key -def reference_to_xml(obj: model.Reference, tag: str = NS_AAS+"reference") -> etree._Element: +def reference_to_xml(obj: model.Reference, tag: str = NS_AAS + "reference") -> etree._Element: """ Serialization of objects of class :class:`~basyx.aas.model.base.Reference` to XML @@ -268,7 +270,7 @@ def reference_to_xml(obj: model.Reference, tag: str = NS_AAS+"reference") -> etr return et_reference -def qualifier_to_xml(obj: model.Qualifier, tag: str = NS_AAS+"qualifier") -> etree._Element: +def qualifier_to_xml(obj: model.Qualifier, tag: str = NS_AAS + "qualifier") -> etree._Element: """ Serialization of objects of class :class:`~basyx.aas.model.base.Qualifier` to XML @@ -283,11 +285,11 @@ def qualifier_to_xml(obj: model.Qualifier, tag: str = NS_AAS+"qualifier") -> etr if obj.value: et_qualifier.append(_value_to_xml(obj.value, obj.value_type)) if obj.value_id: - et_qualifier.append(reference_to_xml(obj.value_id, NS_AAS+"valueId")) + et_qualifier.append(reference_to_xml(obj.value_id, NS_AAS + "valueId")) return et_qualifier -def extension_to_xml(obj: model.Extension, tag: str = NS_AAS+"extension") -> etree._Element: +def extension_to_xml(obj: model.Extension, tag: str = NS_AAS + "extension") -> etree._Element: """ Serialization of objects of class :class:`~basyx.aas.model.base.Extension` to XML @@ -298,20 +300,22 @@ def extension_to_xml(obj: model.Extension, tag: str = NS_AAS+"extension") -> etr et_extension = abstract_classes_to_xml(tag, obj) et_extension.append(_generate_element(NS_AAS + "name", text=obj.name)) if obj.value_type: - et_extension.append(_generate_element(NS_AAS + "valueType", - text=model.datatypes.XSD_TYPE_NAMES[obj.value_type])) + et_extension.append( + _generate_element(NS_AAS + "valueType", text=model.datatypes.XSD_TYPE_NAMES[obj.value_type]) + ) if obj.value: et_extension.append(_value_to_xml(obj.value, obj.value_type)) # type: ignore # (value_type could be None) if len(obj.refers_to) > 0: - refers_to = _generate_element(NS_AAS+"refersTo") + refers_to = _generate_element(NS_AAS + "refersTo") for reference in obj.refers_to: - refers_to.append(reference_to_xml(reference, NS_AAS+"reference")) + refers_to.append(reference_to_xml(reference, NS_AAS + "reference")) et_extension.append(refers_to) return et_extension -def value_reference_pair_to_xml(obj: model.ValueReferencePair, - tag: str = NS_AAS+"valueReferencePair") -> etree._Element: +def value_reference_pair_to_xml( + obj: model.ValueReferencePair, tag: str = NS_AAS + "valueReferencePair" +) -> etree._Element: """ Serialization of objects of class :class:`~basyx.aas.model.base.ValueReferencePair` to XML @@ -321,14 +325,13 @@ def value_reference_pair_to_xml(obj: model.ValueReferencePair, """ et_vrp = _generate_element(tag) # TODO: value_type isn't used at all by _value_to_xml(), thus we can ignore the type here for now - et_vrp.append(_generate_element(NS_AAS+"value", text=obj.value)) # type: ignore + et_vrp.append(_generate_element(NS_AAS + "value", text=obj.value)) # type: ignore if obj.value_id is not None: - et_vrp.append(reference_to_xml(obj.value_id, NS_AAS+"valueId")) + et_vrp.append(reference_to_xml(obj.value_id, NS_AAS + "valueId")) return et_vrp -def value_list_to_xml(obj: model.ValueList, - tag: str = NS_AAS+"valueList") -> etree._Element: +def value_list_to_xml(obj: model.ValueList, tag: str = NS_AAS + "valueList") -> etree._Element: """ Serialization of objects of class :class:`~basyx.aas.model.base.ValueList` to XML @@ -339,9 +342,9 @@ def value_list_to_xml(obj: model.ValueList, :return: Serialized :class:`~lxml.etree._Element` object """ et_value_list = _generate_element(tag) - et_value_reference_pairs = _generate_element(NS_AAS+"valueReferencePairs") + et_value_reference_pairs = _generate_element(NS_AAS + "valueReferencePairs") for aas_reference_pair in obj: - et_value_reference_pairs.append(value_reference_pair_to_xml(aas_reference_pair, NS_AAS+"valueReferencePair")) + et_value_reference_pairs.append(value_reference_pair_to_xml(aas_reference_pair, NS_AAS + "valueReferencePair")) et_value_list.append(et_value_reference_pairs) return et_value_list @@ -351,8 +354,7 @@ def value_list_to_xml(obj: model.ValueList, # ############################################################## -def specific_asset_id_to_xml(obj: model.SpecificAssetId, tag: str = NS_AAS + "specificAssetId") \ - -> etree._Element: +def specific_asset_id_to_xml(obj: model.SpecificAssetId, tag: str = NS_AAS + "specificAssetId") -> etree._Element: """ Serialization of objects of class :class:`~basyx.aas.model.base.SpecificAssetId` to XML @@ -369,7 +371,7 @@ def specific_asset_id_to_xml(obj: model.SpecificAssetId, tag: str = NS_AAS + "sp return et_asset_information -def asset_information_to_xml(obj: model.AssetInformation, tag: str = NS_AAS+"assetInformation") -> etree._Element: +def asset_information_to_xml(obj: model.AssetInformation, tag: str = NS_AAS + "assetInformation") -> etree._Element: """ Serialization of objects of class :class:`~basyx.aas.model.aas.AssetInformation` to XML @@ -389,13 +391,14 @@ def asset_information_to_xml(obj: model.AssetInformation, tag: str = NS_AAS+"ass if obj.asset_type: et_asset_information.append(_generate_element(name=NS_AAS + "assetType", text=obj.asset_type)) if obj.default_thumbnail: - et_asset_information.append(resource_to_xml(obj.default_thumbnail, NS_AAS+"defaultThumbnail")) + et_asset_information.append(resource_to_xml(obj.default_thumbnail, NS_AAS + "defaultThumbnail")) return et_asset_information -def concept_description_to_xml(obj: model.ConceptDescription, - tag: str = NS_AAS+"conceptDescription") -> etree._Element: +def concept_description_to_xml( + obj: model.ConceptDescription, tag: str = NS_AAS + "conceptDescription" +) -> etree._Element: """ Serialization of objects of class :class:`~basyx.aas.model.concept.ConceptDescription` to XML @@ -405,15 +408,16 @@ def concept_description_to_xml(obj: model.ConceptDescription, """ et_concept_description = abstract_classes_to_xml(tag, obj) if obj.is_case_of: - et_is_case_of = _generate_element(NS_AAS+"isCaseOf") + et_is_case_of = _generate_element(NS_AAS + "isCaseOf") for reference in obj.is_case_of: - et_is_case_of.append(reference_to_xml(reference, NS_AAS+"reference")) + et_is_case_of.append(reference_to_xml(reference, NS_AAS + "reference")) et_concept_description.append(et_is_case_of) return et_concept_description -def embedded_data_specification_to_xml(obj: model.EmbeddedDataSpecification, - tag: str = NS_AAS+"embeddedDataSpecification") -> etree._Element: +def embedded_data_specification_to_xml( + obj: model.EmbeddedDataSpecification, tag: str = NS_AAS + "embeddedDataSpecification" +) -> etree._Element: """ Serialization of objects of class :class:`~basyx.aas.model.base.EmbeddedDataSpecification` to XML @@ -427,8 +431,9 @@ def embedded_data_specification_to_xml(obj: model.EmbeddedDataSpecification, return et_embedded_data_specification -def data_specification_content_to_xml(obj: model.DataSpecificationContent, - tag: str = NS_AAS+"dataSpecificationContent") -> etree._Element: +def data_specification_content_to_xml( + obj: model.DataSpecificationContent, tag: str = NS_AAS + "dataSpecificationContent" +) -> etree._Element: """ Serialization of objects of class :class:`~basyx.aas.model.base.DataSpecificationContent` to XML @@ -444,8 +449,9 @@ def data_specification_content_to_xml(obj: model.DataSpecificationContent, return et_data_specification_content -def data_specification_iec61360_to_xml(obj: model.DataSpecificationIEC61360, - tag: str = NS_AAS+"dataSpecificationIec61360") -> etree._Element: +def data_specification_iec61360_to_xml( + obj: model.DataSpecificationIEC61360, tag: str = NS_AAS + "dataSpecificationIec61360" +) -> etree._Element: """ Serialization of objects of class :class:`~basyx.aas.model.base.DataSpecificationIEC61360` to XML @@ -462,13 +468,15 @@ def data_specification_iec61360_to_xml(obj: model.DataSpecificationIEC61360, if obj.unit_id is not None: et_data_specification_iec61360.append(reference_to_xml(obj.unit_id, NS_AAS + "unitId")) if obj.source_of_definition is not None: - et_data_specification_iec61360.append(_generate_element(NS_AAS + "sourceOfDefinition", - text=obj.source_of_definition)) + et_data_specification_iec61360.append( + _generate_element(NS_AAS + "sourceOfDefinition", text=obj.source_of_definition) + ) if obj.symbol is not None: et_data_specification_iec61360.append(_generate_element(NS_AAS + "symbol", text=obj.symbol)) if obj.data_type is not None: - et_data_specification_iec61360.append(_generate_element(NS_AAS + "dataType", - text=_generic.IEC61360_DATA_TYPES[obj.data_type])) + et_data_specification_iec61360.append( + _generate_element(NS_AAS + "dataType", text=_generic.IEC61360_DATA_TYPES[obj.data_type]) + ) if obj.definition is not None: et_data_specification_iec61360.append(lang_string_set_to_xml(obj.definition, NS_AAS + "definition")) @@ -488,8 +496,9 @@ def data_specification_iec61360_to_xml(obj: model.DataSpecificationIEC61360, return et_data_specification_iec61360 -def asset_administration_shell_to_xml(obj: model.AssetAdministrationShell, - tag: str = NS_AAS+"assetAdministrationShell") -> etree._Element: +def asset_administration_shell_to_xml( + obj: model.AssetAdministrationShell, tag: str = NS_AAS + "assetAdministrationShell" +) -> etree._Element: """ Serialization of objects of class :class:`~basyx.aas.model.aas.AssetAdministrationShell` to XML @@ -499,12 +508,12 @@ def asset_administration_shell_to_xml(obj: model.AssetAdministrationShell, """ et_aas = abstract_classes_to_xml(tag, obj) if obj.derived_from: - et_aas.append(reference_to_xml(obj.derived_from, tag=NS_AAS+"derivedFrom")) + et_aas.append(reference_to_xml(obj.derived_from, tag=NS_AAS + "derivedFrom")) et_aas.append(asset_information_to_xml(obj.asset_information, tag=NS_AAS + "assetInformation")) if obj.submodel: et_submodels = _generate_element(NS_AAS + "submodels") for reference in obj.submodel: - et_submodels.append(reference_to_xml(reference, tag=NS_AAS+"reference")) + et_submodels.append(reference_to_xml(reference, tag=NS_AAS + "reference")) et_aas.append(et_submodels) return et_aas @@ -542,8 +551,7 @@ def submodel_element_to_xml(obj: model.SubmodelElement) -> etree._Element: raise AssertionError(f"Type {obj.__class__.__name__} is not yet supported by the XML serialization!") -def submodel_to_xml(obj: model.Submodel, - tag: str = NS_AAS+"submodel") -> etree._Element: +def submodel_to_xml(obj: model.Submodel, tag: str = NS_AAS + "submodel") -> etree._Element: """ Serialization of objects of class :class:`~basyx.aas.model.submodel.Submodel` to XML @@ -560,8 +568,7 @@ def submodel_to_xml(obj: model.Submodel, return et_submodel -def property_to_xml(obj: model.Property, - tag: str = NS_AAS+"property") -> etree._Element: +def property_to_xml(obj: model.Property, tag: str = NS_AAS + "property") -> etree._Element: """ Serialization of objects of class :class:`~basyx.aas.model.submodel.Property` to XML @@ -578,8 +585,9 @@ def property_to_xml(obj: model.Property, return et_property -def multi_language_property_to_xml(obj: model.MultiLanguageProperty, - tag: str = NS_AAS+"multiLanguageProperty") -> etree._Element: +def multi_language_property_to_xml( + obj: model.MultiLanguageProperty, tag: str = NS_AAS + "multiLanguageProperty" +) -> etree._Element: """ Serialization of objects of class :class:`~basyx.aas.model.submodel.MultiLanguageProperty` to XML @@ -591,12 +599,11 @@ def multi_language_property_to_xml(obj: model.MultiLanguageProperty, if obj.value: et_multi_language_property.append(lang_string_set_to_xml(obj.value, tag=NS_AAS + "value")) if obj.value_id: - et_multi_language_property.append(reference_to_xml(obj.value_id, NS_AAS+"valueId")) + et_multi_language_property.append(reference_to_xml(obj.value_id, NS_AAS + "valueId")) return et_multi_language_property -def range_to_xml(obj: model.Range, - tag: str = NS_AAS+"range") -> etree._Element: +def range_to_xml(obj: model.Range, tag: str = NS_AAS + "range") -> etree._Element: """ Serialization of objects of class :class:`~basyx.aas.model.submodel.Range` to XML @@ -605,8 +612,7 @@ def range_to_xml(obj: model.Range, :return: Serialized :class:`~lxml.etree._Element` object """ et_range = abstract_classes_to_xml(tag, obj) - et_range.append(_generate_element(name=NS_AAS + "valueType", - text=model.datatypes.XSD_TYPE_NAMES[obj.value_type])) + et_range.append(_generate_element(name=NS_AAS + "valueType", text=model.datatypes.XSD_TYPE_NAMES[obj.value_type])) if obj.min is not None: et_range.append(_value_to_xml(obj.min, obj.value_type, tag=NS_AAS + "min")) if obj.max is not None: @@ -614,8 +620,7 @@ def range_to_xml(obj: model.Range, return et_range -def blob_to_xml(obj: model.Blob, - tag: str = NS_AAS+"blob") -> etree._Element: +def blob_to_xml(obj: model.Blob, tag: str = NS_AAS + "blob") -> etree._Element: """ Serialization of objects of class :class:`~basyx.aas.model.submodel.Blob` to XML @@ -633,8 +638,7 @@ def blob_to_xml(obj: model.Blob, return et_blob -def file_to_xml(obj: model.File, - tag: str = NS_AAS+"file") -> etree._Element: +def file_to_xml(obj: model.File, tag: str = NS_AAS + "file") -> etree._Element: """ Serialization of objects of class :class:`~basyx.aas.model.submodel.File` to XML @@ -650,8 +654,7 @@ def file_to_xml(obj: model.File, return et_file -def resource_to_xml(obj: model.Resource, - tag: str = NS_AAS+"resource") -> etree._Element: +def resource_to_xml(obj: model.Resource, tag: str = NS_AAS + "resource") -> etree._Element: """ Serialization of objects of class :class:`~basyx.aas.model.base.Resource` to XML @@ -666,8 +669,7 @@ def resource_to_xml(obj: model.Resource, return et_resource -def reference_element_to_xml(obj: model.ReferenceElement, - tag: str = NS_AAS+"referenceElement") -> etree._Element: +def reference_element_to_xml(obj: model.ReferenceElement, tag: str = NS_AAS + "referenceElement") -> etree._Element: """ Serialization of objects of class :class:`~basyx.aas.model.submodel.ReferenceElement` to XMl @@ -677,12 +679,13 @@ def reference_element_to_xml(obj: model.ReferenceElement, """ et_reference_element = abstract_classes_to_xml(tag, obj) if obj.value: - et_reference_element.append(reference_to_xml(obj.value, NS_AAS+"value")) + et_reference_element.append(reference_to_xml(obj.value, NS_AAS + "value")) return et_reference_element -def submodel_element_collection_to_xml(obj: model.SubmodelElementCollection, - tag: str = NS_AAS+"submodelElementCollection") -> etree._Element: +def submodel_element_collection_to_xml( + obj: model.SubmodelElementCollection, tag: str = NS_AAS + "submodelElementCollection" +) -> etree._Element: """ Serialization of objects of class :class:`~basyx.aas.model.submodel.SubmodelElementCollection` to XML @@ -699,8 +702,9 @@ def submodel_element_collection_to_xml(obj: model.SubmodelElementCollection, return et_submodel_element_collection -def submodel_element_list_to_xml(obj: model.SubmodelElementList, - tag: str = NS_AAS+"submodelElementList") -> etree._Element: +def submodel_element_list_to_xml( + obj: model.SubmodelElementList, tag: str = NS_AAS + "submodelElementList" +) -> etree._Element: """ Serialization of objects of class :class:`~basyx.aas.model.submodel.SubmodelElementList` to XML @@ -711,13 +715,20 @@ def submodel_element_list_to_xml(obj: model.SubmodelElementList, et_submodel_element_list = abstract_classes_to_xml(tag, obj) et_submodel_element_list.append(_generate_element(NS_AAS + "orderRelevant", boolean_to_xml(obj.order_relevant))) if obj.semantic_id_list_element is not None: - et_submodel_element_list.append(reference_to_xml(obj.semantic_id_list_element, - NS_AAS + "semanticIdListElement")) - et_submodel_element_list.append(_generate_element(NS_AAS + "typeValueListElement", _generic.KEY_TYPES[ - model.KEY_TYPES_CLASSES[obj.type_value_list_element]])) + et_submodel_element_list.append( + reference_to_xml(obj.semantic_id_list_element, NS_AAS + "semanticIdListElement") + ) + et_submodel_element_list.append( + _generate_element( + NS_AAS + "typeValueListElement", _generic.KEY_TYPES[model.KEY_TYPES_CLASSES[obj.type_value_list_element]] + ) + ) if obj.value_type_list_element is not None: - et_submodel_element_list.append(_generate_element(NS_AAS + "valueTypeListElement", - model.datatypes.XSD_TYPE_NAMES[obj.value_type_list_element])) + et_submodel_element_list.append( + _generate_element( + NS_AAS + "valueTypeListElement", model.datatypes.XSD_TYPE_NAMES[obj.value_type_list_element] + ) + ) if len(obj.value) > 0: et_value = _generate_element(NS_AAS + "value") for se in obj.value: @@ -726,8 +737,9 @@ def submodel_element_list_to_xml(obj: model.SubmodelElementList, return et_submodel_element_list -def relationship_element_to_xml(obj: model.RelationshipElement, - tag: str = NS_AAS+"relationshipElement") -> etree._Element: +def relationship_element_to_xml( + obj: model.RelationshipElement, tag: str = NS_AAS + "relationshipElement" +) -> etree._Element: """ Serialization of objects of class :class:`~basyx.aas.model.submodel.RelationshipElement` to XML @@ -737,14 +749,15 @@ def relationship_element_to_xml(obj: model.RelationshipElement, """ et_relationship_element = abstract_classes_to_xml(tag, obj) if obj.first is not None: - et_relationship_element.append(reference_to_xml(obj.first, NS_AAS+"first")) + et_relationship_element.append(reference_to_xml(obj.first, NS_AAS + "first")) if obj.second is not None: - et_relationship_element.append(reference_to_xml(obj.second, NS_AAS+"second")) + et_relationship_element.append(reference_to_xml(obj.second, NS_AAS + "second")) return et_relationship_element -def annotated_relationship_element_to_xml(obj: model.AnnotatedRelationshipElement, - tag: str = NS_AAS+"annotatedRelationshipElement") -> etree._Element: +def annotated_relationship_element_to_xml( + obj: model.AnnotatedRelationshipElement, tag: str = NS_AAS + "annotatedRelationshipElement" +) -> etree._Element: """ Serialization of objects of class :class:`~basyx.aas.model.submodel.AnnotatedRelationshipElement` to XML @@ -761,7 +774,7 @@ def annotated_relationship_element_to_xml(obj: model.AnnotatedRelationshipElemen return et_annotated_relationship_element -def operation_variable_to_xml(obj: model.SubmodelElement, tag: str = NS_AAS+"operationVariable") -> etree._Element: +def operation_variable_to_xml(obj: model.SubmodelElement, tag: str = NS_AAS + "operationVariable") -> etree._Element: """ Serialization of :class:`~basyx.aas.model.submodel.SubmodelElement` to the XML OperationVariable representation Since we don't implement the ``OperationVariable`` class, which is just a wrapper for a single @@ -773,14 +786,13 @@ def operation_variable_to_xml(obj: model.SubmodelElement, tag: str = NS_AAS+"ope :return: Serialized :class:`~lxml.etree._Element` object """ et_operation_variable = _generate_element(tag) - et_value = _generate_element(NS_AAS+"value") + et_value = _generate_element(NS_AAS + "value") et_value.append(submodel_element_to_xml(obj)) et_operation_variable.append(et_value) return et_operation_variable -def operation_to_xml(obj: model.Operation, - tag: str = NS_AAS+"operation") -> etree._Element: +def operation_to_xml(obj: model.Operation, tag: str = NS_AAS + "operation") -> etree._Element: """ Serialization of objects of class :class:`~basyx.aas.model.submodel.Operation` to XML @@ -789,9 +801,11 @@ def operation_to_xml(obj: model.Operation, :return: Serialized :class:`~lxml.etree._Element` object """ et_operation = abstract_classes_to_xml(tag, obj) - for tag, nss in ((NS_AAS+"inputVariables", obj.input_variable), - (NS_AAS+"outputVariables", obj.output_variable), - (NS_AAS+"inoutputVariables", obj.in_output_variable)): + for tag, nss in ( + (NS_AAS + "inputVariables", obj.input_variable), + (NS_AAS + "outputVariables", obj.output_variable), + (NS_AAS + "inoutputVariables", obj.in_output_variable), + ): if nss: et_variables = _generate_element(tag) for submodel_element in nss: @@ -800,8 +814,7 @@ def operation_to_xml(obj: model.Operation, return et_operation -def capability_to_xml(obj: model.Capability, - tag: str = NS_AAS+"capability") -> etree._Element: +def capability_to_xml(obj: model.Capability, tag: str = NS_AAS + "capability") -> etree._Element: """ Serialization of objects of class :class:`~basyx.aas.model.submodel.Capability` to XML @@ -812,8 +825,7 @@ def capability_to_xml(obj: model.Capability, return abstract_classes_to_xml(tag, obj) -def entity_to_xml(obj: model.Entity, - tag: str = NS_AAS+"entity") -> etree._Element: +def entity_to_xml(obj: model.Entity, tag: str = NS_AAS + "entity") -> etree._Element: """ Serialization of objects of class :class:`~basyx.aas.model.submodel.Entity` to XML @@ -839,7 +851,7 @@ def entity_to_xml(obj: model.Entity, return et_entity -def basic_event_element_to_xml(obj: model.BasicEventElement, tag: str = NS_AAS+"basicEventElement") -> etree._Element: +def basic_event_element_to_xml(obj: model.BasicEventElement, tag: str = NS_AAS + "basicEventElement") -> etree._Element: """ Serialization of objects of class :class:`~basyx.aas.model.submodel.BasicEventElement` to XML @@ -848,22 +860,25 @@ def basic_event_element_to_xml(obj: model.BasicEventElement, tag: str = NS_AAS+" :return: Serialized :class:`~lxml.etree._Element` object """ et_basic_event_element = abstract_classes_to_xml(tag, obj) - et_basic_event_element.append(reference_to_xml(obj.observed, NS_AAS+"observed")) - et_basic_event_element.append(_generate_element(NS_AAS+"direction", text=_generic.DIRECTION[obj.direction])) - et_basic_event_element.append(_generate_element(NS_AAS+"state", text=_generic.STATE_OF_EVENT[obj.state])) + et_basic_event_element.append(reference_to_xml(obj.observed, NS_AAS + "observed")) + et_basic_event_element.append(_generate_element(NS_AAS + "direction", text=_generic.DIRECTION[obj.direction])) + et_basic_event_element.append(_generate_element(NS_AAS + "state", text=_generic.STATE_OF_EVENT[obj.state])) if obj.message_topic is not None: - et_basic_event_element.append(_generate_element(NS_AAS+"messageTopic", text=obj.message_topic)) + et_basic_event_element.append(_generate_element(NS_AAS + "messageTopic", text=obj.message_topic)) if obj.message_broker is not None: - et_basic_event_element.append(reference_to_xml(obj.message_broker, NS_AAS+"messageBroker")) + et_basic_event_element.append(reference_to_xml(obj.message_broker, NS_AAS + "messageBroker")) if obj.last_update is not None: - et_basic_event_element.append(_generate_element(NS_AAS+"lastUpdate", - text=model.datatypes.xsd_repr(obj.last_update))) + et_basic_event_element.append( + _generate_element(NS_AAS + "lastUpdate", text=model.datatypes.xsd_repr(obj.last_update)) + ) if obj.min_interval is not None: - et_basic_event_element.append(_generate_element(NS_AAS+"minInterval", - text=model.datatypes.xsd_repr(obj.min_interval))) + et_basic_event_element.append( + _generate_element(NS_AAS + "minInterval", text=model.datatypes.xsd_repr(obj.min_interval)) + ) if obj.max_interval is not None: - et_basic_event_element.append(_generate_element(NS_AAS+"maxInterval", - text=model.datatypes.xsd_repr(obj.max_interval))) + et_basic_event_element.append( + _generate_element(NS_AAS + "maxInterval", text=model.datatypes.xsd_repr(obj.max_interval)) + ) return et_basic_event_element @@ -871,6 +886,7 @@ def basic_event_element_to_xml(obj: model.BasicEventElement, tag: str = NS_AAS+" # general functions # ############################################################## + def _write_element(file: _generic.PathOrBinaryIO, element: etree._Element, **kwargs) -> None: etree.ElementTree(element).write(file, encoding="UTF-8", xml_declaration=True, method="xml", **kwargs) @@ -975,9 +991,7 @@ def object_store_to_xml_element(data: model.AbstractObjectStore) -> etree._Eleme return root -def write_aas_xml_file(file: _generic.PathOrBinaryIO, - data: model.AbstractObjectStore, - **kwargs) -> None: +def write_aas_xml_file(file: _generic.PathOrBinaryIO, data: model.AbstractObjectStore, **kwargs) -> None: """ Write a set of AAS objects to an Asset Administration Shell XML file according to 'Details of the Asset Administration Shell', chapter 5.4 diff --git a/sdk/basyx/aas/backend/couchdb.py b/sdk/basyx/aas/backend/couchdb.py index fe02345eb..21d9e9ab4 100644 --- a/sdk/basyx/aas/backend/couchdb.py +++ b/sdk/basyx/aas/backend/couchdb.py @@ -11,6 +11,7 @@ The :class:`~CouchDBIdentifiableStore` handles adding, deleting and otherwise managing the AAS objects in a specific CouchDB. """ + import threading import warnings import weakref @@ -99,6 +100,7 @@ class CouchDBIdentifiableStore(model.AbstractObjectStore[model.Identifier, model objects are thread-safe, as long as no CouchDB credentials are added (via ``register_credentials()``) during transactions. """ + def __init__(self, url: str, database: str): """ Initializer of class CouchDBIdentifiableStore @@ -114,8 +116,9 @@ def __init__(self, url: str, database: str): # local replication of each object is kept in the application and retrieving an object from the store always # returns the **same** (not only equal) object. Still, objects are forgotten, when they are not referenced # anywhere else to save memory. - self._object_cache: weakref.WeakValueDictionary[model.Identifier, model.Identifiable]\ - = weakref.WeakValueDictionary() + self._object_cache: weakref.WeakValueDictionary[model.Identifier, model.Identifiable] = ( + weakref.WeakValueDictionary() + ) self._object_cache_lock = threading.Lock() def check_database(self, create=False): @@ -128,7 +131,7 @@ def check_database(self, create=False): """ try: - self._do_request("{}/{}".format(self.url, self.database_name), 'HEAD') + self._do_request("{}/{}".format(self.url, self.database_name), "HEAD") except CouchDBServerError as e: # If an HTTPError is raised, re-raise it, unless it is a 404 error and we are requested to create the # database @@ -140,7 +143,7 @@ def check_database(self, create=False): # Create database logger.info("Creating CouchDB database %s/%s ...", self.url, self.database_name) - self._do_request("{}/{}".format(self.url, self.database_name), 'PUT') + self._do_request("{}/{}".format(self.url, self.database_name), "PUT") def get_identifiable_by_couchdb_id(self, couchdb_id: str) -> model.Identifiable: """ @@ -154,18 +157,21 @@ def get_identifiable_by_couchdb_id(self, couchdb_id: str) -> model.Identifiable: try: data = self._do_request( - "{}/{}/{}".format(self.url, self.database_name, urllib.parse.quote(couchdb_id, safe=''))) + "{}/{}/{}".format(self.url, self.database_name, urllib.parse.quote(couchdb_id, safe="")) + ) except CouchDBServerError as e: if e.code == 404: raise KeyError("No Identifiable with couchdb-id {} found in CouchDB database".format(couchdb_id)) from e raise - obj = data['data'] + obj = data["data"] if not isinstance(obj, model.Identifiable): - raise CouchDBResponseError("The CouchDB document with id {} does not contain an identifiable AAS object." - .format(couchdb_id)) - set_couchdb_revision("{}/{}/{}".format(self.url, self.database_name, urllib.parse.quote(couchdb_id, safe='')), - data["_rev"]) + raise CouchDBResponseError( + "The CouchDB document with id {} does not contain an identifiable AAS object.".format(couchdb_id) + ) + set_couchdb_revision( + "{}/{}/{}".format(self.url, self.database_name, urllib.parse.quote(couchdb_id, safe="")), data["_rev"] + ) with self._object_cache_lock: if obj.id in self._object_cache: @@ -199,18 +205,20 @@ def add(self, x: model.Identifiable) -> None: """ logger.debug("Adding object %s to CouchDB database ...", repr(x)) # Serialize data - data = json.dumps({'data': x}, cls=json_serialization.AASToJsonEncoder) + data = json.dumps({"data": x}, cls=json_serialization.AASToJsonEncoder) # Create and issue HTTP request (raises HTTPError on status != 200) try: response = self._do_request( "{}/{}/{}".format(self.url, self.database_name, self._transform_id(x.id)), - 'PUT', - {'Content-type': 'application/json'}, - data.encode('utf-8')) - set_couchdb_revision("{}/{}/{}".format(self.url, self.database_name, self._transform_id(x.id)), - response["rev"]) + "PUT", + {"Content-type": "application/json"}, + data.encode("utf-8"), + ) + set_couchdb_revision( + "{}/{}/{}".format(self.url, self.database_name, self._transform_id(x.id)), response["rev"] + ) except CouchDBServerError as e: if e.code == 409: raise KeyError("Identifiable with id {} already exists in CouchDB database".format(x.id)) from e @@ -232,21 +240,19 @@ def commit(self, x: model.Identifiable) -> None: rev = get_couchdb_revision(doc_url) if rev is None: raise KeyError("No revision found for object with id {} — not fetched from this store".format(x.id)) - data = json.dumps({'data': x}, cls=json_serialization.AASToJsonEncoder) + data = json.dumps({"data": x}, cls=json_serialization.AASToJsonEncoder) try: response = self._do_request( - "{}?rev={}".format(doc_url, rev), - 'PUT', - {'Content-type': 'application/json'}, - data.encode('utf-8')) + "{}?rev={}".format(doc_url, rev), "PUT", {"Content-type": "application/json"}, data.encode("utf-8") + ) set_couchdb_revision(doc_url, response["rev"]) except CouchDBServerError as e: if e.code == 404: raise KeyError("No AAS object with id {} exists in CouchDB database".format(x.id)) from e elif e.code == 409: raise CouchDBConflictError( - "Object with id {} has been modified in the database since it was last fetched." - .format(x.id)) from e + "Object with id {} has been modified in the database since it was last fetched.".format(x.id) + ) from e raise def discard(self, x: model.Identifiable, safe_delete=False) -> None: @@ -264,9 +270,7 @@ def discard(self, x: model.Identifiable, safe_delete=False) -> None: (see ``_do_request()`` for details) """ logger.debug("Deleting object %s from CouchDB database ...", repr(x)) - rev = get_couchdb_revision("{}/{}/{}".format(self.url, - self.database_name, - self._transform_id(x.id))) + rev = get_couchdb_revision("{}/{}/{}".format(self.url, self.database_name, self._transform_id(x.id))) if rev is not None and safe_delete: logger.debug("using the object's stored revision token %s for deletion." % rev) @@ -278,34 +282,34 @@ def discard(self, x: model.Identifiable, safe_delete=False) -> None: try: logger.debug("fetching the current object revision for deletion ...") headers = self._do_request( - "{}/{}/{}".format(self.url, self.database_name, self._transform_id(x.id)), 'HEAD') - rev = headers['ETag'][1:-1] + "{}/{}/{}".format(self.url, self.database_name, self._transform_id(x.id)), "HEAD" + ) + rev = headers["ETag"][1:-1] except CouchDBServerError as e: if e.code == 404: - raise KeyError("No AAS object with id {} exists in CouchDB database".format(x.id))\ - from e + raise KeyError("No AAS object with id {} exists in CouchDB database".format(x.id)) from e raise try: self._do_request( - "{}/{}/{}?rev={}".format(self.url, self.database_name, self._transform_id(x.id), rev), - 'DELETE') + "{}/{}/{}?rev={}".format(self.url, self.database_name, self._transform_id(x.id), rev), "DELETE" + ) except CouchDBServerError as e: if e.code == 404: raise KeyError("No AAS object with id {} exists in CouchDB database".format(x.id)) from e elif e.code == 409: raise CouchDBConflictError( "Object with id {} has been modified in the database since " - "the version requested to be deleted.".format(x.id)) from e + "the version requested to be deleted.".format(x.id) + ) from e raise - delete_couchdb_revision("{}/{}/{}".format(self.url, - self.database_name, - self._transform_id(x.id))) + delete_couchdb_revision("{}/{}/{}".format(self.url, self.database_name, self._transform_id(x.id))) with self._object_cache_lock: del self._object_cache[x.id] @classmethod - def _do_request(cls, url: str, method: str = "GET", additional_headers: Dict[str, str] = {}, - body: Optional[bytes] = None) -> MutableMapping[str, Any]: + def _do_request( + cls, url: str, method: str = "GET", additional_headers: Dict[str, str] = {}, body: Optional[bytes] = None + ) -> MutableMapping[str, Any]: """ Perform an HTTP(S) request to the CouchDBServer, parse the result and handle errors @@ -320,9 +324,10 @@ def _do_request(cls, url: str, method: str = "GET", additional_headers: Dict[str url_parts = urllib.parse.urlparse(url) host = url_parts.scheme + url_parts.netloc auth = _credentials_store.get(host) - headers = urllib3.make_headers(keep_alive=True, accept_encoding=True, - basic_auth="{}:{}".format(*auth) if auth else None) - headers['Accept'] = 'application/json' + headers = urllib3.make_headers( + keep_alive=True, accept_encoding=True, basic_auth="{}:{}".format(*auth) if auth else None + ) + headers["Accept"] = "application/json" headers.update(additional_headers) try: response = _http_pool_manager.request(method, url, headers=headers, body=body) @@ -332,32 +337,37 @@ def _do_request(cls, url: str, method: str = "GET", additional_headers: Dict[str raise CouchDBResponseError("Error while connecting to the CouchDB server: {}".format(e)) from e if not (200 <= response.status < 300): - logger.debug("Request %s %s finished with HTTP status code %s.", - method, url, response.status) - if response.headers.get('Content-type', None) != 'application/json': - raise CouchDBResponseError("Unexpected Content-type header {} of response from CouchDB server" - .format(response.headers.get('Content-type', None))) - - if method == 'HEAD': + logger.debug("Request %s %s finished with HTTP status code %s.", method, url, response.status) + if response.headers.get("Content-type", None) != "application/json": + raise CouchDBResponseError( + "Unexpected Content-type header {} of response from CouchDB server".format( + response.headers.get("Content-type", None) + ) + ) + + if method == "HEAD": raise CouchDBServerError(response.status, "", "", "HTTP {}".format(response.status)) try: - data = json.loads(response.data.decode('utf-8')) + data = json.loads(response.data.decode("utf-8")) except json.JSONDecodeError: - raise CouchDBResponseError("Could not parse error message of HTTP {}" - .format(response.status)) - raise CouchDBServerError(response.status, data['error'], data['reason'], - "HTTP {}: {} (reason: {})".format(response.status, data['error'], data['reason'])) + raise CouchDBResponseError("Could not parse error message of HTTP {}".format(response.status)) + raise CouchDBServerError( + response.status, + data["error"], + data["reason"], + "HTTP {}: {} (reason: {})".format(response.status, data["error"], data["reason"]), + ) # Check response & parse data logger.debug("Request %s %s finished successfully.", method, url) - if method == 'HEAD': + if method == "HEAD": return response.headers - if response.headers.get('Content-type') != 'application/json': + if response.headers.get("Content-type") != "application/json": raise CouchDBResponseError("Unexpected Content-type header") try: - data = json.loads(response.data.decode('utf-8'), cls=json_deserialization.AASFromJsonDecoder) + data = json.loads(response.data.decode("utf-8"), cls=json_deserialization.AASFromJsonDecoder) except json.JSONDecodeError as e: raise CouchDBResponseError("Could not parse CouchDB server response as JSON data.") from e return data @@ -382,8 +392,7 @@ def __contains__(self, x: object) -> bool: logger.debug("Checking existence of object with id %s in database ...", repr(x)) try: - self._do_request( - "{}/{}/{}".format(self.url, self.database_name, self._transform_id(identifier)), 'HEAD') + self._do_request("{}/{}/{}".format(self.url, self.database_name, self._transform_id(identifier)), "HEAD") except CouchDBServerError as e: if e.code == 404: return False @@ -400,7 +409,7 @@ def __len__(self) -> int: """ logger.debug("Fetching number of documents from database ...") data = self._do_request("{}/{}".format(self.url, self.database_name)) - return data['doc_count'] + return data["doc_count"] def __iter__(self) -> Iterator[model.Identifiable]: """ @@ -412,6 +421,7 @@ def __iter__(self) -> Iterator[model.Identifiable]: :raises CouchDBError: If error occur during fetching the list of objects from the CouchDB server (see ``_do_request()`` for details) """ + # Iterator class storing the list of ids and fetching Identifiable objects on the fly class CouchDBIdentifiableIterator(Iterator[model.Identifiable]): def __init__(self, store: CouchDBIdentifiableStore, ids: Iterable[str]): @@ -425,7 +435,7 @@ def __next__(self): # Fetch a list of all ids and construct Iterator object logger.debug("Creating iterator over objects in database ...") data = self._do_request("{}/{}/_all_docs".format(self.url, self.database_name)) - return CouchDBIdentifiableIterator(self, (row['id'] for row in data['rows'])) + return CouchDBIdentifiableIterator(self, (row["id"] for row in data["rows"])) @staticmethod def _transform_id(identifier: model.Identifier, url_quote=True) -> str: @@ -435,7 +445,7 @@ def _transform_id(identifier: model.Identifier, url_quote=True) -> str: :param url_quote: If True, the result id string is url-encoded to be used in an HTTP request URL """ if url_quote: - identifier = urllib.parse.quote(identifier, safe='') + identifier = urllib.parse.quote(identifier, safe="") return identifier @@ -444,6 +454,7 @@ class CouchDBObjectStore(CouchDBIdentifiableStore): `CouchDBObjectStore` has been renamed to :class:`~.CouchDBIdentifiableStore` and will be removed in a future release. Please migrate to :class:`~.CouchDBIdentifiableStore`. """ + def __init__(self, url: str, database: str): warnings.warn( "`CouchDBObjectStore` is deprecated and will be removed in a future release. Use " @@ -465,23 +476,27 @@ def get_identifiable(self, identifier: model.Identifier) -> model.Identifiable: # ################################################################################################# # Custom Exception classes for reporting errors during interaction with the CouchDB server + class CouchDBError(Exception): pass class CouchDBConnectionError(CouchDBError): """Exception raised when the CouchDB server could not be reached""" + pass class CouchDBResponseError(CouchDBError): """Exception raised by when an HTTP of the CouchDB server could not be handled (e.g. no JSON body)""" + pass class CouchDBServerError(CouchDBError): """Exception raised when the CouchDB server returns an unexpected error code""" + def __init__(self, code: int, error: str, reason: str, *args): super().__init__(*args) self.code = code @@ -491,4 +506,5 @@ def __init__(self, code: int, error: str, reason: str, *args): class CouchDBConflictError(CouchDBError): """Exception raised when an object could not be committed due to a concurrent modification in the database""" + pass diff --git a/sdk/basyx/aas/backend/local_file.py b/sdk/basyx/aas/backend/local_file.py index df21ddfee..9b7f044e6 100644 --- a/sdk/basyx/aas/backend/local_file.py +++ b/sdk/basyx/aas/backend/local_file.py @@ -11,6 +11,7 @@ The :class:`~LocalFileIdentifiableStore` handles adding, deleting and otherwise managing the AAS objects in a specific Directory. """ + from typing import Iterator import logging import json @@ -40,6 +41,7 @@ class LocalFileIdentifiableStore(model.AbstractObjectStore[model.Identifier, mod with the last writer winning and no error raised. Use a dedicated database backend for any production deployment. """ + def __init__(self, directory_path: str): """ Initializer of class LocalFileIdentifiableStore @@ -53,8 +55,9 @@ def __init__(self, directory_path: str): # local replication of each object is kept in the application and retrieving an object from the store always # returns the **same** (not only equal) object. Still, objects are forgotten, when they are not referenced # anywhere else to save memory. - self._object_cache: weakref.WeakValueDictionary[model.Identifier, model.Identifiable] \ - = weakref.WeakValueDictionary() + self._object_cache: weakref.WeakValueDictionary[model.Identifier, model.Identifiable] = ( + weakref.WeakValueDictionary() + ) self._object_cache_lock = threading.Lock() def check_directory(self, create=False): @@ -212,6 +215,7 @@ class LocalFileObjectStore(LocalFileIdentifiableStore): `LocalFileObjectStore` has been renamed to :class:`~.LocalFileIdentifiableStore` and will be removed in a future release. Please migrate to :class:`~.LocalFileIdentifiableStore`. """ + def __init__(self, directory_path: str): warnings.warn( "`LocalFileObjectStore` is deprecated and will be removed in a future release. Use " diff --git a/sdk/basyx/aas/examples/data/__init__.py b/sdk/basyx/aas/examples/data/__init__.py index 3dce392cb..fdb249bc3 100644 --- a/sdk/basyx/aas/examples/data/__init__.py +++ b/sdk/basyx/aas/examples/data/__init__.py @@ -17,13 +17,18 @@ Module for the creation of an example submodel template containing all kind of submodel elements where the kind is always TEMPLATE. """ + import os from basyx.aas import model -from basyx.aas.examples.data import example_aas_missing_attributes, example_aas, \ - example_aas_mandatory_attributes, example_submodel_template +from basyx.aas.examples.data import ( + example_aas_missing_attributes, + example_aas, + example_aas_mandatory_attributes, + example_submodel_template, +) -TEST_PDF_FILE = os.path.join(os.path.dirname(__file__), 'TestFile.pdf') +TEST_PDF_FILE = os.path.join(os.path.dirname(__file__), "TestFile.pdf") def create_example() -> model.DictIdentifiableStore: @@ -55,12 +60,12 @@ def create_example_aas_binding() -> model.DictIdentifiableStore: identifiable_store.update(example_aas_missing_attributes.create_full_example()) identifiable_store.add(example_submodel_template.create_example_submodel_template()) - aas = identifiable_store.get_item('https://example.org/Test_AssetAdministrationShell') - sm = identifiable_store.get_item('https://example.org/Test_Submodel_Template') - assert (isinstance(aas, model.aas.AssetAdministrationShell)) # make mypy happy - assert (isinstance(sm, model.submodel.Submodel)) # make mypy happy + aas = identifiable_store.get_item("https://example.org/Test_AssetAdministrationShell") + sm = identifiable_store.get_item("https://example.org/Test_Submodel_Template") + assert isinstance(aas, model.aas.AssetAdministrationShell) # make mypy happy + assert isinstance(sm, model.submodel.Submodel) # make mypy happy aas.submodel.add(model.ModelReference.from_referable(sm)) - cd = identifiable_store.get_item('https://example.org/Test_ConceptDescription_Mandatory') - assert (isinstance(cd, model.concept.ConceptDescription)) # make mypy happy + cd = identifiable_store.get_item("https://example.org/Test_ConceptDescription_Mandatory") + assert isinstance(cd, model.concept.ConceptDescription) # make mypy happy return identifiable_store diff --git a/sdk/basyx/aas/examples/data/_helper.py b/sdk/basyx/aas/examples/data/_helper.py index 4093de32f..d9b8e4813 100644 --- a/sdk/basyx/aas/examples/data/_helper.py +++ b/sdk/basyx/aas/examples/data/_helper.py @@ -10,6 +10,7 @@ .. warning:: This module is intended for internal use only. """ + import pprint from typing import List, NamedTuple, Iterator, Dict, Any, Type, Union, Set, Iterable, TypeVar @@ -25,10 +26,13 @@ class CheckResult(NamedTuple): data: Dict[str, Any] def __repr__(self): - return "{}: {} ({})".format("OK " if self.result else "FAIL", - self.expectation, - ", ".join("{}={}".format(k, pprint.pformat(v, depth=2, width=2 ** 14, compact=True)) - for k, v in self.data.items())) + return "{}: {} ({})".format( + "OK " if self.result else "FAIL", + self.expectation, + ", ".join( + "{}={}".format(k, pprint.pformat(v, depth=2, width=2**14, compact=True)) for k, v in self.data.items() + ), + ) class DataChecker: @@ -39,12 +43,12 @@ class DataChecker: .. code-block:: python - data = {'a': 1, 'b': 2} + data = {"a": 1, "b": 2} dc = DataChecker() dc.check(len(data) > 0, "data is not empty") - if dc.check('a' in data, "a is in data", keys=list(data.keys())): - dc.check(data['a'] == data['b'], "a == b", a=data['a'], b=data['b']) - dc.check(data['a'] > 0, "a is positive", a=data['a']) + if dc.check("a" in data, "a is in data", keys=list(data.keys())): + dc.check(data["a"] == data["b"], "a == b", a=data["a"], b=data["b"]) + dc.check(data["a"] > 0, "a is positive", a=data["a"]) for result in dc.failed_checks: print(result) @@ -93,8 +97,9 @@ def raise_failed(self) -> None: """ failed = list(self.failed_checks) if len(failed) > 0: - raise AssertionError("{} of {} checks failed".format(len(failed), len(self.checks)), - [f.expectation for f in failed]) + raise AssertionError( + "{} of {} checks failed".format(len(failed), len(self.checks)), [f.expectation for f in failed] + ) class AASDataChecker(DataChecker): @@ -133,7 +138,7 @@ def _check_submodel_element(self, object_: model.SubmodelElement, expected_objec if isinstance(object_, model.BasicEventElement): return self.check_basic_event_element_equal(object_, expected_object) # type: ignore else: - raise AttributeError('Submodel Element class not implemented') + raise AttributeError("Submodel Element class not implemented") def _check_has_extension_equal(self, object_: model.HasExtension, expected_object: model.HasExtension): """ @@ -146,14 +151,14 @@ def _check_has_extension_equal(self, object_: model.HasExtension, expected_objec """ if not self.check_extensions: return - self.check_contained_element_length(object_, 'extension', model.Extension, len(expected_object.extension)) + self.check_contained_element_length(object_, "extension", model.Extension, len(expected_object.extension)) for expected_extension in expected_object.extension: - extension = object_.extension.get('name', expected_extension.name) - if self.check(extension is not None, f'{expected_extension!r} must exist'): + extension = object_.extension.get("name", expected_extension.name) + if self.check(extension is not None, f"{expected_extension!r} must exist"): self._check_extension_equal(extension, expected_extension) # type: ignore found_extensions = self._find_extra_namespace_set_elements_by_name(object_.extension, expected_object.extension) - self.check(found_extensions == set(), f'{object_!r} must not have extra extensions', value=found_extensions) + self.check(found_extensions == set(), f"{object_!r} must not have extra extensions", value=found_extensions) def _check_extension_equal(self, object_: model.Extension, expected_object: model.Extension): """ @@ -165,10 +170,10 @@ def _check_extension_equal(self, object_: model.Extension, expected_object: mode :return: The value of expression to be used in control statements """ self._check_has_semantics_equal(object_, expected_object) - self.check_attribute_equal(object_, 'name', expected_object.name) - self.check_attribute_equal(object_, 'value_type', expected_object.value_type) - self.check_attribute_equal(object_, 'value', expected_object.value) - self.check_attribute_equal(object_, 'refers_to', expected_object.refers_to) + self.check_attribute_equal(object_, "name", expected_object.name) + self.check_attribute_equal(object_, "value_type", expected_object.value_type) + self.check_attribute_equal(object_, "value", expected_object.value) + self.check_attribute_equal(object_, "refers_to", expected_object.refers_to) def _check_referable_equal(self, object_: model.Referable, expected_object: model.Referable): """ @@ -227,13 +232,18 @@ def _check_has_semantics_equal(self, object_: model.HasSemantics, expected_objec ) for suppl_semantic_id in expected_object.supplemental_semantic_id: given_semantic_id = self._find_reference(suppl_semantic_id, object_.supplemental_semantic_id) - self.check(given_semantic_id is not None, f"{object_!r} must have supplementalSemanticId", - value=suppl_semantic_id) + self.check( + given_semantic_id is not None, f"{object_!r} must have supplementalSemanticId", value=suppl_semantic_id + ) - found_elements = self._find_extra_object(object_.supplemental_semantic_id, - expected_object.supplemental_semantic_id, model.Reference) - self.check(found_elements == set(), '{} must not have extra supplementalSemanticId'.format(repr(object_)), - value=found_elements) + found_elements = self._find_extra_object( + object_.supplemental_semantic_id, expected_object.supplemental_semantic_id, model.Reference + ) + self.check( + found_elements == set(), + "{} must not have extra supplementalSemanticId".format(repr(object_)), + value=found_elements, + ) def _check_has_kind_equal(self, object_: model.HasKind, expected_object: model.HasKind): """ @@ -255,22 +265,27 @@ def _check_qualifiable_equal(self, object_: model.Qualifiable, expected_object: :param expected_object: The expected qualifiable object :return: The value of expression to be used in control statements """ - self.check_contained_element_length(object_, 'qualifier', model.Qualifier, len(expected_object.qualifier)) + self.check_contained_element_length(object_, "qualifier", model.Qualifier, len(expected_object.qualifier)) for expected_element in expected_object.qualifier: - element = self._find_element_by_attribute(expected_element, list(object_.qualifier), 'type') - if self.check(element is not None, '{} must exist'.format(repr(expected_element))): + element = self._find_element_by_attribute(expected_element, list(object_.qualifier), "type") + if self.check(element is not None, "{} must exist".format(repr(expected_element))): if isinstance(element, model.Qualifier): self._check_qualifier_equal(element, expected_element) # type: ignore else: - raise TypeError('Qualifier class not implemented') - - found_elements = self._find_extra_elements_by_attribute(list(object_.qualifier), - list(expected_object.qualifier), 'type') - self.check(found_elements == set(), 'Qualifiable Element {} must not have extra elements'.format(repr(object_)), - value=found_elements) - - def _check_has_data_specification_equal(self, object_: model.HasDataSpecification, - expected_object: model.HasDataSpecification): + raise TypeError("Qualifier class not implemented") + + found_elements = self._find_extra_elements_by_attribute( + list(object_.qualifier), list(expected_object.qualifier), "type" + ) + self.check( + found_elements == set(), + "Qualifiable Element {} must not have extra elements".format(repr(object_)), + value=found_elements, + ) + + def _check_has_data_specification_equal( + self, object_: model.HasDataSpecification, expected_object: model.HasDataSpecification + ): """ Checks if the HasDataSpecification object_ has the same HasDataSpecification attributes as the expected_value object and adds / stores the check result for later analysis. @@ -278,24 +293,39 @@ def _check_has_data_specification_equal(self, object_: model.HasDataSpecificatio :param object_: The HasDataSpecification object which shall be checked :param expected_object: The expected HasDataSpecification object """ - self.check_contained_element_length(object_, 'embedded_data_specifications', model.EmbeddedDataSpecification, - len(expected_object.embedded_data_specifications)) + self.check_contained_element_length( + object_, + "embedded_data_specifications", + model.EmbeddedDataSpecification, + len(expected_object.embedded_data_specifications), + ) for expected_dspec in expected_object.embedded_data_specifications: - given_dspec = self._find_element_by_attribute(expected_dspec, object_.embedded_data_specifications, - 'data_specification') - if self.check(given_dspec is not None, 'EmbeddedDataSpecification {} must exist in {}'.format( - repr(expected_dspec.data_specification), repr(object_))): - self.check_data_specification_content_equal(given_dspec.data_specification_content, # type: ignore - expected_dspec.data_specification_content) - - found_elements = self._find_extra_elements_by_attribute(object_.embedded_data_specifications, - expected_object.embedded_data_specifications, - 'data_specification') - self.check(found_elements == set(), '{} must not have extra data specifications'.format(repr(object_)), - value=found_elements) - - def _check_abstract_attributes_submodel_element_equal(self, object_: model.SubmodelElement, - expected_value: model.SubmodelElement): + given_dspec = self._find_element_by_attribute( + expected_dspec, object_.embedded_data_specifications, "data_specification" + ) + if self.check( + given_dspec is not None, + "EmbeddedDataSpecification {} must exist in {}".format( + repr(expected_dspec.data_specification), repr(object_) + ), + ): + self.check_data_specification_content_equal( + given_dspec.data_specification_content, # type: ignore + expected_dspec.data_specification_content, + ) + + found_elements = self._find_extra_elements_by_attribute( + object_.embedded_data_specifications, expected_object.embedded_data_specifications, "data_specification" + ) + self.check( + found_elements == set(), + "{} must not have extra data specifications".format(repr(object_)), + value=found_elements, + ) + + def _check_abstract_attributes_submodel_element_equal( + self, object_: model.SubmodelElement, expected_value: model.SubmodelElement + ): """ Checks if the given SubmodelElement objects are equal @@ -308,8 +338,9 @@ def _check_abstract_attributes_submodel_element_equal(self, object_: model.Submo self._check_qualifiable_equal(object_, expected_value) self._check_has_data_specification_equal(object_, expected_value) - def _check_submodel_elements_equal_unordered(self, object_: _LIST_OR_COLLECTION, - expected_value: _LIST_OR_COLLECTION): + def _check_submodel_elements_equal_unordered( + self, object_: _LIST_OR_COLLECTION, expected_value: _LIST_OR_COLLECTION + ): """ Checks if the given SubmodelElement objects are equal (in any order) @@ -322,11 +353,12 @@ def _check_submodel_elements_equal_unordered(self, object_: _LIST_OR_COLLECTION, element = object_.get_referable(expected_element.id_short) self._check_submodel_element(element, expected_element) # type: ignore except KeyError: - self.check(False, 'Submodel Element {} must exist'.format(repr(expected_element))) + self.check(False, "Submodel Element {} must exist".format(repr(expected_element))) found_elements = self._find_extra_namespace_set_elements_by_id_short(object_.value, expected_value.value) - self.check(found_elements == set(), '{} must not have extra elements'.format(repr(object_)), - value=found_elements) + self.check( + found_elements == set(), "{} must not have extra elements".format(repr(object_)), value=found_elements + ) def check_property_equal(self, object_: model.Property, expected_value: model.Property): """ @@ -337,12 +369,13 @@ def check_property_equal(self, object_: model.Property, expected_value: model.Pr :return: """ self._check_abstract_attributes_submodel_element_equal(object_, expected_value) - self.check_attribute_equal(object_, 'value_type', expected_value.value_type) - self.check_attribute_equal(object_, 'value', expected_value.value) - self.check_attribute_equal(object_, 'value_id', expected_value.value_id) + self.check_attribute_equal(object_, "value_type", expected_value.value_type) + self.check_attribute_equal(object_, "value", expected_value.value) + self.check_attribute_equal(object_, "value_id", expected_value.value_id) - def check_multi_language_property_equal(self, object_: model.MultiLanguageProperty, - expected_value: model.MultiLanguageProperty): + def check_multi_language_property_equal( + self, object_: model.MultiLanguageProperty, expected_value: model.MultiLanguageProperty + ): """ Checks if the given MultiLanguageProperty objects are equal @@ -351,8 +384,8 @@ def check_multi_language_property_equal(self, object_: model.MultiLanguageProper :return: """ self._check_abstract_attributes_submodel_element_equal(object_, expected_value) - self.check_attribute_equal(object_, 'value', expected_value.value) - self.check_attribute_equal(object_, 'value_id', expected_value.value_id) + self.check_attribute_equal(object_, "value", expected_value.value) + self.check_attribute_equal(object_, "value_id", expected_value.value_id) def check_range_equal(self, object_: model.Range, expected_value: model.Range): """ @@ -363,9 +396,9 @@ def check_range_equal(self, object_: model.Range, expected_value: model.Range): :return: """ self._check_abstract_attributes_submodel_element_equal(object_, expected_value) - self.check_attribute_equal(object_, 'value_type', expected_value.value_type) - self.check_attribute_equal(object_, 'min', expected_value.min) - self.check_attribute_equal(object_, 'max', expected_value.max) + self.check_attribute_equal(object_, "value_type", expected_value.value_type) + self.check_attribute_equal(object_, "min", expected_value.min) + self.check_attribute_equal(object_, "max", expected_value.max) def check_blob_equal(self, object_: model.Blob, expected_value: model.Blob): """ @@ -376,8 +409,8 @@ def check_blob_equal(self, object_: model.Blob, expected_value: model.Blob): :return: """ self._check_abstract_attributes_submodel_element_equal(object_, expected_value) - self.check_attribute_equal(object_, 'value', expected_value.value) - self.check_attribute_equal(object_, 'content_type', expected_value.content_type) + self.check_attribute_equal(object_, "value", expected_value.value) + self.check_attribute_equal(object_, "content_type", expected_value.content_type) def check_file_equal(self, object_: model.File, expected_value: model.File): """ @@ -388,8 +421,8 @@ def check_file_equal(self, object_: model.File, expected_value: model.File): :return: """ self._check_abstract_attributes_submodel_element_equal(object_, expected_value) - self.check_attribute_equal(object_, 'value', expected_value.value) - self.check_attribute_equal(object_, 'content_type', expected_value.content_type) + self.check_attribute_equal(object_, "value", expected_value.value) + self.check_attribute_equal(object_, "content_type", expected_value.content_type) def check_resource_equal(self, object_: model.Resource, expected_value: model.Resource): """ @@ -399,8 +432,8 @@ def check_resource_equal(self, object_: model.Resource, expected_value: model.Re :param expected_value: expected Resource object :return: """ - self.check_attribute_equal(object_, 'path', expected_value.path) - self.check_attribute_equal(object_, 'content_type', expected_value.content_type) + self.check_attribute_equal(object_, "path", expected_value.path) + self.check_attribute_equal(object_, "content_type", expected_value.content_type) def check_reference_element_equal(self, object_: model.ReferenceElement, expected_value: model.ReferenceElement): """ @@ -411,10 +444,11 @@ def check_reference_element_equal(self, object_: model.ReferenceElement, expecte :return: """ self._check_abstract_attributes_submodel_element_equal(object_, expected_value) - self.check_attribute_equal(object_, 'value', expected_value.value) + self.check_attribute_equal(object_, "value", expected_value.value) - def check_submodel_element_collection_equal(self, object_: model.SubmodelElementCollection, - expected_value: model.SubmodelElementCollection): + def check_submodel_element_collection_equal( + self, object_: model.SubmodelElementCollection, expected_value: model.SubmodelElementCollection + ): """ Checks if the given SubmodelElementCollection objects are equal @@ -424,11 +458,12 @@ def check_submodel_element_collection_equal(self, object_: model.SubmodelElement """ # the submodel elements are compared unordered, as collections are unordered self._check_abstract_attributes_submodel_element_equal(object_, expected_value) - self.check_contained_element_length(object_, 'value', model.SubmodelElement, len(expected_value.value)) + self.check_contained_element_length(object_, "value", model.SubmodelElement, len(expected_value.value)) self._check_submodel_elements_equal_unordered(object_, expected_value) - def check_submodel_element_list_equal(self, object_: model.SubmodelElementList, - expected_value: model.SubmodelElementList): + def check_submodel_element_list_equal( + self, object_: model.SubmodelElementList, expected_value: model.SubmodelElementList + ): """ Checks if the given SubmodelElementList objects are equal @@ -437,12 +472,13 @@ def check_submodel_element_list_equal(self, object_: model.SubmodelElementList, :return: """ self._check_abstract_attributes_submodel_element_equal(object_, expected_value) - self.check_attribute_equal(object_, 'order_relevant', expected_value.order_relevant) - self.check_attribute_equal(object_, 'semantic_id_list_element', expected_value.semantic_id_list_element) - self.check_attribute_equal(object_, 'value_type_list_element', expected_value.value_type_list_element) - self.check_attribute_equal(object_, 'type_value_list_element', expected_value.type_value_list_element) - self.check_contained_element_length(object_, 'value', object_.type_value_list_element, - len(expected_value.value)) + self.check_attribute_equal(object_, "order_relevant", expected_value.order_relevant) + self.check_attribute_equal(object_, "semantic_id_list_element", expected_value.semantic_id_list_element) + self.check_attribute_equal(object_, "value_type_list_element", expected_value.value_type_list_element) + self.check_attribute_equal(object_, "type_value_list_element", expected_value.type_value_list_element) + self.check_contained_element_length( + object_, "value", object_.type_value_list_element, len(expected_value.value) + ) if not object_.order_relevant or not expected_value.order_relevant: # It is impossible to compare SubmodelElementLists with order_relevant=False, since it is impossible # to know which element should be compared against which other element. @@ -452,8 +488,9 @@ def check_submodel_element_list_equal(self, object_: model.SubmodelElementList, for se1, se2 in zip(object_.value, expected_value.value): self._check_submodel_element(se1, se2) - def check_relationship_element_equal(self, object_: model.RelationshipElement, - expected_value: model.RelationshipElement): + def check_relationship_element_equal( + self, object_: model.RelationshipElement, expected_value: model.RelationshipElement + ): """ Checks if the given RelationshipElement objects are equal @@ -462,11 +499,12 @@ def check_relationship_element_equal(self, object_: model.RelationshipElement, :return: """ self._check_abstract_attributes_submodel_element_equal(object_, expected_value) - self.check_attribute_equal(object_, 'first', expected_value.first) - self.check_attribute_equal(object_, 'second', expected_value.second) + self.check_attribute_equal(object_, "first", expected_value.first) + self.check_attribute_equal(object_, "second", expected_value.second) - def check_annotated_relationship_element_equal(self, object_: model.AnnotatedRelationshipElement, - expected_value: model.AnnotatedRelationshipElement): + def check_annotated_relationship_element_equal( + self, object_: model.AnnotatedRelationshipElement, expected_value: model.AnnotatedRelationshipElement + ): """ Checks if the given AnnotatedRelationshipElement objects are equal @@ -475,19 +513,21 @@ def check_annotated_relationship_element_equal(self, object_: model.AnnotatedRel :return: """ self.check_relationship_element_equal(object_, expected_value) - self.check_contained_element_length(object_, 'annotation', model.DataElement, - len(expected_value.annotation)) + self.check_contained_element_length(object_, "annotation", model.DataElement, len(expected_value.annotation)) for expected_data_element in expected_value.annotation: try: object_.get_referable(expected_data_element.id_short) except KeyError: - self.check(False, 'Annotation {} must exist'.format(repr(expected_data_element))) + self.check(False, "Annotation {} must exist".format(repr(expected_data_element))) - found_elements = self._find_extra_namespace_set_elements_by_id_short(object_.annotation, - expected_value.annotation) - self.check(found_elements == set(), 'Annotated Reference {} must not have extra ' - 'references'.format(repr(object_)), - value=found_elements) + found_elements = self._find_extra_namespace_set_elements_by_id_short( + object_.annotation, expected_value.annotation + ) + self.check( + found_elements == set(), + "Annotated Reference {} must not have extra references".format(repr(object_)), + value=found_elements, + ) def _check_reference_equal(self, object_: model.Reference, expected_value: model.Reference): """ @@ -512,8 +552,9 @@ def _find_reference(self, object_: model.Reference, search_list: Iterable) -> Un return element return None - def _find_specific_asset_id(self, object_: model.SpecificAssetId, search_list: Iterable) \ - -> Union[model.SpecificAssetId, None]: + def _find_specific_asset_id( + self, object_: model.SpecificAssetId, search_list: Iterable + ) -> Union[model.SpecificAssetId, None]: """ Find a SpecificAssetId in a list @@ -545,8 +586,7 @@ def _find_element_by_attribute(self, object_: object, search_list: Iterable, *at return element return None - def _find_extra_object(self, object_list: Iterable, search_list: Iterable, - type_) -> Union[Set, None]: + def _find_extra_object(self, object_list: Iterable, search_list: Iterable, type_) -> Union[Set, None]: """ Find extra objects that are in object_list but still in search_list @@ -568,8 +608,9 @@ def _find_extra_object(self, object_list: Iterable, search_list: Iterable, found_elements.add(object_list_element) return found_elements - def _find_extra_elements_by_attribute(self, object_list: Union[Set, List], search_list: Union[Set, List], - *attribute: str) -> Set: + def _find_extra_elements_by_attribute( + self, object_list: Union[Set, List], search_list: Union[Set, List], *attribute: str + ) -> Set: """ Find extra elements that are in object_list but not in search_list @@ -593,8 +634,9 @@ def _find_extra_elements_by_attribute(self, object_list: Union[Set, List], searc found_elements.add(object_list_element) return found_elements - def _find_extra_namespace_set_elements_by_attribute(self, object_list: model.NamespaceSet, - search_list: model.NamespaceSet, attr_name: str) -> Set: + def _find_extra_namespace_set_elements_by_attribute( + self, object_list: model.NamespaceSet, search_list: model.NamespaceSet, attr_name: str + ) -> Set: """ Find extra elements that are in object_list but not in search_list by identifying attribute @@ -610,8 +652,9 @@ def _find_extra_namespace_set_elements_by_attribute(self, object_list: model.Nam found_elements.add(object_list_element) return found_elements - def _find_extra_namespace_set_elements_by_id_short(self, object_list: model.NamespaceSet, - search_list: model.NamespaceSet) -> Set: + def _find_extra_namespace_set_elements_by_id_short( + self, object_list: model.NamespaceSet, search_list: model.NamespaceSet + ) -> Set: """ Find extra Referable objects that are in object_list but not in search_list @@ -619,10 +662,11 @@ def _find_extra_namespace_set_elements_by_id_short(self, object_list: model.Name :param search_list: List which should be searched :return: Set of elements that are in object_list but not in search_list """ - return self._find_extra_namespace_set_elements_by_attribute(object_list, search_list, 'id_short') + return self._find_extra_namespace_set_elements_by_attribute(object_list, search_list, "id_short") - def _find_extra_namespace_set_elements_by_name(self, object_list: model.NamespaceSet, - search_list: model.NamespaceSet) -> Set: + def _find_extra_namespace_set_elements_by_name( + self, object_list: model.NamespaceSet, search_list: model.NamespaceSet + ) -> Set: """ Find extra Extension object that are in object_list but not in search_list @@ -630,7 +674,7 @@ def _find_extra_namespace_set_elements_by_name(self, object_list: model.Namespac :param search_list: List which should be searched :return: Set of elements that are in object_list but not in search_list """ - return self._find_extra_namespace_set_elements_by_attribute(object_list, search_list, 'name') + return self._find_extra_namespace_set_elements_by_attribute(object_list, search_list, "name") def check_operation_equal(self, object_: model.Operation, expected_value: model.Operation): """ @@ -642,9 +686,10 @@ def check_operation_equal(self, object_: model.Operation, expected_value: model. """ self._check_abstract_attributes_submodel_element_equal(object_, expected_value) for input_nss, expected_nss, attr_name in ( - (object_.input_variable, expected_value.input_variable, 'input_variable'), - (object_.output_variable, expected_value.output_variable, 'output_variable'), - (object_.in_output_variable, expected_value.in_output_variable, 'in_output_variable')): + (object_.input_variable, expected_value.input_variable, "input_variable"), + (object_.output_variable, expected_value.output_variable, "output_variable"), + (object_.in_output_variable, expected_value.in_output_variable, "in_output_variable"), + ): self.check_contained_element_length(object_, attr_name, model.SubmodelElement, len(expected_nss)) for var1, var2 in zip(input_nss, expected_nss): self._check_submodel_element(var1, var2) @@ -668,30 +713,33 @@ def check_entity_equal(self, object_: model.Entity, expected_value: model.Entity :return: """ self._check_abstract_attributes_submodel_element_equal(object_, expected_value) - self.check_attribute_equal(object_, 'entity_type', expected_value.entity_type) - self.check_attribute_equal(object_, 'global_asset_id', expected_value.global_asset_id) + self.check_attribute_equal(object_, "entity_type", expected_value.entity_type) + self.check_attribute_equal(object_, "global_asset_id", expected_value.global_asset_id) self._check_specific_asset_ids_equal(object_.specific_asset_id, expected_value.specific_asset_id, object_) - self.check_contained_element_length(object_, 'statement', model.SubmodelElement, len(expected_value.statement)) + self.check_contained_element_length(object_, "statement", model.SubmodelElement, len(expected_value.statement)) for expected_element in expected_value.statement: element = object_.get_referable(expected_element.id_short) - self.check(element is not None, f'Entity {repr(expected_element)} must exist') + self.check(element is not None, f"Entity {repr(expected_element)} must exist") - found_elements = self._find_extra_namespace_set_elements_by_id_short(object_.statement, - expected_value.statement) - self.check(found_elements == set(), f'Entity {repr(object_)} must not have extra statements', - value=found_elements) + found_elements = self._find_extra_namespace_set_elements_by_id_short( + object_.statement, expected_value.statement + ) + self.check( + found_elements == set(), f"Entity {repr(object_)} must not have extra statements", value=found_elements + ) - def _check_specific_asset_ids_equal(self, object_: Iterable[model.SpecificAssetId], - expected_value: Iterable[model.SpecificAssetId], - object_parent): + def _check_specific_asset_ids_equal( + self, object_: Iterable[model.SpecificAssetId], expected_value: Iterable[model.SpecificAssetId], object_parent + ): for expected_pair in expected_value: pair = self._find_specific_asset_id(expected_pair, object_) - if self.check(pair is not None, f'SpecificAssetId {repr(expected_pair)} must exist'): + if self.check(pair is not None, f"SpecificAssetId {repr(expected_pair)} must exist"): self.check_specific_asset_id(pair, expected_pair) # type: ignore found_elements = self._find_extra_object(object_, expected_value, model.SpecificAssetId) - self.check(found_elements == set(), f'{repr(object_parent)} must not have extra specificAssetIds', - value=found_elements) + self.check( + found_elements == set(), f"{repr(object_parent)} must not have extra specificAssetIds", value=found_elements + ) def _check_event_element_equal(self, object_: model.EventElement, expected_value: model.EventElement): """ @@ -703,8 +751,9 @@ def _check_event_element_equal(self, object_: model.EventElement, expected_value """ self._check_abstract_attributes_submodel_element_equal(object_, expected_value) - def check_basic_event_element_equal(self, object_: model.BasicEventElement, - expected_value: model.BasicEventElement): + def check_basic_event_element_equal( + self, object_: model.BasicEventElement, expected_value: model.BasicEventElement + ): """ Checks if the given BasicEventElement objects are equal @@ -714,14 +763,14 @@ def check_basic_event_element_equal(self, object_: model.BasicEventElement, """ self._check_abstract_attributes_submodel_element_equal(object_, expected_value) self._check_event_element_equal(object_, expected_value) - self.check_attribute_equal(object_, 'observed', expected_value.observed) - self.check_attribute_equal(object_, 'direction', expected_value.direction) - self.check_attribute_equal(object_, 'state', expected_value.state) - self.check_attribute_equal(object_, 'message_topic', expected_value.message_topic) - self.check_attribute_equal(object_, 'message_broker', expected_value.message_broker) - self.check_attribute_equal(object_, 'last_update', expected_value.last_update) - self.check_attribute_equal(object_, 'min_interval', expected_value.min_interval) - self.check_attribute_equal(object_, 'max_interval', expected_value.max_interval) + self.check_attribute_equal(object_, "observed", expected_value.observed) + self.check_attribute_equal(object_, "direction", expected_value.direction) + self.check_attribute_equal(object_, "state", expected_value.state) + self.check_attribute_equal(object_, "message_topic", expected_value.message_topic) + self.check_attribute_equal(object_, "message_broker", expected_value.message_broker) + self.check_attribute_equal(object_, "last_update", expected_value.last_update) + self.check_attribute_equal(object_, "min_interval", expected_value.min_interval) + self.check_attribute_equal(object_, "max_interval", expected_value.max_interval) def check_submodel_equal(self, object_: model.Submodel, expected_value: model.Submodel): """ @@ -736,19 +785,24 @@ def check_submodel_equal(self, object_: model.Submodel, expected_value: model.Su self._check_has_kind_equal(object_, expected_value) self._check_qualifiable_equal(object_, expected_value) self._check_has_data_specification_equal(object_, expected_value) - self.check_contained_element_length(object_, 'submodel_element', model.SubmodelElement, - len(expected_value.submodel_element)) + self.check_contained_element_length( + object_, "submodel_element", model.SubmodelElement, len(expected_value.submodel_element) + ) for expected_element in expected_value.submodel_element: try: element = object_.get_referable(expected_element.id_short) self._check_submodel_element(element, expected_element) # type: ignore except KeyError: - self.check(False, 'Submodel Element {} must exist'.format(repr(expected_element))) + self.check(False, "Submodel Element {} must exist".format(repr(expected_element))) - found_elements = self._find_extra_namespace_set_elements_by_id_short(object_.submodel_element, - expected_value.submodel_element) - self.check(found_elements == set(), 'Submodel {} must not have extra submodel elements'.format(repr(object_)), - value=found_elements) + found_elements = self._find_extra_namespace_set_elements_by_id_short( + object_.submodel_element, expected_value.submodel_element + ) + self.check( + found_elements == set(), + "Submodel {} must not have extra submodel elements".format(repr(object_)), + value=found_elements, + ) def _check_qualifier_equal(self, object_: model.Qualifier, expected_value: model.Qualifier): """ @@ -758,14 +812,13 @@ def _check_qualifier_equal(self, object_: model.Qualifier, expected_value: model :param expected_value: expected Qualifier object :return: """ - self.check_attribute_equal(object_, 'type', expected_value.type) - self.check_attribute_equal(object_, 'value_type', expected_value.value_type) - self.check_attribute_equal(object_, 'value', expected_value.value) - self.check_attribute_equal(object_, 'value_id', expected_value.value_id) - self.check_attribute_equal(object_, 'kind', expected_value.kind) + self.check_attribute_equal(object_, "type", expected_value.type) + self.check_attribute_equal(object_, "value_type", expected_value.value_type) + self.check_attribute_equal(object_, "value", expected_value.value) + self.check_attribute_equal(object_, "value_id", expected_value.value_id) + self.check_attribute_equal(object_, "kind", expected_value.kind) - def check_specific_asset_id(self, object_: model.SpecificAssetId, - expected_value: model.SpecificAssetId): + def check_specific_asset_id(self, object_: model.SpecificAssetId, expected_value: model.SpecificAssetId): """ Checks if the given SpecificAssetId objects are equal @@ -786,26 +839,32 @@ def check_asset_information_equal(self, object_: model.AssetInformation, expecte :param expected_value: expected AssetInformation object :return: """ - self.check_attribute_equal(object_, 'asset_kind', expected_value.asset_kind) - self.check_attribute_equal(object_, 'global_asset_id', expected_value.global_asset_id) - self.check_contained_element_length(object_, 'specific_asset_id', model.SpecificAssetId, - len(expected_value.specific_asset_id)) + self.check_attribute_equal(object_, "asset_kind", expected_value.asset_kind) + self.check_attribute_equal(object_, "global_asset_id", expected_value.global_asset_id) + self.check_contained_element_length( + object_, "specific_asset_id", model.SpecificAssetId, len(expected_value.specific_asset_id) + ) self._check_specific_asset_ids_equal(object_.specific_asset_id, expected_value.specific_asset_id, object_) - self.check_attribute_equal(object_, 'asset_type', object_.asset_type) + self.check_attribute_equal(object_, "asset_type", object_.asset_type) if object_.default_thumbnail and expected_value.default_thumbnail: self.check_resource_equal(object_.default_thumbnail, expected_value.default_thumbnail) else: if object_.default_thumbnail: - self.check(expected_value.default_thumbnail is not None, - 'defaultThumbnail object {} must exist'.format(repr(object_.default_thumbnail)), - value=expected_value.default_thumbnail) + self.check( + expected_value.default_thumbnail is not None, + "defaultThumbnail object {} must exist".format(repr(object_.default_thumbnail)), + value=expected_value.default_thumbnail, + ) else: - self.check(expected_value.default_thumbnail is None, '{} must not have a ' - 'defaultThumbnail object'.format(repr(object_)), - value=expected_value.default_thumbnail) - - def check_asset_administration_shell_equal(self, object_: model.AssetAdministrationShell, - expected_value: model.AssetAdministrationShell): + self.check( + expected_value.default_thumbnail is None, + "{} must not have a defaultThumbnail object".format(repr(object_)), + value=expected_value.default_thumbnail, + ) + + def check_asset_administration_shell_equal( + self, object_: model.AssetAdministrationShell, expected_value: model.AssetAdministrationShell + ): """ Checks if the given AssetAdministrationShell objects are equal @@ -817,20 +876,23 @@ def check_asset_administration_shell_equal(self, object_: model.AssetAdministrat self._check_has_data_specification_equal(object_, expected_value) self.check_asset_information_equal(object_.asset_information, expected_value.asset_information) - self.check_attribute_equal(object_, 'derived_from', expected_value.derived_from) - self.check_contained_element_length(object_, 'submodel', model.ModelReference, len(expected_value.submodel)) + self.check_attribute_equal(object_, "derived_from", expected_value.derived_from) + self.check_contained_element_length(object_, "submodel", model.ModelReference, len(expected_value.submodel)) for expected_ref in expected_value.submodel: ref = self._find_reference(expected_ref, object_.submodel) - if self.check(ref is not None, 'Submodel Reference {} must exist'.format(repr(expected_ref))): + if self.check(ref is not None, "Submodel Reference {} must exist".format(repr(expected_ref))): self._check_reference_equal(ref, expected_ref) # type: ignore found_elements = self._find_extra_object(object_.submodel, expected_value.submodel, model.ModelReference) - self.check(found_elements == set(), 'Asset Administration Shell {} must not have extra submodel ' - 'references'.format(repr(object_)), - value=found_elements) - - def check_concept_description_equal(self, object_: model.ConceptDescription, - expected_value: model.ConceptDescription): + self.check( + found_elements == set(), + "Asset Administration Shell {} must not have extra submodel references".format(repr(object_)), + value=found_elements, + ) + + def check_concept_description_equal( + self, object_: model.ConceptDescription, expected_value: model.ConceptDescription + ): """ Checks if the given ConceptDescription objects are equal @@ -840,22 +902,22 @@ def check_concept_description_equal(self, object_: model.ConceptDescription, """ self._check_identifiable_equal(object_, expected_value) self._check_has_data_specification_equal(object_, expected_value) - self.check_contained_element_length(object_, 'is_case_of', model.Reference, - len(expected_value.is_case_of)) + self.check_contained_element_length(object_, "is_case_of", model.Reference, len(expected_value.is_case_of)) for expected_ref in expected_value.is_case_of: ref = self._find_reference(expected_ref, object_.is_case_of) - if self.check(ref is not None, 'Concept Description Reference {} must exist'.format(repr(expected_ref))): + if self.check(ref is not None, "Concept Description Reference {} must exist".format(repr(expected_ref))): self._check_reference_equal(ref, expected_ref) # type: ignore - found_elements = self._find_extra_object(object_.is_case_of, expected_value.is_case_of, - model.ModelReference) - self.check(found_elements == set(), 'Concept Description Reference {} must not have extra ' - 'is case of references'.format(repr(object_)), - value=found_elements) + found_elements = self._find_extra_object(object_.is_case_of, expected_value.is_case_of, model.ModelReference) + self.check( + found_elements == set(), + "Concept Description Reference {} must not have extra is case of references".format(repr(object_)), + value=found_elements, + ) def check_data_specification_content_equal( - self, object_: model.DataSpecificationContent, - expected_value: model.DataSpecificationContent): + self, object_: model.DataSpecificationContent, expected_value: model.DataSpecificationContent + ): """ Checks if the given DataSpecificationContent objects are equal @@ -863,13 +925,16 @@ def check_data_specification_content_equal( :param expected_value: expected DataSpecificationContent object :return: """ - self.check(type(object_) is type(expected_value), "type({}) must be == type({})" - .format(repr(object_), repr(expected_value))) + self.check( + type(object_) is type(expected_value), + "type({}) must be == type({})".format(repr(object_), repr(expected_value)), + ) if isinstance(object_, model.base.DataSpecificationIEC61360): self._check_data_specification_iec61360_equal(object_, expected_value) # type: ignore - def _check_data_specification_iec61360_equal(self, object_: model.base.DataSpecificationIEC61360, - expected_value: model.base.DataSpecificationIEC61360): + def _check_data_specification_iec61360_equal( + self, object_: model.base.DataSpecificationIEC61360, expected_value: model.base.DataSpecificationIEC61360 + ): """ Checks if the given IEC61360ConceptDescription objects are equal @@ -877,27 +942,32 @@ def _check_data_specification_iec61360_equal(self, object_: model.base.DataSpeci :param expected_value: expected IEC61360ConceptDescription object :return: """ - self.check_attribute_equal(object_, 'preferred_name', expected_value.preferred_name) - self.check_attribute_equal(object_, 'short_name', expected_value.short_name) - self.check_attribute_equal(object_, 'data_type', expected_value.data_type) - self.check_attribute_equal(object_, 'definition', expected_value.definition) - self.check_attribute_equal(object_, 'unit', expected_value.unit) - self.check_attribute_equal(object_, 'unit_id', expected_value.unit_id) - self.check_attribute_equal(object_, 'source_of_definition', expected_value.source_of_definition) - self.check_attribute_equal(object_, 'symbol', expected_value.symbol) - self.check_attribute_equal(object_, 'value_format', expected_value.value_format) - self.check_attribute_equal(object_, 'value', expected_value.value) - self.check_attribute_equal(object_, 'level_types', expected_value.level_types) + self.check_attribute_equal(object_, "preferred_name", expected_value.preferred_name) + self.check_attribute_equal(object_, "short_name", expected_value.short_name) + self.check_attribute_equal(object_, "data_type", expected_value.data_type) + self.check_attribute_equal(object_, "definition", expected_value.definition) + self.check_attribute_equal(object_, "unit", expected_value.unit) + self.check_attribute_equal(object_, "unit_id", expected_value.unit_id) + self.check_attribute_equal(object_, "source_of_definition", expected_value.source_of_definition) + self.check_attribute_equal(object_, "symbol", expected_value.symbol) + self.check_attribute_equal(object_, "value_format", expected_value.value_format) + self.check_attribute_equal(object_, "value", expected_value.value) + self.check_attribute_equal(object_, "level_types", expected_value.level_types) if expected_value.value_list is not None: - if self.check(object_.value_list is not None, - "ValueList must contain {} ValueReferencePairs".format(len(expected_value.value_list)), - value=expected_value.value_list): + if self.check( + object_.value_list is not None, + "ValueList must contain {} ValueReferencePairs".format(len(expected_value.value_list)), + value=expected_value.value_list, + ): self._check_value_list_equal(object_.value_list, expected_value.value_list) # type: ignore if object_.value_list is not None: - if self.check(expected_value.value_list is not None, - "ValueList must contain 0 ValueReferencePairs", value=len(object_.value_list)): + if self.check( + expected_value.value_list is not None, + "ValueList must contain 0 ValueReferencePairs", + value=len(object_.value_list), + ): self._check_value_list_equal(object_.value_list, expected_value.value_list) # type: ignore def _check_value_list_equal(self, object_: model.ValueList, expected_value: model.ValueList): @@ -909,18 +979,19 @@ def _check_value_list_equal(self, object_: model.ValueList, expected_value: mode :return: """ for expected_pair in expected_value: - pair = self._find_element_by_attribute(expected_pair, object_, 'value', 'value_id') - self.check(pair is not None, 'ValueReferencePair[value={}, value_id={}] ' - 'must exist'.format(expected_pair.value, expected_pair.value_id)) + pair = self._find_element_by_attribute(expected_pair, object_, "value", "value_id") + self.check( + pair is not None, + "ValueReferencePair[value={}, value_id={}] must exist".format( + expected_pair.value, expected_pair.value_id + ), + ) - found_elements = self._find_extra_elements_by_attribute(object_, expected_value, 'value', 'value_id') - self.check(found_elements == set(), 'ValueList must not have extra ValueReferencePairs', - value=found_elements) + found_elements = self._find_extra_elements_by_attribute(object_, expected_value, "value", "value_id") + self.check(found_elements == set(), "ValueList must not have extra ValueReferencePairs", value=found_elements) def check_identifiable_store( - self, - identifiable_store_1: model.DictIdentifiableStore, - identifiable_store_2: model.DictIdentifiableStore + self, identifiable_store_1: model.DictIdentifiableStore, identifiable_store_2: model.DictIdentifiableStore ): """ Checks if the given object stores are equal @@ -941,7 +1012,7 @@ def check_identifiable_store( elif isinstance(identifiable, model.ConceptDescription): concept_description_list_1.append(identifiable) else: - raise KeyError('Check for {} not implemented'.format(identifiable)) + raise KeyError("Check for {} not implemented".format(identifiable)) # separate different kind of objects submodel_list_2 = [] @@ -955,38 +1026,46 @@ def check_identifiable_store( elif isinstance(identifiable, model.ConceptDescription): concept_description_list_2.append(identifiable) else: - raise KeyError('Check for {} not implemented'.format(identifiable)) + raise KeyError("Check for {} not implemented".format(identifiable)) for shell_2 in shell_list_2: shell_1 = identifiable_store_1.get(shell_2.id) - if self.check(shell_1 is not None, 'Asset administration shell {} must exist in given asset administration' - 'shell list'.format(shell_2)): + if self.check( + shell_1 is not None, + "Asset administration shell {} must exist in given asset administrationshell list".format(shell_2), + ): self.check_asset_administration_shell_equal(shell_1, shell_2) # type: ignore - found_elements = self._find_extra_elements_by_attribute(shell_list_1, shell_list_2, 'id') - self.check(found_elements == set(), 'Given asset administration shell list must not have extra asset ' - 'administration shells', value=found_elements) + found_elements = self._find_extra_elements_by_attribute(shell_list_1, shell_list_2, "id") + self.check( + found_elements == set(), + "Given asset administration shell list must not have extra asset administration shells", + value=found_elements, + ) for submodel_2 in submodel_list_2: submodel_1 = identifiable_store_1.get(submodel_2.id) - if self.check(submodel_1 is not None, 'Submodel {} must exist in given submodel list'.format(submodel_2)): + if self.check(submodel_1 is not None, "Submodel {} must exist in given submodel list".format(submodel_2)): self.check_submodel_equal(submodel_1, submodel_2) # type: ignore - found_elements = self._find_extra_elements_by_attribute(submodel_list_1, submodel_list_2, 'id') - self.check(found_elements == set(), 'Given submodel list must not have extra submodels', - value=found_elements) + found_elements = self._find_extra_elements_by_attribute(submodel_list_1, submodel_list_2, "id") + self.check(found_elements == set(), "Given submodel list must not have extra submodels", value=found_elements) for cd_2 in concept_description_list_2: cd_1 = identifiable_store_1.get(cd_2.id) - if self.check(cd_1 is not None, 'Concept description {} must exist in given concept description ' - 'list'.format(cd_2)): + if self.check( + cd_1 is not None, "Concept description {} must exist in given concept description list".format(cd_2) + ): self.check_concept_description_equal(cd_1, cd_2) # type: ignore - found_elements = self._find_extra_elements_by_attribute(concept_description_list_1, concept_description_list_2, - 'id') - self.check(found_elements == set(), 'Given concept description list must not have extra concept ' - 'descriptions', - value=found_elements) + found_elements = self._find_extra_elements_by_attribute( + concept_description_list_1, concept_description_list_2, "id" + ) + self.check( + found_elements == set(), + "Given concept description list must not have extra concept descriptions", + value=found_elements, + ) def check_attribute_equal(self, object_: object, attribute_name: str, expected_value: object, **kwargs) -> bool: """ @@ -1000,19 +1079,24 @@ def check_attribute_equal(self, object_: object, attribute_name: str, expected_v :return: The value of expression to be used in control statements """ # TODO: going by attribute name here isn't exactly pretty... - if attribute_name in ('value_type', 'value_type_list_element', 'type_value_list_element') \ - and getattr(object_, attribute_name) is not None: + if ( + attribute_name in ("value_type", "value_type_list_element", "type_value_list_element") + and getattr(object_, attribute_name) is not None + ): # value_type_list_element can be None and doesn't have the __name__ attribute in this case - kwargs['value'] = getattr(object_, attribute_name).__name__ - return self.check(getattr(object_, attribute_name) is expected_value, # type:ignore - "Attribute {} of {} must be == {}".format( - attribute_name, repr(object_), expected_value.__name__), # type:ignore - **kwargs) + kwargs["value"] = getattr(object_, attribute_name).__name__ + return self.check( + getattr(object_, attribute_name) is expected_value, # type:ignore + "Attribute {} of {} must be == {}".format(attribute_name, repr(object_), expected_value.__name__), # type:ignore + **kwargs, + ) else: - kwargs['value'] = getattr(object_, attribute_name) - return self.check(getattr(object_, attribute_name) == expected_value, - "Attribute {} of {} must be == {}".format(attribute_name, repr(object_), expected_value), - **kwargs) + kwargs["value"] = getattr(object_, attribute_name) + return self.check( + getattr(object_, attribute_name) == expected_value, + "Attribute {} of {} must be == {}".format(attribute_name, repr(object_), expected_value), + **kwargs, + ) def check_element_in(self, object_: model.Referable, parent: object, **kwargs) -> bool: """ @@ -1023,12 +1107,13 @@ def check_element_in(self, object_: model.Referable, parent: object, **kwargs) - :param kwargs: Relevant values to add to the check result for further analysis (e.g. the compared values) :return: The value of expression to be used in control statements """ - return self.check(object_.parent == parent, - "{} must exist in {}s".format(repr(object_), repr(parent)), - **kwargs) + return self.check( + object_.parent == parent, "{} must exist in {}s".format(repr(object_), repr(parent)), **kwargs + ) - def check_contained_element_length(self, object_: object, attribute_name: str, class_name: Type, - length: int, **kwargs) -> bool: + def check_contained_element_length( + self, object_: object, attribute_name: str, class_name: Type, length: int, **kwargs + ) -> bool: """ Checks if the ``object_`` has ``length`` elements of class ``class_name`` in attribute ``attribute_name`` and adds / stores the check result for later analysis. @@ -1044,11 +1129,12 @@ def check_contained_element_length(self, object_: object, attribute_name: str, c for element in getattr(object_, attribute_name): if isinstance(element, class_name): count = count + 1 - kwargs['count'] = count - return self.check(count == length, - "Attribute {} of {} must contain {} {}s".format(attribute_name, repr(object_), - length, class_name.__name__), - **kwargs) + kwargs["count"] = count + return self.check( + count == length, + "Attribute {} of {} must contain {} {}s".format(attribute_name, repr(object_), length, class_name.__name__), + **kwargs, + ) def check_is_instance(self, object_: object, class_name: Type, **kwargs) -> bool: """ @@ -1059,10 +1145,12 @@ def check_is_instance(self, object_: object, class_name: Type, **kwargs) -> bool :param kwargs: Relevant values to add to the check result for further analysis (e.g. the compared values) :return: The value of expression to be used in control statements """ - kwargs['class'] = object_.__class__.__name__ - return self.check(isinstance(object_, class_name), - "{} must be of class {}".format(repr(object_), class_name.__name__), - **kwargs) + kwargs["class"] = object_.__class__.__name__ + return self.check( + isinstance(object_, class_name), + "{} must be of class {}".format(repr(object_), class_name.__name__), + **kwargs, + ) def check_attribute_is_none(self, object_: object, attribute_name: str, **kwargs) -> bool: """ @@ -1074,7 +1162,9 @@ def check_attribute_is_none(self, object_: object, attribute_name: str, **kwargs :param kwargs: Relevant values to add to the check result for further analysis (e.g. the compared values) :return: The value of expression to be used in control statements """ - kwargs['value'] = getattr(object_, attribute_name) - return self.check(getattr(object_, attribute_name) is None, - "Attribute {} of {} must be None".format(attribute_name, repr(object_)), - **kwargs) + kwargs["value"] = getattr(object_, attribute_name) + return self.check( + getattr(object_, attribute_name) is None, + "Attribute {} of {} must be None".format(attribute_name, repr(object_)), + **kwargs, + ) diff --git a/sdk/basyx/aas/examples/data/example_aas.py b/sdk/basyx/aas/examples/data/example_aas.py index bb080756a..15f8005c7 100644 --- a/sdk/basyx/aas/examples/data/example_aas.py +++ b/sdk/basyx/aas/examples/data/example_aas.py @@ -11,6 +11,7 @@ To get this object store use the function :meth:`~basyx.aas.examples.data.example_aas.create_full_example`. If you want to get single example objects or want to get more information use the other functions. """ + import datetime import logging @@ -21,29 +22,56 @@ _embedded_data_specification_iec61360 = model.EmbeddedDataSpecification( - data_specification=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='https://admin-shell.io/DataSpecificationTemplates/' - 'DataSpecificationIEC61360/3/1'),)), - data_specification_content=model.DataSpecificationIEC61360(preferred_name=model.PreferredNameTypeIEC61360({ - 'de': 'Test Specification', - 'en-US': 'TestSpecification' - }), data_type=model.DataTypeIEC61360.REAL_MEASURE, - definition=model.DefinitionTypeIEC61360({'de': 'Dies ist eine Data Specification für Testzwecke', - 'en-US': 'This is a DataSpecification for testing purposes'}), - short_name=model.ShortNameTypeIEC61360({'de': 'Test Spec', 'en-US': 'TestSpec'}), unit='SpaceUnit', - unit_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/Units/SpaceUnit'),)), - source_of_definition='http://example.org/DataSpec/ExampleDef', symbol='SU', value_format="M", + data_specification=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="https://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/1", + ), + ) + ), + data_specification_content=model.DataSpecificationIEC61360( + preferred_name=model.PreferredNameTypeIEC61360({"de": "Test Specification", "en-US": "TestSpecification"}), + data_type=model.DataTypeIEC61360.REAL_MEASURE, + definition=model.DefinitionTypeIEC61360( + { + "de": "Dies ist eine Data Specification für Testzwecke", + "en-US": "This is a DataSpecification for testing purposes", + } + ), + short_name=model.ShortNameTypeIEC61360({"de": "Test Spec", "en-US": "TestSpec"}), + unit="SpaceUnit", + unit_id=model.ExternalReference( + (model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, value="http://example.org/Units/SpaceUnit"),) + ), + source_of_definition="http://example.org/DataSpec/ExampleDef", + symbol="SU", + value_format="M", value_list={ model.ValueReferencePair( - value='exampleValue', - value_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/ValueId/ExampleValueId'),)), ), + value="exampleValue", + value_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, value="http://example.org/ValueId/ExampleValueId" + ), + ) + ), + ), model.ValueReferencePair( - value='exampleValue2', - value_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/ValueId/ExampleValueId2'),)), )}, - value="TEST", level_types={model.IEC61360LevelType.MIN, model.IEC61360LevelType.MAX}) + value="exampleValue2", + value_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, value="http://example.org/ValueId/ExampleValueId2" + ), + ) + ), + ), + }, + value="TEST", + level_types={model.IEC61360LevelType.MIN, model.IEC61360LevelType.MAX}, + ), ) @@ -74,125 +102,163 @@ def create_example_asset_identification_submodel() -> model.Submodel: """ qualifier = model.Qualifier( - type_='http://example.org/Qualifier/ExampleQualifier', + type_="http://example.org/Qualifier/ExampleQualifier", value_type=model.datatypes.Int, value=100, - value_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/ValueId/ExampleValueId'),)), - kind=model.QualifierKind.CONCEPT_QUALIFIER) + value_id=model.ExternalReference( + (model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, value="http://example.org/ValueId/ExampleValueId"),) + ), + kind=model.QualifierKind.CONCEPT_QUALIFIER, + ) qualifier2 = model.Qualifier( - type_='http://example.org/Qualifier/ExampleQualifier2', + type_="http://example.org/Qualifier/ExampleQualifier2", value_type=model.datatypes.Int, value=50, - value_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/ValueId/ExampleValueId'),)), - kind=model.QualifierKind.TEMPLATE_QUALIFIER) + value_id=model.ExternalReference( + (model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, value="http://example.org/ValueId/ExampleValueId"),) + ), + kind=model.QualifierKind.TEMPLATE_QUALIFIER, + ) qualifier3 = model.Qualifier( - type_='http://example.org/Qualifier/ExampleQualifier3', + type_="http://example.org/Qualifier/ExampleQualifier3", value_type=model.datatypes.DateTime, value=model.datatypes.DateTime(2023, 4, 7, 16, 59, 54, 870123), - value_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/ValueId/ExampleValueId'),)), - kind=model.QualifierKind.VALUE_QUALIFIER) + value_id=model.ExternalReference( + (model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, value="http://example.org/ValueId/ExampleValueId"),) + ), + kind=model.QualifierKind.VALUE_QUALIFIER, + ) extension = model.Extension( - name='ExampleExtension', + name="ExampleExtension", value_type=model.datatypes.String, value="ExampleExtensionValue", - refers_to=[model.ModelReference((model.Key(type_=model.KeyTypes.ASSET_ADMINISTRATION_SHELL, - value='http://example.org/RefersTo/ExampleRefersTo'),), - model.AssetAdministrationShell)],) + refers_to=[ + model.ModelReference( + ( + model.Key( + type_=model.KeyTypes.ASSET_ADMINISTRATION_SHELL, + value="http://example.org/RefersTo/ExampleRefersTo", + ), + ), + model.AssetAdministrationShell, + ) + ], + ) # Property-Element conform to 'Verwaltungsschale in der Praxis' page 41 ManufacturerName: # https://www.plattform-i40.de/PI40/Redaktion/DE/Downloads/Publikation/2019-verwaltungsschale-in-der-praxis.html identification_submodel_element_manufacturer_name = model.Property( - id_short='ManufacturerName', + id_short="ManufacturerName", value_type=model.datatypes.String, - value='ACPLT', - value_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/ValueId/ExampleValueId'),)), + value="ACPLT", + value_id=model.ExternalReference( + (model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, value="http://example.org/ValueId/ExampleValueId"),) + ), category="PARAMETER", - description=model.MultiLanguageTextType({ - 'en-US': 'Legally valid designation of the natural or judicial person which ' - 'is directly responsible for the design, production, packaging and ' - 'labeling of a product in respect to its being brought into ' - 'circulation.', - 'de': 'Bezeichnung für eine natürliche oder juristische Person, die für die ' - 'Auslegung, Herstellung und Verpackung sowie die Etikettierung eines ' - 'Produkts im Hinblick auf das \'Inverkehrbringen\' im eigenen Namen ' - 'verantwortlich ist' - }), + description=model.MultiLanguageTextType( + { + "en-US": "Legally valid designation of the natural or judicial person which " + "is directly responsible for the design, production, packaging and " + "labeling of a product in respect to its being brought into " + "circulation.", + "de": "Bezeichnung für eine natürliche oder juristische Person, die für die " + "Auslegung, Herstellung und Verpackung sowie die Etikettierung eines " + "Produkts im Hinblick auf das 'Inverkehrbringen' im eigenen Namen " + "verantwortlich ist", + } + ), parent=None, - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='0173-1#02-AAO677#002'),)), + semantic_id=model.ExternalReference( + (model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, value="0173-1#02-AAO677#002"),) + ), qualifier={qualifier, qualifier2}, extension={extension}, supplemental_semantic_id=(), - embedded_data_specifications=() + embedded_data_specifications=(), ) # Property-Element conform to 'Verwaltungsschale in der Praxis' page 44 InstanceId: # https://www.plattform-i40.de/PI40/Redaktion/DE/Downloads/Publikation/2019-verwaltungsschale-in-der-praxis.html identification_submodel_element_instance_id = model.Property( - id_short='InstanceId', + id_short="InstanceId", value_type=model.datatypes.String, - value='978-8234-234-342', - value_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/ValueId/ExampleValueId'),)), + value="978-8234-234-342", + value_id=model.ExternalReference( + (model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, value="http://example.org/ValueId/ExampleValueId"),) + ), category="PARAMETER", - description=model.MultiLanguageTextType({ - 'en-US': 'Legally valid designation of the natural or judicial person which ' - 'is directly responsible for the design, production, packaging and ' - 'labeling of a product in respect to its being brought into ' - 'circulation.', - 'de': 'Bezeichnung für eine natürliche oder juristische Person, die für die ' - 'Auslegung, Herstellung und Verpackung sowie die Etikettierung eines ' - 'Produkts im Hinblick auf das \'Inverkehrbringen\' im eigenen Namen ' - 'verantwortlich ist' - }), + description=model.MultiLanguageTextType( + { + "en-US": "Legally valid designation of the natural or judicial person which " + "is directly responsible for the design, production, packaging and " + "labeling of a product in respect to its being brought into " + "circulation.", + "de": "Bezeichnung für eine natürliche oder juristische Person, die für die " + "Auslegung, Herstellung und Verpackung sowie die Etikettierung eines " + "Produkts im Hinblick auf das 'Inverkehrbringen' im eigenen Namen " + "verantwortlich ist", + } + ), parent=None, - semantic_id=model.ExternalReference((model.Key( - type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://opcfoundation.org/UA/DI/1.1/DeviceType/Serialnumber' - ),)), + semantic_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://opcfoundation.org/UA/DI/1.1/DeviceType/Serialnumber", + ), + ) + ), qualifier={qualifier3}, extension=(), supplemental_semantic_id=(), - embedded_data_specifications=() + embedded_data_specifications=(), ) # asset identification submodel which will be included in the asset object identification_submodel = model.Submodel( - id_='http://example.org/Submodels/Assets/TestAsset/Identification', - submodel_element=(identification_submodel_element_manufacturer_name, - identification_submodel_element_instance_id), - id_short='Identification', + id_="http://example.org/Submodels/Assets/TestAsset/Identification", + submodel_element=( + identification_submodel_element_manufacturer_name, + identification_submodel_element_instance_id, + ), + id_short="Identification", category=None, - description=model.MultiLanguageTextType({ - 'en-US': 'An example asset identification submodel for the test application', - 'de': 'Ein Beispiel-Identifikations-Submodel für eine Test-Anwendung' - }), + description=model.MultiLanguageTextType( + { + "en-US": "An example asset identification submodel for the test application", + "de": "Ein Beispiel-Identifikations-Submodel für eine Test-Anwendung", + } + ), parent=None, - administration=model.AdministrativeInformation(version='9', - revision='0', - creator=model.ExternalReference(( - model.Key(model.KeyTypes.GLOBAL_REFERENCE, - 'http://example.org/AdministrativeInformation/' - 'TestAsset/Identification'), - )), - template_id='http://example.org/AdministrativeInformation' - 'Templates/TestAsset/Identification'), - semantic_id=model.ModelReference((model.Key(type_=model.KeyTypes.SUBMODEL, - value='http://example.org/SubmodelTemplates/' - 'AssetIdentification'),), - model.Submodel), + administration=model.AdministrativeInformation( + version="9", + revision="0", + creator=model.ExternalReference( + ( + model.Key( + model.KeyTypes.GLOBAL_REFERENCE, + "http://example.org/AdministrativeInformation/TestAsset/Identification", + ), + ) + ), + template_id="http://example.org/AdministrativeInformationTemplates/TestAsset/Identification", + ), + semantic_id=model.ModelReference( + ( + model.Key( + type_=model.KeyTypes.SUBMODEL, value="http://example.org/SubmodelTemplates/AssetIdentification" + ), + ), + model.Submodel, + ), qualifier=(), kind=model.ModellingKind.INSTANCE, extension=(), supplemental_semantic_id=(), - embedded_data_specifications=() + embedded_data_specifications=(), ) return identification_submodel @@ -205,125 +271,148 @@ def create_example_bill_of_material_submodel() -> model.Submodel: :return: example bill of material submodel """ submodel_element_property = model.Property( - id_short='ExampleProperty', + id_short="ExampleProperty", value_type=model.datatypes.String, - value='exampleValue', - value_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/ValueId/ExampleValueId'),)), - category='CONSTANT', - description=model.MultiLanguageTextType({'en-US': 'Example Property object', - 'de': 'Beispiel Property Element'}), + value="exampleValue", + value_id=model.ExternalReference( + (model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, value="http://example.org/ValueId/ExampleValueId"),) + ), + category="CONSTANT", + description=model.MultiLanguageTextType( + {"en-US": "Example Property object", "de": "Beispiel Property Element"} + ), parent=None, - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/Properties/ExampleProperty'),)), + semantic_id=model.ExternalReference( + (model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, value="http://example.org/Properties/ExampleProperty"),) + ), qualifier=(), extension=(), supplemental_semantic_id=(), - embedded_data_specifications=() + embedded_data_specifications=(), ) submodel_element_property2 = model.Property( - id_short='ExampleProperty2', + id_short="ExampleProperty2", value_type=model.datatypes.String, - value='exampleValue2', - value_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/ValueId/ExampleValueId'),)), - category='CONSTANT', - description=model.MultiLanguageTextType({'en-US': 'Example Property object', - 'de': 'Beispiel Property Element'}), + value="exampleValue2", + value_id=model.ExternalReference( + (model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, value="http://example.org/ValueId/ExampleValueId"),) + ), + category="CONSTANT", + description=model.MultiLanguageTextType( + {"en-US": "Example Property object", "de": "Beispiel Property Element"} + ), parent=None, - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/Properties/ExampleProperty'),)), + semantic_id=model.ExternalReference( + (model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, value="http://example.org/Properties/ExampleProperty"),) + ), qualifier=(), extension=(), supplemental_semantic_id=(), - embedded_data_specifications=() + embedded_data_specifications=(), ) entity = model.Entity( - id_short='ExampleEntity', + id_short="ExampleEntity", entity_type=model.EntityType.SELF_MANAGED_ENTITY, statement={submodel_element_property, submodel_element_property2}, - global_asset_id='http://example.org/TestAsset/', + global_asset_id="http://example.org/TestAsset/", specific_asset_id={ - model.SpecificAssetId(name="TestKey", value="TestValue", - external_subject_id=model.ExternalReference( - (model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/SpecificAssetId/'),)) - )}, + model.SpecificAssetId( + name="TestKey", + value="TestValue", + external_subject_id=model.ExternalReference( + (model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, value="http://example.org/SpecificAssetId/"),) + ), + ) + }, category="PARAMETER", - description=model.MultiLanguageTextType({ - 'en-US': 'Legally valid designation of the natural or judicial person which ' - 'is directly responsible for the design, production, packaging and ' - 'labeling of a product in respect to its being brought into ' - 'circulation.', - 'de': 'Bezeichnung für eine natürliche oder juristische Person, die für die ' - 'Auslegung, Herstellung und Verpackung sowie die Etikettierung eines ' - 'Produkts im Hinblick auf das \'Inverkehrbringen\' im eigenen Namen ' - 'verantwortlich ist' - }), + description=model.MultiLanguageTextType( + { + "en-US": "Legally valid designation of the natural or judicial person which " + "is directly responsible for the design, production, packaging and " + "labeling of a product in respect to its being brought into " + "circulation.", + "de": "Bezeichnung für eine natürliche oder juristische Person, die für die " + "Auslegung, Herstellung und Verpackung sowie die Etikettierung eines " + "Produkts im Hinblick auf das 'Inverkehrbringen' im eigenen Namen " + "verantwortlich ist", + } + ), parent=None, - semantic_id=model.ExternalReference((model.Key( - type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://opcfoundation.org/UA/DI/1.1/DeviceType/Serialnumber' - ),)), + semantic_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://opcfoundation.org/UA/DI/1.1/DeviceType/Serialnumber", + ), + ) + ), qualifier=(), extension=(), supplemental_semantic_id=(), - embedded_data_specifications=() + embedded_data_specifications=(), ) entity_2 = model.Entity( - id_short='ExampleEntity2', + id_short="ExampleEntity2", entity_type=model.EntityType.CO_MANAGED_ENTITY, statement=(), global_asset_id=None, specific_asset_id=(), category="PARAMETER", - description=model.MultiLanguageTextType({ - 'en-US': 'Legally valid designation of the natural or judicial person which ' - 'is directly responsible for the design, production, packaging and ' - 'labeling of a product in respect to its being brought into ' - 'circulation.', - 'de': 'Bezeichnung für eine natürliche oder juristische Person, die für die ' - 'Auslegung, Herstellung und Verpackung sowie die Etikettierung eines ' - 'Produkts im Hinblick auf das \'Inverkehrbringen\' im eigenen Namen ' - 'verantwortlich ist' - }), + description=model.MultiLanguageTextType( + { + "en-US": "Legally valid designation of the natural or judicial person which " + "is directly responsible for the design, production, packaging and " + "labeling of a product in respect to its being brought into " + "circulation.", + "de": "Bezeichnung für eine natürliche oder juristische Person, die für die " + "Auslegung, Herstellung und Verpackung sowie die Etikettierung eines " + "Produkts im Hinblick auf das 'Inverkehrbringen' im eigenen Namen " + "verantwortlich ist", + } + ), parent=None, - semantic_id=model.ExternalReference((model.Key( - type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://opcfoundation.org/UA/DI/1.1/DeviceType/Serialnumber' - ),)), + semantic_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://opcfoundation.org/UA/DI/1.1/DeviceType/Serialnumber", + ), + ) + ), qualifier=(), extension=(), supplemental_semantic_id=(), - embedded_data_specifications=() + embedded_data_specifications=(), ) # bill of material submodel which will be included in the asset object bill_of_material = model.Submodel( - id_='http://example.org/Submodels/Assets/TestAsset/BillOfMaterial', - submodel_element=(entity, - entity_2), - id_short='BillOfMaterial', + id_="http://example.org/Submodels/Assets/TestAsset/BillOfMaterial", + submodel_element=(entity, entity_2), + id_short="BillOfMaterial", category=None, - description=model.MultiLanguageTextType({ - 'en-US': 'An example bill of material submodel for the test application', - 'de': 'Ein Beispiel-BillOfMaterial-Submodel für eine Test-Anwendung' - }), + description=model.MultiLanguageTextType( + { + "en-US": "An example bill of material submodel for the test application", + "de": "Ein Beispiel-BillOfMaterial-Submodel für eine Test-Anwendung", + } + ), parent=None, - administration=model.AdministrativeInformation(version='9', - template_id='http://example.org/AdministrativeInformation' - 'Templates/TestAsset/BillOfMaterial'), - semantic_id=model.ModelReference((model.Key(type_=model.KeyTypes.SUBMODEL, - value='http://example.org/SubmodelTemplates/BillOfMaterial'),), - model.Submodel), + administration=model.AdministrativeInformation( + version="9", template_id="http://example.org/AdministrativeInformationTemplates/TestAsset/BillOfMaterial" + ), + semantic_id=model.ModelReference( + (model.Key(type_=model.KeyTypes.SUBMODEL, value="http://example.org/SubmodelTemplates/BillOfMaterial"),), + model.Submodel, + ), qualifier=(), kind=model.ModellingKind.INSTANCE, extension=(), supplemental_semantic_id=(), - embedded_data_specifications=() + embedded_data_specifications=(), ) return bill_of_material @@ -339,422 +428,556 @@ def create_example_submodel() -> model.Submodel: submodel_element_property = model.Property( id_short=None, value_type=model.datatypes.String, - value='exampleValue', - value_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/ValueId/ExampleValueId'),)), - display_name=model.MultiLanguageNameType({'en-US': 'ExampleProperty', - 'de': 'BeispielProperty'}), - category='CONSTANT', - description=model.MultiLanguageTextType({'en-US': 'Example Property object', - 'de': 'Beispiel Property Element'}), + value="exampleValue", + value_id=model.ExternalReference( + (model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, value="http://example.org/ValueId/ExampleValueId"),) + ), + display_name=model.MultiLanguageNameType({"en-US": "ExampleProperty", "de": "BeispielProperty"}), + category="CONSTANT", + description=model.MultiLanguageTextType( + {"en-US": "Example Property object", "de": "Beispiel Property Element"} + ), parent=None, - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/Properties/ExampleProperty'),), ), + semantic_id=model.ExternalReference( + (model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, value="http://example.org/Properties/ExampleProperty"),), + ), qualifier=(), extension=(), - supplemental_semantic_id=(model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/Properties/' - 'ExampleProperty/SupplementalId1'),)), - model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/Properties/' - 'ExampleProperty/SupplementalId2'),))), - embedded_data_specifications=(_embedded_data_specification_iec61360,)) + supplemental_semantic_id=( + model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/Properties/ExampleProperty/SupplementalId1", + ), + ) + ), + model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/Properties/ExampleProperty/SupplementalId2", + ), + ) + ), + ), + embedded_data_specifications=(_embedded_data_specification_iec61360,), + ) submodel_element_property_2 = model.Property( id_short=None, value_type=model.datatypes.String, - value='exampleValue', - value_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/ValueId/ExampleValueId'),)), - display_name=model.MultiLanguageNameType({'en-US': 'ExampleProperty', - 'de': 'BeispielProperty'}), - category='CONSTANT', - description=model.MultiLanguageTextType({'en-US': 'Example Property object', - 'de': 'Beispiel Property Element'}), + value="exampleValue", + value_id=model.ExternalReference( + (model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, value="http://example.org/ValueId/ExampleValueId"),) + ), + display_name=model.MultiLanguageNameType({"en-US": "ExampleProperty", "de": "BeispielProperty"}), + category="CONSTANT", + description=model.MultiLanguageTextType( + {"en-US": "Example Property object", "de": "Beispiel Property Element"} + ), parent=None, - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/Properties/ExampleProperty'),)), + semantic_id=model.ExternalReference( + (model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, value="http://example.org/Properties/ExampleProperty"),) + ), qualifier=(), extension=(), - supplemental_semantic_id=(model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/Properties/' - 'ExampleProperty2/SupplementalId'),)),), - embedded_data_specifications=() + supplemental_semantic_id=( + model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/Properties/ExampleProperty2/SupplementalId", + ), + ) + ), + ), + embedded_data_specifications=(), ) submodel_element_multi_language_property = model.MultiLanguageProperty( - id_short='ExampleMultiLanguageProperty', - value=model.MultiLanguageTextType({'en-US': 'Example value of a MultiLanguageProperty element', - 'de': 'Beispielwert für ein MultiLanguageProperty-Element'}), - value_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/ValueId/ExampleMultiLanguageValueId'),)), - category='CONSTANT', - description=model.MultiLanguageTextType({'en-US': 'Example MultiLanguageProperty object', - 'de': 'Beispiel MultiLanguageProperty Element'}), + id_short="ExampleMultiLanguageProperty", + value=model.MultiLanguageTextType( + { + "en-US": "Example value of a MultiLanguageProperty element", + "de": "Beispielwert für ein MultiLanguageProperty-Element", + } + ), + value_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/ValueId/ExampleMultiLanguageValueId", + ), + ) + ), + category="CONSTANT", + description=model.MultiLanguageTextType( + {"en-US": "Example MultiLanguageProperty object", "de": "Beispiel MultiLanguageProperty Element"} + ), parent=None, - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/MultiLanguageProperties/' - 'ExampleMultiLanguageProperty'),), - referred_semantic_id=model.ExternalReference((model.Key( - type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/Properties/ExampleProperty/Referred'),))), + semantic_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/MultiLanguageProperties/ExampleMultiLanguageProperty", + ), + ), + referred_semantic_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/Properties/ExampleProperty/Referred", + ), + ) + ), + ), qualifier=(), extension=(), supplemental_semantic_id=(), - embedded_data_specifications=() + embedded_data_specifications=(), ) submodel_element_range = model.Range( - id_short='ExampleRange', + id_short="ExampleRange", value_type=model.datatypes.Int, min=0, max=100, - category='PARAMETER', - description=model.MultiLanguageTextType({'en-US': 'Example Range object', - 'de': 'Beispiel Range Element'}), + category="PARAMETER", + description=model.MultiLanguageTextType({"en-US": "Example Range object", "de": "Beispiel Range Element"}), parent=None, - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/Ranges/ExampleRange'),)), + semantic_id=model.ExternalReference( + (model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, value="http://example.org/Ranges/ExampleRange"),) + ), qualifier=(), extension=(), supplemental_semantic_id=(), - embedded_data_specifications=() + embedded_data_specifications=(), ) submodel_element_blob = model.Blob( - id_short='ExampleBlob', - content_type='application/pdf', - value=bytes(b'\x01\x02\x03\x04\x05'), - category='PARAMETER', - description=model.MultiLanguageTextType({'en-US': 'Example Blob object', - 'de': 'Beispiel Blob Element'}), + id_short="ExampleBlob", + content_type="application/pdf", + value=bytes(b"\x01\x02\x03\x04\x05"), + category="PARAMETER", + description=model.MultiLanguageTextType({"en-US": "Example Blob object", "de": "Beispiel Blob Element"}), parent=None, - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/Blobs/ExampleBlob'),)), + semantic_id=model.ExternalReference( + (model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, value="http://example.org/Blobs/ExampleBlob"),) + ), qualifier=(), extension=(), supplemental_semantic_id=(), - embedded_data_specifications=() + embedded_data_specifications=(), ) submodel_element_file = model.File( - id_short='ExampleFile', - content_type='application/pdf', - value='/TestFile.pdf', - category='PARAMETER', - description=model.MultiLanguageTextType({'en-US': 'Example File object', - 'de': 'Beispiel File Element'}), + id_short="ExampleFile", + content_type="application/pdf", + value="/TestFile.pdf", + category="PARAMETER", + description=model.MultiLanguageTextType({"en-US": "Example File object", "de": "Beispiel File Element"}), parent=None, - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/Files/ExampleFile'),)), + semantic_id=model.ExternalReference( + (model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, value="http://example.org/Files/ExampleFile"),) + ), qualifier=(), extension=(), supplemental_semantic_id=(), - embedded_data_specifications=() + embedded_data_specifications=(), ) submodel_element_file_uri = model.File( - id_short='ExampleFileURI', - content_type='application/pdf', - value='https://www.plattform-i40.de/PI40/Redaktion/DE/Downloads/Publikation/Details-of-the-Asset-' - 'Administration-Shell-Part1.pdf?__blob=publicationFile&v=5', - category='CONSTANT', - description=model.MultiLanguageTextType({ - 'en-US': 'Details of the Asset Administration Shell — An example for an external file reference', - 'de': 'Details of the Asset Administration Shell – Ein Beispiel für eine extern referenzierte Datei' - }), + id_short="ExampleFileURI", + content_type="application/pdf", + value="https://www.plattform-i40.de/PI40/Redaktion/DE/Downloads/Publikation/Details-of-the-Asset-" + "Administration-Shell-Part1.pdf?__blob=publicationFile&v=5", + category="CONSTANT", + description=model.MultiLanguageTextType( + { + "en-US": "Details of the Asset Administration Shell — An example for an external file reference", + "de": "Details of the Asset Administration Shell – Ein Beispiel für eine extern referenzierte Datei", + } + ), parent=None, - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/Files/ExampleFile'),)), + semantic_id=model.ExternalReference( + (model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, value="http://example.org/Files/ExampleFile"),) + ), qualifier=(), extension=(), supplemental_semantic_id=(), - embedded_data_specifications=() + embedded_data_specifications=(), ) submodel_element_reference_element = model.ReferenceElement( - id_short='ExampleReferenceElement', - value=model.ModelReference((model.Key(type_=model.KeyTypes.SUBMODEL, - value='http://example.org/Test_Submodel'), - model.Key(type_=model.KeyTypes.PROPERTY, - value='ExampleProperty'),), - model.Property), - category='PARAMETER', - description=model.MultiLanguageTextType({'en-US': 'Example Reference Element object', - 'de': 'Beispiel Reference Element Element'}), + id_short="ExampleReferenceElement", + value=model.ModelReference( + ( + model.Key(type_=model.KeyTypes.SUBMODEL, value="http://example.org/Test_Submodel"), + model.Key(type_=model.KeyTypes.PROPERTY, value="ExampleProperty"), + ), + model.Property, + ), + category="PARAMETER", + description=model.MultiLanguageTextType( + {"en-US": "Example Reference Element object", "de": "Beispiel Reference Element Element"} + ), parent=None, - semantic_id=model.ExternalReference((model.Key( - type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/ReferenceElements/ExampleReferenceElement' - ),)), + semantic_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/ReferenceElements/ExampleReferenceElement", + ), + ) + ), qualifier=(), extension=(), supplemental_semantic_id=(), - embedded_data_specifications=() + embedded_data_specifications=(), ) submodel_element_relationship_element = model.RelationshipElement( - id_short='ExampleRelationshipElement', - first=model.ModelReference(( - model.Key( - type_=model.KeyTypes.SUBMODEL, - value='http://example.org/Test_Submodel'), - model.Key( - type_=model.KeyTypes.PROPERTY, - value='ExampleProperty'), - ), model.Property), - second=model.ModelReference(( - model.Key( - type_=model.KeyTypes.SUBMODEL, - value='http://example.org/Test_Submodel'), - model.Key( - type_=model.KeyTypes.PROPERTY, - value='ExampleProperty2'), - ), model.Property), - category='PARAMETER', - description=model.MultiLanguageTextType({'en-US': 'Example RelationshipElement object', - 'de': 'Beispiel RelationshipElement Element'}), + id_short="ExampleRelationshipElement", + first=model.ModelReference( + ( + model.Key(type_=model.KeyTypes.SUBMODEL, value="http://example.org/Test_Submodel"), + model.Key(type_=model.KeyTypes.PROPERTY, value="ExampleProperty"), + ), + model.Property, + ), + second=model.ModelReference( + ( + model.Key(type_=model.KeyTypes.SUBMODEL, value="http://example.org/Test_Submodel"), + model.Key(type_=model.KeyTypes.PROPERTY, value="ExampleProperty2"), + ), + model.Property, + ), + category="PARAMETER", + description=model.MultiLanguageTextType( + {"en-US": "Example RelationshipElement object", "de": "Beispiel RelationshipElement Element"} + ), parent=None, - semantic_id=model.ModelReference((model.Key(type_=model.KeyTypes.CONCEPT_DESCRIPTION, - value='https://example.org/Test_ConceptDescription'),), - model.ConceptDescription), + semantic_id=model.ModelReference( + (model.Key(type_=model.KeyTypes.CONCEPT_DESCRIPTION, value="https://example.org/Test_ConceptDescription"),), + model.ConceptDescription, + ), qualifier=(), extension=(), supplemental_semantic_id=(), - embedded_data_specifications=() + embedded_data_specifications=(), ) submodel_element_annotated_relationship_element = model.AnnotatedRelationshipElement( - id_short='ExampleAnnotatedRelationshipElement', - first=model.ModelReference((model.Key(type_=model.KeyTypes.SUBMODEL, value='http://example.org/Test_Submodel'), - model.Key(type_=model.KeyTypes.PROPERTY, - value='ExampleProperty'),), - model.Property), - second=model.ModelReference((model.Key(type_=model.KeyTypes.SUBMODEL, value='http://example.org/Test_Submodel'), - model.Key(type_=model.KeyTypes.PROPERTY, - value='ExampleProperty2'),), - model.Property), - annotation={model.Property(id_short="ExampleAnnotatedProperty", - value_type=model.datatypes.String, - value='exampleValue', - category="PARAMETER", - parent=None), - model.Range(id_short="ExampleAnnotatedRange", - value_type=model.datatypes.Integer, - min=1, - max=5, - category="PARAMETER", - parent=None) - }, - category='PARAMETER', - description=model.MultiLanguageTextType({'en-US': 'Example AnnotatedRelationshipElement object', - 'de': 'Beispiel AnnotatedRelationshipElement Element'}), + id_short="ExampleAnnotatedRelationshipElement", + first=model.ModelReference( + ( + model.Key(type_=model.KeyTypes.SUBMODEL, value="http://example.org/Test_Submodel"), + model.Key(type_=model.KeyTypes.PROPERTY, value="ExampleProperty"), + ), + model.Property, + ), + second=model.ModelReference( + ( + model.Key(type_=model.KeyTypes.SUBMODEL, value="http://example.org/Test_Submodel"), + model.Key(type_=model.KeyTypes.PROPERTY, value="ExampleProperty2"), + ), + model.Property, + ), + annotation={ + model.Property( + id_short="ExampleAnnotatedProperty", + value_type=model.datatypes.String, + value="exampleValue", + category="PARAMETER", + parent=None, + ), + model.Range( + id_short="ExampleAnnotatedRange", + value_type=model.datatypes.Integer, + min=1, + max=5, + category="PARAMETER", + parent=None, + ), + }, + category="PARAMETER", + description=model.MultiLanguageTextType( + { + "en-US": "Example AnnotatedRelationshipElement object", + "de": "Beispiel AnnotatedRelationshipElement Element", + } + ), parent=None, - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/RelationshipElements/' - 'ExampleAnnotatedRelationshipElement'),)), + semantic_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/RelationshipElements/ExampleAnnotatedRelationshipElement", + ), + ) + ), qualifier=(), extension=(), supplemental_semantic_id=(), - embedded_data_specifications=() + embedded_data_specifications=(), ) input_variable_property = model.Property( - id_short='ExamplePropertyInput', + id_short="ExamplePropertyInput", value_type=model.datatypes.String, - value='exampleValue', - value_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/ValueId/ExampleValueId'),)), - display_name=model.MultiLanguageNameType({'en-US': 'ExampleProperty', - 'de': 'BeispielProperty'}), - category='CONSTANT', - description=model.MultiLanguageTextType({'en-US': 'Example Property object', - 'de': 'Beispiel Property Element'}), + value="exampleValue", + value_id=model.ExternalReference( + (model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, value="http://example.org/ValueId/ExampleValueId"),) + ), + display_name=model.MultiLanguageNameType({"en-US": "ExampleProperty", "de": "BeispielProperty"}), + category="CONSTANT", + description=model.MultiLanguageTextType( + {"en-US": "Example Property object", "de": "Beispiel Property Element"} + ), parent=None, - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/Properties/ExamplePropertyInput'),)), + semantic_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, value="http://example.org/Properties/ExamplePropertyInput" + ), + ) + ), qualifier=(), extension=(), supplemental_semantic_id=(), - embedded_data_specifications=() + embedded_data_specifications=(), ) output_variable_property = model.Property( - id_short='ExamplePropertyOutput', + id_short="ExamplePropertyOutput", value_type=model.datatypes.String, - value='exampleValue', - value_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/ValueId/ExampleValueId'),)), - display_name=model.MultiLanguageNameType({'en-US': 'ExampleProperty', - 'de': 'BeispielProperty'}), - category='CONSTANT', - description=model.MultiLanguageTextType({'en-US': 'Example Property object', - 'de': 'Beispiel Property Element'}), + value="exampleValue", + value_id=model.ExternalReference( + (model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, value="http://example.org/ValueId/ExampleValueId"),) + ), + display_name=model.MultiLanguageNameType({"en-US": "ExampleProperty", "de": "BeispielProperty"}), + category="CONSTANT", + description=model.MultiLanguageTextType( + {"en-US": "Example Property object", "de": "Beispiel Property Element"} + ), parent=None, - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/Properties/ExamplePropertyOutput'),)), + semantic_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, value="http://example.org/Properties/ExamplePropertyOutput" + ), + ) + ), qualifier=(), extension=(), supplemental_semantic_id=(), - embedded_data_specifications=() + embedded_data_specifications=(), ) in_output_variable_property = model.Property( - id_short='ExamplePropertyInOutput', + id_short="ExamplePropertyInOutput", value_type=model.datatypes.String, - value='exampleValue', - value_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/ValueId/ExampleValueId'),)), - display_name=model.MultiLanguageNameType({'en-US': 'ExampleProperty', - 'de': 'BeispielProperty'}), - category='CONSTANT', - description=model.MultiLanguageTextType({'en-US': 'Example Property object', - 'de': 'Beispiel Property Element'}), + value="exampleValue", + value_id=model.ExternalReference( + (model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, value="http://example.org/ValueId/ExampleValueId"),) + ), + display_name=model.MultiLanguageNameType({"en-US": "ExampleProperty", "de": "BeispielProperty"}), + category="CONSTANT", + description=model.MultiLanguageTextType( + {"en-US": "Example Property object", "de": "Beispiel Property Element"} + ), parent=None, - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/Properties/' - 'ExamplePropertyInOutput'),)), + semantic_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, value="http://example.org/Properties/ExamplePropertyInOutput" + ), + ) + ), qualifier=(), extension=(), supplemental_semantic_id=(), - embedded_data_specifications=() + embedded_data_specifications=(), ) submodel_element_operation = model.Operation( - id_short='ExampleOperation', + id_short="ExampleOperation", input_variable=[input_variable_property], output_variable=[output_variable_property], in_output_variable=[in_output_variable_property], - category='PARAMETER', - description=model.MultiLanguageTextType({'en-US': 'Example Operation object', - 'de': 'Beispiel Operation Element'}), + category="PARAMETER", + description=model.MultiLanguageTextType( + {"en-US": "Example Operation object", "de": "Beispiel Operation Element"} + ), parent=None, - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/Operations/' - 'ExampleOperation'),)), + semantic_id=model.ExternalReference( + (model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, value="http://example.org/Operations/ExampleOperation"),) + ), qualifier=(), extension=(), supplemental_semantic_id=(), - embedded_data_specifications=() + embedded_data_specifications=(), ) submodel_element_capability = model.Capability( - id_short='ExampleCapability', - category='PARAMETER', - description=model.MultiLanguageTextType({'en-US': 'Example Capability object', - 'de': 'Beispiel Capability Element'}), + id_short="ExampleCapability", + category="PARAMETER", + description=model.MultiLanguageTextType( + {"en-US": "Example Capability object", "de": "Beispiel Capability Element"} + ), parent=None, - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/Capabilities/' - 'ExampleCapability'),)), + semantic_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, value="http://example.org/Capabilities/ExampleCapability" + ), + ) + ), qualifier=(), extension=(), supplemental_semantic_id=(), - embedded_data_specifications=() + embedded_data_specifications=(), ) submodel_element_basic_event_element = model.BasicEventElement( - id_short='ExampleBasicEventElement', - observed=model.ModelReference((model.Key(type_=model.KeyTypes.SUBMODEL, - value='http://example.org/Test_Submodel'), - model.Key(type_=model.KeyTypes.PROPERTY, - value='ExampleProperty'),), - model.Property), + id_short="ExampleBasicEventElement", + observed=model.ModelReference( + ( + model.Key(type_=model.KeyTypes.SUBMODEL, value="http://example.org/Test_Submodel"), + model.Key(type_=model.KeyTypes.PROPERTY, value="ExampleProperty"), + ), + model.Property, + ), direction=model.Direction.OUTPUT, state=model.StateOfEvent.ON, - message_topic='ExampleTopic', - message_broker=model.ModelReference((model.Key(model.KeyTypes.SUBMODEL, - "http://example.org/ExampleMessageBroker"),), - model.Submodel), + message_topic="ExampleTopic", + message_broker=model.ModelReference( + (model.Key(model.KeyTypes.SUBMODEL, "http://example.org/ExampleMessageBroker"),), model.Submodel + ), last_update=model.datatypes.DateTime(2022, 11, 12, 23, 50, 23, 123456, datetime.timezone.utc), min_interval=model.datatypes.Duration(microseconds=1), - max_interval=model.datatypes.Duration(years=1, months=2, days=3, hours=4, minutes=5, seconds=6, - microseconds=123456), - category='PARAMETER', - description=model.MultiLanguageTextType({'en-US': 'Example BasicEventElement object', - 'de': 'Beispiel BasicEventElement Element'}), + max_interval=model.datatypes.Duration( + years=1, months=2, days=3, hours=4, minutes=5, seconds=6, microseconds=123456 + ), + category="PARAMETER", + description=model.MultiLanguageTextType( + {"en-US": "Example BasicEventElement object", "de": "Beispiel BasicEventElement Element"} + ), parent=None, - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/Events/ExampleBasicEventElement'),)), + semantic_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, value="http://example.org/Events/ExampleBasicEventElement" + ), + ) + ), qualifier=(), extension=(), supplemental_semantic_id=(), - embedded_data_specifications=() + embedded_data_specifications=(), ) submodel_element_submodel_element_list = model.SubmodelElementList( - id_short='ExampleSubmodelList', + id_short="ExampleSubmodelList", type_value_list_element=model.Property, value=(submodel_element_property, submodel_element_property_2), - semantic_id_list_element=model.ExternalReference((model.Key( - type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/Properties/ExampleProperty' - ),)), + semantic_id_list_element=model.ExternalReference( + (model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, value="http://example.org/Properties/ExampleProperty"),) + ), value_type_list_element=model.datatypes.String, order_relevant=True, - category='PARAMETER', - description=model.MultiLanguageTextType({'en-US': 'Example SubmodelElementList object', - 'de': 'Beispiel SubmodelElementList Element'}), + category="PARAMETER", + description=model.MultiLanguageTextType( + {"en-US": "Example SubmodelElementList object", "de": "Beispiel SubmodelElementList Element"} + ), parent=None, - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/SubmodelElementLists/' - 'ExampleSubmodelElementList'),)), + semantic_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/SubmodelElementLists/ExampleSubmodelElementList", + ), + ) + ), qualifier=(), extension=(), supplemental_semantic_id=(), - embedded_data_specifications=() + embedded_data_specifications=(), ) submodel_element_submodel_element_collection = model.SubmodelElementCollection( - id_short='ExampleSubmodelCollection', - value=(submodel_element_blob, - submodel_element_file, - submodel_element_file_uri, - submodel_element_multi_language_property, - submodel_element_range, - submodel_element_reference_element, - submodel_element_submodel_element_list), - category='PARAMETER', - description=model.MultiLanguageTextType({'en-US': 'Example SubmodelElementCollection object', - 'de': 'Beispiel SubmodelElementCollection Element'}), + id_short="ExampleSubmodelCollection", + value=( + submodel_element_blob, + submodel_element_file, + submodel_element_file_uri, + submodel_element_multi_language_property, + submodel_element_range, + submodel_element_reference_element, + submodel_element_submodel_element_list, + ), + category="PARAMETER", + description=model.MultiLanguageTextType( + {"en-US": "Example SubmodelElementCollection object", "de": "Beispiel SubmodelElementCollection Element"} + ), parent=None, - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/SubmodelElementCollections/' - 'ExampleSubmodelElementCollection'),)), + semantic_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/SubmodelElementCollections/ExampleSubmodelElementCollection", + ), + ) + ), qualifier=(), extension=(), supplemental_semantic_id=(), - embedded_data_specifications=() + embedded_data_specifications=(), ) submodel = model.Submodel( - id_='https://example.org/Test_Submodel', - submodel_element=(submodel_element_relationship_element, - submodel_element_annotated_relationship_element, - submodel_element_operation, - submodel_element_capability, - submodel_element_basic_event_element, - submodel_element_submodel_element_collection), - id_short='TestSubmodel', + id_="https://example.org/Test_Submodel", + submodel_element=( + submodel_element_relationship_element, + submodel_element_annotated_relationship_element, + submodel_element_operation, + submodel_element_capability, + submodel_element_basic_event_element, + submodel_element_submodel_element_collection, + ), + id_short="TestSubmodel", category=None, - description=model.MultiLanguageTextType({'en-US': 'An example submodel for the test application', - 'de': 'Ein Beispiel-Teilmodell für eine Test-Anwendung'}), + description=model.MultiLanguageTextType( + { + "en-US": "An example submodel for the test application", + "de": "Ein Beispiel-Teilmodell für eine Test-Anwendung", + } + ), parent=None, - administration=model.AdministrativeInformation(version='9', - revision='0', - creator=model.ExternalReference(( - model.Key(model.KeyTypes.GLOBAL_REFERENCE, - 'http://example.org/AdministrativeInformation/' - 'Test_Submodel'), - )),), - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/SubmodelTemplates/' - 'ExampleSubmodel'),)), + administration=model.AdministrativeInformation( + version="9", + revision="0", + creator=model.ExternalReference( + ( + model.Key( + model.KeyTypes.GLOBAL_REFERENCE, "http://example.org/AdministrativeInformation/Test_Submodel" + ), + ) + ), + ), + semantic_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, value="http://example.org/SubmodelTemplates/ExampleSubmodel" + ), + ) + ), qualifier=(), kind=model.ModellingKind.INSTANCE, extension=(), supplemental_semantic_id=(), - embedded_data_specifications=() + embedded_data_specifications=(), ) return submodel @@ -766,33 +989,45 @@ def create_example_concept_description() -> model.ConceptDescription: :return: example concept description """ concept_description = model.ConceptDescription( - id_='https://example.org/Test_ConceptDescription', - is_case_of={model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/DataSpecifications/' - 'ConceptDescriptions/TestConceptDescription'),))}, - id_short='TestConceptDescription', + id_="https://example.org/Test_ConceptDescription", + is_case_of={ + model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/DataSpecifications/ConceptDescriptions/TestConceptDescription", + ), + ) + ) + }, + id_short="TestConceptDescription", category=None, - description=model.MultiLanguageTextType({'en-US': 'An example concept description for the test application', - 'de': 'Ein Beispiel-ConceptDescription für eine Test-Anwendung'}), + description=model.MultiLanguageTextType( + { + "en-US": "An example concept description for the test application", + "de": "Ein Beispiel-ConceptDescription für eine Test-Anwendung", + } + ), parent=None, - administration=model.AdministrativeInformation(version='9', - revision='0', - creator=model.ExternalReference(( - model.Key(model.KeyTypes.GLOBAL_REFERENCE, - 'http://example.org/AdministrativeInformation/' - 'Test_ConceptDescription'), - )), - template_id='http://example.org/AdministrativeInformation' - 'Templates/Test_ConceptDescription', - embedded_data_specifications=( - _embedded_data_specification_iec61360, - )) + administration=model.AdministrativeInformation( + version="9", + revision="0", + creator=model.ExternalReference( + ( + model.Key( + model.KeyTypes.GLOBAL_REFERENCE, + "http://example.org/AdministrativeInformation/Test_ConceptDescription", + ), + ) + ), + template_id="http://example.org/AdministrativeInformationTemplates/Test_ConceptDescription", + embedded_data_specifications=(_embedded_data_specification_iec61360,), + ), ) return concept_description -def create_example_asset_administration_shell() -> \ - model.AssetAdministrationShell: +def create_example_asset_administration_shell() -> model.AssetAdministrationShell: """ Creates an :class:`~basyx.aas.model.aas.AssetAdministrationShell` with references to an example :class:`~basyx.aas.model.submodel.Submodel`. @@ -802,69 +1037,100 @@ def create_example_asset_administration_shell() -> \ asset_information = model.AssetInformation( asset_kind=model.AssetKind.INSTANCE, - global_asset_id='http://example.org/TestAsset/', - specific_asset_id={model.SpecificAssetId(name="TestKey", - value="TestValue", - external_subject_id=model.ExternalReference( - (model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/SpecificAssetId/'),)), - semantic_id=model.ExternalReference((model.Key( - model.KeyTypes.GLOBAL_REFERENCE, - "http://example.org/SpecificAssetId/" - ),)))}, - asset_type='http://example.org/TestAssetType/', - default_thumbnail=model.Resource( - "file:///path/to/thumbnail.png", - "image/png" - ) + global_asset_id="http://example.org/TestAsset/", + specific_asset_id={ + model.SpecificAssetId( + name="TestKey", + value="TestValue", + external_subject_id=model.ExternalReference( + (model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, value="http://example.org/SpecificAssetId/"),) + ), + semantic_id=model.ExternalReference( + (model.Key(model.KeyTypes.GLOBAL_REFERENCE, "http://example.org/SpecificAssetId/"),) + ), + ) + }, + asset_type="http://example.org/TestAssetType/", + default_thumbnail=model.Resource("file:///path/to/thumbnail.png", "image/png"), ) asset_administration_shell = model.AssetAdministrationShell( asset_information=asset_information, - id_='https://example.org/Test_AssetAdministrationShell', - id_short='TestAssetAdministrationShell', + id_="https://example.org/Test_AssetAdministrationShell", + id_short="TestAssetAdministrationShell", category=None, - description=model.MultiLanguageTextType({ - 'en-US': 'An Example Asset Administration Shell for the test application', - 'de': 'Ein Beispiel-Verwaltungsschale für eine Test-Anwendung' - }), + description=model.MultiLanguageTextType( + { + "en-US": "An Example Asset Administration Shell for the test application", + "de": "Ein Beispiel-Verwaltungsschale für eine Test-Anwendung", + } + ), parent=None, - administration=model.AdministrativeInformation(version='9', - revision='0', - creator=model.ExternalReference(( - model.Key(model.KeyTypes.GLOBAL_REFERENCE, - 'http://example.org/AdministrativeInformation/' - 'Test_AssetAdministrationShell'), - )), - template_id='http://example.org/AdministrativeInformation' - 'Templates/Test_AssetAdministrationShell'), - submodel={model.ModelReference((model.Key(type_=model.KeyTypes.SUBMODEL, - value='https://example.org/Test_Submodel'),), - model.Submodel, - model.ExternalReference(( - model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/SubmodelTemplates/ExampleSubmodel'), - ))), - model.ModelReference((model.Key(type_=model.KeyTypes.SUBMODEL, - value='http://example.org/Submodels/Assets/' - 'TestAsset/Identification'),), - model.Submodel, - model.ModelReference(( - model.Key(type_=model.KeyTypes.SUBMODEL, - value='http://example.org/SubmodelTemplates/' - 'AssetIdentification'),), - model.Submodel - )), - model.ModelReference((model.Key(type_=model.KeyTypes.SUBMODEL, - value='http://example.org/Submodels/Assets/' - 'TestAsset/BillOfMaterial'),), - model.Submodel), - }, - derived_from=model.ModelReference((model.Key(type_=model.KeyTypes.ASSET_ADMINISTRATION_SHELL, - value='https://example.org/TestAssetAdministrationShell2'),), - model.AssetAdministrationShell), + administration=model.AdministrativeInformation( + version="9", + revision="0", + creator=model.ExternalReference( + ( + model.Key( + model.KeyTypes.GLOBAL_REFERENCE, + "http://example.org/AdministrativeInformation/Test_AssetAdministrationShell", + ), + ) + ), + template_id="http://example.org/AdministrativeInformationTemplates/Test_AssetAdministrationShell", + ), + submodel={ + model.ModelReference( + (model.Key(type_=model.KeyTypes.SUBMODEL, value="https://example.org/Test_Submodel"),), + model.Submodel, + model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/SubmodelTemplates/ExampleSubmodel", + ), + ) + ), + ), + model.ModelReference( + ( + model.Key( + type_=model.KeyTypes.SUBMODEL, + value="http://example.org/Submodels/Assets/TestAsset/Identification", + ), + ), + model.Submodel, + model.ModelReference( + ( + model.Key( + type_=model.KeyTypes.SUBMODEL, + value="http://example.org/SubmodelTemplates/AssetIdentification", + ), + ), + model.Submodel, + ), + ), + model.ModelReference( + ( + model.Key( + type_=model.KeyTypes.SUBMODEL, + value="http://example.org/Submodels/Assets/TestAsset/BillOfMaterial", + ), + ), + model.Submodel, + ), + }, + derived_from=model.ModelReference( + ( + model.Key( + type_=model.KeyTypes.ASSET_ADMINISTRATION_SHELL, + value="https://example.org/TestAssetAdministrationShell2", + ), + ), + model.AssetAdministrationShell, + ), extension=(), - embedded_data_specifications=(_embedded_data_specification_iec61360,) + embedded_data_specifications=(_embedded_data_specification_iec61360,), ) return asset_administration_shell diff --git a/sdk/basyx/aas/examples/data/example_aas_mandatory_attributes.py b/sdk/basyx/aas/examples/data/example_aas_mandatory_attributes.py index c3911bd99..938b13ab0 100644 --- a/sdk/basyx/aas/examples/data/example_aas_mandatory_attributes.py +++ b/sdk/basyx/aas/examples/data/example_aas_mandatory_attributes.py @@ -14,6 +14,7 @@ :meth:`~basyx.aas.examples.data.example_aas_mandatory_attributes.create_full_example`. If you want to get single example objects or want to get more information use the other functions. """ + import logging from ... import model @@ -47,104 +48,112 @@ def create_example_submodel() -> model.Submodel: :return: example submodel """ submodel_element_property = model.Property( - id_short='ExampleProperty', - category="PARAMETER", - value_type=model.datatypes.String) + id_short="ExampleProperty", category="PARAMETER", value_type=model.datatypes.String + ) submodel_element_multi_language_property = model.MultiLanguageProperty( - category="PARAMETER", - id_short='ExampleMultiLanguageProperty') + category="PARAMETER", id_short="ExampleMultiLanguageProperty" + ) - submodel_element_range = model.Range( - id_short='ExampleRange', - category="PARAMETER", - value_type=model.datatypes.Int) + submodel_element_range = model.Range(id_short="ExampleRange", category="PARAMETER", value_type=model.datatypes.Int) - submodel_element_blob = model.Blob( - id_short='ExampleBlob', - content_type='application/pdf') + submodel_element_blob = model.Blob(id_short="ExampleBlob", content_type="application/pdf") - submodel_element_file = model.File( - id_short='ExampleFile', - content_type='application/pdf') + submodel_element_file = model.File(id_short="ExampleFile", content_type="application/pdf") submodel_element_reference_element = model.ReferenceElement( - category="PARAMETER", - id_short='ExampleReferenceElement') + category="PARAMETER", id_short="ExampleReferenceElement" + ) submodel_element_relationship_element = model.RelationshipElement( - id_short='ExampleRelationshipElement', - first=model.ModelReference((model.Key(type_=model.KeyTypes.SUBMODEL, - value='http://example.org/Test_Submodel'), - model.Key(type_=model.KeyTypes.PROPERTY, - value='ExampleProperty'),), - model.Property), - second=model.ModelReference((model.Key(type_=model.KeyTypes.SUBMODEL, - value='http://example.org/Test_Submodel'), - model.Key(type_=model.KeyTypes.PROPERTY, - value='ExampleProperty'),), - model.Property)) + id_short="ExampleRelationshipElement", + first=model.ModelReference( + ( + model.Key(type_=model.KeyTypes.SUBMODEL, value="http://example.org/Test_Submodel"), + model.Key(type_=model.KeyTypes.PROPERTY, value="ExampleProperty"), + ), + model.Property, + ), + second=model.ModelReference( + ( + model.Key(type_=model.KeyTypes.SUBMODEL, value="http://example.org/Test_Submodel"), + model.Key(type_=model.KeyTypes.PROPERTY, value="ExampleProperty"), + ), + model.Property, + ), + ) submodel_element_annotated_relationship_element = model.AnnotatedRelationshipElement( - id_short='ExampleAnnotatedRelationshipElement', - first=model.ModelReference((model.Key(type_=model.KeyTypes.SUBMODEL, - value='http://example.org/Test_Submodel'), - model.Key(type_=model.KeyTypes.PROPERTY, - value='ExampleProperty'),), - model.Property), - second=model.ModelReference((model.Key(type_=model.KeyTypes.SUBMODEL, - value='http://example.org/Test_Submodel'), - model.Key(type_=model.KeyTypes.PROPERTY, - value='ExampleProperty'),), - model.Property)) - - submodel_element_operation = model.Operation( - id_short='ExampleOperation') - - submodel_element_capability = model.Capability( - id_short='ExampleCapability') + id_short="ExampleAnnotatedRelationshipElement", + first=model.ModelReference( + ( + model.Key(type_=model.KeyTypes.SUBMODEL, value="http://example.org/Test_Submodel"), + model.Key(type_=model.KeyTypes.PROPERTY, value="ExampleProperty"), + ), + model.Property, + ), + second=model.ModelReference( + ( + model.Key(type_=model.KeyTypes.SUBMODEL, value="http://example.org/Test_Submodel"), + model.Key(type_=model.KeyTypes.PROPERTY, value="ExampleProperty"), + ), + model.Property, + ), + ) + + submodel_element_operation = model.Operation(id_short="ExampleOperation") + + submodel_element_capability = model.Capability(id_short="ExampleCapability") submodel_element_basic_event_element = model.BasicEventElement( - id_short='ExampleBasicEventElement', - observed=model.ModelReference((model.Key(type_=model.KeyTypes.SUBMODEL, - value='http://example.org/Test_Submodel'), - model.Key(type_=model.KeyTypes.PROPERTY, value='ExampleProperty'),), - model.Property), + id_short="ExampleBasicEventElement", + observed=model.ModelReference( + ( + model.Key(type_=model.KeyTypes.SUBMODEL, value="http://example.org/Test_Submodel"), + model.Key(type_=model.KeyTypes.PROPERTY, value="ExampleProperty"), + ), + model.Property, + ), direction=model.Direction.INPUT, - state=model.StateOfEvent.OFF) + state=model.StateOfEvent.OFF, + ) submodel_element_submodel_element_collection = model.SubmodelElementCollection( id_short=None, - value=(submodel_element_blob, - submodel_element_file, - submodel_element_multi_language_property, - submodel_element_property, - submodel_element_range, - submodel_element_reference_element)) - - submodel_element_submodel_element_collection_2 = model.SubmodelElementCollection( - id_short=None, - value=()) + value=( + submodel_element_blob, + submodel_element_file, + submodel_element_multi_language_property, + submodel_element_property, + submodel_element_range, + submodel_element_reference_element, + ), + ) + + submodel_element_submodel_element_collection_2 = model.SubmodelElementCollection(id_short=None, value=()) submodel_element_submodel_element_list = model.SubmodelElementList( - id_short='ExampleSubmodelList', + id_short="ExampleSubmodelList", type_value_list_element=model.SubmodelElementCollection, - value=(submodel_element_submodel_element_collection, submodel_element_submodel_element_collection_2)) + value=(submodel_element_submodel_element_collection, submodel_element_submodel_element_collection_2), + ) submodel_element_submodel_element_list_2 = model.SubmodelElementList( - id_short='ExampleSubmodelList2', - type_value_list_element=model.Capability, - value=()) + id_short="ExampleSubmodelList2", type_value_list_element=model.Capability, value=() + ) submodel = model.Submodel( - id_='https://example.org/Test_Submodel_Mandatory', - submodel_element=(submodel_element_relationship_element, - submodel_element_annotated_relationship_element, - submodel_element_operation, - submodel_element_capability, - submodel_element_basic_event_element, - submodel_element_submodel_element_list, - submodel_element_submodel_element_list_2)) + id_="https://example.org/Test_Submodel_Mandatory", + submodel_element=( + submodel_element_relationship_element, + submodel_element_annotated_relationship_element, + submodel_element_operation, + submodel_element_capability, + submodel_element_basic_event_element, + submodel_element_submodel_element_list, + submodel_element_submodel_element_list_2, + ), + ) return submodel @@ -154,8 +163,7 @@ def create_example_empty_submodel() -> model.Submodel: :return: example submodel """ - return model.Submodel( - id_='https://example.org/Test_Submodel2_Mandatory') + return model.Submodel(id_="https://example.org/Test_Submodel2_Mandatory") def create_example_concept_description() -> model.ConceptDescription: @@ -164,13 +172,11 @@ def create_example_concept_description() -> model.ConceptDescription: :return: example concept description """ - concept_description = model.ConceptDescription( - id_='https://example.org/Test_ConceptDescription_Mandatory') + concept_description = model.ConceptDescription(id_="https://example.org/Test_ConceptDescription_Mandatory") return concept_description -def create_example_asset_administration_shell() -> \ - model.AssetAdministrationShell: +def create_example_asset_administration_shell() -> model.AssetAdministrationShell: """ Creates an example :class:`~basyx.aas.model.aas.AssetAdministrationShell` containing references to the example, the example :class:`~Submodels `. @@ -178,18 +184,23 @@ def create_example_asset_administration_shell() -> \ :return: example asset administration shell """ asset_information = model.AssetInformation( - asset_kind=model.AssetKind.INSTANCE, - global_asset_id='http://example.org/Test_Asset_Mandatory/') + asset_kind=model.AssetKind.INSTANCE, global_asset_id="http://example.org/Test_Asset_Mandatory/" + ) asset_administration_shell = model.AssetAdministrationShell( asset_information=asset_information, - id_='https://example.org/Test_AssetAdministrationShell_Mandatory', - submodel={model.ModelReference((model.Key(type_=model.KeyTypes.SUBMODEL, - value='https://example.org/Test_Submodel_Mandatory'),), - model.Submodel), - model.ModelReference((model.Key(type_=model.KeyTypes.SUBMODEL, - value='https://example.org/Test_Submodel2_Mandatory'),), - model.Submodel)},) + id_="https://example.org/Test_AssetAdministrationShell_Mandatory", + submodel={ + model.ModelReference( + (model.Key(type_=model.KeyTypes.SUBMODEL, value="https://example.org/Test_Submodel_Mandatory"),), + model.Submodel, + ), + model.ModelReference( + (model.Key(type_=model.KeyTypes.SUBMODEL, value="https://example.org/Test_Submodel2_Mandatory"),), + model.Submodel, + ), + }, + ) return asset_administration_shell @@ -201,9 +212,9 @@ def create_example_empty_asset_administration_shell() -> model.AssetAdministrati :return: example :class:`~basyx.aas.model.aas.AssetAdministrationShell` """ asset_administration_shell = model.AssetAdministrationShell( - asset_information=model.AssetInformation( - global_asset_id='http://example.org/TestAsset2_Mandatory/'), - id_='https://example.org/Test_AssetAdministrationShell2_Mandatory') + asset_information=model.AssetInformation(global_asset_id="http://example.org/TestAsset2_Mandatory/"), + id_="https://example.org/Test_AssetAdministrationShell2_Mandatory", + ) return asset_administration_shell @@ -220,8 +231,9 @@ def check_example_asset_administration_shell(checker: AASDataChecker, shell: mod checker.check_asset_administration_shell_equal(shell, expected_shell) -def check_example_empty_asset_administration_shell(checker: AASDataChecker, shell: model.AssetAdministrationShell) \ - -> None: +def check_example_empty_asset_administration_shell( + checker: AASDataChecker, shell: model.AssetAdministrationShell +) -> None: expected_shell = create_example_empty_asset_administration_shell() checker.check_asset_administration_shell_equal(shell, expected_shell) diff --git a/sdk/basyx/aas/examples/data/example_aas_missing_attributes.py b/sdk/basyx/aas/examples/data/example_aas_missing_attributes.py index 25f0a1714..86ddfc569 100644 --- a/sdk/basyx/aas/examples/data/example_aas_missing_attributes.py +++ b/sdk/basyx/aas/examples/data/example_aas_missing_attributes.py @@ -8,6 +8,7 @@ Module for the creation of an :class:`IdentifiableStore ` with missing object attribute combinations for testing the serialization. """ + import datetime import logging @@ -40,301 +41,417 @@ def create_example_submodel() -> model.Submodel: :return: example submodel """ qualifier = model.Qualifier( - type_='http://example.org/Qualifier/ExampleQualifier', - value_type=model.datatypes.String) + type_="http://example.org/Qualifier/ExampleQualifier", value_type=model.datatypes.String + ) submodel_element_property = model.Property( - id_short='ExampleProperty', + id_short="ExampleProperty", value_type=model.datatypes.String, - value='exampleValue', + value="exampleValue", value_id=None, # TODO - category='CONSTANT', - description=model.MultiLanguageTextType({'en-US': 'Example Property object', - 'de': 'Beispiel Property Element'}), + category="CONSTANT", + description=model.MultiLanguageTextType( + {"en-US": "Example Property object", "de": "Beispiel Property Element"} + ), parent=None, - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/Properties/ExampleProperty'),)), - qualifier={qualifier}) + semantic_id=model.ExternalReference( + (model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, value="http://example.org/Properties/ExampleProperty"),) + ), + qualifier={qualifier}, + ) submodel_element_multi_language_property = model.MultiLanguageProperty( - id_short='ExampleMultiLanguageProperty', - value=model.MultiLanguageTextType({'en-US': 'Example value of a MultiLanguageProperty element', - 'de': 'Beispielwert für ein MultiLanguageProperty-Element'}), + id_short="ExampleMultiLanguageProperty", + value=model.MultiLanguageTextType( + { + "en-US": "Example value of a MultiLanguageProperty element", + "de": "Beispielwert für ein MultiLanguageProperty-Element", + } + ), value_id=None, # TODO - category='CONSTANT', - description=model.MultiLanguageTextType({'en-US': 'Example MultiLanguageProperty object', - 'de': 'Beispiel MultiLanguageProperty Element'}), + category="CONSTANT", + description=model.MultiLanguageTextType( + {"en-US": "Example MultiLanguageProperty object", "de": "Beispiel MultiLanguageProperty Element"} + ), parent=None, - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/MultiLanguageProperties/' - 'ExampleMultiLanguageProperty'),)), - qualifier=()) + semantic_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/MultiLanguageProperties/ExampleMultiLanguageProperty", + ), + ) + ), + qualifier=(), + ) submodel_element_range = model.Range( - id_short='ExampleRange', + id_short="ExampleRange", value_type=model.datatypes.Int, min=0, max=100, - category='PARAMETER', - description=model.MultiLanguageTextType({'en-US': 'Example Range object', - 'de': 'Beispiel Range Element'}), + category="PARAMETER", + description=model.MultiLanguageTextType({"en-US": "Example Range object", "de": "Beispiel Range Element"}), parent=None, - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/Ranges/ExampleRange'),)), - qualifier=()) + semantic_id=model.ExternalReference( + (model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, value="http://example.org/Ranges/ExampleRange"),) + ), + qualifier=(), + ) submodel_element_blob = model.Blob( - id_short='ExampleBlob', - content_type='application/pdf', - value=bytearray(b'\x01\x02\x03\x04\x05'), - category='PARAMETER', - description=model.MultiLanguageTextType({'en-US': 'Example Blob object', - 'de': 'Beispiel Blob Element'}), + id_short="ExampleBlob", + content_type="application/pdf", + value=bytearray(b"\x01\x02\x03\x04\x05"), + category="PARAMETER", + description=model.MultiLanguageTextType({"en-US": "Example Blob object", "de": "Beispiel Blob Element"}), parent=None, - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/Blobs/ExampleBlob'),)), - qualifier=()) + semantic_id=model.ExternalReference( + (model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, value="http://example.org/Blobs/ExampleBlob"),) + ), + qualifier=(), + ) submodel_element_file = model.File( - id_short='ExampleFile', - content_type='application/pdf', - value='/TestFile.pdf', - category='PARAMETER', - description=model.MultiLanguageTextType({'en-US': 'Example File object', - 'de': 'Beispiel File Element'}), + id_short="ExampleFile", + content_type="application/pdf", + value="/TestFile.pdf", + category="PARAMETER", + description=model.MultiLanguageTextType({"en-US": "Example File object", "de": "Beispiel File Element"}), parent=None, - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/Files/ExampleFile'),)), - qualifier=()) + semantic_id=model.ExternalReference( + (model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, value="http://example.org/Files/ExampleFile"),) + ), + qualifier=(), + ) submodel_element_reference_element = model.ReferenceElement( - id_short='ExampleReferenceElement', - value=model.ModelReference((model.Key(type_=model.KeyTypes.SUBMODEL, - value='http://example.org/Test_Submodel'), - model.Key(type_=model.KeyTypes.PROPERTY, - value='ExampleProperty'),), model.Submodel), - category='PARAMETER', - description=model.MultiLanguageTextType({'en-US': 'Example Reference Element object', - 'de': 'Beispiel Reference Element Element'}), + id_short="ExampleReferenceElement", + value=model.ModelReference( + ( + model.Key(type_=model.KeyTypes.SUBMODEL, value="http://example.org/Test_Submodel"), + model.Key(type_=model.KeyTypes.PROPERTY, value="ExampleProperty"), + ), + model.Submodel, + ), + category="PARAMETER", + description=model.MultiLanguageTextType( + {"en-US": "Example Reference Element object", "de": "Beispiel Reference Element Element"} + ), parent=None, - semantic_id=model.ExternalReference((model.Key( - type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/ReferenceElements/ExampleReferenceElement' - ),)), - qualifier=()) + semantic_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/ReferenceElements/ExampleReferenceElement", + ), + ) + ), + qualifier=(), + ) submodel_element_relationship_element = model.RelationshipElement( - id_short='ExampleRelationshipElement', - first=model.ModelReference((model.Key(type_=model.KeyTypes.SUBMODEL, - value='http://example.org/Test_Submodel'), - model.Key(type_=model.KeyTypes.PROPERTY, - value='ExampleProperty'),), - model.Property), - second=model.ModelReference((model.Key(type_=model.KeyTypes.SUBMODEL, - value='http://example.org/Test_Submodel'), - model.Key(type_=model.KeyTypes.PROPERTY, - value='ExampleProperty'),), - model.Property), - category='PARAMETER', - description=model.MultiLanguageTextType({'en-US': 'Example RelationshipElement object', - 'de': 'Beispiel RelationshipElement Element'}), + id_short="ExampleRelationshipElement", + first=model.ModelReference( + ( + model.Key(type_=model.KeyTypes.SUBMODEL, value="http://example.org/Test_Submodel"), + model.Key(type_=model.KeyTypes.PROPERTY, value="ExampleProperty"), + ), + model.Property, + ), + second=model.ModelReference( + ( + model.Key(type_=model.KeyTypes.SUBMODEL, value="http://example.org/Test_Submodel"), + model.Key(type_=model.KeyTypes.PROPERTY, value="ExampleProperty"), + ), + model.Property, + ), + category="PARAMETER", + description=model.MultiLanguageTextType( + {"en-US": "Example RelationshipElement object", "de": "Beispiel RelationshipElement Element"} + ), parent=None, - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/RelationshipElements/' - 'ExampleRelationshipElement'),)), - qualifier=()) + semantic_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/RelationshipElements/ExampleRelationshipElement", + ), + ) + ), + qualifier=(), + ) submodel_element_annotated_relationship_element = model.AnnotatedRelationshipElement( - id_short='ExampleAnnotatedRelationshipElement', - first=model.ModelReference((model.Key(type_=model.KeyTypes.SUBMODEL, - value='http://example.org/Test_Submodel'), - model.Key(type_=model.KeyTypes.PROPERTY, - value='ExampleProperty'),), - model.Property), - second=model.ModelReference((model.Key(type_=model.KeyTypes.SUBMODEL, - value='http://example.org/Test_Submodel'), - model.Key(type_=model.KeyTypes.PROPERTY, - value='ExampleProperty'),), - model.Property), - annotation={model.Property(id_short="ExampleAnnotatedProperty", - value_type=model.datatypes.String, - value='exampleValue', - category="PARAMETER", - parent=None), - model.Range(id_short="ExampleAnnotatedRange", - value_type=model.datatypes.Integer, - min=1, - max=5, - category="PARAMETER", - parent=None) - }, - category='PARAMETER', - description=model.MultiLanguageTextType({'en-US': 'Example AnnotatedRelationshipElement object', - 'de': 'Beispiel AnnotatedRelationshipElement Element'}), + id_short="ExampleAnnotatedRelationshipElement", + first=model.ModelReference( + ( + model.Key(type_=model.KeyTypes.SUBMODEL, value="http://example.org/Test_Submodel"), + model.Key(type_=model.KeyTypes.PROPERTY, value="ExampleProperty"), + ), + model.Property, + ), + second=model.ModelReference( + ( + model.Key(type_=model.KeyTypes.SUBMODEL, value="http://example.org/Test_Submodel"), + model.Key(type_=model.KeyTypes.PROPERTY, value="ExampleProperty"), + ), + model.Property, + ), + annotation={ + model.Property( + id_short="ExampleAnnotatedProperty", + value_type=model.datatypes.String, + value="exampleValue", + category="PARAMETER", + parent=None, + ), + model.Range( + id_short="ExampleAnnotatedRange", + value_type=model.datatypes.Integer, + min=1, + max=5, + category="PARAMETER", + parent=None, + ), + }, + category="PARAMETER", + description=model.MultiLanguageTextType( + { + "en-US": "Example AnnotatedRelationshipElement object", + "de": "Beispiel AnnotatedRelationshipElement Element", + } + ), parent=None, - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/RelationshipElements/' - 'ExampleAnnotatedRelationshipElement'),)), - qualifier=()) + semantic_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/RelationshipElements/ExampleAnnotatedRelationshipElement", + ), + ) + ), + qualifier=(), + ) operation_variable_property = model.Property( - id_short='ExampleProperty', + id_short="ExampleProperty", value_type=model.datatypes.String, - value='exampleValue', - value_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/ValueId/ExampleValueId'),)), - display_name=model.MultiLanguageNameType({'en-US': 'ExampleProperty', - 'de': 'BeispielProperty'}), - category='CONSTANT', - description=model.MultiLanguageTextType({'en-US': 'Example Property object', - 'de': 'Beispiel Property Element'}), + value="exampleValue", + value_id=model.ExternalReference( + (model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, value="http://example.org/ValueId/ExampleValueId"),) + ), + display_name=model.MultiLanguageNameType({"en-US": "ExampleProperty", "de": "BeispielProperty"}), + category="CONSTANT", + description=model.MultiLanguageTextType( + {"en-US": "Example Property object", "de": "Beispiel Property Element"} + ), parent=None, - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/Properties/ExampleProperty'),)), - qualifier=()) + semantic_id=model.ExternalReference( + (model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, value="http://example.org/Properties/ExampleProperty"),) + ), + qualifier=(), + ) input_variable_property = model.Property( - id_short='ExamplePropertyInput', + id_short="ExamplePropertyInput", value_type=model.datatypes.String, - value='exampleValue', - value_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/ValueId/ExampleValueId'),)), - display_name=model.MultiLanguageNameType({'en-US': 'ExampleProperty', - 'de': 'BeispielProperty'}), - category='CONSTANT', - description=model.MultiLanguageTextType({'en-US': 'Example Property object', - 'de': 'Beispiel Property Element'}), + value="exampleValue", + value_id=model.ExternalReference( + (model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, value="http://example.org/ValueId/ExampleValueId"),) + ), + display_name=model.MultiLanguageNameType({"en-US": "ExampleProperty", "de": "BeispielProperty"}), + category="CONSTANT", + description=model.MultiLanguageTextType( + {"en-US": "Example Property object", "de": "Beispiel Property Element"} + ), parent=None, - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/Properties/ExamplePropertyInput'),)), - qualifier=()) + semantic_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, value="http://example.org/Properties/ExamplePropertyInput" + ), + ) + ), + qualifier=(), + ) output_variable_property = model.Property( - id_short='ExamplePropertyOutput', + id_short="ExamplePropertyOutput", value_type=model.datatypes.String, - value='exampleValue', - value_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/ValueId/ExampleValueId'),)), - display_name=model.MultiLanguageNameType({'en-US': 'ExampleProperty', - 'de': 'BeispielProperty'}), - category='CONSTANT', - description=model.MultiLanguageTextType({'en-US': 'Example Property object', - 'de': 'Beispiel Property Element'}), + value="exampleValue", + value_id=model.ExternalReference( + (model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, value="http://example.org/ValueId/ExampleValueId"),) + ), + display_name=model.MultiLanguageNameType({"en-US": "ExampleProperty", "de": "BeispielProperty"}), + category="CONSTANT", + description=model.MultiLanguageTextType( + {"en-US": "Example Property object", "de": "Beispiel Property Element"} + ), parent=None, - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/Properties/ExamplePropertyOutput'),)), + semantic_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, value="http://example.org/Properties/ExamplePropertyOutput" + ), + ) + ), qualifier=(), extension=(), supplemental_semantic_id=(), - embedded_data_specifications=()) + embedded_data_specifications=(), + ) in_output_variable_property = model.Property( - id_short='ExamplePropertyInOutput', + id_short="ExamplePropertyInOutput", value_type=model.datatypes.String, - value='exampleValue', - value_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/ValueId/ExampleValueId'),)), - display_name=model.MultiLanguageNameType({'en-US': 'ExampleProperty', - 'de': 'BeispielProperty'}), - category='CONSTANT', - description=model.MultiLanguageTextType({'en-US': 'Example Property object', - 'de': 'Beispiel Property Element'}), + value="exampleValue", + value_id=model.ExternalReference( + (model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, value="http://example.org/ValueId/ExampleValueId"),) + ), + display_name=model.MultiLanguageNameType({"en-US": "ExampleProperty", "de": "BeispielProperty"}), + category="CONSTANT", + description=model.MultiLanguageTextType( + {"en-US": "Example Property object", "de": "Beispiel Property Element"} + ), parent=None, - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/Properties/' - 'ExamplePropertyInOutput'),)), + semantic_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, value="http://example.org/Properties/ExamplePropertyInOutput" + ), + ) + ), qualifier=(), extension=(), supplemental_semantic_id=(), - embedded_data_specifications=()) + embedded_data_specifications=(), + ) submodel_element_operation = model.Operation( - id_short='ExampleOperation', + id_short="ExampleOperation", input_variable=[input_variable_property], output_variable=[output_variable_property], in_output_variable=[in_output_variable_property], - category='PARAMETER', - description=model.MultiLanguageTextType({'en-US': 'Example Operation object', - 'de': 'Beispiel Operation Element'}), + category="PARAMETER", + description=model.MultiLanguageTextType( + {"en-US": "Example Operation object", "de": "Beispiel Operation Element"} + ), parent=None, - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/Operations/' - 'ExampleOperation'),)), - qualifier=()) + semantic_id=model.ExternalReference( + (model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, value="http://example.org/Operations/ExampleOperation"),) + ), + qualifier=(), + ) submodel_element_capability = model.Capability( - id_short='ExampleCapability', - category='PARAMETER', - description=model.MultiLanguageTextType({'en-US': 'Example Capability object', - 'de': 'Beispiel Capability Element'}), + id_short="ExampleCapability", + category="PARAMETER", + description=model.MultiLanguageTextType( + {"en-US": "Example Capability object", "de": "Beispiel Capability Element"} + ), parent=None, - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/Capabilities/' - 'ExampleCapability'),)), - qualifier=()) + semantic_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, value="http://example.org/Capabilities/ExampleCapability" + ), + ) + ), + qualifier=(), + ) submodel_element_basic_event_element = model.BasicEventElement( - id_short='ExampleBasicEventElement', - observed=model.ModelReference((model.Key(type_=model.KeyTypes.SUBMODEL, - value='http://example.org/Test_Submodel'), - model.Key(type_=model.KeyTypes.PROPERTY, - value='ExampleProperty'),), - model.Property), + id_short="ExampleBasicEventElement", + observed=model.ModelReference( + ( + model.Key(type_=model.KeyTypes.SUBMODEL, value="http://example.org/Test_Submodel"), + model.Key(type_=model.KeyTypes.PROPERTY, value="ExampleProperty"), + ), + model.Property, + ), direction=model.Direction.OUTPUT, state=model.StateOfEvent.ON, - message_topic='ExampleTopic', - message_broker=model.ModelReference((model.Key(model.KeyTypes.SUBMODEL, - "http://example.org/ExampleMessageBroker"),), - model.Submodel), + message_topic="ExampleTopic", + message_broker=model.ModelReference( + (model.Key(model.KeyTypes.SUBMODEL, "http://example.org/ExampleMessageBroker"),), model.Submodel + ), last_update=model.datatypes.DateTime(2022, 11, 12, 23, 50, 23, 123456, datetime.timezone.utc), min_interval=model.datatypes.Duration(microseconds=1), - max_interval=model.datatypes.Duration(years=1, months=2, days=3, hours=4, minutes=5, seconds=6, - microseconds=123456), - category='PARAMETER', - description=model.MultiLanguageTextType({'en-US': 'Example BasicEventElement object', - 'de': 'Beispiel BasicEventElement Element'}), + max_interval=model.datatypes.Duration( + years=1, months=2, days=3, hours=4, minutes=5, seconds=6, microseconds=123456 + ), + category="PARAMETER", + description=model.MultiLanguageTextType( + {"en-US": "Example BasicEventElement object", "de": "Beispiel BasicEventElement Element"} + ), parent=None, - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/Events/ExampleBasicEventElement'),)), - qualifier=()) + semantic_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, value="http://example.org/Events/ExampleBasicEventElement" + ), + ) + ), + qualifier=(), + ) submodel_element_submodel_element_collection = model.SubmodelElementCollection( - id_short='ExampleSubmodelCollection', - value=(submodel_element_blob, - submodel_element_file, - submodel_element_multi_language_property, - submodel_element_property, - submodel_element_range, - submodel_element_reference_element), - category='PARAMETER', - description=model.MultiLanguageTextType({'en-US': 'Example SubmodelElementCollection object', - 'de': 'Beispiel SubmodelElementCollection Element'}), + id_short="ExampleSubmodelCollection", + value=( + submodel_element_blob, + submodel_element_file, + submodel_element_multi_language_property, + submodel_element_property, + submodel_element_range, + submodel_element_reference_element, + ), + category="PARAMETER", + description=model.MultiLanguageTextType( + {"en-US": "Example SubmodelElementCollection object", "de": "Beispiel SubmodelElementCollection Element"} + ), parent=None, - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/SubmodelElementCollections/' - 'ExampleSubmodelElementCollection'),)), - qualifier=()) + semantic_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/SubmodelElementCollections/ExampleSubmodelElementCollection", + ), + ) + ), + qualifier=(), + ) submodel = model.Submodel( - id_='https://example.org/Test_Submodel_Missing', - submodel_element=(submodel_element_relationship_element, - submodel_element_annotated_relationship_element, - submodel_element_operation, - submodel_element_capability, - submodel_element_basic_event_element, - submodel_element_submodel_element_collection), - id_short='TestSubmodel', + id_="https://example.org/Test_Submodel_Missing", + submodel_element=( + submodel_element_relationship_element, + submodel_element_annotated_relationship_element, + submodel_element_operation, + submodel_element_capability, + submodel_element_basic_event_element, + submodel_element_submodel_element_collection, + ), + id_short="TestSubmodel", category=None, - description=model.MultiLanguageTextType({'en-US': 'An example submodel for the test application', - 'de': 'Ein Beispiel-Teilmodell für eine Test-Anwendung'}), + description=model.MultiLanguageTextType( + { + "en-US": "An example submodel for the test application", + "de": "Ein Beispiel-Teilmodell für eine Test-Anwendung", + } + ), parent=None, - administration=model.AdministrativeInformation(version='9', - revision='0'), - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/SubmodelTemplates/' - 'ExampleSubmodel'),)), + administration=model.AdministrativeInformation(version="9", revision="0"), + semantic_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, value="http://example.org/SubmodelTemplates/ExampleSubmodel" + ), + ) + ), qualifier=(), - kind=model.ModellingKind.INSTANCE) + kind=model.ModellingKind.INSTANCE, + ) return submodel @@ -345,15 +462,19 @@ def create_example_concept_description() -> model.ConceptDescription: :return: example concept description """ concept_description = model.ConceptDescription( - id_='https://example.org/Test_ConceptDescription_Missing', + id_="https://example.org/Test_ConceptDescription_Missing", is_case_of=None, - id_short='TestConceptDescription', + id_short="TestConceptDescription", category=None, - description=model.MultiLanguageTextType({'en-US': 'An example concept description for the test application', - 'de': 'Ein Beispiel-ConceptDescription für eine Test-Anwendung'}), + description=model.MultiLanguageTextType( + { + "en-US": "An example concept description for the test application", + "de": "Ein Beispiel-ConceptDescription für eine Test-Anwendung", + } + ), parent=None, - administration=model.AdministrativeInformation(version='9', - revision='0')) + administration=model.AdministrativeInformation(version="9", revision="0"), + ) return concept_description @@ -365,35 +486,44 @@ def create_example_asset_administration_shell() -> model.AssetAdministrationShel :return: example asset administration shell """ - resource = model.Resource( - content_type='application/pdf', - path='file:///TestFile.pdf') + resource = model.Resource(content_type="application/pdf", path="file:///TestFile.pdf") asset_information = model.AssetInformation( asset_kind=model.AssetKind.INSTANCE, - global_asset_id='http://example.org/Test_Asset_Missing/', - specific_asset_id={model.SpecificAssetId(name="TestKey", value="TestValue", - external_subject_id=model.ExternalReference( - (model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/' - 'SpecificAssetId/'),)))}, - default_thumbnail=resource) + global_asset_id="http://example.org/Test_Asset_Missing/", + specific_asset_id={ + model.SpecificAssetId( + name="TestKey", + value="TestValue", + external_subject_id=model.ExternalReference( + (model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, value="http://example.org/SpecificAssetId/"),) + ), + ) + }, + default_thumbnail=resource, + ) asset_administration_shell = model.AssetAdministrationShell( asset_information=asset_information, - id_='https://example.org/Test_AssetAdministrationShell_Missing', - id_short='TestAssetAdministrationShell', + id_="https://example.org/Test_AssetAdministrationShell_Missing", + id_short="TestAssetAdministrationShell", category=None, - description=model.MultiLanguageTextType({'en-US': 'An Example Asset Administration Shell for the test ' - 'application', - 'de': 'Ein Beispiel-Verwaltungsschale für eine Test-Anwendung'}), + description=model.MultiLanguageTextType( + { + "en-US": "An Example Asset Administration Shell for the test application", + "de": "Ein Beispiel-Verwaltungsschale für eine Test-Anwendung", + } + ), parent=None, - administration=model.AdministrativeInformation(version='9', - revision='0'), - submodel={model.ModelReference((model.Key(type_=model.KeyTypes.SUBMODEL, - value='https://example.org/Test_Submodel_Missing'),), - model.Submodel)}, - derived_from=None) + administration=model.AdministrativeInformation(version="9", revision="0"), + submodel={ + model.ModelReference( + (model.Key(type_=model.KeyTypes.SUBMODEL, value="https://example.org/Test_Submodel_Missing"),), + model.Submodel, + ) + }, + derived_from=None, + ) return asset_administration_shell diff --git a/sdk/basyx/aas/examples/data/example_submodel_template.py b/sdk/basyx/aas/examples/data/example_submodel_template.py index 10d19e0bb..6b04638cc 100644 --- a/sdk/basyx/aas/examples/data/example_submodel_template.py +++ b/sdk/basyx/aas/examples/data/example_submodel_template.py @@ -9,6 +9,7 @@ :class:`SubmodelElements ` where the kind is always ``TEMPLATE``. """ + import datetime import logging @@ -27,311 +28,445 @@ def create_example_submodel_template() -> model.Submodel: :return: example submodel """ submodel_element_property = model.Property( - id_short='ExampleProperty', + id_short="ExampleProperty", value_type=model.datatypes.String, value=None, value_id=None, # TODO - category='CONSTANT', - description=model.MultiLanguageTextType({'en-US': 'Example Property object', - 'de': 'Beispiel Property Element'}), + category="CONSTANT", + description=model.MultiLanguageTextType( + {"en-US": "Example Property object", "de": "Beispiel Property Element"} + ), parent=None, - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/Properties/ExampleProperty'),)), - qualifier=()) + semantic_id=model.ExternalReference( + (model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, value="http://example.org/Properties/ExampleProperty"),) + ), + qualifier=(), + ) submodel_element_multi_language_property = model.MultiLanguageProperty( - id_short='ExampleMultiLanguageProperty', + id_short="ExampleMultiLanguageProperty", value=None, value_id=None, # TODO - category='CONSTANT', - description=model.MultiLanguageTextType({'en-US': 'Example MultiLanguageProperty object', - 'de': 'Beispiel MultiLanguageProperty Element'}), + category="CONSTANT", + description=model.MultiLanguageTextType( + {"en-US": "Example MultiLanguageProperty object", "de": "Beispiel MultiLanguageProperty Element"} + ), parent=None, - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/MultiLanguageProperties/' - 'ExampleMultiLanguageProperty'),)), - qualifier=(),) + semantic_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/MultiLanguageProperties/ExampleMultiLanguageProperty", + ), + ) + ), + qualifier=(), + ) submodel_element_range = model.Range( - id_short='ExampleRange', + id_short="ExampleRange", value_type=model.datatypes.Int, min=None, max=100, - category='PARAMETER', - description=model.MultiLanguageTextType({'en-US': 'Example Range object', - 'de': 'Beispiel Range Element'}), + category="PARAMETER", + description=model.MultiLanguageTextType({"en-US": "Example Range object", "de": "Beispiel Range Element"}), parent=None, - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/Ranges/ExampleRange'),)), - qualifier=(),) + semantic_id=model.ExternalReference( + (model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, value="http://example.org/Ranges/ExampleRange"),) + ), + qualifier=(), + ) submodel_element_range_2 = model.Range( - id_short='ExampleRange2', + id_short="ExampleRange2", value_type=model.datatypes.Int, min=0, max=None, - category='PARAMETER', - description=model.MultiLanguageTextType({'en-US': 'Example Range object', - 'de': 'Beispiel Range Element'}), + category="PARAMETER", + description=model.MultiLanguageTextType({"en-US": "Example Range object", "de": "Beispiel Range Element"}), parent=None, - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/Ranges/ExampleRange'),)), - qualifier=()) + semantic_id=model.ExternalReference( + (model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, value="http://example.org/Ranges/ExampleRange"),) + ), + qualifier=(), + ) submodel_element_blob = model.Blob( - id_short='ExampleBlob', - content_type='application/pdf', + id_short="ExampleBlob", + content_type="application/pdf", value=None, - category='PARAMETER', - description=model.MultiLanguageTextType({'en-US': 'Example Blob object', - 'de': 'Beispiel Blob Element'}), + category="PARAMETER", + description=model.MultiLanguageTextType({"en-US": "Example Blob object", "de": "Beispiel Blob Element"}), parent=None, - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/Blobs/ExampleBlob'),)), - qualifier=()) + semantic_id=model.ExternalReference( + (model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, value="http://example.org/Blobs/ExampleBlob"),) + ), + qualifier=(), + ) submodel_element_file = model.File( - id_short='ExampleFile', - content_type='application/pdf', + id_short="ExampleFile", + content_type="application/pdf", value=None, - category='PARAMETER', - description=model.MultiLanguageTextType({'en-US': 'Example File object', - 'de': 'Beispiel File Element'}), + category="PARAMETER", + description=model.MultiLanguageTextType({"en-US": "Example File object", "de": "Beispiel File Element"}), parent=None, - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/Files/ExampleFile'),)), - qualifier=()) + semantic_id=model.ExternalReference( + (model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, value="http://example.org/Files/ExampleFile"),) + ), + qualifier=(), + ) submodel_element_reference_element = model.ReferenceElement( - id_short='ExampleReferenceElement', + id_short="ExampleReferenceElement", value=None, - category='PARAMETER', - description=model.MultiLanguageTextType({'en-US': 'Example Reference Element object', - 'de': 'Beispiel Reference Element Element'}), + category="PARAMETER", + description=model.MultiLanguageTextType( + {"en-US": "Example Reference Element object", "de": "Beispiel Reference Element Element"} + ), parent=None, - semantic_id=model.ExternalReference((model.Key( - type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/ReferenceElements/ExampleReferenceElement' - ),)), - qualifier=()) + semantic_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/ReferenceElements/ExampleReferenceElement", + ), + ) + ), + qualifier=(), + ) submodel_element_relationship_element = model.RelationshipElement( - id_short='ExampleRelationshipElement', - first=model.ModelReference((model.Key(type_=model.KeyTypes.SUBMODEL, value='http://example.org/Test_Submodel'), - model.Key(type_=model.KeyTypes.PROPERTY, - value='ExampleProperty'),), - model.Property), - second=model.ModelReference((model.Key(type_=model.KeyTypes.SUBMODEL, value='http://example.org/Test_Submodel'), - model.Key(type_=model.KeyTypes.PROPERTY, - value='ExampleProperty'),), - model.Property), - category='PARAMETER', - description=model.MultiLanguageTextType({'en-US': 'Example RelationshipElement object', - 'de': 'Beispiel RelationshipElement Element'}), + id_short="ExampleRelationshipElement", + first=model.ModelReference( + ( + model.Key(type_=model.KeyTypes.SUBMODEL, value="http://example.org/Test_Submodel"), + model.Key(type_=model.KeyTypes.PROPERTY, value="ExampleProperty"), + ), + model.Property, + ), + second=model.ModelReference( + ( + model.Key(type_=model.KeyTypes.SUBMODEL, value="http://example.org/Test_Submodel"), + model.Key(type_=model.KeyTypes.PROPERTY, value="ExampleProperty"), + ), + model.Property, + ), + category="PARAMETER", + description=model.MultiLanguageTextType( + {"en-US": "Example RelationshipElement object", "de": "Beispiel RelationshipElement Element"} + ), parent=None, - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/RelationshipElements/' - 'ExampleRelationshipElement'),)), - qualifier=()) + semantic_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/RelationshipElements/ExampleRelationshipElement", + ), + ) + ), + qualifier=(), + ) submodel_element_annotated_relationship_element = model.AnnotatedRelationshipElement( - id_short='ExampleAnnotatedRelationshipElement', - first=model.ModelReference((model.Key(type_=model.KeyTypes.SUBMODEL, value='http://example.org/Test_Submodel'), - model.Key(type_=model.KeyTypes.PROPERTY, - value='ExampleProperty'),), - model.Property), - second=model.ModelReference((model.Key(type_=model.KeyTypes.SUBMODEL, value='http://example.org/Test_Submodel'), - model.Key(type_=model.KeyTypes.PROPERTY, - value='ExampleProperty'),), - model.Property), + id_short="ExampleAnnotatedRelationshipElement", + first=model.ModelReference( + ( + model.Key(type_=model.KeyTypes.SUBMODEL, value="http://example.org/Test_Submodel"), + model.Key(type_=model.KeyTypes.PROPERTY, value="ExampleProperty"), + ), + model.Property, + ), + second=model.ModelReference( + ( + model.Key(type_=model.KeyTypes.SUBMODEL, value="http://example.org/Test_Submodel"), + model.Key(type_=model.KeyTypes.PROPERTY, value="ExampleProperty"), + ), + model.Property, + ), annotation=(), - category='PARAMETER', - description=model.MultiLanguageTextType({'en-US': 'Example AnnotatedRelationshipElement object', - 'de': 'Beispiel AnnotatedRelationshipElement Element'}), + category="PARAMETER", + description=model.MultiLanguageTextType( + { + "en-US": "Example AnnotatedRelationshipElement object", + "de": "Beispiel AnnotatedRelationshipElement Element", + } + ), parent=None, - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/RelationshipElements/' - 'ExampleAnnotatedRelationshipElement'),)), - qualifier=()) + semantic_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/RelationshipElements/ExampleAnnotatedRelationshipElement", + ), + ) + ), + qualifier=(), + ) input_variable_property = model.Property( - id_short='ExamplePropertyInput', + id_short="ExamplePropertyInput", value_type=model.datatypes.String, value=None, value_id=None, - category='CONSTANT', - description=model.MultiLanguageTextType({'en-US': 'Example Property object', - 'de': 'Beispiel Property Element'}), + category="CONSTANT", + description=model.MultiLanguageTextType( + {"en-US": "Example Property object", "de": "Beispiel Property Element"} + ), parent=None, - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/Properties/ExamplePropertyInput'),)), - qualifier=()) + semantic_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, value="http://example.org/Properties/ExamplePropertyInput" + ), + ) + ), + qualifier=(), + ) output_variable_property = model.Property( - id_short='ExamplePropertyOutput', + id_short="ExamplePropertyOutput", value_type=model.datatypes.String, value=None, value_id=None, - category='CONSTANT', - description=model.MultiLanguageTextType({'en-US': 'Example Property object', - 'de': 'Beispiel Property Element'}), + category="CONSTANT", + description=model.MultiLanguageTextType( + {"en-US": "Example Property object", "de": "Beispiel Property Element"} + ), parent=None, - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/Properties/ExamplePropertyOutput'),)), - qualifier=()) + semantic_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, value="http://example.org/Properties/ExamplePropertyOutput" + ), + ) + ), + qualifier=(), + ) in_output_variable_property = model.Property( - id_short='ExamplePropertyInOutput', + id_short="ExamplePropertyInOutput", value_type=model.datatypes.String, value=None, value_id=None, - category='CONSTANT', - description=model.MultiLanguageTextType({'en-US': 'Example Property object', - 'de': 'Beispiel Property Element'}), + category="CONSTANT", + description=model.MultiLanguageTextType( + {"en-US": "Example Property object", "de": "Beispiel Property Element"} + ), parent=None, - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/Properties/' - 'ExamplePropertyInOutput'),)), - qualifier=()) + semantic_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, value="http://example.org/Properties/ExamplePropertyInOutput" + ), + ) + ), + qualifier=(), + ) submodel_element_operation = model.Operation( - id_short='ExampleOperation', + id_short="ExampleOperation", input_variable=[input_variable_property], output_variable=[output_variable_property], in_output_variable=[in_output_variable_property], - category='PARAMETER', - description=model.MultiLanguageTextType({'en-US': 'Example Operation object', - 'de': 'Beispiel Operation Element'}), + category="PARAMETER", + description=model.MultiLanguageTextType( + {"en-US": "Example Operation object", "de": "Beispiel Operation Element"} + ), parent=None, - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/Operations/' - 'ExampleOperation'),)), - qualifier=()) + semantic_id=model.ExternalReference( + (model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, value="http://example.org/Operations/ExampleOperation"),) + ), + qualifier=(), + ) submodel_element_capability = model.Capability( - id_short='ExampleCapability', - category='PARAMETER', - description=model.MultiLanguageTextType({'en-US': 'Example Capability object', - 'de': 'Beispiel Capability Element'}), + id_short="ExampleCapability", + category="PARAMETER", + description=model.MultiLanguageTextType( + {"en-US": "Example Capability object", "de": "Beispiel Capability Element"} + ), parent=None, - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/Capabilities/' - 'ExampleCapability'),)), - qualifier=()) + semantic_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, value="http://example.org/Capabilities/ExampleCapability" + ), + ) + ), + qualifier=(), + ) submodel_element_basic_event_element = model.BasicEventElement( - id_short='ExampleBasicEventElement', - observed=model.ModelReference((model.Key(type_=model.KeyTypes.SUBMODEL, - value='http://example.org/Test_Submodel'), - model.Key(type_=model.KeyTypes.PROPERTY, - value='ExampleProperty'),), - model.Property), + id_short="ExampleBasicEventElement", + observed=model.ModelReference( + ( + model.Key(type_=model.KeyTypes.SUBMODEL, value="http://example.org/Test_Submodel"), + model.Key(type_=model.KeyTypes.PROPERTY, value="ExampleProperty"), + ), + model.Property, + ), direction=model.Direction.OUTPUT, state=model.StateOfEvent.ON, - message_topic='ExampleTopic', - message_broker=model.ModelReference((model.Key(model.KeyTypes.SUBMODEL, - "http://example.org/ExampleMessageBroker"),), - model.Submodel), + message_topic="ExampleTopic", + message_broker=model.ModelReference( + (model.Key(model.KeyTypes.SUBMODEL, "http://example.org/ExampleMessageBroker"),), model.Submodel + ), last_update=model.datatypes.DateTime(2022, 11, 12, 23, 50, 23, 123456, datetime.timezone.utc), min_interval=model.datatypes.Duration(microseconds=1), - max_interval=model.datatypes.Duration(years=1, months=2, days=3, hours=4, minutes=5, seconds=6, - microseconds=123456), - category='PARAMETER', - description=model.MultiLanguageTextType({'en-US': 'Example BasicEventElement object', - 'de': 'Beispiel BasicEventElement Element'}), + max_interval=model.datatypes.Duration( + years=1, months=2, days=3, hours=4, minutes=5, seconds=6, microseconds=123456 + ), + category="PARAMETER", + description=model.MultiLanguageTextType( + {"en-US": "Example BasicEventElement object", "de": "Beispiel BasicEventElement Element"} + ), parent=None, - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/Events/ExampleBasicEventElement'),)), - qualifier=()) + semantic_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, value="http://example.org/Events/ExampleBasicEventElement" + ), + ) + ), + qualifier=(), + ) submodel_element_submodel_element_collection = model.SubmodelElementCollection( id_short=None, value=( - submodel_element_property, - submodel_element_multi_language_property, - submodel_element_range, - submodel_element_range_2, - submodel_element_blob, - submodel_element_file, - submodel_element_reference_element), - category='PARAMETER', - description=model.MultiLanguageTextType({'en-US': 'Example SubmodelElementCollection object', - 'de': 'Beispiel SubmodelElementCollection Element'}), + submodel_element_property, + submodel_element_multi_language_property, + submodel_element_range, + submodel_element_range_2, + submodel_element_blob, + submodel_element_file, + submodel_element_reference_element, + ), + category="PARAMETER", + description=model.MultiLanguageTextType( + {"en-US": "Example SubmodelElementCollection object", "de": "Beispiel SubmodelElementCollection Element"} + ), parent=None, - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/SubmodelElementCollections/' - 'ExampleSubmodelElementCollection'),)), - qualifier=()) + semantic_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/SubmodelElementCollections/ExampleSubmodelElementCollection", + ), + ) + ), + qualifier=(), + ) submodel_element_submodel_element_collection_2 = model.SubmodelElementCollection( id_short=None, value=(), - category='PARAMETER', - description=model.MultiLanguageTextType({'en-US': 'Example SubmodelElementCollection object', - 'de': 'Beispiel SubmodelElementCollection Element'}), + category="PARAMETER", + description=model.MultiLanguageTextType( + {"en-US": "Example SubmodelElementCollection object", "de": "Beispiel SubmodelElementCollection Element"} + ), parent=None, - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/SubmodelElementCollections/' - 'ExampleSubmodelElementCollection'),)), - qualifier=()) + semantic_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/SubmodelElementCollections/ExampleSubmodelElementCollection", + ), + ) + ), + qualifier=(), + ) submodel_element_submodel_element_list = model.SubmodelElementList( - id_short='ExampleSubmodelList', + id_short="ExampleSubmodelList", type_value_list_element=model.SubmodelElementCollection, value=(submodel_element_submodel_element_collection, submodel_element_submodel_element_collection_2), - semantic_id_list_element=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/' - 'SubmodelElementCollections/' - 'ExampleSubmodelElementCollection'),)), + semantic_id_list_element=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/SubmodelElementCollections/ExampleSubmodelElementCollection", + ), + ) + ), order_relevant=True, - category='PARAMETER', - description=model.MultiLanguageTextType({'en-US': 'Example SubmodelElementList object', - 'de': 'Beispiel SubmodelElementList Element'}), + category="PARAMETER", + description=model.MultiLanguageTextType( + {"en-US": "Example SubmodelElementList object", "de": "Beispiel SubmodelElementList Element"} + ), parent=None, - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/SubmodelElementLists/' - 'ExampleSubmodelElementList'),)), - qualifier=()) + semantic_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/SubmodelElementLists/ExampleSubmodelElementList", + ), + ) + ), + qualifier=(), + ) submodel_element_submodel_element_list_2 = model.SubmodelElementList( - id_short='ExampleSubmodelList2', + id_short="ExampleSubmodelList2", type_value_list_element=model.Capability, value=(), - semantic_id_list_element=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/' - 'SubmodelElementCollections/' - 'ExampleSubmodelElementCollection'),)), + semantic_id_list_element=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/SubmodelElementCollections/ExampleSubmodelElementCollection", + ), + ) + ), order_relevant=True, - category='PARAMETER', - description=model.MultiLanguageTextType({'en-US': 'Example SubmodelElementList object', - 'de': 'Beispiel SubmodelElementList Element'}), + category="PARAMETER", + description=model.MultiLanguageTextType( + {"en-US": "Example SubmodelElementList object", "de": "Beispiel SubmodelElementList Element"} + ), parent=None, - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/SubmodelElementLists/' - 'ExampleSubmodelElementList'),)), - qualifier=()) + semantic_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, + value="http://example.org/SubmodelElementLists/ExampleSubmodelElementList", + ), + ) + ), + qualifier=(), + ) submodel = model.Submodel( - id_='https://example.org/Test_Submodel_Template', - submodel_element=(submodel_element_relationship_element, - submodel_element_annotated_relationship_element, - submodel_element_operation, - submodel_element_capability, - submodel_element_basic_event_element, - submodel_element_submodel_element_list, - submodel_element_submodel_element_list_2), - id_short='TestSubmodel', + id_="https://example.org/Test_Submodel_Template", + submodel_element=( + submodel_element_relationship_element, + submodel_element_annotated_relationship_element, + submodel_element_operation, + submodel_element_capability, + submodel_element_basic_event_element, + submodel_element_submodel_element_list, + submodel_element_submodel_element_list_2, + ), + id_short="TestSubmodel", category=None, - description=model.MultiLanguageTextType({'en-US': 'An example submodel for the test application', - 'de': 'Ein Beispiel-Teilmodell für eine Test-Anwendung'}), + description=model.MultiLanguageTextType( + { + "en-US": "An example submodel for the test application", + "de": "Ein Beispiel-Teilmodell für eine Test-Anwendung", + } + ), parent=None, - administration=model.AdministrativeInformation(version='9', - revision='0'), - semantic_id=model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/SubmodelTemplates/' - 'ExampleSubmodel'),)), + administration=model.AdministrativeInformation(version="9", revision="0"), + semantic_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, value="http://example.org/SubmodelTemplates/ExampleSubmodel" + ), + ) + ), qualifier=(), - kind=model.ModellingKind.TEMPLATE) + kind=model.ModellingKind.TEMPLATE, + ) return submodel diff --git a/sdk/basyx/aas/examples/tutorial_aasx.py b/sdk/basyx/aas/examples/tutorial_aasx.py index 8878ed0c8..e2f188272 100755 --- a/sdk/basyx/aas/examples/tutorial_aasx.py +++ b/sdk/basyx/aas/examples/tutorial_aasx.py @@ -10,6 +10,7 @@ *Details of the Asset Administration Shell* some specifications of AASX files will change, resulting in changes of the :class:`~basyx.aas.adapter.aasx.AASXWriter` interface. """ + import datetime from pathlib import Path # Used for easier handling of auxiliary file's local path @@ -29,22 +30,17 @@ # Let's first create a basic Asset Administration Shell with a simple submodel. # See `tutorial_create_simple_aas.py` for more details. -submodel = model.Submodel( - id_='https://example.org/Simple_Submodel' -) +submodel = model.Submodel(id_="https://example.org/Simple_Submodel") aas = model.AssetAdministrationShell( - id_='https://example.org/Simple_AAS', + id_="https://example.org/Simple_AAS", asset_information=model.AssetInformation( - asset_kind=model.AssetKind.INSTANCE, - global_asset_id='http://example.org/Simple_Asset' + asset_kind=model.AssetKind.INSTANCE, global_asset_id="http://example.org/Simple_Asset" ), - submodel={model.ModelReference.from_referable(submodel)} + submodel={model.ModelReference.from_referable(submodel)}, ) # Another submodel, which is not related to the AAS: -unrelated_submodel = model.Submodel( - id_='https://example.org/Unrelated_Submodel' -) +unrelated_submodel = model.Submodel(id_="https://example.org/Unrelated_Submodel") # We add these objects to an IdentifiableStore for easy retrieval by id. # See `tutorial_storage.py` for more details. We could also use a database-backed IdentifiableStore here @@ -69,7 +65,7 @@ # (This is actually a requirement of the underlying Open Packaging Conventions (ECMA376-2) format, which imposes the # specification of the MIME type ("content type") of every single file within the package.) -with open(Path(__file__).parent / 'data' / 'TestFile.pdf', 'rb') as f: +with open(Path(__file__).parent / "data" / "TestFile.pdf", "rb") as f: actual_file_name = file_store.add_file("/aasx/suppl/MyExampleFile.pdf", f, "application/pdf") @@ -77,9 +73,8 @@ # Submodel, in the form of a `File` object: submodel.submodel_element.add( - model.File(id_short="documentationFile", - content_type="application/pdf", - value=actual_file_name)) + model.File(id_short="documentationFile", content_type="application/pdf", value=actual_file_name) +) ###################################################################### @@ -102,9 +97,7 @@ # ATTENTION: As of Version 3.0 RC01 of Details of the Asset Administration Shell, it is no longer valid to add more # than one "aas-spec" part (JSON/XML part with AAS objects) to an AASX package. Thus, `write_aas` MUST # only be called once per AASX package! - writer.write_aas(aas_ids=['https://example.org/Simple_AAS'], - object_store=identifiable_store, - file_store=file_store) + writer.write_aas(aas_ids=["https://example.org/Simple_AAS"], object_store=identifiable_store, file_store=file_store) # Alternatively, we can use a more low-level interface to add a JSON/XML part with any Identifiable objects (not # only an AAS and referenced objects) in the AASX package manually. `write_aas_objects()` will also take care of @@ -116,9 +109,9 @@ identifiables_to_be_written: model.DictIdentifiableStore[model.Identifiable] = model.DictIdentifiableStore( [unrelated_submodel] ) - writer.write_all_aas_objects(part_name="/aasx/my_aas_part.xml", - objects=identifiables_to_be_written, - file_store=file_store) + writer.write_all_aas_objects( + part_name="/aasx/my_aas_part.xml", objects=identifiables_to_be_written, file_store=file_store + ) # We can also add a thumbnail image to the package (using `writer.write_thumbnail()`) or add metadata: meta_data = pyecma376_2.OPCCoreProperties() @@ -144,8 +137,7 @@ # package file is properly closed when we are finished. with aasx.AASXReader("MyAASXPackage.aasx") as reader: # Read all contained AAS objects and all referenced auxiliary files - reader.read_into(object_store=new_identifiable_store, - file_store=new_file_store) + reader.read_into(object_store=new_identifiable_store, file_store=new_file_store) # We can also read the metadata new_meta_data = reader.get_core_properties() @@ -154,6 +146,6 @@ # Some quick checks to make sure, reading worked as expected -assert 'https://example.org/Simple_Submodel' in new_identifiable_store +assert "https://example.org/Simple_Submodel" in new_identifiable_store assert actual_file_name in new_file_store assert new_meta_data.creator == "Chair of Process Control Engineering" diff --git a/sdk/basyx/aas/examples/tutorial_backend_couchdb.py b/sdk/basyx/aas/examples/tutorial_backend_couchdb.py index ed09cf432..99e3fd722 100755 --- a/sdk/basyx/aas/examples/tutorial_backend_couchdb.py +++ b/sdk/basyx/aas/examples/tutorial_backend_couchdb.py @@ -44,13 +44,17 @@ # password of a CouchDB user account which is "member" of this database (see above). Alternatively, you can provide # your CouchDB server's admin credentials. config = ConfigParser() -config.read([Path(__file__).parent.parent.parent.parent / 'test' / 'test_config.default.ini', - Path(__file__).parent.parent.parent.parent / 'test' / 'test_config.ini']) - -couchdb_url = config['couchdb']['url'] -couchdb_database = config['couchdb']['database'] -couchdb_user = config['couchdb']['user'] -couchdb_password = config['couchdb']['password'] +config.read( + [ + Path(__file__).parent.parent.parent.parent / "test" / "test_config.default.ini", + Path(__file__).parent.parent.parent.parent / "test" / "test_config.ini", + ] +) + +couchdb_url = config["couchdb"]["url"] +couchdb_database = config["couchdb"]["database"] +couchdb_user = config["couchdb"]["user"] +couchdb_password = config["couchdb"]["password"] # Provide the login credentials to the CouchDB backend. diff --git a/sdk/basyx/aas/examples/tutorial_create_simple_aas.py b/sdk/basyx/aas/examples/tutorial_create_simple_aas.py index 3cacd74ab..9a88be066 100755 --- a/sdk/basyx/aas/examples/tutorial_create_simple_aas.py +++ b/sdk/basyx/aas/examples/tutorial_create_simple_aas.py @@ -25,15 +25,14 @@ ############################################################################################ # Step 1.1: create the AssetInformation object asset_information = model.AssetInformation( - asset_kind=model.AssetKind.INSTANCE, - global_asset_id='http://example.org/Simple_Asset' + asset_kind=model.AssetKind.INSTANCE, global_asset_id="http://example.org/Simple_Asset" ) # step 1.2: create the Asset Administration Shell -identifier = 'https://example.org/Simple_AAS' +identifier = "https://example.org/Simple_AAS" aas = model.AssetAdministrationShell( id_=identifier, # set identifier - asset_information=asset_information + asset_information=asset_information, ) @@ -42,10 +41,8 @@ ############################################################# # Step 2.1: create the Submodel object -identifier = 'https://example.org/Simple_Submodel' -submodel = model.Submodel( - id_=identifier -) +identifier = "https://example.org/Simple_Submodel" +submodel = model.Submodel(id_=identifier) # Step 2.2: create a reference to that Submodel and add it to the Asset Administration Shell's `submodel` set aas.submodel.add(model.ModelReference.from_referable(submodel)) @@ -54,13 +51,11 @@ # =============================================================== # ALTERNATIVE: step 1 and 2 can alternatively be done in one step # In this version, the Submodel reference is passed to the Asset Administration Shell's constructor. -submodel = model.Submodel( - id_='https://example.org/Simple_Submodel' -) +submodel = model.Submodel(id_="https://example.org/Simple_Submodel") aas = model.AssetAdministrationShell( - id_='https://example.org/Simple_AAS', + id_="https://example.org/Simple_AAS", asset_information=asset_information, - submodel={model.ModelReference.from_referable(submodel)} + submodel={model.ModelReference.from_referable(submodel)}, ) @@ -71,18 +66,15 @@ # Step 3.1: create a global reference to a semantic description of the Property # A global reference consist of one key which points to the address where the semantic description is stored semantic_reference = model.ExternalReference( - (model.Key( - type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/Properties/SimpleProperty' - ),) + (model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, value="http://example.org/Properties/SimpleProperty"),) ) # Step 3.2: create the simple Property property_ = model.Property( - id_short='ExampleProperty', # Identifying string of the element within the Submodel namespace + id_short="ExampleProperty", # Identifying string of the element within the Submodel namespace value_type=model.datatypes.String, # Data type of the value - value='exampleValue', # Value of the Property - semantic_id=semantic_reference # set the semantic reference + value="exampleValue", # Value of the Property + semantic_id=semantic_reference, # set the semantic reference ) # Step 3.3: add the Property to the Submodel @@ -93,18 +85,19 @@ # ALTERNATIVE: step 2 and 3 can also be combined in a single statement: # Again, we pass the Property to the Submodel's constructor instead of adding it afterward. submodel = model.Submodel( - id_='https://example.org/Simple_Submodel', + id_="https://example.org/Simple_Submodel", submodel_element={ model.Property( - id_short='ExampleProperty', + id_short="ExampleProperty", value_type=model.datatypes.String, - value='exampleValue', + value="exampleValue", semantic_id=model.ExternalReference( - (model.Key( - type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/Properties/SimpleProperty' - ),) - ) + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, value="http://example.org/Properties/SimpleProperty" + ), + ) + ), ) - } + }, ) diff --git a/sdk/basyx/aas/examples/tutorial_navigate_aas.py b/sdk/basyx/aas/examples/tutorial_navigate_aas.py index 2bbb9f89d..f963cf4e6 100644 --- a/sdk/basyx/aas/examples/tutorial_navigate_aas.py +++ b/sdk/basyx/aas/examples/tutorial_navigate_aas.py @@ -45,11 +45,7 @@ submodel = model.Submodel(id_="https://iat.rwth-aachen.de/Simple_Submodel") # Step 1.2: Add a single Property to the Submodel -my_property = model.Property( - id_short="MyProperty", - value_type=model.datatypes.String, - value="I am a simple Property" -) +my_property = model.Property(id_short="MyProperty", value_type=model.datatypes.String, value="I am a simple Property") submodel.submodel_element.add(my_property) # Step 1.3: Add a SubmodelElementCollection of Properties to the Submodel @@ -59,14 +55,14 @@ model.Property( id_short="MyProperty0", value_type=model.datatypes.String, - value="I am the first of two Properties within a SubmodelElementCollection" + value="I am the first of two Properties within a SubmodelElementCollection", ), model.Property( id_short="MyProperty1", value_type=model.datatypes.String, - value="I am the second of two Properties within a SubmodelElementCollection" - ) - } + value="I am the second of two Properties within a SubmodelElementCollection", + ), + }, ) submodel.submodel_element.add(my_property_collection) @@ -78,49 +74,51 @@ order_relevant=True, value=[ model.Property( - id_short=None, - value_type=model.datatypes.String, - value="I am Property 0 within a SubmodelElementList" + id_short=None, value_type=model.datatypes.String, value="I am Property 0 within a SubmodelElementList" ), model.Property( - id_short=None, - value_type=model.datatypes.String, - value="I am Property 1 within a SubmodelElementList" - ) - ] + id_short=None, value_type=model.datatypes.String, value="I am Property 1 within a SubmodelElementList" + ), + ], ) submodel.submodel_element.add(my_property_list) # Step 1.5: Add a SubmodelElementList of SubmodelElementCollections to the Submodel my_property_collection_0 = model.SubmodelElementCollection( id_short=None, - value={model.Property( - id_short="MyProperty", - value_type=model.datatypes.String, - value="I am a simple Property within SubmodelElementCollection 0" - )} + value={ + model.Property( + id_short="MyProperty", + value_type=model.datatypes.String, + value="I am a simple Property within SubmodelElementCollection 0", + ) + }, ) my_property_collection_1 = model.SubmodelElementCollection( id_short=None, - value={model.Property( - id_short="MyProperty", - value_type=model.datatypes.String, - value="I am a simple Property within SubmodelElementCollection 1" - )} + value={ + model.Property( + id_short="MyProperty", + value_type=model.datatypes.String, + value="I am a simple Property within SubmodelElementCollection 1", + ) + }, ) my_property_collection_2 = model.SubmodelElementCollection( id_short=None, - value={model.Property( - id_short="MyProperty", - value_type=model.datatypes.String, - value="I am a simple Property within SubmodelElementCollection 2" - )} + value={ + model.Property( + id_short="MyProperty", + value_type=model.datatypes.String, + value="I am a simple Property within SubmodelElementCollection 2", + ) + }, ) my_collection_list = model.SubmodelElementList( id_short="MyCollectionList", type_value_list_element=model.SubmodelElementCollection, order_relevant=True, - value=[my_property_collection_0, my_property_collection_1, my_property_collection_2] + value=[my_property_collection_0, my_property_collection_1, my_property_collection_2], ) submodel.submodel_element.add(my_collection_list) @@ -145,8 +143,7 @@ # Step 2.2.2: Access a Property within a SubmodelElementCollection via its IdShortPath my_property_collection_property_1 = cast( - model.Property, - submodel.get_referable(["MyPropertyCollection", "MyProperty1"]) + model.Property, submodel.get_referable(["MyPropertyCollection", "MyProperty1"]) ) print( f"my_property_collection_property_1: " @@ -178,8 +175,7 @@ my_collection_list = cast(model.SubmodelElementList, submodel.get_referable("MyCollectionList")) my_collection_list_collection_0 = cast(model.SubmodelElementCollection, my_collection_list.get_referable("0")) my_collection_list_collection_0_property_0 = cast( - model.Property, - my_collection_list_collection_0.get_referable("MyProperty") + model.Property, my_collection_list_collection_0.get_referable("MyProperty") ) print( f"my_collection_list_collection_0_property_0: " @@ -189,8 +185,7 @@ # Step 2.4.2: Access a Property within a SubmodelElementList of SubmodelElementCollections via its IdShortPath my_collection_list_collection_2_property_0 = cast( - model.Property, - submodel.get_referable(["MyCollectionList", "2", "MyProperty"]) + model.Property, submodel.get_referable(["MyCollectionList", "2", "MyProperty"]) ) print( f"my_collection_list_collection_2_property_0: " diff --git a/sdk/basyx/aas/examples/tutorial_serialization_deserialization.py b/sdk/basyx/aas/examples/tutorial_serialization_deserialization.py index 955cb062a..8b7b7ebf9 100755 --- a/sdk/basyx/aas/examples/tutorial_serialization_deserialization.py +++ b/sdk/basyx/aas/examples/tutorial_serialization_deserialization.py @@ -32,23 +32,26 @@ # For more details, take a look at `tutorial_create_simple_aas.py` submodel = model.Submodel( - id_='https://example.org/Simple_Submodel', + id_="https://example.org/Simple_Submodel", submodel_element={ model.Property( - id_short='ExampleProperty', + id_short="ExampleProperty", value_type=basyx.aas.model.datatypes.String, - value='exampleValue', - semantic_id=model.ExternalReference((model.Key( - type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/Properties/SimpleProperty' - ),) - ) - )} + value="exampleValue", + semantic_id=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, value="http://example.org/Properties/SimpleProperty" + ), + ) + ), + ) + }, ) aashell = model.AssetAdministrationShell( - id_='https://example.org/Simple_AAS', + id_="https://example.org/Simple_AAS", asset_information=model.AssetInformation(global_asset_id="test"), - submodel={model.ModelReference.from_referable(submodel)} + submodel={model.ModelReference.from_referable(submodel)}, ) @@ -62,14 +65,13 @@ # dumped data structure. aashell_json_string = json.dumps(aashell, cls=basyx.aas.adapter.json.AASToJsonEncoder) -property_json_string = json.dumps(submodel.submodel_element.get_object_by_attribute("id_short", 'ExampleProperty'), - cls=basyx.aas.adapter.json.AASToJsonEncoder) +property_json_string = json.dumps( + submodel.submodel_element.get_object_by_attribute("id_short", "ExampleProperty"), + cls=basyx.aas.adapter.json.AASToJsonEncoder, +) # Using this technique, we can also serialize Python dict and list data structures with nested AAS objects: -json_string = json.dumps({'the_submodel': submodel, - 'the_aas': aashell - }, - cls=basyx.aas.adapter.json.AASToJsonEncoder) +json_string = json.dumps({"the_submodel": submodel, "the_aas": aashell}, cls=basyx.aas.adapter.json.AASToJsonEncoder) ###################################################################### @@ -98,13 +100,13 @@ identifiable_store.add(aashell) # step 4.2: writing the contents of the IdentifiableStore to a JSON file -basyx.aas.adapter.json.write_aas_json_file('data.json', identifiable_store) +basyx.aas.adapter.json.write_aas_json_file("data.json", identifiable_store) # We can pass the additional keyword argument `indent=4` to `write_aas_json_file()` to format the JSON file in a more # human-readable (but much more space-consuming) manner. # step 4.3: writing the contents of the IdentifiableStore to an XML file -basyx.aas.adapter.xml.write_aas_xml_file('data.xml', identifiable_store) +basyx.aas.adapter.xml.write_aas_xml_file("data.xml", identifiable_store) ################################################################## @@ -112,17 +114,17 @@ ################################################################## # step 5.1: reading contents of the JSON file as an IdentifiableStore -json_file_data = basyx.aas.adapter.json.read_aas_json_file('data.json') +json_file_data = basyx.aas.adapter.json.read_aas_json_file("data.json") # By passing the `failsafe=False` argument to `read_aas_json_file()`, we can switch to the `StrictAASFromJsonDecoder` # (see step 3) for a stricter error reporting. # step 5.2: reading contents of the XML file as an IdentifiableStore -xml_file_data = basyx.aas.adapter.xml.read_aas_xml_file('data.xml') +xml_file_data = basyx.aas.adapter.xml.read_aas_xml_file("data.xml") # Again, we can use `failsafe=False` for switching on stricter error reporting in the parser. # step 5.3: Retrieving the objects from the IdentifiableStore # For more information on the available techniques, see `tutorial_storage.py`. -submodel_from_xml = xml_file_data.get_item('https://example.org/Simple_Submodel') +submodel_from_xml = xml_file_data.get_item("https://example.org/Simple_Submodel") assert isinstance(submodel_from_xml, model.Submodel) diff --git a/sdk/basyx/aas/examples/tutorial_storage.py b/sdk/basyx/aas/examples/tutorial_storage.py index c422a7710..bc09ff7fe 100755 --- a/sdk/basyx/aas/examples/tutorial_storage.py +++ b/sdk/basyx/aas/examples/tutorial_storage.py @@ -18,7 +18,6 @@ # Step 3: retrieving objects from the store by their identifier # Step 4: using the IdentifiableStore to resolve a reference - from basyx.aas import model from basyx.aas.model import AssetInformation, AssetAdministrationShell, Submodel @@ -30,29 +29,22 @@ # For more details, take a look at `tutorial_create_simple_aas.py` asset_information = AssetInformation( - asset_kind=model.AssetKind.INSTANCE, - global_asset_id='http://example.org/Simple_Asset' + asset_kind=model.AssetKind.INSTANCE, global_asset_id="http://example.org/Simple_Asset" ) prop = model.Property( - id_short='ExampleProperty', + id_short="ExampleProperty", value_type=model.datatypes.String, - value='exampleValue', + value="exampleValue", semantic_id=model.ExternalReference( - (model.Key( - type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/Properties/SimpleProperty' - ),) - ) -) -submodel = Submodel( - id_='https://example.org/Simple_Submodel', - submodel_element={prop} + (model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, value="http://example.org/Properties/SimpleProperty"),) + ), ) +submodel = Submodel(id_="https://example.org/Simple_Submodel", submodel_element={prop}) aas = AssetAdministrationShell( - id_='https://example.org/Simple_AAS', + id_="https://example.org/Simple_AAS", asset_information=asset_information, - submodel={model.ModelReference.from_referable(submodel)} + submodel={model.ModelReference.from_referable(submodel)}, ) @@ -80,8 +72,7 @@ # Step 3: Retrieving Objects From the Store by Their Identifier # ################################################################# -tmp_submodel = identifiable_store.get_item( - 'https://example.org/Simple_Submodel') +tmp_submodel = identifiable_store.get_item("https://example.org/Simple_Submodel") assert submodel is tmp_submodel @@ -92,8 +83,7 @@ # The `aas` object already contains a reference to the submodel. # Let's create a list of all submodels, to which the AAS has references, by resolving each of the submodel references: -submodels = [reference.resolve(identifiable_store) - for reference in aas.submodel] +submodels = [reference.resolve(identifiable_store) for reference in aas.submodel] # The first (and only) element of this list should be our example submodel: assert submodel is submodels[0] @@ -102,14 +92,11 @@ # identifying the submodel by its id, the second one resolving to the Property within the submodel by its # idShort. property_reference = model.ModelReference( - (model.Key( - type_=model.KeyTypes.SUBMODEL, - value='https://example.org/Simple_Submodel'), - model.Key( - type_=model.KeyTypes.PROPERTY, - value='ExampleProperty'), - ), - type_=model.Property + ( + model.Key(type_=model.KeyTypes.SUBMODEL, value="https://example.org/Simple_Submodel"), + model.Key(type_=model.KeyTypes.PROPERTY, value="ExampleProperty"), + ), + type_=model.Property, ) # Now, we can resolve this new reference. diff --git a/sdk/basyx/aas/model/_string_constraints.py b/sdk/basyx/aas/model/_string_constraints.py index da4c8b178..a7491a63e 100644 --- a/sdk/basyx/aas/model/_string_constraints.py +++ b/sdk/basyx/aas/model/_string_constraints.py @@ -32,34 +32,43 @@ _T = TypeVar("_T") -AASD130_RE = re.compile("[\x09\x0A\x0D\x20-\uD7FF\uE000-\uFFFD\U00010000-\U0010FFFF]*") +AASD130_RE = re.compile("[\x09\x0a\x0d\x20-\ud7ff\ue000-\ufffd\U00010000-\U0010ffff]*") def _unicode_escape(value: str) -> str: """ - Escapes unicode characters such as \uD7FF, that may be used in regular expressions, for better error messages. + Escapes unicode characters such as \ud7ff, that may be used in regular expressions, for better error messages. """ return value.encode("unicode_escape").decode("utf-8") # Functions to verify the constraints for a given value. -def check(value: str, type_name: str, min_length: int = 0, max_length: Optional[int] = None, - pattern: Optional[re.Pattern] = None) -> None: +def check( + value: str, + type_name: str, + min_length: int = 0, + max_length: Optional[int] = None, + pattern: Optional[re.Pattern] = None, +) -> None: if len(value) < min_length: raise ValueError(f"{type_name} has a minimum length of {min_length}! (length: {len(value)})") if max_length is not None and len(value) > max_length: raise ValueError(f"{type_name} has a maximum length of {max_length}! (length: {len(value)})") if pattern is not None and not pattern.fullmatch(value): - raise ValueError(f"{type_name} must match the pattern '{_unicode_escape(pattern.pattern)}'! " - f"(value: '{_unicode_escape(value)}')") + raise ValueError( + f"{type_name} must match the pattern '{_unicode_escape(pattern.pattern)}'! " + f"(value: '{_unicode_escape(value)}')" + ) # Constraint AASd-130: an attribute with data type "string" shall consist of these characters only: if not AASD130_RE.fullmatch(value): # It's easier to implement this as a ValueError, because otherwise AASConstraintViolation would need to be # imported from `base` and the ConstrainedLangStringSet would need to except AASConstraintViolation errors # as well, while only re-raising ValueErrors. Thus, even if an AASConstraintViolation would be raised here, # in case of a ConstrainedLangStringSet it would be re-raised as a ValueError anyway. - raise ValueError(f"Every string must match the pattern '{_unicode_escape(AASD130_RE.pattern)}'! " - f"(value: '{_unicode_escape(value)}')") + raise ValueError( + f"Every string must match the pattern '{_unicode_escape(AASD130_RE.pattern)}'! " + f"(value: '{_unicode_escape(value)}')" + ) def check_content_type(value: str, type_name: str = "ContentType") -> None: @@ -102,8 +111,9 @@ def check_version_type(value: str, type_name: str = "VersionType") -> None: return check(value, type_name, 1, 4, re.compile(r"([0-9]|[1-9][0-9]*)")) -def create_check_function(min_length: int = 0, max_length: Optional[int] = None, pattern: Optional[re.Pattern] = None) \ - -> Callable[[str, str], None]: +def create_check_function( + min_length: int = 0, max_length: Optional[int] = None, pattern: Optional[re.Pattern] = None +) -> Callable[[str, str], None]: """ Returns a ``check_type`` function for the given constraints. @@ -112,14 +122,15 @@ def create_check_function(min_length: int = 0, max_length: Optional[int] = None, :class:`ConstrainedLangStringSets ` that define their own length and pattern rules. """ + def check_fn(value: str, type_name: str) -> None: return check(value, type_name, min_length, max_length, pattern) + return check_fn # Decorator functions to add getter/setter to classes for verification, whenever a value is updated. -def constrain_attr(pub_attr_name: str, constraint_check_fn: Callable[[str], None]) \ - -> Callable[[Type[_T]], Type[_T]]: +def constrain_attr(pub_attr_name: str, constraint_check_fn: Callable[[str], None]) -> Callable[[Type[_T]], Type[_T]]: def decorator_fn(decorated_class: Type[_T]) -> Type[_T]: def _getter(self) -> Optional[str]: return getattr(self, "_" + pub_attr_name) diff --git a/sdk/basyx/aas/model/aas.py b/sdk/basyx/aas/model/aas.py index 4a9fb4640..a2ee45911 100644 --- a/sdk/basyx/aas/model/aas.py +++ b/sdk/basyx/aas/model/aas.py @@ -49,12 +49,14 @@ class AssetInformation: :ivar default_thumbnail: Thumbnail of the asset represented by the asset administration shell. Used as default. """ - def __init__(self, - asset_kind: base.AssetKind = base.AssetKind.INSTANCE, - global_asset_id: Optional[base.Identifier] = None, - specific_asset_id: Iterable[base.SpecificAssetId] = (), - asset_type: Optional[base.Identifier] = None, - default_thumbnail: Optional[base.Resource] = None): + def __init__( + self, + asset_kind: base.AssetKind = base.AssetKind.INSTANCE, + global_asset_id: Optional[base.Identifier] = None, + specific_asset_id: Iterable[base.SpecificAssetId] = (), + asset_type: Optional[base.Identifier] = None, + default_thumbnail: Optional[base.Resource] = None, + ): super().__init__() self.asset_kind: base.AssetKind = asset_kind @@ -64,7 +66,7 @@ def __init__(self, self._specific_asset_id: base.ConstrainedList[base.SpecificAssetId] = base.ConstrainedList( specific_asset_id, item_set_hook=self._check_constraint_set_spec_asset_id, - item_del_hook=self._check_constraint_del_spec_asset_id + item_del_hook=self._check_constraint_del_spec_asset_id, ) self._global_asset_id: Optional[base.Identifier] = global_asset_id self._validate_global_asset_id(global_asset_id) @@ -89,14 +91,17 @@ def specific_asset_id(self, specific_asset_id: Iterable[base.SpecificAssetId]) - # constraints are checked via _check_constraint_set_spec_asset_id() in this case self._specific_asset_id[:] = specific_asset_id - def _check_constraint_set_spec_asset_id(self, items_to_replace: List[base.SpecificAssetId], - new_items: List[base.SpecificAssetId], - old_list: List[base.SpecificAssetId]) -> None: - self._validate_aasd_131(self.global_asset_id, - len(old_list) - len(items_to_replace) + len(new_items) > 0) - - def _check_constraint_del_spec_asset_id(self, _item_to_del: base.SpecificAssetId, - old_list: List[base.SpecificAssetId]) -> None: + def _check_constraint_set_spec_asset_id( + self, + items_to_replace: List[base.SpecificAssetId], + new_items: List[base.SpecificAssetId], + old_list: List[base.SpecificAssetId], + ) -> None: + self._validate_aasd_131(self.global_asset_id, len(old_list) - len(items_to_replace) + len(new_items) > 0) + + def _check_constraint_del_spec_asset_id( + self, _item_to_del: base.SpecificAssetId, old_list: List[base.SpecificAssetId] + ) -> None: self._validate_aasd_131(self.global_asset_id, len(old_list) > 1) @staticmethod @@ -107,15 +112,23 @@ def _validate_global_asset_id(global_asset_id: Optional[base.Identifier]) -> Non @staticmethod def _validate_aasd_131(global_asset_id: Optional[base.Identifier], specific_asset_id_nonempty: bool) -> None: if global_asset_id is None and not specific_asset_id_nonempty: - raise base.AASConstraintViolation(131, - "An AssetInformation has to have a globalAssetId or a specificAssetId") + raise base.AASConstraintViolation( + 131, "An AssetInformation has to have a globalAssetId or a specificAssetId" + ) if global_asset_id is not None: _string_constraints.check_identifier(global_asset_id) def __repr__(self) -> str: - return "AssetInformation(assetKind={}, globalAssetId={}, specificAssetId={}, assetType={}, " \ - "defaultThumbnail={})".format(self.asset_kind, self._global_asset_id, str(self.specific_asset_id), - self.asset_type, str(self.default_thumbnail)) + return ( + "AssetInformation(assetKind={}, globalAssetId={}, specificAssetId={}, assetType={}, " + "defaultThumbnail={})".format( + self.asset_kind, + self._global_asset_id, + str(self.specific_asset_id), + self.asset_type, + str(self.default_thumbnail), + ) + ) class AssetAdministrationShell(base.Identifiable, base.UniqueIdShortNamespace, base.HasDataSpecification): @@ -145,20 +158,22 @@ class AssetAdministrationShell(base.Identifiable, base.UniqueIdShortNamespace, b :ivar extension: An extension of the element. (from :class:`~basyx.aas.model.base.HasExtension`) """ - def __init__(self, - asset_information: AssetInformation, - id_: base.Identifier, - id_short: Optional[base.NameType] = None, - display_name: Optional[base.MultiLanguageNameType] = None, - category: Optional[base.NameType] = None, - description: Optional[base.MultiLanguageTextType] = None, - parent: Optional[base.UniqueIdShortNamespace] = None, - administration: Optional[base.AdministrativeInformation] = None, - submodel: Optional[Set[base.ModelReference[Submodel]]] = None, - derived_from: Optional[base.ModelReference["AssetAdministrationShell"]] = None, - embedded_data_specifications: Iterable[base.EmbeddedDataSpecification] - = (), - extension: Iterable[base.Extension] = ()): + + def __init__( + self, + asset_information: AssetInformation, + id_: base.Identifier, + id_short: Optional[base.NameType] = None, + display_name: Optional[base.MultiLanguageNameType] = None, + category: Optional[base.NameType] = None, + description: Optional[base.MultiLanguageTextType] = None, + parent: Optional[base.UniqueIdShortNamespace] = None, + administration: Optional[base.AdministrativeInformation] = None, + submodel: Optional[Set[base.ModelReference[Submodel]]] = None, + derived_from: Optional[base.ModelReference["AssetAdministrationShell"]] = None, + embedded_data_specifications: Iterable[base.EmbeddedDataSpecification] = (), + extension: Iterable[base.Extension] = (), + ): super().__init__() self.id: base.Identifier = id_ self.asset_information: AssetInformation = asset_information diff --git a/sdk/basyx/aas/model/base.py b/sdk/basyx/aas/model/base.py index bd64a1e6d..2e564b1c8 100644 --- a/sdk/basyx/aas/model/base.py +++ b/sdk/basyx/aas/model/base.py @@ -13,8 +13,26 @@ import inspect import itertools from enum import Enum, unique -from typing import List, Optional, Set, TypeVar, MutableSet, Generic, Iterable, Dict, Iterator, Union, overload, \ - MutableSequence, Type, Any, TYPE_CHECKING, Tuple, Callable, MutableMapping +from typing import ( + List, + Optional, + Set, + TypeVar, + MutableSet, + Generic, + Iterable, + Dict, + Iterator, + Union, + overload, + MutableSequence, + Type, + Any, + TYPE_CHECKING, + Tuple, + Callable, + MutableMapping, +) import re from . import datatypes, _string_constraints @@ -41,7 +59,7 @@ VersionType = str ValueTypeIEC61360 = str -MAX_RECURSION_DEPTH = 32*2 # see https://github.com/admin-shell-io/aas-specs-metamodel/issues/333 +MAX_RECURSION_DEPTH = 32 * 2 # see https://github.com/admin-shell-io/aas-specs-metamodel/issues/333 @unique @@ -160,7 +178,7 @@ def is_aas_submodel_element(self) -> bool: self.RELATIONSHIP_ELEMENT, self.SUBMODEL_ELEMENT, self.SUBMODEL_ELEMENT_COLLECTION, - self.SUBMODEL_ELEMENT_LIST + self.SUBMODEL_ELEMENT_LIST, ) @property @@ -291,6 +309,7 @@ class LangStringSet(MutableMapping[str, str]): "en-GB" for English (United Kingdom) and English (United States). IETF language tags are referencing ISO 639, ISO 3166 and ISO 15924. """ + def __init__(self, dict_: Dict[str, str]): self._dict: Dict[str, str] = {} if not isinstance(dict_, dict): @@ -312,27 +331,20 @@ def _check_language_tag_constraints(cls, ltag: str): "i-klingon|i-lux|i-mingo|i-navajo|i-pwn|i-tao|i-tay|" "i-tsu|sgn-BE-FR|sgn-BE-NL|sgn-CH-DE)" ) - regular = ( - "(art-lojban|cel-gaulish|no-bok|no-nyn|zh-guoyu|zh-hakka|" - "zh-min|zh-min-nan|zh-xiang)" - ) + regular = "(art-lojban|cel-gaulish|no-bok|no-nyn|zh-guoyu|zh-hakka|zh-min|zh-min-nan|zh-xiang)" grandfathered = f"({irregular}|{regular})" language = f"([a-zA-Z]{{2,3}}(-{extlang})?|[a-zA-Z]{{4}}|[a-zA-Z]{{5,8}})" script = "[a-zA-Z]{4}" region = "([a-zA-Z]{2}|[0-9]{3})" variant = f"(({alphanum}){{5,8}}|[0-9]({alphanum}){{3}})" privateuse = f"[xX](-({alphanum}){{1,8}})+" - langtag = ( - f"{language}(-{script})?(-{region})?(-{variant})*(-{extension})*(-" - f"{privateuse})?" - ) + langtag = f"{language}(-{script})?(-{region})?(-{variant})*(-{extension})*(-{privateuse})?" language_tag = f"({langtag}|{privateuse}|{grandfathered})" pattern = f"^{language_tag}$" if re.match(pattern, ltag) is None: - raise ValueError(f"The language tag must follow the format defined in BCP 47. " - f"Given language tag: {ltag}") + raise ValueError(f"The language tag must follow the format defined in BCP 47. Given language tag: {ltag}") def __getitem__(self, item: str) -> str: return self._dict[item] @@ -363,6 +375,7 @@ class ConstrainedLangStringSet(LangStringSet, metaclass=abc.ABCMeta): """ A :class:`LangStringSet` with constrained values. """ + @abc.abstractmethod def __init__(self, dict_: Dict[str, str], constraint_check_fn: Callable[[str, str], None]): super().__init__(dict_) @@ -386,6 +399,7 @@ class MultiLanguageNameType(ConstrainedLangStringSet): A :class:`~.ConstrainedLangStringSet` where each value is a :class:`NameType`. See also: :func:`basyx.aas.model._string_constraints.check_name_type` """ + def __init__(self, dict_: Dict[str, str]): super().__init__(dict_, _string_constraints.check_name_type) @@ -394,6 +408,7 @@ class MultiLanguageTextType(ConstrainedLangStringSet): """ A :class:`~.ConstrainedLangStringSet` where each value must have at least 1 and at most 1023 characters. """ + def __init__(self, dict_: Dict[str, str]): super().__init__(dict_, _string_constraints.create_check_function(min_length=1, max_length=1023)) @@ -402,6 +417,7 @@ class DefinitionTypeIEC61360(ConstrainedLangStringSet): """ A :class:`~.ConstrainedLangStringSet` where each value must have at least 1 and at most 1023 characters. """ + def __init__(self, dict_: Dict[str, str]): super().__init__(dict_, _string_constraints.create_check_function(min_length=1, max_length=1023)) @@ -410,6 +426,7 @@ class PreferredNameTypeIEC61360(ConstrainedLangStringSet): """ A :class:`~.ConstrainedLangStringSet` where each value must have at least 1 and at most 255 characters. """ + def __init__(self, dict_: Dict[str, str]): super().__init__(dict_, _string_constraints.create_check_function(min_length=1, max_length=255)) @@ -418,6 +435,7 @@ class ShortNameTypeIEC61360(ConstrainedLangStringSet): """ A :class:`~.ConstrainedLangStringSet` where each value must have at least 1 and at most 18 characters. """ + def __init__(self, dict_: Dict[str, str]): super().__init__(dict_, _string_constraints.create_check_function(min_length=1, max_length=18)) @@ -432,21 +450,19 @@ class Key: :ivar value: The key value, for example an IRDI or IRI """ - def __init__(self, - type_: KeyTypes, - value: Identifier): + def __init__(self, type_: KeyTypes, value: Identifier): """ TODO: Add instruction what to do after construction """ _string_constraints.check_identifier(value) self.type: KeyTypes self.value: Identifier - super().__setattr__('type', type_) - super().__setattr__('value', value) + super().__setattr__("type", type_) + super().__setattr__("value", value) def __setattr__(self, key, value): """Prevent modification of attributes.""" - raise AttributeError('Reference is immutable') + raise AttributeError("Reference is immutable") def __repr__(self) -> str: return "Key(type={}, value={})".format(self.type.name, self.value) @@ -457,8 +473,7 @@ def __str__(self) -> str: def __eq__(self, other: object) -> bool: if not isinstance(other, Key): return NotImplemented - return (self.value == other.value - and self.type == other.type) + return self.value == other.value and self.type == other.type def __hash__(self): return hash((self.value, self.type)) @@ -490,6 +505,7 @@ def from_referable(referable: "Referable") -> "Key": @staticmethod def _get_key_type_for_referable(referable: "Referable") -> KeyTypes: from . import KEY_TYPES_CLASSES, resolve_referable_class_in_key_types + ref_type = resolve_referable_class_in_key_types(referable) key_type = KEY_TYPES_CLASSES[ref_type] return key_type @@ -497,6 +513,7 @@ def _get_key_type_for_referable(referable: "Referable") -> KeyTypes: @staticmethod def _get_key_value_for_referable(referable: "Referable") -> str: from . import SubmodelElementList + if isinstance(referable, Identifiable): return referable.id elif isinstance(referable.parent, SubmodelElementList): @@ -510,7 +527,7 @@ def _get_key_value_for_referable(referable: "Referable") -> str: return referable.id_short -_NSO = TypeVar('_NSO', bound=Union["Referable", "Qualifier", "HasSemantics", "Extension"]) +_NSO = TypeVar("_NSO", bound=Union["Referable", "Qualifier", "HasSemantics", "Extension"]) class Namespace(metaclass=abc.ABCMeta): @@ -522,6 +539,7 @@ class Namespace(metaclass=abc.ABCMeta): :ivar namespace_element_sets: List of :class:`NamespaceSets ` """ + @abc.abstractmethod def __init__(self) -> None: super().__init__() @@ -581,6 +599,7 @@ class HasExtension(Namespace, metaclass=abc.ABCMeta): :ivar namespace_element_sets: List of :class:`NamespaceSets ` :ivar extension: A :class:`~.NamespaceSet` of :class:`Extensions <.Extension>` of the element. """ + @abc.abstractmethod def __init__(self) -> None: super().__init__() @@ -639,6 +658,7 @@ class Referable(HasExtension, metaclass=abc.ABCMeta): :ivar parent: Reference (in form of a :class:`~.UniqueIdShortNamespace`) to the next referable parent element of the element. """ + @abc.abstractmethod def __init__(self): super().__init__() @@ -678,8 +698,9 @@ def get_identifiable_root(self) -> Optional["Identifiable"]: elif isinstance(item, Referable): item = item.parent else: - raise AttributeError('Referable must have an identifiable as root object and only parents that are ' - 'referable') + raise AttributeError( + "Referable must have an identifiable as root object and only parents that are referable" + ) return None def get_id_short_path(self) -> str: @@ -702,16 +723,20 @@ def get_id_short_path_as_a_list(self) -> List[str]: :class:`~.Identifiable` """ from .submodel import SubmodelElementList + if self.id_short is None and not isinstance(self.parent, SubmodelElementList): - raise ValueError(f"Can't create id_short_path for {self.__class__.__name__} without an id_short or " - f"if its parent is a SubmodelElementList!") + raise ValueError( + f"Can't create id_short_path for {self.__class__.__name__} without an id_short or " + f"if its parent is a SubmodelElementList!" + ) item = self # type: Any path: List[str] = [] while item is not None: if not isinstance(item, Referable): - raise AttributeError('Referable must have an identifiable as root object and only parents that are ' - 'referable') + raise AttributeError( + "Referable must have an identifiable as root object and only parents that are referable" + ) if isinstance(item, Identifiable): break elif isinstance(item.parent, SubmodelElementList): @@ -749,12 +774,12 @@ def parse_id_short_path(cls, id_short_path: str) -> List[str]: """ id_shorts_and_indexes = [] for part in id_short_path.split("."): - id_short = part[0:part.find('[')] if '[' in part else part + id_short = part[0 : part.find("[")] if "[" in part else part id_shorts_and_indexes.append(id_short) indexes_part = part.removeprefix(id_short) if indexes_part: - if not re.fullmatch(r'(?:\[\d+\])+', indexes_part): + if not re.fullmatch(r"(?:\[\d+\])+", indexes_part): raise ValueError(f"Invalid index format in id_short_path: '{id_short_path}', part: '{part}'") indexes = indexes_part.strip("[]").split("][") id_shorts_and_indexes.extend(indexes) @@ -799,20 +824,11 @@ def validate_id_short(cls, id_short: NameType) -> None: _string_constraints.check_name_type(id_short) test_id_short: NameType = str(id_short) if not re.fullmatch("[A-Za-z0-9_-]*", test_id_short): - raise AASConstraintViolation( - 2, - "The id_short must contain only letters, digits underscore and hyphen" - ) + raise AASConstraintViolation(2, "The id_short must contain only letters, digits underscore and hyphen") if not test_id_short[0].isalpha(): - raise AASConstraintViolation( - 2, - "The id_short must start with a letter" - ) + raise AASConstraintViolation(2, "The id_short must start with a letter") if test_id_short.endswith("-"): - raise AASConstraintViolation( - 2, - "The id_short must not end with a hyphen" - ) + raise AASConstraintViolation(2, "The id_short must not end with a hyphen") category = property(_get_category, _set_category) @@ -841,13 +857,16 @@ def _set_id_short(self, id_short: Optional[NameType]): if self.parent is not None: if id_short is None: - raise AASConstraintViolation(117, f"id_short of {self!r} cannot be unset, since it is already " - f"contained in {self.parent!r}") + raise AASConstraintViolation( + 117, f"id_short of {self!r} cannot be unset, since it is already contained in {self.parent!r}" + ) from .submodel import SubmodelElementList + for set_ in self.parent.namespace_element_sets: if set_.contains_id("id_short", id_short): - raise AASConstraintViolation(22, "Object with id_short '{}' is already present in the parent " - "Namespace".format(id_short)) + raise AASConstraintViolation( + 22, "Object with id_short '{}' is already present in the parent Namespace".format(id_short) + ) set_add_list: List[NamespaceSet] = [] for set_ in self.parent.namespace_element_sets: @@ -893,7 +912,7 @@ def update_from(self, other: "Referable"): """ for name in dir(other): # Skip private and protected attributes - if name.startswith('_'): + if name.startswith("_"): continue # Do not update 'parent', 'namespace_element_sets' @@ -920,7 +939,7 @@ def update_from(self, other: "Referable"): id_short = property(_get_id_short, _set_id_short) -_RT = TypeVar('_RT', bound=Referable) +_RT = TypeVar("_RT", bound=Referable) class UnexpectedTypeError(TypeError): @@ -930,6 +949,7 @@ class UnexpectedTypeError(TypeError): :ivar value: The object of unexpected type """ + def __init__(self, value: Referable, *args): super().__init__(*args) self.value = value @@ -957,6 +977,7 @@ class Reference(metaclass=abc.ABCMeta): :ivar referred_semantic_id: SemanticId of the referenced model element. For external references there typically is no semantic id. """ + @abc.abstractmethod def __init__(self, key: Tuple[Key, ...], referred_semantic_id: Optional["Reference"] = None): if len(key) < 1: @@ -966,12 +987,12 @@ def __init__(self, key: Tuple[Key, ...], referred_semantic_id: Optional["Referen self.key: Tuple[Key, ...] self.referred_semantic_id: Optional["Reference"] - super().__setattr__('key', key) - super().__setattr__('referred_semantic_id', referred_semantic_id) + super().__setattr__("key", key) + super().__setattr__("referred_semantic_id", referred_semantic_id) def __setattr__(self, key, value): """Prevent modification of attributes.""" - raise AttributeError('Reference is immutable') + raise AttributeError("Reference is immutable") def __hash__(self): return hash((self.__class__, self.key)) @@ -981,8 +1002,10 @@ def __eq__(self, other: object) -> bool: return NotImplemented if len(self.key) != len(other.key): return False - return all(k1 == k2 for k1, k2 in zip(self.key, other.key)) \ + return ( + all(k1 == k2 for k1, k2 in zip(self.key, other.key)) and self.referred_semantic_id == other.referred_semantic_id + ) class ExternalReference(Reference): @@ -1010,11 +1033,16 @@ def __init__(self, key: Tuple[Key, ...], referred_semantic_id: Optional["Referen super().__init__(key, referred_semantic_id) if not key[0].type.is_generic_globally_identifiable: - raise AASConstraintViolation(122, "The type of the first key of an ExternalReference must be a " - f"GenericGloballyIdentifiable: {key[0]!r}") + raise AASConstraintViolation( + 122, + f"The type of the first key of an ExternalReference must be a GenericGloballyIdentifiable: {key[0]!r}", + ) if not key[-1].type.is_generic_globally_identifiable and not key[-1].type.is_generic_fragment_key: - raise AASConstraintViolation(124, "The type of the last key of an ExternalReference must be a " - f"GenericGloballyIdentifiable or a GenericFragmentKey: {key[-1]!r}") + raise AASConstraintViolation( + 124, + "The type of the last key of an ExternalReference must be a " + f"GenericGloballyIdentifiable or a GenericFragmentKey: {key[-1]!r}", + ) def __repr__(self) -> str: return "ExternalReference(key={})".format(self.key) @@ -1054,31 +1082,40 @@ class ModelReference(Reference, Generic[_RT]): :ivar referred_semantic_id: SemanticId of the referenced model element. For external references there typically is no semantic id. """ + def __init__(self, key: Tuple[Key, ...], type_: Type[_RT], referred_semantic_id: Optional[Reference] = None): super().__init__(key, referred_semantic_id) if not key[0].type.is_aas_identifiable: - raise AASConstraintViolation(123, "The type of the first key of a ModelReference must be an " - f"AasIdentifiable: {key[0]!r}") + raise AASConstraintViolation( + 123, f"The type of the first key of a ModelReference must be an AasIdentifiable: {key[0]!r}" + ) for k in key[1:]: if not k.type.is_fragment_key_element: - raise AASConstraintViolation(125, "The type of all keys following the first of a ModelReference " - f"must be one of FragmentKeyElements: {k!r}") + raise AASConstraintViolation( + 125, + "The type of all keys following the first of a ModelReference " + f"must be one of FragmentKeyElements: {k!r}", + ) if not key[-1].type.is_generic_fragment_key: for k in key[:-1]: if k.type.is_generic_fragment_key: - raise AASConstraintViolation(126, f"Key {k!r} is a GenericFragmentKey, " - f"but the last key of the chain is not: {key[-1]!r}") + raise AASConstraintViolation( + 126, f"Key {k!r} is a GenericFragmentKey, but the last key of the chain is not: {key[-1]!r}" + ) for pk, k in zip(key, key[1:]): if k.type == KeyTypes.FRAGMENT_REFERENCE and pk.type not in (KeyTypes.BLOB, KeyTypes.FILE): raise AASConstraintViolation(127, f"{k!r} is not preceded by a key of type File or Blob, but {pk!r}") if pk.type == KeyTypes.SUBMODEL_ELEMENT_LIST and not k.value.isnumeric(): - raise AASConstraintViolation(128, f"Key {pk!r} references a SubmodelElementList, " - f"but the value of the succeeding key ({k!r}) is not a non-negative " - f"integer: {k.value}") + raise AASConstraintViolation( + 128, + f"Key {pk!r} references a SubmodelElementList, " + f"but the value of the succeeding key ({k!r}) is not a non-negative " + f"integer: {k.value}", + ) self.type: Type[_RT] - object.__setattr__(self, 'type', type_) + object.__setattr__(self, "type", type_) def resolve(self, provider_: "provider.AbstractObjectProvider") -> _RT: """ @@ -1110,13 +1147,16 @@ def resolve(self, provider_: "provider.AbstractObjectProvider") -> _RT: # id_short path via get_referable(). # This is cursed af, but at least it keeps the code DRY. get_referable() will check the type of self in the # first iteration, so we can ignore the type here. - item = UniqueIdShortNamespace.get_referable(item, # type: ignore[arg-type] - map(lambda k: k.value, self.key[1:])) + item = UniqueIdShortNamespace.get_referable( + item, # type: ignore[arg-type] + map(lambda k: k.value, self.key[1:]), + ) # Check type if not isinstance(item, self.type): - raise UnexpectedTypeError(item, "Retrieved object {} is not an instance of referenced type {}" - .format(item, self.type.__name__)) + raise UnexpectedTypeError( + item, "Retrieved object {} is not an instance of referenced type {}".format(item, self.type.__name__) + ) return item def get_identifier(self) -> Identifier: @@ -1128,13 +1168,13 @@ def get_identifier(self) -> Identifier: :raises ValueError: If this :class:`~.ModelReference` does not include a Key of AasIdentifiable type """ try: - last_identifier = next(key.get_identifier() - for key in reversed(self.key) - if key.get_identifier()) + last_identifier = next(key.get_identifier() for key in reversed(self.key) if key.get_identifier()) return last_identifier # type: ignore # MyPy doesn't get the generator expression above except StopIteration: - raise ValueError("ModelReference cannot be represented as an Identifier, since it does not contain a Key" - f" of an AasIdentifiable type ({[t.name for t in KeyTypes if t.is_aas_identifiable]})") + raise ValueError( + "ModelReference cannot be represented as an Identifier, since it does not contain a Key" + f" of an AasIdentifiable type ({[t.name for t in KeyTypes if t.is_aas_identifiable]})" + ) def __repr__(self) -> str: return "ModelReference<{}>(key={})".format(self.type.__name__, self.key) @@ -1157,6 +1197,7 @@ def from_referable(referable: Referable) -> "ModelReference": """ # Get the first class from the base classes list (via inspect.getmro), that is contained in KEY_ELEMENTS_CLASSES from . import resolve_referable_class_in_key_types + try: ref_type = resolve_referable_class_in_key_types(referable) except StopIteration: @@ -1172,8 +1213,10 @@ def from_referable(referable: Referable) -> "ModelReference": raise ValueError(f"The given Referable object is not embedded within an Identifiable object: {ref}") ref = ref.parent if len(keys) > MAX_RECURSION_DEPTH: - raise ValueError(f"The given Referable object is embedded in >64 layers of Referables " - f"or there is a loop in the parent chain {ref}") + raise ValueError( + f"The given Referable object is embedded in >64 layers of Referables " + f"or there is a loop in the parent chain {ref}" + ) @_string_constraints.constrain_content_type("content_type") @@ -1187,6 +1230,7 @@ class Resource: :ivar content_type: Content type of the content of the file. The content type states which file extensions the file can have. """ + def __init__(self, path: PathType, content_type: Optional[ContentType] = None): self.path: PathType = path self.content_type: Optional[ContentType] = content_type @@ -1206,6 +1250,7 @@ class DataSpecificationContent: shall contain the external reference to the IRI of the corresponding data specification template ``https://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/1`` """ + @abc.abstractmethod def __init__(self): pass @@ -1218,6 +1263,7 @@ class EmbeddedDataSpecification: :ivar data_specification: Reference to the data specification :ivar data_specification_content: Actual content of the data specification """ + def __init__( self, data_specification: Reference, @@ -1243,6 +1289,7 @@ class HasDataSpecification(metaclass=abc.ABCMeta): :ivar embedded_data_specifications: List of :class:`~.EmbeddedDataSpecification`. """ + @abc.abstractmethod def __init__( self, @@ -1281,12 +1328,14 @@ class AdministrativeInformation(HasDataSpecification): The creation of submodel templates can also be guided by another submodel template. """ - def __init__(self, - version: Optional[VersionType] = None, - revision: Optional[RevisionType] = None, - creator: Optional[Reference] = None, - template_id: Optional[Identifier] = None, - embedded_data_specifications: Iterable[EmbeddedDataSpecification] = ()): + def __init__( + self, + version: Optional[VersionType] = None, + revision: Optional[RevisionType] = None, + creator: Optional[Reference] = None, + template_id: Optional[Identifier] = None, + embedded_data_specifications: Iterable[EmbeddedDataSpecification] = (), + ): """ Initializer of AdministrativeInformation @@ -1307,8 +1356,11 @@ def _get_revision(self): def _set_revision(self, revision: Optional[RevisionType]): if self.version is None and revision: - raise AASConstraintViolation(5, "A revision requires a version. This means, if there is no version " - "there is no revision neither. Please set version first.") + raise AASConstraintViolation( + 5, + "A revision requires a version. This means, if there is no version " + "there is no revision neither. Please set version first.", + ) if revision is not None: _string_constraints.check_revision_type(revision) self._revision = revision @@ -1318,14 +1370,17 @@ def _set_revision(self, revision: Optional[RevisionType]): def __eq__(self, other) -> bool: if not isinstance(other, AdministrativeInformation): return NotImplemented - return self.version == other.version \ - and self._revision == other._revision \ - and self.creator == other.creator \ + return ( + self.version == other.version + and self._revision == other._revision + and self.creator == other.creator and self.template_id == other.template_id + ) def __repr__(self) -> str: return "AdministrativeInformation(version={}, revision={}, creator={}, template_id={})".format( - self.version, self.revision, self.creator, self.template_id) + self.version, self.revision, self.creator, self.template_id + ) @_string_constraints.constrain_identifier("id") @@ -1339,6 +1394,7 @@ class Identifiable(Referable, metaclass=abc.ABCMeta): :ivar administration: :class:`~.AdministrativeInformation` of an identifiable element. :ivar id: The globally unique id of the element. """ + @abc.abstractmethod def __init__(self) -> None: super().__init__() @@ -1371,9 +1427,13 @@ class ConstrainedList(MutableSequence[_T], Generic[_T]): or ``del list[i]``. It is passed the item about to be deleted and the current list elements. """ - def __init__(self, items: Iterable[_T], item_add_hook: Optional[Callable[[_T, List[_T]], None]] = None, - item_set_hook: Optional[Callable[[List[_T], List[_T], List[_T]], None]] = None, - item_del_hook: Optional[Callable[[_T, List[_T]], None]] = None) -> None: + def __init__( + self, + items: Iterable[_T], + item_add_hook: Optional[Callable[[_T, List[_T]], None]] = None, + item_set_hook: Optional[Callable[[List[_T], List[_T], List[_T]], None]] = None, + item_del_hook: Optional[Callable[[_T, List[_T]], None]] = None, + ) -> None: super().__init__() self._list: List[_T] = [] self._item_add_hook: Optional[Callable[[_T, List[_T]], None]] = item_add_hook @@ -1472,6 +1532,7 @@ class HasSemantics(metaclass=abc.ABCMeta): :ivar supplemental_semantic_id: Identifier of a supplemental semantic definition of the element. It is called supplemental semantic ID of the element. """ + @abc.abstractmethod def __init__(self) -> None: super().__init__() @@ -1479,7 +1540,8 @@ def __init__(self) -> None: # of Referable.parent as `UniqueIdShortNamespace` self.parent: Optional[Any] = None self._supplemental_semantic_id: ConstrainedList[Reference] = ConstrainedList( - [], item_add_hook=self._check_constraint_add) + [], item_add_hook=self._check_constraint_add + ) self._semantic_id: Optional[Reference] = None def _check_constraint_add(self, _new: Reference, _list: List[Reference]) -> None: @@ -1493,8 +1555,11 @@ def semantic_id(self) -> Optional[Reference]: @semantic_id.setter def semantic_id(self, semantic_id: Optional[Reference]) -> None: if semantic_id is None and len(self.supplemental_semantic_id) > 0: - raise AASConstraintViolation(118, "semantic_id can not be removed while there is at least one " - f"supplemental_semantic_id: {self.supplemental_semantic_id!r}") + raise AASConstraintViolation( + 118, + "semantic_id can not be removed while there is at least one " + f"supplemental_semantic_id: {self.supplemental_semantic_id!r}", + ) if self.parent is not None: if semantic_id is not None: for set_ in self.parent.namespace_element_sets: @@ -1534,13 +1599,15 @@ class Extension(HasSemantics): :class:`~basyx.aas.model.base.HasSemantics`) """ - def __init__(self, - name: NameType, - value_type: Optional[DataTypeDefXsd] = None, - value: Optional[ValueDataType] = None, - refers_to: Iterable[ModelReference] = (), - semantic_id: Optional[Reference] = None, - supplemental_semantic_id: Iterable[Reference] = ()): + def __init__( + self, + name: NameType, + value_type: Optional[DataTypeDefXsd] = None, + value: Optional[ValueDataType] = None, + refers_to: Iterable[ModelReference] = (), + semantic_id: Optional[Reference] = None, + supplemental_semantic_id: Iterable[Reference] = (), + ): super().__init__() self.parent: Optional[HasExtension] = None self._name: NameType @@ -1565,7 +1632,7 @@ def value(self, value) -> None: self._value = None else: if self.value_type is None: - raise ValueError('ValueType must be set, if value is not None') + raise ValueError("ValueType must be set, if value is not None") self._value = datatypes.trivial_cast(value, self.value_type) @property @@ -1578,8 +1645,7 @@ def name(self, name: NameType) -> None: if self.parent is not None: for set_ in self.parent.namespace_element_sets: if set_.contains_id("name", name): - raise KeyError("Object with name '{}' is already present in the parent Namespace" - .format(name)) + raise KeyError("Object with name '{}' is already present in the parent Namespace".format(name)) set_add_list: List[NamespaceSet] = [] for set_ in self.parent.namespace_element_sets: if self in set_: @@ -1601,6 +1667,7 @@ class HasKind(metaclass=abc.ABCMeta): :ivar _kind: Kind of the element: either type or instance. Default = :attr:`~ModellingKind.INSTANCE`. """ + @abc.abstractmethod def __init__(self) -> None: super().__init__() @@ -1626,6 +1693,7 @@ class Qualifiable(Namespace, metaclass=abc.ABCMeta): :ivar qualifier: Unordered list of :class:`Qualifiers ` that gives additional qualification of a qualifiable element. """ + @abc.abstractmethod def __init__(self) -> None: super().__init__() @@ -1679,14 +1747,16 @@ class Qualifier(HasSemantics): :class:`~basyx.aas.model.base.HasSemantics`) """ - def __init__(self, - type_: QualifierType, - value_type: DataTypeDefXsd, - value: Optional[ValueDataType] = None, - value_id: Optional[Reference] = None, - kind: QualifierKind = QualifierKind.CONCEPT_QUALIFIER, - semantic_id: Optional[Reference] = None, - supplemental_semantic_id: Iterable[Reference] = ()): + def __init__( + self, + type_: QualifierType, + value_type: DataTypeDefXsd, + value: Optional[ValueDataType] = None, + value_id: Optional[Reference] = None, + kind: QualifierKind = QualifierKind.CONCEPT_QUALIFIER, + semantic_id: Optional[Reference] = None, + supplemental_semantic_id: Iterable[Reference] = (), + ): """ TODO: Add instruction what to do after construction """ @@ -1725,8 +1795,7 @@ def type(self, type_: QualifierType) -> None: if self.parent is not None: for set_ in self.parent.namespace_element_sets: if set_.contains_id("type", type_): - raise KeyError("Object with type '{}' is already present in the parent Namespace" - .format(type_)) + raise KeyError("Object with type '{}' is already present in the parent Namespace".format(type_)) set_add_list: List[NamespaceSet] = [] for set_ in self.parent.namespace_element_sets: if self in set_: @@ -1750,9 +1819,7 @@ class ValueReferencePair: :ivar value_id: Global unique id of the value. """ - def __init__(self, - value: ValueTypeIEC61360, - value_id: Optional[Reference] = None): + def __init__(self, value: ValueTypeIEC61360, value_id: Optional[Reference] = None): """ @@ -1777,6 +1844,7 @@ class UniqueIdShortNamespace(Namespace, metaclass=abc.ABCMeta): :ivar namespace_element_sets: A list of all :class:`NamespaceSets <.NamespaceSet>` of this Namespace """ + @abc.abstractmethod def __init__(self) -> None: super().__init__() @@ -1796,6 +1864,7 @@ def get_referable(self, id_short_path: Union[str, NameType, Iterable[NameType]]) :raises KeyError: If no such :class:`~.Referable` can be found """ from .submodel import SubmodelElementList + if isinstance(id_short_path, (str, NameType)): id_short_path = Referable.parse_id_short_path(id_short_path) item: Union[UniqueIdShortNamespace, Referable] = self @@ -1803,8 +1872,10 @@ def get_referable(self, id_short_path: Union[str, NameType, Iterable[NameType]]) # This is redundant on first iteration, but it's a negligible overhead. # Also, ModelReference.resolve() relies on this check. if not isinstance(item, UniqueIdShortNamespace): - raise TypeError(f"Cannot resolve id_short or index '{id_}' at {item!r}, " - f"because it is not a {UniqueIdShortNamespace.__name__}!") + raise TypeError( + f"Cannot resolve id_short or index '{id_}' at {item!r}, " + f"because it is not a {UniqueIdShortNamespace.__name__}!" + ) is_submodel_element_list = isinstance(item, SubmodelElementList) try: if is_submodel_element_list: @@ -1817,8 +1888,11 @@ def get_referable(self, id_short_path: Union[str, NameType, Iterable[NameType]]) except ValueError as e: raise ValueError(f"Cannot resolve '{id_}' at {item!r}, because it is not a numeric index!") from e except (KeyError, IndexError) as e: - raise KeyError("Referable with {} {} not found in {}".format( - "index" if is_submodel_element_list else "id_short", id_, repr(item))) from e + raise KeyError( + "Referable with {} {} not found in {}".format( + "index" if is_submodel_element_list else "id_short", id_, repr(item) + ) + ) from e # All UniqueIdShortNamespaces are Referables, and we only ever assign Referable to item. return item # type: ignore[return-value] @@ -1862,6 +1936,7 @@ class UniqueSemanticIdNamespace(Namespace, metaclass=abc.ABCMeta): :ivar namespace_element_sets: A list of all NamespaceSets of this Namespace """ + @abc.abstractmethod def __init__(self) -> None: super().__init__() @@ -1919,11 +1994,16 @@ class NamespaceSet(MutableSet[_NSO], Generic[_NSO]): :raises KeyError: When ``items`` contains multiple objects with same unique attribute """ - def __init__(self, parent: Union[UniqueIdShortNamespace, UniqueSemanticIdNamespace, Qualifiable, HasExtension], - attribute_names: List[Tuple[str, bool]], items: Iterable[_NSO] = (), - item_add_hook: Optional[Callable[[_NSO, Iterable[_NSO]], None]] = None, - item_id_set_hook: Optional[Callable[[_NSO], None]] = None, - item_id_del_hook: Optional[Callable[[_NSO], None]] = None) -> None: + + def __init__( + self, + parent: Union[UniqueIdShortNamespace, UniqueSemanticIdNamespace, Qualifiable, HasExtension], + attribute_names: List[Tuple[str, bool]], + items: Iterable[_NSO] = (), + item_add_hook: Optional[Callable[[_NSO, Iterable[_NSO]], None]] = None, + item_id_set_hook: Optional[Callable[[_NSO], None]] = None, + item_id_del_hook: Optional[Callable[[_NSO], None]] = None, + ) -> None: """ Initialize a new NamespaceSet. @@ -2017,20 +2097,28 @@ def _validate_namespace_constraints(self, element: _NSO): def _check_attr_is_not_none(self, element: _NSO, attr_name: str, attr): if attr is None: if attr_name == "id_short": - raise AASConstraintViolation(117, f"{element!r} has attribute {attr_name}=None, " - f"which is not allowed within a {self.parent.__class__.__name__}!") + raise AASConstraintViolation( + 117, + f"{element!r} has attribute {attr_name}=None, " + f"which is not allowed within a {self.parent.__class__.__name__}!", + ) else: raise ValueError(f"{element!r} has attribute {attr_name}=None, which is not allowed!") - def _check_value_is_not_in_backend(self, element: _NSO, attr_name: str, attr, - backend_dict: Dict[ATTRIBUTE_TYPES, _NSO], set_: "NamespaceSet"): + def _check_value_is_not_in_backend( + self, element: _NSO, attr_name: str, attr, backend_dict: Dict[ATTRIBUTE_TYPES, _NSO], set_: "NamespaceSet" + ): if attr in backend_dict: if set_ is self: - text = f"Object with attribute (name='{attr_name}', value='{getattr(element, attr_name)}') " \ - f"is already present in this set of objects" + text = ( + f"Object with attribute (name='{attr_name}', value='{getattr(element, attr_name)}') " + f"is already present in this set of objects" + ) else: - text = f"Object with attribute (name='{attr_name}', value='{getattr(element, attr_name)}') " \ - f"is already present in another set in the same namespace" + text = ( + f"Object with attribute (name='{attr_name}', value='{getattr(element, attr_name)}') " + f"is already present in another set in the same namespace" + ) raise AASConstraintViolation(ATTRIBUTES_CONSTRAINT_IDS.get(attr_name, 0), text) def _execute_item_id_set_hook(self, element: _NSO): @@ -2165,11 +2253,16 @@ class OrderedNamespaceSet(NamespaceSet[_NSO], MutableSequence[_NSO], Generic[_NS (actually it is derived from MutableSequence). However, we don't permit duplicate entries in the ordered list of objects. """ - def __init__(self, parent: Union[UniqueIdShortNamespace, UniqueSemanticIdNamespace, Qualifiable, HasExtension], - attribute_names: List[Tuple[str, bool]], items: Iterable[_NSO] = (), - item_add_hook: Optional[Callable[[_NSO, Iterable[_NSO]], None]] = None, - item_id_set_hook: Optional[Callable[[_NSO], None]] = None, - item_id_del_hook: Optional[Callable[[_NSO], None]] = None) -> None: + + def __init__( + self, + parent: Union[UniqueIdShortNamespace, UniqueSemanticIdNamespace, Qualifiable, HasExtension], + attribute_names: List[Tuple[str, bool]], + items: Iterable[_NSO] = (), + item_add_hook: Optional[Callable[[_NSO, Iterable[_NSO]], None]] = None, + item_id_set_hook: Optional[Callable[[_NSO], None]] = None, + item_id_del_hook: Optional[Callable[[_NSO], None]] = None, + ) -> None: """ Initialize a new OrderedNamespaceSet. @@ -2275,7 +2368,7 @@ def __delitem__(self, i: slice) -> None: ... def __delitem__(self, i: Union[int, slice]) -> None: if isinstance(i, int): - i = slice(i, i+1) + i = slice(i, i + 1) for o in self._order[i]: super().remove(o) del self._order[i] @@ -2301,12 +2394,14 @@ class SpecificAssetId(HasSemantics): :class:`~basyx.aas.model.base.HasSemantics`) """ - def __init__(self, - name: LabelType, - value: Identifier, - external_subject_id: Optional[ExternalReference] = None, - semantic_id: Optional[Reference] = None, - supplemental_semantic_id: Iterable[Reference] = ()): + def __init__( + self, + name: LabelType, + value: Identifier, + external_subject_id: Optional[ExternalReference] = None, + semantic_id: Optional[Reference] = None, + supplemental_semantic_id: Iterable[Reference] = (), + ): super().__init__() if value == "": raise ValueError("value is not allowed to be an empty string") @@ -2316,11 +2411,11 @@ def __init__(self, self.value: Identifier self.external_subject_id: ExternalReference - super().__setattr__('name', name) - super().__setattr__('value', value) - super().__setattr__('external_subject_id', external_subject_id) - super().__setattr__('semantic_id', semantic_id) - super().__setattr__('supplemental_semantic_id', supplemental_semantic_id) + super().__setattr__("name", name) + super().__setattr__("value", value) + super().__setattr__("external_subject_id", external_subject_id) + super().__setattr__("semantic_id", semantic_id) + super().__setattr__("supplemental_semantic_id", supplemental_semantic_id) def __setattr__(self, key, value): """Prevent modification of attributes.""" @@ -2328,27 +2423,31 @@ def __setattr__(self, key, value): # HasSemantics.__init__ sets the parent attribute to None, so that has to be possible. It needs to be set # because its value is checked in the semantic_id setter and since every subclass of HasSemantics is expected # to have this attribute. Additionally, the protected _semantic_id attribute must be settable. - if key == '_semantic_id' or key == '_supplemental_semantic_id' or (key == 'parent' and value is None): + if key == "_semantic_id" or key == "_supplemental_semantic_id" or (key == "parent" and value is None): return super(HasSemantics, self).__setattr__(key, value) - raise AttributeError('SpecificAssetId is immutable') + raise AttributeError("SpecificAssetId is immutable") def __eq__(self, other: object) -> bool: if not isinstance(other, SpecificAssetId): return NotImplemented - return (self.name == other.name - and self.value == other.value - and self.external_subject_id == other.external_subject_id - and self.semantic_id == other.semantic_id - and self.supplemental_semantic_id == other.supplemental_semantic_id) + return ( + self.name == other.name + and self.value == other.value + and self.external_subject_id == other.external_subject_id + and self.semantic_id == other.semantic_id + and self.supplemental_semantic_id == other.supplemental_semantic_id + ) def __hash__(self): return hash((self.name, self.value, self.external_subject_id)) def __repr__(self) -> str: - return "SpecificAssetId(key={}, value={}, external_subject_id={}, " \ - "semantic_id={}, supplemental_semantic_id={})".format( - self.name, self.value, self.external_subject_id, self.semantic_id, - self.supplemental_semantic_id) + return ( + "SpecificAssetId(key={}, value={}, external_subject_id={}, " + "semantic_id={}, supplemental_semantic_id={})".format( + self.name, self.value, self.external_subject_id, self.semantic_id, self.supplemental_semantic_id + ) + ) class AASConstraintViolation(Exception): @@ -2359,6 +2458,7 @@ class AASConstraintViolation(Exception): :ivar constraint_id: The ID of the constraint that is violated :ivar message: The error message of the Exception """ + def __init__(self, constraint_id: int, message: str): self.constraint_id: int = constraint_id self.message: str = message + " (Constraint AASd-" + str(constraint_id).zfill(3) + ")" @@ -2391,6 +2491,7 @@ class DataTypeIEC61360(Enum): :cvar BLOB: :cvar FILE: """ + DATE = 0 STRING = 1 STRING_TRANSLATABLE = 2 @@ -2423,6 +2524,7 @@ class IEC61360LevelType(Enum): :cvar NOM: :cvar TYP: """ + MIN = 0 MAX = 1 NOM = 2 @@ -2449,19 +2551,22 @@ class DataSpecificationIEC61360(DataSpecificationContent): :ivar value: Optional value data type object :ivar level_types: Optional set of level types of the DataSpecificationContent """ - def __init__(self, - preferred_name: PreferredNameTypeIEC61360, - data_type: Optional[DataTypeIEC61360] = None, - definition: Optional[DefinitionTypeIEC61360] = None, - short_name: Optional[ShortNameTypeIEC61360] = None, - unit: Optional[str] = None, - unit_id: Optional[Reference] = None, - source_of_definition: Optional[str] = None, - symbol: Optional[str] = None, - value_format: Optional[str] = None, - value_list: Optional[ValueList] = None, - value: Optional[ValueTypeIEC61360] = None, - level_types: Iterable[IEC61360LevelType] = ()): + + def __init__( + self, + preferred_name: PreferredNameTypeIEC61360, + data_type: Optional[DataTypeIEC61360] = None, + definition: Optional[DefinitionTypeIEC61360] = None, + short_name: Optional[ShortNameTypeIEC61360] = None, + unit: Optional[str] = None, + unit_id: Optional[Reference] = None, + source_of_definition: Optional[str] = None, + symbol: Optional[str] = None, + value_format: Optional[str] = None, + value_list: Optional[ValueList] = None, + value: Optional[ValueTypeIEC61360] = None, + level_types: Iterable[IEC61360LevelType] = (), + ): self.preferred_name: PreferredNameTypeIEC61360 = preferred_name self.short_name: Optional[ShortNameTypeIEC61360] = short_name diff --git a/sdk/basyx/aas/model/concept.py b/sdk/basyx/aas/model/concept.py index a9cbd1a33..fb3f78a93 100644 --- a/sdk/basyx/aas/model/concept.py +++ b/sdk/basyx/aas/model/concept.py @@ -7,6 +7,7 @@ """ This module contains the class :class:`~.ConceptDescription` from the AAS metamodel. """ + from typing import Optional, Set, Iterable, List from . import base @@ -23,7 +24,7 @@ "EVENT", "ENTITY", "APPLICATION_CLASS", - "QUALIFIER" + "QUALIFIER", } @@ -54,20 +55,21 @@ class ConceptDescription(base.Identifiable, base.HasDataSpecification): :ivar embedded_data_specifications: List of Embedded data specification. :ivar extension: An extension of the element. (from :class:`~basyx.aas.model.base.HasExtension`) -""" + """ - def __init__(self, - id_: base.Identifier, - is_case_of: Optional[Set[base.Reference]] = None, - id_short: Optional[base.NameType] = None, - display_name: Optional[base.MultiLanguageNameType] = None, - category: Optional[base.NameType] = None, - description: Optional[base.MultiLanguageTextType] = None, - parent: Optional[base.UniqueIdShortNamespace] = None, - administration: Optional[base.AdministrativeInformation] = None, - embedded_data_specifications: Iterable[base.EmbeddedDataSpecification] - = (), - extension: Iterable[base.Extension] = ()): + def __init__( + self, + id_: base.Identifier, + is_case_of: Optional[Set[base.Reference]] = None, + id_short: Optional[base.NameType] = None, + display_name: Optional[base.MultiLanguageNameType] = None, + category: Optional[base.NameType] = None, + description: Optional[base.MultiLanguageTextType] = None, + parent: Optional[base.UniqueIdShortNamespace] = None, + administration: Optional[base.AdministrativeInformation] = None, + embedded_data_specifications: Iterable[base.EmbeddedDataSpecification] = (), + extension: Iterable[base.Extension] = (), + ): super().__init__() self.id: base.Identifier = id_ @@ -89,6 +91,6 @@ def _set_category(self, category: Optional[str]): raise base.AASConstraintViolation( 51, "ConceptDescription must have one of the following " - "categories: " + str(ALLOWED_CONCEPT_DESCRIPTION_CATEGORIES) + "categories: " + str(ALLOWED_CONCEPT_DESCRIPTION_CATEGORIES), ) self._category = category diff --git a/sdk/basyx/aas/model/datatypes.py b/sdk/basyx/aas/model/datatypes.py index 07de9700c..06c499b05 100644 --- a/sdk/basyx/aas/model/datatypes.py +++ b/sdk/basyx/aas/model/datatypes.py @@ -23,6 +23,7 @@ Meant for fixing the type of :class:`Properties' ` values automatically, esp. for literal values. """ + import base64 import datetime import decimal @@ -42,10 +43,11 @@ class Date(datetime.date): - __slots__ = '_tzinfo' + __slots__ = "_tzinfo" - def __new__(cls, year: int, month: Optional[int] = None, day: Optional[int] = None, - tzinfo: Optional[datetime.tzinfo] = None) -> "Date": + def __new__( + cls, year: int, month: Optional[int] = None, day: Optional[int] = None, tzinfo: Optional[datetime.tzinfo] = None + ) -> "Date": res: "Date" = datetime.date.__new__(cls, year, month, day) # type: ignore # pickle support is not in typeshed # TODO normalize tzinfo to '+12:00' through '-11:59' res._tzinfo = tzinfo # type: ignore # Workaround for MyPy bug, not recognizing our additional __slots__ @@ -75,7 +77,7 @@ def __repr__(self): def __eq__(self, other: object) -> bool: if not isinstance(other, datetime.date): return NotImplemented - other_tzinfo = other.tzinfo if hasattr(other, 'tzinfo') else None # type: ignore + other_tzinfo = other.tzinfo if hasattr(other, "tzinfo") else None # type: ignore return datetime.date.__eq__(self, other) and self.tzinfo == other_tzinfo def __copy__(self): @@ -95,7 +97,7 @@ def __deepcopy__(self, memo): class GYearMonth: - __slots__ = ('year', 'month', 'tzinfo') + __slots__ = ("year", "month", "tzinfo") def __init__(self, year: int, month: int, tzinfo: Optional[datetime.tzinfo] = None): # TODO normalize tzinfo to '+12:00' through '-11:59' @@ -115,7 +117,7 @@ def into_date(self, day: int = 1) -> Date: @classmethod def from_date(cls, date: datetime.date) -> "GYearMonth": - tzinfo = date.tzinfo if hasattr(date, 'tzinfo') else None # type: ignore + tzinfo = date.tzinfo if hasattr(date, "tzinfo") else None # type: ignore return cls(date.year, date.month, tzinfo) def __eq__(self, other: object) -> bool: @@ -128,7 +130,7 @@ def __eq__(self, other: object) -> bool: class GYear: - __slots__ = ('year', 'tzinfo') + __slots__ = ("year", "tzinfo") def __init__(self, year: int, tzinfo: Optional[datetime.tzinfo] = None): # TODO normalize tzinfo to '+12:00' through '-11:59' @@ -145,7 +147,7 @@ def into_date(self, month: int = 1, day: int = 1) -> Date: @classmethod def from_date(cls, date: datetime.date) -> "GYear": - tzinfo = date.tzinfo if hasattr(date, 'tzinfo') else None # type: ignore + tzinfo = date.tzinfo if hasattr(date, "tzinfo") else None # type: ignore return cls(date.year, tzinfo) def __eq__(self, other: object) -> bool: @@ -158,7 +160,7 @@ def __eq__(self, other: object) -> bool: class GMonthDay: - __slots__ = ('month', 'day', 'tzinfo') + __slots__ = ("month", "day", "tzinfo") def __init__(self, month: int, day: int, tzinfo: Optional[datetime.tzinfo] = None): # TODO normalize tzinfo to '+12:00' through '-11:59' @@ -175,7 +177,7 @@ def into_date(self, year: int = 1970) -> Date: @classmethod def from_date(cls, date: datetime.date) -> "GMonthDay": - tzinfo = date.tzinfo if hasattr(date, 'tzinfo') else None # type: ignore + tzinfo = date.tzinfo if hasattr(date, "tzinfo") else None # type: ignore return cls(date.month, date.day, tzinfo) def __eq__(self, other: object) -> bool: @@ -188,7 +190,7 @@ def __eq__(self, other: object) -> bool: class GDay: - __slots__ = ('day', 'tzinfo') + __slots__ = ("day", "tzinfo") def __init__(self, day: int, tzinfo: Optional[datetime.tzinfo] = None): # TODO normalize tzinfo to '+12:00' through '-11:59' @@ -202,7 +204,7 @@ def into_date(self, year: int = 1970, month: int = 1) -> Date: @classmethod def from_date(cls, date: datetime.date) -> "GDay": - tzinfo = date.tzinfo if hasattr(date, 'tzinfo') else None # type: ignore + tzinfo = date.tzinfo if hasattr(date, "tzinfo") else None # type: ignore return cls(date.day, tzinfo) def __eq__(self, other: object) -> bool: @@ -215,7 +217,7 @@ def __eq__(self, other: object) -> bool: class GMonth: - __slots__ = ('month', 'tzinfo') + __slots__ = ("month", "tzinfo") def __init__(self, month: int, tzinfo: Optional[datetime.tzinfo] = None): # TODO normalize tzinfo to '+12:00' through '-11:59' @@ -229,7 +231,7 @@ def into_date(self, year: int = 1970, day: int = 1) -> Date: @classmethod def from_date(cls, date: datetime.date) -> "GMonth": - tzinfo = date.tzinfo if hasattr(date, 'tzinfo') else None # type: ignore + tzinfo = date.tzinfo if hasattr(date, "tzinfo") else None # type: ignore return cls(date.month, tzinfo) def __eq__(self, other: object) -> bool: @@ -250,7 +252,8 @@ class HexBinary(bytearray): class Float(float): - """ A 32bit IEEE754 float. This can not be represented with Python """ + """A 32bit IEEE754 float. This can not be represented with Python""" + pass @@ -258,7 +261,7 @@ class Long(int): def __new__(cls, *args, **kwargs): res = int.__new__(cls, *args, **kwargs) # [-9223372036854775808, 9223372036854775807] - if res > 2**63-1 or res < -2**63: + if res > 2**63 - 1 or res < -(2**63): raise ValueError("{} is out of the allowed range for type {}".format(res, cls.__name__)) return res @@ -267,7 +270,7 @@ class Int(int): def __new__(cls, *args, **kwargs): res = int.__new__(cls, *args, **kwargs) # [-2147483648, 2147483647] - if res > 2**31-1 or res < -2**31: + if res > 2**31 - 1 or res < -(2**31): raise ValueError("{} is out of the allowed range for type {}".format(res, cls.__name__)) return res @@ -276,7 +279,7 @@ class Short(int): def __new__(cls, *args, **kwargs): res = int.__new__(cls, *args, **kwargs) # [-32768, 32767] - if res > 2**15-1 or res < -2**15: + if res > 2**15 - 1 or res < -(2**15): raise ValueError("{} is out of the allowed range for type {}".format(res, cls.__name__)) return res @@ -285,7 +288,7 @@ class Byte(int): def __new__(cls, *args, **kwargs): res = int.__new__(cls, *args, **kwargs) # [-128,127] - if res > 2**7-1 or res < -2**7: + if res > 2**7 - 1 or res < -(2**7): raise ValueError("{} is out of the allowed range for type {}".format(res, cls.__name__)) return res @@ -325,7 +328,7 @@ def __new__(cls, *args, **kwargs): class UnsignedLong(int): def __new__(cls, *args, **kwargs): res = int.__new__(cls, *args, **kwargs) - if not 0 <= res <= 2**64-1: + if not 0 <= res <= 2**64 - 1: raise ValueError("{} is out of the allowed range for type {}".format(res, cls.__name__)) return res @@ -333,7 +336,7 @@ def __new__(cls, *args, **kwargs): class UnsignedInt(int): def __new__(cls, *args, **kwargs): res = int.__new__(cls, *args, **kwargs) - if not 0 <= res <= 2**32-1: + if not 0 <= res <= 2**32 - 1: raise ValueError("{} is out of the allowed range for type {}".format(res, cls.__name__)) return res @@ -341,7 +344,7 @@ def __new__(cls, *args, **kwargs): class UnsignedShort(int): def __new__(cls, *args, **kwargs): res = int.__new__(cls, *args, **kwargs) - if not 0 <= res <= 2**16-1: + if not 0 <= res <= 2**16 - 1: raise ValueError("{} is out of the allowed range for type {}".format(res, cls.__name__)) return res @@ -349,7 +352,7 @@ def __new__(cls, *args, **kwargs): class UnsignedByte(int): def __new__(cls, *args, **kwargs): res = int.__new__(cls, *args, **kwargs) - if not 0 <= res <= 2**8-1: + if not 0 <= res <= 2**8 - 1: raise ValueError("{} is out of the allowed range for type {}".format(res, cls.__name__)) return res @@ -362,7 +365,7 @@ class AnyURI(str): class NormalizedString(str): def __new__(cls, *args, **kwargs): res = str.__new__(cls, *args, **kwargs) - if ('\r' in res) or ('\n' in res) or ('\t' in res): + if ("\r" in res) or ("\n" in res) or ("\t" in res): raise ValueError("\\r, \\n and \\t are not allowed in NormalizedStrings") return res @@ -375,45 +378,76 @@ def from_string(cls, value: str) -> "NormalizedString": AnyXSDType = Union[ - Duration, DateTime, Date, Time, GYearMonth, GYear, GMonthDay, GMonth, GDay, Boolean, Base64Binary, - HexBinary, Float, Double, Decimal, Integer, Long, Int, Short, Byte, NonPositiveInteger, NegativeInteger, - NonNegativeInteger, PositiveInteger, UnsignedLong, UnsignedInt, UnsignedShort, UnsignedByte, AnyURI, String, - NormalizedString] - - -XSD_TYPE_NAMES: Dict[Type[AnyXSDType], str] = {k: "xs:" + v for k, v in { - Duration: "duration", - DateTime: "dateTime", - Date: "date", - Time: "time", - GYearMonth: "gYearMonth", - GYear: "gYear", - GMonthDay: "gMonthDay", - GMonth: "gMonth", - GDay: "gDay", - Boolean: "boolean", - Base64Binary: "base64Binary", - HexBinary: "hexBinary", - Float: "float", - Double: "double", - Decimal: "decimal", - Integer: "integer", - Long: "long", - Int: "int", - Short: "short", - Byte: "byte", - NonPositiveInteger: "nonPositiveInteger", - NegativeInteger: "negativeInteger", - NonNegativeInteger: "nonNegativeInteger", - PositiveInteger: "positiveInteger", - UnsignedLong: "unsignedLong", - UnsignedShort: "unsignedShort", - UnsignedInt: "unsignedInt", - UnsignedByte: "unsignedByte", - AnyURI: "anyURI", - String: "string", - NormalizedString: "normalizedString", -}.items()} + Duration, + DateTime, + Date, + Time, + GYearMonth, + GYear, + GMonthDay, + GMonth, + GDay, + Boolean, + Base64Binary, + HexBinary, + Float, + Double, + Decimal, + Integer, + Long, + Int, + Short, + Byte, + NonPositiveInteger, + NegativeInteger, + NonNegativeInteger, + PositiveInteger, + UnsignedLong, + UnsignedInt, + UnsignedShort, + UnsignedByte, + AnyURI, + String, + NormalizedString, +] + + +XSD_TYPE_NAMES: Dict[Type[AnyXSDType], str] = { + k: "xs:" + v + for k, v in { + Duration: "duration", + DateTime: "dateTime", + Date: "date", + Time: "time", + GYearMonth: "gYearMonth", + GYear: "gYear", + GMonthDay: "gMonthDay", + GMonth: "gMonth", + GDay: "gDay", + Boolean: "boolean", + Base64Binary: "base64Binary", + HexBinary: "hexBinary", + Float: "float", + Double: "double", + Decimal: "decimal", + Integer: "integer", + Long: "long", + Int: "int", + Short: "short", + Byte: "byte", + NonPositiveInteger: "nonPositiveInteger", + NegativeInteger: "negativeInteger", + NonNegativeInteger: "nonNegativeInteger", + PositiveInteger: "positiveInteger", + UnsignedLong: "unsignedLong", + UnsignedShort: "unsignedShort", + UnsignedInt: "unsignedInt", + UnsignedByte: "unsignedByte", + AnyURI: "anyURI", + String: "string", + NormalizedString: "normalizedString", + }.items() +} XSD_TYPE_CLASSES: Dict[str, Type[AnyXSDType]] = {v: k for k, v in XSD_TYPE_NAMES.items()} @@ -481,7 +515,7 @@ def xsd_repr(value: AnyXSDType) -> str: elif isinstance(value, str): return value elif isinstance(value, float): - return repr(value).translate({0x65: 'E', 0x66: 'F', 0x69: 'I', 0x6e: 'N'}) + return repr(value).translate({0x65: "E", 0x66: "F", 0x69: "I", 0x6E: "N"}) else: return str(value) @@ -491,21 +525,30 @@ def _serialize_date_tzinfo(date: Union[Date, GYear, GMonth, GDay, GYearMonth, GM if not isinstance(date, Date): date = date.into_date() offset: datetime.timedelta = date.tzinfo.utcoffset(datetime.datetime(date.year, date.month, date.day, 0, 0, 0)) - offset_seconds = (offset.total_seconds() + 3600*12) % (3600*24) - 3600*12 + offset_seconds = (offset.total_seconds() + 3600 * 12) % (3600 * 24) - 3600 * 12 if offset_seconds // 60 == 0: return "Z" - return "{}{:02.0f}:{:02.0f}".format("+" if offset_seconds >= 0 else "-", - abs(offset_seconds) // 3600, - (abs(offset_seconds) // 60) % 60) + return "{}{:02.0f}:{:02.0f}".format( + "+" if offset_seconds >= 0 else "-", abs(offset_seconds) // 3600, (abs(offset_seconds) // 60) % 60 + ) return "" def _serialize_duration(value: Duration) -> str: value = value.normalized() - signs = set(val < 0 - for val in (value.years, value.months, value.days, value.hours, value.minutes, value.seconds, - value.microseconds) - if val != 0) + signs = set( + val < 0 + for val in ( + value.years, + value.months, + value.days, + value.hours, + value.minutes, + value.seconds, + value.microseconds, + ) + if val != 0 + ) if len(signs) > 1: raise ValueError("Relative Durations with mixed signs are not allowed according to XSD.") elif len(signs) == 0: @@ -526,8 +569,9 @@ def _serialize_duration(value: Duration) -> str: if value.minutes: time += "{:.0f}M".format(abs(value.minutes)) if value.seconds or value.microseconds: - time += "{:.8g}S".format(decimal.Decimal(abs(value.seconds)) - + decimal.Decimal(abs(value.microseconds)) / 1000000) + time += "{:.8g}S".format( + decimal.Decimal(abs(value.seconds)) + decimal.Decimal(abs(value.microseconds)) / 1000000 + ) if time: result += "T" + time return result @@ -579,23 +623,25 @@ def from_xsd(value: str, type_: Type[AnyXSDType]) -> AnyXSDType: # workaround. raise ValueError("{} is not a valid simple built-in XSD type".format(type_.__name__)) -DURATION_RE = re.compile(r'^(-?)P(\d+Y)?(\d+M)?(\d+D)?(T(\d+H)?(\d+M)?((\d+)(\.\d+)?S)?)?$') -DATETIME_RE = re.compile(r'^(-?)(\d\d\d\d)-(\d\d)-(\d\d)T(\d\d):(\d\d):(\d\d)(\.\d+)?([+\-](\d\d):(\d\d)|Z)?$') -TIME_RE = re.compile(r'^(\d\d):(\d\d):(\d\d)(\.\d+)?([+\-](\d\d):(\d\d)|Z)?$') -DATE_RE = re.compile(r'^(-?)(\d\d\d\d)-(\d\d)-(\d\d)([+\-](\d\d):(\d\d)|Z)?$') +DURATION_RE = re.compile(r"^(-?)P(\d+Y)?(\d+M)?(\d+D)?(T(\d+H)?(\d+M)?((\d+)(\.\d+)?S)?)?$") +DATETIME_RE = re.compile(r"^(-?)(\d\d\d\d)-(\d\d)-(\d\d)T(\d\d):(\d\d):(\d\d)(\.\d+)?([+\-](\d\d):(\d\d)|Z)?$") +TIME_RE = re.compile(r"^(\d\d):(\d\d):(\d\d)(\.\d+)?([+\-](\d\d):(\d\d)|Z)?$") +DATE_RE = re.compile(r"^(-?)(\d\d\d\d)-(\d\d)-(\d\d)([+\-](\d\d):(\d\d)|Z)?$") def _parse_xsd_duration(value: str) -> Duration: match = DURATION_RE.match(value) if not match: raise ValueError("Value is not a valid XSD duration string") - res = Duration(years=int(match[2][:-1]) if match[2] else 0, - months=int(match[3][:-1]) if match[3] else 0, - days=int(match[4][:-1]) if match[4] else 0, - hours=int(match[6][:-1]) if match[6] else 0, - minutes=int(match[7][:-1]) if match[7] else 0, - seconds=int(match[9]) if match[8] else 0, - microseconds=int(float(match[10])*1e6) if match[10] else 0) + res = Duration( + years=int(match[2][:-1]) if match[2] else 0, + months=int(match[3][:-1]) if match[3] else 0, + days=int(match[4][:-1]) if match[4] else 0, + hours=int(match[6][:-1]) if match[6] else 0, + minutes=int(match[7][:-1]) if match[7] else 0, + seconds=int(match[9]) if match[8] else 0, + microseconds=int(float(match[10]) * 1e6) if match[10] else 0, + ) if match[1]: res = -res return res @@ -606,8 +652,9 @@ def _parse_xsd_date_tzinfo(value: str) -> Optional[datetime.tzinfo]: return None if value == "Z": return datetime.timezone.utc - return datetime.timezone(datetime.timedelta(hours=int(value[1:3]), minutes=int(value[4:6])) - * (-1 if value[0] == '-' else 1)) + return datetime.timezone( + datetime.timedelta(hours=int(value[1:3]), minutes=int(value[4:6])) * (-1 if value[0] == "-" else 1) + ) def _parse_xsd_date(value: str) -> Date: @@ -615,8 +662,10 @@ def _parse_xsd_date(value: str) -> Date: if not match: raise ValueError("Value is not a valid XSD date string") if match[1]: - raise NotImplementedError("Negative dates are not supported: Python stdlib datetime requires year >= 1. " - "Report at https://github.com/eclipse-basyx/basyx-python-sdk/issues") + raise NotImplementedError( + "Negative dates are not supported: Python stdlib datetime requires year >= 1. " + "Report at https://github.com/eclipse-basyx/basyx-python-sdk/issues" + ) return Date( year=int(match[2]), month=int(match[3]), @@ -630,8 +679,10 @@ def _parse_xsd_datetime(value: str) -> DateTime: if not match: raise ValueError(f"{value} is not a valid XSD datetime string") if match[1]: - raise NotImplementedError("Negative dates are not supported: Python stdlib datetime requires year >= 1. " - "Report at https://github.com/eclipse-basyx/basyx-python-sdk/issues") + raise NotImplementedError( + "Negative dates are not supported: Python stdlib datetime requires year >= 1. " + "Report at https://github.com/eclipse-basyx/basyx-python-sdk/issues" + ) microseconds = int(float(match[8]) * 1e6) if match[8] else 0 hour = int(match[5]) # xsd_datetime allows for hour=24 to represent midnight, @@ -689,11 +740,11 @@ def _parse_xsd_bool(value: str) -> Boolean: raise ValueError("Invalid literal for XSD bool type") -GYEAR_RE = re.compile(r'^(-?)(\d{4,})([+\-]\d\d:\d\d|Z)?$') -GMONTH_RE = re.compile(r'^--(\d\d)([+\-]\d\d:\d\d|Z)?$') -GDAY_RE = re.compile(r'^---(\d\d)([+\-]\d\d:\d\d|Z)?$') -GYEARMONTH_RE = re.compile(r'^(-?)(\d{4,})-(\d\d)([+\-]\d\d:\d\d|Z)?$') -GMONTHDAY_RE = re.compile(r'^--(\d\d)-(\d\d)([+\-]\d\d:\d\d|Z)?$') +GYEAR_RE = re.compile(r"^(-?)(\d{4,})([+\-]\d\d:\d\d|Z)?$") +GMONTH_RE = re.compile(r"^--(\d\d)([+\-]\d\d:\d\d|Z)?$") +GDAY_RE = re.compile(r"^---(\d\d)([+\-]\d\d:\d\d|Z)?$") +GYEARMONTH_RE = re.compile(r"^(-?)(\d{4,})-(\d\d)([+\-]\d\d:\d\d|Z)?$") +GMONTHDAY_RE = re.compile(r"^--(\d\d)-(\d\d)([+\-]\d\d:\d\d|Z)?$") def _parse_xsd_gyear(value: str) -> GYear: diff --git a/sdk/basyx/aas/model/provider.py b/sdk/basyx/aas/model/provider.py index 9a91a346d..d45662f76 100644 --- a/sdk/basyx/aas/model/provider.py +++ b/sdk/basyx/aas/model/provider.py @@ -17,8 +17,8 @@ from .base import Identifier, Identifiable -_KEY = TypeVar('_KEY') # Generic key type -_VALUE = TypeVar('_VALUE') # Generic value type +_KEY = TypeVar("_KEY") # Generic key type +_VALUE = TypeVar("_VALUE") # Generic value type class AbstractObjectProvider(Generic[_KEY, _VALUE], metaclass=abc.ABCMeta): @@ -122,11 +122,10 @@ def get_item(self, key: _KEY) -> _VALUE: return provider.get_item(key) except KeyError: pass - raise KeyError("Key could not be found in any of the {} consulted registries." - .format(len(self.providers))) + raise KeyError("Key could not be found in any of the {} consulted registries.".format(len(self.providers))) -_IDENTIFIABLE = TypeVar('_IDENTIFIABLE', bound=Identifiable) +_IDENTIFIABLE = TypeVar("_IDENTIFIABLE", bound=Identifiable) class DictIdentifiableStore(AbstractObjectStore[Identifier, _IDENTIFIABLE]): @@ -154,8 +153,7 @@ def get_item(self, identifier: Identifier) -> _IDENTIFIABLE: def add(self, x: _IDENTIFIABLE) -> None: if x.id in self._backend and self._backend.get(x.id) is not x: - raise KeyError("Identifiable object with same id {} is already stored in this store" - .format(x.id)) + raise KeyError("Identifiable object with same id {} is already stored in this store".format(x.id)) self._backend[x.id] = x def commit(self, x: _IDENTIFIABLE) -> None: diff --git a/sdk/basyx/aas/model/submodel.py b/sdk/basyx/aas/model/submodel.py index a361ce341..01093da34 100644 --- a/sdk/basyx/aas/model/submodel.py +++ b/sdk/basyx/aas/model/submodel.py @@ -13,12 +13,14 @@ from typing import Optional, Set, Iterable, TYPE_CHECKING, List, Type, TypeVar, Generic, Union from . import base, datatypes, _string_constraints + if TYPE_CHECKING: from . import aas -class SubmodelElement(base.Referable, base.Qualifiable, base.HasSemantics, - base.HasDataSpecification, metaclass=abc.ABCMeta): +class SubmodelElement( + base.Referable, base.Qualifiable, base.HasSemantics, base.HasDataSpecification, metaclass=abc.ABCMeta +): """ A submodel element is an element suitable for the description and differentiation of assets. @@ -51,18 +53,21 @@ class SubmodelElement(base.Referable, base.Qualifiable, base.HasSemantics, :class:`~basyx.aas.model.base.HasSemantics`) :ivar embedded_data_specifications: List of Embedded data specification. """ + @abc.abstractmethod - def __init__(self, - id_short: Optional[base.NameType], - display_name: Optional[base.MultiLanguageNameType] = None, - category: Optional[base.NameType] = None, - description: Optional[base.MultiLanguageTextType] = None, - parent: Optional[base.UniqueIdShortNamespace] = None, - semantic_id: Optional[base.Reference] = None, - qualifier: Iterable[base.Qualifier] = (), - extension: Iterable[base.Extension] = (), - supplemental_semantic_id: Iterable[base.Reference] = (), - embedded_data_specifications: Iterable[base.EmbeddedDataSpecification] = ()): + def __init__( + self, + id_short: Optional[base.NameType], + display_name: Optional[base.MultiLanguageNameType] = None, + category: Optional[base.NameType] = None, + description: Optional[base.MultiLanguageTextType] = None, + parent: Optional[base.UniqueIdShortNamespace] = None, + semantic_id: Optional[base.Reference] = None, + qualifier: Iterable[base.Qualifier] = (), + extension: Iterable[base.Extension] = (), + supplemental_semantic_id: Iterable[base.Reference] = (), + embedded_data_specifications: Iterable[base.EmbeddedDataSpecification] = (), + ): """ TODO: Add instruction what to do after construction """ @@ -80,8 +85,14 @@ def __init__(self, self.embedded_data_specifications: List[base.EmbeddedDataSpecification] = list(embedded_data_specifications) -class Submodel(base.Identifiable, base.HasSemantics, base.HasKind, base.Qualifiable, - base.UniqueIdShortNamespace, base.HasDataSpecification): +class Submodel( + base.Identifiable, + base.HasSemantics, + base.HasKind, + base.Qualifiable, + base.UniqueIdShortNamespace, + base.HasDataSpecification, +): """ A Submodel defines a specific aspect of the asset represented by the AAS. @@ -118,21 +129,23 @@ class Submodel(base.Identifiable, base.HasSemantics, base.HasKind, base.Qualifia :ivar embedded_data_specifications: List of Embedded data specification. """ - def __init__(self, - id_: base.Identifier, - submodel_element: Iterable[SubmodelElement] = (), - id_short: Optional[base.NameType] = None, - display_name: Optional[base.MultiLanguageNameType] = None, - category: Optional[base.NameType] = None, - description: Optional[base.MultiLanguageTextType] = None, - parent: Optional[base.UniqueIdShortNamespace] = None, - administration: Optional[base.AdministrativeInformation] = None, - semantic_id: Optional[base.Reference] = None, - qualifier: Iterable[base.Qualifier] = (), - kind: base.ModellingKind = base.ModellingKind.INSTANCE, - extension: Iterable[base.Extension] = (), - supplemental_semantic_id: Iterable[base.Reference] = (), - embedded_data_specifications: Iterable[base.EmbeddedDataSpecification] = ()): + def __init__( + self, + id_: base.Identifier, + submodel_element: Iterable[SubmodelElement] = (), + id_short: Optional[base.NameType] = None, + display_name: Optional[base.MultiLanguageNameType] = None, + category: Optional[base.NameType] = None, + description: Optional[base.MultiLanguageTextType] = None, + parent: Optional[base.UniqueIdShortNamespace] = None, + administration: Optional[base.AdministrativeInformation] = None, + semantic_id: Optional[base.Reference] = None, + qualifier: Iterable[base.Qualifier] = (), + kind: base.ModellingKind = base.ModellingKind.INSTANCE, + extension: Iterable[base.Extension] = (), + supplemental_semantic_id: Iterable[base.Reference] = (), + embedded_data_specifications: Iterable[base.EmbeddedDataSpecification] = (), + ): super().__init__() self.id: base.Identifier = id_ self.submodel_element = base.NamespaceSet(self, [("id_short", True)], submodel_element) @@ -181,20 +194,33 @@ class DataElement(SubmodelElement, metaclass=abc.ABCMeta): :class:`~basyx.aas.model.base.HasSemantics`) :ivar embedded_data_specifications: List of Embedded data specification. """ + @abc.abstractmethod - def __init__(self, - id_short: Optional[base.NameType], - display_name: Optional[base.MultiLanguageNameType] = None, - category: Optional[base.NameType] = None, - description: Optional[base.MultiLanguageTextType] = None, - parent: Optional[base.UniqueIdShortNamespace] = None, - semantic_id: Optional[base.Reference] = None, - qualifier: Iterable[base.Qualifier] = (), - extension: Iterable[base.Extension] = (), - supplemental_semantic_id: Iterable[base.Reference] = (), - embedded_data_specifications: Iterable[base.EmbeddedDataSpecification] = ()): - super().__init__(id_short, display_name, category, description, parent, semantic_id, qualifier, extension, - supplemental_semantic_id, embedded_data_specifications) + def __init__( + self, + id_short: Optional[base.NameType], + display_name: Optional[base.MultiLanguageNameType] = None, + category: Optional[base.NameType] = None, + description: Optional[base.MultiLanguageTextType] = None, + parent: Optional[base.UniqueIdShortNamespace] = None, + semantic_id: Optional[base.Reference] = None, + qualifier: Iterable[base.Qualifier] = (), + extension: Iterable[base.Extension] = (), + supplemental_semantic_id: Iterable[base.Reference] = (), + embedded_data_specifications: Iterable[base.EmbeddedDataSpecification] = (), + ): + super().__init__( + id_short, + display_name, + category, + description, + parent, + semantic_id, + qualifier, + extension, + supplemental_semantic_id, + embedded_data_specifications, + ) class Property(DataElement): @@ -230,29 +256,42 @@ class Property(DataElement): :ivar embedded_data_specifications: List of Embedded data specification. """ - def __init__(self, - id_short: Optional[base.NameType], - value_type: base.DataTypeDefXsd, - value: Optional[base.ValueDataType] = None, - value_id: Optional[base.Reference] = None, - display_name: Optional[base.MultiLanguageNameType] = None, - category: Optional[base.NameType] = None, - description: Optional[base.MultiLanguageTextType] = None, - parent: Optional[base.UniqueIdShortNamespace] = None, - semantic_id: Optional[base.Reference] = None, - qualifier: Iterable[base.Qualifier] = (), - extension: Iterable[base.Extension] = (), - supplemental_semantic_id: Iterable[base.Reference] = (), - embedded_data_specifications: Iterable[base.EmbeddedDataSpecification] = ()): + def __init__( + self, + id_short: Optional[base.NameType], + value_type: base.DataTypeDefXsd, + value: Optional[base.ValueDataType] = None, + value_id: Optional[base.Reference] = None, + display_name: Optional[base.MultiLanguageNameType] = None, + category: Optional[base.NameType] = None, + description: Optional[base.MultiLanguageTextType] = None, + parent: Optional[base.UniqueIdShortNamespace] = None, + semantic_id: Optional[base.Reference] = None, + qualifier: Iterable[base.Qualifier] = (), + extension: Iterable[base.Extension] = (), + supplemental_semantic_id: Iterable[base.Reference] = (), + embedded_data_specifications: Iterable[base.EmbeddedDataSpecification] = (), + ): """ TODO: Add instruction what to do after construction """ - super().__init__(id_short, display_name, category, description, parent, semantic_id, qualifier, extension, - supplemental_semantic_id, embedded_data_specifications) + super().__init__( + id_short, + display_name, + category, + description, + parent, + semantic_id, + qualifier, + extension, + supplemental_semantic_id, + embedded_data_specifications, + ) self.value_type: base.DataTypeDefXsd = value_type - self._value: Optional[base.ValueDataType] = (datatypes.trivial_cast(value, value_type) - if value is not None else None) + self._value: Optional[base.ValueDataType] = ( + datatypes.trivial_cast(value, value_type) if value is not None else None + ) self.value_id: Optional[base.Reference] = value_id @property @@ -300,25 +339,37 @@ class MultiLanguageProperty(DataElement): :ivar embedded_data_specifications: List of Embedded data specification. """ - def __init__(self, - id_short: Optional[base.NameType], - value: Optional[base.MultiLanguageTextType] = None, - value_id: Optional[base.Reference] = None, - display_name: Optional[base.MultiLanguageNameType] = None, - category: Optional[base.NameType] = None, - description: Optional[base.MultiLanguageTextType] = None, - parent: Optional[base.UniqueIdShortNamespace] = None, - semantic_id: Optional[base.Reference] = None, - qualifier: Iterable[base.Qualifier] = (), - extension: Iterable[base.Extension] = (), - supplemental_semantic_id: Iterable[base.Reference] = (), - embedded_data_specifications: Iterable[base.EmbeddedDataSpecification] = ()): + def __init__( + self, + id_short: Optional[base.NameType], + value: Optional[base.MultiLanguageTextType] = None, + value_id: Optional[base.Reference] = None, + display_name: Optional[base.MultiLanguageNameType] = None, + category: Optional[base.NameType] = None, + description: Optional[base.MultiLanguageTextType] = None, + parent: Optional[base.UniqueIdShortNamespace] = None, + semantic_id: Optional[base.Reference] = None, + qualifier: Iterable[base.Qualifier] = (), + extension: Iterable[base.Extension] = (), + supplemental_semantic_id: Iterable[base.Reference] = (), + embedded_data_specifications: Iterable[base.EmbeddedDataSpecification] = (), + ): """ TODO: Add instruction what to do after construction """ - super().__init__(id_short, display_name, category, description, parent, semantic_id, qualifier, extension, - supplemental_semantic_id, embedded_data_specifications) + super().__init__( + id_short, + display_name, + category, + description, + parent, + semantic_id, + qualifier, + extension, + supplemental_semantic_id, + embedded_data_specifications, + ) self.value: Optional[base.MultiLanguageTextType] = value self.value_id: Optional[base.Reference] = value_id @@ -367,26 +418,38 @@ class Range(DataElement): :ivar embedded_data_specifications: List of Embedded data specification. """ - def __init__(self, - id_short: Optional[base.NameType], - value_type: base.DataTypeDefXsd, - min: Optional[base.ValueDataType] = None, - max: Optional[base.ValueDataType] = None, - display_name: Optional[base.MultiLanguageNameType] = None, - category: Optional[base.NameType] = None, - description: Optional[base.MultiLanguageTextType] = None, - parent: Optional[base.UniqueIdShortNamespace] = None, - semantic_id: Optional[base.Reference] = None, - qualifier: Iterable[base.Qualifier] = (), - extension: Iterable[base.Extension] = (), - supplemental_semantic_id: Iterable[base.Reference] = (), - embedded_data_specifications: Iterable[base.EmbeddedDataSpecification] = ()): + def __init__( + self, + id_short: Optional[base.NameType], + value_type: base.DataTypeDefXsd, + min: Optional[base.ValueDataType] = None, + max: Optional[base.ValueDataType] = None, + display_name: Optional[base.MultiLanguageNameType] = None, + category: Optional[base.NameType] = None, + description: Optional[base.MultiLanguageTextType] = None, + parent: Optional[base.UniqueIdShortNamespace] = None, + semantic_id: Optional[base.Reference] = None, + qualifier: Iterable[base.Qualifier] = (), + extension: Iterable[base.Extension] = (), + supplemental_semantic_id: Iterable[base.Reference] = (), + embedded_data_specifications: Iterable[base.EmbeddedDataSpecification] = (), + ): """ TODO: Add instruction what to do after construction """ - super().__init__(id_short, display_name, category, description, parent, semantic_id, qualifier, extension, - supplemental_semantic_id, embedded_data_specifications) + super().__init__( + id_short, + display_name, + category, + description, + parent, + semantic_id, + qualifier, + extension, + supplemental_semantic_id, + embedded_data_specifications, + ) self.value_type: base.DataTypeDefXsd = value_type self._min: Optional[base.ValueDataType] = datatypes.trivial_cast(min, value_type) if min is not None else None self._max: Optional[base.ValueDataType] = datatypes.trivial_cast(max, value_type) if max is not None else None @@ -450,25 +513,37 @@ class Blob(DataElement): :ivar embedded_data_specifications: List of Embedded data specification. """ - def __init__(self, - id_short: Optional[base.NameType], - content_type: Optional[base.ContentType] = None, - value: Optional[base.BlobType] = None, - display_name: Optional[base.MultiLanguageNameType] = None, - category: Optional[base.NameType] = None, - description: Optional[base.MultiLanguageTextType] = None, - parent: Optional[base.UniqueIdShortNamespace] = None, - semantic_id: Optional[base.Reference] = None, - qualifier: Iterable[base.Qualifier] = (), - extension: Iterable[base.Extension] = (), - supplemental_semantic_id: Iterable[base.Reference] = (), - embedded_data_specifications: Iterable[base.EmbeddedDataSpecification] = ()): + def __init__( + self, + id_short: Optional[base.NameType], + content_type: Optional[base.ContentType] = None, + value: Optional[base.BlobType] = None, + display_name: Optional[base.MultiLanguageNameType] = None, + category: Optional[base.NameType] = None, + description: Optional[base.MultiLanguageTextType] = None, + parent: Optional[base.UniqueIdShortNamespace] = None, + semantic_id: Optional[base.Reference] = None, + qualifier: Iterable[base.Qualifier] = (), + extension: Iterable[base.Extension] = (), + supplemental_semantic_id: Iterable[base.Reference] = (), + embedded_data_specifications: Iterable[base.EmbeddedDataSpecification] = (), + ): """ TODO: Add instruction what to do after construction """ - super().__init__(id_short, display_name, category, description, parent, semantic_id, qualifier, extension, - supplemental_semantic_id, embedded_data_specifications) + super().__init__( + id_short, + display_name, + category, + description, + parent, + semantic_id, + qualifier, + extension, + supplemental_semantic_id, + embedded_data_specifications, + ) self.value: Optional[base.BlobType] = value self.content_type: Optional[base.ContentType] = content_type @@ -504,25 +579,37 @@ class File(DataElement): :ivar embedded_data_specifications: List of Embedded data specification. """ - def __init__(self, - id_short: Optional[base.NameType], - content_type: Optional[base.ContentType] = None, - value: Optional[base.PathType] = None, - display_name: Optional[base.MultiLanguageNameType] = None, - category: Optional[base.NameType] = None, - description: Optional[base.MultiLanguageTextType] = None, - parent: Optional[base.UniqueIdShortNamespace] = None, - semantic_id: Optional[base.Reference] = None, - qualifier: Iterable[base.Qualifier] = (), - extension: Iterable[base.Extension] = (), - supplemental_semantic_id: Iterable[base.Reference] = (), - embedded_data_specifications: Iterable[base.EmbeddedDataSpecification] = ()): + def __init__( + self, + id_short: Optional[base.NameType], + content_type: Optional[base.ContentType] = None, + value: Optional[base.PathType] = None, + display_name: Optional[base.MultiLanguageNameType] = None, + category: Optional[base.NameType] = None, + description: Optional[base.MultiLanguageTextType] = None, + parent: Optional[base.UniqueIdShortNamespace] = None, + semantic_id: Optional[base.Reference] = None, + qualifier: Iterable[base.Qualifier] = (), + extension: Iterable[base.Extension] = (), + supplemental_semantic_id: Iterable[base.Reference] = (), + embedded_data_specifications: Iterable[base.EmbeddedDataSpecification] = (), + ): """ TODO: Add instruction what to do after construction """ - super().__init__(id_short, display_name, category, description, parent, semantic_id, qualifier, extension, - supplemental_semantic_id, embedded_data_specifications) + super().__init__( + id_short, + display_name, + category, + description, + parent, + semantic_id, + qualifier, + extension, + supplemental_semantic_id, + embedded_data_specifications, + ) self.value: Optional[base.PathType] = value self.content_type: Optional[base.ContentType] = content_type @@ -559,24 +646,36 @@ class ReferenceElement(DataElement): :ivar embedded_data_specifications: List of Embedded data specification. """ - def __init__(self, - id_short: Optional[base.NameType], - value: Optional[base.Reference] = None, - display_name: Optional[base.MultiLanguageNameType] = None, - category: Optional[base.NameType] = None, - description: Optional[base.MultiLanguageTextType] = None, - parent: Optional[base.UniqueIdShortNamespace] = None, - semantic_id: Optional[base.Reference] = None, - qualifier: Iterable[base.Qualifier] = (), - extension: Iterable[base.Extension] = (), - supplemental_semantic_id: Iterable[base.Reference] = (), - embedded_data_specifications: Iterable[base.EmbeddedDataSpecification] = ()): + def __init__( + self, + id_short: Optional[base.NameType], + value: Optional[base.Reference] = None, + display_name: Optional[base.MultiLanguageNameType] = None, + category: Optional[base.NameType] = None, + description: Optional[base.MultiLanguageTextType] = None, + parent: Optional[base.UniqueIdShortNamespace] = None, + semantic_id: Optional[base.Reference] = None, + qualifier: Iterable[base.Qualifier] = (), + extension: Iterable[base.Extension] = (), + supplemental_semantic_id: Iterable[base.Reference] = (), + embedded_data_specifications: Iterable[base.EmbeddedDataSpecification] = (), + ): """ TODO: Add instruction what to do after construction """ - super().__init__(id_short, display_name, category, description, parent, semantic_id, qualifier, extension, - supplemental_semantic_id, embedded_data_specifications) + super().__init__( + id_short, + display_name, + category, + description, + parent, + semantic_id, + qualifier, + extension, + supplemental_semantic_id, + embedded_data_specifications, + ) self.value: Optional[base.Reference] = value @@ -607,21 +706,34 @@ class SubmodelElementCollection(SubmodelElement, base.UniqueIdShortNamespace): :class:`~basyx.aas.model.base.HasSemantics`) :ivar embedded_data_specifications: List of Embedded data specification. """ - def __init__(self, - id_short: Optional[base.NameType], - value: Iterable[SubmodelElement] = (), - display_name: Optional[base.MultiLanguageNameType] = None, - category: Optional[base.NameType] = None, - description: Optional[base.MultiLanguageTextType] = None, - parent: Optional[base.UniqueIdShortNamespace] = None, - semantic_id: Optional[base.Reference] = None, - qualifier: Iterable[base.Qualifier] = (), - extension: Iterable[base.Extension] = (), - supplemental_semantic_id: Iterable[base.Reference] = (), - embedded_data_specifications: Iterable[base.EmbeddedDataSpecification] = ()): - - super().__init__(id_short, display_name, category, description, parent, semantic_id, qualifier, extension, - supplemental_semantic_id, embedded_data_specifications) + + def __init__( + self, + id_short: Optional[base.NameType], + value: Iterable[SubmodelElement] = (), + display_name: Optional[base.MultiLanguageNameType] = None, + category: Optional[base.NameType] = None, + description: Optional[base.MultiLanguageTextType] = None, + parent: Optional[base.UniqueIdShortNamespace] = None, + semantic_id: Optional[base.Reference] = None, + qualifier: Iterable[base.Qualifier] = (), + extension: Iterable[base.Extension] = (), + supplemental_semantic_id: Iterable[base.Reference] = (), + embedded_data_specifications: Iterable[base.EmbeddedDataSpecification] = (), + ): + + super().__init__( + id_short, + display_name, + category, + description, + parent, + semantic_id, + qualifier, + extension, + supplemental_semantic_id, + embedded_data_specifications, + ) self.value: base.NamespaceSet[SubmodelElement] = base.NamespaceSet(self, [("id_short", True)], value) @@ -678,24 +790,37 @@ class SubmodelElementList(SubmodelElement, base.UniqueIdShortNamespace, Generic[ :class:`~basyx.aas.model.base.HasSemantics`) :ivar embedded_data_specifications: List of Embedded data specification. """ - def __init__(self, - id_short: Optional[base.NameType], - type_value_list_element: Type[_SE], - value: Iterable[_SE] = (), - semantic_id_list_element: Optional[base.Reference] = None, - value_type_list_element: Optional[base.DataTypeDefXsd] = None, - order_relevant: bool = True, - display_name: Optional[base.MultiLanguageNameType] = None, - category: Optional[base.NameType] = None, - description: Optional[base.MultiLanguageTextType] = None, - parent: Optional[base.UniqueIdShortNamespace] = None, - semantic_id: Optional[base.Reference] = None, - qualifier: Iterable[base.Qualifier] = (), - extension: Iterable[base.Extension] = (), - supplemental_semantic_id: Iterable[base.Reference] = (), - embedded_data_specifications: Iterable[base.EmbeddedDataSpecification] = ()): - super().__init__(id_short, display_name, category, description, parent, semantic_id, qualifier, extension, - supplemental_semantic_id, embedded_data_specifications) + + def __init__( + self, + id_short: Optional[base.NameType], + type_value_list_element: Type[_SE], + value: Iterable[_SE] = (), + semantic_id_list_element: Optional[base.Reference] = None, + value_type_list_element: Optional[base.DataTypeDefXsd] = None, + order_relevant: bool = True, + display_name: Optional[base.MultiLanguageNameType] = None, + category: Optional[base.NameType] = None, + description: Optional[base.MultiLanguageTextType] = None, + parent: Optional[base.UniqueIdShortNamespace] = None, + semantic_id: Optional[base.Reference] = None, + qualifier: Iterable[base.Qualifier] = (), + extension: Iterable[base.Extension] = (), + supplemental_semantic_id: Iterable[base.Reference] = (), + embedded_data_specifications: Iterable[base.EmbeddedDataSpecification] = (), + ): + super().__init__( + id_short, + display_name, + category, + description, + parent, + semantic_id, + qualifier, + extension, + supplemental_semantic_id, + embedded_data_specifications, + ) # Counter to generate a unique idShort whenever a SubmodelElement is added self._uuid_seq: int = 0 @@ -706,15 +831,22 @@ def __init__(self, self._value_type_list_element: Optional[base.DataTypeDefXsd] = value_type_list_element if self.type_value_list_element in (Property, Range) and self.value_type_list_element is None: - raise base.AASConstraintViolation(109, f"type_value_list_element={self.type_value_list_element.__name__}, " - "but value_type_list_element is not set!") + raise base.AASConstraintViolation( + 109, + f"type_value_list_element={self.type_value_list_element.__name__}, " + "but value_type_list_element is not set!", + ) # Items must be added after the above constraint has been checked. Otherwise, it can lead to errors, since the # constraints in _check_constraints() assume that this constraint has been checked. - self._value: base.OrderedNamespaceSet[_SE] = base.OrderedNamespaceSet(self, [("id_short", True)], (), - item_add_hook=self._check_constraints, - item_id_set_hook=self._generate_id_short, - item_id_del_hook=self._unset_id_short) + self._value: base.OrderedNamespaceSet[_SE] = base.OrderedNamespaceSet( + self, + [("id_short", True)], + (), + item_add_hook=self._check_constraints, + item_id_set_hook=self._generate_id_short, + item_id_del_hook=self._unset_id_short, + ) # SubmodelElements need to be added after the assignment of the ordered NamespaceSet, otherwise, if a constraint # check fails, Referable.__repr__ may be called for an already-contained item during the AASd-114 check, which # in turn tries to access the SubmodelElementLists value / _value attribute, which wouldn't be set yet if all @@ -749,40 +881,54 @@ def _check_constraints(self, new: _SE, existing: Iterable[_SE]) -> None: # self.type_value_list_element wouldn't raise a ConstraintViolation, when it should. # Example: AnnotatedRelationshipElement is a subclass of RelationshipElement if type(new) is not self.type_value_list_element: - raise base.AASConstraintViolation(108, "All first level elements must be of the type specified in " - f"type_value_list_element={self.type_value_list_element.__name__}, " - f"got {new!r}") - - if self.semantic_id_list_element is not None and new.semantic_id is not None \ - and new.semantic_id != self.semantic_id_list_element: + raise base.AASConstraintViolation( + 108, + "All first level elements must be of the type specified in " + f"type_value_list_element={self.type_value_list_element.__name__}, " + f"got {new!r}", + ) + + if ( + self.semantic_id_list_element is not None + and new.semantic_id is not None + and new.semantic_id != self.semantic_id_list_element + ): # Constraint AASd-115 specifies that if the semantic_id of an item is not specified # but semantic_id_list_element is, the semantic_id of the new is assumed to be identical. # Not really a constraint... # TODO: maybe set the semantic_id of new to semantic_id_list_element if it is None - raise base.AASConstraintViolation(107, f"If semantic_id_list_element={self.semantic_id_list_element!r} " - "is specified all first level children must have the same " - f"semantic_id, got {new!r} with semantic_id={new.semantic_id!r}") + raise base.AASConstraintViolation( + 107, + f"If semantic_id_list_element={self.semantic_id_list_element!r} " + "is specified all first level children must have the same " + f"semantic_id, got {new!r} with semantic_id={new.semantic_id!r}", + ) # If we got here we know that `new` is an instance of type_value_list_element and that type_value_list_element # is either Property or Range. Thus, `new` must have the value_type property. # Furthermore, value_type_list_element cannot be None, as this is already checked in __init__(). # Ignore the types here because the typechecker doesn't get it. - if self.type_value_list_element in (Property, Range) \ - and new.value_type is not self.value_type_list_element: # type: ignore - raise base.AASConstraintViolation(109, "All first level elements must have the value_type " # type: ignore - "specified by value_type_list_element=" - f"{self.value_type_list_element.__name__}, got " # type: ignore - f"{new!r} with value_type={new.value_type.__name__}") # type: ignore + if self.type_value_list_element in (Property, Range) and new.value_type is not self.value_type_list_element: # type: ignore + raise base.AASConstraintViolation( + 109, + "All first level elements must have the value_type " # type: ignore + "specified by value_type_list_element=" + f"{self.value_type_list_element.__name__}, got " # type: ignore + f"{new!r} with value_type={new.value_type.__name__}", + ) # type: ignore # If semantic_id_list_element is not None that would already enforce the semantic_id for all first level # elements. Thus, we only need to perform this check if semantic_id_list_element is None. if new.semantic_id is not None and self.semantic_id_list_element is None: for item in existing: if item.semantic_id is not None and new.semantic_id != item.semantic_id: - raise base.AASConstraintViolation(114, f"Element to be added {new!r} has semantic_id " - f"{new.semantic_id!r}, while already contained element " - f"{item!r} has semantic_id {item.semantic_id!r}, which " - "aren't equal.") + raise base.AASConstraintViolation( + 114, + f"Element to be added {new!r} has semantic_id " + f"{new.semantic_id!r}, while already contained element " + f"{item!r} has semantic_id {item.semantic_id!r}, which " + "aren't equal.", + ) # Re-assign id_short new.id_short = saved_id_short @@ -845,25 +991,37 @@ class RelationshipElement(SubmodelElement): :ivar embedded_data_specifications: List of Embedded data specification. """ - def __init__(self, - id_short: Optional[base.NameType], - first: Optional[base.Reference] = None, - second: Optional[base.Reference] = None, - display_name: Optional[base.MultiLanguageNameType] = None, - category: Optional[base.NameType] = None, - description: Optional[base.MultiLanguageTextType] = None, - parent: Optional[base.UniqueIdShortNamespace] = None, - semantic_id: Optional[base.Reference] = None, - qualifier: Iterable[base.Qualifier] = (), - extension: Iterable[base.Extension] = (), - supplemental_semantic_id: Iterable[base.Reference] = (), - embedded_data_specifications: Iterable[base.EmbeddedDataSpecification] = ()): + def __init__( + self, + id_short: Optional[base.NameType], + first: Optional[base.Reference] = None, + second: Optional[base.Reference] = None, + display_name: Optional[base.MultiLanguageNameType] = None, + category: Optional[base.NameType] = None, + description: Optional[base.MultiLanguageTextType] = None, + parent: Optional[base.UniqueIdShortNamespace] = None, + semantic_id: Optional[base.Reference] = None, + qualifier: Iterable[base.Qualifier] = (), + extension: Iterable[base.Extension] = (), + supplemental_semantic_id: Iterable[base.Reference] = (), + embedded_data_specifications: Iterable[base.EmbeddedDataSpecification] = (), + ): """ TODO: Add instruction what to do after construction """ - super().__init__(id_short, display_name, category, description, parent, semantic_id, qualifier, extension, - supplemental_semantic_id, embedded_data_specifications) + super().__init__( + id_short, + display_name, + category, + description, + parent, + semantic_id, + qualifier, + extension, + supplemental_semantic_id, + embedded_data_specifications, + ) self.first: Optional[base.Reference] = first self.second: Optional[base.Reference] = second @@ -902,26 +1060,40 @@ class AnnotatedRelationshipElement(RelationshipElement, base.UniqueIdShortNamesp :ivar embedded_data_specifications: List of Embedded data specification. """ - def __init__(self, - id_short: Optional[base.NameType], - first: Optional[base.Reference] = None, - second: Optional[base.Reference] = None, - display_name: Optional[base.MultiLanguageNameType] = None, - annotation: Iterable[DataElement] = (), - category: Optional[base.NameType] = None, - description: Optional[base.MultiLanguageTextType] = None, - parent: Optional[base.UniqueIdShortNamespace] = None, - semantic_id: Optional[base.Reference] = None, - qualifier: Iterable[base.Qualifier] = (), - extension: Iterable[base.Extension] = (), - supplemental_semantic_id: Iterable[base.Reference] = (), - embedded_data_specifications: Iterable[base.EmbeddedDataSpecification] = ()): + def __init__( + self, + id_short: Optional[base.NameType], + first: Optional[base.Reference] = None, + second: Optional[base.Reference] = None, + display_name: Optional[base.MultiLanguageNameType] = None, + annotation: Iterable[DataElement] = (), + category: Optional[base.NameType] = None, + description: Optional[base.MultiLanguageTextType] = None, + parent: Optional[base.UniqueIdShortNamespace] = None, + semantic_id: Optional[base.Reference] = None, + qualifier: Iterable[base.Qualifier] = (), + extension: Iterable[base.Extension] = (), + supplemental_semantic_id: Iterable[base.Reference] = (), + embedded_data_specifications: Iterable[base.EmbeddedDataSpecification] = (), + ): """ TODO: Add instruction what to do after construction """ - super().__init__(id_short, first, second, display_name, category, description, parent, semantic_id, qualifier, - extension, supplemental_semantic_id, embedded_data_specifications) + super().__init__( + id_short, + first, + second, + display_name, + category, + description, + parent, + semantic_id, + qualifier, + extension, + supplemental_semantic_id, + embedded_data_specifications, + ) self.annotation = base.NamespaceSet(self, [("id_short", True)], annotation) @@ -964,26 +1136,39 @@ class Operation(SubmodelElement, base.UniqueIdShortNamespace): :class:`~basyx.aas.model.base.HasSemantics`) :ivar embedded_data_specifications: List of Embedded data specification. """ - def __init__(self, - id_short: Optional[base.NameType], - input_variable: Iterable[SubmodelElement] = (), - output_variable: Iterable[SubmodelElement] = (), - in_output_variable: Iterable[SubmodelElement] = (), - display_name: Optional[base.MultiLanguageNameType] = None, - category: Optional[base.NameType] = None, - description: Optional[base.MultiLanguageTextType] = None, - parent: Optional[base.UniqueIdShortNamespace] = None, - semantic_id: Optional[base.Reference] = None, - qualifier: Iterable[base.Qualifier] = (), - extension: Iterable[base.Extension] = (), - supplemental_semantic_id: Iterable[base.Reference] = (), - embedded_data_specifications: Iterable[base.EmbeddedDataSpecification] = ()): + + def __init__( + self, + id_short: Optional[base.NameType], + input_variable: Iterable[SubmodelElement] = (), + output_variable: Iterable[SubmodelElement] = (), + in_output_variable: Iterable[SubmodelElement] = (), + display_name: Optional[base.MultiLanguageNameType] = None, + category: Optional[base.NameType] = None, + description: Optional[base.MultiLanguageTextType] = None, + parent: Optional[base.UniqueIdShortNamespace] = None, + semantic_id: Optional[base.Reference] = None, + qualifier: Iterable[base.Qualifier] = (), + extension: Iterable[base.Extension] = (), + supplemental_semantic_id: Iterable[base.Reference] = (), + embedded_data_specifications: Iterable[base.EmbeddedDataSpecification] = (), + ): """ TODO: Add instruction what to do after construction """ - super().__init__(id_short, display_name, category, description, parent, semantic_id, qualifier, extension, - supplemental_semantic_id, embedded_data_specifications) + super().__init__( + id_short, + display_name, + category, + description, + parent, + semantic_id, + qualifier, + extension, + supplemental_semantic_id, + embedded_data_specifications, + ) self.input_variable = base.NamespaceSet(self, [("id_short", True)], input_variable) self.output_variable = base.NamespaceSet(self, [("id_short", True)], output_variable) self.in_output_variable = base.NamespaceSet(self, [("id_short", True)], in_output_variable) @@ -1017,23 +1202,35 @@ class Capability(SubmodelElement): :ivar embedded_data_specifications: List of Embedded data specification. """ - def __init__(self, - id_short: Optional[base.NameType], - display_name: Optional[base.MultiLanguageNameType] = None, - category: Optional[base.NameType] = None, - description: Optional[base.MultiLanguageTextType] = None, - parent: Optional[base.UniqueIdShortNamespace] = None, - semantic_id: Optional[base.Reference] = None, - qualifier: Iterable[base.Qualifier] = (), - extension: Iterable[base.Extension] = (), - supplemental_semantic_id: Iterable[base.Reference] = (), - embedded_data_specifications: Iterable[base.EmbeddedDataSpecification] = ()): + def __init__( + self, + id_short: Optional[base.NameType], + display_name: Optional[base.MultiLanguageNameType] = None, + category: Optional[base.NameType] = None, + description: Optional[base.MultiLanguageTextType] = None, + parent: Optional[base.UniqueIdShortNamespace] = None, + semantic_id: Optional[base.Reference] = None, + qualifier: Iterable[base.Qualifier] = (), + extension: Iterable[base.Extension] = (), + supplemental_semantic_id: Iterable[base.Reference] = (), + embedded_data_specifications: Iterable[base.EmbeddedDataSpecification] = (), + ): """ TODO: Add instruction what to do after construction """ - super().__init__(id_short, display_name, category, description, parent, semantic_id, qualifier, extension, - supplemental_semantic_id, embedded_data_specifications) + super().__init__( + id_short, + display_name, + category, + description, + parent, + semantic_id, + qualifier, + extension, + supplemental_semantic_id, + embedded_data_specifications, + ) class Entity(SubmodelElement, base.UniqueIdShortNamespace): @@ -1073,26 +1270,38 @@ class Entity(SubmodelElement, base.UniqueIdShortNamespace): :ivar embedded_data_specifications: List of Embedded data specification. """ - def __init__(self, - id_short: Optional[base.NameType], - entity_type: Optional[base.EntityType], - statement: Iterable[SubmodelElement] = (), - global_asset_id: Optional[base.Identifier] = None, - specific_asset_id: Iterable[base.SpecificAssetId] = (), - display_name: Optional[base.MultiLanguageNameType] = None, - category: Optional[base.NameType] = None, - description: Optional[base.MultiLanguageTextType] = None, - parent: Optional[base.UniqueIdShortNamespace] = None, - semantic_id: Optional[base.Reference] = None, - qualifier: Iterable[base.Qualifier] = (), - extension: Iterable[base.Extension] = (), - supplemental_semantic_id: Iterable[base.Reference] = (), - embedded_data_specifications: Iterable[base.EmbeddedDataSpecification] = ()): + def __init__( + self, + id_short: Optional[base.NameType], + entity_type: Optional[base.EntityType], + statement: Iterable[SubmodelElement] = (), + global_asset_id: Optional[base.Identifier] = None, + specific_asset_id: Iterable[base.SpecificAssetId] = (), + display_name: Optional[base.MultiLanguageNameType] = None, + category: Optional[base.NameType] = None, + description: Optional[base.MultiLanguageTextType] = None, + parent: Optional[base.UniqueIdShortNamespace] = None, + semantic_id: Optional[base.Reference] = None, + qualifier: Iterable[base.Qualifier] = (), + extension: Iterable[base.Extension] = (), + supplemental_semantic_id: Iterable[base.Reference] = (), + embedded_data_specifications: Iterable[base.EmbeddedDataSpecification] = (), + ): """ TODO: Add instruction what to do after construction """ - super().__init__(id_short, display_name, category, description, parent, semantic_id, qualifier, extension, - supplemental_semantic_id, embedded_data_specifications) + super().__init__( + id_short, + display_name, + category, + description, + parent, + semantic_id, + qualifier, + extension, + supplemental_semantic_id, + embedded_data_specifications, + ) self.statement = base.NamespaceSet(self, [("id_short", True)], statement) # assign private attributes, bypassing setters, as constraints will be checked below self._entity_type: Optional[base.EntityType] = entity_type @@ -1101,7 +1310,7 @@ def __init__(self, specific_asset_id, item_add_hook=self._check_constraint_add_spec_asset_id, item_set_hook=self._check_constraint_set_spec_asset_id, - item_del_hook=self._check_constraint_del_spec_asset_id + item_del_hook=self._check_constraint_del_spec_asset_id, ) self._validate_global_asset_id(global_asset_id) self._validate_aasd_014(entity_type, global_asset_id, bool(specific_asset_id)) @@ -1134,18 +1343,24 @@ def specific_asset_id(self, specific_asset_id: Iterable[base.SpecificAssetId]) - # constraints are checked via _check_constraint_set_spec_asset_id() in this case self._specific_asset_id[:] = specific_asset_id - def _check_constraint_add_spec_asset_id(self, _new_item: base.SpecificAssetId, - _old_list: List[base.SpecificAssetId]) -> None: + def _check_constraint_add_spec_asset_id( + self, _new_item: base.SpecificAssetId, _old_list: List[base.SpecificAssetId] + ) -> None: self._validate_aasd_014(self.entity_type, self.global_asset_id, True) - def _check_constraint_set_spec_asset_id(self, items_to_replace: List[base.SpecificAssetId], - new_items: List[base.SpecificAssetId], - old_list: List[base.SpecificAssetId]) -> None: - self._validate_aasd_014(self.entity_type, self.global_asset_id, - len(old_list) - len(items_to_replace) + len(new_items) > 0) + def _check_constraint_set_spec_asset_id( + self, + items_to_replace: List[base.SpecificAssetId], + new_items: List[base.SpecificAssetId], + old_list: List[base.SpecificAssetId], + ) -> None: + self._validate_aasd_014( + self.entity_type, self.global_asset_id, len(old_list) - len(items_to_replace) + len(new_items) > 0 + ) - def _check_constraint_del_spec_asset_id(self, _item_to_del: base.SpecificAssetId, - old_list: List[base.SpecificAssetId]) -> None: + def _check_constraint_del_spec_asset_id( + self, _item_to_del: base.SpecificAssetId, old_list: List[base.SpecificAssetId] + ) -> None: self._validate_aasd_014(self.entity_type, self.global_asset_id, len(old_list) > 1) @staticmethod @@ -1154,19 +1369,27 @@ def _validate_global_asset_id(global_asset_id: Optional[base.Identifier]) -> Non _string_constraints.check_identifier(global_asset_id) @staticmethod - def _validate_aasd_014(entity_type: Optional[base.EntityType], - global_asset_id: Optional[base.Identifier], - specific_asset_id_nonempty: bool) -> None: + def _validate_aasd_014( + entity_type: Optional[base.EntityType], + global_asset_id: Optional[base.Identifier], + specific_asset_id_nonempty: bool, + ) -> None: if entity_type is None: return - if entity_type == base.EntityType.SELF_MANAGED_ENTITY and global_asset_id is None \ - and not specific_asset_id_nonempty: + if ( + entity_type == base.EntityType.SELF_MANAGED_ENTITY + and global_asset_id is None + and not specific_asset_id_nonempty + ): raise base.AASConstraintViolation( - 14, "A self-managed entity has to have a globalAssetId or a specificAssetId") - elif entity_type == base.EntityType.CO_MANAGED_ENTITY and (global_asset_id is not None - or specific_asset_id_nonempty): + 14, "A self-managed entity has to have a globalAssetId or a specificAssetId" + ) + elif entity_type == base.EntityType.CO_MANAGED_ENTITY and ( + global_asset_id is not None or specific_asset_id_nonempty + ): raise base.AASConstraintViolation( - 14, "A co-managed entity has to have neither a globalAssetId nor a specificAssetId") + 14, "A co-managed entity has to have neither a globalAssetId nor a specificAssetId" + ) class EventElement(SubmodelElement, metaclass=abc.ABCMeta): @@ -1196,20 +1419,33 @@ class EventElement(SubmodelElement, metaclass=abc.ABCMeta): :class:`~basyx.aas.model.base.HasSemantics`) :ivar embedded_data_specifications: List of Embedded data specification. """ + @abc.abstractmethod - def __init__(self, - id_short: Optional[base.NameType], - display_name: Optional[base.MultiLanguageNameType] = None, - category: Optional[base.NameType] = None, - description: Optional[base.MultiLanguageTextType] = None, - parent: Optional[base.UniqueIdShortNamespace] = None, - semantic_id: Optional[base.Reference] = None, - qualifier: Iterable[base.Qualifier] = (), - extension: Iterable[base.Extension] = (), - supplemental_semantic_id: Iterable[base.Reference] = (), - embedded_data_specifications: Iterable[base.EmbeddedDataSpecification] = ()): - super().__init__(id_short, display_name, category, description, parent, semantic_id, qualifier, extension, - supplemental_semantic_id, embedded_data_specifications) + def __init__( + self, + id_short: Optional[base.NameType], + display_name: Optional[base.MultiLanguageNameType] = None, + category: Optional[base.NameType] = None, + description: Optional[base.MultiLanguageTextType] = None, + parent: Optional[base.UniqueIdShortNamespace] = None, + semantic_id: Optional[base.Reference] = None, + qualifier: Iterable[base.Qualifier] = (), + extension: Iterable[base.Extension] = (), + supplemental_semantic_id: Iterable[base.Reference] = (), + embedded_data_specifications: Iterable[base.EmbeddedDataSpecification] = (), + ): + super().__init__( + id_short, + display_name, + category, + description, + parent, + semantic_id, + qualifier, + extension, + supplemental_semantic_id, + embedded_data_specifications, + ) @_string_constraints.constrain_message_topic_type("message_topic") @@ -1265,40 +1501,54 @@ class BasicEventElement(EventElement): :ivar embedded_data_specifications: List of Embedded data specification. """ - def __init__(self, - id_short: Optional[base.NameType], - observed: base.ModelReference[Union["aas.AssetAdministrationShell", Submodel, SubmodelElement]], - direction: base.Direction, - state: base.StateOfEvent, - message_topic: Optional[base.MessageTopicType] = None, - message_broker: Optional[base.ModelReference[Union[Submodel, SubmodelElementList, - SubmodelElementCollection, Entity]]] = None, - last_update: Optional[datatypes.DateTime] = None, - min_interval: Optional[datatypes.Duration] = None, - max_interval: Optional[datatypes.Duration] = None, - display_name: Optional[base.MultiLanguageNameType] = None, - category: Optional[base.NameType] = None, - description: Optional[base.MultiLanguageTextType] = None, - parent: Optional[base.UniqueIdShortNamespace] = None, - semantic_id: Optional[base.Reference] = None, - qualifier: Iterable[base.Qualifier] = (), - extension: Iterable[base.Extension] = (), - supplemental_semantic_id: Iterable[base.Reference] = (), - embedded_data_specifications: Iterable[base.EmbeddedDataSpecification] = ()): + def __init__( + self, + id_short: Optional[base.NameType], + observed: base.ModelReference[Union["aas.AssetAdministrationShell", Submodel, SubmodelElement]], + direction: base.Direction, + state: base.StateOfEvent, + message_topic: Optional[base.MessageTopicType] = None, + message_broker: Optional[ + base.ModelReference[Union[Submodel, SubmodelElementList, SubmodelElementCollection, Entity]] + ] = None, + last_update: Optional[datatypes.DateTime] = None, + min_interval: Optional[datatypes.Duration] = None, + max_interval: Optional[datatypes.Duration] = None, + display_name: Optional[base.MultiLanguageNameType] = None, + category: Optional[base.NameType] = None, + description: Optional[base.MultiLanguageTextType] = None, + parent: Optional[base.UniqueIdShortNamespace] = None, + semantic_id: Optional[base.Reference] = None, + qualifier: Iterable[base.Qualifier] = (), + extension: Iterable[base.Extension] = (), + supplemental_semantic_id: Iterable[base.Reference] = (), + embedded_data_specifications: Iterable[base.EmbeddedDataSpecification] = (), + ): """ TODO: Add instruction what to do after construction """ - super().__init__(id_short, display_name, category, description, parent, semantic_id, qualifier, extension, - supplemental_semantic_id, embedded_data_specifications) + super().__init__( + id_short, + display_name, + category, + description, + parent, + semantic_id, + qualifier, + extension, + supplemental_semantic_id, + embedded_data_specifications, + ) self.observed: base.ModelReference[Union["aas.AssetAdministrationShell", Submodel, SubmodelElement]] = observed # max_interval must be set here because the direction setter attempts to read it self.max_interval: Optional[datatypes.Duration] = None self.direction: base.Direction = direction self.state: base.StateOfEvent = state self.message_topic: Optional[base.MessageTopicType] = message_topic - self.message_broker: Optional[base.ModelReference[Union[Submodel, SubmodelElementList, - SubmodelElementCollection, Entity]]] = message_broker + self.message_broker: Optional[ + base.ModelReference[Union[Submodel, SubmodelElementList, SubmodelElementCollection, Entity]] + ] = message_broker self.last_update: Optional[datatypes.DateTime] = last_update self.min_interval: Optional[datatypes.Duration] = min_interval self.max_interval: Optional[datatypes.Duration] = max_interval diff --git a/sdk/basyx/aas/util/identification.py b/sdk/basyx/aas/util/identification.py index 8d6a1b77e..d72658cd2 100644 --- a/sdk/basyx/aas/util/identification.py +++ b/sdk/basyx/aas/util/identification.py @@ -31,6 +31,7 @@ class AbstractIdentifierGenerator(metaclass=abc.ABCMeta): IRDIs, etc. Some of them may use a given private namespace and create ids within this namespace, others may just use long random numbers to ensure uniqueness. """ + @abc.abstractmethod def generate_id(self, proposal: Optional[str] = None) -> model.Identifier: """ @@ -47,6 +48,7 @@ class UUIDGenerator(AbstractIdentifierGenerator): """ An IdentifierGenerator, that generates URNs of version 1 UUIDs according to RFC 4122. """ + def __init__(self): super().__init__() self._sequence = 0 @@ -70,6 +72,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[model.Identifier, model.Identifiable]): """ Create a new NamespaceIRIGenerator @@ -78,7 +81,7 @@ def __init__(self, namespace: str, provider: model.AbstractObjectProvider[model. :param provider: An AbstractObjectProvider to check existence of Identifiers """ super().__init__() - if not re.match(r'^[a-zA-Z][a-zA-Z0-9+\-\.]*:.*[#/=]$', namespace): + if not re.match(r"^[a-zA-Z][a-zA-Z0-9+\-\.]*:.*[#/=]$", namespace): raise ValueError("Namespace must be a valid IRI, ending with #, / or =") self.provider = provider self._namespace = namespace @@ -111,17 +114,36 @@ def generate_id(self, proposal: Optional[str] = None) -> model.Identifier: # minus '/', '?', '=', '&', '#', which can be used in a path, querystring and fragment # plus not allowed characters (see) https://stackoverflow.com/a/36667242/10315508 _iri_segment_quote_table_tmpl: Dict[Union[str, int], Optional[str]] = { - c: '%{:02X}'.format(c.encode()[0]) + c: "%{:02X}".format(c.encode()[0]) for c in [ - ':', '[', ']', '@', # '/', '?', '#', - '!', '$', '\'', '(', ')', '*', '+', ',', ';', # '=', '&', - ' ', '"', '<', '>', '\\', '^', '`', '{', '|', '}', - ]} + ":", + "[", + "]", + "@", # '/', '?', '#', + "!", + "$", + "'", + "(", + ")", + "*", + "+", + ",", + ";", # '=', '&', + " ", + '"', + "<", + ">", + "\\", + "^", + "`", + "{", + "|", + "}", + ] +} # Remove ASCII control characters -_iri_segment_quote_table_tmpl.update({ - i: None - for i in range(0, 0x1f)}) -_iri_segment_quote_table_tmpl[0x7f] = None +_iri_segment_quote_table_tmpl.update({i: None for i in range(0, 0x1F)}) +_iri_segment_quote_table_tmpl[0x7F] = None _iri_segment_quote_table: Dict[int, Optional[str]] = str.maketrans(_iri_segment_quote_table_tmpl) diff --git a/sdk/docs/source/conf.py b/sdk/docs/source/conf.py index 976c72dac..5c4aedb5e 100644 --- a/sdk/docs/source/conf.py +++ b/sdk/docs/source/conf.py @@ -15,15 +15,15 @@ import datetime -sys.path.insert(0, os.path.abspath('../..')) +sys.path.insert(0, os.path.abspath("../..")) from basyx.aas import __version__ # -- Project information ----------------------------------------------------- -project = 'Eclipse BaSyx Python SDK' -project_copyright = str(datetime.datetime.now().year) + ', the Eclipse BaSyx Authors' -author = 'The Eclipse BaSyx Authors' +project = "Eclipse BaSyx Python SDK" +project_copyright = str(datetime.datetime.now().year) + ", the Eclipse BaSyx Authors" +author = "The Eclipse BaSyx Authors" # The full version, including alpha/beta/rc tags release = __version__ @@ -36,11 +36,11 @@ # ones. extensions = [ - 'sphinx.ext.autodoc', - 'sphinx.ext.coverage', - 'sphinx.ext.intersphinx', - 'sphinx_rtd_theme', - 'sphinxarg.ext' + "sphinx.ext.autodoc", + "sphinx.ext.coverage", + "sphinx.ext.intersphinx", + "sphinx_rtd_theme", + "sphinxarg.ext", ] # Add any paths that contain templates here, relative to this directory. @@ -55,16 +55,13 @@ add_module_names = False # Include all public documented and undocumented members by default. -autodoc_default_options = { - 'members': True, - 'undoc-members': True -} +autodoc_default_options = {"members": True, "undoc-members": True} # Mapping for correctly linking other module documentations. intersphinx_mapping = { - 'python': ('https://docs.python.org/3', None), - 'dateutil': ('https://dateutil.readthedocs.io/en/stable/', None), - 'lxml': ('https://lxml.de/apidoc/', None) + "python": ("https://docs.python.org/3", None), + "dateutil": ("https://dateutil.readthedocs.io/en/stable/", None), + "lxml": ("https://lxml.de/apidoc/", None), } @@ -86,23 +83,23 @@ def setup(app): # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # -html_theme = 'sphinx_rtd_theme' +html_theme = "sphinx_rtd_theme" # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] +html_static_path = ["_static"] # Fix white-space wrapping in tables. # See https://github.com/readthedocs/sphinx_rtd_theme/issues/1505 # Once fixed, this can be removed and '_static' can be removed from html_static_path. -html_style = 'custom.css' +html_style = "custom.css" # Configuration of the 'Edit on GitHub' button at the top right. html_context = { - 'display_github': True, - 'github_user': 'eclipse-basyx', - 'github_repo': 'basyx-python-sdk', - 'github_version': release, - 'conf_py_path': '/docs/source/' + "display_github": True, + "github_user": "eclipse-basyx", + "github_repo": "basyx-python-sdk", + "github_version": release, + "conf_py_path": "/docs/source/", } diff --git a/sdk/test/_helper/setup_testdb.py b/sdk/test/_helper/setup_testdb.py index 20f56c4d9..6f7c45322 100755 --- a/sdk/test/_helper/setup_testdb.py +++ b/sdk/test/_helper/setup_testdb.py @@ -16,6 +16,7 @@ If no CouchDB server at the configured URL, the script will exit with exit code 1. To avoid the error exit code (for use in CI), provide the ``--failsafe`` option. """ + import base64 import configparser import argparse @@ -27,69 +28,74 @@ # Parse test config (required to setup the CouchDB as expected) TEST_CONFIG = configparser.ConfigParser() -TEST_CONFIG.read((os.path.join(os.path.dirname(__file__), "..", "test_config.default.ini"), - os.path.join(os.path.dirname(__file__), "..", "test_config.ini"))) +TEST_CONFIG.read( + ( + os.path.join(os.path.dirname(__file__), "..", "test_config.default.ini"), + os.path.join(os.path.dirname(__file__), "..", "test_config.ini"), + ) +) # Parse command line arguments -parser = argparse.ArgumentParser(description='Setup CouchDB test database according to test_config.ini') -parser.add_argument('--admin-user', '-u', help='Name of the CouchDB admin user') -parser.add_argument('--admin-password', '-p', help='Password of the CouchDB admin user', default='') -parser.add_argument('--failsafe', '-f', action='store_true', - help='Exit with exit code 0 even if no database server could be reached or the database already ' - 'exists on the server.') +parser = argparse.ArgumentParser(description="Setup CouchDB test database according to test_config.ini") +parser.add_argument("--admin-user", "-u", help="Name of the CouchDB admin user") +parser.add_argument("--admin-password", "-p", help="Password of the CouchDB admin user", default="") +parser.add_argument( + "--failsafe", + "-f", + action="store_true", + help="Exit with exit code 0 even if no database server could be reached or the database already " + "exists on the server.", +) args = parser.parse_args() # Some basic data for couchdb setup default_headers = { - 'Accept': 'application/json', + "Accept": "application/json", } if args.admin_user is not None: - default_headers['Authorization'] = 'Basic %s' % base64.b64encode( - ('%s:%s' % (args.admin_user, args.admin_password)).encode('ascii')).decode("ascii") + default_headers["Authorization"] = "Basic %s" % base64.b64encode( + ("%s:%s" % (args.admin_user, args.admin_password)).encode("ascii") + ).decode("ascii") # Check if CouchDB server is available -request = urllib.request.Request( - TEST_CONFIG['couchdb']['url'], - headers=default_headers, - method='HEAD') +request = urllib.request.Request(TEST_CONFIG["couchdb"]["url"], headers=default_headers, method="HEAD") try: urllib.request.urlopen(request) except urllib.error.URLError as e: - print("Could not reach CouchDB server at {}: {}".format(TEST_CONFIG['couchdb']['url'], e)) + print("Could not reach CouchDB server at {}: {}".format(TEST_CONFIG["couchdb"]["url"], e)) sys.exit(0 if args.failsafe else 1) # The actual work # Check if the System databases exist and create them otherwise request = urllib.request.Request( - "{}/_users".format(TEST_CONFIG['couchdb']['url']), - headers=default_headers, - method='HEAD') + "{}/_users".format(TEST_CONFIG["couchdb"]["url"]), headers=default_headers, method="HEAD" +) try: urllib.request.urlopen(request) except urllib.error.HTTPError as e: if e.code != 404: raise - for db in ('_global_changes', '_replicator', '_users'): + for db in ("_global_changes", "_replicator", "_users"): request = urllib.request.Request( - "{}/{}".format(TEST_CONFIG['couchdb']['url'], db), - headers=default_headers, - method='PUT') + "{}/{}".format(TEST_CONFIG["couchdb"]["url"], db), headers=default_headers, method="PUT" + ) urllib.request.urlopen(request) # Create the database if not existing request = urllib.request.Request( - "{}/{}".format(TEST_CONFIG['couchdb']['url'], TEST_CONFIG['couchdb']['database']), + "{}/{}".format(TEST_CONFIG["couchdb"]["url"], TEST_CONFIG["couchdb"]["database"]), headers=default_headers, - method='PUT') + method="PUT", +) try: urllib.request.urlopen(request) except urllib.error.HTTPError as e: if e.code == 412: # TODO make more failsafe: Delete and recreate database if it exists already - print("CouchDB database {} already existed. Exiting ...".format(TEST_CONFIG['couchdb']['database'])) + print("CouchDB database {} already existed. Exiting ...".format(TEST_CONFIG["couchdb"]["database"])) sys.exit(0 if args.failsafe else 1) else: raise @@ -97,15 +103,18 @@ # Create the user if not existing request = urllib.request.Request( - "{}/_users/org.couchdb.user:{}".format(TEST_CONFIG['couchdb']['url'], TEST_CONFIG['couchdb']['user']), + "{}/_users/org.couchdb.user:{}".format(TEST_CONFIG["couchdb"]["url"], TEST_CONFIG["couchdb"]["user"]), headers=default_headers, - method='PUT', - data=json.dumps({ - "name": TEST_CONFIG['couchdb']['user'], - "password": TEST_CONFIG['couchdb']['password'], - "roles": [], - "type": "user" - }).encode()) + method="PUT", + data=json.dumps( + { + "name": TEST_CONFIG["couchdb"]["user"], + "password": TEST_CONFIG["couchdb"]["password"], + "roles": [], + "type": "user", + } + ).encode(), +) # TODO make more failsafe: Set password of user if they exist already urllib.request.urlopen(request) @@ -113,15 +122,11 @@ # Add user as member of database # TODO make more failsafe: Keep existing authorizations request = urllib.request.Request( - "{}/{}/_security".format(TEST_CONFIG['couchdb']['url'], TEST_CONFIG['couchdb']['database']), + "{}/{}/_security".format(TEST_CONFIG["couchdb"]["url"], TEST_CONFIG["couchdb"]["database"]), headers=default_headers, - method='PUT', - data=json.dumps({ - "admins": { - "names": [], - "roles": []}, - "members": { - "names": [TEST_CONFIG['couchdb']['user']], - "roles": []} - }).encode()) + method="PUT", + data=json.dumps( + {"admins": {"names": [], "roles": []}, "members": {"names": [TEST_CONFIG["couchdb"]["user"]], "roles": []}} + ).encode(), +) urllib.request.urlopen(request) diff --git a/sdk/test/_helper/test_helpers.py b/sdk/test/_helper/test_helpers.py index 24be3bc7f..f7bfa4c79 100644 --- a/sdk/test/_helper/test_helpers.py +++ b/sdk/test/_helper/test_helpers.py @@ -5,20 +5,26 @@ import base64 TEST_CONFIG = configparser.ConfigParser() -TEST_CONFIG.read((os.path.join(os.path.dirname(__file__), "..", "test_config.default.ini"), - os.path.join(os.path.dirname(__file__), "..", "test_config.ini"))) +TEST_CONFIG.read( + ( + os.path.join(os.path.dirname(__file__), "..", "test_config.default.ini"), + os.path.join(os.path.dirname(__file__), "..", "test_config.ini"), + ) +) # Check if CouchDB database is available. Otherwise, skip tests. try: request = urllib.request.Request( - "{}/{}".format(TEST_CONFIG['couchdb']['url'], TEST_CONFIG['couchdb']['database']), + "{}/{}".format(TEST_CONFIG["couchdb"]["url"], TEST_CONFIG["couchdb"]["database"]), headers={ - 'Authorization': 'Basic %s' % base64.b64encode( - ('%s:%s' % (TEST_CONFIG['couchdb']['user'], TEST_CONFIG['couchdb']['password'])) - .encode('ascii')).decode("ascii") - }, - method='HEAD') + "Authorization": "Basic %s" + % base64.b64encode( + ("%s:%s" % (TEST_CONFIG["couchdb"]["user"], TEST_CONFIG["couchdb"]["password"])).encode("ascii") + ).decode("ascii") + }, + method="HEAD", + ) urllib.request.urlopen(request) COUCHDB_OKAY = True COUCHDB_ERROR = None diff --git a/sdk/test/adapter/aasx/test_aasx.py b/sdk/test/adapter/aasx/test_aasx.py index e4ab1a1dd..496bf76d7 100644 --- a/sdk/test/adapter/aasx/test_aasx.py +++ b/sdk/test/adapter/aasx/test_aasx.py @@ -22,7 +22,7 @@ class TestAASXUtils(unittest.TestCase): def test_supplementary_file_container(self) -> None: container = aasx.DictSupplementaryFileContainer() - with open(os.path.join(os.path.dirname(__file__), 'TestFile.pdf'), 'rb') as f: + with open(os.path.join(os.path.dirname(__file__), "TestFile.pdf"), "rb") as f: saved_file_name = container.add_file("/TestFile.pdf", f, "application/pdf") # Name should not be modified, since there is no conflict self.assertEqual("/TestFile.pdf", saved_file_name) @@ -33,7 +33,7 @@ def test_supplementary_file_container(self) -> None: self.assertEqual("/TestFile.pdf", same_file_with_same_name) # Add other file with the same name to create a conflict - with open(__file__, 'rb') as f: + with open(__file__, "rb") as f: saved_file_name_2 = container.add_file("/TestFile.pdf", f, "application/pdf") # Now, we have a conflict self.assertNotEqual(saved_file_name, saved_file_name_2) @@ -63,8 +63,10 @@ def test_supplementary_file_container(self) -> None: # Check metadata self.assertEqual("application/pdf", container.get_content_type("/TestFile.pdf")) - self.assertEqual("142a0061de1ef5c22137ab05bb6001335596c0fc8693d33fa9b011ceac652342", - container.get_sha256("/TestFile.pdf").hex()) + self.assertEqual( + "142a0061de1ef5c22137ab05bb6001335596c0fc8693d33fa9b011ceac652342", + container.get_sha256("/TestFile.pdf").hex(), + ) self.assertIn("/TestFile.pdf", container) # Check contents @@ -73,7 +75,7 @@ def test_supplementary_file_container(self) -> None: self.assertEqual(hashlib.sha1(file_content.getvalue()).hexdigest(), "241e62aef8b4cdad0975f6c68a4ed8b3923d8db1") # Add same file again with different content_type to test reference counting - with open(__file__, 'rb') as f: + with open(__file__, "rb") as f: duplicate_file = container.add_file("/TestFile.pdf", f, "image/jpeg") self.assertIn(duplicate_file, container) @@ -122,14 +124,18 @@ def test_write_missing_aas_objects(self): # try to write non-existing object writer.write_aas_objects( "/aasx/selection.xml", - ["https://example.org/Test_AssetAdministrationShell", - "http://false-identifier.org/", - "http://example.org/Submodels/Assets/TestAsset/Identification"], - data, aasx.DictSupplementaryFileContainer() + [ + "https://example.org/Test_AssetAdministrationShell", + "http://false-identifier.org/", + "http://example.org/Submodels/Assets/TestAsset/Identification", + ], + data, + aasx.DictSupplementaryFileContainer(), ) - self.assertIn("Could not find identifiable http://false-identifier.org/ in IdentifiableStore", - log.output[0]) + self.assertIn( + "Could not find identifiable http://false-identifier.org/ in IdentifiableStore", log.output[0] + ) # assert only the two existing objects have been written to aasx file object_store = model.DictIdentifiableStore() @@ -161,7 +167,7 @@ def test_writing_with_missing_file(self) -> None: self.assertIn("Could not find file", cm.exception.args[0]) def test_writing_file_twice(self) -> None: - with (tempfile.TemporaryDirectory() as tmpdir): + with tempfile.TemporaryDirectory() as tmpdir: tmpdir_path = Path(tmpdir) # ---- Arange ---- @@ -172,22 +178,19 @@ def test_writing_file_twice(self) -> None: # create two submodels that reference the same file in file_store first_submodel = model.Submodel( id_="http://example.org/First_Submodel", - submodel_element=[model.File( - id_short="ExampleFile", - content_type="application/pdf", - value=resulting_file_name - )] + submodel_element=[ + model.File(id_short="ExampleFile", content_type="application/pdf", value=resulting_file_name) + ], ) second_submodel = model.Submodel( id_="http://example.org/SecondSubmodel", - submodel_element=[model.File( - id_short="ExampleFile", - content_type="application/pdf", - value=resulting_file_name - )] + submodel_element=[ + model.File(id_short="ExampleFile", content_type="application/pdf", value=resulting_file_name) + ], + ) + data: model.DictIdentifiableStore[model.Identifiable] = model.DictIdentifiableStore( + [first_submodel, second_submodel] ) - data: model.DictIdentifiableStore[model.Identifiable] \ - = model.DictIdentifiableStore([first_submodel, second_submodel]) # ---- Act & Assert ---- with self.assertNoLogs(level="WARNING"): @@ -217,8 +220,10 @@ def test_write_non_aas(self) -> None: with aasx.AASXWriter(tmpdir_path / "tmp.aasx", failsafe=False) as writer: # try to write a non AAS object writer.write_aas("https://example.org/Test_Submodel", data, file_store) - self.assertIn("Identifier https://example.org/Test_Submodel does not belong " - "to an AssetAdministrationShell", cm.exception.args[0]) + self.assertIn( + "Identifier https://example.org/Test_Submodel does not belong to an AssetAdministrationShell", + cm.exception.args[0], + ) def test_write_aas_missing_submodel(self) -> None: with tempfile.TemporaryDirectory() as tmpdir: @@ -226,11 +231,13 @@ def test_write_aas_missing_submodel(self) -> None: # ---- Arange ---- # leave example_submodel out of object store - data = model.DictIdentifiableStore([ - example_aas.create_example_asset_administration_shell(), - example_aas.create_example_asset_identification_submodel(), - example_aas.create_example_bill_of_material_submodel() - ]) + data = model.DictIdentifiableStore( + [ + example_aas.create_example_asset_administration_shell(), + example_aas.create_example_asset_identification_submodel(), + example_aas.create_example_bill_of_material_submodel(), + ] + ) empty_file_store = aasx.DictSupplementaryFileContainer() # ---- Act & Assert ---- @@ -252,12 +259,14 @@ def test_write_aas_missing_concept_description(self) -> None: # ---- Arange ---- # leave example_concept_description out of object store - data = model.DictIdentifiableStore([ - example_aas.create_example_asset_administration_shell(), - example_aas.create_example_submodel(), - example_aas.create_example_asset_identification_submodel(), - example_aas.create_example_bill_of_material_submodel() - ]) + data = model.DictIdentifiableStore( + [ + example_aas.create_example_asset_administration_shell(), + example_aas.create_example_submodel(), + example_aas.create_example_asset_identification_submodel(), + example_aas.create_example_bill_of_material_submodel(), + ] + ) file_store = aasx.DictSupplementaryFileContainer() with open(Path(__file__).parent / "TestFile.pdf", "rb") as pdf: file_store.add_file("/TestFile.pdf", pdf, "application/pdf") @@ -284,22 +293,23 @@ def test_write_aas_false_semantic_id(self) -> None: # ---- Arange ---- # semanticId of submodel holds reference to an object # that is no ContentDescription - second_submodel = model.Submodel( - id_="https://example.org/Second_Submodel" - ) + second_submodel = model.Submodel(id_="https://example.org/Second_Submodel") submodel = model.Submodel( id_="https://example.org/Test_Submodel", semantic_id=model.ModelReference( key=(model.Key(type_=model.KeyTypes.SUBMODEL, value="https://example.org/Second_Submodel"),), - type_=model.ConceptDescription - ) + type_=model.ConceptDescription, + ), + ) + data = model.DictIdentifiableStore( + [ + example_aas.create_example_asset_administration_shell(), + example_aas.create_example_asset_identification_submodel(), + example_aas.create_example_bill_of_material_submodel(), + submodel, + second_submodel, + ] ) - data = model.DictIdentifiableStore([ - example_aas.create_example_asset_administration_shell(), - example_aas.create_example_asset_identification_submodel(), - example_aas.create_example_bill_of_material_submodel(), - submodel, second_submodel - ]) empty_file_store = aasx.DictSupplementaryFileContainer() # ---- Act & Assert ---- @@ -354,10 +364,10 @@ def test_write_thumbnail_twice(self) -> None: def test_writing_reading_example_aas(self) -> None: # Create example data and file_store - data = example_aas.create_full_example() # creates a complete, valid example AAS - files = aasx.DictSupplementaryFileContainer() # in-memory store for attached files - with open(os.path.join(os.path.dirname(__file__), 'TestFile.pdf'), 'rb') as f: - files.add_file("/TestFile.pdf", f, "application/pdf") # add a real supplementary pdf file + data = example_aas.create_full_example() # creates a complete, valid example AAS + files = aasx.DictSupplementaryFileContainer() # in-memory store for attached files + with open(os.path.join(os.path.dirname(__file__), "TestFile.pdf"), "rb") as f: + files.add_file("/TestFile.pdf", f, "application/pdf") # add a real supplementary pdf file f.seek(0) # Create OPC/AASX core properties # create AASX metadata (core properties) @@ -366,10 +376,10 @@ def test_writing_reading_example_aas(self) -> None: cp.creator = "Eclipse BaSyx Python Testing Framework" # Write AASX file - for write_json in (False, True): # Loop over both XML and JSON modes + for write_json in (False, True): # Loop over both XML and JSON modes with self.subTest(write_json=write_json): - fd, filename = tempfile.mkstemp(suffix=".aasx") # create temporary file - os.close(fd) # close file descriptor + fd, filename = tempfile.mkstemp(suffix=".aasx") # create temporary file + os.close(fd) # close file descriptor # Write AASX file # the zipfile library reports errors as UserWarnings via the warnings library. Let's check for @@ -377,13 +387,15 @@ def test_writing_reading_example_aas(self) -> None: with warnings.catch_warnings(record=True) as w: with aasx.AASXWriter(filename) as writer: # TODO test writing multiple AAS - writer.write_aas('https://example.org/Test_AssetAdministrationShell', - data, files, write_json=write_json) + writer.write_aas( + "https://example.org/Test_AssetAdministrationShell", data, files, write_json=write_json + ) writer.write_core_properties(cp) assert isinstance(w, list) # This should be True due to the record=True parameter - self.assertEqual(0, len(w), f"Warnings were issued while writing the AASX file: " - f"{[warning.message for warning in w]}") + self.assertEqual( + 0, len(w), f"Warnings were issued while writing the AASX file: {[warning.message for warning in w]}" + ) # Read AASX file new_data: model.DictIdentifiableStore[model.Identifiable] = model.DictIdentifiableStore() @@ -408,8 +420,9 @@ def test_writing_reading_example_aas(self) -> None: self.assertEqual(new_files.get_content_type("/TestFile.pdf"), "application/pdf") file_content = io.BytesIO() new_files.write_file("/TestFile.pdf", file_content) - self.assertEqual(hashlib.sha1(file_content.getvalue()).hexdigest(), - "241e62aef8b4cdad0975f6c68a4ed8b3923d8db1") + self.assertEqual( + hashlib.sha1(file_content.getvalue()).hexdigest(), "241e62aef8b4cdad0975f6c68a4ed8b3923d8db1" + ) os.unlink(filename) @@ -419,7 +432,7 @@ def _create_test_aasx(self) -> str: data = example_aas.create_full_example() files = aasx.DictSupplementaryFileContainer() - with open(os.path.join(os.path.dirname(__file__), 'TestFile.pdf'), 'rb') as f: + with open(os.path.join(os.path.dirname(__file__), "TestFile.pdf"), "rb") as f: files.add_file("/TestFile.pdf", f, "application/pdf") f.seek(0) @@ -432,10 +445,7 @@ def _create_test_aasx(self) -> str: os.close(fd) with aasx.AASXWriter(filename) as writer: - writer.write_aas( - 'https://example.org/Test_AssetAdministrationShell', - data, files, write_json=False - ) + writer.write_aas("https://example.org/Test_AssetAdministrationShell", data, files, write_json=False) writer.write_core_properties(cp) return filename @@ -474,19 +484,18 @@ def test_get_thumbnail(self) -> None: # ---- Arange ---- tmpdir_path = Path(tmpdir) - data: model.DictIdentifiableStore[model.Identifiable] = model.DictIdentifiableStore([ - model.AssetAdministrationShell( - id_="http://example.org/Test_AAS", - asset_information=model.AssetInformation( - global_asset_id="http://example.org/Test_Asset" + data: model.DictIdentifiableStore[model.Identifiable] = model.DictIdentifiableStore( + [ + model.AssetAdministrationShell( + id_="http://example.org/Test_AAS", + asset_information=model.AssetInformation(global_asset_id="http://example.org/Test_Asset"), ) - ) - ]) + ] + ) with aasx.AASXWriter(tmpdir_path / "test_thumbnail.aasx") as writer: writer.write_aas( - 'http://example.org/Test_AAS', - data, aasx.DictSupplementaryFileContainer(), write_json=False + "http://example.org/Test_AAS", data, aasx.DictSupplementaryFileContainer(), write_json=False ) with open(Path(__file__).parent / "test.png", "rb") as png: thumbnail = png.read() @@ -525,15 +534,12 @@ def test_read_into(self) -> None: ids = reader.read_into(objects, files) assert isinstance(w, list) - self.assertEqual(0, len(w)) # Ensure no warnings were raised + self.assertEqual(0, len(w)) # Ensure no warnings were raised - self.assertGreater(len(ids), 0) # Ensure at least one AAS was read - self.assertGreater(len(objects), 0) # Ensure objects were populated + self.assertGreater(len(ids), 0) # Ensure at least one AAS was read + self.assertGreater(len(objects), 0) # Ensure objects were populated self.assertGreater(len(files), 0) - self.assertEqual( - files.get_content_type("/TestFile.pdf"), - "application/pdf" - ) + self.assertEqual(files.get_content_type("/TestFile.pdf"), "application/pdf") finally: os.unlink(filename) @@ -550,16 +556,12 @@ def test_supplementary_file_integrity(self) -> None: buf = io.BytesIO() files.write_file("/TestFile.pdf", buf) - self.assertEqual( - hashlib.sha1(buf.getvalue()).hexdigest(), - "241e62aef8b4cdad0975f6c68a4ed8b3923d8db1" - ) + self.assertEqual(hashlib.sha1(buf.getvalue()).hexdigest(), "241e62aef8b4cdad0975f6c68a4ed8b3923d8db1") finally: os.unlink(filename) class AASXWriterReferencedSubmodelsTest(unittest.TestCase): - def test_only_referenced_submodels(self): """ Test that verifies that all Submodels (referenced and unreferenced) are written to the AASX package when using @@ -574,10 +576,9 @@ def test_only_referenced_submodels(self): aas = model.AssetAdministrationShell( id_="Test_AAS", asset_information=model.AssetInformation( - asset_kind=model.AssetKind.INSTANCE, - global_asset_id="http://example.org/Test_Asset" + asset_kind=model.AssetKind.INSTANCE, global_asset_id="http://example.org/Test_Asset" ), - submodel={model.ModelReference.from_referable(referenced_submodel)} + submodel={model.ModelReference.from_referable(referenced_submodel)}, ) # IdentifiableStore containing all objects @@ -599,7 +600,7 @@ def test_only_referenced_submodels(self): aas_ids=[aas.id], object_store=identifiable_store, file_store=file_store, - write_json=write_json + write_json=write_json, ) # Read back @@ -609,7 +610,7 @@ def test_only_referenced_submodels(self): reader.read_into(new_data, new_files) # Assertions - self.assertIn(referenced_submodel.id, new_data) # referenced Submodel is included + self.assertIn(referenced_submodel.id, new_data) # referenced Submodel is included self.assertNotIn(unreferenced_submodel.id, new_data) # unreferenced Submodel is excluded os.unlink(filename) @@ -626,7 +627,7 @@ def test_only_referenced_submodels(self): part_name="/aasx/my_aas_part.xml", objects=identifiable_store, file_store=file_store, - write_json=write_json + write_json=write_json, ) # Read back diff --git a/sdk/test/adapter/json/test_json_deserialization.py b/sdk/test/adapter/json/test_json_deserialization.py index 9067d3e7b..fe89c46d2 100644 --- a/sdk/test/adapter/json/test_json_deserialization.py +++ b/sdk/test/adapter/json/test_json_deserialization.py @@ -11,12 +11,18 @@ when trying to reconstruct the serialized data structure. This module additionally tests error behaviour and verifies deserialization results. """ + import io import json import logging import unittest -from basyx.aas.adapter.json import AASFromJsonDecoder, StrictAASFromJsonDecoder, StrictStrippedAASFromJsonDecoder, \ - read_aas_json_file, read_aas_json_file_into +from basyx.aas.adapter.json import ( + AASFromJsonDecoder, + StrictAASFromJsonDecoder, + StrictStrippedAASFromJsonDecoder, + read_aas_json_file, + read_aas_json_file_into, +) from basyx.aas import model @@ -37,8 +43,11 @@ def test_file_format_wrong_list(self) -> None: } ] }""" - with self.assertRaisesRegex(TypeError, r"AssetAdministrationShell.* was " - r"in the wrong list 'submodels'"): + with self.assertRaisesRegex( + TypeError, + r"AssetAdministrationShell.* was " + r"in the wrong list 'submodels'", + ): read_aas_json_file(io.StringIO(data), failsafe=False) with self.assertLogs(logging.getLogger(), level=logging.WARNING) as cm: read_aas_json_file(io.StringIO(data), failsafe=True) @@ -210,8 +219,9 @@ def get_clean_store() -> model.DictIdentifiableStore: identifiable_store = get_clean_store() with self.assertRaisesRegex(KeyError, r"already exists in store"): - identifiers = read_aas_json_file_into(identifiable_store, string_io, replace_existing=False, - ignore_existing=False) + identifiers = read_aas_json_file_into( + identifiable_store, string_io, replace_existing=False, ignore_existing=False + ) self.assertEqual(len(identifiers), 0) submodel = identifiable_store.pop() self.assertIsInstance(submodel, model.Submodel) diff --git a/sdk/test/adapter/json/test_json_serialization.py b/sdk/test/adapter/json/test_json_serialization.py index 136c04110..4895802c5 100644 --- a/sdk/test/adapter/json/test_json_serialization.py +++ b/sdk/test/adapter/json/test_json_serialization.py @@ -14,17 +14,26 @@ from jsonschema import validate # type: ignore from typing import Set, Union -from basyx.aas.examples.data import example_aas_missing_attributes, example_aas, \ - example_aas_mandatory_attributes, example_submodel_template, create_example +from basyx.aas.examples.data import ( + example_aas_missing_attributes, + example_aas, + example_aas_mandatory_attributes, + example_submodel_template, + create_example, +) -JSON_SCHEMA_FILE = os.path.join(os.path.dirname(__file__), '../schemas/aasJSONSchema.json') +JSON_SCHEMA_FILE = os.path.join(os.path.dirname(__file__), "../schemas/aasJSONSchema.json") class JsonSerializationTest(unittest.TestCase): def test_serialize_object(self) -> None: - test_object = model.Property("test_id_short", model.datatypes.String, category="PARAMETER", - description=model.MultiLanguageTextType({"en-US": "Germany", "de": "Deutschland"})) + test_object = model.Property( + "test_id_short", + model.datatypes.String, + category="PARAMETER", + description=model.MultiLanguageTextType({"en-US": "Germany", "de": "Deutschland"}), + ) json_data = json.dumps(test_object, cls=AASToJsonEncoder) def test_random_object_serialization(self) -> None: @@ -34,16 +43,20 @@ def test_random_object_serialization(self) -> None: assert submodel_identifier is not None submodel_reference = model.ModelReference(submodel_key, model.Submodel) submodel = model.Submodel(submodel_identifier) - test_aas = model.AssetAdministrationShell(model.AssetInformation(global_asset_id="test"), - aas_identifier, submodel={submodel_reference}) + test_aas = model.AssetAdministrationShell( + model.AssetInformation(global_asset_id="test"), aas_identifier, submodel={submodel_reference} + ) # serialize object to json - json_data = json.dumps({ - 'assetAdministrationShells': [test_aas], - 'submodels': [submodel], - 'assets': [], - 'conceptDescriptions': [], - }, cls=AASToJsonEncoder) + json_data = json.dumps( + { + "assetAdministrationShells": [test_aas], + "submodels": [submodel], + "assets": [], + "conceptDescriptions": [], + }, + cls=AASToJsonEncoder, + ) json_data_new = json.loads(json_data) @@ -61,21 +74,22 @@ def test_random_object_serialization(self) -> None: submodel_reference = model.ModelReference(submodel_key, model.Submodel) # The JSONSchema expects every object with HasSemnatics (like Submodels) to have a `semanticId` Reference, which # must be a Reference. (This seems to be a bug in the JSONSchema.) - submodel = model.Submodel(submodel_identifier, - semantic_id=model.ExternalReference((model.Key(model.KeyTypes.GLOBAL_REFERENCE, - "http://example.org/TestSemanticId"),))) - test_aas = model.AssetAdministrationShell(model.AssetInformation(global_asset_id="test"), - aas_identifier, submodel={submodel_reference}) + submodel = model.Submodel( + submodel_identifier, + semantic_id=model.ExternalReference( + (model.Key(model.KeyTypes.GLOBAL_REFERENCE, "http://example.org/TestSemanticId"),) + ), + ) + test_aas = model.AssetAdministrationShell( + model.AssetInformation(global_asset_id="test"), aas_identifier, submodel={submodel_reference} + ) # serialize object to json - json_data = json.dumps({ - 'assetAdministrationShells': [test_aas], - 'submodels': [submodel] - }, cls=AASToJsonEncoder) + json_data = json.dumps({"assetAdministrationShells": [test_aas], "submodels": [submodel]}, cls=AASToJsonEncoder) json_data_new = json.loads(json_data) # load schema - with open(JSON_SCHEMA_FILE, 'r') as json_file: + with open(JSON_SCHEMA_FILE, "r") as json_file: aas_schema = json.load(json_file) # validate serialization against schema @@ -86,7 +100,7 @@ def test_aas_example_serialization(self) -> None: file = io.StringIO() write_aas_json_file(file=file, data=data) - with open(JSON_SCHEMA_FILE, 'r') as json_file: + with open(JSON_SCHEMA_FILE, "r") as json_file: aas_json_schema = json.load(json_file) file.seek(0) @@ -101,7 +115,7 @@ def test_submodel_template_serialization(self) -> None: file = io.StringIO() write_aas_json_file(file=file, data=data) - with open(JSON_SCHEMA_FILE, 'r') as json_file: + with open(JSON_SCHEMA_FILE, "r") as json_file: aas_json_schema = json.load(json_file) file.seek(0) @@ -115,7 +129,7 @@ def test_full_empty_example_serialization(self) -> None: file = io.StringIO() write_aas_json_file(file=file, data=data) - with open(JSON_SCHEMA_FILE, 'r') as json_file: + with open(JSON_SCHEMA_FILE, "r") as json_file: aas_json_schema = json.load(json_file) file.seek(0) @@ -129,7 +143,7 @@ def test_missing_serialization(self) -> None: file = io.StringIO() write_aas_json_file(file=file, data=data) - with open(JSON_SCHEMA_FILE, 'r') as json_file: + with open(JSON_SCHEMA_FILE, "r") as json_file: aas_json_schema = json.load(json_file) file.seek(0) @@ -144,7 +158,7 @@ def test_concept_description_serialization(self) -> None: file = io.StringIO() write_aas_json_file(file=file, data=data) - with open(JSON_SCHEMA_FILE, 'r') as json_file: + with open(JSON_SCHEMA_FILE, "r") as json_file: aas_json_schema = json.load(json_file) file.seek(0) @@ -158,7 +172,7 @@ def test_full_example_serialization(self) -> None: file = io.StringIO() write_aas_json_file(file=file, data=data) - with open(JSON_SCHEMA_FILE, 'r') as json_file: + with open(JSON_SCHEMA_FILE, "r") as json_file: aas_json_schema = json.load(json_file) file.seek(0) @@ -185,9 +199,7 @@ def test_stripped_qualifiable(self) -> None: qualifier2 = model.Qualifier("test_qualifier2", str) operation = model.Operation("test_operation", qualifier={qualifier}) submodel = model.Submodel( - "http://example.org/test_submodel", - submodel_element=[operation], - qualifier={qualifier2} + "http://example.org/test_submodel", submodel_element=[operation], qualifier={qualifier2} ) self._checkNormalAndStripped({"submodelElements", "qualifiers"}, submodel) @@ -195,16 +207,8 @@ def test_stripped_qualifiable(self) -> None: def test_stripped_annotated_relationship_element(self) -> None: mlp = model.MultiLanguageProperty("test_multi_language_property", category="PARAMETER") - ref = model.ModelReference( - (model.Key(model.KeyTypes.SUBMODEL, "http://example.org/test_ref"),), - model.Submodel - ) - are = model.AnnotatedRelationshipElement( - "test_annotated_relationship_element", - ref, - ref, - annotation=[mlp] - ) + ref = model.ModelReference((model.Key(model.KeyTypes.SUBMODEL, "http://example.org/test_ref"),), model.Submodel) + are = model.AnnotatedRelationshipElement("test_annotated_relationship_element", ref, ref, annotation=[mlp]) self._checkNormalAndStripped("annotations", are) @@ -228,13 +232,12 @@ def test_stripped_submodel_element_collection(self) -> None: def test_stripped_asset_administration_shell(self) -> None: submodel_ref = model.ModelReference( - (model.Key(model.KeyTypes.SUBMODEL, "http://example.org/test_ref"),), - model.Submodel + (model.Key(model.KeyTypes.SUBMODEL, "http://example.org/test_ref"),), model.Submodel ) aas = model.AssetAdministrationShell( model.AssetInformation(global_asset_id="http://example.org/test_ref"), "http://example.org/test_aas", - submodel={submodel_ref} + submodel={submodel_ref}, ) self._checkNormalAndStripped({"submodels"}, aas) diff --git a/sdk/test/adapter/json/test_json_serialization_deserialization.py b/sdk/test/adapter/json/test_json_serialization_deserialization.py index 48beb4c27..6c5f1808e 100644 --- a/sdk/test/adapter/json/test_json_serialization_deserialization.py +++ b/sdk/test/adapter/json/test_json_serialization_deserialization.py @@ -12,8 +12,13 @@ from basyx.aas import model from basyx.aas.adapter.json import AASToJsonEncoder, write_aas_json_file, read_aas_json_file -from basyx.aas.examples.data import example_aas_missing_attributes, example_aas, \ - example_aas_mandatory_attributes, example_submodel_template, create_example +from basyx.aas.examples.data import ( + example_aas_missing_attributes, + example_aas, + example_aas_mandatory_attributes, + example_submodel_template, + create_example, +) from basyx.aas.examples.data._helper import AASDataChecker from typing import Iterable, IO @@ -27,16 +32,20 @@ def test_random_object_serialization_deserialization(self) -> None: assert submodel_identifier is not None submodel_reference = model.ModelReference(submodel_key, model.Submodel) submodel = model.Submodel(submodel_identifier) - test_aas = model.AssetAdministrationShell(model.AssetInformation(global_asset_id="test"), - aas_identifier, submodel={submodel_reference}) + test_aas = model.AssetAdministrationShell( + model.AssetInformation(global_asset_id="test"), aas_identifier, submodel={submodel_reference} + ) # serialize object to json - json_data = json.dumps({ - 'assetAdministrationShells': [test_aas], - 'submodels': [submodel], - 'assets': [], - 'conceptDescriptions': [], - }, cls=AASToJsonEncoder) + json_data = json.dumps( + { + "assetAdministrationShells": [test_aas], + "submodels": [submodel], + "assets": [], + "conceptDescriptions": [], + }, + cls=AASToJsonEncoder, + ) json_data_new = json.loads(json_data) # try deserializing the json string into a DictIdentifiableStore of AAS objects with help of the json module diff --git a/sdk/test/adapter/test_load_directory.py b/sdk/test/adapter/test_load_directory.py index 46b87f5d0..68f374e89 100644 --- a/sdk/test/adapter/test_load_directory.py +++ b/sdk/test/adapter/test_load_directory.py @@ -12,41 +12,30 @@ def test_reading_all_files(self): # Create an AAS per file type json_aas = model.AssetAdministrationShell( id_="http://example.org/JSON_AAS", - asset_information=model.AssetInformation( - global_asset_id="http://example.org/JSON_Asset" - ) + asset_information=model.AssetInformation(global_asset_id="http://example.org/JSON_Asset"), ) xml_aas = model.AssetAdministrationShell( id_="http://example.org/XML_AAS", - asset_information=model.AssetInformation( - global_asset_id="http://example.org/XML_Asset" - ) + asset_information=model.AssetInformation(global_asset_id="http://example.org/XML_Asset"), ) aasx_aas = model.AssetAdministrationShell( id_="http://example.org/aasx_AAS", - asset_information=model.AssetInformation( - global_asset_id="http://example.org/aasx_Asset" - ) + asset_information=model.AssetInformation(global_asset_id="http://example.org/aasx_Asset"), ) # load TestFile.pdf to save into aasx file_container = adapter.aasx.DictSupplementaryFileContainer() with open(Path(__file__).parent / "aasx" / "TestFile.pdf", "rb") as pdf: - resulting_file_name = file_container.add_file( - "/aasx/suppl/file.pdf", pdf, "application/json") + resulting_file_name = file_container.add_file("/aasx/suppl/file.pdf", pdf, "application/json") # create submodel for aasx_aas that refers to pdf sm_with_file = model.Submodel( id_="http://example.org/tmp_Submodel", submodel_element={ - model.File( - id_short="SampleFile", - content_type="application/json", - value=resulting_file_name - ) - } + model.File(id_short="SampleFile", content_type="application/json", value=resulting_file_name) + }, ) aasx_aas.submodel.add(model.ModelReference.from_referable(sm_with_file)) @@ -54,17 +43,15 @@ def test_reading_all_files(self): temp_dir_path = Path(temp_dir) # save to json file - adapter.json.write_aas_json_file(temp_dir_path / "testAAS.json", - model.DictIdentifiableStore([json_aas])) + adapter.json.write_aas_json_file(temp_dir_path / "testAAS.json", model.DictIdentifiableStore([json_aas])) # save to xml file - adapter.xml.write_aas_xml_file(temp_dir_path / "testAAS.xml", - model.DictIdentifiableStore([xml_aas])) + adapter.xml.write_aas_xml_file(temp_dir_path / "testAAS.xml", model.DictIdentifiableStore([xml_aas])) # save to aasx file with adapter.aasx.AASXWriter(temp_dir_path / "testAAS.aasx") as writer: writer.write_aas( aas_ids=["http://example.org/aasx_AAS"], object_store=model.DictIdentifiableStore([aasx_aas, sm_with_file]), - file_store=file_container + file_store=file_container, ) # ---- Act ---- diff --git a/sdk/test/adapter/xml/test_xml_deserialization.py b/sdk/test/adapter/xml/test_xml_deserialization.py index 395d23ed7..1b96fbb81 100644 --- a/sdk/test/adapter/xml/test_xml_deserialization.py +++ b/sdk/test/adapter/xml/test_xml_deserialization.py @@ -10,8 +10,13 @@ import unittest from basyx.aas import model -from basyx.aas.adapter.xml import StrictAASFromXmlDecoder, XMLConstructables, read_aas_xml_file, \ - read_aas_xml_file_into, read_aas_xml_element +from basyx.aas.adapter.xml import ( + StrictAASFromXmlDecoder, + XMLConstructables, + read_aas_xml_file, + read_aas_xml_file_into, + read_aas_xml_element, +) from basyx.aas.adapter.xml.xml_deserialization import _tag_replace_namespace from basyx.aas.adapter._generic import XML_NS_MAP from lxml import etree @@ -29,8 +34,9 @@ def _root_cause(exception: BaseException) -> BaseException: class XmlDeserializationTest(unittest.TestCase): - def _assertInExceptionAndLog(self, xml: str, strings: Union[Iterable[str], str], error_type: Type[BaseException], - log_level: int) -> None: + def _assertInExceptionAndLog( + self, xml: str, strings: Union[Iterable[str], str], error_type: Type[BaseException], log_level: int + ) -> None: """ Runs read_xml_aas_file in failsafe mode and checks if each string is contained in the first message logged. Then runs it in non-failsafe mode and checks if each string is contained in the first error raised. @@ -53,11 +59,7 @@ def _assertInExceptionAndLog(self, xml: str, strings: Union[Iterable[str], str], self.assertIn(s, str(cause)) def test_malformed_xml(self) -> None: - xml = ( - "invalid xml", - _xml_wrap("<<>>><<<<<"), - _xml_wrap("") - ) + xml = ("invalid xml", _xml_wrap("<<>>><<<<<"), _xml_wrap("")) for s in xml: self._assertInExceptionAndLog(s, [], etree.XMLSyntaxError, logging.ERROR) @@ -345,8 +347,9 @@ def xml(id_: str) -> str: """ - self._assertInExceptionAndLog(xml(""), f'{{{XML_NS_MAP["aas"]}}}id on line 5 has no text', KeyError, - logging.ERROR) + self._assertInExceptionAndLog( + xml(""), f"{{{XML_NS_MAP['aas']}}}id on line 5 has no text", KeyError, logging.ERROR + ) read_aas_xml_file(io.StringIO(xml("urn:x-test:test-submodel"))) @@ -421,8 +424,9 @@ def test_stripped_asset_administration_shell(self) -> None: self.assertEqual(len(aas.submodel), 1) # check if submodels are ignored in stripped mode - aas = read_aas_xml_element(string_io, XMLConstructables.ASSET_ADMINISTRATION_SHELL, failsafe=False, - stripped=True) + aas = read_aas_xml_element( + string_io, XMLConstructables.ASSET_ADMINISTRATION_SHELL, failsafe=False, stripped=True + ) self.assertIsInstance(aas, model.AssetAdministrationShell) assert isinstance(aas, model.AssetAdministrationShell) self.assertEqual(len(aas.submodel), 0) @@ -516,8 +520,9 @@ def __init__(self, *args, **kwargs): class EnhancedAASDecoder(StrictAASFromXmlDecoder): @classmethod - def construct_submodel(cls, element: etree._Element, object_class=EnhancedSubmodel, **kwargs) \ - -> model.Submodel: + def construct_submodel( + cls, element: etree._Element, object_class=EnhancedSubmodel, **kwargs + ) -> model.Submodel: return super().construct_submodel(element, object_class=object_class, **kwargs) xml = f""" @@ -535,25 +540,25 @@ def construct_submodel(cls, element: etree._Element, object_class=EnhancedSubmod class TestTagReplaceNamespace(unittest.TestCase): def test_known_namespace(self): - tag = '{https://admin-shell.io/aas/3/1}tag' - expected = 'aas:tag' + tag = "{https://admin-shell.io/aas/3/1}tag" + expected = "aas:tag" self.assertEqual(_tag_replace_namespace(tag, XML_NS_MAP), expected) def test_empty_prefix(self): # Empty prefix should not be replaced as otherwise it would apply everywhere - tag = '{https://admin-shell.io/aas/3/1}tag' + tag = "{https://admin-shell.io/aas/3/1}tag" nsmap = {"": "https://admin-shell.io/aas/3/1"} - expected = '{https://admin-shell.io/aas/3/1}tag' + expected = "{https://admin-shell.io/aas/3/1}tag" self.assertEqual(_tag_replace_namespace(tag, nsmap), expected) def test_empty_namespace(self): # Empty namespaces should also have no effect - tag = '{https://admin-shell.io/aas/3/1}tag' + tag = "{https://admin-shell.io/aas/3/1}tag" nsmap = {"aas": ""} - expected = '{https://admin-shell.io/aas/3/1}tag' + expected = "{https://admin-shell.io/aas/3/1}tag" self.assertEqual(_tag_replace_namespace(tag, nsmap), expected) def test_unknown_namespace(self): - tag = '{http://unknownnamespace.com}unknown' - expected = '{http://unknownnamespace.com}unknown' # Unknown namespace should remain unchanged + tag = "{http://unknownnamespace.com}unknown" + expected = "{http://unknownnamespace.com}unknown" # Unknown namespace should remain unchanged self.assertEqual(_tag_replace_namespace(tag, XML_NS_MAP), expected) diff --git a/sdk/test/adapter/xml/test_xml_serialization.py b/sdk/test/adapter/xml/test_xml_serialization.py index 9d0d6a6dd..2e51431aa 100644 --- a/sdk/test/adapter/xml/test_xml_serialization.py +++ b/sdk/test/adapter/xml/test_xml_serialization.py @@ -13,31 +13,38 @@ from basyx.aas import model from basyx.aas.adapter.xml import write_aas_xml_file, xml_serialization -from basyx.aas.examples.data import example_aas_missing_attributes, example_aas, \ - example_submodel_template, example_aas_mandatory_attributes +from basyx.aas.examples.data import ( + example_aas_missing_attributes, + example_aas, + example_submodel_template, + example_aas_mandatory_attributes, +) -XML_SCHEMA_FILE = os.path.join(os.path.dirname(__file__), '../schemas/aasXMLSchema.xsd') +XML_SCHEMA_FILE = os.path.join(os.path.dirname(__file__), "../schemas/aasXMLSchema.xsd") class XMLSerializationTest(unittest.TestCase): def test_serialize_object(self) -> None: - test_object = model.Property("test_id_short", - model.datatypes.String, - category="PARAMETER", - description=model.MultiLanguageTextType({"en-US": "Germany", "de": "Deutschland"})) - xml_data = xml_serialization.property_to_xml(test_object, xml_serialization.NS_AAS+"test_object") + test_object = model.Property( + "test_id_short", + model.datatypes.String, + category="PARAMETER", + description=model.MultiLanguageTextType({"en-US": "Germany", "de": "Deutschland"}), + ) + xml_data = xml_serialization.property_to_xml(test_object, xml_serialization.NS_AAS + "test_object") # todo: is this a correct way to test it? def test_random_object_serialization(self) -> None: aas_identifier = "AAS1" submodel_key = (model.Key(model.KeyTypes.SUBMODEL, "SM1"),) submodel_identifier = submodel_key[0].get_identifier() - assert (submodel_identifier is not None) + assert submodel_identifier is not None submodel_reference = model.ModelReference(submodel_key, model.Submodel) submodel = model.Submodel(submodel_identifier) - test_aas = model.AssetAdministrationShell(model.AssetInformation(global_asset_id="Test"), - aas_identifier, submodel={submodel_reference}) + test_aas = model.AssetAdministrationShell( + model.AssetInformation(global_asset_id="Test"), aas_identifier, submodel={submodel_reference} + ) test_data: model.DictIdentifiableStore[model.Identifiable] = model.DictIdentifiableStore() test_data.add(test_aas) @@ -59,12 +66,15 @@ def test_random_object_serialization(self) -> None: submodel_identifier = submodel_key[0].get_identifier() assert submodel_identifier is not None submodel_reference = model.ModelReference(submodel_key, model.Submodel) - submodel = model.Submodel(submodel_identifier, - semantic_id=model.ExternalReference((model.Key(model.KeyTypes.GLOBAL_REFERENCE, - "http://example.org/" - "TestSemanticId"),))) - test_aas = model.AssetAdministrationShell(model.AssetInformation(global_asset_id="Test"), - aas_identifier, submodel={submodel_reference}) + submodel = model.Submodel( + submodel_identifier, + semantic_id=model.ExternalReference( + (model.Key(model.KeyTypes.GLOBAL_REFERENCE, "http://example.org/TestSemanticId"),) + ), + ) + test_aas = model.AssetAdministrationShell( + model.AssetInformation(global_asset_id="Test"), aas_identifier, submodel={submodel_reference} + ) # serialize object to xml test_data: model.DictIdentifiableStore[model.Identifiable] = model.DictIdentifiableStore() test_data.add(test_aas) diff --git a/sdk/test/adapter/xml/test_xml_serialization_deserialization.py b/sdk/test/adapter/xml/test_xml_serialization_deserialization.py index e38aefd68..cf8e7c551 100644 --- a/sdk/test/adapter/xml/test_xml_serialization_deserialization.py +++ b/sdk/test/adapter/xml/test_xml_serialization_deserialization.py @@ -9,11 +9,21 @@ import unittest from basyx.aas import model -from basyx.aas.adapter.xml import write_aas_xml_file, read_aas_xml_file, write_aas_xml_element, read_aas_xml_element, \ - XMLConstructables +from basyx.aas.adapter.xml import ( + write_aas_xml_file, + read_aas_xml_file, + write_aas_xml_element, + read_aas_xml_element, + XMLConstructables, +) -from basyx.aas.examples.data import example_aas_missing_attributes, example_aas, \ - example_aas_mandatory_attributes, example_submodel_template, create_example +from basyx.aas.examples.data import ( + example_aas_missing_attributes, + example_aas, + example_aas_mandatory_attributes, + example_submodel_template, + create_example, +) from basyx.aas.examples.data._helper import AASDataChecker @@ -62,7 +72,10 @@ def test_submodel_serialization_deserialization(self) -> None: bytes_io = io.BytesIO() write_aas_xml_element(bytes_io, submodel) bytes_io.seek(0) - submodel2: model.Submodel = read_aas_xml_element(bytes_io, # type: ignore[assignment] - XMLConstructables.SUBMODEL, failsafe=False) + submodel2: model.Submodel = read_aas_xml_element( + bytes_io, # type: ignore[assignment] + XMLConstructables.SUBMODEL, + failsafe=False, + ) checker = AASDataChecker(raise_immediately=True) checker.check_submodel_equal(submodel2, submodel) diff --git a/sdk/test/backend/test_couchdb.py b/sdk/test/backend/test_couchdb.py index 16f607047..27f12ced6 100644 --- a/sdk/test/backend/test_couchdb.py +++ b/sdk/test/backend/test_couchdb.py @@ -14,20 +14,25 @@ from test._helper.test_helpers import TEST_CONFIG, COUCHDB_OKAY, COUCHDB_ERROR -source_core: str = "couchdb://" + TEST_CONFIG["couchdb"]["url"].lstrip("http://") + "/" + \ - TEST_CONFIG["couchdb"]["database"] + "/" +source_core: str = ( + "couchdb://" + TEST_CONFIG["couchdb"]["url"].lstrip("http://") + "/" + TEST_CONFIG["couchdb"]["database"] + "/" +) -@unittest.skipUnless(COUCHDB_OKAY, "No CouchDB is reachable at {}/{}: {}".format(TEST_CONFIG['couchdb']['url'], - TEST_CONFIG['couchdb']['database'], - COUCHDB_ERROR)) +@unittest.skipUnless( + COUCHDB_OKAY, + "No CouchDB is reachable at {}/{}: {}".format( + TEST_CONFIG["couchdb"]["url"], TEST_CONFIG["couchdb"]["database"], COUCHDB_ERROR + ), +) class CouchDBBackendTest(unittest.TestCase): def setUp(self) -> None: - self.couch_identifiable_store = couchdb.CouchDBIdentifiableStore(TEST_CONFIG['couchdb']['url'], - TEST_CONFIG['couchdb']['database']) - couchdb.register_credentials(TEST_CONFIG["couchdb"]["url"], - TEST_CONFIG["couchdb"]["user"], - TEST_CONFIG["couchdb"]["password"]) + self.couch_identifiable_store = couchdb.CouchDBIdentifiableStore( + TEST_CONFIG["couchdb"]["url"], TEST_CONFIG["couchdb"]["database"] + ) + couchdb.register_credentials( + TEST_CONFIG["couchdb"]["url"], TEST_CONFIG["couchdb"]["user"], TEST_CONFIG["couchdb"]["password"] + ) self.couch_identifiable_store.check_database() def tearDown(self) -> None: @@ -44,12 +49,12 @@ def test_retrieval(self): self.couch_identifiable_store.add(test_object) # When retrieving the object, we should get the *same* instance as we added - test_object_retrieved = self.couch_identifiable_store.get_item('https://example.org/Test_Submodel') + test_object_retrieved = self.couch_identifiable_store.get_item("https://example.org/Test_Submodel") self.assertIs(test_object, test_object_retrieved) # When retrieving it again, we should still get the same object del test_object - test_object_retrieved_again = self.couch_identifiable_store.get_item('https://example.org/Test_Submodel') + test_object_retrieved_again = self.couch_identifiable_store.get_item("https://example.org/Test_Submodel") self.assertIs(test_object_retrieved, test_object_retrieved_again) def test_example_submodel_storing(self) -> None: @@ -61,8 +66,8 @@ def test_example_submodel_storing(self) -> None: self.assertIn(example_submodel, self.couch_identifiable_store) # Restore example submodel and check data - submodel_restored = self.couch_identifiable_store.get_item('https://example.org/Test_Submodel') - assert (isinstance(submodel_restored, model.Submodel)) + submodel_restored = self.couch_identifiable_store.get_item("https://example.org/Test_Submodel") + assert isinstance(submodel_restored, model.Submodel) checker = AASDataChecker(raise_immediately=True) check_example_submodel(checker, submodel_restored) @@ -94,19 +99,23 @@ def test_key_errors(self) -> None: self.couch_identifiable_store.add(example_submodel) with self.assertRaises(KeyError) as cm: self.couch_identifiable_store.add(example_submodel) - self.assertEqual("'Identifiable with id https://example.org/Test_Submodel already exists in " - "CouchDB database'", str(cm.exception)) + self.assertEqual( + "'Identifiable with id https://example.org/Test_Submodel already exists in CouchDB database'", + str(cm.exception), + ) # Querying a deleted object should raise a KeyError - retrieved_submodel = self.couch_identifiable_store.get_item('https://example.org/Test_Submodel') + retrieved_submodel = self.couch_identifiable_store.get_item("https://example.org/Test_Submodel") self.couch_identifiable_store.discard(example_submodel) with self.assertRaises(KeyError) as cm: - self.couch_identifiable_store.get_item('https://example.org/Test_Submodel') - self.assertEqual("'No Identifiable with id https://example.org/Test_Submodel found in CouchDB database'", - str(cm.exception)) + self.couch_identifiable_store.get_item("https://example.org/Test_Submodel") + self.assertEqual( + "'No Identifiable with id https://example.org/Test_Submodel found in CouchDB database'", str(cm.exception) + ) # Double deleting should also raise a KeyError with self.assertRaises(KeyError) as cm: self.couch_identifiable_store.discard(retrieved_submodel) - self.assertEqual("'No AAS object with id https://example.org/Test_Submodel exists in " - "CouchDB database'", str(cm.exception)) + self.assertEqual( + "'No AAS object with id https://example.org/Test_Submodel exists in CouchDB database'", str(cm.exception) + ) diff --git a/sdk/test/backend/test_local_file.py b/sdk/test/backend/test_local_file.py index e71f4f27a..b2351fc5b 100644 --- a/sdk/test/backend/test_local_file.py +++ b/sdk/test/backend/test_local_file.py @@ -40,12 +40,12 @@ def test_retrieval(self): self.identifiable_store.add(test_object) # When retrieving the object, we should get the *same* instance as we added - test_object_retrieved = self.identifiable_store.get_item('https://example.org/Test_Submodel') + test_object_retrieved = self.identifiable_store.get_item("https://example.org/Test_Submodel") self.assertIs(test_object, test_object_retrieved) # When retrieving it again, we should still get the same object del test_object - test_object_retrieved_again = self.identifiable_store.get_item('https://example.org/Test_Submodel') + test_object_retrieved_again = self.identifiable_store.get_item("https://example.org/Test_Submodel") self.assertIs(test_object_retrieved, test_object_retrieved_again) def test_example_submodel_storing(self) -> None: @@ -57,8 +57,8 @@ def test_example_submodel_storing(self) -> None: self.assertIn(example_submodel, self.identifiable_store) # Restore example submodel and check data - submodel_restored = self.identifiable_store.get_item('https://example.org/Test_Submodel') - assert (isinstance(submodel_restored, model.Submodel)) + submodel_restored = self.identifiable_store.get_item("https://example.org/Test_Submodel") + assert isinstance(submodel_restored, model.Submodel) checker = AASDataChecker(raise_immediately=True) check_example_submodel(checker, submodel_restored) @@ -109,23 +109,27 @@ def test_key_errors(self) -> None: self.identifiable_store.add(example_submodel) with self.assertRaises(KeyError) as cm: self.identifiable_store.add(example_submodel) - self.assertEqual("'Identifiable with id https://example.org/Test_Submodel already exists in " - "local file database'", str(cm.exception)) + self.assertEqual( + "'Identifiable with id https://example.org/Test_Submodel already exists in local file database'", + str(cm.exception), + ) # Querying a deleted object should raise a KeyError - retrieved_submodel = self.identifiable_store.get_item('https://example.org/Test_Submodel') + retrieved_submodel = self.identifiable_store.get_item("https://example.org/Test_Submodel") self.identifiable_store.discard(example_submodel) with self.assertRaises(KeyError) as cm: - self.identifiable_store.get_item('https://example.org/Test_Submodel') - self.assertEqual("'No Identifiable with id https://example.org/Test_Submodel " - "found in local file database'", - str(cm.exception)) + self.identifiable_store.get_item("https://example.org/Test_Submodel") + self.assertEqual( + "'No Identifiable with id https://example.org/Test_Submodel found in local file database'", + str(cm.exception), + ) # Double deleting should also raise a KeyError with self.assertRaises(KeyError) as cm: self.identifiable_store.discard(retrieved_submodel) - self.assertEqual("'No AAS object with id https://example.org/Test_Submodel exists in " - "local file database'", str(cm.exception)) + self.assertEqual( + "'No AAS object with id https://example.org/Test_Submodel exists in local file database'", str(cm.exception) + ) def test_add_and_len_consistent(self) -> None: # Each add() must increment len() by exactly 1 @@ -156,29 +160,27 @@ def test_iter_ignores_non_json_files(self) -> None: def test_mutation_persistence(self) -> None: submodel = model.Submodel( - id_='https://example.org/MutationTest', - submodel_element={ - model.Property(id_short='Prop', value_type=model.datatypes.String, value='before') - } + id_="https://example.org/MutationTest", + submodel_element={model.Property(id_short="Prop", value_type=model.datatypes.String, value="before")}, ) self.identifiable_store.add(submodel) - retrieved = self.identifiable_store.get_item('https://example.org/MutationTest') + retrieved = self.identifiable_store.get_item("https://example.org/MutationTest") assert isinstance(retrieved, model.Submodel) - prop = retrieved.get_referable(['Prop']) + prop = retrieved.get_referable(["Prop"]) assert isinstance(prop, model.Property) - prop.update_from(model.Property(id_short='Prop', value_type=model.datatypes.String, value='after')) + prop.update_from(model.Property(id_short="Prop", value_type=model.datatypes.String, value="after")) self.identifiable_store.commit(retrieved) # Drop all strong references to evict the WeakValueDictionary cache del submodel, retrieved, prop gc.collect() - fresh = self.identifiable_store.get_item('https://example.org/MutationTest') + fresh = self.identifiable_store.get_item("https://example.org/MutationTest") assert isinstance(fresh, model.Submodel) - fresh_prop = fresh.get_referable(['Prop']) + fresh_prop = fresh.get_referable(["Prop"]) assert isinstance(fresh_prop, model.Property) - self.assertEqual('after', fresh_prop.value) + self.assertEqual("after", fresh_prop.value) def test_reload_discard(self) -> None: # Load example submodel diff --git a/sdk/test/examples/test__init__.py b/sdk/test/examples/test__init__.py index 1f1e26234..d5535e644 100644 --- a/sdk/test/examples/test__init__.py +++ b/sdk/test/examples/test__init__.py @@ -11,7 +11,6 @@ class TestExampleFunctions(unittest.TestCase): - def test_create_example(self): identifiable_store = create_example() @@ -20,9 +19,9 @@ def test_create_example(self): # Check that the object store contains expected elements expected_ids = [ - 'https://example.org/Test_AssetAdministrationShell', - 'https://example.org/Test_Submodel_Template', - 'https://example.org/Test_ConceptDescription_Mandatory' + "https://example.org/Test_AssetAdministrationShell", + "https://example.org/Test_Submodel_Template", + "https://example.org/Test_ConceptDescription_Mandatory", ] for id in expected_ids: self.assertIsNotNone(identifiable_store.get_item(id)) @@ -34,9 +33,9 @@ def test_create_example_aas_binding(self): self.assertGreater(len(identifiable_store), 0) # Check that the object store contains expected elements - aas_id = 'https://example.org/Test_AssetAdministrationShell' - sm_id = 'https://example.org/Test_Submodel_Template' - cd_id = 'https://example.org/Test_ConceptDescription_Mandatory' + aas_id = "https://example.org/Test_AssetAdministrationShell" + sm_id = "https://example.org/Test_Submodel_Template" + cd_id = "https://example.org/Test_ConceptDescription_Mandatory" aas = identifiable_store.get_item(aas_id) sm = identifiable_store.get_item(sm_id) diff --git a/sdk/test/examples/test_examples.py b/sdk/test/examples/test_examples.py index 7f17a5db6..fef630b0a 100644 --- a/sdk/test/examples/test_examples.py +++ b/sdk/test/examples/test_examples.py @@ -6,8 +6,12 @@ # SPDX-License-Identifier: MIT import unittest -from basyx.aas.examples.data import example_aas, example_aas_mandatory_attributes, example_aas_missing_attributes, \ - example_submodel_template +from basyx.aas.examples.data import ( + example_aas, + example_aas_mandatory_attributes, + example_aas_missing_attributes, + example_submodel_template, +) from basyx.aas.examples.data._helper import AASDataChecker from basyx.aas import model @@ -43,15 +47,13 @@ def test_full_example(self): identifiable_store = model.DictIdentifiableStore() with self.assertRaises(AssertionError) as cm: example_aas.check_full_example(checker, identifiable_store) - self.assertIn("AssetAdministrationShell[https://example.org/Test_AssetAdministrationShell]", - str(cm.exception)) + self.assertIn("AssetAdministrationShell[https://example.org/Test_AssetAdministrationShell]", str(cm.exception)) identifiable_store = example_aas.create_full_example() example_aas.check_full_example(checker, identifiable_store) failed_shell = model.AssetAdministrationShell( - asset_information=model.AssetInformation(global_asset_id='test'), - id_='test' + asset_information=model.AssetInformation(global_asset_id="test"), id_="test" ) identifiable_store.add(failed_shell) with self.assertRaises(AssertionError) as cm: @@ -59,14 +61,14 @@ def test_full_example(self): self.assertIn("AssetAdministrationShell[test]", str(cm.exception)) identifiable_store.discard(failed_shell) - failed_submodel = model.Submodel(id_='test') + failed_submodel = model.Submodel(id_="test") identifiable_store.add(failed_submodel) with self.assertRaises(AssertionError) as cm: example_aas.check_full_example(checker, identifiable_store) self.assertIn("Submodel[test]", str(cm.exception)) identifiable_store.discard(failed_submodel) - failed_cd = model.ConceptDescription(id_='test') + failed_cd = model.ConceptDescription(id_="test") identifiable_store.add(failed_cd) with self.assertRaises(AssertionError) as cm: example_aas.check_full_example(checker, identifiable_store) @@ -77,7 +79,8 @@ class DummyIdentifiable(model.Identifiable): def __init__(self, id_: model.Identifier): super().__init__() self.id = id_ - failed_identifiable = DummyIdentifiable(id_='test') + + failed_identifiable = DummyIdentifiable(id_="test") identifiable_store.add(failed_identifiable) with self.assertRaises(KeyError) as cm: example_aas.check_full_example(checker, identifiable_store) @@ -112,7 +115,7 @@ def test_full_example(self): identifiable_store = example_aas_mandatory_attributes.create_full_example() example_aas_mandatory_attributes.check_full_example(checker, identifiable_store) - failed_submodel = model.Submodel(id_='test') + failed_submodel = model.Submodel(id_="test") identifiable_store.add(failed_submodel) with self.assertRaises(AssertionError) as cm: example_aas_mandatory_attributes.check_full_example(checker, identifiable_store) @@ -143,7 +146,7 @@ def test_full_example(self): identifiable_store = example_aas_missing_attributes.create_full_example() example_aas_missing_attributes.check_full_example(checker, identifiable_store) - failed_submodel = model.Submodel(id_='test') + failed_submodel = model.Submodel(id_="test") identifiable_store.add(failed_submodel) with self.assertRaises(AssertionError) as cm: example_aas_missing_attributes.check_full_example(checker, identifiable_store) @@ -165,7 +168,7 @@ def test_full_example(self): identifiable_store.add(example_submodel_template.create_example_submodel_template()) example_submodel_template.check_full_example(checker, identifiable_store) - failed_submodel = model.Submodel(id_='test') + failed_submodel = model.Submodel(id_="test") identifiable_store.add(failed_submodel) with self.assertRaises(AssertionError) as cm: example_submodel_template.check_full_example(checker, identifiable_store) diff --git a/sdk/test/examples/test_helpers.py b/sdk/test/examples/test_helpers.py index 74aebb518..3b1b75940 100644 --- a/sdk/test/examples/test_helpers.py +++ b/sdk/test/examples/test_helpers.py @@ -14,24 +14,24 @@ class DataCheckerTest(unittest.TestCase): def test_check(self): checker = DataChecker(raise_immediately=True) with self.assertRaises(AssertionError) as cm: - checker.check(2 == 3, 'Assertion test') + checker.check(2 == 3, "Assertion test") self.assertEqual("('Check failed: Assertion test', {})", str(cm.exception)) def test_kwargs(self): checker = DataChecker(raise_immediately=True) with self.assertRaises(AssertionError) as cm: - checker.check(2 == 3, 'Assertion test 1', value='kwargs1') + checker.check(2 == 3, "Assertion test 1", value="kwargs1") with self.assertRaises(AssertionError) as cm_2: - checker.check(2 == 3, 'Assertion test 2', value='kwargs2') + checker.check(2 == 3, "Assertion test 2", value="kwargs2") self.assertEqual("('Check failed: Assertion test 1', {'value': 'kwargs1'})", str(cm.exception)) self.assertEqual("('Check failed: Assertion test 2', {'value': 'kwargs2'})", str(cm_2.exception)) def test_raise_failed(self): checker = DataChecker(raise_immediately=False) - checker.check(2 == 2, 'Assertion test') + checker.check(2 == 2, "Assertion test") checker.raise_failed() # no assertion should be occur self.assertEqual(1, sum(1 for _ in checker.successful_checks)) - checker.check(2 == 3, 'Assertion test') + checker.check(2 == 3, "Assertion test") with self.assertRaises(AssertionError) as cm: checker.raise_failed() self.assertEqual("('1 of 2 checks failed', ['Assertion test'])", str(cm.exception)) @@ -39,23 +39,12 @@ def test_raise_failed(self): class AASDataCheckerTest(unittest.TestCase): def test_qualifiable_checker(self): - qualifier_expected = model.Qualifier( - type_='test', - value_type=model.datatypes.String, - value='test value' - ) + qualifier_expected = model.Qualifier(type_="test", value_type=model.datatypes.String, value="test value") property_expected = model.Property( - id_short='Prop1', - value_type=model.datatypes.String, - value='test', - qualifier={qualifier_expected} + id_short="Prop1", value_type=model.datatypes.String, value="test", qualifier={qualifier_expected} ) - property = model.Property( - id_short='Prop1', - value_type=model.datatypes.String, - value='test' - ) + property = model.Property(id_short="Prop1", value_type=model.datatypes.String, value="test") checker = AASDataChecker(raise_immediately=False) @@ -63,8 +52,10 @@ def test_qualifiable_checker(self): self.assertEqual(2, sum(1 for _ in checker.failed_checks)) self.assertEqual(14, sum(1 for _ in checker.successful_checks)) checker_iterator = checker.failed_checks - self.assertEqual("FAIL: Attribute qualifier of Property[Prop1] must contain 1 Qualifiers (count=0)", - repr(next(checker_iterator))) + self.assertEqual( + "FAIL: Attribute qualifier of Property[Prop1] must contain 1 Qualifiers (count=0)", + repr(next(checker_iterator)), + ) self.assertEqual("FAIL: Qualifier(type=test) must exist ()", repr(next(checker_iterator))) def test_submodel_element_list_checker(self): @@ -73,31 +64,33 @@ def test_submodel_element_list_checker(self): range1 = model.Range(None, model.datatypes.Int, 42, 142857) range2 = model.Range(None, model.datatypes.Int, 42, 1337) list_ = model.SubmodelElementList( - id_short='test_list', + id_short="test_list", type_value_list_element=model.Range, value_type_list_element=model.datatypes.Int, order_relevant=True, - value=(range1, range2) + value=(range1, range2), ) range1_expected = model.Range(None, model.datatypes.Int, 42, 142857) range2_expected = model.Range(None, model.datatypes.Int, 42, 1337) list_expected = model.SubmodelElementList( - id_short='test_list', + id_short="test_list", type_value_list_element=model.Range, value_type_list_element=model.datatypes.Int, order_relevant=True, - value=(range2_expected, range1_expected) + value=(range2_expected, range1_expected), ) checker = AASDataChecker(raise_immediately=False) checker.check_submodel_element_list_equal(list_, list_expected) self.assertEqual(2, sum(1 for _ in checker.failed_checks)) checker_iterator = checker.failed_checks - self.assertEqual("FAIL: Attribute max of Range[test_list[0]] must be == 1337 (value=142857)", - repr(next(checker_iterator))) - self.assertEqual("FAIL: Attribute max of Range[test_list[1]] must be == 142857 (value=1337)", - repr(next(checker_iterator))) + self.assertEqual( + "FAIL: Attribute max of Range[test_list[0]] must be == 1337 (value=142857)", repr(next(checker_iterator)) + ) + self.assertEqual( + "FAIL: Attribute max of Range[test_list[1]] must be == 142857 (value=1337)", repr(next(checker_iterator)) + ) # order_relevant # Don't set protected attributes like this in production code! @@ -108,17 +101,19 @@ def test_submodel_element_list_checker(self): self.assertEqual("A SubmodelElementList with order_relevant=False cannot be compared!", str(cm.exception)) self.assertEqual(1, sum(1 for _ in checker.failed_checks)) checker_iterator = checker.failed_checks - self.assertEqual("FAIL: Attribute order_relevant of SubmodelElementList[test_list] must be == True " - "(value=False)", repr(next(checker_iterator))) + self.assertEqual( + "FAIL: Attribute order_relevant of SubmodelElementList[test_list] must be == True (value=False)", + repr(next(checker_iterator)), + ) # value_type_list_element list_ = model.SubmodelElementList( - id_short='test_list', + id_short="test_list", type_value_list_element=model.Range, value_type_list_element=model.datatypes.Int, ) list_expected = model.SubmodelElementList( - id_short='test_list', + id_short="test_list", type_value_list_element=model.Range, value_type_list_element=model.datatypes.String, ) @@ -126,8 +121,10 @@ def test_submodel_element_list_checker(self): checker.check_submodel_element_list_equal(list_, list_expected) self.assertEqual(1, sum(1 for _ in checker.failed_checks)) checker_iterator = checker.failed_checks - self.assertEqual("FAIL: Attribute value_type_list_element of SubmodelElementList[test_list] must be == str " - "(value='Int')", repr(next(checker_iterator))) + self.assertEqual( + "FAIL: Attribute value_type_list_element of SubmodelElementList[test_list] must be == str (value='Int')", + repr(next(checker_iterator)), + ) # Don't set protected attributes like this in production code! list_._value_type_list_element = model.datatypes.String @@ -137,12 +134,12 @@ def test_submodel_element_list_checker(self): # type_value_list_element list_ = model.SubmodelElementList( - id_short='test_list', + id_short="test_list", type_value_list_element=model.Range, value_type_list_element=model.datatypes.Int, ) list_expected = model.SubmodelElementList( - id_short='test_list', + id_short="test_list", type_value_list_element=model.Property, value_type_list_element=model.datatypes.Int, ) @@ -150,9 +147,11 @@ def test_submodel_element_list_checker(self): checker.check_submodel_element_list_equal(list_, list_expected) self.assertEqual(1, sum(1 for _ in checker.failed_checks)) checker_iterator = checker.failed_checks - self.assertEqual("FAIL: Attribute type_value_list_element of SubmodelElementList[test_list] must be == " - "Property (value='Range')", - repr(next(checker_iterator))) + self.assertEqual( + "FAIL: Attribute type_value_list_element of SubmodelElementList[test_list] must be == " + "Property (value='Range')", + repr(next(checker_iterator)), + ) # Don't set protected attributes like this in production code! list_._type_value_list_element = model.Property @@ -162,73 +161,58 @@ def test_submodel_element_list_checker(self): # semantic_id_list_element list_ = model.SubmodelElementList( - id_short='test_list', + id_short="test_list", type_value_list_element=model.MultiLanguageProperty, semantic_id_list_element=model.ExternalReference( - (model.Key(model.KeyTypes.GLOBAL_REFERENCE, "urn:x-test:invalid"),)) + (model.Key(model.KeyTypes.GLOBAL_REFERENCE, "urn:x-test:invalid"),) + ), ) list_expected = model.SubmodelElementList( - id_short='test_list', + id_short="test_list", type_value_list_element=model.MultiLanguageProperty, semantic_id_list_element=model.ExternalReference( - (model.Key(model.KeyTypes.GLOBAL_REFERENCE, "urn:x-test:test"),)) + (model.Key(model.KeyTypes.GLOBAL_REFERENCE, "urn:x-test:test"),) + ), ) checker = AASDataChecker(raise_immediately=False) checker.check_submodel_element_list_equal(list_, list_expected) self.assertEqual(1, sum(1 for _ in checker.failed_checks)) checker_iterator = checker.failed_checks - self.assertEqual("FAIL: Attribute semantic_id_list_element of SubmodelElementList[test_list] must be == " - "ExternalReference(key=(Key(type=GLOBAL_REFERENCE, value=urn:x-test:test),)) " - "(value=ExternalReference(key=(Key(type=GLOBAL_REFERENCE, value=urn:x-test:invalid),)))", - repr(next(checker_iterator))) + self.assertEqual( + "FAIL: Attribute semantic_id_list_element of SubmodelElementList[test_list] must be == " + "ExternalReference(key=(Key(type=GLOBAL_REFERENCE, value=urn:x-test:test),)) " + "(value=ExternalReference(key=(Key(type=GLOBAL_REFERENCE, value=urn:x-test:invalid),)))", + repr(next(checker_iterator)), + ) # Don't set protected attributes like this in production code! list_._semantic_id_list_element = model.ExternalReference( - (model.Key(model.KeyTypes.GLOBAL_REFERENCE, "urn:x-test:test"),)) + (model.Key(model.KeyTypes.GLOBAL_REFERENCE, "urn:x-test:test"),) + ) checker = AASDataChecker(raise_immediately=False) checker.check_submodel_element_list_equal(list_, list_expected) self.assertEqual(0, sum(1 for _ in checker.failed_checks)) def test_submodel_element_collection_checker(self): - property = model.Property( - id_short='Prop1', - value_type=model.datatypes.String, - value='test' - ) - range_ = model.Range( - id_short='Range1', - value_type=model.datatypes.Int, - min=100, - max=200 - ) - collection = model.SubmodelElementCollection( - id_short='Collection', - value=(range_,) - ) - property_expected = model.Property( - id_short='Prop1', - value_type=model.datatypes.String, - value='test' - ) - range_expected = model.Range( - id_short='Range1', - value_type=model.datatypes.Int, - min=100, - max=200 - ) + property = model.Property(id_short="Prop1", value_type=model.datatypes.String, value="test") + range_ = model.Range(id_short="Range1", value_type=model.datatypes.Int, min=100, max=200) + collection = model.SubmodelElementCollection(id_short="Collection", value=(range_,)) + property_expected = model.Property(id_short="Prop1", value_type=model.datatypes.String, value="test") + range_expected = model.Range(id_short="Range1", value_type=model.datatypes.Int, min=100, max=200) collection_expected = model.SubmodelElementCollection( - id_short='Collection', - value=(property_expected, range_expected) + id_short="Collection", value=(property_expected, range_expected) ) checker = AASDataChecker(raise_immediately=False) checker.check_submodel_element_collection_equal(collection, collection_expected) self.assertEqual(2, sum(1 for _ in checker.failed_checks)) checker_iterator = checker.failed_checks - self.assertEqual("FAIL: Attribute value of SubmodelElementCollection[Collection] must contain 2 " - "SubmodelElements (count=1)", - repr(next(checker_iterator))) - self.assertEqual("FAIL: Submodel Element Property[Collection.Prop1] must exist ()", - repr(next(checker_iterator))) + self.assertEqual( + "FAIL: Attribute value of SubmodelElementCollection[Collection] must contain 2 SubmodelElements (count=1)", + repr(next(checker_iterator)), + ) + self.assertEqual( + "FAIL: Submodel Element Property[Collection.Prop1] must exist ()", repr(next(checker_iterator)) + ) collection.add_referable(property) checker = AASDataChecker(raise_immediately=False) @@ -238,121 +222,123 @@ def test_not_implemented(self): class DummySubmodelElement(model.SubmodelElement): def __init__(self, id_short: model.NameType): super().__init__(id_short) - dummy_submodel_element = DummySubmodelElement('test') - submodel_collection = model.SubmodelElementCollection('test') + + dummy_submodel_element = DummySubmodelElement("test") + submodel_collection = model.SubmodelElementCollection("test") submodel_collection.value.add(dummy_submodel_element) checker = AASDataChecker(raise_immediately=True) with self.assertRaises(AttributeError) as cm: checker.check_submodel_element_collection_equal(submodel_collection, submodel_collection) - self.assertEqual( - 'Submodel Element class not implemented', - str(cm.exception) - ) + self.assertEqual("Submodel Element class not implemented", str(cm.exception)) def test_annotated_relationship_element(self): - rel1 = model.AnnotatedRelationshipElement(id_short='test', - first=model.ModelReference(( - model.Key(type_=model.KeyTypes.SUBMODEL, - value='http://example.org/Test_Submodel'), - model.Key( - type_=model.KeyTypes.PROPERTY, - value='ExampleProperty'),), - model.Property), - second=model.ModelReference(( - model.Key(type_=model.KeyTypes.SUBMODEL, - value='http://example.org/Test_Submodel'), - model.Key(type_=model.KeyTypes.PROPERTY, - value='ExampleProperty'),), - model.Property), - ) - rel2 = model.AnnotatedRelationshipElement(id_short='test', - first=model.ModelReference(( - model.Key(type_=model.KeyTypes.SUBMODEL, - value='http://example.org/Test_Submodel'), - model.Key(type_=model.KeyTypes.PROPERTY, - value='ExampleProperty'),), - model.Property), - second=model.ModelReference(( - model.Key(type_=model.KeyTypes.SUBMODEL, - value='http://example.org/Test_Submodel'), - model.Key(type_=model.KeyTypes.PROPERTY, - value='ExampleProperty'),), - model.Property), - annotation={ - model.Property(id_short="ExampleAnnotatedProperty", - value_type=model.datatypes.String, - value='exampleValue', - parent=None) - }) + rel1 = model.AnnotatedRelationshipElement( + id_short="test", + first=model.ModelReference( + ( + model.Key(type_=model.KeyTypes.SUBMODEL, value="http://example.org/Test_Submodel"), + model.Key(type_=model.KeyTypes.PROPERTY, value="ExampleProperty"), + ), + model.Property, + ), + second=model.ModelReference( + ( + model.Key(type_=model.KeyTypes.SUBMODEL, value="http://example.org/Test_Submodel"), + model.Key(type_=model.KeyTypes.PROPERTY, value="ExampleProperty"), + ), + model.Property, + ), + ) + rel2 = model.AnnotatedRelationshipElement( + id_short="test", + first=model.ModelReference( + ( + model.Key(type_=model.KeyTypes.SUBMODEL, value="http://example.org/Test_Submodel"), + model.Key(type_=model.KeyTypes.PROPERTY, value="ExampleProperty"), + ), + model.Property, + ), + second=model.ModelReference( + ( + model.Key(type_=model.KeyTypes.SUBMODEL, value="http://example.org/Test_Submodel"), + model.Key(type_=model.KeyTypes.PROPERTY, value="ExampleProperty"), + ), + model.Property, + ), + annotation={ + model.Property( + id_short="ExampleAnnotatedProperty", + value_type=model.datatypes.String, + value="exampleValue", + parent=None, + ) + }, + ) checker = AASDataChecker(raise_immediately=False) checker.check_annotated_relationship_element_equal(rel1, rel2) self.assertEqual(2, sum(1 for _ in checker.failed_checks)) checker_iterator = checker.failed_checks - self.assertEqual("FAIL: Attribute annotation of AnnotatedRelationshipElement[test] must contain 1 DataElements " - "(count=0)", - repr(next(checker_iterator))) - self.assertEqual("FAIL: Annotation Property[test.ExampleAnnotatedProperty] must exist ()", - repr(next(checker_iterator))) + self.assertEqual( + "FAIL: Attribute annotation of AnnotatedRelationshipElement[test] must contain 1 DataElements (count=0)", + repr(next(checker_iterator)), + ) + self.assertEqual( + "FAIL: Annotation Property[test.ExampleAnnotatedProperty] must exist ()", repr(next(checker_iterator)) + ) def test_submodel_checker(self): - submodel = model.Submodel(id_='test') - property_expected = model.Property( - id_short='Prop1', - value_type=model.datatypes.String, - value='test' - ) - submodel_expected = model.Submodel(id_='test', - submodel_element=(property_expected,) - ) + submodel = model.Submodel(id_="test") + property_expected = model.Property(id_short="Prop1", value_type=model.datatypes.String, value="test") + submodel_expected = model.Submodel(id_="test", submodel_element=(property_expected,)) checker = AASDataChecker(raise_immediately=False) checker.check_submodel_equal(submodel, submodel_expected) self.assertEqual(2, sum(1 for _ in checker.failed_checks)) checker_iterator = checker.failed_checks - self.assertEqual("FAIL: Attribute submodel_element of Submodel[test] must contain 1 " - "SubmodelElements (count=0)", - repr(next(checker_iterator))) - self.assertEqual("FAIL: Submodel Element Property[test / Prop1] must exist ()", - repr(next(checker_iterator))) + self.assertEqual( + "FAIL: Attribute submodel_element of Submodel[test] must contain 1 SubmodelElements (count=0)", + repr(next(checker_iterator)), + ) + self.assertEqual("FAIL: Submodel Element Property[test / Prop1] must exist ()", repr(next(checker_iterator))) def test_asset_administration_shell_checker(self): - shell = model.AssetAdministrationShell(asset_information=model.AssetInformation( - global_asset_id='test'), - id_='test') + shell = model.AssetAdministrationShell( + asset_information=model.AssetInformation(global_asset_id="test"), id_="test" + ) shell_expected = model.AssetAdministrationShell( - asset_information=model.AssetInformation( - global_asset_id='test'), - id_='test', - submodel={model.ModelReference((model.Key(type_=model.KeyTypes.SUBMODEL, - value='test'),), - model.Submodel)} - ) + asset_information=model.AssetInformation(global_asset_id="test"), + id_="test", + submodel={model.ModelReference((model.Key(type_=model.KeyTypes.SUBMODEL, value="test"),), model.Submodel)}, + ) checker = AASDataChecker(raise_immediately=False) checker.check_asset_administration_shell_equal(shell, shell_expected) self.assertEqual(2, sum(1 for _ in checker.failed_checks)) checker_iterator = checker.failed_checks - self.assertEqual("FAIL: Attribute submodel of AssetAdministrationShell[test] must contain 1 " - "ModelReferences (count=0)", - repr(next(checker_iterator))) - self.assertEqual("FAIL: Submodel Reference ModelReference(key=(Key(type=SUBMODEL," - " value=test),)) must exist ()", - repr(next(checker_iterator))) + self.assertEqual( + "FAIL: Attribute submodel of AssetAdministrationShell[test] must contain 1 ModelReferences (count=0)", + repr(next(checker_iterator)), + ) + self.assertEqual( + "FAIL: Submodel Reference ModelReference(key=(Key(type=SUBMODEL, value=test),)) must exist ()", + repr(next(checker_iterator)), + ) def test_concept_description_checker(self): - cd = model.ConceptDescription(id_='test') - cd_expected = model.ConceptDescription(id_='test', - is_case_of={ - model.ExternalReference((model.Key( - type_=model.KeyTypes.GLOBAL_REFERENCE, - value='test'),))} - ) + cd = model.ConceptDescription(id_="test") + cd_expected = model.ConceptDescription( + id_="test", + is_case_of={model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, value="test"),))}, + ) checker = AASDataChecker(raise_immediately=False) checker.check_concept_description_equal(cd, cd_expected) self.assertEqual(2, sum(1 for _ in checker.failed_checks)) checker_iterator = checker.failed_checks - self.assertEqual("FAIL: Attribute is_case_of of ConceptDescription[test] must contain " - "1 References (count=0)", - repr(next(checker_iterator))) - self.assertEqual("FAIL: Concept Description Reference ExternalReference(key=(Key(" - "type=GLOBAL_REFERENCE, value=test),)) must exist ()", - repr(next(checker_iterator))) + self.assertEqual( + "FAIL: Attribute is_case_of of ConceptDescription[test] must contain 1 References (count=0)", + repr(next(checker_iterator)), + ) + self.assertEqual( + "FAIL: Concept Description Reference ExternalReference(key=(Key(" + "type=GLOBAL_REFERENCE, value=test),)) must exist ()", + repr(next(checker_iterator)), + ) diff --git a/sdk/test/examples/test_tutorials.py b/sdk/test/examples/test_tutorials.py index 04f201e53..54ad56cd4 100644 --- a/sdk/test/examples/test_tutorials.py +++ b/sdk/test/examples/test_tutorials.py @@ -9,6 +9,7 @@ Functions to test if a tutorial is executable """ + import os import tempfile import unittest @@ -21,7 +22,8 @@ class TutorialTest(unittest.TestCase): def test_tutorial_create_simple_aas(self): from basyx.aas.examples import tutorial_create_simple_aas - self.assertEqual(tutorial_create_simple_aas.submodel.get_referable('ExampleProperty').value, 'exampleValue') + + self.assertEqual(tutorial_create_simple_aas.submodel.get_referable("ExampleProperty").value, "exampleValue") store = model.DictIdentifiableStore({tutorial_create_simple_aas.submodel}) next(iter(tutorial_create_simple_aas.aas.submodel)).resolve(store) @@ -29,21 +31,26 @@ def test_tutorial_storage(self): from basyx.aas.examples import tutorial_storage # The tutorial already includes assert statements for the relevant points. So no further checks are required. - @unittest.skipUnless(COUCHDB_OKAY, "No CouchDB is reachable at {}/{}: {}".format(TEST_CONFIG['couchdb']['url'], - TEST_CONFIG['couchdb']['database'], - COUCHDB_ERROR)) + @unittest.skipUnless( + COUCHDB_OKAY, + "No CouchDB is reachable at {}/{}: {}".format( + TEST_CONFIG["couchdb"]["url"], TEST_CONFIG["couchdb"]["database"], COUCHDB_ERROR + ), + ) def test_tutorial_backend_couchdb(self): from basyx.aas.examples import tutorial_backend_couchdb def test_tutorial_serialization_deserialization_json(self): with temporary_workingdirectory(): from basyx.aas.examples import tutorial_serialization_deserialization + pass # The tutorial already includes assert statements for the relevant points. So no further checks are required. def test_tutorial_aasx(self): with temporary_workingdirectory(): from basyx.aas.examples import tutorial_aasx + pass # The tutorial already includes assert statements for the relevant points. So no further checks are required. diff --git a/sdk/test/model/test_aas.py b/sdk/test/model/test_aas.py index ed0e5bf6e..315b6cc16 100644 --- a/sdk/test/model/test_aas.py +++ b/sdk/test/model/test_aas.py @@ -14,35 +14,49 @@ class AssetInformationTest(unittest.TestCase): def test_aasd_131_init(self) -> None: with self.assertRaises(model.AASConstraintViolation) as cm: model.AssetInformation(model.AssetKind.INSTANCE) - self.assertEqual("An AssetInformation has to have a globalAssetId or a specificAssetId (Constraint AASd-131)", - str(cm.exception)) + self.assertEqual( + "An AssetInformation has to have a globalAssetId or a specificAssetId (Constraint AASd-131)", + str(cm.exception), + ) model.AssetInformation(model.AssetKind.INSTANCE, global_asset_id="https://example.org/TestAsset") model.AssetInformation(model.AssetKind.INSTANCE, specific_asset_id=(model.SpecificAssetId("test", "test"),)) - model.AssetInformation(model.AssetKind.INSTANCE, global_asset_id="https://example.org/TestAsset", - specific_asset_id=(model.SpecificAssetId("test", "test"),)) + model.AssetInformation( + model.AssetKind.INSTANCE, + global_asset_id="https://example.org/TestAsset", + specific_asset_id=(model.SpecificAssetId("test", "test"),), + ) def test_aasd_131_set(self) -> None: - asset_information = model.AssetInformation(model.AssetKind.INSTANCE, - global_asset_id="https://example.org/TestAsset", - specific_asset_id=(model.SpecificAssetId("test", "test"),)) + asset_information = model.AssetInformation( + model.AssetKind.INSTANCE, + global_asset_id="https://example.org/TestAsset", + specific_asset_id=(model.SpecificAssetId("test", "test"),), + ) asset_information.global_asset_id = None with self.assertRaises(model.AASConstraintViolation) as cm: asset_information.specific_asset_id = model.ConstrainedList(()) - self.assertEqual("An AssetInformation has to have a globalAssetId or a specificAssetId (Constraint AASd-131)", - str(cm.exception)) + self.assertEqual( + "An AssetInformation has to have a globalAssetId or a specificAssetId (Constraint AASd-131)", + str(cm.exception), + ) - asset_information = model.AssetInformation(model.AssetKind.INSTANCE, - global_asset_id="https://example.org/TestAsset", - specific_asset_id=(model.SpecificAssetId("test", "test"),)) + asset_information = model.AssetInformation( + model.AssetKind.INSTANCE, + global_asset_id="https://example.org/TestAsset", + specific_asset_id=(model.SpecificAssetId("test", "test"),), + ) asset_information.specific_asset_id = model.ConstrainedList(()) with self.assertRaises(model.AASConstraintViolation) as cm: asset_information.global_asset_id = None - self.assertEqual("An AssetInformation has to have a globalAssetId or a specificAssetId (Constraint AASd-131)", - str(cm.exception)) + self.assertEqual( + "An AssetInformation has to have a globalAssetId or a specificAssetId (Constraint AASd-131)", + str(cm.exception), + ) def test_aasd_131_specific_asset_id_add(self) -> None: - asset_information = model.AssetInformation(model.AssetKind.INSTANCE, - global_asset_id="https://example.org/TestAsset") + asset_information = model.AssetInformation( + model.AssetKind.INSTANCE, global_asset_id="https://example.org/TestAsset" + ) specific_asset_id1 = model.SpecificAssetId("test", "test") specific_asset_id2 = model.SpecificAssetId("test", "test") asset_information.specific_asset_id.append(specific_asset_id1) @@ -51,12 +65,15 @@ def test_aasd_131_specific_asset_id_add(self) -> None: self.assertIs(asset_information.specific_asset_id[1], specific_asset_id2) def test_aasd_131_specific_asset_id_set(self) -> None: - asset_information = model.AssetInformation(model.AssetKind.INSTANCE, - specific_asset_id=(model.SpecificAssetId("test", "test"),)) + asset_information = model.AssetInformation( + model.AssetKind.INSTANCE, specific_asset_id=(model.SpecificAssetId("test", "test"),) + ) with self.assertRaises(model.AASConstraintViolation) as cm: asset_information.specific_asset_id[:] = () - self.assertEqual("An AssetInformation has to have a globalAssetId or a specificAssetId (Constraint AASd-131)", - str(cm.exception)) + self.assertEqual( + "An AssetInformation has to have a globalAssetId or a specificAssetId (Constraint AASd-131)", + str(cm.exception), + ) specific_asset_id = model.SpecificAssetId("test", "test") self.assertIsNot(asset_information.specific_asset_id[0], specific_asset_id) asset_information.specific_asset_id[:] = (specific_asset_id,) @@ -66,21 +83,27 @@ def test_aasd_131_specific_asset_id_set(self) -> None: def test_aasd_131_specific_asset_id_del(self) -> None: specific_asset_id = model.SpecificAssetId("test", "test") - asset_information = model.AssetInformation(model.AssetKind.INSTANCE, - specific_asset_id=(model.SpecificAssetId("test1", "test1"), - specific_asset_id)) + asset_information = model.AssetInformation( + model.AssetKind.INSTANCE, specific_asset_id=(model.SpecificAssetId("test1", "test1"), specific_asset_id) + ) with self.assertRaises(model.AASConstraintViolation) as cm: del asset_information.specific_asset_id[:] - self.assertEqual("An AssetInformation has to have a globalAssetId or a specificAssetId (Constraint AASd-131)", - str(cm.exception)) + self.assertEqual( + "An AssetInformation has to have a globalAssetId or a specificAssetId (Constraint AASd-131)", + str(cm.exception), + ) with self.assertRaises(model.AASConstraintViolation) as cm: asset_information.specific_asset_id.clear() - self.assertEqual("An AssetInformation has to have a globalAssetId or a specificAssetId (Constraint AASd-131)", - str(cm.exception)) + self.assertEqual( + "An AssetInformation has to have a globalAssetId or a specificAssetId (Constraint AASd-131)", + str(cm.exception), + ) self.assertIsNot(asset_information.specific_asset_id[0], specific_asset_id) del asset_information.specific_asset_id[0] self.assertIs(asset_information.specific_asset_id[0], specific_asset_id) with self.assertRaises(model.AASConstraintViolation) as cm: del asset_information.specific_asset_id[0] - self.assertEqual("An AssetInformation has to have a globalAssetId or a specificAssetId (Constraint AASd-131)", - str(cm.exception)) + self.assertEqual( + "An AssetInformation has to have a globalAssetId or a specificAssetId (Constraint AASd-131)", + str(cm.exception), + ) diff --git a/sdk/test/model/test_base.py b/sdk/test/model/test_base.py index 826d6ed08..517e81654 100644 --- a/sdk/test/model/test_base.py +++ b/sdk/test/model/test_base.py @@ -28,7 +28,7 @@ def test_string_representation(self): def test_equality(self): key1 = model.Key(model.KeyTypes.SUBMODEL, "urn:x-test:submodel1") - ident = 'test' + ident = "test" self.assertEqual(key1.__eq__(ident), NotImplemented) def test_from_referable(self): @@ -70,8 +70,9 @@ def generate_example_referable_tree() -> model.Referable: :return: example_referable """ - def generate_example_referable_with_namespace(id_short: model.NameType, - child: Optional[model.Referable] = None) -> model.Referable: + def generate_example_referable_with_namespace( + id_short: model.NameType, child: Optional[model.Referable] = None + ) -> model.Referable: """ Generates an example referable with a namespace. @@ -82,8 +83,7 @@ def generate_example_referable_with_namespace(id_short: model.NameType, referable = ExampleReferableWithNamespace() referable.id_short = id_short if child: - namespace_set = model.NamespaceSet(parent=referable, attribute_names=[("id_short", True)], - items=[child]) + namespace_set = model.NamespaceSet(parent=referable, attribute_names=[("id_short", True)], items=[child]) return referable example_grandchild = generate_example_referable_with_namespace("exampleGrandchild") @@ -119,12 +119,14 @@ def test_id_short_constraint_aasd_002(self): test_object.id_short = "asdlujSAD8348@S" self.assertEqual( "The id_short must contain only letters, digits underscore and hyphen (Constraint AASd-002)", - str(cm.exception)) + str(cm.exception), + ) with self.assertRaises(model.AASConstraintViolation) as cm: test_object.id_short = "abc\n" self.assertEqual( "The id_short must contain only letters, digits underscore and hyphen (Constraint AASd-002)", - str(cm.exception)) + str(cm.exception), + ) def test_representation(self): class DummyClass: @@ -137,8 +139,9 @@ def __init__(self, value: model.Referable): ref.parent = test_object with self.assertRaises(AttributeError) as cm: ref.__repr__() - self.assertEqual('Referable must have an identifiable as root object and only parents that are referable', - str(cm.exception)) + self.assertEqual( + "Referable must have an identifiable as root object and only parents that are referable", str(cm.exception) + ) def test_get_identifiable_root(self): ref_with_no_parent = ExampleReferableWithNamespace() @@ -190,8 +193,9 @@ def test_get_id_short_path(self): MySubSubmodelElementCollection = model.SubmodelElementCollection("MySubSubmodelElementCollection") MySubSubProperty1 = model.Property("MySubSubProperty1", model.datatypes.String) MySubSubProperty2 = model.Property("MySubSubProperty2", model.datatypes.String) - MySubSubmodelElementList1 = model.SubmodelElementList("MySubSubmodelElementList1", model.Property, - value_type_list_element=model.datatypes.String) + MySubSubmodelElementList1 = model.SubmodelElementList( + "MySubSubmodelElementList1", model.Property, value_type_list_element=model.datatypes.String + ) MySubTestValue1 = model.Property(None, model.datatypes.String) MySubTestValue2 = model.Property(None, model.datatypes.String) MySubSubmodelElementList2 = model.SubmodelElementList("MySubSubmodelElementList2", model.SubmodelElementList) @@ -232,10 +236,10 @@ def test_get_id_short_path(self): def test_update_from(self): example_submodel = example_aas.create_example_submodel() - example_relel = example_submodel.get_referable('ExampleRelationshipElement') + example_relel = example_submodel.get_referable("ExampleRelationshipElement") other_submodel = example_aas.create_example_submodel() - other_relel = other_submodel.get_referable('ExampleRelationshipElement') + other_relel = other_submodel.get_referable("ExampleRelationshipElement") other_submodel.category = "NewCat" other_relel.category = "NewRelElCat" @@ -245,8 +249,8 @@ def test_update_from(self): self.assertEqual("NewCat", example_submodel.category) self.assertEqual("NewRelElCat", example_relel.category) # References to Referable objects shall remain stable - self.assertIs(example_relel, example_submodel.get_referable('ExampleRelationshipElement')) - self.assertIs(example_relel, example_submodel.submodel_element.get("id_short", 'ExampleRelationshipElement')) + self.assertIs(example_relel, example_submodel.get_referable("ExampleRelationshipElement")) + self.assertIs(example_relel, example_submodel.submodel_element.get("id_short", "ExampleRelationshipElement")) # Check Namespace & parent consistency self.assertIs(example_submodel.namespace_element_sets[0], example_submodel.submodel_element) self.assertIs(example_relel.parent, example_submodel) @@ -312,12 +316,15 @@ class ModelNamespaceTest(unittest.TestCase): _namespace_class_qualifier = ExampleNamespaceQualifier def setUp(self): - self.propSemanticID = model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/Test1'),)) - self.propSemanticID2 = model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/Test2'),)) - self.propSemanticID3 = model.ExternalReference((model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, - value='http://example.org/Test3'),)) + self.propSemanticID = model.ExternalReference( + (model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, value="http://example.org/Test1"),) + ) + self.propSemanticID2 = model.ExternalReference( + (model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, value="http://example.org/Test2"),) + ) + self.propSemanticID3 = model.ExternalReference( + (model.Key(type_=model.KeyTypes.GLOBAL_REFERENCE, value="http://example.org/Test3"),) + ) self.prop1 = model.Property("Prop1", model.datatypes.Int, semantic_id=self.propSemanticID) self.prop2 = model.Property("Prop2", model.datatypes.Int, semantic_id=self.propSemanticID) self.prop3 = model.Property("Prop2", model.datatypes.Int, semantic_id=self.propSemanticID2) @@ -328,8 +335,9 @@ def setUp(self): self.prop8 = model.Property("ProP2", model.datatypes.Int, semantic_id=self.propSemanticID3) self.prop1alt = model.Property("Prop1", model.datatypes.Int, semantic_id=self.propSemanticID) self.collection1 = model.SubmodelElementCollection(None) - self.list1 = model.SubmodelElementList("List1", model.SubmodelElementCollection, - semantic_id=self.propSemanticID) + self.list1 = model.SubmodelElementList( + "List1", model.SubmodelElementCollection, semantic_id=self.propSemanticID + ) self.qualifier1 = model.Qualifier("type1", model.datatypes.Int, 1, semantic_id=self.propSemanticID) self.qualifier2 = model.Qualifier("type2", model.datatypes.Int, 1, semantic_id=self.propSemanticID2) self.qualifier1alt = model.Qualifier("type1", model.datatypes.Int, 1, semantic_id=self.propSemanticID) @@ -359,22 +367,26 @@ def test_NamespaceSet(self) -> None: "Object with attribute (name='semantic_id', value='ExternalReference(key=(Key(" "type=GLOBAL_REFERENCE, value=http://example.org/Test1),))') is already present in this set of objects " "(Constraint AASd-000)", - str(cm.exception)) + str(cm.exception), + ) self.namespace.set2.add(self.prop5) self.namespace.set2.add(self.prop6) self.assertEqual(2, len(self.namespace.set2)) with self.assertRaises(model.AASConstraintViolation) as cm: self.namespace.set2.add(self.prop1) - self.assertEqual("Object with attribute (name='id_short', value='Prop1') is already present in another " - "set in the same namespace (Constraint AASd-022)", - str(cm.exception)) + self.assertEqual( + "Object with attribute (name='id_short', value='Prop1') is already present in another " + "set in the same namespace (Constraint AASd-022)", + str(cm.exception), + ) with self.assertRaises(model.AASConstraintViolation) as cm: self.namespace.set2.add(self.prop4) self.assertEqual( "Object with attribute (name='semantic_id', value='" "ExternalReference(key=(Key(type=GLOBAL_REFERENCE, value=http://example.org/Test1),))')" " is already present in another set in the same namespace (Constraint AASd-000)", - str(cm.exception)) + str(cm.exception), + ) self.assertIs(self.prop1, self.namespace.set1.get("id_short", "Prop1")) self.assertIn(self.prop1, self.namespace.set1) @@ -385,26 +397,32 @@ def test_NamespaceSet(self) -> None: with self.assertRaises(model.AASConstraintViolation) as cm: self.namespace.set1.add(self.prop1alt) - self.assertEqual("Object with attribute (name='id_short', value='Prop1') is already present in this set of" - " objects (Constraint AASd-022)", - str(cm.exception)) + self.assertEqual( + "Object with attribute (name='id_short', value='Prop1') is already present in this set of" + " objects (Constraint AASd-022)", + str(cm.exception), + ) self.namespace.set1.add(self.prop3) with self.assertRaises(model.AASConstraintViolation) as cm: self.namespace.set1.add(self.prop7) - self.assertEqual("Object with attribute (name='id_short', value='Prop2') is already present in this set " - "of objects (Constraint AASd-022)", - str(cm.exception)) + self.assertEqual( + "Object with attribute (name='id_short', value='Prop2') is already present in this set " + "of objects (Constraint AASd-022)", + str(cm.exception), + ) with self.assertRaises(model.AASConstraintViolation) as cm: self.namespace.set1.add(self.prop8) - self.assertEqual("Object with attribute (name='id_short', value='ProP2') is already present in this set " - "of objects (Constraint AASd-022)", - str(cm.exception)) + self.assertEqual( + "Object with attribute (name='id_short', value='ProP2') is already present in this set " + "of objects (Constraint AASd-022)", + str(cm.exception), + ) namespace2 = self._namespace_class() with self.assertRaises(ValueError) as cm2: namespace2.set1.add(self.prop1) - self.assertIn('has already a parent', str(cm2.exception)) + self.assertIn("has already a parent", str(cm2.exception)) self.assertEqual(2, len(self.namespace.set1)) self.namespace.set1.remove(self.prop1) @@ -439,9 +457,11 @@ def test_NamespaceSet(self) -> None: self.assertEqual(2, len(self.namespace3.set1)) with self.assertRaises(model.AASConstraintViolation) as cm: self.namespace3.set1.add(self.qualifier1alt) - self.assertEqual("Object with attribute (name='type', value='type1') is already present in this set " - "of objects (Constraint AASd-021)", - str(cm.exception)) + self.assertEqual( + "Object with attribute (name='type', value='type1') is already present in this set " + "of objects (Constraint AASd-021)", + str(cm.exception), + ) def test_namespaceset_hooks(self) -> None: T = TypeVar("T", bound=model.Referable) @@ -452,13 +472,22 @@ def test_namespaceset_hooks(self) -> None: existing_items = [] class DummyNamespace(model.UniqueIdShortNamespace): - def __init__(self, items: Iterable[T], item_add_hook: Optional[Callable[[T, Iterable[T]], None]] = None, - item_id_set_hook: Optional[Callable[[T], None]] = None, - item_id_del_hook: Optional[Callable[[T], None]] = None): + def __init__( + self, + items: Iterable[T], + item_add_hook: Optional[Callable[[T, Iterable[T]], None]] = None, + item_id_set_hook: Optional[Callable[[T], None]] = None, + item_id_del_hook: Optional[Callable[[T], None]] = None, + ): super().__init__() - self.set1 = nss_type(self, [('id_short', True)], items, item_add_hook=item_add_hook, - item_id_set_hook=item_id_set_hook, - item_id_del_hook=item_id_del_hook) + self.set1 = nss_type( + self, + [("id_short", True)], + items, + item_add_hook=item_add_hook, + item_id_set_hook=item_id_set_hook, + item_id_del_hook=item_id_del_hook, + ) def add_hook(new: T, existing: Iterable[T]) -> None: nonlocal new_item, existing_items @@ -479,8 +508,9 @@ def id_del_hook(old: T) -> None: old.id_short = old.id_short[:-3] cap = model.Capability("test_cap") - dummy_ns = DummyNamespace({cap}, item_add_hook=add_hook, item_id_set_hook=id_set_hook, - item_id_del_hook=id_del_hook) + dummy_ns = DummyNamespace( + {cap}, item_add_hook=add_hook, item_id_set_hook=id_set_hook, item_id_del_hook=id_del_hook + ) self.assertEqual(cap.id_short, "test_capnew") self.assertIs(new_item, cap) self.assertEqual(len(existing_items), 0) @@ -526,15 +556,20 @@ def add_hook_constraint(_new: T, _existing: Iterable[T]) -> None: self.assertEqual(cap.id_short, "test_cap") self.assertEqual(mlp.id_short, "test_mlp") with self.assertRaises(ValueError): - DummyNamespace([cap, mlp], item_add_hook=add_hook_constraint, item_id_set_hook=id_set_hook, - item_id_del_hook=id_del_hook) + DummyNamespace( + [cap, mlp], + item_add_hook=add_hook_constraint, + item_id_set_hook=id_set_hook, + item_id_del_hook=id_del_hook, + ) self.assertEqual(cap.id_short, "test_cap") self.assertIsNone(cap.parent) self.assertEqual(mlp.id_short, "test_mlp") self.assertIsNone(mlp.parent) - dummy_ns = DummyNamespace((), item_add_hook=add_hook_constraint, item_id_set_hook=id_set_hook, - item_id_del_hook=id_del_hook) + dummy_ns = DummyNamespace( + (), item_add_hook=add_hook_constraint, item_id_set_hook=id_set_hook, item_id_del_hook=id_del_hook + ) add_hook_counter = 0 dummy_ns.add_referable(cap) self.assertIs(cap.parent, dummy_ns) @@ -547,28 +582,36 @@ def add_hook_constraint(_new: T, _existing: Iterable[T]) -> None: def test_Namespace(self) -> None: with self.assertRaises(model.AASConstraintViolation) as cm: namespace_test = ExampleNamespaceReferable([self.prop1, self.prop2, self.prop1alt]) - self.assertEqual("Object with attribute (name='id_short', value='Prop1') is already present in this set " - "of objects (Constraint AASd-022)", - str(cm.exception)) + self.assertEqual( + "Object with attribute (name='id_short', value='Prop1') is already present in this set " + "of objects (Constraint AASd-022)", + str(cm.exception), + ) self.assertIsNone(self.prop1.parent) namespace = self._namespace_class([self.prop1, self.prop2]) self.assertIs(self.prop2, namespace.get_referable("Prop2")) with self.assertRaises(KeyError) as cm2: namespace.get_referable("Prop3") - self.assertEqual("'Referable with id_short Prop3 not found in " - f"{self._namespace_class.__name__}[{self.namespace.id}]'", str(cm2.exception)) + self.assertEqual( + f"'Referable with id_short Prop3 not found in {self._namespace_class.__name__}[{self.namespace.id}]'", + str(cm2.exception), + ) namespace.remove_referable("Prop2") with self.assertRaises(KeyError) as cm3: namespace.get_referable("Prop2") - self.assertEqual("'Referable with id_short Prop2 not found in " - f"{self._namespace_class.__name__}[{self.namespace.id}]'", str(cm3.exception)) + self.assertEqual( + f"'Referable with id_short Prop2 not found in {self._namespace_class.__name__}[{self.namespace.id}]'", + str(cm3.exception), + ) with self.assertRaises(KeyError) as cm4: namespace.remove_referable("Prop2") - self.assertEqual("'Referable with id_short Prop2 not found in " - f"{self._namespace_class.__name__}[{self.namespace.id}]'", str(cm4.exception)) + self.assertEqual( + f"'Referable with id_short Prop2 not found in {self._namespace_class.__name__}[{self.namespace.id}]'", + str(cm4.exception), + ) def test_id_short_path_resolution(self) -> None: self.namespace.set2.add(self.list1) @@ -577,19 +620,27 @@ def test_id_short_path_resolution(self) -> None: with self.assertRaises(ValueError) as cm: self.namespace.get_referable(["List1", "a"]) - self.assertEqual(f"Cannot resolve 'a' at SubmodelElementList[{self.namespace.id} / List1], " - "because it is not a numeric index!", str(cm.exception)) + self.assertEqual( + f"Cannot resolve 'a' at SubmodelElementList[{self.namespace.id} / List1], " + "because it is not a numeric index!", + str(cm.exception), + ) with self.assertRaises(KeyError) as cm_2: self.namespace.get_referable(["List1", "0", "Prop2"]) - self.assertEqual("'Referable with id_short Prop2 not found in " - f"SubmodelElementCollection[{self.namespace.id} / List1[0]]'", str(cm_2.exception)) + self.assertEqual( + f"'Referable with id_short Prop2 not found in SubmodelElementCollection[{self.namespace.id} / List1[0]]'", + str(cm_2.exception), + ) with self.assertRaises(TypeError) as cm_3: self.namespace.get_referable(["List1", "0", "Prop1", "Test"]) - self.assertEqual("Cannot resolve id_short or index 'Test' at " - f"Property[{self.namespace.id} / List1[0].Prop1], " - "because it is not a UniqueIdShortNamespace!", str(cm_3.exception)) + self.assertEqual( + "Cannot resolve id_short or index 'Test' at " + f"Property[{self.namespace.id} / List1[0].Prop1], " + "because it is not a UniqueIdShortNamespace!", + str(cm_3.exception), + ) self.namespace.get_referable(["List1", "0", "Prop1"]) @@ -604,9 +655,11 @@ def test_renaming(self) -> None: self.assertEqual(2, len(self.namespace.set2)) self.assertIs(self.prop1, self.namespace.get_referable("Prop3")) with self.assertRaises(KeyError) as cm: - self.namespace.get_referable('Prop1') - self.assertEqual("'Referable with id_short Prop1 not found in " - f"{self._namespace_class.__name__}[{self.namespace.id}]'", str(cm.exception)) + self.namespace.get_referable("Prop1") + self.assertEqual( + f"'Referable with id_short Prop1 not found in {self._namespace_class.__name__}[{self.namespace.id}]'", + str(cm.exception), + ) self.assertIs(self.prop2, self.namespace.get_referable("Prop2")) with self.assertRaises(model.AASConstraintViolation) as cm2: self.prop1.id_short = "Prop2" @@ -659,8 +712,7 @@ def test_Namespaceset_update_from(self) -> None: def test_qualifiable_id_short_namespace(self) -> None: prop1 = model.Property("Prop1", model.datatypes.Int, 1) qualifier1 = model.Qualifier("Qualifier1", model.datatypes.Int, 2) - submodel_element_collection = model.SubmodelElementCollection("test_SMC", [prop1], - qualifier=[qualifier1]) + submodel_element_collection = model.SubmodelElementCollection("test_SMC", [prop1], qualifier=[qualifier1]) self.assertIs(submodel_element_collection.get_referable("Prop1"), prop1) self.assertIs(submodel_element_collection.get_qualifier_by_type("Qualifier1"), qualifier1) @@ -669,14 +721,20 @@ def test_aasd_117(self) -> None: se_collection = model.SubmodelElementCollection("foo") with self.assertRaises(model.AASConstraintViolation) as cm: se_collection.add_referable(property) - self.assertEqual("Property has attribute id_short=None, which is not allowed within a " - "SubmodelElementCollection! (Constraint AASd-117)", str(cm.exception)) + self.assertEqual( + "Property has attribute id_short=None, which is not allowed within a " + "SubmodelElementCollection! (Constraint AASd-117)", + str(cm.exception), + ) property.id_short = "property" se_collection.add_referable(property) with self.assertRaises(model.AASConstraintViolation) as cm: property.id_short = None - self.assertEqual("id_short of Property[foo.property] cannot be unset, since it is already contained in " - "SubmodelElementCollection[foo] (Constraint AASd-117)", str(cm.exception)) + self.assertEqual( + "id_short of Property[foo.property] cannot be unset, since it is already contained in " + "SubmodelElementCollection[foo] (Constraint AASd-117)", + str(cm.exception), + ) property.id_short = "bar" @@ -703,17 +761,21 @@ def test_OrderedNamespace(self) -> None: self.assertEqual(2, len(self.namespace.set2)) with self.assertRaises(model.AASConstraintViolation) as cm: self.namespace.set1.insert(0, self.prop1alt) - self.assertEqual('Object with attribute (name=\'id_short\', value=\'Prop1\') is already present in another ' - 'set in the same namespace (Constraint AASd-022)', - str(cm.exception)) + self.assertEqual( + "Object with attribute (name='id_short', value='Prop1') is already present in another " + "set in the same namespace (Constraint AASd-022)", + str(cm.exception), + ) self.assertEqual((self.prop2, self.prop1), tuple(self.namespace.set2)) self.assertEqual(self.prop1, self.namespace.set2[1]) with self.assertRaises(model.AASConstraintViolation) as cm: self.namespace.set2[1] = self.prop2 - self.assertEqual('Object with attribute (name=\'id_short\', value=\'Prop2\') is already present in this ' - 'set of objects (Constraint AASd-022)', - str(cm.exception)) + self.assertEqual( + "Object with attribute (name='id_short', value='Prop2') is already present in this " + "set of objects (Constraint AASd-022)", + str(cm.exception), + ) prop3 = model.Property("Prop3", model.datatypes.Int, semantic_id=self.propSemanticID3) self.assertEqual(2, len(self.namespace.set2)) self.namespace.set2[1] = prop3 @@ -735,9 +797,10 @@ def test_OrderedNamespace(self) -> None: self.assertEqual(1, len(namespace2.set2)) with self.assertRaises(KeyError) as cm2: namespace2.get_referable("Prop1") - self.assertEqual("'Referable with id_short Prop1 not found in " - f"{self._namespace_class.__name__}[{self.namespace.id}]'", # type: ignore[has-type] - str(cm2.exception)) + self.assertEqual( + f"'Referable with id_short Prop1 not found in {self._namespace_class.__name__}[{self.namespace.id}]'", # type: ignore[has-type] + str(cm2.exception), + ) def test_ordered_namespaceset_int_setitem_preserves_index(self) -> None: # __setitem__ int must place the new item at the exact index of the replaced item. @@ -806,17 +869,25 @@ def test_constraints(self): keys = (model.Key(model.KeyTypes.PROPERTY, "urn:x-test:x"),) with self.assertRaises(model.AASConstraintViolation) as cm: model.ExternalReference(keys) - self.assertEqual("The type of the first key of an ExternalReference must be a GenericGloballyIdentifiable: " - f"{keys[0]!r} (Constraint AASd-122)", str(cm.exception)) + self.assertEqual( + "The type of the first key of an ExternalReference must be a GenericGloballyIdentifiable: " + f"{keys[0]!r} (Constraint AASd-122)", + str(cm.exception), + ) model.ExternalReference((model.Key(model.KeyTypes.GLOBAL_REFERENCE, "urn:x-test:x"),)) # AASd-124 - keys = (model.Key(model.KeyTypes.GLOBAL_REFERENCE, "urn:x-test:x"), - model.Key(model.KeyTypes.SUBMODEL, "urn:x-test:x"),) + keys = ( + model.Key(model.KeyTypes.GLOBAL_REFERENCE, "urn:x-test:x"), + model.Key(model.KeyTypes.SUBMODEL, "urn:x-test:x"), + ) with self.assertRaises(model.AASConstraintViolation) as cm: model.ExternalReference(keys) - self.assertEqual("The type of the last key of an ExternalReference must be a GenericGloballyIdentifiable or a" - f" GenericFragmentKey: {keys[-1]!r} (Constraint AASd-124)", str(cm.exception)) + self.assertEqual( + "The type of the last key of an ExternalReference must be a GenericGloballyIdentifiable or a" + f" GenericFragmentKey: {keys[-1]!r} (Constraint AASd-124)", + str(cm.exception), + ) keys += (model.Key(model.KeyTypes.FRAGMENT_REFERENCE, "urn:x-test:x"),) model.ExternalReference(keys) @@ -831,61 +902,85 @@ def test_constraints(self): keys = (model.Key(model.KeyTypes.PROPERTY, "urn:x-test:x"),) with self.assertRaises(model.AASConstraintViolation) as cm: model.ModelReference(keys, model.Property) - self.assertEqual(f"The type of the first key of a ModelReference must be an AasIdentifiable: {keys[0]!r}" - " (Constraint AASd-123)", str(cm.exception)) + self.assertEqual( + f"The type of the first key of a ModelReference must be an AasIdentifiable: {keys[0]!r}" + " (Constraint AASd-123)", + str(cm.exception), + ) keys = (model.Key(model.KeyTypes.SUBMODEL, "urn:x-test:x"),) + keys model.ModelReference(keys, model.Property) # AASd-125 - keys = (model.Key(model.KeyTypes.SUBMODEL, "urn:x-test:x"), - model.Key(model.KeyTypes.ASSET_ADMINISTRATION_SHELL, "urn:x-test:x"), - model.Key(model.KeyTypes.CONCEPT_DESCRIPTION, "urn:x-test:x")) + keys = ( + model.Key(model.KeyTypes.SUBMODEL, "urn:x-test:x"), + model.Key(model.KeyTypes.ASSET_ADMINISTRATION_SHELL, "urn:x-test:x"), + model.Key(model.KeyTypes.CONCEPT_DESCRIPTION, "urn:x-test:x"), + ) with self.assertRaises(model.AASConstraintViolation) as cm: model.ModelReference(keys, model.ConceptDescription) - self.assertEqual("The type of all keys following the first of a ModelReference " - f"must be one of FragmentKeyElements: {keys[1]!r} (Constraint AASd-125)", str(cm.exception)) + self.assertEqual( + "The type of all keys following the first of a ModelReference " + f"must be one of FragmentKeyElements: {keys[1]!r} (Constraint AASd-125)", + str(cm.exception), + ) keys = (keys[0], model.Key(model.KeyTypes.FILE, "urn:x-test:x"), keys[2]) with self.assertRaises(model.AASConstraintViolation) as cm: model.ModelReference(keys, model.ConceptDescription) - self.assertEqual("The type of all keys following the first of a ModelReference " - f"must be one of FragmentKeyElements: {keys[2]!r} (Constraint AASd-125)", str(cm.exception)) + self.assertEqual( + "The type of all keys following the first of a ModelReference " + f"must be one of FragmentKeyElements: {keys[2]!r} (Constraint AASd-125)", + str(cm.exception), + ) keys = tuple(keys[:2]) + (model.Key(model.KeyTypes.FRAGMENT_REFERENCE, "urn:x-test:x"),) model.ModelReference(keys, model.ConceptDescription) # AASd-126 - keys = (model.Key(model.KeyTypes.SUBMODEL, "urn:x-test:x"), - model.Key(model.KeyTypes.FILE, "urn:x-test:x"), - model.Key(model.KeyTypes.FRAGMENT_REFERENCE, "urn:x-test:x"), - model.Key(model.KeyTypes.PROPERTY, "urn:x-test:x")) + keys = ( + model.Key(model.KeyTypes.SUBMODEL, "urn:x-test:x"), + model.Key(model.KeyTypes.FILE, "urn:x-test:x"), + model.Key(model.KeyTypes.FRAGMENT_REFERENCE, "urn:x-test:x"), + model.Key(model.KeyTypes.PROPERTY, "urn:x-test:x"), + ) with self.assertRaises(model.AASConstraintViolation) as cm: model.ModelReference(keys, model.Property) - self.assertEqual(f"Key {keys[2]!r} is a GenericFragmentKey, but the last key of the chain is not: {keys[-1]!r}" - " (Constraint AASd-126)", str(cm.exception)) + self.assertEqual( + f"Key {keys[2]!r} is a GenericFragmentKey, but the last key of the chain is not: {keys[-1]!r}" + " (Constraint AASd-126)", + str(cm.exception), + ) keys = tuple(keys[:3]) model.ModelReference(keys, model.File) # AASd-127 - keys = (model.Key(model.KeyTypes.SUBMODEL, "urn:x-test:x"), - model.Key(model.KeyTypes.PROPERTY, "urn:x-test:x"), - model.Key(model.KeyTypes.FRAGMENT_REFERENCE, "urn:x-test:x")) + keys = ( + model.Key(model.KeyTypes.SUBMODEL, "urn:x-test:x"), + model.Key(model.KeyTypes.PROPERTY, "urn:x-test:x"), + model.Key(model.KeyTypes.FRAGMENT_REFERENCE, "urn:x-test:x"), + ) with self.assertRaises(model.AASConstraintViolation) as cm: model.ModelReference(keys, model.Property) - self.assertEqual(f"{keys[-1]!r} is not preceded by a key of type File or Blob, but {keys[1]!r}" - f" (Constraint AASd-127)", str(cm.exception)) + self.assertEqual( + f"{keys[-1]!r} is not preceded by a key of type File or Blob, but {keys[1]!r} (Constraint AASd-127)", + str(cm.exception), + ) keys = (keys[0], model.Key(model.KeyTypes.BLOB, "urn:x-test:x"), keys[2]) model.ModelReference(keys, model.Blob) # AASd-128 - keys = (model.Key(model.KeyTypes.SUBMODEL, "urn:x-test:x"), - model.Key(model.KeyTypes.SUBMODEL_ELEMENT_LIST, "urn:x-test:x")) + keys = ( + model.Key(model.KeyTypes.SUBMODEL, "urn:x-test:x"), + model.Key(model.KeyTypes.SUBMODEL_ELEMENT_LIST, "urn:x-test:x"), + ) for invalid_key_value in ("string", "-5", "5.5", "5,5", "+5"): invalid_key = model.Key(model.KeyTypes.PROPERTY, invalid_key_value) with self.assertRaises(model.AASConstraintViolation) as cm: model.ModelReference(keys + (invalid_key,), model.Property) - self.assertEqual(f"Key {keys[1]!r} references a SubmodelElementList, but the value of the succeeding key " - f"({invalid_key!r}) is not a non-negative integer: {invalid_key.value} " - "(Constraint AASd-128)", - str(cm.exception)) + self.assertEqual( + f"Key {keys[1]!r} references a SubmodelElementList, but the value of the succeeding key " + f"({invalid_key!r}) is not a non-negative integer: {invalid_key.value} " + "(Constraint AASd-128)", + str(cm.exception), + ) keys = keys[:1] + (model.Key(model.KeyTypes.PROPERTY, "5"),) model.ModelReference(keys, model.Property) @@ -893,35 +988,37 @@ def test_set_reference(self): ref = model.ModelReference((model.Key(model.KeyTypes.SUBMODEL, "urn:x-test:x"),), model.Submodel) with self.assertRaises(AttributeError) as cm: ref.type = model.Property - self.assertEqual('Reference is immutable', str(cm.exception)) + self.assertEqual("Reference is immutable", str(cm.exception)) with self.assertRaises(AttributeError) as cm: ref.key = model.Key(model.KeyTypes.PROPERTY, "urn:x-test:x") - self.assertEqual('Reference is immutable', str(cm.exception)) + self.assertEqual("Reference is immutable", str(cm.exception)) with self.assertRaises(AttributeError) as cm: ref.key = () - self.assertEqual('Reference is immutable', str(cm.exception)) + self.assertEqual("Reference is immutable", str(cm.exception)) with self.assertRaises(AttributeError) as cm: ref.referred_semantic_id = model.ExternalReference( - (model.Key(model.KeyTypes.GLOBAL_REFERENCE, "urn:x-test:x"),)) - self.assertEqual('Reference is immutable', str(cm.exception)) + (model.Key(model.KeyTypes.GLOBAL_REFERENCE, "urn:x-test:x"),) + ) + self.assertEqual("Reference is immutable", str(cm.exception)) def test_equality(self): - ref = model.ModelReference((model.Key(model.KeyTypes.SUBMODEL, "urn:x-test:x"),), - model.Submodel) - ident = 'test' + ref = model.ModelReference((model.Key(model.KeyTypes.SUBMODEL, "urn:x-test:x"),), model.Submodel) + ident = "test" self.assertEqual(ref.__eq__(ident), NotImplemented) - ref_2 = model.ModelReference((model.Key(model.KeyTypes.SUBMODEL, "urn:x-test:x"), - model.Key(model.KeyTypes.PROPERTY, "test")), - model.Submodel) + ref_2 = model.ModelReference( + (model.Key(model.KeyTypes.SUBMODEL, "urn:x-test:x"), model.Key(model.KeyTypes.PROPERTY, "test")), + model.Submodel, + ) self.assertNotEqual(ref, ref_2) - ref_3 = model.ModelReference((model.Key(model.KeyTypes.SUBMODEL, "urn:x-test:x"), - model.Key(model.KeyTypes.PROPERTY, "test")), - model.Submodel) + ref_3 = model.ModelReference( + (model.Key(model.KeyTypes.SUBMODEL, "urn:x-test:x"), model.Key(model.KeyTypes.PROPERTY, "test")), + model.Submodel, + ) self.assertEqual(ref_2, ref_3) referred_semantic_id = model.ExternalReference((model.Key(model.KeyTypes.GLOBAL_REFERENCE, "urn:x-test:x"),)) - object.__setattr__(ref_2, 'referred_semantic_id', referred_semantic_id) + object.__setattr__(ref_2, "referred_semantic_id", referred_semantic_id) self.assertNotEqual(ref_2, ref_3) - object.__setattr__(ref_3, 'referred_semantic_id', referred_semantic_id) + object.__setattr__(ref_3, "referred_semantic_id", referred_semantic_id) self.assertEqual(ref_2, ref_3) def test_reference_typing(self) -> None: @@ -948,42 +1045,63 @@ def get_item(self, identifier: Identifier) -> Identifiable: else: raise KeyError() - ref1 = model.ModelReference((model.Key(model.KeyTypes.SUBMODEL, "urn:x-test:submodel"), - model.Key(model.KeyTypes.SUBMODEL_ELEMENT_LIST, "lst"), - model.Key(model.KeyTypes.SUBMODEL_ELEMENT_COLLECTION, "99"), - model.Key(model.KeyTypes.PROPERTY, "prop")), - model.Property) + ref1 = model.ModelReference( + ( + model.Key(model.KeyTypes.SUBMODEL, "urn:x-test:submodel"), + model.Key(model.KeyTypes.SUBMODEL_ELEMENT_LIST, "lst"), + model.Key(model.KeyTypes.SUBMODEL_ELEMENT_COLLECTION, "99"), + model.Key(model.KeyTypes.PROPERTY, "prop"), + ), + model.Property, + ) with self.assertRaises(KeyError) as cm: ref1.resolve(DummyIdentifiableProvider()) self.assertEqual("'Referable with id_short lst not found in Submodel[urn:x-test:submodel]'", str(cm.exception)) - ref2 = model.ModelReference((model.Key(model.KeyTypes.SUBMODEL, "urn:x-test:submodel"), - model.Key(model.KeyTypes.SUBMODEL_ELEMENT_LIST, "list"), - model.Key(model.KeyTypes.SUBMODEL_ELEMENT_COLLECTION, "99"), - model.Key(model.KeyTypes.PROPERTY, "prop")), - model.Property) + ref2 = model.ModelReference( + ( + model.Key(model.KeyTypes.SUBMODEL, "urn:x-test:submodel"), + model.Key(model.KeyTypes.SUBMODEL_ELEMENT_LIST, "list"), + model.Key(model.KeyTypes.SUBMODEL_ELEMENT_COLLECTION, "99"), + model.Key(model.KeyTypes.PROPERTY, "prop"), + ), + model.Property, + ) with self.assertRaises(KeyError) as cm_2: ref2.resolve(DummyIdentifiableProvider()) - self.assertEqual("'Referable with index 99 not found in SubmodelElementList[urn:x-test:submodel / list]'", - str(cm_2.exception)) - - ref3 = model.ModelReference((model.Key(model.KeyTypes.SUBMODEL, "urn:x-test:submodel"), - model.Key(model.KeyTypes.SUBMODEL_ELEMENT_LIST, "list"), - model.Key(model.KeyTypes.SUBMODEL_ELEMENT_COLLECTION, "0"), - model.Key(model.KeyTypes.PROPERTY, "prop")), - model.Property) + self.assertEqual( + "'Referable with index 99 not found in SubmodelElementList[urn:x-test:submodel / list]'", + str(cm_2.exception), + ) + + ref3 = model.ModelReference( + ( + model.Key(model.KeyTypes.SUBMODEL, "urn:x-test:submodel"), + model.Key(model.KeyTypes.SUBMODEL_ELEMENT_LIST, "list"), + model.Key(model.KeyTypes.SUBMODEL_ELEMENT_COLLECTION, "0"), + model.Key(model.KeyTypes.PROPERTY, "prop"), + ), + model.Property, + ) self.assertIs(prop, ref3.resolve(DummyIdentifiableProvider())) - ref4 = model.ModelReference((model.Key(model.KeyTypes.SUBMODEL, "urn:x-test:submodel"), - model.Key(model.KeyTypes.SUBMODEL_ELEMENT_LIST, "list"), - model.Key(model.KeyTypes.SUBMODEL_ELEMENT_COLLECTION, "0"), - model.Key(model.KeyTypes.PROPERTY, "prop"), - model.Key(model.KeyTypes.PROPERTY, "prop")), - model.Property) + ref4 = model.ModelReference( + ( + model.Key(model.KeyTypes.SUBMODEL, "urn:x-test:submodel"), + model.Key(model.KeyTypes.SUBMODEL_ELEMENT_LIST, "list"), + model.Key(model.KeyTypes.SUBMODEL_ELEMENT_COLLECTION, "0"), + model.Key(model.KeyTypes.PROPERTY, "prop"), + model.Key(model.KeyTypes.PROPERTY, "prop"), + ), + model.Property, + ) with self.assertRaises(TypeError) as cm_3: ref4.resolve(DummyIdentifiableProvider()) - self.assertEqual("Cannot resolve id_short or index 'prop' at Property[urn:x-test:submodel / list[0].prop], " - "because it is not a UniqueIdShortNamespace!", str(cm_3.exception)) + self.assertEqual( + "Cannot resolve id_short or index 'prop' at Property[urn:x-test:submodel / list[0].prop], " + "because it is not a UniqueIdShortNamespace!", + str(cm_3.exception), + ) with self.assertRaises(AttributeError) as cm_4: ref1.key[2].value = "prop1" @@ -1004,34 +1122,54 @@ def get_item(self, identifier: Identifier) -> Identifiable: with self.assertRaises(ValueError) as cm_7: ref7 = model.ModelReference((), model.Submodel) - self.assertEqual('A reference must have at least one key!', str(cm_7.exception)) - - ref8 = model.ModelReference((model.Key(model.KeyTypes.SUBMODEL, "urn:x-test:submodel"), - model.Key(model.KeyTypes.SUBMODEL_ELEMENT_LIST, "list"), - model.Key(model.KeyTypes.SUBMODEL_ELEMENT_COLLECTION, "0"), - model.Key(model.KeyTypes.PROPERTY, "prop_false")), model.Property) + self.assertEqual("A reference must have at least one key!", str(cm_7.exception)) + + ref8 = model.ModelReference( + ( + model.Key(model.KeyTypes.SUBMODEL, "urn:x-test:submodel"), + model.Key(model.KeyTypes.SUBMODEL_ELEMENT_LIST, "list"), + model.Key(model.KeyTypes.SUBMODEL_ELEMENT_COLLECTION, "0"), + model.Key(model.KeyTypes.PROPERTY, "prop_false"), + ), + model.Property, + ) with self.assertRaises(KeyError) as cm_8: ref8.resolve(DummyIdentifiableProvider()) - self.assertEqual("'Referable with id_short prop_false not found in " - "SubmodelElementCollection[urn:x-test:submodel / list[0]]'", str(cm_8.exception)) - - ref9 = model.ModelReference((model.Key(model.KeyTypes.SUBMODEL, "urn:x-test:submodel"), - model.Key(model.KeyTypes.SUBMODEL_ELEMENT_COLLECTION, "list"), - model.Key(model.KeyTypes.SUBMODEL_ELEMENT_COLLECTION, "collection")), - model.SubmodelElementCollection) + self.assertEqual( + "'Referable with id_short prop_false not found in " + "SubmodelElementCollection[urn:x-test:submodel / list[0]]'", + str(cm_8.exception), + ) + + ref9 = model.ModelReference( + ( + model.Key(model.KeyTypes.SUBMODEL, "urn:x-test:submodel"), + model.Key(model.KeyTypes.SUBMODEL_ELEMENT_COLLECTION, "list"), + model.Key(model.KeyTypes.SUBMODEL_ELEMENT_COLLECTION, "collection"), + ), + model.SubmodelElementCollection, + ) with self.assertRaises(ValueError) as cm_9: ref9.resolve(DummyIdentifiableProvider()) - self.assertEqual("Cannot resolve 'collection' at SubmodelElementList[urn:x-test:submodel / list], " - "because it is not a numeric index!", str(cm_9.exception)) + self.assertEqual( + "Cannot resolve 'collection' at SubmodelElementList[urn:x-test:submodel / list], " + "because it is not a numeric index!", + str(cm_9.exception), + ) def test_get_identifier(self) -> None: ref = model.ModelReference((model.Key(model.KeyTypes.SUBMODEL, "urn:x-test:x"),), model.Submodel) self.assertEqual("urn:x-test:x", ref.get_identifier()) - ref2 = model.ModelReference((model.Key(model.KeyTypes.SUBMODEL, "urn:x-test:x"), - model.Key(model.KeyTypes.PROPERTY, "myProperty"),), model.Submodel) + ref2 = model.ModelReference( + ( + model.Key(model.KeyTypes.SUBMODEL, "urn:x-test:x"), + model.Key(model.KeyTypes.PROPERTY, "myProperty"), + ), + model.Submodel, + ) self.assertEqual("urn:x-test:x", ref2.get_identifier()) def test_from_referable(self) -> None: @@ -1059,8 +1197,9 @@ def test_from_referable(self) -> None: submodel.submodel_element.remove(collection) with self.assertRaises(ValueError) as cm: ref3 = model.ModelReference.from_referable(prop) - self.assertEqual("The given Referable object is not embedded within an Identifiable object", - str(cm.exception).split(":")[0]) + self.assertEqual( + "The given Referable object is not embedded within an Identifiable object", str(cm.exception).split(":")[0] + ) # Test creating a reference to a custom SubmodelElement class class DummyThing(model.SubmodelElement): @@ -1080,24 +1219,29 @@ def __init__(self, id_: model.Identifier): class AdministrativeInformationTest(unittest.TestCase): - def test_setting_version_revision(self) -> None: with self.assertRaises(model.AASConstraintViolation) as cm: - obj = model.AdministrativeInformation(revision='9') - self.assertEqual("A revision requires a version. This means, if there is no version there is no " - "revision neither. Please set version first. (Constraint AASd-005)", str(cm.exception)) + obj = model.AdministrativeInformation(revision="9") + self.assertEqual( + "A revision requires a version. This means, if there is no version there is no " + "revision neither. Please set version first. (Constraint AASd-005)", + str(cm.exception), + ) def test_setting_revision(self) -> None: obj = model.AdministrativeInformation() with self.assertRaises(model.AASConstraintViolation) as cm: - obj.revision = '3' - self.assertEqual("A revision requires a version. This means, if there is no version there is no revision " - "neither. Please set version first. (Constraint AASd-005)", str(cm.exception)) + obj.revision = "3" + self.assertEqual( + "A revision requires a version. This means, if there is no version there is no revision " + "neither. Please set version first. (Constraint AASd-005)", + str(cm.exception), + ) class QualifierTest(unittest.TestCase): def test_set_value(self): - qualifier = model.Qualifier('test', model.datatypes.Int, 2) + qualifier = model.Qualifier("test", model.datatypes.Int, 2) self.assertEqual(qualifier.value, 2) qualifier.value = None self.assertIsNone(qualifier.value) @@ -1105,11 +1249,11 @@ def test_set_value(self): class ExtensionTest(unittest.TestCase): def test_set_value(self): - extension = model.Extension('test', model.datatypes.Int, 2) + extension = model.Extension("test", model.datatypes.Int, 2) self.assertEqual(extension.value, 2) extension.value = None self.assertIsNone(extension.value) - extension2 = model.Extension('test') + extension2 = model.Extension("test") with self.assertRaises(ValueError) as cm: extension2.value = 2 self.assertEqual("ValueType must be set, if value is not None", str(cm.exception)) @@ -1118,8 +1262,8 @@ def test_set_value(self): class ValueReferencePairTest(unittest.TestCase): def test_set_value(self): pair = model.ValueReferencePair( - value="2", - value_id=model.ExternalReference((model.Key(model.KeyTypes.GLOBAL_REFERENCE, 'test'),))) + value="2", value_id=model.ExternalReference((model.Key(model.KeyTypes.GLOBAL_REFERENCE, "test"),)) + ) self.assertEqual(pair.value, "2") pair.value = "3" self.assertEqual(pair.value, "3") @@ -1127,7 +1271,7 @@ def test_set_value(self): class HasSemanticsTest(unittest.TestCase): def test_supplemental_semantic_id_constraint(self) -> None: - extension = model.Extension(name='test') + extension = model.Extension(name="test") key: model.Key = model.Key(model.KeyTypes.GLOBAL_REFERENCE, "global_reference") ref_sem_id: model.Reference = model.ExternalReference((key,)) ref1: model.Reference = model.ExternalReference((key,)) @@ -1135,17 +1279,22 @@ def test_supplemental_semantic_id_constraint(self) -> None: with self.assertRaises(model.AASConstraintViolation) as cm: extension.supplemental_semantic_id.append(ref1) self.assertEqual(cm.exception.constraint_id, 118) - self.assertEqual('A semantic_id must be defined before adding a supplemental_semantic_id! ' - '(Constraint AASd-118)', str(cm.exception)) + self.assertEqual( + "A semantic_id must be defined before adding a supplemental_semantic_id! (Constraint AASd-118)", + str(cm.exception), + ) extension.semantic_id = ref_sem_id extension.supplemental_semantic_id.append(ref1) with self.assertRaises(model.AASConstraintViolation) as cm: extension.semantic_id = None self.assertEqual(cm.exception.constraint_id, 118) - self.assertEqual('semantic_id can not be removed while there is at least one supplemental_semantic_id: ' - '[ExternalReference(key=(Key(type=GLOBAL_REFERENCE, value=global_reference),))] ' - '(Constraint AASd-118)', str(cm.exception)) + self.assertEqual( + "semantic_id can not be removed while there is at least one supplemental_semantic_id: " + "[ExternalReference(key=(Key(type=GLOBAL_REFERENCE, value=global_reference),))] " + "(Constraint AASd-118)", + str(cm.exception), + ) extension.supplemental_semantic_id.clear() extension.semantic_id = None @@ -1194,9 +1343,9 @@ def del_hook(itm: int, list_: List[int]) -> None: self.assertIsNone(new) self.assertEqual(len(existing_items), 0) - c_list: model.ConstrainedList[int] = model.ConstrainedList([1, 2, 3], item_add_hook=add_hook, - item_set_hook=set_hook, - item_del_hook=del_hook) + c_list: model.ConstrainedList[int] = model.ConstrainedList( + [1, 2, 3], item_add_hook=add_hook, item_set_hook=set_hook, item_del_hook=del_hook + ) check_list: List[int] = [1, 2, 3] self.assertEqual(new, 3) @@ -1308,13 +1457,16 @@ def test_language_tag_constraints(self) -> None: with self.assertRaises(ValueError) as cm: model.LangStringSet({"x": "bar"}) - self.assertEqual(f"The language tag must follow the format defined in BCP 47. " - f"Given language tag: x", cm.exception.args[0]) + self.assertEqual( + f"The language tag must follow the format defined in BCP 47. Given language tag: x", cm.exception.args[0] + ) with self.assertRaises(ValueError) as cm: model.LangStringSet({"foo-oo1": "bar"}) - self.assertEqual(f"The language tag must follow the format defined in BCP 47. " - f"Given language tag: foo-oo1", cm.exception.args[0]) + self.assertEqual( + f"The language tag must follow the format defined in BCP 47. Given language tag: foo-oo1", + cm.exception.args[0], + ) lss = model.LangStringSet({"fo-OO": "bar"}) self.assertIn("fo-OO", lss) @@ -1343,23 +1495,29 @@ def test_empty(self) -> None: def test_text_constraints(self) -> None: with self.assertRaises(ValueError) as cm: model.MultiLanguageNameType({"fo": "o" * 129}) - self.assertEqual("The text for the language tag 'fo' is invalid: MultiLanguageNameType has a maximum length of " - "128! (length: 129)", str(cm.exception)) + self.assertEqual( + "The text for the language tag 'fo' is invalid: MultiLanguageNameType has a maximum length of " + "128! (length: 129)", + str(cm.exception), + ) mlnt = model.MultiLanguageNameType({"fo": "o" * 128}) with self.assertRaises(ValueError) as cm: mlnt["fo"] = "" - self.assertEqual("The text for the language tag 'fo' is invalid: MultiLanguageNameType has a minimum length of " - "1! (length: 0)", str(cm.exception)) + self.assertEqual( + "The text for the language tag 'fo' is invalid: MultiLanguageNameType has a minimum length of " + "1! (length: 0)", + str(cm.exception), + ) self.assertEqual(mlnt["fo"], "o" * 128) mlnt["fo"] = "o" self.assertEqual(mlnt["fo"], "o") def test_repr(self) -> None: lss = model.LangStringSet({"fo": "bar"}) - self.assertEqual("LangStringSet(fo=\"bar\")", repr(lss)) + self.assertEqual('LangStringSet(fo="bar")', repr(lss)) self.assertEqual(repr(lss), str(lss)) mltt = model.MultiLanguageTextType({"fo": "bar"}) - self.assertEqual("MultiLanguageTextType(fo=\"bar\")", repr(mltt)) + self.assertEqual('MultiLanguageTextType(fo="bar")', repr(mltt)) self.assertEqual(repr(mltt), str(mltt)) def test_len(self) -> None: diff --git a/sdk/test/model/test_datatypes.py b/sdk/test/model/test_datatypes.py index aaab73aa9..396919667 100644 --- a/sdk/test/model/test_datatypes.py +++ b/sdk/test/model/test_datatypes.py @@ -22,14 +22,15 @@ def test_parse_int(self) -> None: self.assertEqual(8, model.datatypes.from_xsd("8", model.datatypes.Long)) self.assertEqual(9, model.datatypes.from_xsd("9", model.datatypes.Int)) self.assertEqual(10, model.datatypes.from_xsd("10", model.datatypes.Short)) - self.assertEqual(-123456789012345678901234567890, - model.datatypes.from_xsd("-123456789012345678901234567890", model.datatypes.Integer)) + self.assertEqual( + -123456789012345678901234567890, + model.datatypes.from_xsd("-123456789012345678901234567890", model.datatypes.Integer), + ) self.assertEqual(2147483647, model.datatypes.from_xsd("2147483647", model.datatypes.Int)) self.assertEqual(-2147483648, model.datatypes.from_xsd("-2147483648", model.datatypes.Int)) self.assertEqual(-32768, model.datatypes.from_xsd("-32768", model.datatypes.Short)) self.assertEqual(-128, model.datatypes.from_xsd("-128", model.datatypes.Byte)) - self.assertEqual(-9223372036854775808, - model.datatypes.from_xsd("-9223372036854775808", model.datatypes.Long)) + self.assertEqual(-9223372036854775808, model.datatypes.from_xsd("-9223372036854775808", model.datatypes.Long)) def test_serialize_int(self) -> None: self.assertEqual("5", model.datatypes.xsd_repr(model.datatypes.Integer(5))) @@ -58,7 +59,7 @@ def test_range_error(self) -> None: self.assertEqual("2147483648 is out of the allowed range for type Int", str(cm.exception)) with self.assertRaises(ValueError) as cm: model.datatypes.Long(2**63) - self.assertEqual(str(2**63)+" is out of the allowed range for type Long", str(cm.exception)) + self.assertEqual(str(2**63) + " is out of the allowed range for type Long", str(cm.exception)) def test_trivial_cast(self) -> None: val = model.datatypes.trivial_cast(5, model.datatypes.UnsignedByte) @@ -101,30 +102,42 @@ def test_serialize(self) -> None: class TestDateTimeTypes(unittest.TestCase): def test_parse_duration(self) -> None: # Examples from https://www.w3.org/TR/xmlschema-2/#duration-lexical-repr - self.assertEqual(dateutil.relativedelta.relativedelta(years=1, months=2, hours=2), - model.datatypes.from_xsd("P1Y2MT2H", model.datatypes.Duration)) - self.assertEqual(dateutil.relativedelta.relativedelta(months=1347), - model.datatypes.from_xsd("P0Y1347M", model.datatypes.Duration)) - self.assertEqual(dateutil.relativedelta.relativedelta(months=1347), - model.datatypes.from_xsd("P0Y1347M0D", model.datatypes.Duration)) - self.assertEqual(dateutil.relativedelta.relativedelta(months=-1347), - model.datatypes.from_xsd("-P1347M", model.datatypes.Duration)) - self.assertEqual(dateutil.relativedelta.relativedelta(years=1, months=2, days=3, hours=10, minutes=30), - model.datatypes.from_xsd("P1Y2M3DT10H30M", model.datatypes.Duration)) + self.assertEqual( + dateutil.relativedelta.relativedelta(years=1, months=2, hours=2), + model.datatypes.from_xsd("P1Y2MT2H", model.datatypes.Duration), + ) + self.assertEqual( + dateutil.relativedelta.relativedelta(months=1347), + model.datatypes.from_xsd("P0Y1347M", model.datatypes.Duration), + ) + self.assertEqual( + dateutil.relativedelta.relativedelta(months=1347), + model.datatypes.from_xsd("P0Y1347M0D", model.datatypes.Duration), + ) + self.assertEqual( + dateutil.relativedelta.relativedelta(months=-1347), + model.datatypes.from_xsd("-P1347M", model.datatypes.Duration), + ) + self.assertEqual( + dateutil.relativedelta.relativedelta(years=1, months=2, days=3, hours=10, minutes=30), + model.datatypes.from_xsd("P1Y2M3DT10H30M", model.datatypes.Duration), + ) with self.assertRaises(ValueError) as cm: model.datatypes.from_xsd("P-1347M", model.datatypes.Duration) self.assertEqual("Value is not a valid XSD duration string", str(cm.exception)) def test_serialize_duration(self) -> None: - self.assertEqual("P1Y2MT2H", - model.datatypes.xsd_repr(dateutil.relativedelta.relativedelta(years=1, months=2, hours=2))) - self.assertEqual("P112Y3M", - model.datatypes.xsd_repr(dateutil.relativedelta.relativedelta(months=1347))) - self.assertEqual("-P112Y3M", - model.datatypes.xsd_repr(dateutil.relativedelta.relativedelta(months=-1347))) - self.assertEqual("P1Y2M3DT10H30M", - model.datatypes.xsd_repr(dateutil.relativedelta.relativedelta(years=1, months=2, days=3, - hours=10, minutes=30))) + self.assertEqual( + "P1Y2MT2H", model.datatypes.xsd_repr(dateutil.relativedelta.relativedelta(years=1, months=2, hours=2)) + ) + self.assertEqual("P112Y3M", model.datatypes.xsd_repr(dateutil.relativedelta.relativedelta(months=1347))) + self.assertEqual("-P112Y3M", model.datatypes.xsd_repr(dateutil.relativedelta.relativedelta(months=-1347))) + self.assertEqual( + "P1Y2M3DT10H30M", + model.datatypes.xsd_repr( + dateutil.relativedelta.relativedelta(years=1, months=2, days=3, hours=10, minutes=30) + ), + ) zero_val = model.datatypes.xsd_repr(dateutil.relativedelta.relativedelta()) self.assertGreaterEqual(len(zero_val), 3) self.assertEqual("P", zero_val[0]) @@ -134,12 +147,18 @@ def test_serialize_duration(self) -> None: def test_parse_date(self) -> None: self.assertEqual(datetime.date(2020, 1, 24), model.datatypes.from_xsd("2020-01-24", model.datatypes.Date)) - self.assertEqual(model.datatypes.Date(2020, 1, 24, datetime.timezone.utc), - model.datatypes.from_xsd("2020-01-24Z", model.datatypes.Date)) - self.assertEqual(model.datatypes.Date(2020, 1, 24, datetime.timezone(datetime.timedelta(hours=11, minutes=20))), - model.datatypes.from_xsd("2020-01-24+11:20", model.datatypes.Date)) - self.assertEqual(model.datatypes.Date(2020, 1, 24, datetime.timezone(datetime.timedelta(hours=-8))), - model.datatypes.from_xsd("2020-01-24-08:00", model.datatypes.Date)) + self.assertEqual( + model.datatypes.Date(2020, 1, 24, datetime.timezone.utc), + model.datatypes.from_xsd("2020-01-24Z", model.datatypes.Date), + ) + self.assertEqual( + model.datatypes.Date(2020, 1, 24, datetime.timezone(datetime.timedelta(hours=11, minutes=20))), + model.datatypes.from_xsd("2020-01-24+11:20", model.datatypes.Date), + ) + self.assertEqual( + model.datatypes.Date(2020, 1, 24, datetime.timezone(datetime.timedelta(hours=-8))), + model.datatypes.from_xsd("2020-01-24-08:00", model.datatypes.Date), + ) with self.assertRaises(ValueError) as cm: model.datatypes.from_xsd("2020-01-24+11", model.datatypes.Date) self.assertEqual("Value is not a valid XSD date string", str(cm.exception)) @@ -148,30 +167,34 @@ def test_parse_date(self) -> None: def test_serialize_date(self) -> None: self.assertEqual("2020-01-24", model.datatypes.xsd_repr(model.datatypes.Date(2020, 1, 24))) - self.assertEqual("2020-01-24Z", model.datatypes.xsd_repr( - model.datatypes.Date(2020, 1, 24, datetime.timezone.utc))) - self.assertEqual("2020-01-24+11:20", model.datatypes.xsd_repr( - model.datatypes.Date(2020, 1, 24, datetime.timezone(datetime.timedelta(hours=11, minutes=20))))) + self.assertEqual( + "2020-01-24Z", model.datatypes.xsd_repr(model.datatypes.Date(2020, 1, 24, datetime.timezone.utc)) + ) + self.assertEqual( + "2020-01-24+11:20", + model.datatypes.xsd_repr( + model.datatypes.Date(2020, 1, 24, datetime.timezone(datetime.timedelta(hours=11, minutes=20))) + ), + ) def test_parse_partial_dates(self) -> None: - self.assertEqual(model.datatypes.GYear(2019), - model.datatypes.from_xsd("2019", model.datatypes.GYear)) - self.assertEqual(model.datatypes.GYear(-2001), - model.datatypes.from_xsd("-2001", model.datatypes.GYear)) - self.assertEqual(model.datatypes.GYear(20000), - model.datatypes.from_xsd("20000", model.datatypes.GYear)) - self.assertEqual(model.datatypes.GMonth(7), - model.datatypes.from_xsd("--07", model.datatypes.GMonth)) - self.assertEqual(model.datatypes.GYearMonth(2020, 5), - model.datatypes.from_xsd("2020-05", model.datatypes.GYearMonth)) - self.assertEqual(model.datatypes.GYearMonth(-2001, 10), - model.datatypes.from_xsd("-2001-10", model.datatypes.GYearMonth)) - self.assertEqual(model.datatypes.GYearMonth(20000, 5), - model.datatypes.from_xsd("20000-05", model.datatypes.GYearMonth)) - self.assertEqual(model.datatypes.GMonthDay(12, 6), - model.datatypes.from_xsd("--12-06", model.datatypes.GMonthDay)) - self.assertEqual(model.datatypes.GDay(23), - model.datatypes.from_xsd("---23", model.datatypes.GDay)) + self.assertEqual(model.datatypes.GYear(2019), model.datatypes.from_xsd("2019", model.datatypes.GYear)) + self.assertEqual(model.datatypes.GYear(-2001), model.datatypes.from_xsd("-2001", model.datatypes.GYear)) + self.assertEqual(model.datatypes.GYear(20000), model.datatypes.from_xsd("20000", model.datatypes.GYear)) + self.assertEqual(model.datatypes.GMonth(7), model.datatypes.from_xsd("--07", model.datatypes.GMonth)) + self.assertEqual( + model.datatypes.GYearMonth(2020, 5), model.datatypes.from_xsd("2020-05", model.datatypes.GYearMonth) + ) + self.assertEqual( + model.datatypes.GYearMonth(-2001, 10), model.datatypes.from_xsd("-2001-10", model.datatypes.GYearMonth) + ) + self.assertEqual( + model.datatypes.GYearMonth(20000, 5), model.datatypes.from_xsd("20000-05", model.datatypes.GYearMonth) + ) + self.assertEqual( + model.datatypes.GMonthDay(12, 6), model.datatypes.from_xsd("--12-06", model.datatypes.GMonthDay) + ) + self.assertEqual(model.datatypes.GDay(23), model.datatypes.from_xsd("---23", model.datatypes.GDay)) with self.assertRaises(ValueError) as cm: model.datatypes.from_xsd("--23", model.datatypes.GDay) self.assertEqual("Value is not a valid XSD GDay string", str(cm.exception)) @@ -208,24 +231,36 @@ def test_serialize_partial_dates(self) -> None: self.assertEqual("2019Z", model.datatypes.xsd_repr(model.datatypes.GYear(2019, datetime.timezone.utc))) self.assertEqual("--07", model.datatypes.xsd_repr(model.datatypes.GMonth(7))) self.assertEqual("2020-05", model.datatypes.xsd_repr(model.datatypes.GYearMonth(2020, 5))) - self.assertEqual("2020-05-05:15", model.datatypes.xsd_repr( - model.datatypes.GYearMonth(2020, 5, datetime.timezone(datetime.timedelta(hours=-5, minutes=-15))))) + self.assertEqual( + "2020-05-05:15", + model.datatypes.xsd_repr( + model.datatypes.GYearMonth(2020, 5, datetime.timezone(datetime.timedelta(hours=-5, minutes=-15))) + ), + ) self.assertEqual("--12-06", model.datatypes.xsd_repr(model.datatypes.GMonthDay(12, 6))) - self.assertEqual("--12-06+07:00", model.datatypes.xsd_repr( - model.datatypes.GMonthDay(12, 6, datetime.timezone(datetime.timedelta(hours=7))))) + self.assertEqual( + "--12-06+07:00", + model.datatypes.xsd_repr(model.datatypes.GMonthDay(12, 6, datetime.timezone(datetime.timedelta(hours=7)))), + ) self.assertEqual("---23", model.datatypes.xsd_repr(model.datatypes.GDay(23))) def test_parse_datetime(self) -> None: - self.assertEqual(datetime.datetime(2020, 1, 24, 15, 25, 17), - model.datatypes.from_xsd("2020-01-24T15:25:17", model.datatypes.DateTime)) - self.assertEqual(datetime.datetime(2020, 1, 24, 15, 25, 17, tzinfo=datetime.timezone.utc), - model.datatypes.from_xsd("2020-01-24T15:25:17Z", model.datatypes.DateTime)) - self.assertEqual(datetime.datetime(2020, 1, 24, 15, 25, 17, - tzinfo=datetime.timezone(datetime.timedelta(hours=1))), - model.datatypes.from_xsd("2020-01-24T15:25:17+01:00", model.datatypes.DateTime)) - self.assertEqual(datetime.datetime(2020, 1, 24, 15, 25, 17, - tzinfo=datetime.timezone(datetime.timedelta(minutes=-20))), - model.datatypes.from_xsd("2020-01-24T15:25:17-00:20", model.datatypes.DateTime)) + self.assertEqual( + datetime.datetime(2020, 1, 24, 15, 25, 17), + model.datatypes.from_xsd("2020-01-24T15:25:17", model.datatypes.DateTime), + ) + self.assertEqual( + datetime.datetime(2020, 1, 24, 15, 25, 17, tzinfo=datetime.timezone.utc), + model.datatypes.from_xsd("2020-01-24T15:25:17Z", model.datatypes.DateTime), + ) + self.assertEqual( + datetime.datetime(2020, 1, 24, 15, 25, 17, tzinfo=datetime.timezone(datetime.timedelta(hours=1))), + model.datatypes.from_xsd("2020-01-24T15:25:17+01:00", model.datatypes.DateTime), + ) + self.assertEqual( + datetime.datetime(2020, 1, 24, 15, 25, 17, tzinfo=datetime.timezone(datetime.timedelta(minutes=-20))), + model.datatypes.from_xsd("2020-01-24T15:25:17-00:20", model.datatypes.DateTime), + ) with self.assertRaises(ValueError) as cm: model.datatypes.from_xsd("--2020-01-24T15:25:17-00:20", model.datatypes.DateTime) self.assertEqual("--2020-01-24T15:25:17-00:20 is not a valid XSD datetime string", str(cm.exception)) @@ -233,36 +268,54 @@ def test_parse_datetime(self) -> None: model.datatypes.from_xsd("-2020-01-24T15:25:17+01:00", model.datatypes.DateTime) def test_serialize_datetime(self) -> None: - self.assertEqual("2020-01-24T15:25:17", - model.datatypes.xsd_repr(model.datatypes.DateTime(2020, 1, 24, 15, 25, 17))) - self.assertEqual("2020-01-24T15:25:17+00:00", - model.datatypes.xsd_repr( - model.datatypes.DateTime(2020, 1, 24, 15, 25, 17, tzinfo=datetime.timezone.utc))) - self.assertEqual("2020-01-24T15:25:17+01:00", - model.datatypes.xsd_repr( - model.datatypes.DateTime(2020, 1, 24, 15, 25, 17, - tzinfo=datetime.timezone(datetime.timedelta(hours=1))))) - self.assertEqual("2020-01-24T15:25:17-00:20", - model.datatypes.xsd_repr( - model.datatypes.DateTime(2020, 1, 24, 15, 25, 17, - tzinfo=datetime.timezone(datetime.timedelta(minutes=-20))))) + self.assertEqual( + "2020-01-24T15:25:17", model.datatypes.xsd_repr(model.datatypes.DateTime(2020, 1, 24, 15, 25, 17)) + ) + self.assertEqual( + "2020-01-24T15:25:17+00:00", + model.datatypes.xsd_repr(model.datatypes.DateTime(2020, 1, 24, 15, 25, 17, tzinfo=datetime.timezone.utc)), + ) + self.assertEqual( + "2020-01-24T15:25:17+01:00", + model.datatypes.xsd_repr( + model.datatypes.DateTime(2020, 1, 24, 15, 25, 17, tzinfo=datetime.timezone(datetime.timedelta(hours=1))) + ), + ) + self.assertEqual( + "2020-01-24T15:25:17-00:20", + model.datatypes.xsd_repr( + model.datatypes.DateTime( + 2020, 1, 24, 15, 25, 17, tzinfo=datetime.timezone(datetime.timedelta(minutes=-20)) + ) + ), + ) def test_parse_time(self) -> None: - self.assertEqual(datetime.time(15, 25, 17), - model.datatypes.from_xsd("15:25:17", model.datatypes.Time)) - self.assertEqual(datetime.time(15, 25, 17, tzinfo=datetime.timezone.utc), - model.datatypes.from_xsd("15:25:17Z", model.datatypes.Time)) - self.assertEqual(datetime.time(15, 25, 17, 250000, tzinfo=datetime.timezone(datetime.timedelta(hours=1))), - model.datatypes.from_xsd("15:25:17.25+01:00", model.datatypes.Time)) - self.assertEqual(datetime.time(15, 25, 17, tzinfo=datetime.timezone(datetime.timedelta(minutes=-20))), - model.datatypes.from_xsd("15:25:17-00:20", model.datatypes.Time)) + self.assertEqual(datetime.time(15, 25, 17), model.datatypes.from_xsd("15:25:17", model.datatypes.Time)) + self.assertEqual( + datetime.time(15, 25, 17, tzinfo=datetime.timezone.utc), + model.datatypes.from_xsd("15:25:17Z", model.datatypes.Time), + ) + self.assertEqual( + datetime.time(15, 25, 17, 250000, tzinfo=datetime.timezone(datetime.timedelta(hours=1))), + model.datatypes.from_xsd("15:25:17.25+01:00", model.datatypes.Time), + ) + self.assertEqual( + datetime.time(15, 25, 17, tzinfo=datetime.timezone(datetime.timedelta(minutes=-20))), + model.datatypes.from_xsd("15:25:17-00:20", model.datatypes.Time), + ) def test_serialize_time(self) -> None: self.assertEqual("15:25:17", model.datatypes.xsd_repr(datetime.time(15, 25, 17))) - self.assertEqual("15:25:17+00:00", model.datatypes.xsd_repr( - datetime.time(15, 25, 17, tzinfo=datetime.timezone.utc))) - self.assertEqual("15:25:17.250000+01:00", model.datatypes.xsd_repr( - datetime.time(15, 25, 17, 250000, tzinfo=datetime.timezone(datetime.timedelta(hours=1))))) + self.assertEqual( + "15:25:17+00:00", model.datatypes.xsd_repr(datetime.time(15, 25, 17, tzinfo=datetime.timezone.utc)) + ) + self.assertEqual( + "15:25:17.250000+01:00", + model.datatypes.xsd_repr( + datetime.time(15, 25, 17, 250000, tzinfo=datetime.timezone(datetime.timedelta(hours=1))) + ), + ) def test_parse_datetime_midnight_24(self) -> None: res = model.datatypes.from_xsd("2020-01-24T24:00:00", model.datatypes.DateTime) @@ -273,9 +326,7 @@ def test_parse_datetime_midnight_24(self) -> None: def test_parse_datetime_midnight_24_invalid(self) -> None: with self.assertRaises(ValueError) as cm: model.datatypes.from_xsd("2020-01-24T24:01:00", model.datatypes.DateTime) - self.assertEqual( - "2020-01-24T24:01:00 is not a valid xsd:datetime.", - str(cm.exception)) + self.assertEqual("2020-01-24T24:01:00 is not a valid xsd:datetime.", str(cm.exception)) def test_parse_time_midnight_24(self) -> None: res = model.datatypes.from_xsd("24:00:00", model.datatypes.Time) @@ -284,10 +335,7 @@ def test_parse_time_midnight_24(self) -> None: def test_parse_time_midnight_24_invalid(self) -> None: with self.assertRaises(ValueError) as cm: model.datatypes.from_xsd("24:00:01", model.datatypes.Time) - self.assertEqual( - "24:00:01 is not a valid xsd:time.", - str(cm.exception) - ) + self.assertEqual("24:00:01 is not a valid xsd:time.", str(cm.exception)) def test_trivial_cast(self) -> None: val = model.datatypes.trivial_cast(datetime.date(2017, 11, 13), model.datatypes.Date) diff --git a/sdk/test/model/test_provider.py b/sdk/test/model/test_provider.py index 50e19c0da..70686047e 100644 --- a/sdk/test/model/test_provider.py +++ b/sdk/test/model/test_provider.py @@ -15,9 +15,11 @@ class ProvidersTest(unittest.TestCase): def setUp(self) -> None: self.aas1 = model.AssetAdministrationShell( - model.AssetInformation(global_asset_id="http://example.org/TestAsset1/"), "urn:x-test:aas1") + model.AssetInformation(global_asset_id="http://example.org/TestAsset1/"), "urn:x-test:aas1" + ) self.aas2 = model.AssetAdministrationShell( - model.AssetInformation(global_asset_id="http://example.org/TestAsset2/"), "urn:x-test:aas2") + model.AssetInformation(global_asset_id="http://example.org/TestAsset2/"), "urn:x-test:aas2" + ) self.submodel1 = model.Submodel("urn:x-test:submodel1") self.submodel2 = model.Submodel("urn:x-test:submodel2") @@ -33,14 +35,17 @@ def test_store_retrieve(self) -> None: self.assertIn(self.aas1, store) self.assertIn("urn:x-test:aas1", store) self.assertNotIn("urn:x-test:missing", store) - property = model.Property('test', model.datatypes.String) + property = model.Property("test", model.datatypes.String) self.assertFalse(property in store) aas3 = model.AssetAdministrationShell( - model.AssetInformation(global_asset_id="http://example.org/TestAsset/"), "urn:x-test:aas1") + model.AssetInformation(global_asset_id="http://example.org/TestAsset/"), "urn:x-test:aas1" + ) with self.assertRaises(KeyError) as cm: store.add(aas3) - self.assertEqual("'Identifiable object with same id urn:x-test:aas1 is already " - "stored in this store'", str(cm.exception)) + self.assertEqual( + "'Identifiable object with same id urn:x-test:aas1 is already stored in this store'", + str(cm.exception), + ) self.assertEqual(2, len(store)) self.assertIs(self.aas1, store.get_item("urn:x-test:aas1")) self.assertIs(self.aas1, store.get("urn:x-test:aas1")) @@ -94,9 +99,7 @@ def test_store_remove(self) -> None: store.remove(self.aas1) def test_provider_multiplexer(self) -> None: - aas_identifiable_store: model.DictIdentifiableStore[model.Identifiable] = ( - model.DictIdentifiableStore() - ) + 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() diff --git a/sdk/test/model/test_string_constraints.py b/sdk/test/model/test_string_constraints.py index 4fe5c9f8b..7b43ae3ee 100644 --- a/sdk/test/model/test_string_constraints.py +++ b/sdk/test/model/test_string_constraints.py @@ -36,8 +36,9 @@ def test_version_type(self) -> None: version = "0" * 4 with self.assertRaises(ValueError) as cm: _string_constraints.check_version_type(version) - self.assertEqual("VersionType must match the pattern '([0-9]|[1-9][0-9]*)'! (value: '0000')", - cm.exception.args[0]) + self.assertEqual( + "VersionType must match the pattern '([0-9]|[1-9][0-9]*)'! (value: '0000')", cm.exception.args[0] + ) version = "0" _string_constraints.check_version_type(version) @@ -45,18 +46,27 @@ def test_aasd_130(self) -> None: name: model.NameType = "\0" with self.assertRaises(ValueError) as cm: _string_constraints.check_name_type(name) - self.assertEqual(r"Every string must match the pattern '[\t\n\r -\ud7ff\ue000-\ufffd\U00010000-\U0010ffff]*'! " - r"(value: '\x00')", cm.exception.args[0]) + self.assertEqual( + r"Every string must match the pattern '[\t\n\r -\ud7ff\ue000-\ufffd\U00010000-\U0010ffff]*'! " + r"(value: '\x00')", + cm.exception.args[0], + ) name = "\ud800" with self.assertRaises(ValueError) as cm: _string_constraints.check_name_type(name) - self.assertEqual(r"Every string must match the pattern '[\t\n\r -\ud7ff\ue000-\ufffd\U00010000-\U0010ffff]*'! " - r"(value: '\ud800')", cm.exception.args[0]) + self.assertEqual( + r"Every string must match the pattern '[\t\n\r -\ud7ff\ue000-\ufffd\U00010000-\U0010ffff]*'! " + r"(value: '\ud800')", + cm.exception.args[0], + ) name = "\ufffe" with self.assertRaises(ValueError) as cm: _string_constraints.check_name_type(name) - self.assertEqual(r"Every string must match the pattern '[\t\n\r -\ud7ff\ue000-\ufffd\U00010000-\U0010ffff]*'! " - r"(value: '\ufffe')", cm.exception.args[0]) + self.assertEqual( + r"Every string must match the pattern '[\t\n\r -\ud7ff\ue000-\ufffd\U00010000-\U0010ffff]*'! " + r"(value: '\ufffe')", + cm.exception.args[0], + ) name = "this\ris\na\tvalid täst\uffdd\U0010ab12" _string_constraints.check_name_type(name) @@ -87,15 +97,19 @@ def test_ignore_none_values(self) -> None: def test_attribute_name_conflict(self) -> None: # We don't want to overwrite existing attributes in case of a name conflict with self.assertRaises(AttributeError) as cm: + @_string_constraints.constrain_revision_type("foo") class DummyClass: foo = property() + self.assertEqual("DummyClass already has an attribute named 'foo'", cm.exception.args[0]) with self.assertRaises(AttributeError) as cm: + @_string_constraints.constrain_label_type("bar") class DummyClass2: @property def bar(self): return "baz" + self.assertEqual("DummyClass2 already has an attribute named 'bar'", cm.exception.args[0]) diff --git a/sdk/test/model/test_submodel.py b/sdk/test/model/test_submodel.py index b5ee0d7dd..91cc77c9f 100644 --- a/sdk/test/model/test_submodel.py +++ b/sdk/test/model/test_submodel.py @@ -15,68 +15,105 @@ class EntityTest(unittest.TestCase): def test_aasd_014_init_self_managed(self) -> None: with self.assertRaises(model.AASConstraintViolation) as cm: model.Entity("TestEntity", model.EntityType.SELF_MANAGED_ENTITY) - self.assertEqual("A self-managed entity has to have a globalAssetId or a specificAssetId (Constraint AASd-014)", - str(cm.exception)) - model.Entity("TestEntity", model.EntityType.SELF_MANAGED_ENTITY, - global_asset_id="https://example.org/TestAsset") - model.Entity("TestEntity", model.EntityType.SELF_MANAGED_ENTITY, - specific_asset_id=(model.SpecificAssetId("test", "test"),)) - model.Entity("TestEntity", model.EntityType.SELF_MANAGED_ENTITY, - global_asset_id="https://example.org/TestAsset", - specific_asset_id=(model.SpecificAssetId("test", "test"),)) + self.assertEqual( + "A self-managed entity has to have a globalAssetId or a specificAssetId (Constraint AASd-014)", + str(cm.exception), + ) + model.Entity( + "TestEntity", model.EntityType.SELF_MANAGED_ENTITY, global_asset_id="https://example.org/TestAsset" + ) + model.Entity( + "TestEntity", + model.EntityType.SELF_MANAGED_ENTITY, + specific_asset_id=(model.SpecificAssetId("test", "test"),), + ) + model.Entity( + "TestEntity", + model.EntityType.SELF_MANAGED_ENTITY, + global_asset_id="https://example.org/TestAsset", + specific_asset_id=(model.SpecificAssetId("test", "test"),), + ) def test_aasd_014_init_co_managed(self) -> None: model.Entity("TestEntity", model.EntityType.CO_MANAGED_ENTITY) with self.assertRaises(model.AASConstraintViolation) as cm: - model.Entity("TestEntity", model.EntityType.CO_MANAGED_ENTITY, - global_asset_id="https://example.org/TestAsset") - self.assertEqual("A co-managed entity has to have neither a globalAssetId nor a specificAssetId " - "(Constraint AASd-014)", str(cm.exception)) + model.Entity( + "TestEntity", model.EntityType.CO_MANAGED_ENTITY, global_asset_id="https://example.org/TestAsset" + ) + self.assertEqual( + "A co-managed entity has to have neither a globalAssetId nor a specificAssetId (Constraint AASd-014)", + str(cm.exception), + ) with self.assertRaises(model.AASConstraintViolation) as cm: - model.Entity("TestEntity", model.EntityType.CO_MANAGED_ENTITY, - specific_asset_id=(model.SpecificAssetId("test", "test"),)) - self.assertEqual("A co-managed entity has to have neither a globalAssetId nor a specificAssetId " - "(Constraint AASd-014)", str(cm.exception)) + model.Entity( + "TestEntity", + model.EntityType.CO_MANAGED_ENTITY, + specific_asset_id=(model.SpecificAssetId("test", "test"),), + ) + self.assertEqual( + "A co-managed entity has to have neither a globalAssetId nor a specificAssetId (Constraint AASd-014)", + str(cm.exception), + ) with self.assertRaises(model.AASConstraintViolation) as cm: - model.Entity("TestEntity", model.EntityType.CO_MANAGED_ENTITY, - global_asset_id="https://example.org/TestAsset", - specific_asset_id=(model.SpecificAssetId("test", "test"),)) - self.assertEqual("A co-managed entity has to have neither a globalAssetId nor a specificAssetId " - "(Constraint AASd-014)", str(cm.exception)) + model.Entity( + "TestEntity", + model.EntityType.CO_MANAGED_ENTITY, + global_asset_id="https://example.org/TestAsset", + specific_asset_id=(model.SpecificAssetId("test", "test"),), + ) + self.assertEqual( + "A co-managed entity has to have neither a globalAssetId nor a specificAssetId (Constraint AASd-014)", + str(cm.exception), + ) def test_aasd_014_set_self_managed(self) -> None: - entity = model.Entity("TestEntity", model.EntityType.SELF_MANAGED_ENTITY, - global_asset_id="https://example.org/TestAsset", - specific_asset_id=(model.SpecificAssetId("test", "test"),)) + entity = model.Entity( + "TestEntity", + model.EntityType.SELF_MANAGED_ENTITY, + global_asset_id="https://example.org/TestAsset", + specific_asset_id=(model.SpecificAssetId("test", "test"),), + ) entity.global_asset_id = None with self.assertRaises(model.AASConstraintViolation) as cm: entity.specific_asset_id = model.ConstrainedList(()) - self.assertEqual("A self-managed entity has to have a globalAssetId or a specificAssetId (Constraint AASd-014)", - str(cm.exception)) + self.assertEqual( + "A self-managed entity has to have a globalAssetId or a specificAssetId (Constraint AASd-014)", + str(cm.exception), + ) - entity = model.Entity("TestEntity", model.EntityType.SELF_MANAGED_ENTITY, - global_asset_id="https://example.org/TestAsset", - specific_asset_id=(model.SpecificAssetId("test", "test"),)) + entity = model.Entity( + "TestEntity", + model.EntityType.SELF_MANAGED_ENTITY, + global_asset_id="https://example.org/TestAsset", + specific_asset_id=(model.SpecificAssetId("test", "test"),), + ) entity.specific_asset_id = model.ConstrainedList(()) with self.assertRaises(model.AASConstraintViolation) as cm: entity.global_asset_id = None - self.assertEqual("A self-managed entity has to have a globalAssetId or a specificAssetId (Constraint AASd-014)", - str(cm.exception)) + self.assertEqual( + "A self-managed entity has to have a globalAssetId or a specificAssetId (Constraint AASd-014)", + str(cm.exception), + ) def test_aasd_014_set_co_managed(self) -> None: entity = model.Entity("TestEntity", model.EntityType.CO_MANAGED_ENTITY) with self.assertRaises(model.AASConstraintViolation) as cm: entity.global_asset_id = "https://example.org/TestAsset" - self.assertEqual("A co-managed entity has to have neither a globalAssetId nor a specificAssetId " - "(Constraint AASd-014)", str(cm.exception)) + self.assertEqual( + "A co-managed entity has to have neither a globalAssetId nor a specificAssetId (Constraint AASd-014)", + str(cm.exception), + ) with self.assertRaises(model.AASConstraintViolation) as cm: entity.specific_asset_id = model.ConstrainedList((model.SpecificAssetId("test", "test"),)) - self.assertEqual("A co-managed entity has to have neither a globalAssetId nor a specificAssetId " - "(Constraint AASd-014)", str(cm.exception)) + self.assertEqual( + "A co-managed entity has to have neither a globalAssetId nor a specificAssetId (Constraint AASd-014)", + str(cm.exception), + ) def test_aasd_014_specific_asset_id_add_self_managed(self) -> None: - entity = model.Entity("TestEntity", model.EntityType.SELF_MANAGED_ENTITY, - global_asset_id="https://example.org/TestAsset") + entity = model.Entity( + "TestEntity", model.EntityType.SELF_MANAGED_ENTITY, global_asset_id="https://example.org/TestAsset" + ) specific_asset_id1 = model.SpecificAssetId("test", "test") specific_asset_id2 = model.SpecificAssetId("test", "test") entity.specific_asset_id.append(specific_asset_id1) @@ -88,20 +125,29 @@ def test_aasd_014_specific_asset_id_add_co_managed(self) -> None: entity = model.Entity("TestEntity", model.EntityType.CO_MANAGED_ENTITY) with self.assertRaises(model.AASConstraintViolation) as cm: entity.specific_asset_id.append(model.SpecificAssetId("test", "test")) - self.assertEqual("A co-managed entity has to have neither a globalAssetId nor a specificAssetId " - "(Constraint AASd-014)", str(cm.exception)) + self.assertEqual( + "A co-managed entity has to have neither a globalAssetId nor a specificAssetId (Constraint AASd-014)", + str(cm.exception), + ) with self.assertRaises(model.AASConstraintViolation) as cm: entity.specific_asset_id.extend((model.SpecificAssetId("test", "test"),)) - self.assertEqual("A co-managed entity has to have neither a globalAssetId nor a specificAssetId " - "(Constraint AASd-014)", str(cm.exception)) + self.assertEqual( + "A co-managed entity has to have neither a globalAssetId nor a specificAssetId (Constraint AASd-014)", + str(cm.exception), + ) def test_assd_014_specific_asset_id_set_self_managed(self) -> None: - entity = model.Entity("TestEntity", model.EntityType.SELF_MANAGED_ENTITY, - specific_asset_id=(model.SpecificAssetId("test", "test"),)) + entity = model.Entity( + "TestEntity", + model.EntityType.SELF_MANAGED_ENTITY, + specific_asset_id=(model.SpecificAssetId("test", "test"),), + ) with self.assertRaises(model.AASConstraintViolation) as cm: entity.specific_asset_id[:] = () - self.assertEqual("A self-managed entity has to have a globalAssetId or a specificAssetId (Constraint AASd-014)", - str(cm.exception)) + self.assertEqual( + "A self-managed entity has to have a globalAssetId or a specificAssetId (Constraint AASd-014)", + str(cm.exception), + ) specific_asset_id = model.SpecificAssetId("test", "test") self.assertIsNot(entity.specific_asset_id[0], specific_asset_id) entity.specific_asset_id[:] = (specific_asset_id,) @@ -113,30 +159,40 @@ def test_assd_014_specific_asset_id_set_co_managed(self) -> None: entity = model.Entity("TestEntity", model.EntityType.CO_MANAGED_ENTITY) with self.assertRaises(model.AASConstraintViolation) as cm: entity.specific_asset_id[:] = (model.SpecificAssetId("test", "test"),) - self.assertEqual("A co-managed entity has to have neither a globalAssetId nor a specificAssetId " - "(Constraint AASd-014)", str(cm.exception)) + self.assertEqual( + "A co-managed entity has to have neither a globalAssetId nor a specificAssetId (Constraint AASd-014)", + str(cm.exception), + ) entity.specific_asset_id[:] = () def test_aasd_014_specific_asset_id_del_self_managed(self) -> None: specific_asset_id = model.SpecificAssetId("test", "test") - entity = model.Entity("TestEntity", model.EntityType.SELF_MANAGED_ENTITY, - specific_asset_id=(model.SpecificAssetId("test", "test"), - specific_asset_id)) + entity = model.Entity( + "TestEntity", + model.EntityType.SELF_MANAGED_ENTITY, + specific_asset_id=(model.SpecificAssetId("test", "test"), specific_asset_id), + ) with self.assertRaises(model.AASConstraintViolation) as cm: del entity.specific_asset_id[:] - self.assertEqual("A self-managed entity has to have a globalAssetId or a specificAssetId (Constraint AASd-014)", - str(cm.exception)) + self.assertEqual( + "A self-managed entity has to have a globalAssetId or a specificAssetId (Constraint AASd-014)", + str(cm.exception), + ) with self.assertRaises(model.AASConstraintViolation) as cm: entity.specific_asset_id.clear() - self.assertEqual("A self-managed entity has to have a globalAssetId or a specificAssetId (Constraint AASd-014)", - str(cm.exception)) + self.assertEqual( + "A self-managed entity has to have a globalAssetId or a specificAssetId (Constraint AASd-014)", + str(cm.exception), + ) self.assertIsNot(entity.specific_asset_id[0], specific_asset_id) del entity.specific_asset_id[0] self.assertIs(entity.specific_asset_id[0], specific_asset_id) with self.assertRaises(model.AASConstraintViolation) as cm: del entity.specific_asset_id[0] - self.assertEqual("A self-managed entity has to have a globalAssetId or a specificAssetId (Constraint AASd-014)", - str(cm.exception)) + self.assertEqual( + "A self-managed entity has to have a globalAssetId or a specificAssetId (Constraint AASd-014)", + str(cm.exception), + ) def test_aasd_014_specific_asset_id_del_co_managed(self) -> None: entity = model.Entity("TestEntity", model.EntityType.CO_MANAGED_ENTITY) @@ -145,7 +201,7 @@ def test_aasd_014_specific_asset_id_del_co_managed(self) -> None: class PropertyTest(unittest.TestCase): def test_set_value(self): - property = model.Property('test', model.datatypes.Int, 2) + property = model.Property("test", model.datatypes.Int, 2) self.assertEqual(property.value, 2) property.value = None self.assertIsNone(property.value) @@ -153,7 +209,7 @@ def test_set_value(self): class RangeTest(unittest.TestCase): def test_set_min_max(self): - range = model.Range('test', model.datatypes.Int, 2, 5) + range = model.Range("test", model.datatypes.Int, 2, 5) self.assertEqual(range.min, 2) self.assertEqual(range.max, 5) range.min = None @@ -165,42 +221,64 @@ def test_set_min_max(self): class SubmodelElementListTest(unittest.TestCase): def test_constraints(self): # AASd-107 - mlp = model.MultiLanguageProperty(None, semantic_id=model.ExternalReference( - (model.Key(model.KeyTypes.GLOBAL_REFERENCE, "urn:x-test:invalid"),) - )) + mlp = model.MultiLanguageProperty( + None, + semantic_id=model.ExternalReference((model.Key(model.KeyTypes.GLOBAL_REFERENCE, "urn:x-test:invalid"),)), + ) with self.assertRaises(model.AASConstraintViolation) as cm: - model.SubmodelElementList("test_list", model.MultiLanguageProperty, {mlp}, - semantic_id_list_element=model.ExternalReference(( - model.Key(model.KeyTypes.GLOBAL_REFERENCE, "urn:x-test:test"),))) - self.assertEqual("If semantic_id_list_element=ExternalReference(key=(Key(type=GLOBAL_REFERENCE, " - "value=urn:x-test:test),)) is specified all first level children must have " - "the same semantic_id, got MultiLanguageProperty with " - "semantic_id=ExternalReference(key=(Key(type=GLOBAL_REFERENCE, value=urn:x-test:invalid),)) " - "(Constraint AASd-107)", str(cm.exception)) - sel = model.SubmodelElementList("test_list", model.MultiLanguageProperty, {mlp}, - semantic_id_list_element=model.ExternalReference(( - model.Key(model.KeyTypes.GLOBAL_REFERENCE, "urn:x-test:invalid"),))) + model.SubmodelElementList( + "test_list", + model.MultiLanguageProperty, + {mlp}, + semantic_id_list_element=model.ExternalReference( + (model.Key(model.KeyTypes.GLOBAL_REFERENCE, "urn:x-test:test"),) + ), + ) + self.assertEqual( + "If semantic_id_list_element=ExternalReference(key=(Key(type=GLOBAL_REFERENCE, " + "value=urn:x-test:test),)) is specified all first level children must have " + "the same semantic_id, got MultiLanguageProperty with " + "semantic_id=ExternalReference(key=(Key(type=GLOBAL_REFERENCE, value=urn:x-test:invalid),)) " + "(Constraint AASd-107)", + str(cm.exception), + ) + sel = model.SubmodelElementList( + "test_list", + model.MultiLanguageProperty, + {mlp}, + semantic_id_list_element=model.ExternalReference( + (model.Key(model.KeyTypes.GLOBAL_REFERENCE, "urn:x-test:invalid"),) + ), + ) sel.value.clear() model.SubmodelElementList("test_list", model.MultiLanguageProperty, {mlp}, semantic_id_list_element=None) mlp = model.MultiLanguageProperty(None) - model.SubmodelElementList("test_list", model.MultiLanguageProperty, {mlp}, - semantic_id_list_element=model.ExternalReference(( - model.Key(model.KeyTypes.GLOBAL_REFERENCE, "urn:x-test:invalid"),))) + model.SubmodelElementList( + "test_list", + model.MultiLanguageProperty, + {mlp}, + semantic_id_list_element=model.ExternalReference( + (model.Key(model.KeyTypes.GLOBAL_REFERENCE, "urn:x-test:invalid"),) + ), + ) # AASd-108 are = model.AnnotatedRelationshipElement( None, model.ExternalReference((model.Key(model.KeyTypes.GLOBAL_REFERENCE, "urn:x-test:test-first"),)), - model.ExternalReference((model.Key(model.KeyTypes.GLOBAL_REFERENCE, "urn:x-test:test-second"),)) + model.ExternalReference((model.Key(model.KeyTypes.GLOBAL_REFERENCE, "urn:x-test:test-second"),)), ) # This tests checks if subclasses of the required type are rejected in a SubmodelElementList. # Thus, a requirement is that AnnotatedRelationshipElement is a subclass of RelationshipElement: self.assertIsInstance(are, model.RelationshipElement) with self.assertRaises(model.AASConstraintViolation) as cm: model.SubmodelElementList("test_list", model.RelationshipElement, {are}) - self.assertEqual("All first level elements must be of the type specified in " - "type_value_list_element=RelationshipElement, got AnnotatedRelationshipElement " - "(Constraint AASd-108)", str(cm.exception)) + self.assertEqual( + "All first level elements must be of the type specified in " + "type_value_list_element=RelationshipElement, got AnnotatedRelationshipElement " + "(Constraint AASd-108)", + str(cm.exception), + ) model.SubmodelElementList("test_list", model.AnnotatedRelationshipElement, {are}) # AASd-109 @@ -208,15 +286,20 @@ def test_constraints(self): prop = model.Property(None, model.datatypes.Int, 0) with self.assertRaises(model.AASConstraintViolation) as cm: model.SubmodelElementList("test_list", model.Property, [prop]) - self.assertEqual("type_value_list_element=Property, but value_type_list_element is not set! " - "(Constraint AASd-109)", str(cm.exception)) + self.assertEqual( + "type_value_list_element=Property, but value_type_list_element is not set! (Constraint AASd-109)", + str(cm.exception), + ) model.SubmodelElementList("test_list", model.Property, [prop], value_type_list_element=model.datatypes.Int) prop = model.Property(None, model.datatypes.String) with self.assertRaises(model.AASConstraintViolation) as cm: model.SubmodelElementList("test_list", model.Property, {prop}, value_type_list_element=model.datatypes.Int) - self.assertEqual("All first level elements must have the value_type specified by value_type_list_element=Int, " - "got Property with value_type=str (Constraint AASd-109)", str(cm.exception)) + self.assertEqual( + "All first level elements must have the value_type specified by value_type_list_element=Int, " + "got Property with value_type=str (Constraint AASd-109)", + str(cm.exception), + ) model.SubmodelElementList("test_list", model.Property, {prop}, value_type_list_element=model.datatypes.String) # AASd-114 @@ -226,11 +309,14 @@ def test_constraints(self): mlp2 = model.MultiLanguageProperty(None, semantic_id=semantic_id2) with self.assertRaises(model.AASConstraintViolation) as cm: model.SubmodelElementList("test_list", model.MultiLanguageProperty, [mlp1, mlp2]) - self.assertEqual("Element to be added MultiLanguageProperty has semantic_id " - "ExternalReference(key=(Key(type=GLOBAL_REFERENCE, value=urn:x-test:different),)), " - "while already contained element MultiLanguageProperty[test_list[0]] has semantic_id " - "ExternalReference(key=(Key(type=GLOBAL_REFERENCE, value=urn:x-test:test),)), " - "which aren't equal. (Constraint AASd-114)", str(cm.exception)) + self.assertEqual( + "Element to be added MultiLanguageProperty has semantic_id " + "ExternalReference(key=(Key(type=GLOBAL_REFERENCE, value=urn:x-test:different),)), " + "while already contained element MultiLanguageProperty[test_list[0]] has semantic_id " + "ExternalReference(key=(Key(type=GLOBAL_REFERENCE, value=urn:x-test:test),)), " + "which aren't equal. (Constraint AASd-114)", + str(cm.exception), + ) mlp2.semantic_id = semantic_id1 model.SubmodelElementList("test_list", model.MultiLanguageProperty, [mlp1, mlp2]) @@ -241,27 +327,39 @@ def test_aasd_108_add_set(self): list_ = model.SubmodelElementList("test_list", model.MultiLanguageProperty) with self.assertRaises(model.AASConstraintViolation) as cm: list_.add_referable(prop) - self.assertEqual("All first level elements must be of the type specified in type_value_list_element=" - "MultiLanguageProperty, got Property (Constraint AASd-108)", str(cm.exception)) + self.assertEqual( + "All first level elements must be of the type specified in type_value_list_element=" + "MultiLanguageProperty, got Property (Constraint AASd-108)", + str(cm.exception), + ) list_.add_referable(mlp1) with self.assertRaises(model.AASConstraintViolation) as cm: list_.value.add(prop) - self.assertEqual("All first level elements must be of the type specified in type_value_list_element=" - "MultiLanguageProperty, got Property (Constraint AASd-108)", str(cm.exception)) + self.assertEqual( + "All first level elements must be of the type specified in type_value_list_element=" + "MultiLanguageProperty, got Property (Constraint AASd-108)", + str(cm.exception), + ) list_.value.add(mlp2) with self.assertRaises(model.AASConstraintViolation) as cm: list_.value[0] = prop - self.assertEqual("All first level elements must be of the type specified in type_value_list_element=" - "MultiLanguageProperty, got Property (Constraint AASd-108)", str(cm.exception)) + self.assertEqual( + "All first level elements must be of the type specified in type_value_list_element=" + "MultiLanguageProperty, got Property (Constraint AASd-108)", + str(cm.exception), + ) del list_.value[1] list_.value[0] = mlp2 with self.assertRaises(model.AASConstraintViolation) as cm: list_.value = [mlp1, prop] - self.assertEqual("All first level elements must be of the type specified in type_value_list_element=" - "MultiLanguageProperty, got Property (Constraint AASd-108)", str(cm.exception)) + self.assertEqual( + "All first level elements must be of the type specified in type_value_list_element=" + "MultiLanguageProperty, got Property (Constraint AASd-108)", + str(cm.exception), + ) list_.value = [mlp1, mlp2] def test_immutable_attributes(self): @@ -279,21 +377,27 @@ def test_immutable_attributes(self): class BasicEventElementTest(unittest.TestCase): def test_constraints(self): with self.assertRaises(ValueError) as cm: - model.BasicEventElement("test_basic_event_element", - model.ModelReference((model.Key(model.KeyTypes.ASSET_ADMINISTRATION_SHELL, - "urn:x-test:AssetAdministrationShell"),), - model.AssetAdministrationShell), - model.Direction.INPUT, - model.StateOfEvent.ON, - max_interval=model.datatypes.Duration(minutes=10)) + model.BasicEventElement( + "test_basic_event_element", + model.ModelReference( + (model.Key(model.KeyTypes.ASSET_ADMINISTRATION_SHELL, "urn:x-test:AssetAdministrationShell"),), + model.AssetAdministrationShell, + ), + model.Direction.INPUT, + model.StateOfEvent.ON, + max_interval=model.datatypes.Duration(minutes=10), + ) self.assertEqual("max_interval is not applicable if direction = input!", str(cm.exception)) - bee = model.BasicEventElement("test_basic_event_element", - model.ModelReference((model.Key(model.KeyTypes.ASSET_ADMINISTRATION_SHELL, - "urn:x-test:AssetAdministrationShell"),), - model.AssetAdministrationShell), - model.Direction.OUTPUT, - model.StateOfEvent.ON, - max_interval=model.datatypes.Duration(minutes=10)) + bee = model.BasicEventElement( + "test_basic_event_element", + model.ModelReference( + (model.Key(model.KeyTypes.ASSET_ADMINISTRATION_SHELL, "urn:x-test:AssetAdministrationShell"),), + model.AssetAdministrationShell, + ), + model.Direction.OUTPUT, + model.StateOfEvent.ON, + max_interval=model.datatypes.Duration(minutes=10), + ) with self.assertRaises(ValueError) as cm: bee.direction = model.Direction.INPUT self.assertEqual("max_interval is not applicable if direction = input!", str(cm.exception)) @@ -301,8 +405,9 @@ def test_constraints(self): bee.max_interval = None bee.direction = model.Direction.INPUT - timestamp_tzinfo = model.datatypes.DateTime(2022, 11, 13, 23, 45, 30, 123456, - dateutil.tz.gettz("Europe/Berlin")) + timestamp_tzinfo = model.datatypes.DateTime( + 2022, 11, 13, 23, 45, 30, 123456, dateutil.tz.gettz("Europe/Berlin") + ) with self.assertRaises(ValueError) as cm: bee.last_update = timestamp_tzinfo self.assertEqual("last_update must be specified in UTC!", str(cm.exception)) diff --git a/sdk/test/util/test_identification.py b/sdk/test/util/test_identification.py index 36019ede2..c4a25a850 100644 --- a/sdk/test/util/test_identification.py +++ b/sdk/test/util/test_identification.py @@ -15,8 +15,9 @@ class IdentifierGeneratorTest(unittest.TestCase): def test_generate_uuid_identifier(self): generator = UUIDGenerator() identification = generator.generate_id() - self.assertRegex(identification, - r"urn:uuid:[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}") + self.assertRegex( + identification, r"urn:uuid:[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}" + ) ids = set() for i in range(100): identification = generator.generate_id() @@ -29,10 +30,10 @@ def test_generate_iri_identifier(self): # Check expected Errors when Namespaces are not valid with self.assertRaises(ValueError) as cm: generator = NamespaceIRIGenerator("", provider) - self.assertEqual('Namespace must be a valid IRI, ending with #, / or =', str(cm.exception)) + self.assertEqual("Namespace must be a valid IRI, ending with #, / or =", str(cm.exception)) with self.assertRaises(ValueError) as cm: generator = NamespaceIRIGenerator("http", provider) - self.assertEqual('Namespace must be a valid IRI, ending with #, / or =', str(cm.exception)) + self.assertEqual("Namespace must be a valid IRI, ending with #, / or =", str(cm.exception)) generator = NamespaceIRIGenerator("http://example.org/AAS/", provider) self.assertEqual("http://example.org/AAS/", generator.namespace) diff --git a/sdk/test/util/test_traversal.py b/sdk/test/util/test_traversal.py index 881d7f7dc..8cfad0d7e 100644 --- a/sdk/test/util/test_traversal.py +++ b/sdk/test/util/test_traversal.py @@ -31,8 +31,9 @@ def test_collection_traversal(self): def test_list_traversal(self): child = model.Property(None, model.datatypes.String) - sml = model.SubmodelElementList("sml", type_value_list_element=model.Property, - value_type_list_element=model.datatypes.String, value=[child]) + sml = model.SubmodelElementList( + "sml", type_value_list_element=model.Property, value_type_list_element=model.datatypes.String, value=[child] + ) sm = self._submodel(sml) result = list(walk_submodel(sm)) self.assertCountEqual([child, sml], result) @@ -54,8 +55,7 @@ def test_operation_variables_traversal(self): in_var = model.Property("in_var", model.datatypes.String) out_var = model.Property("out_var", model.datatypes.String) inout_var = model.Property("inout_var", model.datatypes.String) - op = model.Operation("op", input_variable=[in_var], output_variable=[out_var], - in_output_variable=[inout_var]) + op = model.Operation("op", input_variable=[in_var], output_variable=[out_var], in_output_variable=[inout_var]) sm = self._submodel(op) result = list(walk_submodel(sm)) self.assertCountEqual([in_var, out_var, inout_var, op], result) diff --git a/server/app/adapter/jsonization.py b/server/app/adapter/jsonization.py index 897590c77..8cb6da1bf 100644 --- a/server/app/adapter/jsonization.py +++ b/server/app/adapter/jsonization.py @@ -208,7 +208,6 @@ class ServerStrictStrippedAASFromJsonDecoder(ServerStrictAASFromJsonDecoder, Ser class ServerAASToJsonEncoder(AASToJsonEncoder): - @classmethod def _get_aas_class_serializers(cls) -> Dict[Type, Callable]: serializers = super()._get_aas_class_serializers() diff --git a/server/app/interfaces/base.py b/server/app/interfaces/base.py index d32312370..c6bf6d11f 100644 --- a/server/app/interfaces/base.py +++ b/server/app/interfaces/base.py @@ -90,8 +90,12 @@ def __init__(self, cursor: Optional[str] = None): class APIResponse(abc.ABC, Response): @abc.abstractmethod def __init__( - self, obj: Optional[ResponseData] = None, paging_metadata: Optional[PagingMetadata] = None, - stripped: bool = False, *args, **kwargs + self, + obj: Optional[ResponseData] = None, + paging_metadata: Optional[PagingMetadata] = None, + stripped: bool = False, + *args, + **kwargs, ): super().__init__(*args, **kwargs) if obj is None: @@ -229,8 +233,10 @@ def _get_slice(cls, request: Request, iterator: Iterable[T]) -> Tuple[Iterator[T limit_str = request.args.get("limit", default="100") cursor_str = request.args.get("cursor", default="1") try: - limit, cursor = (NonNegativeInteger(int(limit_str)), - NonNegativeInteger(int(cursor_str) - 1)) # cursor is 1-indexed + limit, cursor = ( + NonNegativeInteger(int(limit_str)), + NonNegativeInteger(int(cursor_str) - 1), + ) # cursor is 1-indexed except ValueError: raise BadRequest("Limit can not be negative, cursor must be positive!") start_index = cursor diff --git a/server/app/interfaces/discovery.py b/server/app/interfaces/discovery.py index a340a0e12..3f290241d 100644 --- a/server/app/interfaces/discovery.py +++ b/server/app/interfaces/discovery.py @@ -19,10 +19,12 @@ from app.util.converters import IdentifierToBase64URLConverter, base64url_decode from app.model import ServiceSpecificationProfileEnum, ServiceDescription -SUPPORTED_PROFILES: ServiceDescription = ServiceDescription([ - ServiceSpecificationProfileEnum.DISCOVERY_FULL, - ServiceSpecificationProfileEnum.DISCOVERY_READ, -]) +SUPPORTED_PROFILES: ServiceDescription = ServiceDescription( + [ + ServiceSpecificationProfileEnum.DISCOVERY_FULL, + ServiceSpecificationProfileEnum.DISCOVERY_READ, + ] +) class DiscoveryStore: @@ -106,10 +108,7 @@ def to_file(self, filename: str) -> None: corrupting the existing store if serialization fails. """ data = { - "aas_id_to_asset_ids": { - aas_id: list(asset_ids) - for aas_id, asset_ids in self.aas_id_to_asset_ids.items() - } + "aas_id_to_asset_ids": {aas_id: list(asset_ids) for aas_id, asset_ids in self.aas_id_to_asset_ids.items()} } temp_filename = f"{filename}.tmp" @@ -164,7 +163,7 @@ def get_description(self, request: Request, url_args: Dict, response_t: Type[API return response_t(SUPPORTED_PROFILES.to_dict()) def get_all_aas_ids_by_asset_link( - self, request: Request, url_args: dict, response_t: Type[APIResponse], **_kwargs + self, request: Request, url_args: dict, response_t: Type[APIResponse], **_kwargs ) -> Response: asset_ids_param = request.args.get("assetIds", "") if not asset_ids_param: diff --git a/server/app/interfaces/registry.py b/server/app/interfaces/registry.py index 274602fff..eadca8753 100644 --- a/server/app/interfaces/registry.py +++ b/server/app/interfaces/registry.py @@ -20,12 +20,14 @@ from app.model import DictDescriptorStore, ServiceSpecificationProfileEnum, ServiceDescription from app.util.converters import IdentifierToBase64URLConverter, base64url_decode -SUPPORTED_PROFILES: ServiceDescription = ServiceDescription([ - ServiceSpecificationProfileEnum.AAS_REGISTRY_FULL, - ServiceSpecificationProfileEnum.SUBMODEL_REGISTRY_FULL, - ServiceSpecificationProfileEnum.AAS_REGISTRY_READ, - ServiceSpecificationProfileEnum.SUBMODEL_REGISTRY_READ, -]) +SUPPORTED_PROFILES: ServiceDescription = ServiceDescription( + [ + ServiceSpecificationProfileEnum.AAS_REGISTRY_FULL, + ServiceSpecificationProfileEnum.SUBMODEL_REGISTRY_FULL, + ServiceSpecificationProfileEnum.AAS_REGISTRY_READ, + ServiceSpecificationProfileEnum.SUBMODEL_REGISTRY_READ, + ] +) class RegistryAPI(ObjectStoreWSGIApp): @@ -127,8 +129,7 @@ def _get_all_aas_descriptors( asset_kind = model.AssetKind[asset_kind_str] except KeyError: raise BadRequest( - f"Invalid assetKind '{asset_kind_str}', " - f"must be one of {list(model.AssetKind.__members__)}" + f"Invalid assetKind '{asset_kind_str}', must be one of {list(model.AssetKind.__members__)}" ) descriptors = filter(lambda desc: desc.asset_kind == asset_kind, descriptors) @@ -147,9 +148,9 @@ def _get_all_aas_descriptors( def _get_aas_descriptor(self, url_args: Dict) -> server_model.AssetAdministrationShellDescriptor: return self._get_obj_ts(url_args["aas_id"], server_model.AssetAdministrationShellDescriptor) - def _get_all_submodel_descriptors(self, request: Request) -> Tuple[ - Iterator[server_model.SubmodelDescriptor], Optional[PagingMetadata] - ]: + def _get_all_submodel_descriptors( + self, request: Request + ) -> Tuple[Iterator[server_model.SubmodelDescriptor], Optional[PagingMetadata]]: submodel_descriptors: Iterator[server_model.SubmodelDescriptor] = self._get_all_obj_of_type( server_model.SubmodelDescriptor ) diff --git a/server/app/interfaces/repository.py b/server/app/interfaces/repository.py index c604408e9..21d8ffe00 100644 --- a/server/app/interfaces/repository.py +++ b/server/app/interfaces/repository.py @@ -27,12 +27,14 @@ from .base import ObjectStoreWSGIApp, APIResponse, is_stripped_request, HTTPApiDecoder, T from app.model import ServiceSpecificationProfileEnum, ServiceDescription -SUPPORTED_PROFILES: ServiceDescription = ServiceDescription([ - ServiceSpecificationProfileEnum.AAS_REPOSITORY_FULL, - ServiceSpecificationProfileEnum.SUBMODEL_REPOSITORY_FULL, - ServiceSpecificationProfileEnum.AAS_REPOSITORY_READ, - ServiceSpecificationProfileEnum.SUBMODEL_REPOSITORY_READ, -]) +SUPPORTED_PROFILES: ServiceDescription = ServiceDescription( + [ + ServiceSpecificationProfileEnum.AAS_REPOSITORY_FULL, + ServiceSpecificationProfileEnum.SUBMODEL_REPOSITORY_FULL, + ServiceSpecificationProfileEnum.AAS_REPOSITORY_READ, + ServiceSpecificationProfileEnum.SUBMODEL_REPOSITORY_READ, + ] +) class WSGIApp(ObjectStoreWSGIApp): @@ -431,7 +433,7 @@ def _get_submodel_reference( raise NotFound(f"The AAS {aas!r} doesn't have a submodel reference to {submodel_id!r}!") def _get_shells( - self, request: Request + self, request: Request ) -> Tuple[Iterator[model.AssetAdministrationShell], Optional[PagingMetadata]]: aas: Iterator[model.AssetAdministrationShell] = self._get_all_obj_of_type(model.AssetAdministrationShell) @@ -489,7 +491,9 @@ def _get_submodels(self, request: Request) -> Tuple[Iterator[model.Submodel], Op semantic_id = request.args.get("semanticId") if semantic_id is not None: spec_semantic_id = HTTPApiDecoder.base64url_json( - semantic_id, model.Reference, False # type: ignore[type-abstract] + semantic_id, + model.Reference, + False, # type: ignore[type-abstract] ) submodels = filter(lambda sm: sm.semantic_id == spec_semantic_id, submodels) paginated_submodels, paging_metadata = self._get_slice(request, submodels) @@ -590,18 +594,20 @@ def get_aas_submodel_refs( submodel_refs, paging_metadata = self._get_slice(request, sorted_submodel_refs) return response_t(list(submodel_refs), paging_metadata=paging_metadata) - def post_aas_submodel_refs(self, request: Request, url_args: Dict, response_t: Type[APIResponse], - map_adapter: MapAdapter, **_kwargs) -> Response: + def post_aas_submodel_refs( + self, request: Request, url_args: Dict, response_t: Type[APIResponse], map_adapter: MapAdapter, **_kwargs + ) -> Response: aas = self._get_shell(url_args) sm_ref = HTTPApiDecoder.request_body(request, model.ModelReference, False) if sm_ref in aas.submodel: raise Conflict(f"{sm_ref!r} already exists!") aas.submodel.add(sm_ref) self.object_store.commit(aas) - created_resource_url = map_adapter.build(self.delete_aas_submodel_refs_specific, { - "aas_id": aas.id, - "submodel_id": sm_ref.key[0].value - }, force_external=True) + created_resource_url = map_adapter.build( + self.delete_aas_submodel_refs_specific, + {"aas_id": aas.id, "submodel_id": sm_ref.key[0].value}, + force_external=True, + ) return response_t(sm_ref, status=201, headers={"Location": created_resource_url}) def delete_aas_submodel_refs_specific( @@ -672,8 +678,9 @@ def post_submodel( created_resource_url = map_adapter.build(self.get_submodel, {"submodel_id": submodel.id}, force_external=True) return response_t(submodel, status=201, headers={"Location": created_resource_url}) - def get_submodel_all_metadata(self, request: Request, url_args: Dict, response_t: Type[APIResponse], - **_kwargs) -> Response: + def get_submodel_all_metadata( + self, request: Request, url_args: Dict, response_t: Type[APIResponse], **_kwargs + ) -> Response: if "level" in request.args: raise BadRequest(f"level cannot be used when retrieving metadata!") submodels, paging_metadata = self._get_submodels(request) @@ -698,8 +705,9 @@ def get_submodel(self, request: Request, url_args: Dict, response_t: Type[APIRes submodel = self._get_submodel(url_args) return response_t(submodel, stripped=is_stripped_request(request)) - def get_submodels_metadata(self, request: Request, url_args: Dict, response_t: Type[APIResponse], - **_kwargs) -> Response: + def get_submodels_metadata( + self, request: Request, url_args: Dict, response_t: Type[APIResponse], **_kwargs + ) -> Response: if "level" in request.args: raise BadRequest(f"level cannot be used when retrieving metadata!") submodel = self._get_submodel(url_args) @@ -726,8 +734,9 @@ def get_submodel_submodel_elements( list(submodel_elements), paging_metadata=paging_metadata, stripped=is_stripped_request(request) ) - def get_submodel_submodel_elements_metadata(self, request: Request, url_args: Dict, response_t: Type[APIResponse], - **_kwargs) -> Response: + def get_submodel_submodel_elements_metadata( + self, request: Request, url_args: Dict, response_t: Type[APIResponse], **_kwargs + ) -> Response: if "level" in request.args: raise BadRequest(f"level cannot be used when retrieving metadata!") submodel_elements, paging_metadata = self._get_submodel_submodel_elements(request, url_args) @@ -748,8 +757,9 @@ def get_submodel_submodel_elements_id_short_path( submodel_element = self._get_submodel_submodel_elements_id_short_path(url_args) return response_t(submodel_element, stripped=is_stripped_request(request)) - def get_submodel_submodel_elements_id_short_path_metadata(self, request: Request, url_args: Dict, - response_t: Type[APIResponse], **_kwargs) -> Response: + def get_submodel_submodel_elements_id_short_path_metadata( + self, request: Request, url_args: Dict, response_t: Type[APIResponse], **_kwargs + ) -> Response: if "level" in request.args: raise BadRequest(f"level cannot be used when retrieving metadata!") submodel_element = self._get_submodel_submodel_elements_id_short_path(url_args) @@ -773,7 +783,9 @@ def post_submodel_submodel_elements_id_short_path( # TODO: remove the following type: ignore comment when mypy supports abstract types for Type[T] # see https://github.com/python/mypy/issues/5374 new_submodel_element = HTTPApiDecoder.request_body( - request, model.SubmodelElement, is_stripped_request(request) # type: ignore[type-abstract] + request, + model.SubmodelElement, + is_stripped_request(request), # type: ignore[type-abstract] ) try: parent.add_referable(new_submodel_element) @@ -781,7 +793,7 @@ def post_submodel_submodel_elements_id_short_path( if e.constraint_id != 22: raise raise Conflict( - f"SubmodelElement with idShort {new_submodel_element.id_short} already exists " f"within {parent}!" + f"SubmodelElement with idShort {new_submodel_element.id_short} already exists within {parent}!" ) self.object_store.commit(self._get_submodel(url_args)) submodel = self._get_submodel(url_args) @@ -800,7 +812,9 @@ def put_submodel_submodel_elements_id_short_path( # TODO: remove the following type: ignore comment when mypy supports abstract types for Type[T] # see https://github.com/python/mypy/issues/5374 new_submodel_element = HTTPApiDecoder.request_body( - request, model.SubmodelElement, is_stripped_request(request) # type: ignore[type-abstract] + request, + model.SubmodelElement, + is_stripped_request(request), # type: ignore[type-abstract] ) submodel_element.update_from(new_submodel_element) self.object_store.commit(self._get_submodel(url_args)) @@ -961,8 +975,9 @@ def get_concept_description_all( ) -> Response: concept_descriptions: Iterator[model.ConceptDescription] = self._get_all_obj_of_type(model.ConceptDescription) concept_descriptions, paging_metadata = self._get_slice(request, concept_descriptions) - return response_t(list(concept_descriptions), paging_metadata=paging_metadata, - stripped=is_stripped_request(request)) + return response_t( + list(concept_descriptions), paging_metadata=paging_metadata, stripped=is_stripped_request(request) + ) def post_concept_description( self, request: Request, url_args: Dict, response_t: Type[APIResponse], map_adapter: MapAdapter diff --git a/server/app/model/descriptor.py b/server/app/model/descriptor.py index 13fdf8d00..2680c256c 100644 --- a/server/app/model/descriptor.py +++ b/server/app/model/descriptor.py @@ -41,7 +41,6 @@ def update_from(self, other: "Descriptor", update_source: bool = False): class SubmodelDescriptor(Descriptor): - def __init__( self, id_: model.Identifier, @@ -63,7 +62,6 @@ def __init__( class AssetAdministrationShellDescriptor(Descriptor): - def __init__( self, id_: model.Identifier, diff --git a/server/app/model/endpoint.py b/server/app/model/endpoint.py index 06301e9a1..cd51e2bb0 100644 --- a/server/app/model/endpoint.py +++ b/server/app/model/endpoint.py @@ -38,7 +38,6 @@ def __init__(self, type_: SecurityTypeEnum, key: str, value: str): class ProtocolInformation: - def __init__( self, href: str, diff --git a/server/app/model/service_specification.py b/server/app/model/service_specification.py index 5181901ad..336525cf2 100644 --- a/server/app/model/service_specification.py +++ b/server/app/model/service_specification.py @@ -25,16 +25,21 @@ class ServiceSpecificationProfileEnum(str, enum.Enum): AASX_FILESERVER_FULL = "https://admin-shell.io/aas/API/3/1/AasxFileServerServiceSpecification/SSP-001" # --- AAS Registry --- - AAS_REGISTRY_FULL = \ + AAS_REGISTRY_FULL = ( "https://admin-shell.io/aas/API/3/1/AssetAdministrationShellRegistryServiceSpecification/SSP-001" - AAS_REGISTRY_READ = \ + ) + AAS_REGISTRY_READ = ( "https://admin-shell.io/aas/API/3/1/AssetAdministrationShellRegistryServiceSpecification/SSP-002" - AAS_REGISTRY_BULK = \ + ) + AAS_REGISTRY_BULK = ( "https://admin-shell.io/aas/API/3/1/AssetAdministrationShellRegistryServiceSpecification/SSP-003" - AAS_REGISTRY_QUERY = \ + ) + AAS_REGISTRY_QUERY = ( "https://admin-shell.io/aas/API/3/1/AssetAdministrationShellRegistryServiceSpecification/SSP-004" - AAS_REGISTRY_MINIMAL_READ = \ + ) + AAS_REGISTRY_MINIMAL_READ = ( "https://admin-shell.io/aas/API/3/1/AssetAdministrationShellRegistryServiceSpecification/SSP-005" + ) # --- Submodel Registry --- SUBMODEL_REGISTRY_FULL = "https://admin-shell.io/aas/API/3/1/SubmodelRegistryServiceSpecification/SSP-001" @@ -43,28 +48,32 @@ class ServiceSpecificationProfileEnum(str, enum.Enum): SUBMODEL_REGISTRY_QUERY = "https://admin-shell.io/aas/API/3/1/SubmodelRegistryServiceSpecification/SSP-004" # --- AAS Repository --- - AAS_REPOSITORY_FULL = \ + AAS_REPOSITORY_FULL = ( "https://admin-shell.io/aas/API/3/1/AssetAdministrationShellRepositoryServiceSpecification/SSP-001" - AAS_REPOSITORY_READ = \ + ) + AAS_REPOSITORY_READ = ( "https://admin-shell.io/aas/API/3/1/AssetAdministrationShellRepositoryServiceSpecification/SSP-002" - AAS_REPOSITORY_QUERY = \ + ) + AAS_REPOSITORY_QUERY = ( "https://admin-shell.io/aas/API/3/1/AssetAdministrationShellRepositoryServiceSpecification/SSP-003" + ) # --- Submodel Repository --- SUBMODEL_REPOSITORY_FULL = "https://admin-shell.io/aas/API/3/1/SubmodelRepositoryServiceSpecification/SSP-001" SUBMODEL_REPOSITORY_READ = "https://admin-shell.io/aas/API/3/1/SubmodelRepositoryServiceSpecification/SSP-002" - SUBMODEL_REPOSITORY_TEMPLATE = \ - "https://admin-shell.io/aas/API/3/1/SubmodelRepositoryServiceSpecification/SSP-003" - SUBMODEL_REPOSITORY_TEMPLATE_READ = \ + SUBMODEL_REPOSITORY_TEMPLATE = "https://admin-shell.io/aas/API/3/1/SubmodelRepositoryServiceSpecification/SSP-003" + SUBMODEL_REPOSITORY_TEMPLATE_READ = ( "https://admin-shell.io/aas/API/3/1/SubmodelRepositoryServiceSpecification/SSP-004" - SUBMODEL_REPOSITORY_QUERY = \ - "https://admin-shell.io/aas/API/3/1/SubmodelRepositoryServiceSpecification/SSP-005" + ) + SUBMODEL_REPOSITORY_QUERY = "https://admin-shell.io/aas/API/3/1/SubmodelRepositoryServiceSpecification/SSP-005" # --- Concept Description Repository --- - CONCEPT_DESCRIPTION_REPOSITORY_FULL = \ + CONCEPT_DESCRIPTION_REPOSITORY_FULL = ( "https://admin-shell.io/aas/API/3/1/ConceptDescriptionRepositoryServiceSpecification/SSP-001" - CONCEPT_DESCRIPTION_REPOSITORY_QUERY = \ + ) + CONCEPT_DESCRIPTION_REPOSITORY_QUERY = ( "https://admin-shell.io/aas/API/3/1/ConceptDescriptionRepositoryServiceSpecification/SSP-002" + ) # --- Discovery --- DISCOVERY_FULL = "https://admin-shell.io/aas/API/3/1/DiscoveryServiceSpecification/SSP-001" diff --git a/server/test/backend/test_local_file.py b/server/test/backend/test_local_file.py index aeafb85c5..93788f113 100644 --- a/server/test/backend/test_local_file.py +++ b/server/test/backend/test_local_file.py @@ -76,7 +76,7 @@ def test_key_errors(self) -> None: with self.assertRaises(KeyError) as cm: self.descriptor_store.add(self.aasd1) self.assertEqual( - "'Descriptor with id https://example.org/AASDescriptor/1 already exists in " "local file database'", + "'Descriptor with id https://example.org/AASDescriptor/1 already exists in local file database'", str(cm.exception), ) @@ -85,7 +85,7 @@ def test_key_errors(self) -> None: self.descriptor_store.get_item("https://example.org/AASDescriptor/1") self.assertIsNone(self.descriptor_store.get("https://example.org/AASDescriptor/1")) self.assertEqual( - "'No Identifiable with id https://example.org/AASDescriptor/1 found in local " "file database'", + "'No Identifiable with id https://example.org/AASDescriptor/1 found in local file database'", str(cm.exception), ) diff --git a/server/test/model/test_provider.py b/server/test/model/test_provider.py index ee3b810da..567eb6fa2 100644 --- a/server/test/model/test_provider.py +++ b/server/test/model/test_provider.py @@ -35,7 +35,7 @@ def test_store_retrieve(self) -> None: with self.assertRaises(KeyError) as cm: descriptor_store.add(aasd3) self.assertEqual( - "'Descriptor object with same id https://example.org/AASDescriptor/1 is already " "stored in this store'", + "'Descriptor object with same id https://example.org/AASDescriptor/1 is already stored in this store'", str(cm.exception), ) self.assertEqual(2, len(descriptor_store)) diff --git a/server/test/test_api_base_path.py b/server/test/test_api_base_path.py index 7617102d7..4d0a88de2 100644 --- a/server/test/test_api_base_path.py +++ b/server/test/test_api_base_path.py @@ -47,8 +47,4 @@ def test_base_path_aligned_across_known_files(self) -> None: values[str(path.relative_to(SERVER_ROOT))] = _extract(path) distinct = set(values.values()) - self.assertEqual( - 1, - len(distinct), - _list_divergences(values) - ) + self.assertEqual(1, len(distinct), _list_divergences(values))