diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1e65ca3b7..8ded4cb0e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,13 +7,19 @@ env: jobs: 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"] env: COUCHDB_ADMIN_PASSWORD: "yo0Quai3" - AAS_SPECS_RELEASE_TAG: "V3.0.7" + # (2024-10-11, s-heppner) + # Specify the tag of the released schema files from https://github.com/admin-shell-io/aas-specs/releases + # that you want to use to test the serialization adapter against. + # Currently, we need to update this manually, however I'm afraid this is not possible to automatically infer, + # since it's heavily dependant of the version of the AAS specification we support. + AAS_SPECS_RELEASE_TAG: "IDTA-01001-3-0-1_schemasV3.0.8" services: couchdb: image: couchdb:3 @@ -22,7 +28,6 @@ jobs: env: COUCHDB_USER: "admin" COUCHDB_PASSWORD: ${{ env.COUCHDB_ADMIN_PASSWORD }} - steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} @@ -37,8 +42,7 @@ jobs: - name: Install Python dependencies run: | python -m pip install --upgrade pip - pip install coverage - pip install -r requirements.txt + pip install .[dev] - name: Setup test config and CouchDB database server run: | python test/_helper/setup_testdb.py -u "admin" -p "$COUCHDB_ADMIN_PASSWORD" @@ -51,8 +55,8 @@ jobs: coverage report -m static-analysis: + # This job runs static code analysis, namely pycodestyle and mypy runs-on: ubuntu-latest - steps: - uses: actions/checkout@v2 - name: Set up Python ${{ env.X_PYTHON_VERSION }} @@ -62,8 +66,7 @@ jobs: - name: Install Python dependencies run: | python -m pip install --upgrade pip - pip install pycodestyle mypy - pip install -r requirements.txt + pip install .[dev] - name: Check typing with MyPy run: | mypy basyx test @@ -72,8 +75,8 @@ jobs: pycodestyle --count --max-line-length 120 basyx test readme-codeblocks: + # This job runs the same static code analysis (mypy and pycodestyle) on the codeblocks in our docstrings. runs-on: ubuntu-latest - steps: - uses: actions/checkout@v2 - name: Set up Python ${{ env.X_PYTHON_VERSION }} @@ -83,8 +86,7 @@ jobs: - name: Install Python dependencies run: | python -m pip install --upgrade pip - pip install pycodestyle mypy codeblocks - pip install -r requirements.txt + pip install .[dev] - name: Check typing with MyPy run: | mypy <(codeblocks python README.md) @@ -96,8 +98,8 @@ jobs: codeblocks python README.md | python docs: + # This job checks, if the automatically generated documentation using sphinx can be compiled runs-on: ubuntu-latest - steps: - uses: actions/checkout@v2 - name: Set up Python ${{ env.X_PYTHON_VERSION }} @@ -107,15 +109,15 @@ jobs: - name: Install Python dependencies run: | python -m pip install --upgrade pip - pip install -r requirements.txt + pip install . pip install -r docs/add-requirements.txt - name: Check documentation for errors run: | SPHINXOPTS="-a -E -n -W --keep-going" make -C docs html package: + # This job checks if we can build our SDK package runs-on: ubuntu-latest - steps: - uses: actions/checkout@v2 - name: Set up Python ${{ env.X_PYTHON_VERSION }} @@ -125,7 +127,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install setuptools wheel build + pip install build - name: Create source and wheel dist run: | python -m build diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0edc20093..912e7da42 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -6,8 +6,8 @@ on: jobs: publish: + # This job publishes the package to PyPI runs-on: ubuntu-latest - steps: - uses: actions/checkout@v2 - name: Set up Python 3.10 @@ -17,10 +17,10 @@ jobs: - 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 - name: Publish distribution to PyPI uses: pypa/gh-action-pypi-publish@release/v1 with: diff --git a/.gitignore b/.gitignore index 587fc5b12..eb5b0c468 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,6 @@ /test/test_config.ini # Schema files needed for testing /test/adapter/schemas + +# Ignore dynamically generated version file +basyx/version.py diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 339e42442..90e2d8fdc 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -127,7 +127,7 @@ Additionally, we use [PEP 484 -- Type Hints](https://www.python.org/dev/peps/pep 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 +pip install .[dev] ``` Running all checks: diff --git a/README.md b/README.md index e9c981a39..ac30e4054 100644 --- a/README.md +++ b/README.md @@ -6,12 +6,12 @@ The Eclipse BaSyx Python project focuses on providing a Python implementation of for Industry 4.0 Systems. These are the currently implemented specifications: -| Specification | Version | -|---------------------------------------|-----------------------------------------------------------------------------------------------------------------------------| -| Part 1: Metamodel | [v3.0 (01001-3-0)](https://industrialdigitaltwin.org/content-hub/aasspecifications/idta_01001-3-0_metamodel) | -| Part 2: API | not implemented yet | -| Part 3a: Data Specification IEC 61360 | [v3.0 (01003-a-3-0)](https://industrialdigitaltwin.org/content-hub/aasspecifications/idta_01003-a-3-0_data_specification) | -| Part 5: Package File Format (AASX) | [v3.0 (01005-3-0)](https://industrialdigitaltwin.org/content-hub/aasspecifications/idta-01005-3-0_package_file_format_aasx) | +| Specification | Version | +|---------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Part 1: Metamodel | [v3.0 (01001-3-0)](https://industrialdigitaltwin.org/wp-content/uploads/2023/06/IDTA-01001-3-0_SpecificationAssetAdministrationShell_Part1_Metamodel.pdf) | +| Part 2: API | not implemented yet | +| 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) | +| Part 5: Package File Format (AASX) | [v3.0 (01005-3-0)](https://industrialdigitaltwin.org/wp-content/uploads/2023/04/IDTA-01005-3-0_SpecificationAssetAdministrationShell_Part5_AASXPackageFileFormat.pdf) | ## Features diff --git a/basyx/aas/adapter/http.py b/basyx/aas/adapter/http.py index 4f07f43d1..df3a8e9c9 100644 --- a/basyx/aas/adapter/http.py +++ b/basyx/aas/adapter/http.py @@ -149,7 +149,7 @@ def serialize(self, obj: ResponseData, cursor: Optional[int], stripped: bool) -> data = obj else: data = { - "paging_metadata": {"cursor": cursor}, + "paging_metadata": {"cursor": str(cursor)}, "result": obj } return json.dumps( @@ -226,7 +226,7 @@ def get_response_type(request: Request) -> Type[APIResponse]: "application/xml": XmlResponse, "text/xml": XmlResponseAlt } - if len(request.accept_mimetypes) == 0: + if len(request.accept_mimetypes) == 0 or request.accept_mimetypes.best in (None, "*/*"): return JsonResponse mime_type = request.accept_mimetypes.best_match(response_types) if mime_type is None: diff --git a/basyx/aas/backend/couchdb.py b/basyx/aas/backend/couchdb.py index cd24a5562..be3354b1f 100644 --- a/basyx/aas/backend/couchdb.py +++ b/basyx/aas/backend/couchdb.py @@ -13,7 +13,7 @@ """ import threading import weakref -from typing import List, Dict, Any, Optional, Iterator, Iterable, Union, Tuple +from typing import List, Dict, Any, Optional, Iterator, Iterable, Union, Tuple, MutableMapping import urllib.parse import urllib.request import urllib.error @@ -108,7 +108,7 @@ def _parse_source(cls, source: str) -> str: @classmethod def do_request(cls, url: str, method: str = "GET", additional_headers: Dict[str, str] = {}, - body: Optional[bytes] = None) -> Dict[str, Any]: + body: Optional[bytes] = None) -> MutableMapping[str, Any]: """ Perform an HTTP(S) request to the CouchDBServer, parse the result and handle errors diff --git a/basyx/aas/model/datatypes.py b/basyx/aas/model/datatypes.py index f20c4d045..fb2a622d6 100644 --- a/basyx/aas/model/datatypes.py +++ b/basyx/aas/model/datatypes.py @@ -78,6 +78,17 @@ def __eq__(self, other: object) -> bool: other_tzinfo = other.tzinfo if hasattr(other, 'tzinfo') else None # type: ignore return datetime.date.__eq__(self, other) and self.tzinfo == other_tzinfo + def __copy__(self): + cls = self.__class__ + result = cls.__new__(cls, self.year, self.month, self.day, self.tzinfo) + return result + + def __deepcopy__(self, memo): + cls = self.__class__ + result = cls.__new__(cls, self.year, self.month, self.day, self.tzinfo) + memo[id(self)] = result + return result + # TODO override comparsion operators # TODO add into_datetime function # TODO add includes(:DateTime) -> bool function diff --git a/pyproject.toml b/pyproject.toml index 75aeea661..c4d9aeaf8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,10 +1,26 @@ [build-system] -requires = ["setuptools>=45", "wheel", "setuptools_scm[toml]>=6.2"] +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 basyx.version import version +# print(f"Project version: {version}") +# ``` +write_to = "basyx/version.py" + [project] name = "basyx-python-sdk" -version = "1.0.0" +dynamic = ["version"] description = "The Eclipse BaSyx Python SDK, an implementation of the Asset Administration Shell for Industry 4.0 systems" authors = [ { name = "The Eclipse BaSyx Authors", email = "admins@iat.rwth-aachen.de" } @@ -19,10 +35,16 @@ classifiers = [ ] requires-python = ">=3.8" dependencies = [ - "python-dateutil>=2.8,<3", + "jsonschema~=4.7", "lxml>=4.2,<5", - "urllib3>=1.26,<2.0", - "pyecma376-2>=0.2.4" + "python-dateutil>=2.8,<3", + "types-python-dateutil", + "pyecma376-2>=0.2.4", + "urllib3>=1.26,<3", + "Werkzeug>=3.0.3,<4", + "schemathesis~=3.7", + "hypothesis~=6.13", + "lxml-stubs~=0.5.1", ] [project.optional-dependencies] @@ -37,12 +59,8 @@ dev = [ "Homepage" = "https://github.com/eclipse-basyx/basyx-python-sdk" [tool.setuptools] -packages = ["basyx"] +packages = { find = { include = ["basyx*"], exclude = ["test*"] } } [tool.setuptools.package-data] basyx = ["py.typed"] "basyx.aas.examples.data" = ["TestFile.pdf"] - -[tool.setuptools.exclude-package-data] -"*" = ["test", "test.*"] - diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index e087cdd18..000000000 --- a/requirements.txt +++ /dev/null @@ -1,10 +0,0 @@ -jsonschema~=4.7 -lxml>=4.2,<5 -python-dateutil>=2.8,<3.0 -types-python-dateutil -pyecma376-2>=0.2.4 -urllib3>=1.26,<2.0 -Werkzeug>=3.0.3,<4 -schemathesis~=3.7 -hypothesis~=6.13 -lxml-stubs~=0.5.1 diff --git a/test/model/test_datatypes.py b/test/model/test_datatypes.py index be4666c28..8021414db 100644 --- a/test/model/test_datatypes.py +++ b/test/model/test_datatypes.py @@ -7,6 +7,7 @@ import datetime import math import unittest +import copy import dateutil @@ -177,6 +178,13 @@ def test_parse_partial_dates(self) -> None: model.datatypes.from_xsd("10-10", model.datatypes.GYearMonth) self.assertEqual("Value is not a valid XSD GYearMonth string", str(cm.exception)) + def test_copy_date(self) -> None: + date = model.datatypes.Date(2020, 1, 24) + date_copy_shallow = copy.copy(date) + self.assertEqual(date, date_copy_shallow) + date_copy_deep = copy.deepcopy(date) + self.assertEqual(date, date_copy_deep) + def test_serialize_partial_dates(self) -> None: self.assertEqual("2019", model.datatypes.xsd_repr(model.datatypes.GYear(2019))) self.assertEqual("2019Z", model.datatypes.xsd_repr(model.datatypes.GYear(2019, datetime.timezone.utc)))