diff --git a/tests/test_wheel.py b/tests/test_wheel.py index 0dedf6cc..9c2d54c3 100644 --- a/tests/test_wheel.py +++ b/tests/test_wheel.py @@ -13,10 +13,8 @@ # limitations under the License. import os import pathlib -import re import zipfile -import pretend import pytest from twine import exceptions @@ -40,26 +38,24 @@ def test_version_parsing(example_wheel): assert example_wheel.py_version == "py2.py3" -def test_version_parsing_missing_pyver(monkeypatch, example_wheel): - wheel.wheel_file_re = pretend.stub(match=lambda a: None) - assert example_wheel.py_version == "any" +@pytest.mark.parametrize( + "file_name,valid", + [ + ("name-1.2.3-py313-none-any.whl", True), + ("name-1.2.3-42-py313-none-any.whl", True), + ("long_name-1.2.3-py3-none-any.whl", True), + ("missing_components-1.2.3.whl", False), + ], +) +def test_parse_wheel_file_name(file_name, valid): + assert bool(wheel.wheel_file_re.match(file_name)) == valid -def test_find_metadata_files(): - names = [ - "package/lib/__init__.py", - "package/lib/version.py", - "package/METADATA.txt", - "package/METADATA.json", - "package/METADATA", - ] - expected = [ - ["package", "METADATA"], - ["package", "METADATA.json"], - ["package", "METADATA.txt"], - ] - candidates = wheel.Wheel.find_candidate_metadata_files(names) - assert expected == candidates +def test_invalid_file_name(monkeypatch): + parent = pathlib.Path(__file__).parent + file_name = str(parent / "fixtures" / "twine-1.5.0-py2.whl") + with pytest.raises(exceptions.InvalidDistribution, match="Invalid wheel filename"): + wheel.Wheel(file_name) def test_read_valid(example_wheel): @@ -71,33 +67,35 @@ def test_read_valid(example_wheel): def test_read_non_existent_wheel_file_name(): """Raise an exception when wheel file doesn't exist.""" - file_name = str(pathlib.Path("/foo/bar/baz.whl").resolve()) - with pytest.raises( - exceptions.InvalidDistribution, match=re.escape(f"No such file: {file_name}") - ): + file_name = str(pathlib.Path("/foo/bar/baz-1.2.3-py3-none-any.whl").resolve()) + with pytest.raises(exceptions.InvalidDistribution, match="No such file"): wheel.Wheel(file_name).read() def test_read_invalid_wheel_extension(): """Raise an exception when file is missing .whl extension.""" file_name = str(pathlib.Path(__file__).parent / "fixtures" / "twine-1.5.0.tar.gz") - with pytest.raises( - exceptions.InvalidDistribution, - match=re.escape(f"Not a known archive format for file: {file_name}"), - ): + with pytest.raises(exceptions.InvalidDistribution, match="Invalid wheel filename"): + wheel.Wheel(file_name).read() + + +def test_read_wheel_missing_metadata(example_wheel, monkeypatch): + """Raise an exception when a wheel file is missing METADATA.""" + + def patch(self, name): + raise KeyError + + monkeypatch.setattr(zipfile.ZipFile, "read", patch) + parent = pathlib.Path(__file__).parent + file_name = str(parent / "fixtures" / "twine-1.5.0-py2.py3-none-any.whl") + with pytest.raises(exceptions.InvalidDistribution, match="No METADATA in archive"): wheel.Wheel(file_name).read() -def test_read_wheel_empty_metadata(tmpdir): +def test_read_wheel_empty_metadata(example_wheel, monkeypatch): """Raise an exception when a wheel file is missing METADATA.""" - whl_file = tmpdir.mkdir("wheel").join("not-a-wheel.whl") - with zipfile.ZipFile(whl_file, "w") as zip_file: - zip_file.writestr("METADATA", "") - - with pytest.raises( - exceptions.InvalidDistribution, - match=re.escape( - f"No METADATA in archive or METADATA missing 'Metadata-Version': {whl_file}" - ), - ): - wheel.Wheel(whl_file).read() + monkeypatch.setattr(zipfile.ZipFile, "read", lambda self, name: b"") + parent = pathlib.Path(__file__).parent + file_name = str(parent / "fixtures" / "twine-1.5.0-py2.py3-none-any.whl") + with pytest.raises(exceptions.InvalidDistribution, match="No METADATA in archive"): + wheel.Wheel(file_name).read() diff --git a/twine/wheel.py b/twine/wheel.py index 955214ae..00bf1335 100644 --- a/twine/wheel.py +++ b/twine/wheel.py @@ -14,7 +14,7 @@ import os import re import zipfile -from typing import List +from contextlib import suppress from twine import distribution from twine import exceptions @@ -33,53 +33,29 @@ class Wheel(distribution.Distribution): def __init__(self, filename: str) -> None: + m = wheel_file_re.match(os.path.basename(filename)) + if not m: + raise exceptions.InvalidDistribution(f"Invalid wheel filename: {filename}") + self.name = m.group("name") + self.version = m.group("version") + self.pyver = m.group("pyver") + if not os.path.exists(filename): raise exceptions.InvalidDistribution(f"No such file: {filename}") self.filename = filename @property def py_version(self) -> str: - wheel_info = wheel_file_re.match(os.path.basename(self.filename)) - if wheel_info is None: - return "any" - else: - return wheel_info.group("pyver") - - @staticmethod - def find_candidate_metadata_files(names: List[str]) -> List[List[str]]: - """Filter files that may be METADATA files.""" - tuples = [x.split("/") for x in names if "METADATA" in x] - return [x[1] for x in sorted((len(x), x) for x in tuples)] + return self.pyver def read(self) -> bytes: - fqn = os.path.abspath(os.path.normpath(self.filename)) - if not os.path.exists(fqn): - raise exceptions.InvalidDistribution("No such file: %s" % fqn) - - if fqn.endswith(".whl"): - archive = zipfile.ZipFile(fqn) - names = archive.namelist() - - def read_file(name: str) -> bytes: - return archive.read(name) - - else: - raise exceptions.InvalidDistribution( - "Not a known archive format for file: %s" % fqn - ) - - searched_files: List[str] = [] - try: - for path in self.find_candidate_metadata_files(names): - candidate = "/".join(path) - data = read_file(candidate) + with zipfile.ZipFile(self.filename) as wheel: + with suppress(KeyError): + # The wheel needs to contain the METADATA file at this location. + data = wheel.read(f"{self.name}-{self.version}.dist-info/METADATA") if b"Metadata-Version" in data: return data - searched_files.append(candidate) - finally: - archive.close() - raise exceptions.InvalidDistribution( - "No METADATA in archive or METADATA missing 'Metadata-Version': " - "%s (searched %s)" % (fqn, ",".join(searched_files)) + "No METADATA in archive or " + f"METADATA missing 'Metadata-Version': {self.filename}" )