Verified Commit cc804cda authored by Leon Mauritz Möller's avatar Leon Mauritz Möller
Browse files

Merge branch 'master' into feature/xml_deserialization

parents fb6d458c 6278d03f
# PyI40AAS – Python Industry 4.0 Asset Administration Shell
The PyI40AAS project aims to provide an implementation of the Asset Administration Shell for Industry 4.0 Systems, compliant
The PyI40AAS project aims to provide an implementation of the Asset Administration Shell (AAS) for Industry 4.0 Systems, compliant
with the meta model and interface specification provided in
[the document “Details of the Asset Administration Shell” (v2.0)](https://www.plattform-i40.de/PI40/Redaktion/DE/Downloads/Publikation/Details-of-the-Asset-Administration-Shell-Part1.html).
The implementation will include the data model as well as interface adapters for serving, retreiving, importing and
exporting Asset Administration Shells.
## Features
* Modelling of AASs as Python objects (according to DotAAS sec. 4)
* (De-)serialization of AAS objects into/from JSON and XML (according to DotAAS sec. 5)
* Reading and writing of AASX package files (according to DotAAS sec. 7)
* Storing of AAS objects in CouchDB
* Compliance checking of AAS XML and JSON files
### Project Structure
The PyI40AAS project provides the `aas` Python package with 5 submodules:
* `aas.model`: The AAS metamodel implemented in python
* `aas.adapter`: Adapters for various file formats and storage backends
* `aas.compliance_tools`: Compliance checker for AAS files
* `aas.util`: Provides utilities
* `aas.examples`: Example data and tutorials
## License
......@@ -13,4 +31,114 @@ exporting Asset Administration Shells.
The PyI40AAS project is provided under the terms of the Apache License (Version 2.0).
For more information, especially considering the licenses of included third-party works, please consult the `NOTICE`
file.
file.
## Dependencies
PyI40AAS requires the following Python packages to be installed for production usage:
* `python-dateutil` (BSD 3-clause License)
* `lxml` (BSD 3-clause License, using `libxml2` under MIT License)
* `pyecma376-2` (Apache License v2.0)
## Getting Started
### Installation
For most users, the recommended method to install is from PyPI:
```python
pip install pyi40aas
```
### Example
The following code example shows how to create a `Submodel` with a `Property` serialize it into an XML file using PyI40AAS:
Create a `Submodel`:
```python
from aas import model # Import all PYI40AAS classes from the model package
identifier = model.Identifier('https://acplt.org/Simple_Submodel', model.IdentifierType.IRI)
submodel = model.Submodel(identification=identifier)
```
Create a `Property` and add it to the `Submodel`:
```python
# create a global reference to a semantic description of the property
semantic_reference = model.Reference(
(model.Key(
type_=model.KeyElements.GLOBAL_REFERENCE,
local=False,
value='http://acplt.org/Properties/SimpleProperty',
id_type=model.KeyType.IRI
),)
)
property = model.Property(
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
)
submodel.submodel_element.add(property)
```
Serialize the `Submodel` to XML:
```python
import aas.adapter.xml.xml_serialization
data: model.DictObjectStore[model.Identifiable] = model.DictObjectStore()
data.add(submodel)
with open('Simple_Submodel.xml', 'w', encoding='utf-8') as f:
aas.adapter.xml.xml_serialization.write_aas_xml_file(file=f, data=data)
```
### Examples and Tutorials
For further examples and tutorials, check out the `aas.examples`-package. Here is a quick overview:
* `examples.tutorial_create_simple_aas`: Create an Asset Administration Shell, containing an asset reference and a
submodel reference
* `examples.tutorial_serialization_deserialization_json`: Tutorial for the serialization and deserialization of asset
administration shells, submodels and assets
* `examples.tutorial_storage`: Tutorial for storing asset administration shells, submodels and assets
## Contributing
If you plan contributing code to the PyI40AAS project, please get in touch with us via E-Mail first: m.thies@plt.rwth-aachen.de
### Codestyle and Testing
Our code follows the [PEP 8 -- Style Guide for Python Code](https://www.python.org/dev/peps/pep-0008/).
Additionally, we use [PEP 484 -- Type Hints](https://www.python.org/dev/peps/pep-0484/) throughout the code to enable type checking the code.
Before submitting any changes, make sure to let `mypy` and `pycodestyle` check your code and run the unit tests with
Python's builtin `unittest`. To install the required tools, use:
```bash
pip install mypy pycodestyle
```
Running all checks:
```bash
mypy aas test
python -m pycodestyle --max-line-length 120 aas test
python -m unittest
```
We aim to cover our code with test by at least 80%. To check test coverage, you can use `coverage`:
```bash
pip install coverage
coverage run --source aas --branch -m unittest
coverage report -m
```
### Contribute Code/Patches
TBD
"""
Dicts to serialize enum classes
Dicts to serialize and deserialize enum classes
"""
from typing import Dict
......@@ -76,3 +76,15 @@ IEC61360_LEVEL_TYPES: Dict[model.concept.IEC61360LevelType, str] = {
model.concept.IEC61360LevelType.NOM: 'Nom',
model.concept.IEC61360LevelType.TYP: 'Typ',
}
MODELING_KIND_INVERSE: Dict[str, model.ModelingKind] = {v: k for k, v in MODELING_KIND.items()}
ASSET_KIND_INVERSE: Dict[str, model.AssetKind] = {v: k for k, v in ASSET_KIND.items()}
KEY_ELEMENTS_INVERSE: Dict[str, model.KeyElements] = {v: k for k, v in KEY_ELEMENTS.items()}
KEY_TYPES_INVERSE: Dict[str, model.KeyType] = {v: k for k, v in KEY_TYPES.items()}
IDENTIFIER_TYPES_INVERSE: Dict[str, model.IdentifierType] = {v: k for k, v in IDENTIFIER_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.concept.IEC61360DataType] = {v: k for k, v in IEC61360_DATA_TYPES.items()}
IEC61360_LEVEL_TYPES_INVERSE: Dict[str, model.concept.IEC61360LevelType] = \
{v: k for k, v in IEC61360_LEVEL_TYPES.items()}
KEY_ELEMENTS_CLASSES_INVERSE: Dict[model.KeyElements, type] = {v: k for k, v in model.KEY_ELEMENTS_CLASSES.items()}
This diff is collapsed.
......@@ -29,22 +29,12 @@ import pprint
from typing import Dict, Callable, TypeVar, Type, List, IO, Optional
from ... import model
from .._generic import MODELING_KIND, ASSET_KIND, KEY_ELEMENTS, KEY_TYPES, IDENTIFIER_TYPES, ENTITY_TYPES,\
IEC61360_DATA_TYPES, IEC61360_LEVEL_TYPES
from .._generic import MODELING_KIND_INVERSE, ASSET_KIND_INVERSE, KEY_ELEMENTS_INVERSE, KEY_TYPES_INVERSE,\
IDENTIFIER_TYPES_INVERSE, ENTITY_TYPES_INVERSE, IEC61360_DATA_TYPES_INVERSE, IEC61360_LEVEL_TYPES_INVERSE,\
KEY_ELEMENTS_CLASSES_INVERSE
logger = logging.getLogger(__name__)
MODELING_KIND_INVERSE: Dict[str, model.ModelingKind] = {v: k for k, v in MODELING_KIND.items()}
ASSET_KIND_INVERSE: Dict[str, model.AssetKind] = {v: k for k, v in ASSET_KIND.items()}
KEY_ELEMENTS_INVERSE: Dict[str, model.KeyElements] = {v: k for k, v in KEY_ELEMENTS.items()}
KEY_TYPES_INVERSE: Dict[str, model.KeyType] = {v: k for k, v in KEY_TYPES.items()}
IDENTIFIER_TYPES_INVERSE: Dict[str, model.IdentifierType] = {v: k for k, v in IDENTIFIER_TYPES.items()}
ENTITY_TYPES_INVERSE: Dict[str, model.EntityType] = {v: k for k, v in ENTITY_TYPES.items()}
KEY_ELEMENTS_CLASSES_INVERSE: Dict[model.KeyElements, type] = {v: k for k, v in model.KEY_ELEMENTS_CLASSES.items()}
IEC61360_DATA_TYPES_INVERSE: Dict[str, model.concept.IEC61360DataType] = {v: k for k, v in IEC61360_DATA_TYPES.items()}
IEC61360_LEVEL_TYPES_INVERSE: Dict[str, model.concept.IEC61360LevelType] = \
{v: k for k, v in IEC61360_LEVEL_TYPES.items()}
# #############################################################################
# Helper functions (for simplifying implementation of constructor functions)
......@@ -268,7 +258,7 @@ class AASFromJsonDecoder(json.JSONDecoder):
@classmethod
def _construct_reference(cls, dct: Dict[str, object], object_class=model.Reference) -> model.Reference:
keys = [cls._construct_key(key_data) for key_data in _get_ts(dct, "keys", list)]
return model.Reference(tuple(keys))
return object_class(tuple(keys))
@classmethod
def _construct_aas_reference(cls, dct: Dict[str, object], type_: Type[T], object_class=model.AASReference)\
......
......@@ -637,7 +637,18 @@ def create_example_asset_administration_shell(concept_dictionary: model.ConceptD
local=False,
value='https://acplt.org/Test_Submodel',
id_type=model.KeyType.IRI),),
model.Submodel)},
model.Submodel),
model.AASReference((model.Key(type_=model.KeyElements.SUBMODEL,
local=False,
value='http://acplt.org/Submodels/Assets/TestAsset/Identification',
id_type=model.KeyType.IRI),),
model.Submodel),
model.AASReference((model.Key(type_=model.KeyElements.SUBMODEL,
local=False,
value='http://acplt.org/Submodels/Assets/TestAsset/BillOfMaterial',
id_type=model.KeyType.IRI),),
model.Submodel),
},
concept_dictionary=[concept_dictionary],
view=[],
derived_from=model.AASReference((model.Key(type_=model.KeyElements.ASSET_ADMINISTRATION_SHELL,
......
......@@ -76,8 +76,10 @@ class DictObjectStore(AbstractObjectStore[_IT], Generic[_IT]):
"""
A local in-memory object store for Identifiable Objects, backed by a dict, mapping Identifier → Identifiable
"""
def __init__(self):
def __init__(self, objects: Iterable[_IT] = ()) -> None:
self._backend: Dict[Identifier, _IT] = {}
for x in objects:
self.add(x)
def get_identifiable(self, identifier: Identifier) -> _IT:
return self._backend[identifier]
......
# Copyright 2019 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.
"""
A module with helper functions for traversing AAS object strcutures.
"""
from typing import Union, Iterator
from .. import model
def walk_submodel(collection: Union[model.Submodel, model.SubmodelElementCollection]) \
-> Iterator[model.SubmodelElement]:
"""
Traverse the SubmodelElements in a Submodel or a SubmodelElementCollection recursively in post-order tree-traversal.
This is a generator function, yielding all the SubmodelElements. No SubmodelElements should be added, removed or
moved while iterating, as this could result in undefined behaviour.
"""
elements = collection.submodel_element if isinstance(collection, model.Submodel) else collection.value
for element in elements:
if isinstance(element, model.SubmodelElementCollection):
yield from walk_submodel(element)
yield element
jsonschema>=3.2,<4.0
lxml>=4.2,<5
python-dateutil>=2.8,<3.0
\ No newline at end of file
python-dateutil>=2.8,<3.0
pyecma376-2>=0.2
......@@ -12,7 +12,7 @@
import setuptools
with open("README.md", "r") as fh:
with open("README.md", "r", encoding='utf-8') as fh:
long_description = fh.read()
setuptools.setup(
......@@ -25,6 +25,8 @@ setuptools.setup(
long_description_content_type="text/markdown",
url="https://git.rwth-aachen.de/acplt/pyaas",
packages=setuptools.find_packages(),
zip_safe=False,
package_data={"aas": ["py.typed"]},
classifiers=[
"Programming Language :: Python :: 3",
"License :: OSI Approved :: Apache Software License",
......@@ -40,5 +42,6 @@ setuptools.setup(
install_requires=[
'python-dateutil>=2.8,<3',
'lxml>=4.2,<5',
'pyecma376-2>=0.2',
]
)
# Copyright 2019 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.
import datetime
import hashlib
import io
import os
import tempfile
import unittest
import pyecma376_2
from aas import model
from aas.adapter import aasx
from aas.examples.data import example_aas, _helper
class TestAASXUtils(unittest.TestCase):
def test_name_friendlyfier(self) -> None:
friendlyfier = aasx.NameFriendlyfier()
name1 = friendlyfier.get_friendly_name(model.Identifier("http://example.com/AAS-a", model.IdentifierType.IRI))
self.assertEqual("http___example_com_AAS_a", name1)
name2 = friendlyfier.get_friendly_name(model.Identifier("http://example.com/AAS+a", model.IdentifierType.IRI))
self.assertEqual("http___example_com_AAS_a_1", name2)
def test_supplementary_file_container(self) -> None:
container = aasx.DictSupplementaryFileContainer()
with open(os.path.join(os.path.dirname(__file__), 'TestFile.pdf'), 'rb') as f:
new_name = container.add_file("/TestFile.pdf", f, "application/pdf")
# Name should not be modified, since there is no conflict
self.assertEqual("/TestFile.pdf", new_name)
f.seek(0)
container.add_file("/TestFile.pdf", f, "application/pdf")
# Name should not be modified, since there is still no conflict
self.assertEqual("/TestFile.pdf", new_name)
with open(__file__, 'rb') as f:
new_name = container.add_file("/TestFile.pdf", f, "application/pdf")
# Now, we have a conflict
self.assertNotEqual("/TestFile.pdf", new_name)
self.assertIn(new_name, container)
# Check metadata
self.assertEqual("application/pdf", container.get_content_type("/TestFile.pdf"))
self.assertEqual("b18229b24a4ee92c6c2b6bc6a8018563b17472f1150d35d5a5945afeb447ed44",
container.get_sha256("/TestFile.pdf").hex())
self.assertIn("/TestFile.pdf", container)
# Check contents
file_content = io.BytesIO()
container.write_file("/TestFile.pdf", file_content)
self.assertEqual(hashlib.sha1(file_content.getvalue()).hexdigest(), "78450a66f59d74c073bf6858db340090ea72a8b1")
class AASXWriterTest(unittest.TestCase):
def test_writing_reading_example_aas(self) -> None:
# Create example data and file_store
data = example_aas.create_full_example()
files = aasx.DictSupplementaryFileContainer()
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)
# Create OPC/AASX core properties
cp = pyecma376_2.OPCCoreProperties()
cp.created = datetime.datetime.now()
cp.creator = "PyI40AAS Testing Framework"
# Write AASX file
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)
writer.write_core_properties(cp)
# Read AASX file
new_data: model.DictObjectStore[model.Identifiable] = model.DictObjectStore()
new_files = aasx.DictSupplementaryFileContainer()
with aasx.AASXReader(filename) as reader:
reader.read_into(new_data, new_files)
new_cp = reader.get_core_properties()
# Check AAS objects
checker = _helper.AASDataChecker(raise_immediately=True)
example_aas.check_full_example(checker, new_data)
# Check core properties
self.assertEqual(new_cp.created, cp.created)
self.assertEqual(new_cp.creator, "PyI40AAS Testing Framework")
self.assertIsNone(new_cp.lastModifiedBy)
# Check files
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")
os.unlink(filename)
......@@ -15,7 +15,8 @@ import aas.compliance_tool.compliance_check_json as compliance_tool
from aas.compliance_tool.state_manager import ComplianceToolStateManager, Status
dirname = os.path.dirname
JSON_SCHEMA_FILE = os.path.join(dirname(dirname(dirname(__file__))), 'test\\adapter\\json\\aasJSONSchemaV2.0.json')
JSON_SCHEMA_FILE = os.path.join(dirname(dirname(dirname(__file__))), 'test', 'adapter', 'json',
'aasJSONSchemaV2.0.json')
class ComplianceToolJsonTest(unittest.TestCase):
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment