Skip to content

Modernise typing syntax and enforce pyupgrade - #100

Merged
febus982 merged 1 commit into
chore/require-python-3.11from
chore/modernize-typing-syntax
Aug 4, 2026
Merged

Modernise typing syntax and enforce pyupgrade#100
febus982 merged 1 commit into
chore/require-python-3.11from
chore/modernize-typing-syntax

Conversation

@febus982

@febus982 febus982 commented Aug 3, 2026

Copy link
Copy Markdown
Owner

Stacked on #99. Base is chore/require-python-3.11, not main — the diff below is only this change. Merge #99 first and GitHub will retarget this to main automatically.

Summary

With the floor at 3.11, PEP 604 unions (X | Y) and PEP 585 builtin generics (list[T]) are available everywhere, and the abstract collection types belong in collections.abc rather than typing.

This also enables ruff's UP (pyupgrade) ruleset permanently, so the style is enforced rather than cleaned up once. Without it the codebase drifts straight back, because new code follows the style of the code around it. The ruleset tracks target-version, so it will flag the next batch automatically whenever the floor moves again.

Net −43 lines. No behaviour change.

What ruff did

131 findings, applied with ruff check --fix:

Rule What it changes
UP007 Union[X, Y]X | Y
UP006 List / Dict / Type / Tuplelist / dict / type / tuple
UP035 Mapping, Iterator, AsyncIterator move from typing to collections.abc

Most of the size reduction is multi-line unions collapsing:

config: Union[
    Mapping[str, SQLAlchemyConfig],
    SQLAlchemyConfig,
]
# becomes
config: Mapping[str, SQLAlchemyConfig] | SQLAlchemyConfig

The three hand edits — worth a look during review

Everything else is mechanical; these are not.

  1. PRIMARY_KEY in _repository/common.py. This is a module-level alias, not an annotation, so rewriting it produces a types.UnionType at runtime rather than a typing.Union — a different object, observable by anything that introspects it. Ruff deliberately declines to auto-fix it. It is safe here because the alias lives in a private module and is not re-exported through repository.py or the package root, so no downstream code can see the change. Flagging it explicitly because it is the one line in this PR with runtime semantics attached.

  2. _unit_of_work/__init__.py. Matches the "__init__.py" = ["F401"] per-file ignore, so the imports the rewrite left unused were not stripped automatically and had to be removed by hand.

  3. Docstrings. Sphinx :type: fields still named the old spellings (Union[CursorReference, None], List[MODEL]). mkdocstrings publishes these, so they would have contradicted the annotations directly above them in the rendered docs.

Out of scope

  • PEP 695 (class Repository[MODEL]:) is 3.12+, so out of reach at a 3.11 floor. The TypeVar / Generic[MODEL] machinery in common.py is untouched, including the deliberately constrained CURSOR_VALUE and its explanatory comment.
  • Pyright flags @contextmanager + -> Iterator[T] as favouring Generator[T] in _unit_of_work/__init__.py. I checked the annotations against the base commit: they are byte-identical, only the import source moved. Pre-existing, not introduced here, and mypy — the checker this project actually configures — does not flag it.

Verification

Full tox matrix green: py311, py312, py313, py314, typing, lint, format — 7/7 OK, 210 tests, coverage still at 100% (fail_under = 100).

🤖 Generated with Claude Code

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%.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BJb27fB7d7HbQqfXc7U66V
@febus982
febus982 merged commit a7784e0 into chore/require-python-3.11 Aug 4, 2026
@febus982
febus982 deleted the chore/modernize-typing-syntax branch August 4, 2026 06:46
febus982 added a commit that referenced this pull request Aug 4, 2026
* Require Python 3.11 or later

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

* Modernise typing syntax and enforce pyupgrade (#100)

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>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
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