diff --git a/aas/adapter/aasx.py b/aas/adapter/aasx.py index ecdddff0d2812617b7322ca93679e2a2daf44ec7..847c66245fb86807168dd07c35dee98ae54bd8b3 100644 --- a/aas/adapter/aasx.py +++ b/aas/adapter/aasx.py @@ -25,6 +25,7 @@ into/form object stores. For handling of embedded supplementary files, this modu import abc import hashlib import io +import itertools import logging import os import re @@ -345,17 +346,10 @@ class AASXWriter: for concept_rescription_ref in dictionary.concept_description: objects_to_be_written.add(concept_rescription_ref.get_identifier()) - if not submodel_split_parts: - for submodel_ref in aas.submodel: - objects_to_be_written.add(submodel_ref.get_identifier()) - - # Write AAS part - logger.debug("Writing AAS {} to part {} in AASX package ...".format(aas.identification, aas_part_name)) - self.write_aas_objects(aas_part_name, objects_to_be_written, object_store, file_store, write_json) - + # Write submodels: Either create a split part for each of them or otherwise add them to objects_to_be_written + aas_split_part_names: List[str] = [] if submodel_split_parts: # Create a AAS split part for each (available) submodel of the AAS - aas_split_part_names: List[str] = [] aas_friendlyfier = NameFriendlyfier() for submodel_ref in aas.submodel: submodel_identification = submodel_ref.get_identifier() @@ -363,19 +357,21 @@ class AASXWriter: submodel_part_name = "/aasx/{0}/{1}/{1}.submodel.{2}".format(aas_friendly_name, submodel_friendly_name, "json" if write_json else "xml") self.write_aas_objects(submodel_part_name, [submodel_identification], object_store, file_store, - write_json) + write_json, split_part=True) aas_split_part_names.append(submodel_part_name) + else: + for submodel_ref in aas.submodel: + objects_to_be_written.add(submodel_ref.get_identifier()) - # Add relationships from AAS part to (submodel) split parts - logger.debug("Writing aas-spec-split relationships for AAS {} to AASX package ..." - .format(aas.identification)) - self.writer.write_relationships( - (pyecma376_2.OPCRelationship("r{}".format(i), - RELATIONSHIP_TYPE_AAS_SPEC_SPLIT, - submodel_part_name, - pyecma376_2.OPCTargetMode.INTERNAL) - for i, submodel_part_name in enumerate(aas_split_part_names)), - aas_part_name) + # Write AAS part + logger.debug("Writing AAS {} to part {} in AASX package ...".format(aas.identification, aas_part_name)) + self.write_aas_objects(aas_part_name, objects_to_be_written, object_store, file_store, write_json, + split_part=False, + additional_relationships=(pyecma376_2.OPCRelationship("r{}".format(i), + RELATIONSHIP_TYPE_AAS_SPEC_SPLIT, + submodel_part_name, + pyecma376_2.OPCTargetMode.INTERNAL) + for i, submodel_part_name in enumerate(aas_split_part_names))) def write_aas_objects(self, part_name: str, @@ -383,7 +379,8 @@ class AASXWriter: object_store: model.AbstractObjectStore, file_store: "AbstractSupplementaryFileContainer", write_json: bool = False, - split_part: bool = False) -> None: + split_part: bool = False, + additional_relationships: Iterable[pyecma376_2.OPCRelationship] = ()) -> None: """ Write a defined list of AAS objects to an XML or JSON part in the AASX package and append the referenced supplementary files to the package. @@ -405,6 +402,8 @@ class AASXWriter: :param write_json: If True, the part is written as a JSON file instead of an XML file. Defaults to False. :param split_part: If True, no aas-spec relationship is added from the aasx-origin to this part. You must make sure to reference it via a aas-spec-split relationship from another aas-spec part + :param additional_relationships: Optional OPC/ECMA376 relationships which should originate at the AAS object + part to be written, in addition to the aas-suppl relationships which are created automatically. """ logger.debug("Writing AASX part {} with AAS objects ...".format(part_name)) @@ -464,11 +463,13 @@ class AASXWriter: # Add relationships from submodel to supplementary parts logger.debug("Writing aas-suppl relationships for AAS object part {} to AASX package ...".format(part_name)) self.writer.write_relationships( - (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)), + 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) def write_core_properties(self, core_properties: pyecma376_2.OPCCoreProperties): diff --git a/test/adapter/aasx/test_aasx.py b/test/adapter/aasx/test_aasx.py index aa54494c9a992749c65f2d2624d6eb4665cd6b0c..7d1390304ad6507a9d2d0382f356e3a0bf97e755 100644 --- a/test/adapter/aasx/test_aasx.py +++ b/test/adapter/aasx/test_aasx.py @@ -14,6 +14,7 @@ import io import os import tempfile import unittest +import warnings import pyecma376_2 from aas import model @@ -78,11 +79,21 @@ class AASXWriterTest(unittest.TestCase): with self.subTest(write_json=write_json, submodel_split_parts=submodel_split_parts): fd, filename = tempfile.mkstemp(suffix=".aasx") os.close(fd) - with aasx.AASXWriter(filename) as writer: - writer.write_aas(model.Identifier(id_='https://acplt.org/Test_AssetAdministrationShell', - id_type=model.IdentifierType.IRI), - data, files, write_json=write_json, submodel_split_parts=submodel_split_parts) - writer.write_core_properties(cp) + + # Write AASX file + # the zipfile library reports errors as UserWarnings via the warnings library. Let's check for + # warnings + with warnings.catch_warnings(record=True) as w: + with aasx.AASXWriter(filename) as writer: + writer.write_aas(model.Identifier(id_='https://acplt.org/Test_AssetAdministrationShell', + id_type=model.IdentifierType.IRI), + data, files, write_json=write_json, + submodel_split_parts=submodel_split_parts) + 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]}") # Read AASX file new_data: model.DictObjectStore[model.Identifiable] = model.DictObjectStore()