From 25106717e26b727892dbf4f94b8502426ba0b2ce Mon Sep 17 00:00:00 2001 From: guillaumepichon Date: Fri, 11 Sep 2026 11:03:15 +0200 Subject: [PATCH] Support Python 3.10-3.14, fix registry read_text() usage, drop unused accelerator-toolbox dependency Add pytest suite and GitHub Actions workflow covering Python 3.10-3.14. --- .github/workflows/unit-tests.yml | 31 +++++++++++++ pyproject.toml | 14 +++--- src/pyaml_test_lattice/registries.py | 17 ++++--- .../test_lattice_and_configuration_access.py | 46 +++++++++++++++++++ tests/test_package_import.py | 14 ++++++ tests/test_supported_python_versions.py | 14 ++++++ 6 files changed, 123 insertions(+), 13 deletions(-) create mode 100644 .github/workflows/unit-tests.yml create mode 100644 tests/test_lattice_and_configuration_access.py create mode 100644 tests/test_package_import.py create mode 100644 tests/test_supported_python_versions.py diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml new file mode 100644 index 0000000..029e05c --- /dev/null +++ b/.github/workflows/unit-tests.yml @@ -0,0 +1,31 @@ +name: Unit Tests + +on: + push: + branches: [main] + pull_request: + workflow_dispatch: + +jobs: + test: + name: Test (Python ${{ matrix.python-version }}) + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] + + steps: + - name: Check out source + uses: actions/checkout@v5 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v6 + with: + python-version: ${{ matrix.python-version }} + + - name: Install package with test dependencies + run: python -m pip install ".[test]" + + - name: Run tests + run: pytest diff --git a/pyproject.toml b/pyproject.toml index bb108b6..cbff5a2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,7 +3,7 @@ name = "pyaml-test-lattice" description = "Lattices and configuration files for pyAML tests and tutorials" readme = "README.md" license = { file = "LICENSE" } -requires-python = ">=3.11" +requires-python = ">=3.10" dynamic = ["version"] keywords = ["accelerator", "synchrotron"] @@ -17,6 +17,7 @@ classifiers = [ "Natural Language :: English", "Programming Language :: Python", "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", @@ -24,10 +25,6 @@ classifiers = [ "Topic :: Scientific/Engineering :: Physics", ] -dependencies = [ - "accelerator-toolbox>=0.6.1", -] - [project.urls] Homepage = "https://python-accelerator-middle-layer.github.io/documentation/" Repository = "https://github.com/python-accelerator-middle-layer/pyaml-test-lattice" @@ -69,9 +66,12 @@ allow-direct-references = true [tool.hatch.build.hooks.vcs] version-file = "src/pyaml_test_lattice/_version.py" +[tool.pytest.ini_options] +testpaths = ["tests"] + [tool.ruff] line-length = 127 -target-version = "py311" +target-version = "py310" [tool.ruff.lint] # Basic, sane default set: pycodestyle (E,W), pyflakes (F), @@ -98,6 +98,6 @@ indent-style = "space" skip-magic-trailing-comma = false [tool.mypy] -python_version = "3.11" +python_version = "3.10" strict = false ignore_missing_imports = true diff --git a/src/pyaml_test_lattice/registries.py b/src/pyaml_test_lattice/registries.py index 41b381c..aa6b41b 100644 --- a/src/pyaml_test_lattice/registries.py +++ b/src/pyaml_test_lattice/registries.py @@ -1,10 +1,14 @@ """Automatic registries for packaged lattice and configuration files.""" from collections.abc import Iterator, KeysView -from importlib.abc import Traversable from importlib.resources import files from pprint import pformat +try: + from importlib.resources.abc import Traversable # Python 3.12+ +except ImportError: # Python 3.10 / 3.11 + from importlib.abc import Traversable + class _Registry: """Dictionary-like registry of packaged files. @@ -49,8 +53,8 @@ def _visit(self, directory: Traversable, prefix: str = "") -> None: elif entry.is_dir(): self._visit(entry, key) - def __getitem__(self, key: str) -> str: - """Return the filesystem path of a packaged resource. + def __getitem__(self, key: str) -> Traversable: + """Return the packaged resource, ready to be read. Parameters ---------- @@ -59,10 +63,11 @@ def __getitem__(self, key: str) -> str: Returns ------- - str - Filesystem path to the requested packaged resource. + importlib.abc.Traversable + The requested packaged resource. Supports ``.read_text()`` and + ``.read_bytes()``, and stringifies to its filesystem path. """ - return str(self._files[key]) + return self._files[key] def __iter__(self) -> Iterator[str]: """Iterate over registered resource paths. diff --git a/tests/test_lattice_and_configuration_access.py b/tests/test_lattice_and_configuration_access.py new file mode 100644 index 0000000..d2b99ca --- /dev/null +++ b/tests/test_lattice_and_configuration_access.py @@ -0,0 +1,46 @@ +"""Checks that the usage documented in README.md actually works. + +These tests double as copy-paste examples: they use the registries exactly +as the README describes, nothing more. +""" + +from importlib.resources import files + +from pyaml_test_lattice import configurations, lattices + +LATTICE_KEY = "fodo_1gev_6d.json" +CONFIGURATION_KEY = "pyaml/tango/tango-pyaml/fodo_1gev_6d_pyaml.yaml" + + +def test_lattice_file_can_be_read_as_text(): + lattice_file = lattices[LATTICE_KEY] + lattice_json = lattice_file.read_text() + + assert lattice_json != "" + + +def test_configuration_file_can_be_read_as_text(): + config_file = configurations[CONFIGURATION_KEY] + config_text = config_file.read_text() + + assert config_text != "" + + +def test_lattice_key_is_listed(): + assert LATTICE_KEY in lattices.keys() + + +def test_configuration_key_is_listed(): + assert CONFIGURATION_KEY in configurations.keys() + + +def test_printing_a_registry_shows_its_description(): + # A ".description" file next to a data file is shown when the + # registry is printed, as documented in the README. Read the sidecar + # file's actual content instead of hardcoding it, so this test keeps + # checking that the registry extracts it correctly even if the + # description text itself changes. + description_file = files("pyaml_test_lattice") / "data" / "lattice" / f"{LATTICE_KEY}.description" + expected_description = description_file.read_text(encoding="utf-8").strip() + + assert expected_description in str(lattices) diff --git a/tests/test_package_import.py b/tests/test_package_import.py new file mode 100644 index 0000000..25ba052 --- /dev/null +++ b/tests/test_package_import.py @@ -0,0 +1,14 @@ +"""Sanity check that the package can be imported. + +This is a regression test for the kind of failure reported against Python +3.14: an import error inside ``pyaml_test_lattice`` (or one of its +submodules) makes this test fail at collection time, on every Python version +the package claims to support. +""" + +import pyaml_test_lattice + + +def test_package_imports_and_has_a_version(): + assert isinstance(pyaml_test_lattice.__version__, str) + assert pyaml_test_lattice.__version__ != "" diff --git a/tests/test_supported_python_versions.py b/tests/test_supported_python_versions.py new file mode 100644 index 0000000..faf481e --- /dev/null +++ b/tests/test_supported_python_versions.py @@ -0,0 +1,14 @@ +"""Keeps the supported Python range honest. + +Update MIN_PYTHON / MAX_PYTHON together with `requires-python` in +pyproject.toml and the CI matrix in .github/workflows/unit-tests.yml. +""" + +import sys + +MIN_PYTHON = (3, 10) +MAX_PYTHON = (3, 14) + + +def test_running_python_version_is_within_the_supported_range(): + assert MIN_PYTHON <= sys.version_info[:2] <= MAX_PYTHON