Skip to content

Require Python 3.11 or later - #99

Merged
febus982 merged 2 commits into
mainfrom
chore/require-python-3.11
Aug 4, 2026
Merged

Require Python 3.11 or later#99
febus982 merged 2 commits into
mainfrom
chore/require-python-3.11

Conversation

@febus982

@febus982 febus982 commented Aug 3, 2026

Copy link
Copy Markdown
Owner

Summary

Raises the minimum supported Python from 3.9 to 3.11.

Python 3.9 reached end of life in October 2025, and 3.10 is security-only until October 2026. Dropping both puts the floor on a version that stays fully supported for the remainder of this package's current cycle.

Changes

File Change
pyproject.toml requires-python = ">=3.11,<3.15", trove classifiers, mypy python_version, ruff target-version = "py311"
tox.ini dropped py39 and py310 from env_list
.github/workflows/python-tests.yml matrix ["3.11", "3.12", "3.13", "3.14"] — 6 jobs down to 4
README.md version badge
uv.lock regenerated

On the lock file diff

It is large (~1400 deletions) for two reasons, both mechanical:

  • Supporting 3.9 forced uv to keep dual resolutions for much of the dev toolchain — a modern version plus an older 3.9-compatible one (mypy 1.19.1/1.20.0, tox 4.30.3/4.52.0, and ~20 others). Those collapse to single entries.
  • Two compatibility shims drop out entirely, both stdlib from 3.11 onwards: exceptiongroup (ExceptionGroup/except*) and backports-asyncio-runner (asyncio.Runner). Both were transitive dev dependencies of pytest and pytest-asyncio; no source file imported them.

The lock was regenerated on top of #97, and pymdown-extensions is still pinned at 11.0.1 — that security fix is preserved.

Not included

No library code changes. The modules still use typing.Union / typing.Dict rather than PEP 604 and PEP 585 syntax. Ruff's UP ruleset is not enabled, so nothing flags it; modernising is left as separate work.

Verification

Full tox matrix green on this branch — py311, py312, py313, py314, typing, lint, format: 7/7 OK, 210 tests passing.

🤖 Generated with Claude Code

https://claude.ai/code/session_01BJb27fB7d7HbQqfXc7U66V

Python 3.9 reached end of life in October 2025 and 3.10 is security-only
until October 2026. Dropping both raises the floor to a version that will
be fully supported for the remainder of this package's current cycle.

Update requires-python, trove classifiers, the mypy and ruff target
versions, the tox env list, the CI matrix and the README badge.

Regenerating the lock drops two compatibility shims that are part of the
standard library from 3.11 onwards, exceptiongroup and
backports-asyncio-runner, both pulled in transitively by pytest and
pytest-asyncio. Supporting 3.9 had also forced dual resolutions for much
of the dev toolchain; those collapse to single entries.

No library code changes. The modules still use typing.Union and
typing.Dict rather than PEP 604 and PEP 585 syntax; modernising them is
left as separate work.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BJb27fB7d7HbQqfXc7U66V
Now that the floor is 3.11, PEP 604 unions and PEP 585 builtin generics
are available everywhere, and the abstract collection types belong in
collections.abc rather than typing.

Enable ruff's UP (pyupgrade) ruleset so this is enforced rather than a
one-off cleanup. Without it the codebase drifts back, because new code
follows the style of the code around it. The ruleset also tracks
target-version, so it will flag the next batch automatically whenever the
floor moves again.

Almost all of the diff is `ruff check --fix`: Union[X, Y] becomes X | Y,
List/Dict/Type/Tuple become their builtin equivalents, and Mapping,
Iterator and AsyncIterator move to collections.abc. Multi-line unions
collapse to a single line, which is where most of the net reduction comes
from.

Three changes were made by hand:

- PRIMARY_KEY is a module-level alias rather than an annotation, so
  rewriting it produces a types.UnionType at runtime instead of a
  typing.Union. Ruff will not apply that fix automatically. It is safe
  here because the alias lives in a private module and is not re-exported,
  so nothing downstream can observe the change.
- _unit_of_work/__init__.py matches the "__init__.py" = ["F401"] per-file
  ignore, so the imports left unused by the rewrite had to be removed
  manually.
- Sphinx :type: fields in docstrings still named the old spellings, which
  mkdocstrings publishes. They now match the annotations they document.

No behaviour change. Full tox matrix green: py311 through py314, typing,
lint and format, with coverage still at 100%.


Claude-Session: https://claude.ai/code/session_01BJb27fB7d7HbQqfXc7U66V

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
@febus982
febus982 merged commit 02ea409 into main Aug 4, 2026
9 checks passed
@febus982
febus982 deleted the chore/require-python-3.11 branch August 4, 2026 06:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant