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

Catch + report BadZipFile errors in get_wheel_distribution #10535

Merged
merged 6 commits into from
Nov 12, 2021
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
1 change: 1 addition & 0 deletions news/10535.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Present a better error message when an invalid wheel file is encountered, providing more context where the invalid wheel file is.
11 changes: 11 additions & 0 deletions src/pip/_internal/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,17 @@ class UnsupportedWheel(InstallationError):
"""Unsupported wheel."""


class InvalidWheel(InstallationError):
"""Invalid (e.g. corrupt) wheel."""

def __init__(self, location: str, name: str):
self.location = location
self.name = name

def __str__(self) -> str:
return f"Wheel '{self.name}' located at {self.location} is invalid."


class MetadataInconsistent(InstallationError):
"""Built metadata contains inconsistent information.

Expand Down
14 changes: 12 additions & 2 deletions src/pip/_internal/metadata/pkg_resources.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import email.message
import logging
from typing import Collection, Iterable, Iterator, List, NamedTuple, Optional
from zipfile import BadZipFile

from pip._vendor import pkg_resources
from pip._vendor.packaging.requirements import Requirement
from pip._vendor.packaging.utils import NormalizedName, canonicalize_name
from pip._vendor.packaging.version import parse as parse_version

from pip._internal.exceptions import InvalidWheel
from pip._internal.utils import misc # TODO: Move definition here.
from pip._internal.utils.packaging import get_installer, get_metadata
from pip._internal.utils.wheel import pkg_resources_distribution_for_wheel
Expand Down Expand Up @@ -34,8 +36,16 @@ def __init__(self, dist: pkg_resources.Distribution) -> None:

@classmethod
def from_wheel(cls, wheel: Wheel, name: str) -> "Distribution":
with wheel.as_zipfile() as zf:
dist = pkg_resources_distribution_for_wheel(zf, name, wheel.location)
"""Load the distribution from a given wheel.

:raises InvalidWheel: Whenever loading of the wheel causes a
:py:exc:`zipfile.BadZipFile` exception to be thrown.
"""
try:
with wheel.as_zipfile() as zf:
dist = pkg_resources_distribution_for_wheel(zf, name, wheel.location)
except BadZipFile as e:
raise InvalidWheel(wheel.location, name) from e
return cls(dist)

@property
Expand Down
1 change: 1 addition & 0 deletions tests/data/packages/corruptwheel-1.0-py2.py3-none-any.whl
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
This is a corrupt wheel which _clearly_ is not a zip file.
11 changes: 9 additions & 2 deletions tests/functional/test_install_wheel.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,20 @@ def test_install_from_future_wheel_version(script, tmpdir):
result.assert_installed("futurewheel", without_egg_link=True, editable=False)


def test_install_from_broken_wheel(script, data):
@pytest.mark.parametrize(
"wheel_name",
[
"brokenwheel-1.0-py2.py3-none-any.whl",
"corruptwheel-1.0-py2.py3-none-any.whl",
],
)
def test_install_from_broken_wheel(script, data, wheel_name):
"""
Test that installing a broken wheel fails properly
"""
from tests.lib import TestFailure

package = data.packages.joinpath("brokenwheel-1.0-py2.py3-none-any.whl")
package = data.packages.joinpath(wheel_name)
result = script.pip("install", package, "--no-index", expect_error=True)
with pytest.raises(TestFailure):
result.assert_installed("futurewheel", without_egg_link=True, editable=False)
Expand Down
4 changes: 2 additions & 2 deletions tests/unit/test_network_lazy_wheel.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
from typing import Iterator
from zipfile import BadZipfile

from pip._vendor.packaging.version import Version
from pytest import fixture, mark, raises

from pip._internal.exceptions import InvalidWheel
from pip._internal.network.lazy_wheel import (
HTTPRangeRequestUnsupported,
dist_from_wheel_url,
Expand Down Expand Up @@ -62,5 +62,5 @@ def test_dist_from_wheel_url_no_range(
@mark.network
def test_dist_from_wheel_url_not_zip(session: PipSession) -> None:
"""Test handling with the given URL does not point to a ZIP."""
with raises(BadZipfile):
with raises(InvalidWheel):
dist_from_wheel_url("python", "https://www.python.org/", session)
9 changes: 9 additions & 0 deletions tests/unit/test_wheel.py
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,15 @@ def test_wheel_root_is_purelib(text: str, expected: bool) -> None:
assert wheel.wheel_root_is_purelib(message_from_string(text)) == expected


def test_dist_from_broken_wheel_fails(data: TestData) -> None:
from pip._internal.exceptions import InvalidWheel
from pip._internal.metadata import FilesystemWheel, get_wheel_distribution

package = data.packages.joinpath("corruptwheel-1.0-py2.py3-none-any.whl")
with pytest.raises(InvalidWheel):
get_wheel_distribution(FilesystemWheel(package), "brokenwheel")


class TestWheelFile:
def test_unpack_wheel_no_flatten(self, tmpdir: Path) -> None:
filepath = os.path.join(DATA_DIR, "packages", "meta-1.0-py2.py3-none-any.whl")
Expand Down