Aufgrund einer Störung des s3 Storage, könnten in nächster Zeit folgende GitLab Funktionen nicht zur Verfügung stehen: LFS, Container Registry, Job Artifacs, Uploads (Wiki, Bilder, Projekt-Exporte). Wir bitten um Verständnis. Es wird mit Hochdruck an der Behebung des Problems gearbeitet. Weitere Informationen zur Störung des Object Storage finden Sie hier: https://maintenance.itc.rwth-aachen.de/ticket/status/messages/59-object-storage-pilot

Commit 1350e6a7 authored by Torben Miny's avatar Torben Miny
Browse files

Merge branch feature/compliance_tool_aasx_extension with refs/heads/master...

Merge branch feature/compliance_tool_aasx_extension with refs/heads/master into refs/merge-requests/53/train
parents 59d2e36d 25c75158
Pipeline #354224 passed with stage
in 59 seconds
...@@ -125,10 +125,12 @@ The PyI40AAS project contains a compliance tool for testing xml and json files i ...@@ -125,10 +125,12 @@ The PyI40AAS project contains a compliance tool for testing xml and json files i
`aas.compliance_tool`-package. Following functionalities are supported: `aas.compliance_tool`-package. Following functionalities are supported:
* create an xml or json file compliant to the official schema containing example Asset Administration Shell elements * create an xml or json file compliant to the official schema containing example Asset Administration Shell elements
* create an aasx file with xml or json files compliant to the official schema containing example Asset Administration
Shell elements
* check if a given xml or json file is compliant to the official schema * check if a given xml or json file is compliant to the official schema
* check if a given xml or json file is deserializable even if it is not compliant to the offical schema * check if a given xml, json or aasx file is readable even if it is not compliant to the offical schema
* check if the data in a given xml or json file is the same as the example data * check if the data in a given xml, json or aasx file is the same as the example data
* check if two given xml or json file contain the same Asset Administration Shell elements in any order * check if two given xml, json or aasx files contain the same Asset Administration Shell elements in any order
Invoking should work with either `python -m aas.compliance_tool.cli` or (when installed correctly and PATH is set Invoking should work with either `python -m aas.compliance_tool.cli` or (when installed correctly and PATH is set
correctly) with `aas_compliance_check` on the command line. correctly) with `aas_compliance_check` on the command line.
......
...@@ -15,14 +15,20 @@ Command line script which is a compliance tool for creating and checking json an ...@@ -15,14 +15,20 @@ Command line script which is a compliance tool for creating and checking json an
examples.data.__init__.py examples.data.__init__.py
""" """
import argparse import argparse
import datetime
import logging import logging
import pyecma376_2
from aas import model
from aas.adapter import aasx
from aas.adapter.xml import write_aas_xml_file from aas.adapter.xml import write_aas_xml_file
from aas.compliance_tool import compliance_check_json as compliance_tool_json from aas.compliance_tool import compliance_check_json as compliance_tool_json
from aas.compliance_tool import compliance_check_xml as compliance_tool_xml from aas.compliance_tool import compliance_check_xml as compliance_tool_xml
from aas.compliance_tool import compliance_check_aasx as compliance_tool_aasx
from aas.adapter.json import write_aas_json_file from aas.adapter.json import write_aas_json_file
from aas.examples.data import create_example from aas.examples.data import create_example, create_example_aas_binding, TEST_PDF_FILE
from aas.compliance_tool.state_manager import ComplianceToolStateManager, Status from aas.compliance_tool.state_manager import ComplianceToolStateManager, Status
...@@ -32,11 +38,11 @@ def main(): ...@@ -32,11 +38,11 @@ def main():
description='Compliance tool for creating and checking json and xml files in compliance with "Details of the ' 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' 'Asset Administration Shell" specification of Plattform Industrie 4.0. \n\n'
'This tool has five features: \n' 'This tool has five features: \n'
'1. create an xml or json file with example aas elements\n' '1. create a xml or json file or an AASX file using xml or json files with example aas elements\n'
'2. check a given xml or json file if it is compliant with the official json or xml aas schema\n' '2. check a given xml or json file if it is compliant with the official json or xml aas schema\n'
'3. check if a given xml or json file is deserializable\n' '3. check if a given xml, json or aasx file is deserializable\n'
'4. check if the data in a given xml or json file is the same as the example data\n' '4. check if the data in a given xml, json or aasx file is the same as the example data\n'
'5. check if two given xml or json files contain the same aas elements in any order\n\n' '5. 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, schema, deserialization, example, ' 'As a first argument, the feature must be specified (create, schema, deserialization, example, '
'files) or in short (c, s, d, e or f).\n' 'files) or in short (c, s, d, e or f).\n'
'Depending the chosen feature, different additional arguments must be specified:\n' 'Depending the chosen feature, different additional arguments must be specified:\n'
...@@ -44,9 +50,11 @@ def main(): ...@@ -44,9 +50,11 @@ def main():
'schema or s: file to be checked (file_1)\n' 'schema or s: file to be checked (file_1)\n'
'deserialization or d: file to be checked (file_1)\n' 'deserialization or d: file to be checked (file_1)\n'
'example or e: 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' '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 ' 'In any case, it must be specified whether the (given or created) files are json (--json) or '
'xml (--xml).\n\n' 'xml (--xml).\n'
'All features except "schema" 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' 'Additionally, the tool offers some extra features for more convenient usage:\n'
'a. Different levels of verbosity:\n' 'a. Different levels of verbosity:\n'
' Default output is just the status for each step performed. With -v or --verbose, additional ' ' Default output is just the status for each step performed. With -v or --verbose, additional '
...@@ -76,6 +84,7 @@ def main(): ...@@ -76,6 +84,7 @@ def main():
group.add_argument('--json', help="Use AAS json format when checking or creating files", action='store_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') 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('-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')
args = parser.parse_args() args = parser.parse_args()
...@@ -88,11 +97,39 @@ def main(): ...@@ -88,11 +97,39 @@ def main():
if args.action == 'create' or args.action == 'c': if args.action == 'create' or args.action == 'c':
manager.add_step('Create example data') manager.add_step('Create example data')
data = create_example() if args.aasx:
data = create_example_aas_binding()
else:
data = create_example()
manager.set_step_status(Status.SUCCESS) manager.set_step_status(Status.SUCCESS)
try: try:
manager.add_step('Open file') manager.add_step('Open file')
if args.json: if args.aasx:
with aasx.AASXWriter(args.file_1) as writer:
manager.set_step_status(Status.SUCCESS)
manager.add_step('Write data to file')
files = aasx.DictSupplementaryFileContainer()
with open(TEST_PDF_FILE, 'rb') as f:
files.add_file("/TestFile.pdf", f, "application/pdf")
# Create OPC/AASX core properties
cp = pyecma376_2.OPCCoreProperties()
cp.created = datetime.datetime(2020, 1, 1, 0, 0, 0)
cp.creator = "PyI40AAS Testing Framework"
cp.description = "Test_Description"
cp.lastModifiedBy = "PyI40AAS Testing Framework Compliance Tool"
cp.modified = datetime.datetime(2020, 1, 1, 0, 0, 1)
cp.revision = "1.0"
cp.version = "2.0.1"
cp.title = "Test Title"
writer.write_aas_objects("/aasx/data.json" if args.json else "/aasx/data.xml",
[obj.identification 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.set_step_status(Status.SUCCESS)
manager.add_step('Write data to file') manager.add_step('Write data to file')
...@@ -113,18 +150,24 @@ def main(): ...@@ -113,18 +150,24 @@ def main():
if args.xml: if args.xml:
compliance_tool_xml.check_schema(args.file_1, manager) compliance_tool_xml.check_schema(args.file_1, manager)
elif args.action == 'deserialization' or args.action == 'd': elif args.action == 'deserialization' or args.action == 'd':
if args.json: if args.aasx:
compliance_tool_aasx.check_deserialization(args.file_1, manager)
elif args.json:
compliance_tool_json.check_deserialization(args.file_1, manager) compliance_tool_json.check_deserialization(args.file_1, manager)
elif args.xml: elif args.xml:
compliance_tool_xml.check_deserialization(args.file_1, manager) 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.json: if args.aasx:
compliance_tool_aasx.check_aas_example(args.file_1, manager)
elif args.json:
compliance_tool_json.check_aas_example(args.file_1, manager) compliance_tool_json.check_aas_example(args.file_1, manager)
elif args.xml: elif args.xml:
compliance_tool_xml.check_aas_example(args.file_1, manager) compliance_tool_xml.check_aas_example(args.file_1, manager)
elif args.action == 'files' or args.action == 'f': elif args.action == 'files' or args.action == 'f':
if args.file_2: if args.file_2:
if args.json: if args.aasx:
compliance_tool_aasx.check_aasx_files_equivalence(args.file_1, args.file_2, manager)
elif args.json:
compliance_tool_json.check_json_files_equivalence(args.file_1, args.file_2, manager) compliance_tool_json.check_json_files_equivalence(args.file_1, args.file_2, manager)
elif args.xml: elif args.xml:
compliance_tool_xml.check_xml_files_equivalence(args.file_1, args.file_2, manager) compliance_tool_xml.check_xml_files_equivalence(args.file_1, args.file_2, manager)
......
# Copyright 2020 PyI40AAS Contributors
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
# specific language governing permissions and limitations under the License.
"""
Module which offers functions to use in a confirmation tool related to AASX files
check_deserialization: Checks if a AASX file can be deserialized
check_aas_example: Checks if a AASX file consist the data of the example data defined in
aas.examples.data.example_aas.py
check_aasx_files_equivalence: Checks if two AASX files have the same data regardless of their order
All functions reports any issues using the given StateManager by adding new steps and associated LogRecords
"""
import datetime
import json
import logging
from typing import Optional, Tuple
import pyecma376_2
from .. import model
from ..adapter import aasx
from ..examples.data import example_aas, create_example_aas_binding
from ..examples.data._helper import AASDataChecker, DataChecker
from .state_manager import ComplianceToolStateManager, Status
def check_deserialization(file_path: str, state_manager: ComplianceToolStateManager,
file_info: Optional[str] = None) \
-> Tuple[model.DictObjectStore, aasx.DictSupplementaryFileContainer, pyecma376_2.OPCCoreProperties]:
"""
Read a AASX file and reports any issues using the given StateManager
add the steps: 'Open {} file' and 'Read {} file'
:param file_path: given file which should be deserialized
:param state_manager: manager to log the steps
:param file_info: additional information about the file for name of the steps
:return: returns the read object store
"""
logger = logging.getLogger('compliance_check')
logger.addHandler(state_manager)
logger.propagate = False
logger.setLevel(logging.INFO)
# create handler to get logger info
logger_deserialization = logging.getLogger(aasx.__name__)
logger_deserialization.addHandler(state_manager)
logger_deserialization.propagate = False
logger_deserialization.setLevel(logging.INFO)
if file_info:
state_manager.add_step('Open {} file'.format(file_info))
else:
state_manager.add_step('Open file')
try:
# open given file
reader = aasx.AASXReader(file_path)
state_manager.set_step_status_from_log()
except ValueError as error:
logger.error(error)
state_manager.set_step_status_from_log()
state_manager.add_step('Read file')
state_manager.set_step_status(Status.NOT_EXECUTED)
return model.DictObjectStore(), aasx.DictSupplementaryFileContainer(), pyecma376_2.OPCCoreProperties()
try:
# read given file
state_manager.add_step('Read file')
obj_store: model.DictObjectStore[model.Identifiable] = model.DictObjectStore()
files = aasx.DictSupplementaryFileContainer()
reader.read_into(obj_store, files)
new_cp = reader.get_core_properties()
state_manager.set_step_status(Status.SUCCESS)
except ValueError as error:
logger.error(error)
state_manager.set_step_status(Status.FAILED)
return model.DictObjectStore(), aasx.DictSupplementaryFileContainer(), pyecma376_2.OPCCoreProperties()
finally:
reader.close()
return obj_store, files, new_cp
def check_aas_example(file_path: str, state_manager: ComplianceToolStateManager) -> None:
"""
Checks if a file contains all elements of the aas example and reports any issues using the given StateManager
calls the check_deserialization and add the steps: 'Check if data is equal to example data'
:param file_path: given file which should be checked
:param state_manager: manager to log the steps
"""
logger = logging.getLogger('compliance_check')
logger.addHandler(state_manager)
logger.propagate = False
logger.setLevel(logging.INFO)
# create handler to get logger info
logger_example = logging.getLogger(example_aas.__name__)
logger_example.addHandler(state_manager)
logger_example.propagate = False
logger_example.setLevel(logging.INFO)
obj_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.set_step_status(Status.NOT_EXECUTED)
state_manager.add_step('Check if core properties are equal')
state_manager.set_step_status(Status.NOT_EXECUTED)
return
checker = AASDataChecker(raise_immediately=False)
state_manager.add_step('Check if data is equal to example data')
example_data = create_example_aas_binding()
checker.check_object_store(obj_store, example_data)
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.set_step_status(Status.NOT_EXECUTED)
return
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)
cp.creator = "PyI40AAS Testing Framework"
cp.description = "Test_Description"
cp.lastModifiedBy = "PyI40AAS Testing Framework Compliance Tool"
cp.modified = datetime.datetime(2020, 1, 1, 0, 0, 1)
cp.revision = "1.0"
cp.version = "2.0.1"
cp.title = "Test Title"
checker2 = DataChecker(raise_immediately=False)
assert (isinstance(cp_new.created, datetime.datetime))
duration = 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)
assert (isinstance(cp_new.modified, datetime.datetime))
duration = cp_new.modified - cp.modified
checker2.check(duration.microseconds < 20, "modified must be {}".format(cp.modified), modified=cp_new.modified)
checker2.check(cp_new.revision == cp.revision, "revision must be {}".format(cp.revision), revision=cp_new.revision)
checker2.check(cp_new.version == cp.version, "version must be {}".format(cp.version), version=cp_new.version)
checker2.check(cp_new.title == cp.title, "title must be {}".format(cp.title), title=cp_new.title)
# Check if file in file object is the same
list_of_id_shorts = ["ExampleSubmodelCollectionUnordered", "ExampleFile"]
obj = example_data.get_identifiable(model.Identifier("https://acplt.org/Test_Submodel", model.IdentifierType.IRI))
for id_short in list_of_id_shorts:
obj = obj.get_referable(id_short)
obj2 = obj_store.get_identifiable(model.Identifier("https://acplt.org/Test_Submodel", model.IdentifierType.IRI))
for id_short in list_of_id_shorts:
obj2 = obj2.get_referable(id_short)
try:
sha_file = files.get_sha256(obj.value)
except KeyError as error:
state_manager.add_log_records_from_data_checker(checker2)
logger.error(error)
state_manager.set_step_status(Status.FAILED)
return
checker2.check(sha_file == files.get_sha256(obj2.value), "File of {} must be {}.".format(obj.value, obj2.value),
value=obj2.value)
state_manager.add_log_records_from_data_checker(checker2)
if state_manager.status in (Status.FAILED, Status.NOT_EXECUTED):
state_manager.set_step_status(Status.FAILED)
else:
state_manager.set_step_status(Status.SUCCESS)
def check_aasx_files_equivalence(file_path_1: str, file_path_2: str, state_manager: ComplianceToolStateManager) -> None:
"""
Checks if two aasx files contain the same elements in any order and reports any issues using the given StateManager
calls the check_deserialization for ech file and add the steps: 'Check if data in files are equal'
:param file_path_1: given first file which should be checked
:param file_path_2: given second file which should be checked
:param state_manager: manager to log the steps
"""
logger = logging.getLogger('compliance_check')
logger.addHandler(state_manager)
logger.propagate = False
logger.setLevel(logging.INFO)
obj_store_1, files_1, cp_1 = check_deserialization(file_path_1, state_manager, 'first')
obj_store_2, files_2, cp_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.set_step_status(Status.NOT_EXECUTED)
state_manager.add_step('Check if core properties are equal')
state_manager.set_step_status(Status.NOT_EXECUTED)
return
checker = AASDataChecker(raise_immediately=False)
try:
state_manager.add_step('Check if data in files are equal')
checker.check_object_store(obj_store_1, obj_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.set_step_status(Status.NOT_EXECUTED)
return
state_manager.add_log_records_from_data_checker(checker)
if state_manager.status is Status.FAILED:
state_manager.add_step('Check if core properties are equal')
state_manager.set_step_status(Status.NOT_EXECUTED)
return
state_manager.add_step('Check if core properties are equal')
checker2 = DataChecker(raise_immediately=False)
assert (isinstance(cp_1.created, datetime.datetime))
assert (isinstance(cp_2.created, datetime.datetime))
duration = cp_1.created - 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)
state_manager.add_log_records_from_data_checker(checker2)
...@@ -20,14 +20,18 @@ example_submodel_template.py ...@@ -20,14 +20,18 @@ example_submodel_template.py
Module for the creation of an example submodel template containing all kind of submodel elements where the kind is Module for the creation of an example submodel template containing all kind of submodel elements where the kind is
always TEMPLATE. always TEMPLATE.
""" """
import os
from aas import model from aas import model
from aas.examples.data import example_aas, example_aas_mandatory_attributes, example_aas_missing_attributes, \ from aas.examples.data import example_aas, example_aas_mandatory_attributes, example_aas_missing_attributes, \
example_submodel_template, example_concept_description example_submodel_template, example_concept_description
TEST_PDF_FILE = os.path.join(os.path.dirname(__file__), 'TestFile.pdf')
def create_example() -> model.DictObjectStore: def create_example() -> model.DictObjectStore:
""" """
creates an object store which is filled with a example assets, submodels, concept descriptions and asset creates an object store which is filled with example assets, submodels, concept descriptions and asset
administration shells using the functionality of this package administration shells using the functionality of this package
:return: object store :return: object store
...@@ -39,3 +43,38 @@ def create_example() -> model.DictObjectStore: ...@@ -39,3 +43,38 @@ def create_example() -> model.DictObjectStore:
obj_store.add(example_submodel_template.create_example_submodel_template()) obj_store.add(example_submodel_template.create_example_submodel_template())
obj_store.add(example_concept_description.create_iec61360_concept_description()) obj_store.add(example_concept_description.create_iec61360_concept_description())
return obj_store return obj_store
def create_example_aas_binding() -> model.DictObjectStore:
"""
creates an object store which is filled with example assets, submodels, concept descriptions and asset
administration shells using the functionality of this package where each asset, submodel and concept description is
at least referred by one asset administration shell
:return: object store
"""
obj_store: model.DictObjectStore[model.Identifiable] = model.DictObjectStore()
obj_store.update(example_aas.create_full_example())
obj_store.update(example_aas_mandatory_attributes.create_full_example())
obj_store.update(example_aas_missing_attributes.create_full_example())
obj_store.add(example_submodel_template.create_example_submodel_template())
aas = obj_store.get_identifiable(model.Identifier('https://acplt.org/Test_AssetAdministrationShell',
model.IdentifierType.IRI))
sm = obj_store.get_identifiable(model.Identifier('https://acplt.org/Test_Submodel_Template',
model.IdentifierType.IRI))
assert (isinstance(aas, model.aas.AssetAdministrationShell)) # make mypy happy
assert (isinstance(sm, model.submodel.Submodel)) # make mypy happy
aas.submodel.add(model.AASReference.from_referable(sm))
obj_store.add(example_concept_description.create_iec61360_concept_description())
cd = obj_store.get_identifiable(model.Identifier('http://acplt.org/DataSpecifciations/Example/Identification',
model.IdentifierType.IRI))
assert (isinstance(cd, model.concept.IEC61360ConceptDescription)) # make mypy happy
cdict = aas.concept_dictionary.get_referable("TestConceptDictionary")
cdict.concept_description.add(model.AASReference.from_referable(cd))
cd2 = obj_store.get_identifiable(model.Identifier('https://acplt.org/Test_ConceptDescription_Mandatory',
model.IdentifierType.IRI))
assert (isinstance(cd2, model.concept.ConceptDescription)) # make mypy happy
cdict.concept_description.add(model.AASReference.from_referable(cd2))
return obj_store
...@@ -30,6 +30,7 @@ setuptools.setup( ...@@ -30,6 +30,7 @@ setuptools.setup(
"aas": ["py.typed"], "aas": ["py.typed"],
"aas.adapter.json": ["aasJSONSchema.json"], "aas.adapter.json": ["aasJSONSchema.json"],
"aas.adapter.xml": ["AAS.xsd", "AAS_ABAC.xsd", "IEC61360.xsd"], "aas.adapter.xml": ["AAS.xsd", "AAS_ABAC.xsd", "IEC61360.xsd"],
"aas.examples.data": ["TestFile.pdf"],
}, },
classifiers=[ classifiers=[
"Programming Language :: Python :: 3", "Programming Language :: Python :: 3",
......
{ {
"assetAdministrationShells": [ "assetAdministrationShells": [
{ {
"idShort": "TestAssetAdministrationShell", "idShort": "TestAssetAdministrationShell",
...@@ -3003,4 +3003,4 @@ ...@@ -3003,4 +3003,4 @@
] ]
} }
] ]
} }
\ No newline at end of file
{ {
"assetAdministrationShells": [ "assetAdministrationShells": [
{ {
"idShort": "TestAssetAdministrationShell123", "idShort": "TestAssetAdministrationShell123",
...@@ -3003,4 +3003,4 @@ ...@@ -3003,4 +3003,4 @@
] ]
} }
] ]
} }
\ No newline at end of file
...@@ -1784,4 +1784,4 @@ ...@@ -1784,4 +1784,4 @@
</aas:isCaseOf> </aas:isCaseOf>
</aas:conceptDescription> </aas:conceptDescription>
</aas:conceptDescriptions> </aas:conceptDescriptions>
</aas:aasenv> </aas:aasenv>
\ No newline at end of file
...@@ -8,17 +8,24 @@ ...@@ -8,17 +8,24 @@
# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an # Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
# specific language governing permissions and limitations under the License. # specific language governing permissions and limitations under the License.
import datetime
import hashlib