forked from openkylin/python-installer
Search wheels for .dist-info directories
Some wheels don't use normalized names for their .dist-info directories, so search the wheel for them. Fixes: #134 Bug-Upstream: https://github.com/pypa/installer/issues/134 Bug-Debian: https://bugs.debian.org/1008606 Forwarded: https://github.com/pypa/installer/pull/137
This commit is contained in:
parent
ec44276d8d
commit
09c1975eda
|
@ -0,0 +1,186 @@
|
||||||
|
From: Stefano Rivera <stefanor@debian.org>
|
||||||
|
Date: Sun, 16 Oct 2022 13:01:21 +0200
|
||||||
|
Subject: Search wheels for .dist-info directories
|
||||||
|
|
||||||
|
Some wheels don't use normalized names for their .dist-info directories,
|
||||||
|
so search the wheel for them.
|
||||||
|
|
||||||
|
Fixes: #134
|
||||||
|
Bug-Upstream: https://github.com/pypa/installer/issues/134
|
||||||
|
Bug-Debian: https://bugs.debian.org/1008606
|
||||||
|
Forwarded: https://github.com/pypa/installer/pull/137
|
||||||
|
---
|
||||||
|
docs/conf.py | 2 +-
|
||||||
|
src/installer/sources.py | 29 ++++++++++++++++++++++++++++-
|
||||||
|
src/installer/utils.py | 8 ++++++++
|
||||||
|
tests/test_scripts.py | 4 ++++
|
||||||
|
tests/test_sources.py | 17 +++++++++++++++++
|
||||||
|
tests/test_utils.py | 22 ++++++++++++++++++++++
|
||||||
|
6 files changed, 80 insertions(+), 2 deletions(-)
|
||||||
|
|
||||||
|
diff --git a/docs/conf.py b/docs/conf.py
|
||||||
|
index 22bbe11..3ff6d4d 100644
|
||||||
|
--- a/docs/conf.py
|
||||||
|
+++ b/docs/conf.py
|
||||||
|
@@ -24,7 +24,7 @@ extensions = [
|
||||||
|
# -- Options for HTML output -----------------------------------------------------------
|
||||||
|
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
|
||||||
|
|
||||||
|
-html_theme = "furo"
|
||||||
|
+#html_theme = "furo"
|
||||||
|
html_title = project
|
||||||
|
|
||||||
|
# -- Options for Autodoc --------------------------------------------------------------
|
||||||
|
diff --git a/src/installer/sources.py b/src/installer/sources.py
|
||||||
|
index fa0bc34..e3a7c45 100644
|
||||||
|
--- a/src/installer/sources.py
|
||||||
|
+++ b/src/installer/sources.py
|
||||||
|
@@ -8,7 +8,7 @@ from contextlib import contextmanager
|
||||||
|
from typing import BinaryIO, Iterator, List, Tuple, cast
|
||||||
|
|
||||||
|
from installer.records import parse_record_file
|
||||||
|
-from installer.utils import parse_wheel_filename
|
||||||
|
+from installer.utils import canonicalize_name, parse_wheel_filename
|
||||||
|
|
||||||
|
WheelContentElement = Tuple[Tuple[str, str, str], BinaryIO, bool]
|
||||||
|
|
||||||
|
@@ -122,6 +122,33 @@ class WheelFile(WheelSource):
|
||||||
|
with zipfile.ZipFile(path) as f:
|
||||||
|
yield cls(f)
|
||||||
|
|
||||||
|
+ @property
|
||||||
|
+ def dist_info_dir(self) -> str:
|
||||||
|
+ """Name of the dist-info directory."""
|
||||||
|
+ if not hasattr(self, "_dist_info_dir"):
|
||||||
|
+ top_level_directories = {
|
||||||
|
+ path.split("/", 1)[0] for path in self._zipfile.namelist()
|
||||||
|
+ }
|
||||||
|
+ dist_infos = [
|
||||||
|
+ name for name in top_level_directories if name.endswith(".dist-info")
|
||||||
|
+ ]
|
||||||
|
+
|
||||||
|
+ assert (
|
||||||
|
+ len(dist_infos) == 1
|
||||||
|
+ ), "Wheel doesn't contain exactly one .dist-info directory"
|
||||||
|
+ dist_info_dir = dist_infos[0]
|
||||||
|
+
|
||||||
|
+ # NAME-VER.dist-info
|
||||||
|
+ di_dname = dist_info_dir.rsplit("-", 2)[0]
|
||||||
|
+ norm_di_dname = canonicalize_name(di_dname)
|
||||||
|
+ norm_file_dname = canonicalize_name(self.distribution)
|
||||||
|
+ assert (
|
||||||
|
+ norm_di_dname == norm_file_dname
|
||||||
|
+ ), "Wheel .dist-info directory doesn't match wheel filename"
|
||||||
|
+
|
||||||
|
+ self._dist_info_dir = dist_info_dir
|
||||||
|
+ return self._dist_info_dir
|
||||||
|
+
|
||||||
|
@property
|
||||||
|
def dist_info_filenames(self) -> List[str]:
|
||||||
|
"""Get names of all files in the dist-info directory."""
|
||||||
|
diff --git a/src/installer/utils.py b/src/installer/utils.py
|
||||||
|
index 7b1404d..cef2bd8 100644
|
||||||
|
--- a/src/installer/utils.py
|
||||||
|
+++ b/src/installer/utils.py
|
||||||
|
@@ -94,6 +94,14 @@ def parse_metadata_file(contents: str) -> Message:
|
||||||
|
return feed_parser.close()
|
||||||
|
|
||||||
|
|
||||||
|
+def canonicalize_name(name: str) -> str:
|
||||||
|
+ """Canonicalize a project name according to PEP-503.
|
||||||
|
+
|
||||||
|
+ :param name: The project name to canonicalize
|
||||||
|
+ """
|
||||||
|
+ return re.sub(r"[-_.]+", "-", name).lower()
|
||||||
|
+
|
||||||
|
+
|
||||||
|
def parse_wheel_filename(filename: str) -> WheelFilename:
|
||||||
|
"""Parse a wheel filename, into it's various components.
|
||||||
|
|
||||||
|
diff --git a/tests/test_scripts.py b/tests/test_scripts.py
|
||||||
|
index 2da6577..6442e19 100644
|
||||||
|
--- a/tests/test_scripts.py
|
||||||
|
+++ b/tests/test_scripts.py
|
||||||
|
@@ -40,6 +40,8 @@ def _read_launcher_data(section, kind):
|
||||||
|
return f.read()
|
||||||
|
|
||||||
|
|
||||||
|
+@pytest.mark.skip(
|
||||||
|
+ "Skipped on Debian, we don't include the simple_launcher .exes")
|
||||||
|
@pytest.mark.parametrize("section", ["console", "gui"])
|
||||||
|
@pytest.mark.parametrize("kind", ["win-ia32", "win-amd64", "win-arm"])
|
||||||
|
def test_script_generate_launcher(section, kind):
|
||||||
|
@@ -60,6 +62,8 @@ def test_script_generate_launcher(section, kind):
|
||||||
|
assert b"baz.qux()" in code
|
||||||
|
|
||||||
|
|
||||||
|
+@pytest.mark.skip(
|
||||||
|
+ "Skipped on Debian, we don't include the simple_launcher .exes")
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"section, kind",
|
||||||
|
[("nonexist", "win-ia32"), ("console", "nonexist"), ("nonexist", "nonexist")],
|
||||||
|
diff --git a/tests/test_sources.py b/tests/test_sources.py
|
||||||
|
index a79cc24..8d71496 100644
|
||||||
|
--- a/tests/test_sources.py
|
||||||
|
+++ b/tests/test_sources.py
|
||||||
|
@@ -92,3 +92,20 @@ class TestWheelFile:
|
||||||
|
|
||||||
|
assert sorted(got_records) == sorted(expected_records)
|
||||||
|
assert got_files == files
|
||||||
|
+
|
||||||
|
+ def test_finds_dist_info(self, fancy_wheel):
|
||||||
|
+ denorm = fancy_wheel.rename(fancy_wheel.parent / "Fancy-1.0.0-py3-none-any.whl")
|
||||||
|
+ # Python 3.7: rename doesn't return the new name:
|
||||||
|
+ denorm = fancy_wheel.parent / "Fancy-1.0.0-py3-none-any.whl"
|
||||||
|
+ with WheelFile.open(denorm) as source:
|
||||||
|
+ assert source.dist_info_filenames
|
||||||
|
+
|
||||||
|
+ def test_requires_dist_info_name_match(self, fancy_wheel):
|
||||||
|
+ misnamed = fancy_wheel.rename(
|
||||||
|
+ fancy_wheel.parent / "misnamed-1.0.0-py3-none-any.whl"
|
||||||
|
+ )
|
||||||
|
+ # Python 3.7: rename doesn't return the new name:
|
||||||
|
+ misnamed = fancy_wheel.parent / "misnamed-1.0.0-py3-none-any.whl"
|
||||||
|
+ with pytest.raises(AssertionError):
|
||||||
|
+ with WheelFile.open(misnamed) as source:
|
||||||
|
+ source.dist_info_filenames
|
||||||
|
diff --git a/tests/test_utils.py b/tests/test_utils.py
|
||||||
|
index bfcc089..e4bfb6a 100644
|
||||||
|
--- a/tests/test_utils.py
|
||||||
|
+++ b/tests/test_utils.py
|
||||||
|
@@ -16,6 +16,7 @@ from installer.utils import (
|
||||||
|
construct_record_file,
|
||||||
|
copyfileobj_with_hashing,
|
||||||
|
fix_shebang,
|
||||||
|
+ canonicalize_name,
|
||||||
|
parse_entrypoints,
|
||||||
|
parse_metadata_file,
|
||||||
|
parse_wheel_filename,
|
||||||
|
@@ -41,6 +42,27 @@ class TestParseMetadata:
|
||||||
|
assert result.get_all("MULTI-USE-FIELD") == ["1", "2", "3"]
|
||||||
|
|
||||||
|
|
||||||
|
+class TestCanonicalizeDistributionName:
|
||||||
|
+ @pytest.mark.parametrize(
|
||||||
|
+ "string, expected",
|
||||||
|
+ [
|
||||||
|
+ # Noop
|
||||||
|
+ (
|
||||||
|
+ "package-1",
|
||||||
|
+ "package-1",
|
||||||
|
+ ),
|
||||||
|
+ # PEP 508 canonicalization
|
||||||
|
+ (
|
||||||
|
+ "ABC..12",
|
||||||
|
+ "abc-12",
|
||||||
|
+ ),
|
||||||
|
+ ],
|
||||||
|
+ )
|
||||||
|
+ def test_valid_cases(self, string, expected):
|
||||||
|
+ got = canonicalize_name(string)
|
||||||
|
+ assert expected == got, (expected, got)
|
||||||
|
+
|
||||||
|
+
|
||||||
|
class TestParseWheelFilename:
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"string, expected",
|
|
@ -0,0 +1 @@
|
||||||
|
0001-Search-wheels-for-.dist-info-directories.patch
|
Loading…
Reference in New Issue