Skip to content

build!: drop python 3.9 and 3.10, drop pyside2, require >=3.11 - #745

Merged
tlambert03 merged 1 commit into
mainfrom
feat/drop-py39-py310
Aug 26, 2026
Merged

build!: drop python 3.9 and 3.10, drop pyside2, require >=3.11#745
tlambert03 merged 1 commit into
mainfrom
feat/drop-py39-py310

Conversation

@tlambert03

@tlambert03 tlambert03 commented Aug 26, 2026

Copy link
Copy Markdown
Member

Drops Python 3.9/3.10 and moves the floor to >=3.11, plus the ruff updates that fall out of it.

⚠️ This also drops PySide2

Not a choice so much as a consequence: PySide2 5.15.2.1 declares requires_python: ... <3.11 and ships no wheel past cp310. It cannot be installed on the new floor, so its extra, dependency-group and CI jobs are all dead weight. Removing it also lets us delete the test-qt-legacy group and the [tool.uv] conflicts block from #743, which existed only to hold PySide2 on pytest-qt 4.4.

Changes

Version floor

  • requires-python = ">=3.11", 3.9/3.10 classifiers removed
  • CI: base matrix 3.103.11, min-deps 3.93.11, pyqt5 job 3.93.11, test-dependents default 3.103.11 (it would otherwise try to install magicgui on 3.10)
  • SLOTS is unconditional now; the <3.11 weakref_slot skip is dead code
  • PyQt5 is untouched — it supports 3.11+, only its job's Python moved

Ruff

  • target-version py39py311
  • Dev toolchain re-pinned to what pre-commit already runs. These had drifted badly: ruff>=0.8.3 resolved to 0.15.12 locally while pre-commit pinned 0.16.4 — different enough that RUF036 is preview in one and stable in the other, so uv run ruff and CI disagreed about the codebase.
  • 91 autofixes: Callablecollections.abc, typing_extensionstyping where 3.11 suffices, zip(..., strict=False) (explicit, behavior-preserving), one dead type: ignore

Two rules deliberately disabled

UP007/UP045 (Optional[X]X | None) are in ignore, because for this codebase they are not a cosmetic change:

get_origin(Union[int, None]) is Union   # True
get_origin(int | None) is Union         # False on 3.11-3.13, True on 3.14

magicgui compares get_origin(...) is Union in four places, so PEP 604 unions take a different path. Letting ruff apply these broke 7 tests in test_ui_field.py — it rewrote Optional[int] to int | None and UiField then extracted a different annotation. Tests also deliberately exercise both spellings, so blanket-rewriting them would quietly cost coverage.

TC003collections.abc is added to flake8-type-checking.exempt-modules, alongside the typing/typing_extensions that ruff exempts by default. These 16 findings only appeared because Callable moved off typing; magicgui resolves annotations at runtime, so those names must stay importable at runtime rather than move into TYPE_CHECKING.

Known pre-existing bug (not fixed here)

The above uncovered a real bug on main, unrelated to this PR:

@magicgui
def f(x: Optional[int] = None): ...   # .annotation -> int          ✅

@magicgui
def f(x: int | None = None): ...      # .annotation -> int | None   ❌

PEP 604 unions aren't unwrapped by the four get_origin(...) is Union checks on Python 3.11–3.13 (masked on 3.14, where types.UnionType became typing.Union). Worth its own PR; happy to write it.

Testing

Full suite green on 3.11 / 3.13 / 3.14, across PyQt6, PyQt5, PySide6. prek run -a clean.

🤖 Generated with Claude Code

BREAKING CHANGE: python 3.9 and 3.10 are no longer supported, and the
PySide2 backend is dropped along with them (PySide2 5.15.2.1 declares
`requires_python <3.11` and ships no wheel past cp310, so it cannot be
installed or tested on the new floor).

- requires-python = ">=3.11"; drop the 3.9/3.10 classifiers
- drop the pyside2 extra, dependency-group, docs and CI jobs, along with
  the test-qt-legacy group and [tool.uv] conflicts block that existed
  only to keep PySide2 on pytest-qt 4.4
- CI: base matrix 3.10 -> 3.11, min-deps 3.9 -> 3.11, pyqt5 job 3.9 ->
  3.11, test-dependents default 3.10 -> 3.11
- SLOTS is now unconditional, and the <3.11 weakref_slot skip is dead
- ruff target-version py39 -> py311, and pin the dev ruff/mypy to the
  versions pre-commit already runs (they had drifted to ruff 0.15.12
  against pre-commit's 0.16.4)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@tlambert03 tlambert03 changed the title feat!: drop python 3.9 and 3.10, require >=3.11 build!: drop python 3.9 and 3.10, require >=3.11 Aug 26, 2026
@tlambert03 tlambert03 changed the title build!: drop python 3.9 and 3.10, require >=3.11 build!: drop python 3.9 and 3.10, drop pyside2, require >=3.11 Aug 26, 2026
@codecov

codecov Bot commented Aug 26, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 89.31%. Comparing base (128e4ba) to head (2ca01a9).

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #745      +/-   ##
==========================================
- Coverage   89.32%   89.31%   -0.02%     
==========================================
  Files          40       40              
  Lines        4835     4849      +14     
==========================================
+ Hits         4319     4331      +12     
- Misses        516      518       +2     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@larsoner larsoner left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I dropped PySide2 in PyVistaQt as well, no regrets! 😄

@tlambert03
tlambert03 merged commit d17990c into main Aug 26, 2026
31 checks passed
@tlambert03
tlambert03 deleted the feat/drop-py39-py310 branch August 26, 2026 12:44
tlambert03 added a commit that referenced this pull request Aug 26, 2026
UP035 under the new py311 target. #742 merged after #745 switched
target-version, so its CI ran against the old config and main is
currently failing ruff.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
tlambert03 added a commit that referenced this pull request Aug 26, 2026
* fix: handle PEP 604 unions (`X | Y`) like `Union[X, Y]`

`get_origin(int | None)` is `types.UnionType`, not `typing.Union`, on
python < 3.14 -- so the four `get_origin(...) is Union` comparisons
silently took the non-union path for PEP 604 annotations.

Most visibly, the Optional wrapper was not stripped from a widget's
reported annotation:

    @magicgui
    def f(x: Optional[int] = None): ...   # .annotation -> int
    @magicgui
    def f(x: int | None = None): ...      # .annotation -> int | None

and `register_type(int | str, return_callback=...)` registered nothing
for the individual member types.

Adds `magicgui._util.is_union`, which accepts both spellings, and uses
it at all four sites.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* style: import Callable from collections.abc in the ipynb backend

UP035 under the new py311 target. #742 merged after #745 switched
target-version, so its CI ran against the old config and main is
currently failing ruff.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* style: enable UP045 for source now that PEP 604 unions work

The preceding fix makes `X | None` behave like `Optional[X]`, so the
pyupgrade rewrite is safe for src. Scoped deliberately:

- UP045 is enabled for src/ and docs/ only; tests/ keep it ignored,
  since they exercise both spellings on purpose (see test_no_order).
- UP007 stays ignored: `Union` is still needed as a runtime *value* for
  the public type aliases (PathLike, ChoicesType, AppRef, TableData,
  WidgetRef) and for `Union[args]` construction. ruff offers no fix for
  those 11 sites, so enabling it would just leave permanent errors.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <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.

2 participants