Пример #1
0
def _verify_one(req, wheel_path):
    # type: (InstallRequirement, str) -> None
    canonical_name = canonicalize_name(req.name or "")
    w = Wheel(os.path.basename(wheel_path))
    if canonicalize_name(w.name) != canonical_name:
        raise InvalidWheelFilename(
            "Wheel has unexpected file name: expected {!r}, "
            "got {!r}".format(canonical_name, w.name),
        )
    dist = get_wheel_distribution(wheel_path, canonical_name)
    dist_verstr = str(dist.version)
    if canonicalize_version(dist_verstr) != canonicalize_version(w.version):
        raise InvalidWheelFilename(
            "Wheel has unexpected file name: expected {!r}, "
            "got {!r}".format(dist_verstr, w.version),
        )
    metadata_version_value = dist.metadata_version
    if metadata_version_value is None:
        raise UnsupportedWheel("Missing Metadata-Version")
    try:
        metadata_version = Version(metadata_version_value)
    except InvalidVersion:
        msg = f"Invalid Metadata-Version: {metadata_version_value}"
        raise UnsupportedWheel(msg)
    if (metadata_version >= Version("1.2")
            and not isinstance(dist.version, Version)):
        raise UnsupportedWheel(
            "Metadata 1.2 mandates PEP 440 version, "
            "but {!r} is not".format(dist_verstr)
        )
Пример #2
0
def wheel_dist_info_dir(source: ZipFile, name: str) -> str:
    """Returns the name of the contained .dist-info directory.

    Raises AssertionError or UnsupportedWheel if not found, >1 found, or
    it doesn't match the provided name.
    """
    # Zip file path separators must be /
    subdirs = {p.split("/", 1)[0] for p in source.namelist()}

    info_dirs = [s for s in subdirs if s.endswith(".dist-info")]

    if not info_dirs:
        raise UnsupportedWheel(".dist-info directory not found")

    if len(info_dirs) > 1:
        raise UnsupportedWheel(
            "multiple .dist-info directories found: {}".format(
                ", ".join(info_dirs)))

    info_dir = info_dirs[0]

    info_dir_name = canonicalize_name(info_dir)
    canonical_name = canonicalize_name(name)
    if not info_dir_name.startswith(canonical_name):
        raise UnsupportedWheel(
            ".dist-info directory {!r} does not start with {!r}".format(
                info_dir, canonical_name))

    return info_dir
Пример #3
0
def check_compatibility(version, name):
    # type: (Optional[Tuple[int, ...]], str) -> None
    """
    Raises errors or warns if called with an incompatible Wheel-Version.

    Pip should refuse to install a Wheel-Version that's a major series
    ahead of what it's compatible with (e.g 2.0 > 1.1); and warn when
    installing a version only minor version ahead (e.g 1.2 > 1.1).

    version: a 2-tuple representing a Wheel-Version (Major, Minor)
    name: name of wheel or package to raise exception about

    :raises UnsupportedWheel: when an incompatible Wheel-Version is given
    """
    if not version:
        raise UnsupportedWheel("%s is in an unsupported or invalid wheel" %
                               name)
    if version[0] > VERSION_COMPATIBLE[0]:
        raise UnsupportedWheel(
            "%s's Wheel-Version (%s) is not compatible with this version "
            "of pip" % (name, '.'.join(map(str, version))))
    elif version > VERSION_COMPATIBLE:
        logger.warning(
            'Installing from a newer Wheel-Version (%s)',
            '.'.join(map(str, version)),
        )
def wheel_dist_info_dir(source, name):
    # type: (ZipFile, str) -> str
    """Returns the name of the contained .dist-info directory.

    Raises AssertionError or UnsupportedWheel if not found, >1 found, or
    it doesn't match the provided name.
    """
    # Zip file path separators must be /
    subdirs = list(set(p.split("/")[0] for p in source.namelist()))

    info_dirs = [s for s in subdirs if s.endswith('.dist-info')]

    if not info_dirs:
        raise UnsupportedWheel(".dist-info directory not found")

    if len(info_dirs) > 1:
        raise UnsupportedWheel(
            "multiple .dist-info directories found: {}".format(
                ", ".join(info_dirs)))

    info_dir = info_dirs[0]

    info_dir_name = canonicalize_name(info_dir)
    canonical_name = canonicalize_name(name)
    if not info_dir_name.startswith(canonical_name):
        raise UnsupportedWheel(
            ".dist-info directory {!r} does not start with {!r}".format(
                info_dir, canonical_name))

    # Zip file paths can be unicode or str depending on the zip entry flags,
    # so normalize it.
    return ensure_str(info_dir)
Пример #5
0
def wheel_version(wheel_data: Message) -> Tuple[int, ...]:
    """Given WHEEL metadata, return the parsed Wheel-Version.
    Otherwise, raise UnsupportedWheel.
    """
    version_text = wheel_data["Wheel-Version"]
    if version_text is None:
        raise UnsupportedWheel("WHEEL is missing Wheel-Version")

    version = version_text.strip()

    try:
        return tuple(map(int, version.split(".")))
    except ValueError:
        raise UnsupportedWheel(f"invalid Wheel-Version: {version!r}")
Пример #6
0
def read_wheel_metadata_file(source: ZipFile, path: str) -> bytes:
    try:
        return source.read(path)
        # BadZipFile for general corruption, KeyError for missing entry,
        # and RuntimeError for password-protected files
    except (BadZipFile, KeyError, RuntimeError) as e:
        raise UnsupportedWheel(f"could not read {path!r} file: {e!r}")
Пример #7
0
    def from_wheel(cls, wheel: Wheel, name: str) -> "Distribution":
        """Load the distribution from a given wheel.

        :raises InvalidWheel: Whenever loading of the wheel causes a
            :py:exc:`zipfile.BadZipFile` exception to be thrown.
        :raises UnsupportedWheel: If the wheel is a valid zip, but malformed
            internally.
        """
        try:
            with wheel.as_zipfile() as zf:
                info_dir, _ = parse_wheel(zf, name)
                metadata_text = {
                    path.split("/", 1)[-1]: read_wheel_metadata_file(zf, path)
                    for path in zf.namelist()
                    if path.startswith(f"{info_dir}/")
                }
        except zipfile.BadZipFile as e:
            raise InvalidWheel(wheel.location, name) from e
        except UnsupportedWheel as e:
            raise UnsupportedWheel(f"{name} has an invalid wheel, {e}")
        dist = pkg_resources.DistInfoDistribution(
            location=wheel.location,
            metadata=WheelMetadata(metadata_text, wheel.location),
            project_name=name,
        )
        return cls(dist)
Пример #8
0
def pkg_resources_distribution_for_wheel(wheel_zip, name, location):
    # type: (ZipFile, str, str) -> Distribution
    """Get a pkg_resources distribution given a wheel.

    :raises UnsupportedWheel: on any errors
    """
    info_dir, _ = parse_wheel(wheel_zip, name)

    metadata_files = [
        p for p in wheel_zip.namelist() if p.startswith(f"{info_dir}/")
    ]

    metadata_text = {}  # type: Dict[str, bytes]
    for path in metadata_files:
        _, metadata_name = path.split("/", 1)

        try:
            metadata_text[metadata_name] = read_wheel_metadata_file(
                wheel_zip, path)
        except UnsupportedWheel as e:
            raise UnsupportedWheel("{} has an invalid wheel, {}".format(
                name, str(e)))

    metadata = WheelMetadata(metadata_text, location)

    return DistInfoDistribution(location=location,
                                metadata=metadata,
                                project_name=name)
def pkg_resources_distribution_for_wheel(wheel_zip, name, location):
    # type: (ZipFile, str, str) -> Distribution
    """Get a pkg_resources distribution given a wheel.

    :raises UnsupportedWheel: on any errors
    """
    info_dir, _ = parse_wheel(wheel_zip, name)

    metadata_files = [
        p for p in wheel_zip.namelist() if p.startswith("{}/".format(info_dir))
    ]

    metadata_text = {}  # type: Dict[str, bytes]
    for path in metadata_files:
        # If a flag is set, namelist entries may be unicode in Python 2.
        # We coerce them to native str type to match the types used in the rest
        # of the code. This cannot fail because unicode can always be encoded
        # with UTF-8.
        full_path = ensure_str(path)
        _, metadata_name = full_path.split("/", 1)

        try:
            metadata_text[metadata_name] = read_wheel_metadata_file(
                wheel_zip, full_path)
        except UnsupportedWheel as e:
            raise UnsupportedWheel("{} has an invalid wheel, {}".format(
                name, str(e)))

    metadata = WheelMetadata(metadata_text, location)

    return DistInfoDistribution(location=location,
                                metadata=metadata,
                                project_name=name)
Пример #10
0
 def get_metadata(self, name: str) -> str:
     try:
         return self._metadata[name].decode()
     except UnicodeDecodeError as e:
         # Augment the default error with the origin of the file.
         raise UnsupportedWheel(
             f"Error decoding metadata for {self._wheel_name}: {e} in {name} file"
         )
 def get_metadata(self, name):
     # type: (str) -> str
     try:
         return super(WheelMetadata, self).get_metadata(name)
     except UnicodeDecodeError as e:
         # Augment the default error with the origin of the file.
         raise UnsupportedWheel("Error decoding metadata for {}: {}".format(
             self._wheel_name, e))
Пример #12
0
 def _fail_if_link_is_unsupported_wheel(self, link: Link) -> None:
     if not link.is_wheel:
         return
     wheel = Wheel(link.filename)
     if wheel.supported(self._finder.target_python.get_tags()):
         return
     msg = f"{link.filename} is not a supported wheel on this platform."
     raise UnsupportedWheel(msg)
Пример #13
0
def parse_wheel(wheel_zip: ZipFile, name: str) -> Tuple[str, Message]:
    """Extract information from the provided wheel, ensuring it meets basic
    standards.

    Returns the name of the .dist-info directory and the parsed WHEEL metadata.
    """
    try:
        info_dir = wheel_dist_info_dir(wheel_zip, name)
        metadata = wheel_metadata(wheel_zip, info_dir)
        version = wheel_version(metadata)
    except UnsupportedWheel as e:
        raise UnsupportedWheel("{} has an invalid wheel, {}".format(
            name, str(e)))

    check_compatibility(version, name)

    return info_dir, metadata
Пример #14
0
def wheel_metadata(source: ZipFile, dist_info_dir: str) -> Message:
    """Return the WHEEL metadata of an extracted wheel, if possible.
    Otherwise, raise UnsupportedWheel.
    """
    path = f"{dist_info_dir}/WHEEL"
    # Zip file path separators must be /
    wheel_contents = read_wheel_metadata_file(source, path)

    try:
        wheel_text = wheel_contents.decode()
    except UnicodeDecodeError as e:
        raise UnsupportedWheel(f"error decoding {path!r}: {e!r}")

    # FeedParser (used by Parser) does not raise any exceptions. The returned
    # message may have .defects populated, but for backwards-compatibility we
    # currently ignore them.
    return Parser().parsestr(wheel_text)
Пример #15
0
def check_compatibility(version: Tuple[int, ...], name: str) -> None:
    """Raises errors or warns if called with an incompatible Wheel-Version.

    pip should refuse to install a Wheel-Version that's a major series
    ahead of what it's compatible with (e.g 2.0 > 1.1); and warn when
    installing a version only minor version ahead (e.g 1.2 > 1.1).

    version: a 2-tuple representing a Wheel-Version (Major, Minor)
    name: name of wheel or package to raise exception about

    :raises UnsupportedWheel: when an incompatible Wheel-Version is given
    """
    if version[0] > VERSION_COMPATIBLE[0]:
        raise UnsupportedWheel(
            "{}'s Wheel-Version ({}) is not compatible with this version "
            "of pip".format(name, ".".join(map(str, version))))
    elif version > VERSION_COMPATIBLE:
        logger.warning(
            "Installing from a newer Wheel-Version (%s)",
            ".".join(map(str, version)),
        )
Пример #16
0
    def _candidate_sort_key(self, candidate, ignore_compatibility=True):
        """
        Function used to generate link sort key for link tuples.
        The greater the return value, the more preferred it is.
        If not finding wheels, then sorted by version only.
        If finding wheels, then the sort order is by version, then:
          1. existing installs
          2. wheels ordered via Wheel.support_index_min(self.valid_tags)
          3. source archives
        If prefer_binary was set, then all wheels are sorted above sources.
        Note: it was considered to embed this logic into the Link
              comparison operators, but then different sdist links
              with the same version, would have to be considered equal
        """
        support_num = len(self.valid_tags)
        build_tag = tuple()
        binary_preference = 0
        if candidate.location.is_wheel:
            # can raise InvalidWheelFilename
            wheel = Wheel(candidate.location.filename)
            if not wheel.supported(
                    self.valid_tags) and not ignore_compatibility:
                raise UnsupportedWheel(
                    "%s is not a supported wheel for this platform. It "
                    "can't be sorted." % wheel.filename)
            if self.prefer_binary:
                binary_preference = 1
            tags = self.valid_tags if not ignore_compatibility else None
            try:
                pri = -(wheel.support_index_min(tags=tags))
            except TypeError:
                pri = -(support_num)

            if wheel.build_tag is not None:
                match = re.match(r'^(\d+)(.*)$', wheel.build_tag)
                build_tag_groups = match.groups()
                build_tag = (int(build_tag_groups[0]), build_tag_groups[1])
        else:  # sdist
            pri = -(support_num)
        return (binary_preference, candidate.version, build_tag, pri)
Пример #17
0
    def _sort_key(self,
                  candidate: InstallationCandidate,
                  ignore_compatibility: bool = True) -> CandidateSortingKey:
        """
        Function to pass as the `key` argument to a call to sorted() to sort
        InstallationCandidates by preference.

        Returns a tuple such that tuples sorting as greater using Python's
        default comparison operator are more preferred.

        The preference is as follows:

        First and foremost, candidates with allowed (matching) hashes are
        always preferred over candidates without matching hashes. This is
        because e.g. if the only candidate with an allowed hash is yanked,
        we still want to use that candidate.

        Second, excepting hash considerations, candidates that have been
        yanked (in the sense of PEP 592) are always less preferred than
        candidates that haven't been yanked. Then:

        If not finding wheels, they are sorted by version only.
        If finding wheels, then the sort order is by version, then:
          1. existing installs
          2. wheels ordered via Wheel.support_index_min(self._supported_tags)
          3. source archives
        If prefer_binary was set, then all wheels are sorted above sources.

        Note: it was considered to embed this logic into the Link
              comparison operators, but then different sdist links
              with the same version, would have to be considered equal
        """
        valid_tags = self._supported_tags
        support_num = len(valid_tags)
        build_tag: BuildTag = ()
        binary_preference = 0
        link = candidate.link
        if link.is_wheel:
            # can raise InvalidWheelFilename
            wheel = Wheel(link.filename)
            try:
                pri = -(wheel.find_most_preferred_tag(
                    valid_tags, self._wheel_tag_preferences))
            except ValueError:
                if not ignore_compatibility:
                    raise UnsupportedWheel(
                        "{} is not a supported wheel for this platform. It "
                        "can't be sorted.".format(wheel.filename))
                pri = -(support_num)
            if self._prefer_binary:
                binary_preference = 1
            if wheel.build_tag is not None:
                match = re.match(r'^(\d+)(.*)$', wheel.build_tag)
                build_tag_groups = match.groups()
                build_tag = (int(build_tag_groups[0]), build_tag_groups[1])
        else:  # sdist
            pri = -(support_num)
        has_allowed_hash = int(link.is_hash_allowed(self._hashes))
        yank_value = -1 * int(link.is_yanked)  # -1 for yanked.
        return (
            has_allowed_hash,
            yank_value,
            binary_preference,
            candidate.version,
            pri,
            build_tag,
        )