Repository navigation
Apply the whole Requires-Python specifier set in Wheel.support_py - #3369
Merged
gaborbernat merged 3 commits intoOct 1, 2026
Merged
Conversation
Wheel.support_py() parsed the Requires-Python value by hand. It knew only !=, ==, <=, >=, < and >, so a ~= compatible release matched nothing and was silently ignored, and it compared only the first two components of a version, so >=3.9.1 was read as >=3.9. discover_wheels() calls this to decide which seed wheel suits the target interpreter, so a wheel that does not support it could be offered. Hand the value to packaging.specifiers.SpecifierSet instead, the same way as_version_tuple() now uses packaging.version. The whole set is applied, so every clause has to hold and every component is compared. Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
for more information, see https://pre-commit.ci
SpecifierSet raises on a value such as ">=3.6.*", which the old parser skipped, so one malformed wheel in app-data or an extra search dir aborted environment creation. Skip such a wheel with a warning, as pip does. Read Requires-Python with HeaderParser, so a lowercase or folded header counts and a line in the description body does not. Callers pass major.minor, so say so instead of claiming every component is compared. Fold the support_py tests into tables built from a wheel fixture instead of a mocked ZipFile, cover discover_wheels, and rename the changelog fragment to this PR's number with the contributor credit. Order util.py so callers precede what they call, mark its constants Final, and replace the suppressed magic number and the impossible None guard in from_path.
gaborbernat
force-pushed
the
gsz/support-py-requires-python-specifiers
branch
from
October 1, 2026 22:11
1523df3 to
87e2c1a
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Thanks for contributing, make sure you address all the checklists (for details on how see development documentation)
tox -e fix)docs/changelogfolderpyvenv.cfg, wheel downloads, app-data or release workflows: explained in thePR text which outside input the change handles, such as a path, a prompt or a downloaded file, and how it stops that
input from running as code. See the
security scope.
The bug
Wheel.support_py()insrc/virtualenv/seed/wheels/util.pyparsed theRequires-Pythonvalue out of a wheel'sMETADATAby hand. It recognised six operators,!=,==,<=,>=,<and>, and it compared the targetversion as a tuple truncated to two components.
Two things go wrong as a result:
discover_wheels()callssupport_py()to decide which wheel in a folder suits the interpreter being created, so awheel that does not support that Python can be offered. A
Requires-Pythonvalue that is not a plain comparison alsoraised, because the parser tried to
int()text such as9; python_version<'4'.The code dates back to the periodic update feature (#1841, 2020) and had almost no coverage: two tests, one asserting
a wheel with no requirement is compatible and one asserting a bundled wheel rejects 3.3.
The change
support_py()now hands the value topackaging.specifiers.SpecifierSet, which is the same moveas_version_tuple()makes withpackaging.version.Versionin #3361. The whole set is applied, so every clause has tohold and every component of the version is compared.
packagingis already a runtime dependency, added in #3361, sothis adds no new dependency.
The change is 18 lines out and 1 line in.
Tests
Four new tests, all failing on unmodified
main(a6bdda0) and passing with the fix.Before, with
src/virtualenv/seed/wheels/util.pyreverted tomain:After:
I also compared
support_py()againstpackaging.specifiers.SpecifierSetdirectly over a matrix of real-worldrequirements (
~=3.9,~=3.10.1,>=3.9.1,==3.9.*,!=3.10.*,>=3.9,>=2.7, !=3.0.*, !=3.1.*,<=3.9) againstPython 2.7, 3.8, 3.9, 3.10 and 3.14. Before the change 8 of those comparisons disagreed with
packaging; after it,0 do.
Wider selections, all passing:
Linter and type checks:
Seeding still works end to end. All 14 bundled wheels (pip and setuptools for 3.9 through 3.16) are still accepted for
their target version, and a freshly created environment reports
pip 26.2.1 from .../lib/python3.14/site-packages/pip (python 3.14).Full suite
tox -e pyon this branch: 10 failed, 485 passed, 43 skipped. The same run on unmodifiedmain: 10 failed,481 passed, 43 skipped. The difference is the four new tests, nothing else. All 10 failures reproduce on unmodified
main and are artifacts of this sandbox rather than of this change:
tests/unit/test_util.py(2): thesafe_deletetests rely on directory permissions denying an unlink, which doesnot happen for uid 0. This container runs as root.
tests/unit/activation/test_bash.py::test_bash(8): bash renders the\$prompt escape as#for uid 0 and as$for any other user, so the expected prompt text does not match. Confirmed by running the generated
activatescriptas root and as uid 65534:
(x#y)versus(x$y).fish,cshandtcshare installed here, so those activator tests ran.powershellandnuare not installed, sothe PowerShell and Nushell activator tests skip. CI runs them, so a green local run does not guarantee a green CI run
for those files.
Security scope
The outside input here is the
Requires-Pythonfield of a wheel'sMETADATA, read out of the zip. That is data, andit is passed to
packagingfor evaluation rather than interpreted by virtualenv. Nothing on this path is executed,and no new input is read. Previously an unusual value could raise out of the parser; now
packagingrejects itinstead, which is a more predictable failure.