Skip to content

feat: require Python >= 3.10 and re-resolve the lockfile - #289

Open
jooyoungseo wants to merge 1 commit into
mainfrom
claude/python-3.9-eol-migration-ownrx1
Open

feat: require Python >= 3.10 and re-resolve the lockfile#289
jooyoungseo wants to merge 1 commit into
mainfrom
claude/python-3.9-eol-migration-ownrx1

Conversation

@jooyoungseo

@jooyoungseo jooyoungseo commented Jul 27, 2026

Copy link
Copy Markdown
Member

Pull Request

Description

Dependabot's grouped security-update run has been failing, which is why most of the repository's open security alerts could not be fixed automatically. The root cause is requires-python = ">=3.9": patched requests / urllib3 require Python >= 3.10, and the exact dev pin python-semantic-release==9.21.0 requires requests>=2.25, so uv cannot find a resolution for the python_full_version == '3.9.*' split:

No solution found when resolving dependencies for split (markers: python_full_version == '3.9.*'):
Because requests>=2.25.0,<=2.29.0 depends on urllib3>=1.21.1,<1.27 and urllib3==2.7.0,
we can conclude that requests>=2.25.0,<=2.29.0 cannot be used.
...
hint: The `requires-python` value (>=3.9) includes Python versions that are not supported
by your dependencies (e.g., urllib3==2.7.0 only supports >=3.10).

Python 3.9 reached end-of-life in October 2025. This PR raises the floor to Python 3.10 and re-resolves the lockfile, which unblocks the security upgrades.

Important

This is a breaking change to py-maidr's published Python support matrix. Python 3.9 users can no longer install new releases of maidr (pip will fall back to the last 3.9-compatible release).

Decision: ship this as a minor bump (1.19.1 → 1.20.0). The commit is typed feat:, which python-semantic-release maps to a minor bump, and that holds under a squash merge too since the squash message comes from this PR's feat: title. No BREAKING CHANGE: footer is intended — if a maintainer later wants 2.0.0 instead, add that footer to the squash body at merge time.

Related Issues

Changes Made

File Change
pyproject.toml requires-python = ">=3.9"">=3.10"; removed the Programming Language :: Python :: 3.9 classifier
.github/workflows/ci.yml Test matrix ["3.9", "3.10", "3.11", "3.12"]["3.10", "3.11", "3.12"]
.github/workflows/release.yml Same matrix change — not in the original scope, but this job runs uv sync --locked and would have failed on 3.9 against the new requires-python
tox.ini Removed py39 from envlist and the 3.9: py39 [gh-actions] mapping
docs/_quarto.yml Schema.org runtimePlatform: "Python 3.9+""Python 3.10+"
docs/index.qmd Installation section: "Python 3.x is required" → "Python 3.10 or later is required"
uv.lock Re-resolved with uv lock --upgrade

README.md and CONTRIBUTING.md were checked and make no Python-version claims, so they are unchanged. No source code changes.

Security impact

Method

The Dependabot alerts API is not reachable from this session, so the alert list could not be read directly. Instead, every (package, version) pair in the lockfile before and after this change was queried against the OSV database and the results were reduced to distinct GHSA advisories per package — the same unit Dependabot counts. That model reproduces the three reported per-package counts exactly (pillow 19, jupyterlab 6, jupyter-server 2), which is good evidence it matches how the alerts were counted.

Before → after

The old lockfile carried 64 distinct GHSA advisories across 15 packages. The new lockfile carries 1.

Package Old version(s) GHSA advisories New version Resolved
pillow 11.3.0, 12.2.0 19 12.3.0
jupyterlab 4.5.7 6 4.6.2
jupyter-server 2.18.0 2 2.20.0
mistune 3.2.1 10 3.3.4
gitpython 3.1.49 9 3.1.57
tornado 6.5.5 4 6.5.7
bleach 6.2.0, 6.3.0 3 6.4.0
filelock 3.19.1 2 3.32.0
soupsieve 2.8.3 2 2.9.1
urllib3 2.6.3 2 2.7.0
black 25.11.0 1 26.5.1
idna 3.13 1 3.18
requests 2.32.5 1 2.34.2
setuptools 82.0.1 1 (no longer in the lock)
pytest 7.4.4 1 7.4.4 not fixed

Every advisory-carrying package except pytest is now past its fix version, so all open alerts should close except any alert filed against pytest.

On the count: the OSV-derived total of 64 is slightly higher than the 59 open alerts reported. The per-package figures match exactly where they were known, so the gap is most likely advisory-database drift since the alert count was taken (plus any dismissed alerts). Please treat 64 → 1 as the shape of the change rather than an exact alert-by-alert reconciliation.

Critical alert #86 — confirmed fixed ✅

Alert #86 is GHSA-fcw5-x6j4-ccmp, "Jupyter Server: Stored XSS in NbconvertFileHandler / NbconvertPostHandler via missing sandbox CSP" (CRITICAL, CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:A/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H).

The advisory's fixed version is jupyter-server 2.20.0. The new lockfile pins jupyter-server 2.20.0 (up from 2.18.0), and OSV reports no known vulnerabilities for that version. This alert is fixed by this PR.

The second jupyter-server alert, GHSA-gf7q-q4j7-hp7c (path traversal in _get_os_path(), Moderate, fixed in 2.18.2), is also resolved.

Known remaining exposure

GHSA-6w46-j5rx-g56g — "pytest has vulnerable tmpdir handling" (Moderate, local attack vector, test-only dependency). The fix landed in pytest 9.0.3, but this repo pins pytest>=7.3.2,<8 in both the test extra and the dev group, so the upgrade is out of reach here. Lifting that pin is a separate change with its own test-compatibility risk, so it is intentionally not bundled into this PR.

Relationship to #288

This PR supersedes #288. #288 bumps 7 packages — bleach, gitpython, idna, mistune, setuptools, soupsieve, tornado — and this lockfile already advances every one of them to the same version or newer (gitpython 3.1.57 vs 3.1.54, idna 3.18 vs 3.15, mistune 3.3.4 vs 3.3.0, soupsieve 2.9.1 vs 2.8.4; setuptools is dropped from the lock entirely). It also covers the packages #288 could not touch, including pillow, jupyterlab, and jupyter-server.

Either order works, but merging this one and closing #288 is the cleaner path: landing #288 first only creates a uv.lock conflict that this branch would have to resolve, with no additional coverage. Note that #288 currently reports mergeable_state: unstable with a pending status check rather than a fully green run.

Verification

Run locally against the new lockfile:

  • uv lock --check — lockfile in sync with pyproject.toml
  • uv sync --locked --all-extras --dev — resolves and installs cleanly on 3.10, 3.11, and 3.12
  • uv run pytest -vvv178 passed on 3.10.20, 3.11.15, and 3.12.3
  • ruff check --diff — clean ✅

CI on this branch is green: Python 3.10 / 3.11 / 3.12 tests, lint, commit-lint, workflow lint, and all three CodeQL analyses pass. The single skipped job is ci.yml's duplicate-run guard behaving as designed — internal PRs are covered by the push-event run rather than the pull_request run.

Scope of the lockfile churn

uv lock --upgrade pulled in unrelated transitive bumps alongside the security fixes — notably numpy 2.5.1, pandas 3.0.5, matplotlib 3.11.1 (on newer Pythons), plotly 6.9.0, and notebook 7.6.1. Flagging that explicitly, since it rode along with the requires-python change rather than being requested.

This does not change what end users resolve. uv.lock is not shipped: the wheel is packages = ["maidr"] and the sdist include list is maidr/**/*.py, maidr/static/**, README.md, LICENSE, pyproject.toml. So the lockfile governs CI and local dev environments only. No constraint in [project.dependencies] or any extra was touched, so pip install maidr resolves exactly as it did before — the only user-visible change is the Python floor.

One behavioral note from the newer pins: the pre-existing vert: deprecation warning from maidr/patch/boxplot.py is now a MatplotlibDeprecationWarning under matplotlib 3.11 (it was a PendingDeprecationWarning before). Non-fatal and unrelated to this change, but it will need addressing before matplotlib 3.13 removes the parameter.

Checklist

  • I have read the Contributor Guidelines.
  • I have performed a self-review of my own code and ensured it follows the project's coding standards.
  • I have tested the changes locally, and all tests related to this pull request pass.
  • I have commented my code, particularly in hard-to-understand areas. (N/A — no source code changes.)
  • I have updated the documentation, if applicable.
  • I have added appropriate unit tests, if applicable. (N/A — packaging metadata and lockfile change.)

Additional Notes

The minimum supported Python is now 3.10, matching the floor that urllib3 2.7+ and current requests already impose. Once this lands, Dependabot's grouped security-update run should resolve successfully again and can keep the remaining alerts current on its own.

Python 3.9 reached end-of-life in October 2025, and keeping it in
`requires-python` made uv's resolution unsatisfiable: patched `requests`
and `urllib3` need Python >= 3.10, while the dev pin
`python-semantic-release==9.21.0` requires `requests>=2.25`. Dependabot's
grouped security-update run failed on exactly that conflict, so most
security alerts could not be fixed automatically.

- Raise `requires-python` to `>=3.10` and drop the 3.9 classifier
- Drop 3.9 from the CI and release test matrices and from tox's envlist
- Update the Python support claims in the Quarto docs
- Re-resolve `uv.lock` with `uv lock --upgrade`

This is a breaking change to the published Python support matrix:
Python 3.9 users can no longer install new releases of `maidr`.
@github-actions

Copy link
Copy Markdown

Review

This is a clean, well-scoped packaging change — bumping requires-python to unblock Dependabot's resolver conflict is the right fix, and the PR body does a good job of explaining the root cause and quantifying the security impact. A few notes:

Code quality / correctness

  • The four touch points that needed updating for consistency (pyproject.toml classifiers, ci.yml/release.yml matrices, tox.ini envlist/gh-actions mapping, docs) are all covered. I checked for stragglers (docs.yml, copilot-setup-steps.yml, README.md, CONTRIBUTING.md, any sys.version_info/python_requires conditionals in source) and found none referencing 3.9, so nothing was missed.
  • No source code changes, so no _extract_plot_data/patch-layer risk here.

Versioning (the one thing worth a maintainer decision before merge)

  • The PR body flags this well already: this is a breaking change (drops a previously-supported Python version), but the commit is typed feat:, which python-semantic-release maps to a minor bump, not major. Since the decision is already being deferred to "add a BREAKING CHANGE: footer when merging," consider just adding that footer to the commit now rather than leaving it as a merge-time judgment call — it's easy to forget once the PR is approved and merged via the GitHub UI (which uses the PR title/body as the squash commit message, not necessarily preserving the footer from this branch's commit).

Test coverage

  • No new tests needed here — this is packaging/CI metadata, not behavior. tox.ini/CI matrix changes are self-verifying (CI will fail loudly if wrong).
  • The PR body reports pytest -vvv passing on 3.10/3.11/3.12 locally; worth confirming CI itself goes green on this branch (in particular release.yml, since that job's matrix change wasn't in the original issue scope and is easy to overlook in review).

Security

  • The OSV-based before/after methodology (64 → 1 advisories) is a reasonable substitute given the Dependabot alerts API wasn't reachable, and the reasoning for why pytest alert GHSA-6w46-j5rx-g56g remains open (blocked on the <8 pin) is sound and appropriately scoped out rather than bundled in.
  • No secrets, no new external endpoints, no injection surface — this PR only touches version constraints and docs strings.

Minor

  • docs/index.qmd's wording change ("Python 3.10 or later is required") reads well and matches the new floor.
  • Given uv.lock is regenerated wholesale via uv lock --upgrade, it's worth double-checking that pulling in unrelated transitive major bumps (numpy 2.5.1, pandas 3.0.5, matplotlib 3.11.1, plotly 6.9.0 per the PR description) doesn't silently change runtime behavior for users who pip install without a lockfile — the PR body confirms tests still pass, which is reassuring, but transitive major-version jumps riding along with a requires-python bump are worth flagging explicitly to maintainers as "also happened here" rather than being purely incidental.

Overall: solid, well-documented change. The only actionable item is nailing down the version-bump type (minor vs. major) before merge so semantic-release does the right thing.

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