Visitar URL original
Fix ~=X.Y.Z requires-python inferring later Python versions by beniamin-levin · Pull Request #5578 · psf/black · GitHub
Skip to content

Fix ~=X.Y.Z requires-python inferring later Python versions - #5578

Open
beniamin-levin wants to merge 3 commits into
psf:mainfrom
beniamin-levin:fix-compatible-release-requires-python
Open

beniamin-levin wants to merge 3 commits into
psf:mainfrom
beniamin-levin:fix-compatible-release-requires-python

Conversation

@beniamin-levin

@beniamin-levin beniamin-levin commented Oct 22, 2026 •

Copy link
Copy Markdown

Description

Fixes #5468.

strip_specifier_set turned ~=3.11.0 into ~=3.11. That drops the upper bound: ~=3.11.0 means >=3.11.0, ==3.11.* (only 3.11), while ~=3.11 means >=3.11, ==3.*. So requires-python = "~=3.11.0" inferred py311 through py315. A compatible-release specifier with a patch component (three or more release parts) is now stripped to ==X.Y.*; the two-part form is unchanged.

requires-python main this PR
~=3.11.0, ~=3.11.7, ~=3.11.0rc1, ~=3.11.0.1 py311 to py315 py311
~=3.9.2,!=3.9.5 py39 to py315 py39
~=3.11 py311 to py315 py311 to py315 (unchanged)
~=3.0.0 py33 to py315 None (Black has no 3.0 target)

Stability. The minimum inferred target version never rises. For ~=3.7.x through ~=3.15.x the feature set of the inferred targets is the same on main and with this change (checked for every minor version, with and without a patch number), so stable formatting does not change. Only ~=3.3.x to ~=3.6.x differ, and there the new result is the correct one. In a sweep of 6,084 single and pairwise specifier sets, the 445 results that changed all come from a set containing a ~= with three or more release parts.

Known edge, not changed here. ~=3.11.4,!=3.11.0 and ~=3.11.0,>3.11 now give None (on main they gave py312 and up; the right answer is py311). That comes from the existing handling of !=X.Y.0 and of a two-part > (#3581), not from this change.

An earlier pull request for this issue, #5469, took the same approach and was closed by its author without a review. This one was made independently, as described below.

How this was made (AI disclosure). FixedYet, a tool that reproduces bug reports in a sandbox, wrote a failing test for the issue on main; NVIDIA Nemotron, through FixedYet, proposed the change to strip_specifier_set, and FixedYet verified it (the test fails without it and passes with it, and the suite had no new failures). A review with Claude (Anthropic) checked it against PEP 440, moved it to the literal ==X.Y.* form with a comment, and added the table cases and the changelog entry. I read the final diff myself before marking this ready for review.

Checked locally with Black's CI commands, in a clean Python 3.12 container:

  • pytest tests --run-optional no_jupyter --numprocesses auto: 596 passed, 1 skipped (IPython is optional), the same as on main
  • pytest tests --run-optional jupyter -m jupyter --numprocesses auto: 83 passed
  • black, flake8, isort and mypy (with the pre-commit hook's dependencies) on the changed files, and prettier on CHANGES.md: clean
  • the new table cases fail on main without the change

Checklist - did you ...

  • Implement any code style changes under the --preview style, following the stability policy? (not a style change; see Stability above)
  • Add an entry in CHANGES.md if necessary?
  • Add / update tests if necessary?
  • Add new / update outdated documentation? (none needed)

beniamin-levin and others added 3 commits October 22, 2026 10:03
`~=3.11.0` means `>=3.11.0, ==3.11.*`, but strip_specifier_set turned it
into `~=3.11`, which allows every later Python 3 version, so target version
inference returned py311 through py315. A `~=` with a patch component is now
stripped to `==X.Y.*`; the two-part form `~=3.11` is unchanged.

The change was proposed by NVIDIA Nemotron through FixedYet and verified in
its sandbox, then moved to the literal `==X.Y.*` form with a comment in
review.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…elog

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

This branch has not been deployed

No deployments
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.

Compatible-release requires-python constraints infer unsupported targets

1 participant