|
8 | 8 | from zipfile import ZipFile
|
9 | 9 |
|
10 | 10 | from pip._vendor.packaging.utils import canonicalize_name
|
| 11 | +from pip._vendor.pkg_resources import DistInfoDistribution |
11 | 12 | from pip._vendor.six import PY2, ensure_str
|
12 | 13 |
|
13 | 14 | from pip._internal.exceptions import UnsupportedWheel
|
| 15 | +from pip._internal.utils.pkg_resources import DictMetadata |
14 | 16 | from pip._internal.utils.typing import MYPY_CHECK_RUNNING
|
15 | 17 |
|
16 | 18 | if MYPY_CHECK_RUNNING:
|
17 | 19 | from email.message import Message
|
18 |
| - from typing import Tuple |
| 20 | + from typing import Dict, Tuple |
| 21 | + |
| 22 | + from pip._vendor.pkg_resources import Distribution |
19 | 23 |
|
20 | 24 | if PY2:
|
21 | 25 | from zipfile import BadZipfile as BadZipFile
|
|
29 | 33 | logger = logging.getLogger(__name__)
|
30 | 34 |
|
31 | 35 |
|
| 36 | +class WheelMetadata(DictMetadata): |
| 37 | + """Metadata provider that maps metadata decoding exceptions to our |
| 38 | + internal exception type. |
| 39 | + """ |
| 40 | + def __init__(self, metadata, wheel_name): |
| 41 | + # type: (Dict[str, bytes], str) -> None |
| 42 | + super(WheelMetadata, self).__init__(metadata) |
| 43 | + self._wheel_name = wheel_name |
| 44 | + |
| 45 | + def get_metadata(self, name): |
| 46 | + # type: (str) -> str |
| 47 | + try: |
| 48 | + return super(WheelMetadata, self).get_metadata(name) |
| 49 | + except UnicodeDecodeError as e: |
| 50 | + # Augment the default error with the origin of the file. |
| 51 | + raise UnsupportedWheel( |
| 52 | + "Error decoding metadata for {}: {}".format( |
| 53 | + self._wheel_name, e |
| 54 | + ) |
| 55 | + ) |
| 56 | + |
| 57 | + |
| 58 | +def pkg_resources_distribution_for_wheel(wheel_zip, name, location): |
| 59 | + # type: (ZipFile, str, str) -> Distribution |
| 60 | + """Get a pkg_resources distribution given a wheel. |
| 61 | +
|
| 62 | + :raises UnsupportedWheel: on any errors |
| 63 | + """ |
| 64 | + info_dir, _ = parse_wheel(wheel_zip, name) |
| 65 | + |
| 66 | + metadata_files = [ |
| 67 | + p for p in wheel_zip.namelist() if p.startswith("{}/".format(info_dir)) |
| 68 | + ] |
| 69 | + |
| 70 | + metadata_text = {} # type: Dict[str, bytes] |
| 71 | + for path in metadata_files: |
| 72 | + # If a flag is set, namelist entries may be unicode in Python 2. |
| 73 | + # We coerce them to native str type to match the types used in the rest |
| 74 | + # of the code. This cannot fail because unicode can always be encoded |
| 75 | + # with UTF-8. |
| 76 | + full_path = ensure_str(path) |
| 77 | + _, metadata_name = full_path.split("/", 1) |
| 78 | + |
| 79 | + try: |
| 80 | + metadata_text[metadata_name] = read_wheel_metadata_file( |
| 81 | + wheel_zip, full_path |
| 82 | + ) |
| 83 | + except UnsupportedWheel as e: |
| 84 | + raise UnsupportedWheel( |
| 85 | + "{} has an invalid wheel, {}".format(name, str(e)) |
| 86 | + ) |
| 87 | + |
| 88 | + metadata = WheelMetadata(metadata_text, location) |
| 89 | + |
| 90 | + return DistInfoDistribution( |
| 91 | + location=location, metadata=metadata, project_name=name |
| 92 | + ) |
| 93 | + |
| 94 | + |
32 | 95 | def parse_wheel(wheel_zip, name):
|
33 | 96 | # type: (ZipFile, str) -> Tuple[str, Message]
|
34 | 97 | """Extract information from the provided wheel, ensuring it meets basic
|
@@ -88,23 +151,31 @@ def wheel_dist_info_dir(source, name):
|
88 | 151 | return ensure_str(info_dir)
|
89 | 152 |
|
90 | 153 |
|
| 154 | +def read_wheel_metadata_file(source, path): |
| 155 | + # type: (ZipFile, str) -> bytes |
| 156 | + try: |
| 157 | + return source.read(path) |
| 158 | + # BadZipFile for general corruption, KeyError for missing entry, |
| 159 | + # and RuntimeError for password-protected files |
| 160 | + except (BadZipFile, KeyError, RuntimeError) as e: |
| 161 | + raise UnsupportedWheel( |
| 162 | + "could not read {!r} file: {!r}".format(path, e) |
| 163 | + ) |
| 164 | + |
| 165 | + |
91 | 166 | def wheel_metadata(source, dist_info_dir):
|
92 | 167 | # type: (ZipFile, str) -> Message
|
93 | 168 | """Return the WHEEL metadata of an extracted wheel, if possible.
|
94 | 169 | Otherwise, raise UnsupportedWheel.
|
95 | 170 | """
|
96 |
| - try: |
97 |
| - # Zip file path separators must be / |
98 |
| - wheel_contents = source.read("{}/WHEEL".format(dist_info_dir)) |
99 |
| - # BadZipFile for general corruption, KeyError for missing entry, |
100 |
| - # and RuntimeError for password-protected files |
101 |
| - except (BadZipFile, KeyError, RuntimeError) as e: |
102 |
| - raise UnsupportedWheel("could not read WHEEL file: {!r}".format(e)) |
| 171 | + path = "{}/WHEEL".format(dist_info_dir) |
| 172 | + # Zip file path separators must be / |
| 173 | + wheel_contents = read_wheel_metadata_file(source, path) |
103 | 174 |
|
104 | 175 | try:
|
105 | 176 | wheel_text = ensure_str(wheel_contents)
|
106 | 177 | except UnicodeDecodeError as e:
|
107 |
| - raise UnsupportedWheel("error decoding WHEEL: {!r}".format(e)) |
| 178 | + raise UnsupportedWheel("error decoding {!r}: {!r}".format(path, e)) |
108 | 179 |
|
109 | 180 | # FeedParser (used by Parser) does not raise any exceptions. The returned
|
110 | 181 | # message may have .defects populated, but for backwards-compatibility we
|
|
0 commit comments