Repository navigation
Conversation
_PEP440_FALLBACK consumes an optional leading `v` outside the `safe`
group, so `len(safe)` is one short of the real match end. Slicing with
v[len(safe):] therefore started a character late and copied the last
digit of the numeric prefix into the local version segment.
`v` is legal PEP 440 and safe_version already accepts it, so a prefix
must not change the result: best_effort_version("v1.2-foo") returned
1.2.dev0+sanitized.2.foo where best_effort_version("1.2-foo") returned
1.2.dev0+sanitized.foo. Six of nine sampled inputs were affected, and
safer_best_effort_version feeds the .dist-info directory name.
Slice from the end of the whole match instead.
Fixes pypa#5338
Contributor
|
Tick the box to add this pull request to the merge queue (same as
|
2 tasks
feiiiiii5
force-pushed
the
hunt/pkg_resources
branch
from
October 8, 2026 06:34
ba144ed to
2b6c927
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.
Summary of changes
best_effort_versionslices the trailing part of an unparseable version withv[len(safe):], but_PEP440_FALLBACKconsumes the optional leadingvoutside thesafegroup:so
len(safe)is one short of the real match end. With avprefix the slice starts a character late and the last digit of the numeric prefix is copied into the local version segment.vis legal PEP 440 andsafe_versionalready accepts it throughpackaging.version.Version, which normalizes it away — so a prefix must not change the result. It does, for 6 of the 9 inputs I sampled:vvonmain1.2-foo1.2.dev0+sanitized.foo1.2.dev0+sanitized.2.foo1.2.3-2-gabc1.2.3.dev0+sanitized.2.gabc1.2.3.dev0+sanitized.3.2.gabc0.23-0.23.dev0+sanitized0.23.dev0+sanitized.33.11.4-1-gdeadbee3.11.4.dev0+sanitized.1.gdeadbee3.11.4.dev0+sanitized.4.1.gdeadbeeThe other three are already consistent because they parse as valid PEP 440, so the fallback is never reached. The function's own docstring documents
best_effort_version("0.23-")->'0.23.dev0+sanitized', so thev0.23-form contradicts the documented behaviour for the same input.This is not only about malformed input:
safer_best_effort_versionis whatcommand/dist_info.pycalls, so the wrong string reaches a.dist-infodirectory name —78.1.0.dev0+sanitized.0.2.g3a3144f0d.dist-infoinstead of78.1.0.dev0+sanitized.2.g3a3144f0d.dist-info.The fix slices from the end of the whole match. The pattern is anchored with
^and notre.MULTILINE, somatch.start() == 0always andmatch.end()is the correct absolute offset:On #4948
This is the same misalignment #4948 fixes as a side effect, by moving
v?inside thesafegroup instead of changing the slice. I ran both regexes side by side and the two produce the same output here, and #4948's doctest already asserts the corrected value:So there is no disagreement to resolve. I filed this separately because #4948 is a feature PR that adds a
templateparameter, changes the signature, is currentlyCONFLICTING, and is still asking whether the approach is acceptable — this gives the off-by-one a path to land on its own. If you would rather it ride along with #4948, the regression test carries over unchanged and I am happy to close this one.Closes #5338
Tests and gates
Tests
New
setuptools/tests/test_normalization.py: 12 cases, of which the load-bearing ones assert the invariant rather than a literal —best_effort_version("v" + s) == best_effort_version(s)— so they pin the property instead of over-specifying what the sanitizer should do with malformed input. Two of them also check thev-less result stays exactly what the existing doctests already document, so the fix cannot quietly change that.Against unmodified source:
setuptools/tests/test_normalization.pypytest --doctest-modules setuptools/_normalization.pyaddoptscontains--doctest-modules, so this is a gatev[len(safe) :]ruff check/ruff format --checkNot run:
mypy(not installed),tox -e docs, and the rest ofsetuptools/tests— 1028 is a collection count, and most of the remainder build wheels or install into throwaway environments.test_namespaces.pyalso works but costs ~8s per test, so I left it out of the loop.News fragment
newsfragments/5338.bugfix.rst:Stopped duplicating part of the version when sanitizing an unparseable version that starts with a ``v`` prefix.Per
newsfragments/README.rst: past tense, end-user-facing, one sentence. I will renumber to the PR number if that is preferred over the issue number.Pull Request Checklist
newsfragments/.(See documentation for details)