Skip to content

Fix target version inference for strict-greater requires-python specifiers - #5269

Open
harisawan-bit wants to merge 3 commits into
psf:mainfrom
harisawan-bit:fix/issue-3581-target-version-greater-than
Open

Fix target version inference for strict-greater requires-python specifiers#5269
harisawan-bit wants to merge 3 commits into
psf:mainfrom
harisawan-bit:fix/issue-3581-target-version-greater-than

Conversation

@harisawan-bit

Copy link
Copy Markdown

Summary

Fixes #3581 — Black's target-version inference from requires-python dropped valid Python versions for strict-greater specifiers.

Per PEP 440, >3.7 is equivalent to >3.7.0, so any 3.7.x (e.g. 3.7.0) is included. strip_specifier_set only widened a > operator to the inclusive >= when a patch component was present (len(release) > 2), so >3.7 was left strict and py37 was dropped from the inferred target versions.

Now > becomes >= whenever a minor version is present, making >3.7 behave like >3.7.0 / >=3.7.

Before / after

requires-python Before After
>3.7,<3.10 py38, py39 py37, py38, py39
>3.7,!=3.8,!=3.9 py310..py315 py37, py310..py315
>3.10,<3.11 None py310
>=3.7 / >3.7.0 unchanged unchanged

These all now match the equivalent >=3.7 / >3.7.0 forms, as PEP 440 requires.

Test plan

Checklist

  • Changes are covered by tests
  • CHANGES.md updated

Muhammad Haris Awan and others added 2 commits July 28, 2026 21:01
…fiers

Per PEP 440, '>3.7' is equivalent to '>3.7.0', so any 3.7.x (e.g. 3.7.0)
is included. strip_specifier_set only widened a '>' operator to '>=' when a
patch component was present, so '>3.7' was left strict and dropped py37 from
the inferred target versions. Now '>' becomes '>=' whenever a minor version is
present, matching '>3.7.0' / '>=3.7'.

Fixes psf#3581
@github-actions

github-actions Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

diff-shades results comparing this PR (aaac28d) to main (c7801d9):

--preview style: no changes

--stable style: no changes


What is this? | Workflow run | diff-shades documentation

@cobaltt7

cobaltt7 commented Jul 28, 2026

Copy link
Copy Markdown
Collaborator

Thanks @harisawan-bit! To fix the failing CI, please run Black on the codebase and change the issue number in CHANGES.md to the PR number.


This PR could theoretically change formatting in very niche cases, so it should theoretically be gated behind --preview. Here's an example:
Say requires-python is >3.14,<3.15 and Black runs on this code:

try:
    ...
except (a, b):
    ...

Black currently cannot determine a target version from requires-python, so it's conservative and keeps parenthesis around the exception types.
After this PR, the target version is now 3.14, and Black can and does remove the parenthesis in line with PEP 758.

Since most changes lower the detected target version, and Black does not downcompile syntax (meaning if you run Black with -t 313 after the above scenario, Black would not add the parenthesis back), there's not many scenarios where this would be an issue, and where it is it's caused by a likely misconceived configuration. To be clear, this could only violate the stability policy when raising the minimum target version, or when either the previous or new target version was/is undefined.

Adding to the complexity, this can't be gated under --preview without significant refactors, because this code runs before the config is fully determined. Our two main options are then either (a) merge it as-is, it's not likely anyone will have issues, it's their fault if their config is set up wrong, or (b) wait 5 months and merge it for 27.1 without putting it in preview first. Cc @JelleZijlstra


Looking at the related #3583, I took the advantage to also change ~= and != to be more in line with the spec (with much less drastic changes than #3583):

requires-python Before After
~=3.0.0 py33..py315 None
~=3.3.0 py33..py315 py33
~=3.3 py33..py315 unchanged
!=3.3 py34..py315 py33..py315
!=3.3.* py34..py315 unchanged

I can either push those changes to this PR, or make a new one, partially depending on how we decide to handle the --preview issue.
(The reduced complexity is mainly because I left the behavior of specifiers with 4+ parts (like ~=3.15.0.1) undefined, since Python currently has no plans to use a 4-part version)

@JelleZijlstra

Copy link
Copy Markdown
Collaborator

Using ">" (as opposed to ">=") in requires-python is very questionable anyway. Does it ever get used? Has there been discussion in the packaging ecosystem of whether this option should even be supported?

@harisawan-bit

Copy link
Copy Markdown
Author

@JelleZijlstra

The use of > in requires-python is indeed valid per PEP 440, which specifies that >3.7 is equivalent to >3.7.0. This means that any version 3.7.x (e.g., 3.7.0) should be included. The fix ensures that Black's target version inference aligns with this specification, making the behavior consistent and predictable. While it may not be commonly used, supporting it ensures compliance with the PEP 440 standard and avoids potential confusion or errors in edge cases.

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.

Inference of target versions from requires-python config is incorrect.

3 participants