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

Search wheels for .dist-info directories #137

Merged
merged 5 commits into from
Dec 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 28 additions & 1 deletion src/installer/sources.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from typing import BinaryIO, ClassVar, Iterator, List, Tuple, Type, cast

from installer.records import RecordEntry, 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]

Expand Down Expand Up @@ -145,6 +145,33 @@ def open(cls, path: "os.PathLike[str]") -> Iterator["WheelFile"]:
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"
pradyunsg marked this conversation as resolved.
Show resolved Hide resolved

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."""
Expand Down
8 changes: 8 additions & 0 deletions src/installer/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
17 changes: 17 additions & 0 deletions tests/test_sources.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,23 @@ def test_provides_correct_contents(self, fancy_wheel):
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


def replace_file_in_zip(path: str, filename: str, content: "bytes | None") -> None:
"""Helper function for replacing a file in the zip.
Expand Down
22 changes: 22 additions & 0 deletions tests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from installer.records import RecordEntry
from installer.utils import (
WheelFilename,
canonicalize_name,
construct_record_file,
copyfileobj_with_hashing,
fix_shebang,
Expand Down Expand Up @@ -41,6 +42,27 @@ def test_basics(self):
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",
Expand Down