Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove reliance on LegacyVersion #2822

Merged
merged 12 commits into from
Jan 16, 2023
1 change: 1 addition & 0 deletions changelog.d/2497.breaking.rst
Original file line number Diff line number Diff line change
@@ -0,0 +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.
88 changes: 2 additions & 86 deletions pkg_resources/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@
import errno
import tempfile
import textwrap
import itertools
import inspect
import ntpath
import posixpath
Expand Down Expand Up @@ -120,16 +119,7 @@ class PEP440Warning(RuntimeWarning):
"""


def parse_version(v):
try:
return packaging.version.Version(v)
except packaging.version.InvalidVersion:
warnings.warn(
f"{v} is an invalid version and will not be supported in "
"a future release",
PkgResourcesDeprecationWarning,
)
return packaging.version.LegacyVersion(v)
parse_version = packaging.version.Version


_state_vars = {}
Expand Down Expand Up @@ -2076,42 +2066,6 @@ def find_nothing(importer, path_item, only=False):
register_finder(object, find_nothing)


def _by_version_descending(names):
"""
Given a list of filenames, return them in descending order
by version number.

>>> names = 'bar', 'foo', 'Python-2.7.10.egg', 'Python-2.7.2.egg'
>>> _by_version_descending(names)
['Python-2.7.10.egg', 'Python-2.7.2.egg', 'bar', 'foo']
>>> names = 'Setuptools-1.2.3b1.egg', 'Setuptools-1.2.3.egg'
>>> _by_version_descending(names)
['Setuptools-1.2.3.egg', 'Setuptools-1.2.3b1.egg']
>>> names = 'Setuptools-1.2.3b1.egg', 'Setuptools-1.2.3.post1.egg'
>>> _by_version_descending(names)
['Setuptools-1.2.3.post1.egg', 'Setuptools-1.2.3b1.egg']
"""

def try_parse(name):
"""
Attempt to parse as a version or return a null version.
"""
try:
return packaging.version.Version(name)
except Exception:
return packaging.version.Version('0')

def _by_version(name):
"""
Parse each component of the filename
"""
name, ext = os.path.splitext(name)
parts = itertools.chain(name.split('-'), [ext])
return [try_parse(part) for part in parts]

return sorted(names, key=_by_version, reverse=True)


def find_on_path(importer, path_item, only=False):
"""Yield distributions accessible on a sys.path directory"""
path_item = _normalize_cached(path_item)
Expand All @@ -2125,14 +2079,8 @@ def find_on_path(importer, path_item, only=False):

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

# for performance, before sorting by version,
# screen entries for only those that will yield
# distributions
filtered = (entry for entry in entries if dist_factory(path_item, entry, only))

# scan for .egg and .egg-info in directory
path_item_entries = _by_version_descending(filtered)
for entry in path_item_entries:
for entry in sorted(entries):
fullpath = os.path.join(path_item, entry)
factory = dist_factory(path_item, entry, only)
for dist in factory(fullpath):
Expand Down Expand Up @@ -2724,38 +2672,6 @@ def parsed_version(self):

return self._parsed_version

def _warn_legacy_version(self):
LV = packaging.version.LegacyVersion
is_legacy = isinstance(self._parsed_version, LV)
if not is_legacy:
return

# While an empty version is technically a legacy version and
# is not a valid PEP 440 version, it's also unlikely to
# actually come from someone and instead it is more likely that
# it comes from setuptools attempting to parse a filename and
# including it in the list. So for that we'll gate this warning
# on if the version is anything at all or not.
if not self.version:
return

tmpl = (
textwrap.dedent(
"""
'{project_name} ({version})' is being parsed as a legacy,
non PEP 440,
version. You may find odd behavior and sort order.
In particular it will be sorted as less than 0.0. It
is recommended to migrate to PEP 440 compatible
versions.
"""
)
.strip()
.replace('\n', ' ')
)

warnings.warn(tmpl.format(**vars(self)), PEP440Warning)

@property
def version(self):
try:
Expand Down
2 changes: 1 addition & 1 deletion setuptools/command/egg_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ def tags(self) -> str:
if self.tag_build:
version += self.tag_build
if self.tag_date:
version += time.strftime("-%Y%m%d")
version += time.strftime("%Y%m%d")
return version
vtags = property(tags)

Expand Down
40 changes: 18 additions & 22 deletions setuptools/package_index.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,39 +169,35 @@ def distros_for_filename(filename, metadata=None):
def interpret_distro_name(
location, basename, metadata, py_version=None, precedence=SOURCE_DIST, platform=None
):
"""Generate alternative interpretations of a source distro name
"""Generate the interpretation of a source distro name

Note: if `location` is a filesystem filename, you should call
``pkg_resources.normalize_path()`` on it before passing it to this
routine!
"""
# Generate alternative interpretations of a source distro name
# Because some packages are ambiguous as to name/versions split
# e.g. "adns-python-1.1.0", "egenix-mx-commercial", etc.
# So, we generate each possible interpretation (e.g. "adns, python-1.1.0"
# "adns-python, 1.1.0", and "adns-python-1.1.0, no version"). In practice,
# the spurious interpretations should be ignored, because in the event
# there's also an "adns" package, the spurious "python-1.1.0" version will
# compare lower than any numeric version number, and is therefore unlikely
# to match a request for it. It's still a potential problem, though, and
# in the long run PyPI and the distutils should go for "safe" names and
# versions in distribution archive names (sdist and bdist).

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

for p in range(1, len(parts) + 1):
yield Distribution(
location,
metadata,
'-'.join(parts[:p]),
'-'.join(parts[p:]),
py_version=py_version,
precedence=precedence,
platform=platform,
)
# find the pivot (p) that splits the name from the version.
# infer the version as the first item that has a digit.
for p in range(len(parts)):
if parts[p][:1].isdigit():
break
else:
p = len(parts)

yield Distribution(
location,
metadata,
'-'.join(parts[:p]),
'-'.join(parts[p:]),
py_version=py_version,
precedence=precedence,
platform=platform
)


def unique_values(func):
Expand Down
50 changes: 32 additions & 18 deletions setuptools/tests/test_dist_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,18 @@

class TestDistInfo:

metadata_base = DALS("""
metadata_base = DALS(
"""
Metadata-Version: 1.2
Requires-Dist: splort (==4)
Provides-Extra: baz
Requires-Dist: quux (>=1.1); extra == 'baz'
""")
"""
)

@classmethod
def build_metadata(cls, **kwargs):
lines = (
'{key}: {value}\n'.format(**locals())
for key, value in kwargs.items()
)
lines = ('{key}: {value}\n'.format(**locals()) for key, value in kwargs.items())
return cls.metadata_base + ''.join(lines)

@pytest.fixture
Expand Down Expand Up @@ -59,8 +58,7 @@ def metadata(self, tmpdir):

def test_distinfo(self, metadata):
dists = dict(
(d.project_name, d)
for d in pkg_resources.find_distributions(metadata)
(d.project_name, d) for d in pkg_resources.find_distributions(metadata)
)

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

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

def test_tag_arguments(self, tmp_path):
config = """
Expand All @@ -116,7 +117,7 @@ def test_tag_arguments(self, tmp_path):
def test_output_dir(self, tmp_path, keep_egg_info):
config = "[metadata]\nname=proj\nversion=42\n"
(tmp_path / "setup.cfg").write_text(config, encoding="utf-8")
out = (tmp_path / "__out")
out = tmp_path / "__out"
out.mkdir()
opts = ["--keep-egg-info"] if keep_egg_info else []
run_command("dist_info", "--output-dir", out, *opts, cwd=tmp_path)
Expand All @@ -133,7 +134,9 @@ class TestWheelCompatibility:
"""Make sure the .dist-info directory produced with the ``dist_info`` command
is the same as the one produced by ``bdist_wheel``.
"""
SETUPCFG = DALS("""

SETUPCFG = DALS(
"""
[metadata]
name = {name}
version = {version}
Expand All @@ -149,7 +152,8 @@ class TestWheelCompatibility:
executable-name = my_package.module:function
discover =
myproj = my_package.other_module:function
""")
"""
)

EGG_INFO_OPTS = [
# Related: #3088 #2872
Expand Down Expand Up @@ -189,7 +193,17 @@ def test_dist_info_is_the_same_as_in_wheel(
assert read(dist_info / file) == read(wheel_dist_info / file)


def run_command(*cmd, **kwargs):
opts = {"stderr": subprocess.STDOUT, "text": True, **kwargs}
def run_command_inner(*cmd, **kwargs):
opts = {
"stderr": subprocess.STDOUT,
"stdout": subprocess.PIPE,
"text": True,
'check': True,
**kwargs,
}
cmd = [sys.executable, "-c", "__import__('setuptools').setup()", *map(str, cmd)]
return subprocess.check_output(cmd, **opts)
return subprocess.run(cmd, **opts)


def run_command(*args, **kwargs):
return run_command_inner(*args, **kwargs).stdout
2 changes: 1 addition & 1 deletion setuptools/tests/test_packageindex.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ def test_egg_fragment(self):
for v, vc in versions:
dists = list(
setuptools.package_index.distros_for_url(
'http://example.com/example.zip#egg=example-' + v
'http://example.com/example-foo.zip#egg=example-foo-' + v
)
)
assert dists[0].version == ''
Expand Down