diff --git a/AGENTS.md b/AGENTS.md index ecac93d..49e5b3f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -80,7 +80,7 @@ NUMBA_CACHE_DIR=/tmp/numba_cache MPLCONFIGDIR=/tmp/matplotlib python -m pytest t PRs must pass `pytest --cov` on the CI matrix (Python 3.12 **and** 3.13). There is no black/ruff/flake8 gate — formatting is advisory. (`requires-python` in -`pyproject.toml` is `>=3.9`.) +`pyproject.toml` is `>=3.12`.) ## Public API diff --git a/autonerves/__init__.py b/autonerves/__init__.py index 23fce7c..6b2cd8a 100644 --- a/autonerves/__init__.py +++ b/autonerves/__init__.py @@ -42,24 +42,32 @@ def _emit_python_version_warning(): return py = f"{current[0]}.{current[1]}" - lines = [ - f"PyAuto: Python {py} detected -- first-class support is 3.12 and 3.13.", - "", - f"Things will probably work fine on {py}, but it is not the recommended", - "version and you may hit edge cases.", - ] - if current < (3, 11): - lines.extend( - [ - "", - "Note: JAX acceleration is not available on Python <3.11. Models", - "that pass use_jax=True will error.", - ] + if current < (3, 12): + lines = [ + f"PyAuto: Python {py} detected -- Python 3.12 or newer is required.", + "", + "This Python version is unsupported by current PyAuto releases.", + "Install Python 3.12 or 3.13, or pin a pre-migration PyAuto release.", + ] + warning = ( + f"PyAuto: running on unsupported Python {py}; current releases " + "require Python >=3.12." + ) + else: + lines = [ + f"PyAuto: Python {py} detected -- this Python version is experimental.", + "", + "PyAuto currently tests and supports Python 3.12 and 3.13.", + f"Python {py} may encounter known compatibility issues.", + "Use Python 3.12 or 3.13 for production work.", + ] + warning = ( + f"PyAuto: running on experimental Python {py}; supported versions " + "are 3.12/3.13." ) lines.extend( [ "", - "Recommended: install Python 3.12 or 3.13.", "To silence this warning, add to /config/general.yaml:", "", " version:", @@ -76,9 +84,9 @@ def _emit_python_version_warning(): print("\n".join(framed), file=sys.stderr) warnings.warn( - f"PyAuto: running on Python {py}; first-class support is 3.12/3.13. " - f"Suppress this warning via 'version.python_version_check: False' in " - f"config/general.yaml.", + warning + + " Suppress this warning via 'version.python_version_check: False' " + "in config/general.yaml.", UserWarning, stacklevel=2, ) diff --git a/pyproject.toml b/pyproject.toml index 5396575..2872c81 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,7 @@ dynamic = ["version"] description = "PyAuto Configration" readme = { file = "README.md", content-type = "text/markdown" } license = { text = "MIT" } -requires-python = ">=3.9" +requires-python = ">=3.12" authors = [ { name = "James Nightingale", email = "James.Nightingale@newcastle.ac.uk" }, { name = "Richard Hayes", email = "richard@rghsoftware.co.uk" }, @@ -18,15 +18,11 @@ classifiers = [ "Topic :: Scientific/Engineering :: Physics", "Natural Language :: English", "Operating System :: OS Independent", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13" ] keywords = ["cli"] dependencies = [ - "pathlib", "typing-inspect>=0.4.0", "PyYAML>=6.0.1", "numpy>=1.24.0,<3.0.0" @@ -47,9 +43,9 @@ local_scheme = "no-local-version" [project.optional-dependencies] jax = [ - "jax>=0.7.0,<0.11.0; python_version >= '3.11'", - "jaxlib>=0.7.0,<0.11.0; python_version >= '3.11'", - "jaxnnls==1.0.1; python_version >= '3.11'" + "jax>=0.7.0,<0.11.0", + "jaxlib>=0.7.0,<0.11.0", + "jaxnnls==1.0.1" ] optional = [ "autonerves[jax]", @@ -63,4 +59,4 @@ testpaths = ["test_autonerves"] filterwarnings = [ "ignore:cuda_plugin_extension:UserWarning", "ignore::DeprecationWarning:jax", -] \ No newline at end of file +] diff --git a/test_autonerves/test_python_version.py b/test_autonerves/test_python_version.py new file mode 100644 index 0000000..e3a3440 --- /dev/null +++ b/test_autonerves/test_python_version.py @@ -0,0 +1,78 @@ +import sys +from types import SimpleNamespace +import warnings + +import pytest + +import autonerves + + +def _set_python_version(monkeypatch, major, minor): + monkeypatch.setattr( + autonerves, + "sys", + SimpleNamespace(version_info=(major, minor), stderr=sys.stderr), + ) + monkeypatch.setattr( + autonerves, + "_python_version_check_bypassed", + lambda: False, + ) + + +@pytest.mark.parametrize("minor", [12, 13]) +def test_supported_python_versions_do_not_warn(monkeypatch, capsys, minor): + _set_python_version(monkeypatch, major=3, minor=minor) + + with warnings.catch_warnings(record=True) as caught: + warnings.simplefilter("always") + autonerves._emit_python_version_warning() + + assert caught == [] + assert capsys.readouterr().err == "" + + +def test_python_below_minimum_is_reported_as_unsupported(monkeypatch, capsys): + _set_python_version(monkeypatch, major=3, minor=11) + + with pytest.warns(UserWarning, match=r"require Python >=3\.12"): + autonerves._emit_python_version_warning() + + message = capsys.readouterr().err + assert "Python 3.12 or newer is required" in message + assert "unsupported by current PyAuto releases" in message + assert "pre-migration PyAuto release" in message + + +def test_python_above_supported_range_is_reported_as_experimental(monkeypatch, capsys): + _set_python_version(monkeypatch, major=3, minor=14) + + with pytest.warns(UserWarning, match=r"experimental Python 3\.14"): + autonerves._emit_python_version_warning() + + message = capsys.readouterr().err + assert "Python 3.14 detected -- this Python version is experimental" in message + assert "tests and supports Python 3.12 and 3.13" in message + assert "Use Python 3.12 or 3.13 for production work" in message + + +def test_python_version_warning_respects_yaml_bypass(monkeypatch, tmp_path, capsys): + config_path = tmp_path / "config" + config_path.mkdir() + (config_path / "general.yaml").write_text( + "version:\n python_version_check: false\n" + ) + monkeypatch.chdir(tmp_path) + monkeypatch.setattr( + autonerves, + "sys", + SimpleNamespace(version_info=(3, 14)), + ) + + with warnings.catch_warnings(record=True) as caught: + warnings.simplefilter("always") + autonerves._emit_python_version_warning() + + assert autonerves._python_version_check_bypassed() is True + assert caught == [] + assert capsys.readouterr().err == ""