Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions newsfragments/5338.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Stopped duplicating part of the version when sanitizing an unparseable version
that starts with a ``v`` prefix.
7 changes: 6 additions & 1 deletion setuptools/_normalization.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ def best_effort_version(version: str) -> str:
'0.dev0+sanitized'
>>> best_effort_version("42.+?1")
'42.dev0+sanitized.1'
>>> best_effort_version("v42.+?1") # a `v` prefix changes nothing
'42.dev0+sanitized.1'
"""
try:
return safe_version(version)
Expand All @@ -89,7 +91,10 @@ def best_effort_version(version: str) -> str:
match = _PEP440_FALLBACK.search(v)
if match:
safe = match["safe"]
rest = v[len(safe) :]
# `safe` may be shorter than the match, because `_PEP440_FALLBACK`
# also consumes an optional `v` prefix. Slice from the end of the
# whole match, so the prefix is not left out of `rest`.
rest = v[match.end() :]
else:
safe = "0"
rest = version
Expand Down
43 changes: 43 additions & 0 deletions setuptools/tests/test_normalization.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
"""Tests for :mod:`setuptools._normalization`.

The functions here are pure string transformations, so they are checked
directly, without building anything.
"""

import pytest

from setuptools import _normalization


@pytest.mark.parametrize(
('version', 'expected'),
[
# A `v` prefix is legal PEP 440 (and explicitly accepted by
# ``_PEP440_FALLBACK``), so it must not change the sanitized result.
('0.23-', '0.23.dev0+sanitized'),
('v0.23-', '0.23.dev0+sanitized'),
('V0.23-', '0.23.dev0+sanitized'),
('1.2-foo', '1.2.dev0+sanitized.foo'),
('v1.2-foo', '1.2.dev0+sanitized.foo'),
('1!2.0-foo', '1!2.0.dev0+sanitized.foo'),
('v1!2.0-foo', '1!2.0.dev0+sanitized.foo'),
# Nothing after the numeric prefix: no leftover in the local segment.
('v1.2', '1.2'),
('v1.2.', '1.2.dev0+sanitized'),
],
)
def test_best_effort_version_v_prefix_is_not_duplicated(version, expected):
"""The ``v`` prefix is consumed by the fallback regex, so it must not be
miscounted when splitting the version into its numeric and remaining parts.
"""
assert _normalization.best_effort_version(version) == expected


@pytest.mark.parametrize('prefix', ['', 'v', 'V'])
def test_best_effort_version_ignores_v_prefix(prefix):
"""``best_effort_version`` is reached exactly when ``safe_version`` fails,
and both accept a ``v`` prefix, so adding one cannot change the result.
"""
assert _normalization.best_effort_version(f'{prefix}1.2-foo') == (
_normalization.best_effort_version('1.2-foo')
)