diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d8774ebfd..8901688fd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,9 +14,9 @@ jobs: run: working-directory: ./etc/scripts steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Set up Python ${{ env.X_PYTHON_MIN_VERSION }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v5 with: python-version: ${{ env.X_PYTHON_MIN_VERSION }} - name: Install Python dependencies @@ -29,12 +29,14 @@ jobs: ${{ env.X_PYTHON_MIN_VERSION }} \ ${{ env.X_PYTHON_MAX_VERSION }} - - name: Check Python Versions coincide with the SDKs pyproject.toml + - name: Check Python Versions coincide with all pyproject.toml Files run: | - python check_python_versions_coincide.py \ - ../../sdk/pyproject.toml \ - ${{ env.X_PYTHON_MIN_VERSION }} \ - ${{ env.X_PYTHON_MAX_VERSION }} + for file in ../../sdk/pyproject.toml ../../compliance_tool/pyproject.toml; do + python check_python_versions_coincide.py \ + $file \ + ${{ env.X_PYTHON_MIN_VERSION }} \ + ${{ env.X_PYTHON_MAX_VERSION }} + done # Todo: Check other pyproject.toml here as well, as we add them @@ -72,7 +74,7 @@ jobs: exit 1 fi - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Collect schema files from aas-specs @@ -104,7 +106,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Set up Python ${{ env.X_PYTHON_MIN_VERSION }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v5 with: python-version: ${{ env.X_PYTHON_MIN_VERSION }} - name: Install Python dependencies @@ -127,7 +129,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Set up Python ${{ env.X_PYTHON_MIN_VERSION }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v5 with: python-version: ${{ env.X_PYTHON_MIN_VERSION }} - name: Install Python dependencies @@ -153,7 +155,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Set up Python ${{ env.X_PYTHON_MIN_VERSION }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v5 with: python-version: ${{ env.X_PYTHON_MIN_VERSION }} - name: Install Python dependencies @@ -174,7 +176,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Set up Python ${{ env.X_PYTHON_MIN_VERSION }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v5 with: python-version: ${{ env.X_PYTHON_MIN_VERSION }} - name: Install dependencies @@ -185,28 +187,45 @@ jobs: run: | python -m build + repository-check-copyright: + # This job checks that the copyright year in the header of all files is up to date + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 # Ensure full history is available + - name: Install required dependencies + run: | + sudo apt-get update + sudo apt-get install -y bash git + - name: Run copyright check + run: | + chmod +x ./etc/scripts/set_copyright_year.sh + ./etc/scripts/set_copyright_year.sh --check + compliance-tool-test: # This job runs the unittests on the python versions specified down at the matrix runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.8", "3.10", "3.12"] + python-version: ["3.9", "3.12"] defaults: run: working-directory: ./compliance_tool steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Install Python dependencies + # install the local sdk in editable mode so it does not get overwritten run: | python -m pip install --upgrade pip - pip install coverage - pip install -r requirements.txt + pip install -e ../sdk[dev] + pip install .[dev] - name: Test with coverage + unittest run: | coverage run --source=aas_compliance_tool -m unittest @@ -223,16 +242,17 @@ jobs: run: working-directory: ./compliance_tool steps: - - uses: actions/checkout@v2 - - name: Set up Python ${{ env.X_PYTHON_VERSION }} - uses: actions/setup-python@v2 + - uses: actions/checkout@v4 + - name: Set up Python ${{ env.X_PYTHON_MIN_VERSION }} + uses: actions/setup-python@v5 with: - python-version: ${{ env.X_PYTHON_VERSION }} + python-version: ${{ env.X_PYTHON_MIN_VERSION }} - name: Install Python dependencies + # install the local sdk in editable mode so it does not get overwritten run: | python -m pip install --upgrade pip - pip install pycodestyle mypy - pip install -r requirements.txt + pip install -e ../sdk[dev] + pip install .[dev] - name: Check typing with MyPy run: | mypy ./aas_compliance_tool test @@ -248,16 +268,17 @@ jobs: run: working-directory: ./compliance_tool steps: - - uses: actions/checkout@v2 - - name: Set up Python ${{ env.X_PYTHON_VERSION }} - uses: actions/setup-python@v2 + - uses: actions/checkout@v4 + - name: Set up Python ${{ env.X_PYTHON_MIN_VERSION }} + uses: actions/setup-python@v5 with: - python-version: ${{ env.X_PYTHON_VERSION }} + python-version: ${{ env.X_PYTHON_MIN_VERSION }} - name: Install Python dependencies + # install the local sdk in editable mode so it does not get overwritten run: | python -m pip install --upgrade pip - pip install pycodestyle mypy codeblocks - pip install -r requirements.txt + pip install -e ../sdk[dev] + pip install .[dev] - name: Check typing with MyPy run: | mypy <(codeblocks python README.md) @@ -276,18 +297,18 @@ jobs: run: working-directory: ./compliance_tool steps: - - uses: actions/checkout@v2 - - name: Set up Python ${{ env.X_PYTHON_VERSION }} - uses: actions/setup-python@v2 + - uses: actions/checkout@v4 + - name: Set up Python ${{ env.X_PYTHON_MIN_VERSION }} + uses: actions/setup-python@v5 with: - python-version: ${{ env.X_PYTHON_VERSION }} + python-version: ${{ env.X_PYTHON_MIN_VERSION }} - name: Install dependencies run: | python -m pip install --upgrade pip - pip install setuptools wheel + pip install build - name: Create source and wheel dist run: | - python setup.py sdist bdist_wheel + python -m build server-package: # This job checks if we can build our server package @@ -299,7 +320,7 @@ jobs: - uses: actions/checkout@v4 - name: Build the Docker image run: | - docker build -t basyx-python-server . + docker build -t basyx-python-server -f Dockerfile .. - name: Run container run: | docker run -d --name basyx-python-server basyx-python-server diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 15b22ea25..06491c0cb 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -5,16 +5,16 @@ on: types: [published] jobs: - publish: - # This job publishes the package to PyPI + sdk-publish: + # This job publishes the SDK package to PyPI runs-on: ubuntu-latest defaults: run: working-directory: ./sdk steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Set up Python 3.10 - uses: actions/setup-python@v2 + uses: actions/setup-python@v5 with: python-version: "3.10" - name: Install dependencies @@ -31,3 +31,30 @@ jobs: uses: pypa/gh-action-pypi-publish@release/v1 with: password: ${{ secrets.PYPI_ORG_TOKEN }} + + compliance-tool-publish: + # This job publishes the compliance_tool package to PyPI + runs-on: ubuntu-latest + defaults: + run: + working-directory: ./compliance_tool + steps: + - uses: actions/checkout@v4 + - name: Set up Python 3.10 + uses: actions/setup-python@v5 + with: + python-version: "3.10" + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install build + - name: Create source and wheel dist + # (2024-12-11, s-heppner) + # The PyPI Action expects the dist files in a toplevel `/dist` directory, + # so we have to specify this as output directory here. + run: | + python -m build --outdir ../dist + - name: Publish distribution to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + password: ${{ secrets.PYPI_ORG_TOKEN }} diff --git a/.gitignore b/.gitignore index dc7eddbb6..18b522c3a 100644 --- a/.gitignore +++ b/.gitignore @@ -28,6 +28,7 @@ sdk/test/adapter/schemas # Ignore dynamically generated version file sdk/basyx/version.py +compliance_tool/aas_compliance_tool/version.py # ignore the content of the server storage server/storage/ diff --git a/compliance_tool/__init__.py b/1.0.0 similarity index 100% rename from compliance_tool/__init__.py rename to 1.0.0 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ce3a4fd6b..a15c9c20c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -94,7 +94,7 @@ the further steps via the comments. In order to effectively communicate, there are some conventions to respect when writing commit messages and pull requests. -Similarily to when creating an issue, the commit title, as well as the PR title should +Similarly to when creating an issue, the commit title, as well as the PR title should be as short as possible, ideally 72 characters or fewer using imperative language. If a specific module is affected, please mention it at the beginning of the title. @@ -119,16 +119,41 @@ The following guidelines are for the commit or PR message text: via `https://link/to.pdf#Page=123` - Optionally, where applicable reference respective issues: `Fixes #123` -## Codestyle and Testing +## Code Quality +The Eclipse BaSyx Python project emphasizes high code quality. +To achieve this, we apply best practices where possible and have developed an extensive suite of tests that are +expected to pass for each Pull Request to the project. + +### Codestyle Our code follows the [PEP 8 -- Style Guide for Python Code](https://www.python.org/dev/peps/pep-0008/) with the following exceptions: - Line length is allowed to be up to 120 characters, though lines up to 100 characters are preferred. 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: +Before submitting any changes to the SDK, make sure to let `mypy` and `pycodestyle` check your code and run the unit +tests with Python's builtin `unittest`. + +### Testing +There are many automated checks implemented in the CI pipelines of this project, all of which are expected to pass +before new code can be added: + +- We check that the Python packages can be built. +- We run the developed unittests and aim for a code coverage of at least 80%. +- We perform static code analysis for type-checking and codestyle, not just in the code itself, but also in codeblocks + that are inside docstrings and the `README.md`. +- We check that the automatically generated developer documentation compiles. +- We check that the Python Versions we support match between the different subprojects in the monorepository and are + not End of Life. +- We check that the year in the copyright headers in each file (stemming from the license) is correct. + +> [!note] +> We strongly suggest to run the tests locally, before submitting a Pull Request, in order to accelerate the review +> process. + +### Testing the SDK +For testing the SDK locally on your machine, you can install the required tools like so: ```bash pip install .[dev] ``` @@ -142,12 +167,51 @@ Running all checks: mypy basyx test pycodestyle --max-line-length 120 basyx test python -m unittest +coverage run --source basyx --branch -m unittest +coverage report -m ``` -We aim to cover our code with test by at least 80%. To check test coverage, you can use `coverage`: +We aim to cover our code with tests by at least 80%. + +This should help you sort out the most important bugs in your code. +Note that there are more checks that run in the CI once you open a Pull Request. +If you want to run the additional checks, please refer to the [CI definition](./.github/workflows/ci.yml). +### Testing the Server +Currently, the automated server tests are still under development. +To test that the server is working, we expect to at least be able to build the docker images and run a container +of it without error. + +For that, you need to have Docker installed on your system. +In the directory with the `Dockerfile`: ```bash -pip install coverage +docker build -t basyx-python-server . +docker run --name basyx-python-server basyx-python-server +``` +Wait until you see the line: +``` +INFO success: quit_on_failure entered RUNNING state +``` + +### Testing the Compliance Tool +For the Compliance Tool, you can install the required tools like this (from the `./compliance_tool` directory): +```bash +pip install -e ../sdk[dev] +pip install .[dev] +``` +The first line installs the SDK and its dependencies, the second the developer dependencies for the compliance tool +itself. + +Then you can run the checks via: +```bash +mypy basyx test +pycodestyle --max-line-length 120 basyx test +python -m unittest coverage run --source basyx --branch -m unittest coverage report -m ``` + +We aim to cover our code with tests by at least 80%. +This should help you sort out the most important bugs in your code. +Note that there are more checks that run in the CI once you open a Pull Request. +If you want to run the additional checks, please refer to the [CI definition](./.github/workflows/ci.yml). diff --git a/README.md b/README.md index f271c4d5b..82e56055f 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ These are the currently implemented specifications: | Specification | Version | |---------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| Part 1: Metamodel | [v3.0.1 (01001-3-1)](https://industrialdigitaltwin.org/wp-content/uploads/2024/06/IDTA-01001-3-0-1_SpecificationAssetAdministrationShell_Part1_Metamodel.pdf) | +| Part 1: Metamodel | [v3.0.1 (01001-3-0-1)](https://industrialdigitaltwin.org/wp-content/uploads/2024/06/IDTA-01001-3-0-1_SpecificationAssetAdministrationShell_Part1_Metamodel.pdf) | | Schemata (JSONSchema, XSD) | [v3.0.8 (IDTA-01001-3-0-1_schemasV3.0.8)](https://github.com/admin-shell-io/aas-specs/releases/tag/IDTA-01001-3-0-1_schemasV3.0.8) | | Part 2: API | [v3.0 (01002-3-0)](https://industrialdigitaltwin.org/en/wp-content/uploads/sites/2/2023/06/IDTA-01002-3-0_SpecificationAssetAdministrationShell_Part2_API_.pdf) | | Part 3a: Data Specification IEC 61360 | [v3.0 (01003-a-3-0)](https://industrialdigitaltwin.org/wp-content/uploads/2023/04/IDTA-01003-a-3-0_SpecificationAssetAdministrationShell_Part3a_DataSpecification_IEC61360.pdf) | diff --git a/compliance_tool/README.md b/compliance_tool/README.md index 40face78e..185e157a0 100644 --- a/compliance_tool/README.md +++ b/compliance_tool/README.md @@ -6,7 +6,7 @@ Following functionalities are supported: * 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, json or aasx file is readable 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 official schema * check if the data in a given xml, json or aasx file is the same as the example data * check if two given xml, json or aasx files contain the same Asset Administration Shell elements in any order diff --git a/compliance_tool/aas_compliance_tool/cli.py b/compliance_tool/aas_compliance_tool/cli.py index cfd008140..3ffd7d219 100644 --- a/compliance_tool/aas_compliance_tool/cli.py +++ b/compliance_tool/aas_compliance_tool/cli.py @@ -1,4 +1,4 @@ -# Copyright (c) 2020 the Eclipse BaSyx Authors +# Copyright (c) 2025 the Eclipse BaSyx Authors # # This program and the accompanying materials are made available under the terms of the MIT License, available in # the LICENSE file of this project. @@ -19,11 +19,12 @@ from basyx.aas.adapter import aasx from basyx.aas.adapter.xml import write_aas_xml_file -from basyx.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 basyx.aas.compliance_tool.state_manager import ComplianceToolStateManager, Status +from aas_compliance_tool.state_manager import ComplianceToolStateManager, Status def parse_cli_arguments() -> argparse.ArgumentParser: @@ -71,7 +72,7 @@ def parse_cli_arguments() -> argparse.ArgumentParser: '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 " - "choosen") + "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) diff --git a/compliance_tool/aas_compliance_tool/compliance_check_aasx.py b/compliance_tool/aas_compliance_tool/compliance_check_aasx.py index 74c0a3356..9dfbb982c 100644 --- a/compliance_tool/aas_compliance_tool/compliance_check_aasx.py +++ b/compliance_tool/aas_compliance_tool/compliance_check_aasx.py @@ -1,4 +1,4 @@ -# Copyright (c) 2020 the Eclipse BaSyx Authors +# Copyright (c) 2025 the Eclipse BaSyx Authors # # This program and the accompanying materials are made available under the terms of the MIT License, available in # the LICENSE file of this project. @@ -19,14 +19,14 @@ import pyecma376_2 -from . import compliance_check_json, compliance_check_xml +from aas_compliance_tool import compliance_check_json, compliance_check_xml from basyx.aas import model from basyx.aas.adapter import aasx from basyx.aas.adapter.xml import xml_deserialization from basyx.aas.adapter.json import json_deserialization from basyx.aas.examples.data import example_aas, create_example_aas_binding from basyx.aas.examples.data._helper import AASDataChecker, DataChecker -from .state_manager import ComplianceToolStateManager, Status +from aas_compliance_tool.state_manager import ComplianceToolStateManager, Status def check_deserialization(file_path: str, state_manager: ComplianceToolStateManager, diff --git a/compliance_tool/aas_compliance_tool/compliance_check_json.py b/compliance_tool/aas_compliance_tool/compliance_check_json.py index 1469e8355..2050f570c 100644 --- a/compliance_tool/aas_compliance_tool/compliance_check_json.py +++ b/compliance_tool/aas_compliance_tool/compliance_check_json.py @@ -1,4 +1,4 @@ -# Copyright (c) 2020 the Eclipse BaSyx Authors +# Copyright (c) 2025 the Eclipse BaSyx Authors # # This program and the accompanying materials are made available under the terms of the MIT License, available in # the LICENSE file of this project. @@ -20,7 +20,7 @@ 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 .state_manager import ComplianceToolStateManager, Status +from aas_compliance_tool.state_manager import ComplianceToolStateManager, Status JSON_SCHEMA_FILE = os.path.join(os.path.dirname(__file__), 'schemas/aasJSONSchema.json') diff --git a/compliance_tool/aas_compliance_tool/compliance_check_xml.py b/compliance_tool/aas_compliance_tool/compliance_check_xml.py index 51fb06068..6d4e10c55 100644 --- a/compliance_tool/aas_compliance_tool/compliance_check_xml.py +++ b/compliance_tool/aas_compliance_tool/compliance_check_xml.py @@ -1,4 +1,4 @@ -# Copyright (c) 2020 the Eclipse BaSyx Authors +# Copyright (c) 2025 the Eclipse BaSyx Authors # # This program and the accompanying materials are made available under the terms of the MIT License, available in # the LICENSE file of this project. @@ -20,7 +20,7 @@ from basyx.aas.adapter.xml import xml_deserialization from basyx.aas.examples.data import example_aas, create_example from basyx.aas.examples.data._helper import AASDataChecker -from .state_manager import ComplianceToolStateManager, Status +from aas_compliance_tool.state_manager import ComplianceToolStateManager, Status XML_SCHEMA_FILE = os.path.join(os.path.dirname(__file__), 'schemas/aasXMLSchema.xsd') diff --git a/compliance_tool/aas_compliance_tool/state_manager.py b/compliance_tool/aas_compliance_tool/state_manager.py index 3116fe150..33153038f 100644 --- a/compliance_tool/aas_compliance_tool/state_manager.py +++ b/compliance_tool/aas_compliance_tool/state_manager.py @@ -1,4 +1,4 @@ -# Copyright (c) 2020 the Eclipse BaSyx Authors +# Copyright (c) 2025 the Eclipse BaSyx Authors # # This program and the accompanying materials are made available under the terms of the MIT License, available in # the LICENSE file of this project. diff --git a/compliance_tool/pyproject.toml b/compliance_tool/pyproject.toml new file mode 100644 index 000000000..c907f90df --- /dev/null +++ b/compliance_tool/pyproject.toml @@ -0,0 +1,60 @@ +[build-system] +requires = [ + "setuptools>=45", + "wheel", + "setuptools_scm[toml]>=6.2" +] +build-backend = "setuptools.build_meta" + +[tool.setuptools_scm] +# Configure setuptools_scm for version management: +# - Automatically infers the version number from the most recent git tag +# - Generates a version.py file in the package directory +# - Allows for automatic versioning between releases (e.g., 1.0.1.dev4+g12345) +# If you want to use the version anywhere in the code, use +# ``` +# from aas_compliance_tool.version import version +# print(f"Project version: {version}") +# ``` +root = ".." # Defines the path to the root of the repository +version_file = "aas_compliance_tool/version.py" + +[project] +name = "aas_compliance_tool" +dynamic = ["version"] +description = "AAS compliance checker based on the Eclipse BaSyx Python SDK" +authors = [ + { name = "The AAS Compliance Tool authors", email = "admins@iat.rwth-aachen.de" } +] +readme = "README.md" +license = { file = "LICENSE" } +classifiers = [ + "Programming Language :: Python :: 3", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", + "Development Status :: 5 - Production/Stable" +] +requires-python = ">=3.9" +dependencies = [ + "pyecma376-2>=0.2.4", + "jsonschema>=4.21.1", + "basyx-python-sdk>=1.0.0", +] + +[project.optional-dependencies] +dev = [ + "mypy", + "pycodestyle", + "codeblocks", + "coverage", +] + +[project.urls] +"Homepage" = "https://github.com/eclipse-basyx/basyx-python-sdk" + +[tool.setuptools] +packages = { find = { include = ["aas_compliance_tool*"], exclude = ["test*"] } } + +[tool.setuptools.package-data] +aas_compliance_tool = ["py.typed"] + diff --git a/compliance_tool/requirements.txt b/compliance_tool/requirements.txt deleted file mode 100644 index 89a1d39df..000000000 --- a/compliance_tool/requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -pyecma376-2>=0.2.4 -jsonschema>=4.21.1 -basyx-python-sdk>=1.0.0 diff --git a/compliance_tool/setup.py b/compliance_tool/setup.py deleted file mode 100644 index c214d4bcd..000000000 --- a/compliance_tool/setup.py +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env python3 -# Copyright (c) 2019-2021 the Eclipse BaSyx Authors -# -# This program and the accompanying materials are made available under the terms of the MIT License, available in -# the LICENSE file of this project. -# -# SPDX-License-Identifier: MIT - -import setuptools - -with open("README.md", "r", encoding='utf-8') as fh: - long_description = fh.read() - -setuptools.setup( - name="aas_compliance_tool", - version="1.0.0", - author="The AAS Compliance Tool authors", - description="AAS compliance checker based on the Eclipse BaSyx Python SDK", - long_description=long_description, - long_description_content_type="text/markdown", - url="https://github.com/rwth-iat/aas-compliance-tool", - packages=setuptools.find_packages(exclude=["test", "test.*"]), - zip_safe=False, - package_data={ - "aas_compliance_tool": ["py.typed", "schemas/aasJSONSchema.json", "schemas/aasXMLSchema.xsd"], - }, - classifiers=[ - "Programming Language :: Python :: 3", - "License :: OSI Approved :: MIT License", - "Operating System :: OS Independent", - "Development Status :: 5 - Production/Stable", - ], - entry_points={ - 'console_scripts': [ - "aas-compliance-check = aas_compliance_tool:main" - ] - }, - python_requires='>=3.8', - install_requires=[ - 'pyecma376-2>=0.2.4', - 'basyx-python-sdk>=1.0.0', - ] -) diff --git a/compliance_tool/test/files/test_demo_full_example.json b/compliance_tool/test/files/test_demo_full_example.json index 31fde424d..68e154f73 100644 --- a/compliance_tool/test/files/test_demo_full_example.json +++ b/compliance_tool/test/files/test_demo_full_example.json @@ -499,7 +499,7 @@ }, { "language": "de", - "text": "Ein Beispiel-BillofMaterial-Submodel f\u00fcr eine Test-Anwendung" + "text": "Ein Beispiel-BillOfMaterial-Submodel f\u00fcr eine Test-Anwendung" } ], "modelType": "Submodel", @@ -1309,7 +1309,7 @@ }, { "language": "de", - "text": "Beispielswert f\u00fcr ein MulitLanguageProperty-Element" + "text": "Beispielwert f\u00fcr ein MultiLanguageProperty-Element" } ], "valueId": { @@ -2281,7 +2281,7 @@ }, { "language": "de", - "text": "Beispiel MulitLanguageProperty Element" + "text": "Beispiel MultiLanguageProperty Element" } ], "modelType": "MultiLanguageProperty", @@ -2301,7 +2301,7 @@ }, { "language": "de", - "text": "Beispielswert f\u00fcr ein MulitLanguageProperty-Element" + "text": "Beispielwert f\u00fcr ein MultiLanguageProperty-Element" } ] }, @@ -2825,7 +2825,7 @@ }, { "language": "de", - "text": "Beispiel MulitLanguageProperty Element" + "text": "Beispiel MultiLanguageProperty Element" } ], "modelType": "MultiLanguageProperty", diff --git a/compliance_tool/test/files/test_demo_full_example.xml b/compliance_tool/test/files/test_demo_full_example.xml index 39fae5599..759e3c403 100644 --- a/compliance_tool/test/files/test_demo_full_example.xml +++ b/compliance_tool/test/files/test_demo_full_example.xml @@ -486,7 +486,7 @@ de - Ein Beispiel-BillofMaterial-Submodel für eine Test-Anwendung + Ein Beispiel-BillOfMaterial-Submodel für eine Test-Anwendung @@ -1424,7 +1424,7 @@ de - Beispielswert für ein MulitLanguageProperty-Element + Beispielwert für ein MultiLanguageProperty-Element @@ -2109,7 +2109,7 @@ de - Beispiel MulitLanguageProperty Element + Beispiel MultiLanguageProperty Element @@ -2128,7 +2128,7 @@ de - Beispielswert für ein MulitLanguageProperty-Element + Beispielwert für ein MultiLanguageProperty-Element @@ -2632,7 +2632,7 @@ de - Beispiel MulitLanguageProperty Element + Beispiel MultiLanguageProperty Element diff --git a/compliance_tool/test/files/test_demo_full_example_json_aasx/aasx/data.json b/compliance_tool/test/files/test_demo_full_example_json_aasx/aasx/data.json index 7172735e6..7ddb5f17c 100644 --- a/compliance_tool/test/files/test_demo_full_example_json_aasx/aasx/data.json +++ b/compliance_tool/test/files/test_demo_full_example_json_aasx/aasx/data.json @@ -507,7 +507,7 @@ }, { "language": "de", - "text": "Ein Beispiel-BillofMaterial-Submodel f\u00fcr eine Test-Anwendung" + "text": "Ein Beispiel-BillOfMaterial-Submodel f\u00fcr eine Test-Anwendung" } ], "modelType": "Submodel", @@ -1317,7 +1317,7 @@ }, { "language": "de", - "text": "Beispielswert f\u00fcr ein MulitLanguageProperty-Element" + "text": "Beispielwert f\u00fcr ein MultiLanguageProperty-Element" } ], "valueId": { @@ -2289,7 +2289,7 @@ }, { "language": "de", - "text": "Beispiel MulitLanguageProperty Element" + "text": "Beispiel MultiLanguageProperty Element" } ], "modelType": "MultiLanguageProperty", @@ -2309,7 +2309,7 @@ }, { "language": "de", - "text": "Beispielswert f\u00fcr ein MulitLanguageProperty-Element" + "text": "Beispielwert f\u00fcr ein MultiLanguageProperty-Element" } ] }, @@ -2833,7 +2833,7 @@ }, { "language": "de", - "text": "Beispiel MulitLanguageProperty Element" + "text": "Beispiel MultiLanguageProperty Element" } ], "modelType": "MultiLanguageProperty", diff --git a/compliance_tool/test/files/test_demo_full_example_wrong_attribute.json b/compliance_tool/test/files/test_demo_full_example_wrong_attribute.json index 8f816697d..d748e7908 100644 --- a/compliance_tool/test/files/test_demo_full_example_wrong_attribute.json +++ b/compliance_tool/test/files/test_demo_full_example_wrong_attribute.json @@ -499,7 +499,7 @@ }, { "language": "de", - "text": "Ein Beispiel-BillofMaterial-Submodel f\u00fcr eine Test-Anwendung" + "text": "Ein Beispiel-BillOfMaterial-Submodel f\u00fcr eine Test-Anwendung" } ], "modelType": "Submodel", @@ -1309,7 +1309,7 @@ }, { "language": "de", - "text": "Beispielswert f\u00fcr ein MulitLanguageProperty-Element" + "text": "Beispielwert f\u00fcr ein MultiLanguageProperty-Element" } ], "valueId": { @@ -2281,7 +2281,7 @@ }, { "language": "de", - "text": "Beispiel MulitLanguageProperty Element" + "text": "Beispiel MultiLanguageProperty Element" } ], "modelType": "MultiLanguageProperty", @@ -2301,7 +2301,7 @@ }, { "language": "de", - "text": "Beispielswert f\u00fcr ein MulitLanguageProperty-Element" + "text": "Beispielwert f\u00fcr ein MultiLanguageProperty-Element" } ] }, @@ -2825,7 +2825,7 @@ }, { "language": "de", - "text": "Beispiel MulitLanguageProperty Element" + "text": "Beispiel MultiLanguageProperty Element" } ], "modelType": "MultiLanguageProperty", diff --git a/compliance_tool/test/files/test_demo_full_example_wrong_attribute.xml b/compliance_tool/test/files/test_demo_full_example_wrong_attribute.xml index 061ee58b6..94c47d36b 100644 --- a/compliance_tool/test/files/test_demo_full_example_wrong_attribute.xml +++ b/compliance_tool/test/files/test_demo_full_example_wrong_attribute.xml @@ -486,7 +486,7 @@ de - Ein Beispiel-BillofMaterial-Submodel für eine Test-Anwendung + Ein Beispiel-BillOfMaterial-Submodel für eine Test-Anwendung @@ -1424,7 +1424,7 @@ de - Beispielswert für ein MulitLanguageProperty-Element + Beispielwert für ein MultiLanguageProperty-Element @@ -2109,7 +2109,7 @@ de - Beispiel MulitLanguageProperty Element + Beispiel MultiLanguageProperty Element @@ -2128,7 +2128,7 @@ de - Beispielswert für ein MulitLanguageProperty-Element + Beispielwert für ein MultiLanguageProperty-Element @@ -2632,7 +2632,7 @@ de - Beispiel MulitLanguageProperty Element + Beispiel MultiLanguageProperty Element diff --git a/compliance_tool/test/files/test_demo_full_example_xml_aasx/aasx/data.xml b/compliance_tool/test/files/test_demo_full_example_xml_aasx/aasx/data.xml index c0eb40769..cb203c9d8 100644 --- a/compliance_tool/test/files/test_demo_full_example_xml_aasx/aasx/data.xml +++ b/compliance_tool/test/files/test_demo_full_example_xml_aasx/aasx/data.xml @@ -494,7 +494,7 @@ de - Ein Beispiel-BillofMaterial-Submodel für eine Test-Anwendung + Ein Beispiel-BillOfMaterial-Submodel für eine Test-Anwendung @@ -1432,7 +1432,7 @@ de - Beispielswert für ein MulitLanguageProperty-Element + Beispielwert für ein MultiLanguageProperty-Element @@ -2117,7 +2117,7 @@ de - Beispiel MulitLanguageProperty Element + Beispiel MultiLanguageProperty Element @@ -2136,7 +2136,7 @@ de - Beispielswert für ein MulitLanguageProperty-Element + Beispielwert für ein MultiLanguageProperty-Element @@ -2640,7 +2640,7 @@ de - Beispiel MulitLanguageProperty Element + Beispiel MultiLanguageProperty Element diff --git a/compliance_tool/test/files/test_demo_full_example_xml_wrong_attribute_aasx/aasx/data.xml b/compliance_tool/test/files/test_demo_full_example_xml_wrong_attribute_aasx/aasx/data.xml index 5e952db2f..7f2531f6c 100644 --- a/compliance_tool/test/files/test_demo_full_example_xml_wrong_attribute_aasx/aasx/data.xml +++ b/compliance_tool/test/files/test_demo_full_example_xml_wrong_attribute_aasx/aasx/data.xml @@ -494,7 +494,7 @@ de - Ein Beispiel-BillofMaterial-Submodel für eine Test-Anwendung + Ein Beispiel-BillOfMaterial-Submodel für eine Test-Anwendung @@ -1432,7 +1432,7 @@ de - Beispielswert für ein MulitLanguageProperty-Element + Beispielwert für ein MultiLanguageProperty-Element @@ -2117,7 +2117,7 @@ de - Beispiel MulitLanguageProperty Element + Beispiel MultiLanguageProperty Element @@ -2136,7 +2136,7 @@ de - Beispielswert für ein MulitLanguageProperty-Element + Beispielwert für ein MultiLanguageProperty-Element @@ -2640,7 +2640,7 @@ de - Beispiel MulitLanguageProperty Element + Beispiel MultiLanguageProperty Element diff --git a/compliance_tool/test/test_aas_compliance_tool.py b/compliance_tool/test/test_aas_compliance_tool.py index 8cd3004db..cb631cf9c 100644 --- a/compliance_tool/test/test_aas_compliance_tool.py +++ b/compliance_tool/test/test_aas_compliance_tool.py @@ -1,4 +1,4 @@ -# Copyright (c) 2020 the Eclipse BaSyx Authors +# Copyright (c) 2025 the Eclipse BaSyx Authors # # This program and the accompanying materials are made available under the terms of the MIT License, available in # the LICENSE file of this project. @@ -13,8 +13,6 @@ import io import tempfile - -import basyx.aas.compliance_tool from basyx.aas import model from basyx.aas.adapter import aasx from basyx.aas.adapter.json import read_aas_json_file @@ -25,14 +23,21 @@ def _run_compliance_tool(*compliance_tool_args, **kwargs) -> subprocess.CompletedProcess: """ - This function runs the compliance tool using subprocess.run() while adjusting the PYTHONPATH environment variable - and setting the stdout and stderr parameters of subprocess.run() to PIPE. + This function runs the compliance tool using subprocess.run() + and sets the stdout and stderr parameters of subprocess.run() to PIPE. Positional arguments are passed to the compliance tool, while keyword arguments are passed to subprocess.run(). """ env = os.environ.copy() - env['PYTHONPATH'] = "{}:{}".format(os.environ.get('PYTHONPATH', ''), - os.path.join(os.path.dirname(basyx.__file__), os.pardir)) - compliance_tool_path = os.path.join(os.path.dirname(basyx.aas.compliance_tool.__file__), 'cli.py') + parent_dir = os.path.join( + os.path.dirname(os.path.dirname(__file__)), + 'aas_compliance_tool' + ) + env["PYTHONPATH"] = parent_dir + os.pathsep + env.get("PYTHONPATH", "") + compliance_tool_path = os.path.join( + os.path.dirname(os.path.dirname(__file__)), + 'aas_compliance_tool', + 'cli.py' + ) return subprocess.run([sys.executable, compliance_tool_path] + list(compliance_tool_args), stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env, **kwargs) diff --git a/compliance_tool/test/test_compliance_check_aasx.py b/compliance_tool/test/test_compliance_check_aasx.py index 78d9e04b7..953d4e35e 100644 --- a/compliance_tool/test/test_compliance_check_aasx.py +++ b/compliance_tool/test/test_compliance_check_aasx.py @@ -1,4 +1,4 @@ -# Copyright (c) 2020 the Eclipse BaSyx Authors +# Copyright (c) 2025 the Eclipse BaSyx Authors # # This program and the accompanying materials are made available under the terms of the MIT License, available in # the LICENSE file of this project. @@ -7,8 +7,8 @@ import os import unittest -from basyx.aas.compliance_tool import compliance_check_aasx as compliance_tool -from basyx.aas.compliance_tool.state_manager import ComplianceToolStateManager, Status +from aas_compliance_tool import compliance_check_aasx as compliance_tool +from aas_compliance_tool.state_manager import ComplianceToolStateManager, Status class ComplianceToolAASXTest(unittest.TestCase): @@ -20,7 +20,9 @@ def test_check_deserialization(self) -> None: compliance_tool.check_deserialization(file_path_1, manager) self.assertEqual(2, len(manager.steps)) self.assertEqual(Status.FAILED, manager.steps[0].status) - self.assertIn("is not a valid ECMA376-2 (OPC) file", manager.format_step(0, verbose_level=1)) + # we should expect a FileNotFound error here since the file does not exist and that is the first error + # aasx.py will throw if you try to open a file that does not exist. + self.assertIn("No such file or directory:", manager.format_step(0, verbose_level=1)) self.assertEqual(Status.NOT_EXECUTED, manager.steps[1].status) # Todo add more tests for checking wrong aasx files diff --git a/compliance_tool/test/test_compliance_check_json.py b/compliance_tool/test/test_compliance_check_json.py index b6201d108..c738c0f13 100644 --- a/compliance_tool/test/test_compliance_check_json.py +++ b/compliance_tool/test/test_compliance_check_json.py @@ -1,4 +1,4 @@ -# Copyright (c) 2020 the Eclipse BaSyx Authors +# Copyright (c) 2025 the Eclipse BaSyx Authors # # This program and the accompanying materials are made available under the terms of the MIT License, available in # the LICENSE file of this project. @@ -7,8 +7,8 @@ import os import unittest -import basyx.aas.compliance_tool.compliance_check_json as compliance_tool -from basyx.aas.compliance_tool.state_manager import ComplianceToolStateManager, Status +from aas_compliance_tool import compliance_check_json as compliance_tool +from aas_compliance_tool.state_manager import ComplianceToolStateManager, Status class ComplianceToolJsonTest(unittest.TestCase): diff --git a/compliance_tool/test/test_compliance_check_xml.py b/compliance_tool/test/test_compliance_check_xml.py index a1658e508..63089e186 100644 --- a/compliance_tool/test/test_compliance_check_xml.py +++ b/compliance_tool/test/test_compliance_check_xml.py @@ -1,4 +1,4 @@ -# Copyright (c) 2020 the Eclipse BaSyx Authors +# Copyright (c) 2025 the Eclipse BaSyx Authors # # This program and the accompanying materials are made available under the terms of the MIT License, available in # the LICENSE file of this project. @@ -7,8 +7,8 @@ import os import unittest -import basyx.aas.compliance_tool.compliance_check_xml as compliance_tool -from basyx.aas.compliance_tool.state_manager import ComplianceToolStateManager, Status +from aas_compliance_tool import compliance_check_xml as compliance_tool +from aas_compliance_tool.state_manager import ComplianceToolStateManager, Status class ComplianceToolXmlTest(unittest.TestCase): diff --git a/compliance_tool/test/test_state_manager.py b/compliance_tool/test/test_state_manager.py index fc1c8260e..7654203d1 100644 --- a/compliance_tool/test/test_state_manager.py +++ b/compliance_tool/test/test_state_manager.py @@ -1,4 +1,4 @@ -# Copyright (c) 2020 the Eclipse BaSyx Authors +# Copyright (c) 2025 the Eclipse BaSyx Authors # # This program and the accompanying materials are made available under the terms of the MIT License, available in # the LICENSE file of this project. @@ -7,7 +7,7 @@ import logging import unittest -from basyx.aas.compliance_tool.state_manager import ComplianceToolStateManager, Status +from aas_compliance_tool.state_manager import ComplianceToolStateManager, Status from basyx.aas.examples.data._helper import DataChecker diff --git a/etc/scripts/set_copyright_year.sh b/etc/scripts/set_copyright_year.sh index 5ff1fd5f9..de6c97eea 100755 --- a/etc/scripts/set_copyright_year.sh +++ b/etc/scripts/set_copyright_year.sh @@ -9,7 +9,36 @@ # # The script will check the first two lines for a copyright # notice (in case the first line is a shebang). +# +# Run this script with --check to have it raise an error if it +# would change anything. + + +# Set CHECK_MODE based on whether --check is passed + CHECK_MODE=false + if [[ "$1" == "--check" ]]; then + CHECK_MODE=true + shift # Remove --check from the arguments + fi while read -rd $'\0' year file; do - sed -i "1,2s/^\(# Copyright (c) \)[[:digit:]]\{4,\}/\1$year/" "$file" -done < <(git ls-files -z "$@" | xargs -0I{} git log -1 -z --format="%ad {}" --date="format:%Y" "{}") + + # Extract the first year from the copyright notice + current_year=$(sed -n '1,2s/^\(# Copyright (c) \)\([[:digit:]]\{4,\}\).*/\2/p' "$file") + + # Skip the file if no year is found + if [[ -z "$current_year" ]]; then + continue + fi + + if $CHECK_MODE && [[ "$current_year" != "$year" ]]; then + echo "Error: Copyright year mismatch in file $file. Expected $year, found $current_year." + exit 1 + fi + + if ! $CHECK_MODE && [[ "$current_year" != "$year" ]]; then + sed -i "1,2s/^\(# Copyright (c) \)[[:digit:]]\{4,\}/\1$year/" "$file" + echo "Updated copyright year in $file" + fi +done < <(git ls-files -z "$@" | xargs -0I{} git log -1 -z --format="%cd {}" --date="format:%Y" -- "{}") + diff --git a/sdk/.readthedocs.yaml b/sdk/.readthedocs.yaml index 92880ab17..e64e5daaf 100644 --- a/sdk/.readthedocs.yaml +++ b/sdk/.readthedocs.yaml @@ -12,6 +12,7 @@ sphinx: configuration: docs/source/conf.py python: - install: - - requirements: requirements.txt - - requirements: docs/add-requirements.txt + install: + - method: pip + path: . + - requirements: docs/add-requirements.txt diff --git a/sdk/README.md b/sdk/README.md index fde12b465..f63f7afcb 100644 --- a/sdk/README.md +++ b/sdk/README.md @@ -38,19 +38,23 @@ file. ## Dependencies The BaSyx Python SDK requires the following Python packages to be installed for production usage. These dependencies are listed in -`setup.py` to be fetched automatically when installing with `pip`: +`pyproject.toml` to be fetched automatically when installing with `pip`: * `lxml` (BSD 3-clause License, using `libxml2` under MIT License) * `python-dateutil` (BSD 3-clause License) * `pyecma376-2` (Apache License v2.0) * `urllib3` (MIT License) * `Werkzeug` (BSD 3-clause License) -Optional production usage dependencies: -* For using the Compliance Tool to validate JSON files against the JSON Schema: `jsonschema` and its -dependencies (MIT License, Apache License, PSF License) - -Development/testing/documentation/example dependencies (see `requirements.txt`): -* `jsonschema` and its dependencies (MIT License, Apache License, PSF License) +Development/testing/documentation/example dependencies: +* `mypy` (MIT License) +* `pycodestyle` (MIT License) +* `codeblocks` (Apache License v2.0) +* `coverage` (Apache License v2.0) +* `jsonschema` (MIT License, Apache License, PSF License) +* `schemathesis` (MIT License) +* `hypothesis` (MPL v2.0) +* `lxml-stubs` (Apache License) +* `types-python-dateutil` (Apache License v2.0) Dependencies for building the documentation (see `docs/add-requirements.txt`): * `Sphinx` and its dependencies (BSD 2-clause License, MIT License, Apache License) @@ -130,7 +134,3 @@ For further examples and tutorials, check out the `basyx.aas.examples`-package. ### Documentation A detailed, complete API documentation is available on Read the Docs: https://basyx-python-sdk.readthedocs.io - -### Compliance Tool - -The compliance tool functionality moved to [github.com/rwth-iat/aas-compliance-tool](https://github.com/rwth-iat/aas-compliance-tool). diff --git a/sdk/basyx/aas/adapter/_generic.py b/sdk/basyx/aas/adapter/_generic.py index 6a37c7412..79c98fc8c 100644 --- a/sdk/basyx/aas/adapter/_generic.py +++ b/sdk/basyx/aas/adapter/_generic.py @@ -1,4 +1,4 @@ -# Copyright (c) 2023 the Eclipse BaSyx Authors +# Copyright (c) 2025 the Eclipse BaSyx Authors # # This program and the accompanying materials are made available under the terms of the MIT License, available in # the LICENSE file of this project. diff --git a/sdk/basyx/aas/adapter/aasx.py b/sdk/basyx/aas/adapter/aasx.py index 1c27640fa..0b66e533e 100644 --- a/sdk/basyx/aas/adapter/aasx.py +++ b/sdk/basyx/aas/adapter/aasx.py @@ -1,4 +1,4 @@ -# Copyright (c) 2024 the Eclipse BaSyx Authors +# Copyright (c) 2025 the Eclipse BaSyx Authors # # This program and the accompanying materials are made available under the terms of the MIT License, available in # the LICENSE file of this project. @@ -402,17 +402,19 @@ def write_aas(self, concept_descriptions: List[model.ConceptDescription] = [] for identifiable in objects_to_be_written: 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: - logger.info("semanticId %s does not reference a ConceptDescription.", str(semantic_id)) continue try: cd = semantic_id.resolve(object_store) except KeyError: - logger.info("ConceptDescription for semanticId %s not found in object store.", str(semantic_id)) + logger.warning("ConceptDescription for semanticId %s not found in object store. Skipping it.", + str(semantic_id)) continue except model.UnexpectedTypeError as e: - logger.error("semanticId %s resolves to %s, which is not a ConceptDescription", + logger.error("semanticId %s resolves to %s, which is not a ConceptDescription. Skipping it.", str(semantic_id), e.value) continue concept_descriptions.append(cd) diff --git a/sdk/basyx/aas/adapter/http.py b/sdk/basyx/aas/adapter/http.py index a4d7ab289..12bd533f3 100644 --- a/sdk/basyx/aas/adapter/http.py +++ b/sdk/basyx/aas/adapter/http.py @@ -1,4 +1,4 @@ -# Copyright (c) 2024 the Eclipse BaSyx Authors +# Copyright (c) 2025 the Eclipse BaSyx Authors # # This program and the accompanying materials are made available under the terms of the MIT License, available in # the LICENSE file of this project. @@ -42,13 +42,14 @@ import io import json import itertools +import urllib from lxml import etree import werkzeug.exceptions import werkzeug.routing import werkzeug.urls import werkzeug.utils -from werkzeug.exceptions import BadRequest, Conflict, NotFound, UnprocessableEntity +from werkzeug.exceptions import BadRequest, Conflict, NotFound from werkzeug.routing import MapAdapter, Rule, Submount from werkzeug.wrappers import Request, Response from werkzeug.datastructures import FileStorage @@ -252,7 +253,13 @@ def http_exception_to_response(exception: werkzeug.exceptions.HTTPException, res def is_stripped_request(request: Request) -> bool: - return request.args.get("level") == "core" + level = request.args.get("level") + if level not in {"deep", "core", None}: + raise BadRequest(f"Level {level} is not a valid level!") + extent = request.args.get("extent") + if extent is not None: + raise werkzeug.exceptions.NotImplemented(f"The parameter extent is not yet implemented for this server!") + return level == "core" T = TypeVar("T") @@ -300,7 +307,7 @@ def check_type_supportance(cls, type_: type): @classmethod def assert_type(cls, obj: object, type_: Type[T]) -> T: if not isinstance(obj, type_): - raise UnprocessableEntity(f"Object {obj!r} is not of type {type_.__name__}!") + raise BadRequest(f"Object {obj!r} is not of type {type_.__name__}!") return obj @classmethod @@ -312,10 +319,10 @@ def json_list(cls, data: Union[str, bytes], expect_type: Type[T], stripped: bool parsed = json.loads(data, cls=decoder) if not isinstance(parsed, list): if not expect_single: - raise UnprocessableEntity(f"Expected List[{expect_type.__name__}], got {parsed!r}!") + raise BadRequest(f"Expected List[{expect_type.__name__}], got {parsed!r}!") parsed = [parsed] elif expect_single: - raise UnprocessableEntity(f"Expected a single object of type {expect_type.__name__}, got {parsed!r}!") + raise BadRequest(f"Expected a single object of type {expect_type.__name__}, got {parsed!r}!") # TODO: the following is ugly, but necessary because references aren't self-identified objects # in the json schema # TODO: json deserialization will always create an ModelReference[Submodel], xml deserialization determines @@ -339,7 +346,7 @@ def json_list(cls, data: Union[str, bytes], expect_type: Type[T], stripped: bool return [constructor(obj, *args) for obj in parsed] except (KeyError, ValueError, TypeError, json.JSONDecodeError, model.AASConstraintViolation) as e: - raise UnprocessableEntity(str(e)) from e + raise BadRequest(str(e)) from e return [cls.assert_type(obj, expect_type) for obj in parsed] @@ -369,9 +376,9 @@ def xml(cls, data: bytes, expect_type: Type[T], stripped: bool) -> T: f: BaseException = e while f.__cause__ is not None: f = f.__cause__ - raise UnprocessableEntity(str(f)) from e + raise BadRequest(str(f)) from e except (etree.XMLSyntaxError, model.AASConstraintViolation) as e: - raise UnprocessableEntity(str(e)) from e + raise BadRequest(str(e)) from e return cls.assert_type(rv, expect_type) @classmethod @@ -647,13 +654,13 @@ def _get_submodel_reference(cls, aas: model.AssetAdministrationShell, submodel_i @classmethod def _get_slice(cls, request: Request, iterator: Iterable[T]) -> Tuple[Iterator[T], int]: limit_str = request.args.get('limit', default="10") - cursor_str = request.args.get('cursor', default="0") + cursor_str = request.args.get('cursor', default="1") try: - limit, cursor = int(limit_str), int(cursor_str) + limit, cursor = int(limit_str), int(cursor_str) - 1 # cursor is 1-indexed if limit < 0 or cursor < 0: raise ValueError except ValueError: - raise BadRequest("Cursor and limit must be positive integers!") + raise BadRequest("Limit can not be negative, cursor must be positive!") start_index = cursor end_index = cursor + limit paginated_slice = itertools.islice(iterator, start_index, end_index) @@ -667,14 +674,31 @@ def _get_shells(self, request: Request) -> Tuple[Iterator[model.AssetAdministrat aas = filter(lambda shell: shell.id_short == id_short, aas) asset_ids = request.args.getlist("assetIds") - if asset_ids is not None: - # Decode and instantiate SpecificAssetIds - # This needs to be a list, otherwise we can only iterate it once. - specific_asset_ids: List[model.SpecificAssetId] = list( - map(lambda asset_id: HTTPApiDecoder.base64urljson(asset_id, model.SpecificAssetId, False), asset_ids)) - # Filter AAS based on these SpecificAssetIds - aas = filter(lambda shell: all(specific_asset_id in shell.asset_information.specific_asset_id - for specific_asset_id in specific_asset_ids), aas) + + if asset_ids: + specific_asset_ids = [] + global_asset_ids = [] + + for asset_id in asset_ids: + asset_id_json = base64url_decode(asset_id) + asset_dict = json.loads(asset_id_json) + name = asset_dict["name"] + value = asset_dict["value"] + + if name == "specificAssetId": + decoded_specific_id = HTTPApiDecoder.json_list(value, model.SpecificAssetId, + False, True)[0] + specific_asset_ids.append(decoded_specific_id) + elif name == "globalAssetId": + global_asset_ids.append(value) + + # Filter AAS based on both SpecificAssetIds and globalAssetIds + aas = filter(lambda shell: ( + (not specific_asset_ids or all(specific_asset_id in shell.asset_information.specific_asset_id + for specific_asset_id in specific_asset_ids)) and + (len(global_asset_ids) <= 1 and + (not global_asset_ids or shell.asset_information.global_asset_id in global_asset_ids)) + ), aas) paginated_aas, end_index = self._get_slice(request, aas) return paginated_aas, end_index @@ -843,7 +867,7 @@ def delete_aas_submodel_refs_submodel(self, request: Request, url_args: Dict, re aas.commit() return response_t() - def aas_submodel_refs_redirect(self, request: Request, url_args: Dict, map_adapter: MapAdapter, + def aas_submodel_refs_redirect(self, request: Request, url_args: Dict, map_adapter: MapAdapter, response_t=None, **_kwargs) -> Response: aas = self._get_shell(url_args) # the following makes sure the reference exists @@ -852,7 +876,7 @@ def aas_submodel_refs_redirect(self, request: Request, url_args: Dict, map_adapt "submodel_id": url_args["submodel_id"] }, force_external=True) if "path" in url_args: - redirect_url += url_args["path"] + "/" + redirect_url += "/" + url_args["path"] if request.query_string: redirect_url += "?" + request.query_string.decode("ascii") return werkzeug.utils.redirect(redirect_url, 307) @@ -940,6 +964,8 @@ def get_submodel_submodel_elements_id_short_path(self, request: Request, url_arg def get_submodel_submodel_elements_id_short_path_metadata(self, request: Request, url_args: Dict, response_t: Type[APIResponse], **_kwargs) -> Response: submodel_element = self._get_submodel_submodel_elements_id_short_path(url_args) + if isinstance(submodel_element, model.Capability) or isinstance(submodel_element, model.Operation): + raise BadRequest(f"{submodel_element.id_short} does not allow the content modifier metadata!") return response_t(submodel_element, stripped=True) def get_submodel_submodel_elements_id_short_path_reference(self, request: Request, url_args: Dict, diff --git a/sdk/basyx/aas/adapter/json/json_deserialization.py b/sdk/basyx/aas/adapter/json/json_deserialization.py index 455a309e6..78e3713f5 100644 --- a/sdk/basyx/aas/adapter/json/json_deserialization.py +++ b/sdk/basyx/aas/adapter/json/json_deserialization.py @@ -1,4 +1,4 @@ -# Copyright (c) 2023 the Eclipse BaSyx Authors +# Copyright (c) 2025 the Eclipse BaSyx Authors # # This program and the accompanying materials are made available under the terms of the MIT License, available in # the LICENSE file of this project. @@ -340,10 +340,16 @@ def _construct_model_reference(cls, dct: Dict[str, object], type_: Type[T], obje 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)] - if keys and not issubclass(KEY_TYPES_CLASSES_INVERSE.get(keys[-1].type, type(None)), type_): + 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__) - return object_class(tuple(keys), type_, cls._construct_reference(_get_ts(dct, 'referredSemanticId', dict)) + # 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) @classmethod diff --git a/sdk/basyx/aas/adapter/json/json_serialization.py b/sdk/basyx/aas/adapter/json/json_serialization.py index 8c6a671f1..f7d6626eb 100644 --- a/sdk/basyx/aas/adapter/json/json_serialization.py +++ b/sdk/basyx/aas/adapter/json/json_serialization.py @@ -1,4 +1,4 @@ -# Copyright (c) 2023 the Eclipse BaSyx Authors +# Copyright (c) 2025 the Eclipse BaSyx Authors # # This program and the accompanying materials are made available under the terms of the MIT License, available in # the LICENSE file of this project. diff --git a/sdk/basyx/aas/adapter/xml/xml_deserialization.py b/sdk/basyx/aas/adapter/xml/xml_deserialization.py index ab78d3c2e..4063b8df7 100644 --- a/sdk/basyx/aas/adapter/xml/xml_deserialization.py +++ b/sdk/basyx/aas/adapter/xml/xml_deserialization.py @@ -1,4 +1,4 @@ -# Copyright (c) 2023 the Eclipse BaSyx Authors +# Copyright (c) 2025 the Eclipse BaSyx Authors # # This program and the accompanying materials are made available under the terms of the MIT License, available in # the LICENSE file of this project. diff --git a/sdk/basyx/aas/adapter/xml/xml_serialization.py b/sdk/basyx/aas/adapter/xml/xml_serialization.py index 2620dad84..7e6585cea 100644 --- a/sdk/basyx/aas/adapter/xml/xml_serialization.py +++ b/sdk/basyx/aas/adapter/xml/xml_serialization.py @@ -1,4 +1,4 @@ -# Copyright (c) 2023 the Eclipse BaSyx Authors +# Copyright (c) 2025 the Eclipse BaSyx Authors # # This program and the accompanying materials are made available under the terms of the MIT License, available in # the LICENSE file of this project. diff --git a/sdk/basyx/aas/backend/backends.py b/sdk/basyx/aas/backend/backends.py index d921a52d5..31be12628 100644 --- a/sdk/basyx/aas/backend/backends.py +++ b/sdk/basyx/aas/backend/backends.py @@ -1,4 +1,4 @@ -# Copyright (c) 2023 the Eclipse BaSyx Authors +# Copyright (c) 2025 the Eclipse BaSyx Authors # # This program and the accompanying materials are made available under the terms of the MIT License, available in # the LICENSE file of this project. diff --git a/sdk/basyx/aas/backend/couchdb.py b/sdk/basyx/aas/backend/couchdb.py index 43428ae9d..4b6f43611 100644 --- a/sdk/basyx/aas/backend/couchdb.py +++ b/sdk/basyx/aas/backend/couchdb.py @@ -1,4 +1,4 @@ -# Copyright (c) 2023 the Eclipse BaSyx Authors +# Copyright (c) 2025 the Eclipse BaSyx Authors # # This program and the accompanying materials are made available under the terms of the MIT License, available in # the LICENSE file of this project. diff --git a/sdk/basyx/aas/backend/local_file.py b/sdk/basyx/aas/backend/local_file.py index 3f6bcb8dc..ec0757375 100644 --- a/sdk/basyx/aas/backend/local_file.py +++ b/sdk/basyx/aas/backend/local_file.py @@ -1,4 +1,4 @@ -# Copyright (c) 2023 the Eclipse BaSyx Authors +# Copyright (c) 2025 the Eclipse BaSyx Authors # # This program and the accompanying materials are made available under the terms of the MIT License, available in # the LICENSE file of this project. diff --git a/sdk/basyx/aas/examples/data/_helper.py b/sdk/basyx/aas/examples/data/_helper.py index 231ba2ade..a4c3edfce 100644 --- a/sdk/basyx/aas/examples/data/_helper.py +++ b/sdk/basyx/aas/examples/data/_helper.py @@ -1,4 +1,4 @@ -# Copyright (c) 2023 the Eclipse BaSyx Authors +# Copyright (c) 2025 the Eclipse BaSyx Authors # # This program and the accompanying materials are made available under the terms of the MIT License, available in # the LICENSE file of this project. @@ -213,6 +213,18 @@ def _check_has_semantics_equal(self, object_: model.HasSemantics, expected_objec :return: The value of expression to be used in control statements """ self.check_attribute_equal(object_, "semantic_id", expected_object.semantic_id) + if isinstance(expected_object.semantic_id, model.ModelReference) and self.check( + isinstance(object_.semantic_id, model.ModelReference), + "{} must be a ModelReference".format(repr(object_)), + ): # type: ignore + self.check( + object_.semantic_id.type == expected_object.semantic_id.type, # type: ignore + "ModelReference type {} of {} must be equal to {}".format( + object_.semantic_id.type, # type: ignore + repr(object_), + expected_object.semantic_id.type, + ), + ) 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", diff --git a/sdk/basyx/aas/examples/data/example_aas.py b/sdk/basyx/aas/examples/data/example_aas.py index c1c0db426..e093c603a 100644 --- a/sdk/basyx/aas/examples/data/example_aas.py +++ b/sdk/basyx/aas/examples/data/example_aas.py @@ -1,4 +1,4 @@ -# Copyright (c) 2023 the Eclipse BaSyx Authors +# Copyright (c) 2025 the Eclipse BaSyx Authors # # This program and the accompanying materials are made available under the terms of the MIT License, available in # the LICENSE file of this project. 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 d86f6f5fb..a18f3cbc6 100644 --- a/sdk/basyx/aas/examples/data/example_aas_mandatory_attributes.py +++ b/sdk/basyx/aas/examples/data/example_aas_mandatory_attributes.py @@ -1,4 +1,4 @@ -# Copyright (c) 2023 the Eclipse BaSyx Authors +# Copyright (c) 2025 the Eclipse BaSyx Authors # # This program and the accompanying materials are made available under the terms of the MIT License, available in # the LICENSE file of this project. 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 45f3df3c2..83232914a 100644 --- a/sdk/basyx/aas/examples/data/example_aas_missing_attributes.py +++ b/sdk/basyx/aas/examples/data/example_aas_missing_attributes.py @@ -1,4 +1,4 @@ -# Copyright (c) 2023 the Eclipse BaSyx Authors +# Copyright (c) 2025 the Eclipse BaSyx Authors # # This program and the accompanying materials are made available under the terms of the MIT License, available in # the LICENSE file of this project. diff --git a/sdk/basyx/aas/examples/data/example_submodel_template.py b/sdk/basyx/aas/examples/data/example_submodel_template.py index 6f5a7789b..358e06b05 100644 --- a/sdk/basyx/aas/examples/data/example_submodel_template.py +++ b/sdk/basyx/aas/examples/data/example_submodel_template.py @@ -1,4 +1,4 @@ -# Copyright (c) 2023 the Eclipse BaSyx Authors +# Copyright (c) 2025 the Eclipse BaSyx Authors # # This program and the accompanying materials are made available under the terms of the MIT License, available in # the LICENSE file of this project. diff --git a/sdk/basyx/aas/examples/tutorial_serialization_deserialization.py b/sdk/basyx/aas/examples/tutorial_serialization_deserialization.py index 6e1f6a889..6c99409a7 100755 --- a/sdk/basyx/aas/examples/tutorial_serialization_deserialization.py +++ b/sdk/basyx/aas/examples/tutorial_serialization_deserialization.py @@ -10,6 +10,7 @@ from basyx.aas import model import basyx.aas.adapter.xml +import basyx.aas.adapter.json # 'Details of the Asset Administration Shell' specifies multiple official serialization formats for AAS data. In this # tutorial, we show how the Eclipse BaSyx Python library can be used to serialize AAS objects into JSON or XML and to diff --git a/sdk/basyx/aas/model/_string_constraints.py b/sdk/basyx/aas/model/_string_constraints.py index aa9833cf2..376f76b0e 100644 --- a/sdk/basyx/aas/model/_string_constraints.py +++ b/sdk/basyx/aas/model/_string_constraints.py @@ -1,4 +1,4 @@ -# Copyright (c) 2023 the Eclipse BaSyx Authors +# Copyright (c) 2025 the Eclipse BaSyx Authors # # This program and the accompanying materials are made available under the terms of the MIT License, available in # the LICENSE file of this project. diff --git a/sdk/basyx/aas/model/aas.py b/sdk/basyx/aas/model/aas.py index 684a1ff06..4a9fb4640 100644 --- a/sdk/basyx/aas/model/aas.py +++ b/sdk/basyx/aas/model/aas.py @@ -1,4 +1,4 @@ -# Copyright (c) 2023 the Eclipse BaSyx Authors +# Copyright (c) 2025 the Eclipse BaSyx Authors # # This program and the accompanying materials are made available under the terms of the MIT License, available in # the LICENSE file of this project. diff --git a/sdk/basyx/aas/model/base.py b/sdk/basyx/aas/model/base.py index a1dc9f1a9..a93e3cb59 100644 --- a/sdk/basyx/aas/model/base.py +++ b/sdk/basyx/aas/model/base.py @@ -1,4 +1,4 @@ -# Copyright (c) 2023 the Eclipse BaSyx Authors +# Copyright (c) 2025 the Eclipse BaSyx Authors # # This program and the accompanying materials are made available under the terms of the MIT License, available in # the LICENSE file of this project. diff --git a/sdk/basyx/aas/model/concept.py b/sdk/basyx/aas/model/concept.py index 6ee0155b9..a9cbd1a33 100644 --- a/sdk/basyx/aas/model/concept.py +++ b/sdk/basyx/aas/model/concept.py @@ -1,4 +1,4 @@ -# Copyright (c) 2023 the Eclipse BaSyx Authors +# Copyright (c) 2025 the Eclipse BaSyx Authors # # This program and the accompanying materials are made available under the terms of the MIT License, available in # the LICENSE file of this project. diff --git a/sdk/basyx/aas/model/datatypes.py b/sdk/basyx/aas/model/datatypes.py index 24a7fb261..d5acc6d45 100644 --- a/sdk/basyx/aas/model/datatypes.py +++ b/sdk/basyx/aas/model/datatypes.py @@ -1,4 +1,4 @@ -# Copyright (c) 2023 the Eclipse BaSyx Authors +# Copyright (c) 2025 the Eclipse BaSyx Authors # # This program and the accompanying materials are made available under the terms of the MIT License, available in # the LICENSE file of this project. diff --git a/sdk/basyx/aas/model/provider.py b/sdk/basyx/aas/model/provider.py index 0d4430de5..ac50d33da 100644 --- a/sdk/basyx/aas/model/provider.py +++ b/sdk/basyx/aas/model/provider.py @@ -1,4 +1,4 @@ -# Copyright (c) 2023 the Eclipse BaSyx Authors +# Copyright (c) 2025 the Eclipse BaSyx Authors # # This program and the accompanying materials are made available under the terms of the MIT License, available in # the LICENSE file of this project. @@ -11,7 +11,7 @@ """ import abc -from typing import MutableSet, Iterator, Generic, TypeVar, Dict, List, Optional, Iterable +from typing import MutableSet, Iterator, Generic, TypeVar, Dict, List, Optional, Iterable, Set from .base import Identifier, Identifiable @@ -84,6 +84,15 @@ class DictObjectStore(AbstractObjectStore[_IT], Generic[_IT]): """ A local in-memory object store for :class:`~basyx.aas.model.base.Identifiable` objects, backed by a dict, mapping :class:`~basyx.aas.model.base.Identifier` → :class:`~basyx.aas.model.base.Identifiable` + + .. note:: + The `DictObjectStore` provides efficient retrieval of objects by their :class:`~basyx.aas.model.base.Identifier` + However, since object stores are not referenced via the parent attribute, the mapping is not updated + if the :class:`~basyx.aas.model.base.Identifier` of an :class:`~basyx.aas.model.base.Identifiable` changes. + For more details, see [issue #216](https://github.com/eclipse-basyx/basyx-python-sdk/issues/216). + As a result, the `DictObjectStore` is unsuitable for storing objects whose + :class:`~basyx.aas.model.base.Identifier` may change. + In such cases, consider using a :class:`~.SetObjectStore` instead. """ def __init__(self, objects: Iterable[_IT] = ()) -> None: self._backend: Dict[Identifier, _IT] = {} @@ -117,6 +126,64 @@ def __iter__(self) -> Iterator[_IT]: return iter(self._backend.values()) +class SetObjectStore(AbstractObjectStore[_IT], Generic[_IT]): + """ + A local in-memory object store for :class:`~basyx.aas.model.base.Identifiable` objects, backed by a set + + .. note:: + The `SetObjectStore` is slower than the `DictObjectStore` for retrieval of objects, because it has to iterate + over all objects to find the one with the correct :class:`~basyx.aas.model.base.Identifier`. + On the other hand, the `SetObjectStore` is more secure, because it is less affected by changes in the + :class:`~basyx.aas.model.base.Identifier` of an :class:`~basyx.aas.model.base.Identifiable` object. + Therefore, the `SetObjectStore` is suitable for storing objects whose :class:`~basyx.aas.model.base.Identifier` + may change. + """ + def __init__(self, objects: Iterable[_IT] = ()) -> None: + self._backend: Set[_IT] = set() + for x in objects: + self.add(x) + + def get_identifiable(self, identifier: Identifier) -> _IT: + for x in self._backend: + if x.id == identifier: + return x + raise KeyError(identifier) + + def add(self, x: _IT) -> None: + if x in self: + # Object is already in store + return + try: + self.get_identifiable(x.id) + except KeyError: + self._backend.add(x) + else: + raise KeyError(f"Identifiable object with same id {x.id} is already stored in this store") + + def discard(self, x: _IT) -> None: + self._backend.discard(x) + + def remove(self, x: _IT) -> None: + self._backend.remove(x) + + def __contains__(self, x: object) -> bool: + if isinstance(x, Identifier): + try: + self.get_identifiable(x) + return True + except KeyError: + return False + if not isinstance(x, Identifiable): + return False + return x in self._backend + + def __len__(self) -> int: + return len(self._backend) + + def __iter__(self) -> Iterator[_IT]: + return iter(self._backend) + + class ObjectProviderMultiplexer(AbstractObjectProvider): """ A multiplexer for Providers of :class:`~basyx.aas.model.base.Identifiable` objects. diff --git a/sdk/basyx/aas/util/identification.py b/sdk/basyx/aas/util/identification.py index c3647ef90..74cccd99a 100644 --- a/sdk/basyx/aas/util/identification.py +++ b/sdk/basyx/aas/util/identification.py @@ -1,4 +1,4 @@ -# Copyright (c) 2023 the Eclipse BaSyx Authors +# Copyright (c) 2025 the Eclipse BaSyx Authors # # This program and the accompanying materials are made available under the terms of the MIT License, available in # the LICENSE file of this project. diff --git a/sdk/basyx/aas/util/traversal.py b/sdk/basyx/aas/util/traversal.py index 43a55af44..0b288ddf7 100644 --- a/sdk/basyx/aas/util/traversal.py +++ b/sdk/basyx/aas/util/traversal.py @@ -1,4 +1,4 @@ -# Copyright (c) 2023 the Eclipse BaSyx Authors +# Copyright (c) 2025 the Eclipse BaSyx Authors # # This program and the accompanying materials are made available under the terms of the MIT License, available in # the LICENSE file of this project. diff --git a/sdk/pyproject.toml b/sdk/pyproject.toml index 7722b16ef..0fe88d1bd 100644 --- a/sdk/pyproject.toml +++ b/sdk/pyproject.toml @@ -36,24 +36,24 @@ classifiers = [ ] requires-python = ">=3.9" dependencies = [ - "jsonschema~=4.7", - "lxml>=4.2,<5", + "lxml>=5.3", "python-dateutil>=2.8,<3", - "types-python-dateutil", - "pyecma376-2>=0.2.4", + "pyecma376-2>=1.0.1", "urllib3>=1.26,<3", "Werkzeug>=3.0.3,<4", - "schemathesis~=3.7", - "hypothesis~=6.13", - "lxml-stubs~=0.5.1", ] [project.optional-dependencies] dev = [ - "mypy", + "mypy==1.15.0", "pycodestyle", "codeblocks", "coverage", + "schemathesis~=3.7", + "jsonschema~=4.7", + "hypothesis~=6.13", + "types-python-dateutil", + "lxml-stubs~=0.5.1", ] [project.urls] diff --git a/sdk/test/_helper/setup_testdb.py b/sdk/test/_helper/setup_testdb.py index d668ac865..20f56c4d9 100755 --- a/sdk/test/_helper/setup_testdb.py +++ b/sdk/test/_helper/setup_testdb.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# Copyright (c) 2023 the Eclipse BaSyx Authors +# Copyright (c) 2025 the Eclipse BaSyx Authors # # This program and the accompanying materials are made available under the terms of the MIT License, available in # the LICENSE file of this project. diff --git a/sdk/test/adapter/aasx/test_aasx.py b/sdk/test/adapter/aasx/test_aasx.py index 9f1bb5ebb..a83c60186 100644 --- a/sdk/test/adapter/aasx/test_aasx.py +++ b/sdk/test/adapter/aasx/test_aasx.py @@ -1,4 +1,4 @@ -# Copyright (c) 2022 the Eclipse BaSyx Authors +# Copyright (c) 2025 the Eclipse BaSyx Authors # # This program and the accompanying materials are made available under the terms of the MIT License, available in # the LICENSE file of this project. diff --git a/sdk/test/adapter/json/test_json_deserialization.py b/sdk/test/adapter/json/test_json_deserialization.py index 28da288cd..9272bdf98 100644 --- a/sdk/test/adapter/json/test_json_deserialization.py +++ b/sdk/test/adapter/json/test_json_deserialization.py @@ -1,4 +1,4 @@ -# Copyright (c) 2023 the Eclipse BaSyx Authors +# Copyright (c) 2025 the Eclipse BaSyx Authors # # This program and the accompanying materials are made available under the terms of the MIT License, available in # the LICENSE file of this project. diff --git a/sdk/test/adapter/json/test_json_serialization.py b/sdk/test/adapter/json/test_json_serialization.py index 01bc65c95..8e9bc8d01 100644 --- a/sdk/test/adapter/json/test_json_serialization.py +++ b/sdk/test/adapter/json/test_json_serialization.py @@ -1,4 +1,4 @@ -# Copyright (c) 2023 the Eclipse BaSyx Authors +# Copyright (c) 2025 the Eclipse BaSyx Authors # # This program and the accompanying materials are made available under the terms of the MIT License, available in # the LICENSE file of this project. diff --git a/sdk/test/adapter/json/test_json_serialization_deserialization.py b/sdk/test/adapter/json/test_json_serialization_deserialization.py index 9b016e17a..e33921a21 100644 --- a/sdk/test/adapter/json/test_json_serialization_deserialization.py +++ b/sdk/test/adapter/json/test_json_serialization_deserialization.py @@ -1,4 +1,4 @@ -# Copyright (c) 2023 the Eclipse BaSyx Authors +# Copyright (c) 2025 the Eclipse BaSyx Authors # # This program and the accompanying materials are made available under the terms of the MIT License, available in # the LICENSE file of this project. diff --git a/sdk/test/adapter/xml/test_xml_deserialization.py b/sdk/test/adapter/xml/test_xml_deserialization.py index e62ebaa08..331ad98c5 100644 --- a/sdk/test/adapter/xml/test_xml_deserialization.py +++ b/sdk/test/adapter/xml/test_xml_deserialization.py @@ -1,4 +1,4 @@ -# Copyright (c) 2023 the Eclipse BaSyx Authors +# Copyright (c) 2025 the Eclipse BaSyx Authors # # This program and the accompanying materials are made available under the terms of the MIT License, available in # the LICENSE file of this project. diff --git a/sdk/test/adapter/xml/test_xml_serialization.py b/sdk/test/adapter/xml/test_xml_serialization.py index 11328f62f..e07e10255 100644 --- a/sdk/test/adapter/xml/test_xml_serialization.py +++ b/sdk/test/adapter/xml/test_xml_serialization.py @@ -1,4 +1,4 @@ -# Copyright (c) 2023 the Eclipse BaSyx Authors +# Copyright (c) 2025 the Eclipse BaSyx Authors # # This program and the accompanying materials are made available under the terms of the MIT License, available in # the LICENSE file of this project. diff --git a/sdk/test/adapter/xml/test_xml_serialization_deserialization.py b/sdk/test/adapter/xml/test_xml_serialization_deserialization.py index 2e06c44d8..7a4f5c12d 100644 --- a/sdk/test/adapter/xml/test_xml_serialization_deserialization.py +++ b/sdk/test/adapter/xml/test_xml_serialization_deserialization.py @@ -1,4 +1,4 @@ -# Copyright (c) 2023 the Eclipse BaSyx Authors +# Copyright (c) 2025 the Eclipse BaSyx Authors # # This program and the accompanying materials are made available under the terms of the MIT License, available in # the LICENSE file of this project. diff --git a/sdk/test/backend/test_backends.py b/sdk/test/backend/test_backends.py index a7f025558..e0beee8f8 100644 --- a/sdk/test/backend/test_backends.py +++ b/sdk/test/backend/test_backends.py @@ -1,4 +1,4 @@ -# Copyright (c) 2022 the Eclipse BaSyx Authors +# Copyright (c) 2025 the Eclipse BaSyx Authors # # This program and the accompanying materials are made available under the terms of the MIT License, available in # the LICENSE file of this project. diff --git a/sdk/test/backend/test_couchdb.py b/sdk/test/backend/test_couchdb.py index 5e6c2fbe4..89fe992de 100644 --- a/sdk/test/backend/test_couchdb.py +++ b/sdk/test/backend/test_couchdb.py @@ -1,4 +1,4 @@ -# Copyright (c) 2023 the Eclipse BaSyx Authors +# Copyright (c) 2025 the Eclipse BaSyx Authors # # This program and the accompanying materials are made available under the terms of the MIT License, available in # the LICENSE file of this project. diff --git a/sdk/test/backend/test_local_file.py b/sdk/test/backend/test_local_file.py index 4a8186ac3..22aaa3155 100644 --- a/sdk/test/backend/test_local_file.py +++ b/sdk/test/backend/test_local_file.py @@ -1,4 +1,4 @@ -# Copyright (c) 2022 the Eclipse BaSyx Authors +# Copyright (c) 2025 the Eclipse BaSyx Authors # # This program and the accompanying materials are made available under the terms of the MIT License, available in # the LICENSE file of this project. diff --git a/sdk/test/examples/test_examples.py b/sdk/test/examples/test_examples.py index cacf9395c..5695e3b94 100644 --- a/sdk/test/examples/test_examples.py +++ b/sdk/test/examples/test_examples.py @@ -1,4 +1,4 @@ -# Copyright (c) 2023 the Eclipse BaSyx Authors +# Copyright (c) 2025 the Eclipse BaSyx Authors # # This program and the accompanying materials are made available under the terms of the MIT License, available in # the LICENSE file of this project. diff --git a/sdk/test/examples/test_helpers.py b/sdk/test/examples/test_helpers.py index 754d5cdfb..faca8602b 100644 --- a/sdk/test/examples/test_helpers.py +++ b/sdk/test/examples/test_helpers.py @@ -1,4 +1,4 @@ -# Copyright (c) 2023 the Eclipse BaSyx Authors +# Copyright (c) 2025 the Eclipse BaSyx Authors # # This program and the accompanying materials are made available under the terms of the MIT License, available in # the LICENSE file of this project. diff --git a/sdk/test/examples/test_tutorials.py b/sdk/test/examples/test_tutorials.py index b82c3bf7b..2b5dbe54e 100644 --- a/sdk/test/examples/test_tutorials.py +++ b/sdk/test/examples/test_tutorials.py @@ -1,4 +1,4 @@ -# Copyright (c) 2022 the Eclipse BaSyx Authors +# Copyright (c) 2025 the Eclipse BaSyx Authors # # This program and the accompanying materials are made available under the terms of the MIT License, available in # the LICENSE file of this project. diff --git a/sdk/test/model/test_aas.py b/sdk/test/model/test_aas.py index 27ce13b4d..3b974c04f 100644 --- a/sdk/test/model/test_aas.py +++ b/sdk/test/model/test_aas.py @@ -1,4 +1,4 @@ -# Copyright (c) 2023 the Eclipse BaSyx Authors +# Copyright (c) 2025 the Eclipse BaSyx Authors # # This program and the accompanying materials are made available under the terms of the MIT License, available in # the LICENSE file of this project. diff --git a/sdk/test/model/test_base.py b/sdk/test/model/test_base.py index 3cf7ea2d0..1e0432a58 100644 --- a/sdk/test/model/test_base.py +++ b/sdk/test/model/test_base.py @@ -1,4 +1,4 @@ -# Copyright (c) 2023 the Eclipse BaSyx Authors +# Copyright (c) 2025 the Eclipse BaSyx Authors # # This program and the accompanying materials are made available under the terms of the MIT License, available in # the LICENSE file of this project. diff --git a/sdk/test/model/test_concept.py b/sdk/test/model/test_concept.py index c8bfefce9..94fc497b8 100644 --- a/sdk/test/model/test_concept.py +++ b/sdk/test/model/test_concept.py @@ -1,4 +1,4 @@ -# Copyright (c) 2023 the Eclipse BaSyx Authors +# Copyright (c) 2025 the Eclipse BaSyx Authors # # This program and the accompanying materials are made available under the terms of the MIT License, available in # the LICENSE file of this project. diff --git a/sdk/test/model/test_datatypes.py b/sdk/test/model/test_datatypes.py index 8021414db..b83c5e5fb 100644 --- a/sdk/test/model/test_datatypes.py +++ b/sdk/test/model/test_datatypes.py @@ -1,4 +1,4 @@ -# Copyright (c) 2023 the Eclipse BaSyx Authors +# Copyright (c) 2025 the Eclipse BaSyx Authors # # This program and the accompanying materials are made available under the terms of the MIT License, available in # the LICENSE file of this project. diff --git a/sdk/test/model/test_provider.py b/sdk/test/model/test_provider.py index 549232cc3..68fb01cff 100644 --- a/sdk/test/model/test_provider.py +++ b/sdk/test/model/test_provider.py @@ -1,4 +1,4 @@ -# Copyright (c) 2023 the Eclipse BaSyx Authors +# Copyright (c) 2025 the Eclipse BaSyx Authors # # This program and the accompanying materials are made available under the terms of the MIT License, available in # the LICENSE file of this project. diff --git a/sdk/test/model/test_string_constraints.py b/sdk/test/model/test_string_constraints.py index 5adb15523..55d5789f5 100644 --- a/sdk/test/model/test_string_constraints.py +++ b/sdk/test/model/test_string_constraints.py @@ -1,4 +1,4 @@ -# Copyright (c) 2023 the Eclipse BaSyx Authors +# Copyright (c) 2025 the Eclipse BaSyx Authors # # This program and the accompanying materials are made available under the terms of the MIT License, available in # the LICENSE file of this project. diff --git a/sdk/test/model/test_submodel.py b/sdk/test/model/test_submodel.py index 15746bd5d..37a5792df 100644 --- a/sdk/test/model/test_submodel.py +++ b/sdk/test/model/test_submodel.py @@ -1,4 +1,4 @@ -# Copyright (c) 2023 the Eclipse BaSyx Authors +# Copyright (c) 2025 the Eclipse BaSyx Authors # # This program and the accompanying materials are made available under the terms of the MIT License, available in # the LICENSE file of this project. diff --git a/sdk/test/util/test_identification.py b/sdk/test/util/test_identification.py index 9a43ea16b..ebfed8606 100644 --- a/sdk/test/util/test_identification.py +++ b/sdk/test/util/test_identification.py @@ -1,4 +1,4 @@ -# Copyright (c) 2022 the Eclipse BaSyx Authors +# Copyright (c) 2025 the Eclipse BaSyx Authors # # This program and the accompanying materials are made available under the terms of the MIT License, available in # the LICENSE file of this project. diff --git a/server/Dockerfile b/server/Dockerfile index 6dc3c4cac..4df672c41 100644 --- a/server/Dockerfile +++ b/server/Dockerfile @@ -13,13 +13,12 @@ ENV PYTHONUNBUFFERED=1 RUN apk update && \ apk add --no-cache nginx supervisor gcc musl-dev linux-headers python3-dev git bash && \ pip install uwsgi && \ - pip install --no-cache-dir git+https://github.com/eclipse-basyx/basyx-python-sdk@main#subdirectory=sdk && \ apk del git bash -COPY uwsgi.ini /etc/uwsgi/ -COPY supervisord.ini /etc/supervisor/conf.d/supervisord.ini -COPY stop-supervisor.sh /etc/supervisor/stop-supervisor.sh +COPY server/uwsgi.ini /etc/uwsgi/ +COPY server/supervisord.ini /etc/supervisor/conf.d/supervisord.ini +COPY server/stop-supervisor.sh /etc/supervisor/stop-supervisor.sh RUN chmod +x /etc/supervisor/stop-supervisor.sh # Makes it possible to use a different configuration @@ -34,12 +33,16 @@ ENV LISTEN_PORT=80 ENV CLIENT_BODY_BUFFER_SIZE=1M # Copy the entrypoint that will generate Nginx additional configs -COPY entrypoint.sh /entrypoint.sh +COPY server/entrypoint.sh /entrypoint.sh RUN chmod +x /entrypoint.sh ENTRYPOINT ["/entrypoint.sh"] -COPY ./app /app +ENV SETUPTOOLS_SCM_PRETEND_VERSION=1.0.0 + +COPY ./sdk /sdk +COPY ./server/app /app WORKDIR /app +RUN pip install ../sdk CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/supervisord.ini"] diff --git a/server/README.md b/server/README.md index b0e6b80db..37b649d79 100644 --- a/server/README.md +++ b/server/README.md @@ -7,7 +7,7 @@ The server currently implements the following interfaces: - [Submodel Repository Service][5] It uses the [HTTP API][1] and the [AASX][7], [JSON][8], and [XML][9] Adapters of the [BaSyx Python SDK][3], to serve regarding files from a given directory. -The files are only read, chages won't persist. +The files are only read, changes won't persist. Alternatively, the container can also be told to use the [Local-File Backend][2] instead, which stores AAS and Submodels as individual JSON files and allows for persistent changes (except supplementary files, i.e. files referenced by `File` submodel elements). See [below](#options) on how to configure this. @@ -15,7 +15,7 @@ See [below](#options) on how to configure this. ## Building The container image can be built via: ``` -$ docker buildx build -t basyx-python-sdk-http-server . +$ docker build -t basyx-python-server -f Dockerfile .. ``` ## Running @@ -46,17 +46,17 @@ The container can be configured via environment variables: Putting it all together, the container can be started via the following command: ``` -$ docker run -p 8080:80 -v ./storage:/storage basyx-python-sdk-http-server +$ docker run -p 8080:80 -v ./storage:/storage basyx-python-server ``` Since Windows uses backslashes instead of forward slashes in paths, you'll have to adjust the path to the storage directory there: ``` -> docker run -p 8080:80 -v .\storage:/storage basyx-python-sdk-http-server +> docker run -p 8080:80 -v .\storage:/storage basyx-python-server ``` Per default, the server will use the `LOCAL_FILE_READ_ONLY` storage type and serve the API under `/api/v3.0` and read files from `/storage`. If you want to change this, you can do so like this: ``` -$ docker run -p 8080:80 -v ./storage2:/storage2 -e API_BASE_PATH=/api/v3.1 -e STORAGE_TYPE=LOCAL_FILE_BACKEND -e STORAGE_PATH=/storage2 basyx-python-sdk-http-server +$ docker run -p 8080:80 -v ./storage2:/storage2 -e API_BASE_PATH=/api/v3.1 -e STORAGE_TYPE=LOCAL_FILE_BACKEND -e STORAGE_PATH=/storage2 basyx-python-server ``` ## Building and running the image with docker-compose @@ -70,7 +70,9 @@ This is the exemplary `docker-compose` file for the server: ````yaml services: app: - build: . + build: + context: .. + dockerfile: server/Dockerfile ports: - "8080:80" volumes: diff --git a/server/compose.yml b/server/compose.yml index 5465e04ce..666484a5d 100644 --- a/server/compose.yml +++ b/server/compose.yml @@ -1,6 +1,8 @@ services: app: - build: . + build: + context: .. + dockerfile: server/Dockerfile ports: - "8080:80" volumes: