Skip to content

Commit 217d926

Browse files
authored
Merge pull request #2822 from pypa/debt/remove-legacy-version
Remove reliance on LegacyVersion
2 parents ef49c0a + 0a6cd4f commit 217d926

File tree

6 files changed

+55
-128
lines changed

6 files changed

+55
-128
lines changed

changelog.d/2497.breaking.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Support for PEP 440 non-conforming versions has been removed. Environments containing packages with non-conforming versions may fail or the packages may not be recognized.

pkg_resources/__init__.py

Lines changed: 2 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@
3434
import errno
3535
import tempfile
3636
import textwrap
37-
import itertools
3837
import inspect
3938
import ntpath
4039
import posixpath
@@ -120,16 +119,7 @@ class PEP440Warning(RuntimeWarning):
120119
"""
121120

122121

123-
def parse_version(v):
124-
try:
125-
return packaging.version.Version(v)
126-
except packaging.version.InvalidVersion:
127-
warnings.warn(
128-
f"{v} is an invalid version and will not be supported in "
129-
"a future release",
130-
PkgResourcesDeprecationWarning,
131-
)
132-
return packaging.version.LegacyVersion(v)
122+
parse_version = packaging.version.Version
133123

134124

135125
_state_vars = {}
@@ -2083,42 +2073,6 @@ def find_nothing(importer, path_item, only=False):
20832073
register_finder(object, find_nothing)
20842074

20852075

2086-
def _by_version_descending(names):
2087-
"""
2088-
Given a list of filenames, return them in descending order
2089-
by version number.
2090-
2091-
>>> names = 'bar', 'foo', 'Python-2.7.10.egg', 'Python-2.7.2.egg'
2092-
>>> _by_version_descending(names)
2093-
['Python-2.7.10.egg', 'Python-2.7.2.egg', 'bar', 'foo']
2094-
>>> names = 'Setuptools-1.2.3b1.egg', 'Setuptools-1.2.3.egg'
2095-
>>> _by_version_descending(names)
2096-
['Setuptools-1.2.3.egg', 'Setuptools-1.2.3b1.egg']
2097-
>>> names = 'Setuptools-1.2.3b1.egg', 'Setuptools-1.2.3.post1.egg'
2098-
>>> _by_version_descending(names)
2099-
['Setuptools-1.2.3.post1.egg', 'Setuptools-1.2.3b1.egg']
2100-
"""
2101-
2102-
def try_parse(name):
2103-
"""
2104-
Attempt to parse as a version or return a null version.
2105-
"""
2106-
try:
2107-
return packaging.version.Version(name)
2108-
except Exception:
2109-
return packaging.version.Version('0')
2110-
2111-
def _by_version(name):
2112-
"""
2113-
Parse each component of the filename
2114-
"""
2115-
name, ext = os.path.splitext(name)
2116-
parts = itertools.chain(name.split('-'), [ext])
2117-
return [try_parse(part) for part in parts]
2118-
2119-
return sorted(names, key=_by_version, reverse=True)
2120-
2121-
21222076
def find_on_path(importer, path_item, only=False):
21232077
"""Yield distributions accessible on a sys.path directory"""
21242078
path_item = _normalize_cached(path_item)
@@ -2132,14 +2086,8 @@ def find_on_path(importer, path_item, only=False):
21322086

21332087
entries = (os.path.join(path_item, child) for child in safe_listdir(path_item))
21342088

2135-
# for performance, before sorting by version,
2136-
# screen entries for only those that will yield
2137-
# distributions
2138-
filtered = (entry for entry in entries if dist_factory(path_item, entry, only))
2139-
21402089
# scan for .egg and .egg-info in directory
2141-
path_item_entries = _by_version_descending(filtered)
2142-
for entry in path_item_entries:
2090+
for entry in sorted(entries):
21432091
fullpath = os.path.join(path_item, entry)
21442092
factory = dist_factory(path_item, entry, only)
21452093
for dist in factory(fullpath):
@@ -2731,38 +2679,6 @@ def parsed_version(self):
27312679

27322680
return self._parsed_version
27332681

2734-
def _warn_legacy_version(self):
2735-
LV = packaging.version.LegacyVersion
2736-
is_legacy = isinstance(self._parsed_version, LV)
2737-
if not is_legacy:
2738-
return
2739-
2740-
# While an empty version is technically a legacy version and
2741-
# is not a valid PEP 440 version, it's also unlikely to
2742-
# actually come from someone and instead it is more likely that
2743-
# it comes from setuptools attempting to parse a filename and
2744-
# including it in the list. So for that we'll gate this warning
2745-
# on if the version is anything at all or not.
2746-
if not self.version:
2747-
return
2748-
2749-
tmpl = (
2750-
textwrap.dedent(
2751-
"""
2752-
'{project_name} ({version})' is being parsed as a legacy,
2753-
non PEP 440,
2754-
version. You may find odd behavior and sort order.
2755-
In particular it will be sorted as less than 0.0. It
2756-
is recommended to migrate to PEP 440 compatible
2757-
versions.
2758-
"""
2759-
)
2760-
.strip()
2761-
.replace('\n', ' ')
2762-
)
2763-
2764-
warnings.warn(tmpl.format(**vars(self)), PEP440Warning)
2765-
27662682
@property
27672683
def version(self):
27682684
try:

setuptools/command/egg_info.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ def tags(self) -> str:
155155
if self.tag_build:
156156
version += self.tag_build
157157
if self.tag_date:
158-
version += time.strftime("-%Y%m%d")
158+
version += time.strftime("%Y%m%d")
159159
return version
160160
vtags = property(tags)
161161

setuptools/package_index.py

Lines changed: 18 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -169,39 +169,35 @@ def distros_for_filename(filename, metadata=None):
169169
def interpret_distro_name(
170170
location, basename, metadata, py_version=None, precedence=SOURCE_DIST, platform=None
171171
):
172-
"""Generate alternative interpretations of a source distro name
172+
"""Generate the interpretation of a source distro name
173173
174174
Note: if `location` is a filesystem filename, you should call
175175
``pkg_resources.normalize_path()`` on it before passing it to this
176176
routine!
177177
"""
178-
# Generate alternative interpretations of a source distro name
179-
# Because some packages are ambiguous as to name/versions split
180-
# e.g. "adns-python-1.1.0", "egenix-mx-commercial", etc.
181-
# So, we generate each possible interpretation (e.g. "adns, python-1.1.0"
182-
# "adns-python, 1.1.0", and "adns-python-1.1.0, no version"). In practice,
183-
# the spurious interpretations should be ignored, because in the event
184-
# there's also an "adns" package, the spurious "python-1.1.0" version will
185-
# compare lower than any numeric version number, and is therefore unlikely
186-
# to match a request for it. It's still a potential problem, though, and
187-
# in the long run PyPI and the distutils should go for "safe" names and
188-
# versions in distribution archive names (sdist and bdist).
189178

190179
parts = basename.split('-')
191180
if not py_version and any(re.match(r'py\d\.\d$', p) for p in parts[2:]):
192181
# it is a bdist_dumb, not an sdist -- bail out
193182
return
194183

195-
for p in range(1, len(parts) + 1):
196-
yield Distribution(
197-
location,
198-
metadata,
199-
'-'.join(parts[:p]),
200-
'-'.join(parts[p:]),
201-
py_version=py_version,
202-
precedence=precedence,
203-
platform=platform,
204-
)
184+
# find the pivot (p) that splits the name from the version.
185+
# infer the version as the first item that has a digit.
186+
for p in range(len(parts)):
187+
if parts[p][:1].isdigit():
188+
break
189+
else:
190+
p = len(parts)
191+
192+
yield Distribution(
193+
location,
194+
metadata,
195+
'-'.join(parts[:p]),
196+
'-'.join(parts[p:]),
197+
py_version=py_version,
198+
precedence=precedence,
199+
platform=platform
200+
)
205201

206202

207203
def unique_values(func):

setuptools/tests/test_dist_info.py

Lines changed: 32 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -19,19 +19,18 @@
1919

2020
class TestDistInfo:
2121

22-
metadata_base = DALS("""
22+
metadata_base = DALS(
23+
"""
2324
Metadata-Version: 1.2
2425
Requires-Dist: splort (==4)
2526
Provides-Extra: baz
2627
Requires-Dist: quux (>=1.1); extra == 'baz'
27-
""")
28+
"""
29+
)
2830

2931
@classmethod
3032
def build_metadata(cls, **kwargs):
31-
lines = (
32-
'{key}: {value}\n'.format(**locals())
33-
for key, value in kwargs.items()
34-
)
33+
lines = ('{key}: {value}\n'.format(**locals()) for key, value in kwargs.items())
3534
return cls.metadata_base + ''.join(lines)
3635

3736
@pytest.fixture
@@ -59,8 +58,7 @@ def metadata(self, tmpdir):
5958

6059
def test_distinfo(self, metadata):
6160
dists = dict(
62-
(d.project_name, d)
63-
for d in pkg_resources.find_distributions(metadata)
61+
(d.project_name, d) for d in pkg_resources.find_distributions(metadata)
6462
)
6563

6664
assert len(dists) == 2, dists
@@ -84,13 +82,16 @@ def test_conditional_dependencies(self, metadata):
8482
assert d.extras == ['baz']
8583

8684
def test_invalid_version(self, tmp_path):
85+
"""
86+
Supplying an invalid version crashes dist_info.
87+
"""
8788
config = "[metadata]\nname=proj\nversion=42\n[egg_info]\ntag_build=invalid!!!\n"
8889
(tmp_path / "setup.cfg").write_text(config, encoding="utf-8")
8990
msg = re.compile("invalid version", re.M | re.I)
90-
output = run_command("dist_info", cwd=tmp_path)
91-
assert msg.search(output)
92-
dist_info = next(tmp_path.glob("*.dist-info"))
93-
assert dist_info.name.startswith("proj-42")
91+
proc = run_command_inner("dist_info", cwd=tmp_path, check=False)
92+
assert proc.returncode
93+
assert msg.search(proc.stdout)
94+
assert not list(tmp_path.glob("*.dist-info"))
9495

9596
def test_tag_arguments(self, tmp_path):
9697
config = """
@@ -116,7 +117,7 @@ def test_tag_arguments(self, tmp_path):
116117
def test_output_dir(self, tmp_path, keep_egg_info):
117118
config = "[metadata]\nname=proj\nversion=42\n"
118119
(tmp_path / "setup.cfg").write_text(config, encoding="utf-8")
119-
out = (tmp_path / "__out")
120+
out = tmp_path / "__out"
120121
out.mkdir()
121122
opts = ["--keep-egg-info"] if keep_egg_info else []
122123
run_command("dist_info", "--output-dir", out, *opts, cwd=tmp_path)
@@ -133,7 +134,9 @@ class TestWheelCompatibility:
133134
"""Make sure the .dist-info directory produced with the ``dist_info`` command
134135
is the same as the one produced by ``bdist_wheel``.
135136
"""
136-
SETUPCFG = DALS("""
137+
138+
SETUPCFG = DALS(
139+
"""
137140
[metadata]
138141
name = {name}
139142
version = {version}
@@ -149,7 +152,8 @@ class TestWheelCompatibility:
149152
executable-name = my_package.module:function
150153
discover =
151154
myproj = my_package.other_module:function
152-
""")
155+
"""
156+
)
153157

154158
EGG_INFO_OPTS = [
155159
# Related: #3088 #2872
@@ -189,7 +193,17 @@ def test_dist_info_is_the_same_as_in_wheel(
189193
assert read(dist_info / file) == read(wheel_dist_info / file)
190194

191195

192-
def run_command(*cmd, **kwargs):
193-
opts = {"stderr": subprocess.STDOUT, "text": True, **kwargs}
196+
def run_command_inner(*cmd, **kwargs):
197+
opts = {
198+
"stderr": subprocess.STDOUT,
199+
"stdout": subprocess.PIPE,
200+
"text": True,
201+
'check': True,
202+
**kwargs,
203+
}
194204
cmd = [sys.executable, "-c", "__import__('setuptools').setup()", *map(str, cmd)]
195-
return subprocess.check_output(cmd, **opts)
205+
return subprocess.run(cmd, **opts)
206+
207+
208+
def run_command(*args, **kwargs):
209+
return run_command_inner(*args, **kwargs).stdout

setuptools/tests/test_packageindex.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ def test_egg_fragment(self):
180180
for v, vc in versions:
181181
dists = list(
182182
setuptools.package_index.distros_for_url(
183-
'http://example.com/example.zip#egg=example-' + v
183+
'http://example.com/example-foo.zip#egg=example-foo-' + v
184184
)
185185
)
186186
assert dists[0].version == ''

0 commit comments

Comments
 (0)