virtuelle Umgebung teil20b
This commit is contained in:
		@@ -0,0 +1,600 @@
 | 
			
		||||
"""Dependency Resolution
 | 
			
		||||
 | 
			
		||||
The dependency resolution in pip is performed as follows:
 | 
			
		||||
 | 
			
		||||
for top-level requirements:
 | 
			
		||||
    a. only one spec allowed per project, regardless of conflicts or not.
 | 
			
		||||
       otherwise a "double requirement" exception is raised
 | 
			
		||||
    b. they override sub-dependency requirements.
 | 
			
		||||
for sub-dependencies
 | 
			
		||||
    a. "first found, wins" (where the order is breadth first)
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
# The following comment should be removed at some point in the future.
 | 
			
		||||
# mypy: strict-optional=False
 | 
			
		||||
 | 
			
		||||
import logging
 | 
			
		||||
import sys
 | 
			
		||||
from collections import defaultdict
 | 
			
		||||
from itertools import chain
 | 
			
		||||
from typing import DefaultDict, Iterable, List, Optional, Set, Tuple
 | 
			
		||||
 | 
			
		||||
from pip._vendor.packaging import specifiers
 | 
			
		||||
from pip._vendor.packaging.requirements import Requirement
 | 
			
		||||
 | 
			
		||||
from pip._internal.cache import WheelCache
 | 
			
		||||
from pip._internal.exceptions import (
 | 
			
		||||
    BestVersionAlreadyInstalled,
 | 
			
		||||
    DistributionNotFound,
 | 
			
		||||
    HashError,
 | 
			
		||||
    HashErrors,
 | 
			
		||||
    InstallationError,
 | 
			
		||||
    NoneMetadataError,
 | 
			
		||||
    UnsupportedPythonVersion,
 | 
			
		||||
)
 | 
			
		||||
from pip._internal.index.package_finder import PackageFinder
 | 
			
		||||
from pip._internal.metadata import BaseDistribution
 | 
			
		||||
from pip._internal.models.link import Link
 | 
			
		||||
from pip._internal.models.wheel import Wheel
 | 
			
		||||
from pip._internal.operations.prepare import RequirementPreparer
 | 
			
		||||
from pip._internal.req.req_install import (
 | 
			
		||||
    InstallRequirement,
 | 
			
		||||
    check_invalid_constraint_type,
 | 
			
		||||
)
 | 
			
		||||
from pip._internal.req.req_set import RequirementSet
 | 
			
		||||
from pip._internal.resolution.base import BaseResolver, InstallRequirementProvider
 | 
			
		||||
from pip._internal.utils import compatibility_tags
 | 
			
		||||
from pip._internal.utils.compatibility_tags import get_supported
 | 
			
		||||
from pip._internal.utils.direct_url_helpers import direct_url_from_link
 | 
			
		||||
from pip._internal.utils.logging import indent_log
 | 
			
		||||
from pip._internal.utils.misc import normalize_version_info
 | 
			
		||||
from pip._internal.utils.packaging import check_requires_python
 | 
			
		||||
 | 
			
		||||
logger = logging.getLogger(__name__)
 | 
			
		||||
 | 
			
		||||
DiscoveredDependencies = DefaultDict[str, List[InstallRequirement]]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _check_dist_requires_python(
 | 
			
		||||
    dist: BaseDistribution,
 | 
			
		||||
    version_info: Tuple[int, int, int],
 | 
			
		||||
    ignore_requires_python: bool = False,
 | 
			
		||||
) -> None:
 | 
			
		||||
    """
 | 
			
		||||
    Check whether the given Python version is compatible with a distribution's
 | 
			
		||||
    "Requires-Python" value.
 | 
			
		||||
 | 
			
		||||
    :param version_info: A 3-tuple of ints representing the Python
 | 
			
		||||
        major-minor-micro version to check.
 | 
			
		||||
    :param ignore_requires_python: Whether to ignore the "Requires-Python"
 | 
			
		||||
        value if the given Python version isn't compatible.
 | 
			
		||||
 | 
			
		||||
    :raises UnsupportedPythonVersion: When the given Python version isn't
 | 
			
		||||
        compatible.
 | 
			
		||||
    """
 | 
			
		||||
    # This idiosyncratically converts the SpecifierSet to str and let
 | 
			
		||||
    # check_requires_python then parse it again into SpecifierSet. But this
 | 
			
		||||
    # is the legacy resolver so I'm just not going to bother refactoring.
 | 
			
		||||
    try:
 | 
			
		||||
        requires_python = str(dist.requires_python)
 | 
			
		||||
    except FileNotFoundError as e:
 | 
			
		||||
        raise NoneMetadataError(dist, str(e))
 | 
			
		||||
    try:
 | 
			
		||||
        is_compatible = check_requires_python(
 | 
			
		||||
            requires_python,
 | 
			
		||||
            version_info=version_info,
 | 
			
		||||
        )
 | 
			
		||||
    except specifiers.InvalidSpecifier as exc:
 | 
			
		||||
        logger.warning(
 | 
			
		||||
            "Package %r has an invalid Requires-Python: %s", dist.raw_name, exc
 | 
			
		||||
        )
 | 
			
		||||
        return
 | 
			
		||||
 | 
			
		||||
    if is_compatible:
 | 
			
		||||
        return
 | 
			
		||||
 | 
			
		||||
    version = ".".join(map(str, version_info))
 | 
			
		||||
    if ignore_requires_python:
 | 
			
		||||
        logger.debug(
 | 
			
		||||
            "Ignoring failed Requires-Python check for package %r: %s not in %r",
 | 
			
		||||
            dist.raw_name,
 | 
			
		||||
            version,
 | 
			
		||||
            requires_python,
 | 
			
		||||
        )
 | 
			
		||||
        return
 | 
			
		||||
 | 
			
		||||
    raise UnsupportedPythonVersion(
 | 
			
		||||
        "Package {!r} requires a different Python: {} not in {!r}".format(
 | 
			
		||||
            dist.raw_name, version, requires_python
 | 
			
		||||
        )
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Resolver(BaseResolver):
 | 
			
		||||
    """Resolves which packages need to be installed/uninstalled to perform \
 | 
			
		||||
    the requested operation without breaking the requirements of any package.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    _allowed_strategies = {"eager", "only-if-needed", "to-satisfy-only"}
 | 
			
		||||
 | 
			
		||||
    def __init__(
 | 
			
		||||
        self,
 | 
			
		||||
        preparer: RequirementPreparer,
 | 
			
		||||
        finder: PackageFinder,
 | 
			
		||||
        wheel_cache: Optional[WheelCache],
 | 
			
		||||
        make_install_req: InstallRequirementProvider,
 | 
			
		||||
        use_user_site: bool,
 | 
			
		||||
        ignore_dependencies: bool,
 | 
			
		||||
        ignore_installed: bool,
 | 
			
		||||
        ignore_requires_python: bool,
 | 
			
		||||
        force_reinstall: bool,
 | 
			
		||||
        upgrade_strategy: str,
 | 
			
		||||
        py_version_info: Optional[Tuple[int, ...]] = None,
 | 
			
		||||
    ) -> None:
 | 
			
		||||
        super().__init__()
 | 
			
		||||
        assert upgrade_strategy in self._allowed_strategies
 | 
			
		||||
 | 
			
		||||
        if py_version_info is None:
 | 
			
		||||
            py_version_info = sys.version_info[:3]
 | 
			
		||||
        else:
 | 
			
		||||
            py_version_info = normalize_version_info(py_version_info)
 | 
			
		||||
 | 
			
		||||
        self._py_version_info = py_version_info
 | 
			
		||||
 | 
			
		||||
        self.preparer = preparer
 | 
			
		||||
        self.finder = finder
 | 
			
		||||
        self.wheel_cache = wheel_cache
 | 
			
		||||
 | 
			
		||||
        self.upgrade_strategy = upgrade_strategy
 | 
			
		||||
        self.force_reinstall = force_reinstall
 | 
			
		||||
        self.ignore_dependencies = ignore_dependencies
 | 
			
		||||
        self.ignore_installed = ignore_installed
 | 
			
		||||
        self.ignore_requires_python = ignore_requires_python
 | 
			
		||||
        self.use_user_site = use_user_site
 | 
			
		||||
        self._make_install_req = make_install_req
 | 
			
		||||
 | 
			
		||||
        self._discovered_dependencies: DiscoveredDependencies = defaultdict(list)
 | 
			
		||||
 | 
			
		||||
    def resolve(
 | 
			
		||||
        self, root_reqs: List[InstallRequirement], check_supported_wheels: bool
 | 
			
		||||
    ) -> RequirementSet:
 | 
			
		||||
        """Resolve what operations need to be done
 | 
			
		||||
 | 
			
		||||
        As a side-effect of this method, the packages (and their dependencies)
 | 
			
		||||
        are downloaded, unpacked and prepared for installation. This
 | 
			
		||||
        preparation is done by ``pip.operations.prepare``.
 | 
			
		||||
 | 
			
		||||
        Once PyPI has static dependency metadata available, it would be
 | 
			
		||||
        possible to move the preparation to become a step separated from
 | 
			
		||||
        dependency resolution.
 | 
			
		||||
        """
 | 
			
		||||
        requirement_set = RequirementSet(check_supported_wheels=check_supported_wheels)
 | 
			
		||||
        for req in root_reqs:
 | 
			
		||||
            if req.constraint:
 | 
			
		||||
                check_invalid_constraint_type(req)
 | 
			
		||||
            self._add_requirement_to_set(requirement_set, req)
 | 
			
		||||
 | 
			
		||||
        # Actually prepare the files, and collect any exceptions. Most hash
 | 
			
		||||
        # exceptions cannot be checked ahead of time, because
 | 
			
		||||
        # _populate_link() needs to be called before we can make decisions
 | 
			
		||||
        # based on link type.
 | 
			
		||||
        discovered_reqs: List[InstallRequirement] = []
 | 
			
		||||
        hash_errors = HashErrors()
 | 
			
		||||
        for req in chain(requirement_set.all_requirements, discovered_reqs):
 | 
			
		||||
            try:
 | 
			
		||||
                discovered_reqs.extend(self._resolve_one(requirement_set, req))
 | 
			
		||||
            except HashError as exc:
 | 
			
		||||
                exc.req = req
 | 
			
		||||
                hash_errors.append(exc)
 | 
			
		||||
 | 
			
		||||
        if hash_errors:
 | 
			
		||||
            raise hash_errors
 | 
			
		||||
 | 
			
		||||
        return requirement_set
 | 
			
		||||
 | 
			
		||||
    def _add_requirement_to_set(
 | 
			
		||||
        self,
 | 
			
		||||
        requirement_set: RequirementSet,
 | 
			
		||||
        install_req: InstallRequirement,
 | 
			
		||||
        parent_req_name: Optional[str] = None,
 | 
			
		||||
        extras_requested: Optional[Iterable[str]] = None,
 | 
			
		||||
    ) -> Tuple[List[InstallRequirement], Optional[InstallRequirement]]:
 | 
			
		||||
        """Add install_req as a requirement to install.
 | 
			
		||||
 | 
			
		||||
        :param parent_req_name: The name of the requirement that needed this
 | 
			
		||||
            added. The name is used because when multiple unnamed requirements
 | 
			
		||||
            resolve to the same name, we could otherwise end up with dependency
 | 
			
		||||
            links that point outside the Requirements set. parent_req must
 | 
			
		||||
            already be added. Note that None implies that this is a user
 | 
			
		||||
            supplied requirement, vs an inferred one.
 | 
			
		||||
        :param extras_requested: an iterable of extras used to evaluate the
 | 
			
		||||
            environment markers.
 | 
			
		||||
        :return: Additional requirements to scan. That is either [] if
 | 
			
		||||
            the requirement is not applicable, or [install_req] if the
 | 
			
		||||
            requirement is applicable and has just been added.
 | 
			
		||||
        """
 | 
			
		||||
        # If the markers do not match, ignore this requirement.
 | 
			
		||||
        if not install_req.match_markers(extras_requested):
 | 
			
		||||
            logger.info(
 | 
			
		||||
                "Ignoring %s: markers '%s' don't match your environment",
 | 
			
		||||
                install_req.name,
 | 
			
		||||
                install_req.markers,
 | 
			
		||||
            )
 | 
			
		||||
            return [], None
 | 
			
		||||
 | 
			
		||||
        # If the wheel is not supported, raise an error.
 | 
			
		||||
        # Should check this after filtering out based on environment markers to
 | 
			
		||||
        # allow specifying different wheels based on the environment/OS, in a
 | 
			
		||||
        # single requirements file.
 | 
			
		||||
        if install_req.link and install_req.link.is_wheel:
 | 
			
		||||
            wheel = Wheel(install_req.link.filename)
 | 
			
		||||
            tags = compatibility_tags.get_supported()
 | 
			
		||||
            if requirement_set.check_supported_wheels and not wheel.supported(tags):
 | 
			
		||||
                raise InstallationError(
 | 
			
		||||
                    "{} is not a supported wheel on this platform.".format(
 | 
			
		||||
                        wheel.filename
 | 
			
		||||
                    )
 | 
			
		||||
                )
 | 
			
		||||
 | 
			
		||||
        # This next bit is really a sanity check.
 | 
			
		||||
        assert (
 | 
			
		||||
            not install_req.user_supplied or parent_req_name is None
 | 
			
		||||
        ), "a user supplied req shouldn't have a parent"
 | 
			
		||||
 | 
			
		||||
        # Unnamed requirements are scanned again and the requirement won't be
 | 
			
		||||
        # added as a dependency until after scanning.
 | 
			
		||||
        if not install_req.name:
 | 
			
		||||
            requirement_set.add_unnamed_requirement(install_req)
 | 
			
		||||
            return [install_req], None
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            existing_req: Optional[
 | 
			
		||||
                InstallRequirement
 | 
			
		||||
            ] = requirement_set.get_requirement(install_req.name)
 | 
			
		||||
        except KeyError:
 | 
			
		||||
            existing_req = None
 | 
			
		||||
 | 
			
		||||
        has_conflicting_requirement = (
 | 
			
		||||
            parent_req_name is None
 | 
			
		||||
            and existing_req
 | 
			
		||||
            and not existing_req.constraint
 | 
			
		||||
            and existing_req.extras == install_req.extras
 | 
			
		||||
            and existing_req.req
 | 
			
		||||
            and install_req.req
 | 
			
		||||
            and existing_req.req.specifier != install_req.req.specifier
 | 
			
		||||
        )
 | 
			
		||||
        if has_conflicting_requirement:
 | 
			
		||||
            raise InstallationError(
 | 
			
		||||
                "Double requirement given: {} (already in {}, name={!r})".format(
 | 
			
		||||
                    install_req, existing_req, install_req.name
 | 
			
		||||
                )
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
        # When no existing requirement exists, add the requirement as a
 | 
			
		||||
        # dependency and it will be scanned again after.
 | 
			
		||||
        if not existing_req:
 | 
			
		||||
            requirement_set.add_named_requirement(install_req)
 | 
			
		||||
            # We'd want to rescan this requirement later
 | 
			
		||||
            return [install_req], install_req
 | 
			
		||||
 | 
			
		||||
        # Assume there's no need to scan, and that we've already
 | 
			
		||||
        # encountered this for scanning.
 | 
			
		||||
        if install_req.constraint or not existing_req.constraint:
 | 
			
		||||
            return [], existing_req
 | 
			
		||||
 | 
			
		||||
        does_not_satisfy_constraint = install_req.link and not (
 | 
			
		||||
            existing_req.link and install_req.link.path == existing_req.link.path
 | 
			
		||||
        )
 | 
			
		||||
        if does_not_satisfy_constraint:
 | 
			
		||||
            raise InstallationError(
 | 
			
		||||
                "Could not satisfy constraints for '{}': "
 | 
			
		||||
                "installation from path or url cannot be "
 | 
			
		||||
                "constrained to a version".format(install_req.name)
 | 
			
		||||
            )
 | 
			
		||||
        # If we're now installing a constraint, mark the existing
 | 
			
		||||
        # object for real installation.
 | 
			
		||||
        existing_req.constraint = False
 | 
			
		||||
        # If we're now installing a user supplied requirement,
 | 
			
		||||
        # mark the existing object as such.
 | 
			
		||||
        if install_req.user_supplied:
 | 
			
		||||
            existing_req.user_supplied = True
 | 
			
		||||
        existing_req.extras = tuple(
 | 
			
		||||
            sorted(set(existing_req.extras) | set(install_req.extras))
 | 
			
		||||
        )
 | 
			
		||||
        logger.debug(
 | 
			
		||||
            "Setting %s extras to: %s",
 | 
			
		||||
            existing_req,
 | 
			
		||||
            existing_req.extras,
 | 
			
		||||
        )
 | 
			
		||||
        # Return the existing requirement for addition to the parent and
 | 
			
		||||
        # scanning again.
 | 
			
		||||
        return [existing_req], existing_req
 | 
			
		||||
 | 
			
		||||
    def _is_upgrade_allowed(self, req: InstallRequirement) -> bool:
 | 
			
		||||
        if self.upgrade_strategy == "to-satisfy-only":
 | 
			
		||||
            return False
 | 
			
		||||
        elif self.upgrade_strategy == "eager":
 | 
			
		||||
            return True
 | 
			
		||||
        else:
 | 
			
		||||
            assert self.upgrade_strategy == "only-if-needed"
 | 
			
		||||
            return req.user_supplied or req.constraint
 | 
			
		||||
 | 
			
		||||
    def _set_req_to_reinstall(self, req: InstallRequirement) -> None:
 | 
			
		||||
        """
 | 
			
		||||
        Set a requirement to be installed.
 | 
			
		||||
        """
 | 
			
		||||
        # Don't uninstall the conflict if doing a user install and the
 | 
			
		||||
        # conflict is not a user install.
 | 
			
		||||
        if not self.use_user_site or req.satisfied_by.in_usersite:
 | 
			
		||||
            req.should_reinstall = True
 | 
			
		||||
        req.satisfied_by = None
 | 
			
		||||
 | 
			
		||||
    def _check_skip_installed(
 | 
			
		||||
        self, req_to_install: InstallRequirement
 | 
			
		||||
    ) -> Optional[str]:
 | 
			
		||||
        """Check if req_to_install should be skipped.
 | 
			
		||||
 | 
			
		||||
        This will check if the req is installed, and whether we should upgrade
 | 
			
		||||
        or reinstall it, taking into account all the relevant user options.
 | 
			
		||||
 | 
			
		||||
        After calling this req_to_install will only have satisfied_by set to
 | 
			
		||||
        None if the req_to_install is to be upgraded/reinstalled etc. Any
 | 
			
		||||
        other value will be a dist recording the current thing installed that
 | 
			
		||||
        satisfies the requirement.
 | 
			
		||||
 | 
			
		||||
        Note that for vcs urls and the like we can't assess skipping in this
 | 
			
		||||
        routine - we simply identify that we need to pull the thing down,
 | 
			
		||||
        then later on it is pulled down and introspected to assess upgrade/
 | 
			
		||||
        reinstalls etc.
 | 
			
		||||
 | 
			
		||||
        :return: A text reason for why it was skipped, or None.
 | 
			
		||||
        """
 | 
			
		||||
        if self.ignore_installed:
 | 
			
		||||
            return None
 | 
			
		||||
 | 
			
		||||
        req_to_install.check_if_exists(self.use_user_site)
 | 
			
		||||
        if not req_to_install.satisfied_by:
 | 
			
		||||
            return None
 | 
			
		||||
 | 
			
		||||
        if self.force_reinstall:
 | 
			
		||||
            self._set_req_to_reinstall(req_to_install)
 | 
			
		||||
            return None
 | 
			
		||||
 | 
			
		||||
        if not self._is_upgrade_allowed(req_to_install):
 | 
			
		||||
            if self.upgrade_strategy == "only-if-needed":
 | 
			
		||||
                return "already satisfied, skipping upgrade"
 | 
			
		||||
            return "already satisfied"
 | 
			
		||||
 | 
			
		||||
        # Check for the possibility of an upgrade.  For link-based
 | 
			
		||||
        # requirements we have to pull the tree down and inspect to assess
 | 
			
		||||
        # the version #, so it's handled way down.
 | 
			
		||||
        if not req_to_install.link:
 | 
			
		||||
            try:
 | 
			
		||||
                self.finder.find_requirement(req_to_install, upgrade=True)
 | 
			
		||||
            except BestVersionAlreadyInstalled:
 | 
			
		||||
                # Then the best version is installed.
 | 
			
		||||
                return "already up-to-date"
 | 
			
		||||
            except DistributionNotFound:
 | 
			
		||||
                # No distribution found, so we squash the error.  It will
 | 
			
		||||
                # be raised later when we re-try later to do the install.
 | 
			
		||||
                # Why don't we just raise here?
 | 
			
		||||
                pass
 | 
			
		||||
 | 
			
		||||
        self._set_req_to_reinstall(req_to_install)
 | 
			
		||||
        return None
 | 
			
		||||
 | 
			
		||||
    def _find_requirement_link(self, req: InstallRequirement) -> Optional[Link]:
 | 
			
		||||
        upgrade = self._is_upgrade_allowed(req)
 | 
			
		||||
        best_candidate = self.finder.find_requirement(req, upgrade)
 | 
			
		||||
        if not best_candidate:
 | 
			
		||||
            return None
 | 
			
		||||
 | 
			
		||||
        # Log a warning per PEP 592 if necessary before returning.
 | 
			
		||||
        link = best_candidate.link
 | 
			
		||||
        if link.is_yanked:
 | 
			
		||||
            reason = link.yanked_reason or "<none given>"
 | 
			
		||||
            msg = (
 | 
			
		||||
                # Mark this as a unicode string to prevent
 | 
			
		||||
                # "UnicodeEncodeError: 'ascii' codec can't encode character"
 | 
			
		||||
                # in Python 2 when the reason contains non-ascii characters.
 | 
			
		||||
                "The candidate selected for download or install is a "
 | 
			
		||||
                "yanked version: {candidate}\n"
 | 
			
		||||
                "Reason for being yanked: {reason}"
 | 
			
		||||
            ).format(candidate=best_candidate, reason=reason)
 | 
			
		||||
            logger.warning(msg)
 | 
			
		||||
 | 
			
		||||
        return link
 | 
			
		||||
 | 
			
		||||
    def _populate_link(self, req: InstallRequirement) -> None:
 | 
			
		||||
        """Ensure that if a link can be found for this, that it is found.
 | 
			
		||||
 | 
			
		||||
        Note that req.link may still be None - if the requirement is already
 | 
			
		||||
        installed and not needed to be upgraded based on the return value of
 | 
			
		||||
        _is_upgrade_allowed().
 | 
			
		||||
 | 
			
		||||
        If preparer.require_hashes is True, don't use the wheel cache, because
 | 
			
		||||
        cached wheels, always built locally, have different hashes than the
 | 
			
		||||
        files downloaded from the index server and thus throw false hash
 | 
			
		||||
        mismatches. Furthermore, cached wheels at present have undeterministic
 | 
			
		||||
        contents due to file modification times.
 | 
			
		||||
        """
 | 
			
		||||
        if req.link is None:
 | 
			
		||||
            req.link = self._find_requirement_link(req)
 | 
			
		||||
 | 
			
		||||
        if self.wheel_cache is None or self.preparer.require_hashes:
 | 
			
		||||
            return
 | 
			
		||||
        cache_entry = self.wheel_cache.get_cache_entry(
 | 
			
		||||
            link=req.link,
 | 
			
		||||
            package_name=req.name,
 | 
			
		||||
            supported_tags=get_supported(),
 | 
			
		||||
        )
 | 
			
		||||
        if cache_entry is not None:
 | 
			
		||||
            logger.debug("Using cached wheel link: %s", cache_entry.link)
 | 
			
		||||
            if req.link is req.original_link and cache_entry.persistent:
 | 
			
		||||
                req.original_link_is_in_wheel_cache = True
 | 
			
		||||
            if cache_entry.origin is not None:
 | 
			
		||||
                req.download_info = cache_entry.origin
 | 
			
		||||
            else:
 | 
			
		||||
                # Legacy cache entry that does not have origin.json.
 | 
			
		||||
                # download_info may miss the archive_info.hash field.
 | 
			
		||||
                req.download_info = direct_url_from_link(
 | 
			
		||||
                    req.link, link_is_in_wheel_cache=cache_entry.persistent
 | 
			
		||||
                )
 | 
			
		||||
            req.link = cache_entry.link
 | 
			
		||||
 | 
			
		||||
    def _get_dist_for(self, req: InstallRequirement) -> BaseDistribution:
 | 
			
		||||
        """Takes a InstallRequirement and returns a single AbstractDist \
 | 
			
		||||
        representing a prepared variant of the same.
 | 
			
		||||
        """
 | 
			
		||||
        if req.editable:
 | 
			
		||||
            return self.preparer.prepare_editable_requirement(req)
 | 
			
		||||
 | 
			
		||||
        # satisfied_by is only evaluated by calling _check_skip_installed,
 | 
			
		||||
        # so it must be None here.
 | 
			
		||||
        assert req.satisfied_by is None
 | 
			
		||||
        skip_reason = self._check_skip_installed(req)
 | 
			
		||||
 | 
			
		||||
        if req.satisfied_by:
 | 
			
		||||
            return self.preparer.prepare_installed_requirement(req, skip_reason)
 | 
			
		||||
 | 
			
		||||
        # We eagerly populate the link, since that's our "legacy" behavior.
 | 
			
		||||
        self._populate_link(req)
 | 
			
		||||
        dist = self.preparer.prepare_linked_requirement(req)
 | 
			
		||||
 | 
			
		||||
        # NOTE
 | 
			
		||||
        # The following portion is for determining if a certain package is
 | 
			
		||||
        # going to be re-installed/upgraded or not and reporting to the user.
 | 
			
		||||
        # This should probably get cleaned up in a future refactor.
 | 
			
		||||
 | 
			
		||||
        # req.req is only avail after unpack for URL
 | 
			
		||||
        # pkgs repeat check_if_exists to uninstall-on-upgrade
 | 
			
		||||
        # (#14)
 | 
			
		||||
        if not self.ignore_installed:
 | 
			
		||||
            req.check_if_exists(self.use_user_site)
 | 
			
		||||
 | 
			
		||||
        if req.satisfied_by:
 | 
			
		||||
            should_modify = (
 | 
			
		||||
                self.upgrade_strategy != "to-satisfy-only"
 | 
			
		||||
                or self.force_reinstall
 | 
			
		||||
                or self.ignore_installed
 | 
			
		||||
                or req.link.scheme == "file"
 | 
			
		||||
            )
 | 
			
		||||
            if should_modify:
 | 
			
		||||
                self._set_req_to_reinstall(req)
 | 
			
		||||
            else:
 | 
			
		||||
                logger.info(
 | 
			
		||||
                    "Requirement already satisfied (use --upgrade to upgrade): %s",
 | 
			
		||||
                    req,
 | 
			
		||||
                )
 | 
			
		||||
        return dist
 | 
			
		||||
 | 
			
		||||
    def _resolve_one(
 | 
			
		||||
        self,
 | 
			
		||||
        requirement_set: RequirementSet,
 | 
			
		||||
        req_to_install: InstallRequirement,
 | 
			
		||||
    ) -> List[InstallRequirement]:
 | 
			
		||||
        """Prepare a single requirements file.
 | 
			
		||||
 | 
			
		||||
        :return: A list of additional InstallRequirements to also install.
 | 
			
		||||
        """
 | 
			
		||||
        # Tell user what we are doing for this requirement:
 | 
			
		||||
        # obtain (editable), skipping, processing (local url), collecting
 | 
			
		||||
        # (remote url or package name)
 | 
			
		||||
        if req_to_install.constraint or req_to_install.prepared:
 | 
			
		||||
            return []
 | 
			
		||||
 | 
			
		||||
        req_to_install.prepared = True
 | 
			
		||||
 | 
			
		||||
        # Parse and return dependencies
 | 
			
		||||
        dist = self._get_dist_for(req_to_install)
 | 
			
		||||
        # This will raise UnsupportedPythonVersion if the given Python
 | 
			
		||||
        # version isn't compatible with the distribution's Requires-Python.
 | 
			
		||||
        _check_dist_requires_python(
 | 
			
		||||
            dist,
 | 
			
		||||
            version_info=self._py_version_info,
 | 
			
		||||
            ignore_requires_python=self.ignore_requires_python,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        more_reqs: List[InstallRequirement] = []
 | 
			
		||||
 | 
			
		||||
        def add_req(subreq: Requirement, extras_requested: Iterable[str]) -> None:
 | 
			
		||||
            # This idiosyncratically converts the Requirement to str and let
 | 
			
		||||
            # make_install_req then parse it again into Requirement. But this is
 | 
			
		||||
            # the legacy resolver so I'm just not going to bother refactoring.
 | 
			
		||||
            sub_install_req = self._make_install_req(str(subreq), req_to_install)
 | 
			
		||||
            parent_req_name = req_to_install.name
 | 
			
		||||
            to_scan_again, add_to_parent = self._add_requirement_to_set(
 | 
			
		||||
                requirement_set,
 | 
			
		||||
                sub_install_req,
 | 
			
		||||
                parent_req_name=parent_req_name,
 | 
			
		||||
                extras_requested=extras_requested,
 | 
			
		||||
            )
 | 
			
		||||
            if parent_req_name and add_to_parent:
 | 
			
		||||
                self._discovered_dependencies[parent_req_name].append(add_to_parent)
 | 
			
		||||
            more_reqs.extend(to_scan_again)
 | 
			
		||||
 | 
			
		||||
        with indent_log():
 | 
			
		||||
            # We add req_to_install before its dependencies, so that we
 | 
			
		||||
            # can refer to it when adding dependencies.
 | 
			
		||||
            if not requirement_set.has_requirement(req_to_install.name):
 | 
			
		||||
                # 'unnamed' requirements will get added here
 | 
			
		||||
                # 'unnamed' requirements can only come from being directly
 | 
			
		||||
                # provided by the user.
 | 
			
		||||
                assert req_to_install.user_supplied
 | 
			
		||||
                self._add_requirement_to_set(
 | 
			
		||||
                    requirement_set, req_to_install, parent_req_name=None
 | 
			
		||||
                )
 | 
			
		||||
 | 
			
		||||
            if not self.ignore_dependencies:
 | 
			
		||||
                if req_to_install.extras:
 | 
			
		||||
                    logger.debug(
 | 
			
		||||
                        "Installing extra requirements: %r",
 | 
			
		||||
                        ",".join(req_to_install.extras),
 | 
			
		||||
                    )
 | 
			
		||||
                missing_requested = sorted(
 | 
			
		||||
                    set(req_to_install.extras) - set(dist.iter_provided_extras())
 | 
			
		||||
                )
 | 
			
		||||
                for missing in missing_requested:
 | 
			
		||||
                    logger.warning(
 | 
			
		||||
                        "%s %s does not provide the extra '%s'",
 | 
			
		||||
                        dist.raw_name,
 | 
			
		||||
                        dist.version,
 | 
			
		||||
                        missing,
 | 
			
		||||
                    )
 | 
			
		||||
 | 
			
		||||
                available_requested = sorted(
 | 
			
		||||
                    set(dist.iter_provided_extras()) & set(req_to_install.extras)
 | 
			
		||||
                )
 | 
			
		||||
                for subreq in dist.iter_dependencies(available_requested):
 | 
			
		||||
                    add_req(subreq, extras_requested=available_requested)
 | 
			
		||||
 | 
			
		||||
        return more_reqs
 | 
			
		||||
 | 
			
		||||
    def get_installation_order(
 | 
			
		||||
        self, req_set: RequirementSet
 | 
			
		||||
    ) -> List[InstallRequirement]:
 | 
			
		||||
        """Create the installation order.
 | 
			
		||||
 | 
			
		||||
        The installation order is topological - requirements are installed
 | 
			
		||||
        before the requiring thing. We break cycles at an arbitrary point,
 | 
			
		||||
        and make no other guarantees.
 | 
			
		||||
        """
 | 
			
		||||
        # The current implementation, which we may change at any point
 | 
			
		||||
        # installs the user specified things in the order given, except when
 | 
			
		||||
        # dependencies must come earlier to achieve topological order.
 | 
			
		||||
        order = []
 | 
			
		||||
        ordered_reqs: Set[InstallRequirement] = set()
 | 
			
		||||
 | 
			
		||||
        def schedule(req: InstallRequirement) -> None:
 | 
			
		||||
            if req.satisfied_by or req in ordered_reqs:
 | 
			
		||||
                return
 | 
			
		||||
            if req.constraint:
 | 
			
		||||
                return
 | 
			
		||||
            ordered_reqs.add(req)
 | 
			
		||||
            for dep in self._discovered_dependencies[req.name]:
 | 
			
		||||
                schedule(dep)
 | 
			
		||||
            order.append(req)
 | 
			
		||||
 | 
			
		||||
        for install_req in req_set.requirements.values():
 | 
			
		||||
            schedule(install_req)
 | 
			
		||||
        return order
 | 
			
		||||
		Reference in New Issue
	
	Block a user