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:
Stefano Rivera 2022-10-16 13:01:21 +02:00 committed by cckylin-cibot
parent ec44276d8d
commit 09c1975eda
2 changed files with 187 additions and 0 deletions

View File

@ -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",

1
debian/patches/series vendored Normal file
View File

@ -0,0 +1 @@
0001-Search-wheels-for-.dist-info-directories.patch