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