Path: blob/master/venv/Lib/site-packages/pip/_internal/utils/wheel.py
811 views
"""Support functions for working with wheel files.1"""23from __future__ import absolute_import45import logging6from email.parser import Parser7from zipfile import ZipFile89from pip._vendor.packaging.utils import canonicalize_name10from pip._vendor.pkg_resources import DistInfoDistribution11from pip._vendor.six import PY2, ensure_str1213from pip._internal.exceptions import UnsupportedWheel14from pip._internal.utils.pkg_resources import DictMetadata15from pip._internal.utils.typing import MYPY_CHECK_RUNNING1617if MYPY_CHECK_RUNNING:18from email.message import Message19from typing import Dict, Tuple2021from pip._vendor.pkg_resources import Distribution2223if PY2:24from zipfile import BadZipfile as BadZipFile25else:26from zipfile import BadZipFile272829VERSION_COMPATIBLE = (1, 0)303132logger = logging.getLogger(__name__)333435class WheelMetadata(DictMetadata):36"""Metadata provider that maps metadata decoding exceptions to our37internal exception type.38"""39def __init__(self, metadata, wheel_name):40# type: (Dict[str, bytes], str) -> None41super(WheelMetadata, self).__init__(metadata)42self._wheel_name = wheel_name4344def get_metadata(self, name):45# type: (str) -> str46try:47return super(WheelMetadata, self).get_metadata(name)48except UnicodeDecodeError as e:49# Augment the default error with the origin of the file.50raise UnsupportedWheel(51"Error decoding metadata for {}: {}".format(52self._wheel_name, e53)54)555657def pkg_resources_distribution_for_wheel(wheel_zip, name, location):58# type: (ZipFile, str, str) -> Distribution59"""Get a pkg_resources distribution given a wheel.6061:raises UnsupportedWheel: on any errors62"""63info_dir, _ = parse_wheel(wheel_zip, name)6465metadata_files = [66p for p in wheel_zip.namelist() if p.startswith("{}/".format(info_dir))67]6869metadata_text = {} # type: Dict[str, bytes]70for path in metadata_files:71# If a flag is set, namelist entries may be unicode in Python 2.72# We coerce them to native str type to match the types used in the rest73# of the code. This cannot fail because unicode can always be encoded74# with UTF-8.75full_path = ensure_str(path)76_, metadata_name = full_path.split("/", 1)7778try:79metadata_text[metadata_name] = read_wheel_metadata_file(80wheel_zip, full_path81)82except UnsupportedWheel as e:83raise UnsupportedWheel(84"{} has an invalid wheel, {}".format(name, str(e))85)8687metadata = WheelMetadata(metadata_text, location)8889return DistInfoDistribution(90location=location, metadata=metadata, project_name=name91)929394def parse_wheel(wheel_zip, name):95# type: (ZipFile, str) -> Tuple[str, Message]96"""Extract information from the provided wheel, ensuring it meets basic97standards.9899Returns the name of the .dist-info directory and the parsed WHEEL metadata.100"""101try:102info_dir = wheel_dist_info_dir(wheel_zip, name)103metadata = wheel_metadata(wheel_zip, info_dir)104version = wheel_version(metadata)105except UnsupportedWheel as e:106raise UnsupportedWheel(107"{} has an invalid wheel, {}".format(name, str(e))108)109110check_compatibility(version, name)111112return info_dir, metadata113114115def wheel_dist_info_dir(source, name):116# type: (ZipFile, str) -> str117"""Returns the name of the contained .dist-info directory.118119Raises AssertionError or UnsupportedWheel if not found, >1 found, or120it doesn't match the provided name.121"""122# Zip file path separators must be /123subdirs = list(set(p.split("/")[0] for p in source.namelist()))124125info_dirs = [s for s in subdirs if s.endswith('.dist-info')]126127if not info_dirs:128raise UnsupportedWheel(".dist-info directory not found")129130if len(info_dirs) > 1:131raise UnsupportedWheel(132"multiple .dist-info directories found: {}".format(133", ".join(info_dirs)134)135)136137info_dir = info_dirs[0]138139info_dir_name = canonicalize_name(info_dir)140canonical_name = canonicalize_name(name)141if not info_dir_name.startswith(canonical_name):142raise UnsupportedWheel(143".dist-info directory {!r} does not start with {!r}".format(144info_dir, canonical_name145)146)147148# Zip file paths can be unicode or str depending on the zip entry flags,149# so normalize it.150return ensure_str(info_dir)151152153def read_wheel_metadata_file(source, path):154# type: (ZipFile, str) -> bytes155try:156return source.read(path)157# BadZipFile for general corruption, KeyError for missing entry,158# and RuntimeError for password-protected files159except (BadZipFile, KeyError, RuntimeError) as e:160raise UnsupportedWheel(161"could not read {!r} file: {!r}".format(path, e)162)163164165def wheel_metadata(source, dist_info_dir):166# type: (ZipFile, str) -> Message167"""Return the WHEEL metadata of an extracted wheel, if possible.168Otherwise, raise UnsupportedWheel.169"""170path = "{}/WHEEL".format(dist_info_dir)171# Zip file path separators must be /172wheel_contents = read_wheel_metadata_file(source, path)173174try:175wheel_text = ensure_str(wheel_contents)176except UnicodeDecodeError as e:177raise UnsupportedWheel("error decoding {!r}: {!r}".format(path, e))178179# FeedParser (used by Parser) does not raise any exceptions. The returned180# message may have .defects populated, but for backwards-compatibility we181# currently ignore them.182return Parser().parsestr(wheel_text)183184185def wheel_version(wheel_data):186# type: (Message) -> Tuple[int, ...]187"""Given WHEEL metadata, return the parsed Wheel-Version.188Otherwise, raise UnsupportedWheel.189"""190version_text = wheel_data["Wheel-Version"]191if version_text is None:192raise UnsupportedWheel("WHEEL is missing Wheel-Version")193194version = version_text.strip()195196try:197return tuple(map(int, version.split('.')))198except ValueError:199raise UnsupportedWheel("invalid Wheel-Version: {!r}".format(version))200201202def check_compatibility(version, name):203# type: (Tuple[int, ...], str) -> None204"""Raises errors or warns if called with an incompatible Wheel-Version.205206pip should refuse to install a Wheel-Version that's a major series207ahead of what it's compatible with (e.g 2.0 > 1.1); and warn when208installing a version only minor version ahead (e.g 1.2 > 1.1).209210version: a 2-tuple representing a Wheel-Version (Major, Minor)211name: name of wheel or package to raise exception about212213:raises UnsupportedWheel: when an incompatible Wheel-Version is given214"""215if version[0] > VERSION_COMPATIBLE[0]:216raise UnsupportedWheel(217"{}'s Wheel-Version ({}) is not compatible with this version "218"of pip".format(name, '.'.join(map(str, version)))219)220elif version > VERSION_COMPATIBLE:221logger.warning(222'Installing from a newer Wheel-Version (%s)',223'.'.join(map(str, version)),224)225226227