From 1e93fa500a3614588ef8ac93f8c7110dc039a040 Mon Sep 17 00:00:00 2001 From: s-heppner Date: Thu, 14 Nov 2024 17:15:10 +0100 Subject: [PATCH 1/2] SDK: Bump required Python version to >= 3.9, Add version CI check This bumps the `requires-python` fiels in the `pyproject.toml` of the SDK from `">=3.8"` to `">=3.9"`, due to the fact that Python v3.8 reached EoL on 2024-10-07. Furthermore, to avoid future backward compatibility issues in native Python, as well as to ensure we're always supporting the currently supported Python versions, we add a CI check `check-python-versions` that uses two new scripts in `./etc/scripts` to check: a) our currently supported Python versions to an EoL database b) that our currently supported Python versions defined in the CI match the ones in the `pyproject.toml`. This should ensure that issues like #330 are avoided in the future. (Note that we still need to fix #330 though). --- .github/workflows/ci.yml | 58 +++++++++++++++--- etc/scripts/check_python_versions_coincide.py | 46 ++++++++++++++ .../check_python_versions_requirements.txt | 2 + .../check_python_versions_supported.py | 61 +++++++++++++++++++ sdk/pyproject.toml | 2 +- 5 files changed, 158 insertions(+), 11 deletions(-) create mode 100644 etc/scripts/check_python_versions_coincide.py create mode 100644 etc/scripts/check_python_versions_requirements.txt create mode 100644 etc/scripts/check_python_versions_supported.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9ccd9944..e722fd14 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,15 +3,47 @@ name: ci on: [push, pull_request] env: - X_PYTHON_VERSION: "3.12" + X_PYTHON_MIN_VERSION: "3.9" + X_PYTHON_MAX_VERSION: "3.12" jobs: + check-python-versions: + # This job checks that the Python Versions we support match and are not End of Life + runs-on: ubuntu-latest + defaults: + run: + working-directory: ./etc/scripts + steps: + - uses: actions/checkout@v2 + - name: Set up Python ${{ env.X_PYTHON_MIN_VERSION }} + uses: actions/setup-python@v2 + with: + python-version: ${{ env.X_PYTHON_MIN_VERSION }} + - name: Install Python dependencies + run: | + python -m pip install --upgrade pip + pip install -r ./check_python_versions_requirements.txt + - name: Check Supported Python Versions + run: | + python check_python_versions_supported.py \ + ${{ env.X_PYTHON_MIN_VERSION }} \ + ${{ env.X_PYTHON_MAX_VERSION }} + + - name: Check Python Versions coincide with the SDKs pyproject.toml + run: | + python check_python_versions_coincide.py \ + ../../sdk/pyproject.toml \ + ${{ env.X_PYTHON_MIN_VERSION }} \ + ${{ env.X_PYTHON_MAX_VERSION }} + + # Todo: Check other pyproject.toml here as well, as we add them + sdk-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"] env: COUCHDB_ADMIN_PASSWORD: "yo0Quai3" # (2024-10-11, s-heppner) @@ -33,6 +65,12 @@ jobs: working-directory: ./sdk steps: - uses: actions/checkout@v2 + - name: Verify Matrix Version matches Global Version + run: | + if [ "${{ matrix.python-version }}" != "${{ env.X_PYTHON_MIN_VERSION }}" ] && [ "${{ matrix.python-version }}" != "${{ env.X_PYTHON_MAX_VERSION }}" ]; then + echo "Error: Matrix version ${{ matrix.python-version }} does not match global version." + exit 1 + fi - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v2 with: @@ -65,10 +103,10 @@ jobs: working-directory: ./sdk steps: - uses: actions/checkout@v2 - - name: Set up Python ${{ env.X_PYTHON_VERSION }} + - name: Set up Python ${{ env.X_PYTHON_MIN_VERSION }} uses: actions/setup-python@v2 with: - python-version: ${{ env.X_PYTHON_VERSION }} + python-version: ${{ env.X_PYTHON_MIN_VERSION }} - name: Install Python dependencies run: | python -m pip install --upgrade pip @@ -88,10 +126,10 @@ jobs: working-directory: ./sdk steps: - uses: actions/checkout@v2 - - name: Set up Python ${{ env.X_PYTHON_VERSION }} + - name: Set up Python ${{ env.X_PYTHON_MIN_VERSION }} uses: actions/setup-python@v2 with: - python-version: ${{ env.X_PYTHON_VERSION }} + python-version: ${{ env.X_PYTHON_MIN_VERSION }} - name: Install Python dependencies run: | python -m pip install --upgrade pip @@ -114,10 +152,10 @@ jobs: working-directory: ./sdk steps: - uses: actions/checkout@v2 - - name: Set up Python ${{ env.X_PYTHON_VERSION }} + - name: Set up Python ${{ env.X_PYTHON_MIN_VERSION }} uses: actions/setup-python@v2 with: - python-version: ${{ env.X_PYTHON_VERSION }} + python-version: ${{ env.X_PYTHON_MIN_VERSION }} - name: Install Python dependencies run: | python -m pip install --upgrade pip @@ -135,10 +173,10 @@ jobs: working-directory: ./sdk steps: - uses: actions/checkout@v2 - - name: Set up Python ${{ env.X_PYTHON_VERSION }} + - name: Set up Python ${{ env.X_PYTHON_MIN_VERSION }} uses: actions/setup-python@v2 with: - python-version: ${{ env.X_PYTHON_VERSION }} + python-version: ${{ env.X_PYTHON_MIN_VERSION }} - name: Install dependencies run: | python -m pip install --upgrade pip diff --git a/etc/scripts/check_python_versions_coincide.py b/etc/scripts/check_python_versions_coincide.py new file mode 100644 index 00000000..774b67db --- /dev/null +++ b/etc/scripts/check_python_versions_coincide.py @@ -0,0 +1,46 @@ +""" +This helper script checks if the Python versions defined in a `pyproject.toml` coincide with the given `min_version` +and `max_version` and returns an error if they don't. +""" +import re +import argparse +import sys +from packaging.version import Version, InvalidVersion + +def main(pyproject_toml_path: str, min_version: str, max_version: str) -> None: + # Load and check `requires-python` version from `pyproject.toml` + try: + with open(pyproject_toml_path, "r") as f: + pyproject_content = f.read() + + match = re.search(r'requires-python\s*=\s*">=([\d.]+)"', pyproject_content) + if not match: + print(f"Error: `requires-python` field not found or invalid format in `{pyproject_toml_path}`") + sys.exit(1) + + pyproject_version = match.group(1) + if Version(pyproject_version) < Version(min_version): + print(f"Error: Python version in `{pyproject_toml_path}` `requires-python` ({pyproject_version}) " + f"is smaller than `min_version` ({min_version}).") + sys.exit(1) + + except FileNotFoundError: + print(f"Error: File not found: `{pyproject_toml_path}`.") + sys.exit(1) + + print(f"Success: Version in pyproject.toml `requires-python` (>={pyproject_version}) " + f"matches expected versions ([{min_version} to {max_version}]).") + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Check Python version support and alignment with pyproject.toml.") + parser.add_argument("pyproject_toml_path", help="Path to the `pyproject.toml` file to check.") + parser.add_argument("min_version", help="The minimum Python version.") + parser.add_argument("max_version", help="The maximum Python version.") + args = parser.parse_args() + + try: + main(args.pyproject_toml_path, args.min_version, args.max_version) + except InvalidVersion: + print("Error: Invalid version format provided.") + sys.exit(1) diff --git a/etc/scripts/check_python_versions_requirements.txt b/etc/scripts/check_python_versions_requirements.txt new file mode 100644 index 00000000..a8597d49 --- /dev/null +++ b/etc/scripts/check_python_versions_requirements.txt @@ -0,0 +1,2 @@ +requests>=2.23 +packaging>=24.2 diff --git a/etc/scripts/check_python_versions_supported.py b/etc/scripts/check_python_versions_supported.py new file mode 100644 index 00000000..5aad31cc --- /dev/null +++ b/etc/scripts/check_python_versions_supported.py @@ -0,0 +1,61 @@ +""" +This helper script checks that the provided `min_version` and `max_version` are supported and released, respectively, +using the API from the great https://github.com/endoflife-date/endoflife.date project. +""" +import argparse +import sys +import requests +from packaging.version import InvalidVersion +from datetime import datetime + +def main(min_version: str, max_version: str) -> None: + # Fetch supported Python versions and check min/max versions + try: + response = requests.get("https://endoflife.date/api/python.json") + response.raise_for_status() + eol_data = response.json() + eol_versions = {entry["cycle"]: {"eol": entry["eol"], "releaseDate": entry["releaseDate"]} for entry in eol_data} + + # Get current date to compare with EoL and release dates + current_date = datetime.now().date() + + # Check min_version EoL status + min_eol_date = eol_versions.get(min_version, {}).get("eol") + if min_eol_date and datetime.strptime(min_eol_date, "%Y-%m-%d").date() <= current_date: + print(f"Error: min_version {min_version} has reached End-of-Life.") + sys.exit(1) + + # Check max_version EoL and release status + max_info = eol_versions.get(max_version) + if max_info: + max_eol_date = max_info["eol"] + max_release_date = max_info["releaseDate"] + + # Check if max_version has a release date in the future + if max_release_date and datetime.strptime(max_release_date, "%Y-%m-%d").date() > current_date: + print(f"Error: max_version {max_version} has not been officially released yet.") + sys.exit(1) + + # Check if max_version has reached EoL + if max_eol_date and datetime.strptime(max_eol_date, "%Y-%m-%d").date() <= current_date: + print(f"Error: max_version {max_version} has reached End-of-Life.") + sys.exit(1) + + except requests.RequestException: + print("Error: Failed to fetch Python version support data.") + sys.exit(1) + + print(f"Version check passed: min_version [{min_version}] is supported " + f"and max_version [{max_version}] is released.") + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Check Python version support and alignment with pyproject.toml.") + parser.add_argument("min_version", help="The minimum Python version.") + parser.add_argument("max_version", help="The maximum Python version.") + args = parser.parse_args() + + try: + main(args.min_version, args.max_version) + except InvalidVersion: + print("Error: Invalid version format provided.") + sys.exit(1) diff --git a/sdk/pyproject.toml b/sdk/pyproject.toml index 80ed2ca3..7722b16e 100644 --- a/sdk/pyproject.toml +++ b/sdk/pyproject.toml @@ -34,7 +34,7 @@ classifiers = [ "Operating System :: OS Independent", "Development Status :: 5 - Production/Stable" ] -requires-python = ">=3.8" +requires-python = ">=3.9" dependencies = [ "jsonschema~=4.7", "lxml>=4.2,<5", From e17d7c45d062b06294163c832787fa8b95d80974 Mon Sep 17 00:00:00 2001 From: s-heppner Date: Thu, 14 Nov 2024 17:39:11 +0100 Subject: [PATCH 2/2] adapter.http: Fix reference to constant from the future Currently, we're using `datetime.UTC`, a constant defined in the built-in `datetime` module. However, this constant was only introduced in Python version 3.11, as you can see in the documentation: - Does not exist in [datetime Python 3.10] - Exists in [datetime python 3.11] We did not catch this, as we most likely programmed the module with Python >= 3.11 and our CI also ran `mypy` with Python 3.12. [datetime Python 3.10]: https://docs.python.org/3.10/library/datetime.html#constants [datetime python 3.11]: https://docs.python.org/3.11/library/datetime.html#datetime.UTC Fixes #330 --- sdk/basyx/aas/adapter/http.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sdk/basyx/aas/adapter/http.py b/sdk/basyx/aas/adapter/http.py index df3a8e9c..18f2baf8 100644 --- a/sdk/basyx/aas/adapter/http.py +++ b/sdk/basyx/aas/adapter/http.py @@ -80,7 +80,8 @@ def __init__(self, code: str, text: str, message_type: MessageType = MessageType self.code: str = code self.text: str = text self.message_type: MessageType = message_type - self.timestamp: datetime.datetime = timestamp if timestamp is not None else datetime.datetime.now(datetime.UTC) + self.timestamp: datetime.datetime = timestamp if timestamp is not None \ + else datetime.datetime.now(datetime.timezone.utc) class Result: