Repository: pypa/setuptools

Issue:
Remove support for LegacyVersion
In #2466 and pypa/packaging#368, it's become apparent that Setuptools should get out of the business of supporting the `LegacyVersion`.
Remove support for LegacyVersion
In #2466 and pypa/packaging#368, it's become apparent that Setuptools should get out of the business of supporting the `LegacyVersion`.


Discussion:

Resolving patch:
diff --git a/pkg_resources/__init__.py b/pkg_resources/__init__.py
--- a/pkg_resources/__init__.py
+++ b/pkg_resources/__init__.py
@@ -34,7 +34,6 @@
 import errno
 import tempfile
 import textwrap
-import itertools
 import inspect
 import ntpath
 import posixpath
@@ -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 = {}
@@ -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)
@@ -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):
@@ -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:
diff --git a/setuptools/command/egg_info.py b/setuptools/command/egg_info.py
--- a/setuptools/command/egg_info.py
+++ b/setuptools/command/egg_info.py
@@ -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)
 
diff --git a/setuptools/package_index.py b/setuptools/package_index.py
--- a/setuptools/package_index.py
+++ b/setuptools/package_index.py
@@ -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):


Test patch:
diff --git a/setuptools/tests/test_dist_info.py b/setuptools/tests/test_dist_info.py
--- a/setuptools/tests/test_dist_info.py
+++ b/setuptools/tests/test_dist_info.py
@@ -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
@@ -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
@@ -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 = """
@@ -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)
@@ -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}
@@ -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
@@ -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
diff --git a/setuptools/tests/test_packageindex.py b/setuptools/tests/test_packageindex.py
--- a/setuptools/tests/test_packageindex.py
+++ b/setuptools/tests/test_packageindex.py
@@ -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 == ''