Skip to content

build: support Python 3.11+ and verify the range in CI - #39

Merged
Ndevu12 merged 1 commit into
mainfrom
feat/support-python-3.11-plus
Sep 1, 2026
Merged

build: support Python 3.11+ and verify the range in CI#39
Ndevu12 merged 1 commit into
mainfrom
feat/support-python-3.11-plus

Conversation

@Ndevu12

@Ndevu12 Ndevu12 commented Sep 1, 2026

Copy link
Copy Markdown
Owner

Problem

Pipfile pinned python_version = "3.13", so pipenv refused to create a virtualenv on any other interpreter:

Warning: Python 3.13 was not found on your system...
Neither 'pyenv' nor 'asdf' could be found to install Python.

The pin was arbitrary. Nothing in the codebase needs 3.13, and every dependency already ships wheels well past it.

Evidence

Scanned all 155 files / 25,402 lines for version-gated features — no 3.12+ or 3.13+ constructs: no PEP 695 type X = / def f[T], no itertools.batched, no typing.override, no Path.walk, no warnings.deprecated, no copy.replace. The codebase doesn't even use the 3.11 additions (TaskGroup, StrEnum, tomllib, Self).

Wheel availability at the currently locked versions:

package locked CPython wheel tags requires_python
torch 2.12.0+cpu cp310–cp314
numpy 2.4.6 cp311–cp314 >=3.11
hdbscan 0.8.43 cp310–cp314 >=3.10
pymupdf 1.28.2 cp310, cp313, cp314 (abi3) >=3.10

numpy>=3.11 is the real floor, so that is where the floor now sits.

Changes

  • Pipfilepython_version = ">=3.11". Pipenv resolves PEP 440 specifiers natively (_find_python_for_specifier, pipenv/utils/virtualenv.py), selecting the best installed match instead of demanding one exact version.
  • pyproject.toml — ruff target-version = "py311". Targeting the floor is what makes the linter reject 3.12+ syntax that would break the oldest supported interpreter. At py313 the new floor would have been unguarded.
  • .github/workflows/ci.yml — test job matrixed over 3.11 / 3.12 / 3.13 / 3.14 with fail-fast: false, so one broken interpreter never masks the others. The setup-pipenv composite action already took a python-version input and already keyed its cache on it, so it needed no change.
  • Pipfile.lock[requires] participates in pipenv's Pipfile hash (_PIPFILE_SECTIONS, project.py), so editing it invalidated _meta.hash and would have failed CI's pipenv install --deploy. Updated _meta.hash and _meta.requires only — recomputed with pipenv's own calculate_pipfile_hash(), not hand-rolled. No re-resolution, so no dependency version churn: a 2-line diff with all 165 default + 38 develop packages intact.
  • Docs — 3.13 → 3.11+ across README and the docs site. Added a PEP 668 note (pipx install pipenv) for externally-managed distros where pip install pipenv fails outright, and cp -n so the .env step cannot clobber an existing file.

Verification

  • ruff check src tests setups passes at target-version = "py311".
  • Pipenv's own matcher against the new specifier: 3.11.9 / 3.12.7 / 3.13.1 / 3.14.6 accepted, 3.10.14 refused — matching numpy's floor exactly.
  • Lock integrity re-checked: JSON parses, hash matches the edited Pipfile, package counts unchanged.

Not verified locally: a real pipenv install was not run. This checkout is flagged by a local repo scanner, so I did not install its dependencies. The CI matrix is the first end-to-end proof, and the 3.11 leg is the one to watch — a pipenv lock is a single resolution, not a universal one.

Follow-ups (not in this PR)

  • No CHANGELOG.md exists in the repo, so no [Unreleased] bullet was added rather than inventing the convention.
  • A [project] table with requires-python was considered and deliberately skipped: with a src/ layout plus tests/, setups/, scripts/, evals/ at root, adding [project] under the setuptools backend risks a "multiple top-level packages discovered" build error. It deserves its own PR with proper [tool.setuptools] config.

The Pipfile pinned `python_version = "3.13"`, so pipenv refused to build a
virtualenv on any other interpreter — including 3.14, where every dependency
already has wheels. The pin was arbitrary: nothing in the codebase or the
dependency set required 3.13.

Widen the floor to the version the dependencies actually impose (numpy 2.4.6
declares `requires_python >=3.11`) and prove the range in CI instead of
asserting it.

- Pipfile: `python_version = ">=3.11"`. Pipenv resolves PEP 440 specifiers
  natively via `_find_python_for_specifier` (pipenv/utils/virtualenv.py), so
  this selects the best installed match rather than demanding one version.
- pyproject: ruff `target-version = "py311"`. Targeting the floor is what
  makes the linter reject 3.12+ syntax that would break the oldest supported
  interpreter; leaving it at py313 left the floor unguarded.
- ci.yml: matrix the test job over 3.11–3.14 with `fail-fast: false`. The
  setup-pipenv action already accepted a `python-version` input and keyed its
  cache on it, so no change was needed there.
- Pipfile.lock: `[requires]` participates in pipenv's Pipfile hash
  (`_PIPFILE_SECTIONS`, project.py), so editing it invalidated `_meta.hash`
  and would have failed `pipenv install --deploy`. Updated `_meta.hash` and
  `_meta.requires` only — no re-resolution, so no dependency version churn.
- Docs: 3.13 -> 3.11+ across README and the docs site, plus a PEP 668 note
  (`pipx install pipenv`) for externally-managed distros where
  `pip install pipenv` fails, and `cp -n` so the .env step cannot clobber an
  existing file.

Verified: ruff passes at py311 across src/tests/setups; pipenv's own matcher
accepts 3.11-3.14 and refuses 3.10; torch 2.12.0+cpu, numpy 2.4.6, hdbscan
0.8.43 and pymupdf all publish cp311-cp314 wheels.

Claude-Session: https://claude.ai/code/session_01KYPdxd7zY8xNT9p9yxnnaS
@Ndevu12 Ndevu12 self-assigned this Sep 1, 2026
@Ndevu12 Ndevu12 added the enhancement New feature or request label Sep 1, 2026
@Ndevu12
Ndevu12 merged commit 3cc0beb into main Sep 1, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant