Visitar URL original
Fix target version inference for strict-greater requires-python specifiers by harisawan-bit · Pull Request #5269 · psf/black · GitHub
Skip to content

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

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

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

Conversation

@harisawan-bit

@harisawan-bit harisawan-bit commented Jul 28, 2026 •

Copy link
Copy Markdown

Summary

Fixes #3581 — Black's target-version inference from requires-python dropped valid Python versions for strict-greater specifiers.
Closes #5469
Closes #5468
closes #5640
Closes #5578

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 (5fb5f76) to main (f74dfd7):

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

@harisawan-bit

Copy link
Copy Markdown
Author

Apologies for the earlier confusion in my previous comment regarding 3.7.0.

To clarify why this change is needed: Black's target version inference in src/black/files.py already converts > to >= when len(version.release) > 2 (lines 219-222). The rationale is that Black targets minor Python versions (TargetVersion.PY37, TargetVersion.PY38, etc.). When a project specifies e.g. >3.7.0 or >3.7.1, it targets Python 3.7 (specifically 3.7.1+), and Python grammar/syntax does not change between patch versions of the same minor release.

However, under PEP 440, >3.7 and >3.7.0 are equivalent specifiers (Specifier('>3.7') == Specifier('>3.7.0')). Because the check was > 2 rather than >= 2, Black had an internal inconsistency where >3.7.0 inferred py37, but >3.7 inferred py38.

Changing len(version.release) >= 2 simply makes Black's treatment of > consistent across 2-part and 3-part versions.

Regarding @cobaltt7's point on stability policy and --preview: deferring this for the 27.1 annual release makes complete sense if we want to avoid any target version inference shifts mid-cycle. Happy to make any further adjustments if desired!

Signed-off-by: cobalt <61329810+cobaltt7@users.noreply.github.com>
Copilot AI lite review requested due to automatic review settings September 21, 2026 17:07

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@cobaltt7 cobaltt7 added the S: blocked A decision or another issue needs to be made/resolved before this can be worked on (more). label Sep 24, 2026
@harisawan-bit

Copy link
Copy Markdown
Author

Closing in favor of upstream's current state. Rechecked main before deciding:

  • ~= is already handled upstream via elif s.operator in ["~=", "==", ">=", "==="] (strips to major.minor), and != already passes through the final else. So those two commits here are duplicative.
  • The remaining strict-> change is a deliberate upstream behavior: len(version.release) > 2 dates back to Infer target version based on project metadata #3219 and is what keeps >3.14 inferring py315 rather than py314. @JelleZijlstra's concern that > in requires-python is questionable to begin with still stands, and @cobaltt7's point that this shifts formatting in niche cases (so it belongs behind --preview) is the blocker I can't resolve from here.

Thanks for the reviews and the patience with my earlier confusion about 3.7.0. Reopening if there's appetite for the --preview-gated variant.

@harisawan-bit
harisawan-bit deleted the fix/issue-3581-target-version-greater-than branch October 11, 2026 14:01
@cobaltt7

Copy link
Copy Markdown
Collaborator

I was planning to merge this for 27.1 if that's fine with you @harisawan-bit

@harisawan-bit
harisawan-bit restored the fix/issue-3581-target-version-greater-than branch October 26, 2026 19:43
@harisawan-bit harisawan-bit reopened this Oct 26, 2026
@harisawan-bit

Copy link
Copy Markdown
Author

@cobaltt7 That sounds great to me! Restored the branch and reopened the PR. Happy for this to be merged for 27.1.

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

S: blocked A decision or another issue needs to be made/resolved before this can be worked on (more).

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Compatible-release requires-python constraints infer unsupported targets Inference of target versions from requires-python config is incorrect.

4 participants