2020-05-18 10:35:09 +02:00
|
|
|
import collections
|
2020-05-21 12:09:35 +02:00
|
|
|
import logging
|
2020-05-18 10:35:09 +02:00
|
|
|
|
|
|
|
from pip._vendor import six
|
2020-04-07 13:02:39 +02:00
|
|
|
from pip._vendor.packaging.utils import canonicalize_name
|
2020-04-02 13:49:45 +02:00
|
|
|
|
2020-04-10 16:56:53 +02:00
|
|
|
from pip._internal.exceptions import (
|
2020-06-04 14:15:30 +02:00
|
|
|
DistributionNotFound,
|
2020-04-10 16:56:53 +02:00
|
|
|
InstallationError,
|
|
|
|
UnsupportedPythonVersion,
|
2020-06-02 05:02:08 +02:00
|
|
|
UnsupportedWheel,
|
2020-04-10 16:56:53 +02:00
|
|
|
)
|
2020-06-02 05:02:08 +02:00
|
|
|
from pip._internal.models.wheel import Wheel
|
2020-05-07 12:06:16 +02:00
|
|
|
from pip._internal.utils.compatibility_tags import get_supported
|
2020-05-19 11:04:15 +02:00
|
|
|
from pip._internal.utils.hashes import Hashes
|
2020-04-08 11:13:13 +02:00
|
|
|
from pip._internal.utils.misc import (
|
|
|
|
dist_in_site_packages,
|
|
|
|
dist_in_usersite,
|
|
|
|
get_installed_distributions,
|
|
|
|
)
|
2020-03-26 20:14:51 +01:00
|
|
|
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
|
2020-04-08 11:13:13 +02:00
|
|
|
from pip._internal.utils.virtualenv import running_under_virtualenv
|
2020-03-26 20:14:51 +01:00
|
|
|
|
2020-04-02 13:49:45 +02:00
|
|
|
from .candidates import (
|
2020-04-02 19:27:34 +02:00
|
|
|
AlreadyInstalledCandidate,
|
2020-04-04 11:51:43 +02:00
|
|
|
EditableCandidate,
|
2020-04-02 13:49:45 +02:00
|
|
|
ExtrasCandidate,
|
|
|
|
LinkCandidate,
|
|
|
|
RequiresPythonCandidate,
|
|
|
|
)
|
2020-04-01 12:14:36 +02:00
|
|
|
from .requirements import (
|
|
|
|
ExplicitRequirement,
|
2020-04-10 16:56:53 +02:00
|
|
|
RequiresPythonRequirement,
|
2020-04-01 12:14:36 +02:00
|
|
|
SpecifierRequirement,
|
|
|
|
)
|
2020-03-26 20:14:51 +01:00
|
|
|
|
|
|
|
if MYPY_CHECK_RUNNING:
|
2020-05-19 11:04:15 +02:00
|
|
|
from typing import (
|
|
|
|
FrozenSet,
|
|
|
|
Dict,
|
|
|
|
Iterable,
|
|
|
|
List,
|
|
|
|
Optional,
|
|
|
|
Sequence,
|
|
|
|
Set,
|
|
|
|
Tuple,
|
|
|
|
TypeVar,
|
|
|
|
)
|
2020-04-01 12:14:36 +02:00
|
|
|
|
|
|
|
from pip._vendor.packaging.specifiers import SpecifierSet
|
2020-04-03 12:23:35 +02:00
|
|
|
from pip._vendor.packaging.version import _BaseVersion
|
2020-04-02 13:49:45 +02:00
|
|
|
from pip._vendor.pkg_resources import Distribution
|
2020-04-10 16:56:53 +02:00
|
|
|
from pip._vendor.resolvelib import ResolutionImpossible
|
2020-03-26 20:14:51 +01:00
|
|
|
|
2020-05-07 12:06:16 +02:00
|
|
|
from pip._internal.cache import CacheEntry, WheelCache
|
2020-03-26 20:14:51 +01:00
|
|
|
from pip._internal.index.package_finder import PackageFinder
|
|
|
|
from pip._internal.models.link import Link
|
|
|
|
from pip._internal.operations.prepare import RequirementPreparer
|
|
|
|
from pip._internal.req.req_install import InstallRequirement
|
|
|
|
from pip._internal.resolution.base import InstallRequirementProvider
|
|
|
|
|
|
|
|
from .base import Candidate, Requirement
|
2020-04-04 12:12:38 +02:00
|
|
|
from .candidates import BaseCandidate
|
2020-03-26 20:14:51 +01:00
|
|
|
|
2020-04-03 14:05:05 +02:00
|
|
|
C = TypeVar("C")
|
|
|
|
Cache = Dict[Link, C]
|
2020-05-18 11:15:04 +02:00
|
|
|
VersionCandidates = Dict[_BaseVersion, Candidate]
|
2020-04-03 14:05:05 +02:00
|
|
|
|
2020-03-26 20:14:51 +01:00
|
|
|
|
2020-05-21 12:09:35 +02:00
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
2020-03-26 20:14:51 +01:00
|
|
|
class Factory(object):
|
|
|
|
def __init__(
|
|
|
|
self,
|
|
|
|
finder, # type: PackageFinder
|
|
|
|
preparer, # type: RequirementPreparer
|
|
|
|
make_install_req, # type: InstallRequirementProvider
|
2020-05-07 12:06:16 +02:00
|
|
|
wheel_cache, # type: Optional[WheelCache]
|
2020-04-08 11:13:13 +02:00
|
|
|
use_user_site, # type: bool
|
2020-04-06 17:13:01 +02:00
|
|
|
force_reinstall, # type: bool
|
2020-04-02 13:49:45 +02:00
|
|
|
ignore_installed, # type: bool
|
2020-04-01 12:14:36 +02:00
|
|
|
ignore_requires_python, # type: bool
|
|
|
|
py_version_info=None, # type: Optional[Tuple[int, ...]]
|
2020-03-26 20:14:51 +01:00
|
|
|
):
|
|
|
|
# type: (...) -> None
|
2020-05-19 11:04:15 +02:00
|
|
|
self._finder = finder
|
2020-03-27 15:40:05 +01:00
|
|
|
self.preparer = preparer
|
2020-05-07 12:06:16 +02:00
|
|
|
self._wheel_cache = wheel_cache
|
2020-04-01 12:14:36 +02:00
|
|
|
self._python_candidate = RequiresPythonCandidate(py_version_info)
|
2020-04-01 10:14:44 +02:00
|
|
|
self._make_install_req_from_spec = make_install_req
|
2020-04-08 11:13:13 +02:00
|
|
|
self._use_user_site = use_user_site
|
2020-04-06 17:13:01 +02:00
|
|
|
self._force_reinstall = force_reinstall
|
2020-04-02 13:49:45 +02:00
|
|
|
self._ignore_requires_python = ignore_requires_python
|
2020-04-06 16:40:24 +02:00
|
|
|
|
2020-04-03 14:05:05 +02:00
|
|
|
self._link_candidate_cache = {} # type: Cache[LinkCandidate]
|
2020-04-04 11:51:43 +02:00
|
|
|
self._editable_candidate_cache = {} # type: Cache[EditableCandidate]
|
2020-03-26 20:14:51 +01:00
|
|
|
|
2020-04-06 16:40:24 +02:00
|
|
|
if not ignore_installed:
|
|
|
|
self._installed_dists = {
|
2020-04-13 12:05:24 +02:00
|
|
|
canonicalize_name(dist.project_name): dist
|
2020-04-06 16:40:24 +02:00
|
|
|
for dist in get_installed_distributions()
|
|
|
|
}
|
|
|
|
else:
|
|
|
|
self._installed_dists = {}
|
|
|
|
|
2020-06-23 15:39:12 +02:00
|
|
|
@property
|
|
|
|
def force_reinstall(self):
|
|
|
|
# type: () -> bool
|
|
|
|
return self._force_reinstall
|
|
|
|
|
2020-04-02 13:49:45 +02:00
|
|
|
def _make_candidate_from_dist(
|
|
|
|
self,
|
|
|
|
dist, # type: Distribution
|
2020-05-19 11:04:15 +02:00
|
|
|
extras, # type: FrozenSet[str]
|
2020-05-27 17:54:00 +02:00
|
|
|
template, # type: InstallRequirement
|
2020-04-02 13:49:45 +02:00
|
|
|
):
|
|
|
|
# type: (...) -> Candidate
|
2020-05-27 17:54:00 +02:00
|
|
|
base = AlreadyInstalledCandidate(dist, template, factory=self)
|
2020-04-02 13:49:45 +02:00
|
|
|
if extras:
|
|
|
|
return ExtrasCandidate(base, extras)
|
|
|
|
return base
|
|
|
|
|
|
|
|
def _make_candidate_from_link(
|
2020-03-26 20:14:51 +01:00
|
|
|
self,
|
2020-05-07 11:12:15 +02:00
|
|
|
link, # type: Link
|
2020-05-27 14:49:28 +02:00
|
|
|
extras, # type: FrozenSet[str]
|
2020-05-27 17:54:00 +02:00
|
|
|
template, # type: InstallRequirement
|
2020-05-07 11:12:15 +02:00
|
|
|
name, # type: Optional[str]
|
|
|
|
version, # type: Optional[_BaseVersion]
|
2020-03-26 20:14:51 +01:00
|
|
|
):
|
|
|
|
# type: (...) -> Candidate
|
2020-04-10 13:53:18 +02:00
|
|
|
# TODO: Check already installed candidate, and use it if the link and
|
|
|
|
# editable flag match.
|
2020-05-27 17:54:00 +02:00
|
|
|
if template.editable:
|
2020-04-04 11:51:43 +02:00
|
|
|
if link not in self._editable_candidate_cache:
|
|
|
|
self._editable_candidate_cache[link] = EditableCandidate(
|
2020-05-27 17:54:00 +02:00
|
|
|
link, template, factory=self, name=name, version=version,
|
2020-04-04 11:51:43 +02:00
|
|
|
)
|
2020-04-04 12:12:38 +02:00
|
|
|
base = self._editable_candidate_cache[link] # type: BaseCandidate
|
2020-04-04 11:51:43 +02:00
|
|
|
else:
|
|
|
|
if link not in self._link_candidate_cache:
|
|
|
|
self._link_candidate_cache[link] = LinkCandidate(
|
2020-05-27 17:54:00 +02:00
|
|
|
link, template, factory=self, name=name, version=version,
|
2020-04-04 11:51:43 +02:00
|
|
|
)
|
|
|
|
base = self._link_candidate_cache[link]
|
2020-03-26 20:14:51 +01:00
|
|
|
if extras:
|
|
|
|
return ExtrasCandidate(base, extras)
|
|
|
|
return base
|
|
|
|
|
2020-05-19 11:04:15 +02:00
|
|
|
def _iter_found_candidates(
|
|
|
|
self,
|
|
|
|
ireqs, # type: Sequence[InstallRequirement]
|
|
|
|
specifier, # type: SpecifierSet
|
|
|
|
):
|
|
|
|
# type: (...) -> Iterable[Candidate]
|
|
|
|
if not ireqs:
|
|
|
|
return ()
|
|
|
|
|
|
|
|
# The InstallRequirement implementation requires us to give it a
|
2020-05-27 17:54:00 +02:00
|
|
|
# "template". Here we just choose the first requirement to represent
|
|
|
|
# all of them.
|
2020-05-19 11:04:15 +02:00
|
|
|
# Hopefully the Project model can correct this mismatch in the future.
|
2020-05-27 17:54:00 +02:00
|
|
|
template = ireqs[0]
|
|
|
|
name = canonicalize_name(template.req.name)
|
2020-05-19 11:04:15 +02:00
|
|
|
|
|
|
|
hashes = Hashes()
|
|
|
|
extras = frozenset() # type: FrozenSet[str]
|
|
|
|
for ireq in ireqs:
|
|
|
|
specifier &= ireq.req.specifier
|
|
|
|
hashes |= ireq.hashes(trust_internet=False)
|
2020-05-22 10:52:59 +02:00
|
|
|
extras |= frozenset(ireq.extras)
|
2020-05-14 12:33:30 +02:00
|
|
|
|
|
|
|
# We use this to ensure that we only yield a single candidate for
|
|
|
|
# each version (the finder's preferred one for that version). The
|
|
|
|
# requirement needs to return only one candidate per version, so we
|
|
|
|
# implement that logic here so that requirements using this helper
|
|
|
|
# don't all have to do the same thing later.
|
2020-05-18 11:15:04 +02:00
|
|
|
candidates = collections.OrderedDict() # type: VersionCandidates
|
2020-05-13 18:52:09 +02:00
|
|
|
|
2020-06-04 18:46:39 +02:00
|
|
|
# Get the installed version, if it matches, unless the user
|
2020-05-13 18:52:09 +02:00
|
|
|
# specified `--force-reinstall`, when we want the version from
|
|
|
|
# the index instead.
|
2020-05-18 10:35:09 +02:00
|
|
|
installed_version = None
|
2020-06-04 18:46:39 +02:00
|
|
|
installed_candidate = None
|
2020-05-13 18:52:09 +02:00
|
|
|
if not self._force_reinstall and name in self._installed_dists:
|
|
|
|
installed_dist = self._installed_dists[name]
|
|
|
|
installed_version = installed_dist.parsed_version
|
2020-05-19 11:04:15 +02:00
|
|
|
if specifier.contains(installed_version, prereleases=True):
|
2020-06-04 18:46:39 +02:00
|
|
|
installed_candidate = self._make_candidate_from_dist(
|
2020-05-13 18:52:09 +02:00
|
|
|
dist=installed_dist,
|
|
|
|
extras=extras,
|
2020-05-27 17:54:00 +02:00
|
|
|
template=template,
|
2020-05-13 18:52:09 +02:00
|
|
|
)
|
2020-04-10 13:53:18 +02:00
|
|
|
|
2020-05-19 11:04:15 +02:00
|
|
|
found = self._finder.find_best_candidate(
|
|
|
|
project_name=name,
|
|
|
|
specifier=specifier,
|
|
|
|
hashes=hashes,
|
2020-04-06 17:13:01 +02:00
|
|
|
)
|
2020-04-10 13:53:18 +02:00
|
|
|
for ican in found.iter_applicable():
|
2020-06-04 18:46:39 +02:00
|
|
|
if ican.version == installed_version and installed_candidate:
|
|
|
|
candidate = installed_candidate
|
|
|
|
else:
|
|
|
|
candidate = self._make_candidate_from_link(
|
|
|
|
link=ican.link,
|
|
|
|
extras=extras,
|
|
|
|
template=template,
|
|
|
|
name=name,
|
|
|
|
version=ican.version,
|
|
|
|
)
|
2020-05-18 11:15:04 +02:00
|
|
|
candidates[ican.version] = candidate
|
2020-05-18 10:35:09 +02:00
|
|
|
|
2020-06-04 18:46:39 +02:00
|
|
|
# Yield the installed version even if it is not found on the index.
|
|
|
|
if installed_version and installed_candidate:
|
|
|
|
candidates[installed_version] = installed_candidate
|
|
|
|
|
2020-05-18 11:15:04 +02:00
|
|
|
return six.itervalues(candidates)
|
2020-04-02 13:49:45 +02:00
|
|
|
|
2020-05-19 11:04:15 +02:00
|
|
|
def find_candidates(self, requirements, constraint):
|
|
|
|
# type: (Sequence[Requirement], SpecifierSet) -> Iterable[Candidate]
|
|
|
|
explicit_candidates = set() # type: Set[Candidate]
|
|
|
|
ireqs = [] # type: List[InstallRequirement]
|
|
|
|
for req in requirements:
|
|
|
|
cand, ireq = req.get_candidate_lookup()
|
|
|
|
if cand is not None:
|
|
|
|
explicit_candidates.add(cand)
|
|
|
|
if ireq is not None:
|
|
|
|
ireqs.append(ireq)
|
|
|
|
|
|
|
|
# If none of the requirements want an explicit candidate, we can ask
|
|
|
|
# the finder for candidates.
|
|
|
|
if not explicit_candidates:
|
|
|
|
return self._iter_found_candidates(ireqs, constraint)
|
|
|
|
|
|
|
|
if constraint:
|
|
|
|
name = explicit_candidates.pop().name
|
|
|
|
raise InstallationError(
|
|
|
|
"Could not satisfy constraints for {!r}: installation from "
|
|
|
|
"path or url cannot be constrained to a version".format(name)
|
|
|
|
)
|
|
|
|
|
|
|
|
return (
|
|
|
|
c for c in explicit_candidates
|
|
|
|
if all(req.is_satisfied_by(c) for req in requirements)
|
|
|
|
)
|
|
|
|
|
2020-06-02 05:55:12 +02:00
|
|
|
def make_requirement_from_install_req(self, ireq, requested_extras):
|
|
|
|
# type: (InstallRequirement, Iterable[str]) -> Optional[Requirement]
|
|
|
|
if not ireq.match_markers(requested_extras):
|
|
|
|
logger.info(
|
|
|
|
"Ignoring %s: markers '%s' don't match your environment",
|
|
|
|
ireq.name, ireq.markers,
|
|
|
|
)
|
|
|
|
return None
|
2020-05-07 11:12:15 +02:00
|
|
|
if not ireq.link:
|
2020-05-19 11:04:15 +02:00
|
|
|
return SpecifierRequirement(ireq)
|
2020-06-02 05:02:08 +02:00
|
|
|
if ireq.link.is_wheel:
|
|
|
|
wheel = Wheel(ireq.link.filename)
|
|
|
|
if not wheel.supported(self._finder.target_python.get_tags()):
|
|
|
|
msg = "{} is not a supported wheel on this platform.".format(
|
|
|
|
wheel.filename,
|
|
|
|
)
|
|
|
|
raise UnsupportedWheel(msg)
|
2020-05-07 11:12:15 +02:00
|
|
|
cand = self._make_candidate_from_link(
|
|
|
|
ireq.link,
|
2020-05-27 14:49:28 +02:00
|
|
|
extras=frozenset(ireq.extras),
|
2020-05-27 17:54:00 +02:00
|
|
|
template=ireq,
|
2020-05-07 11:12:15 +02:00
|
|
|
name=canonicalize_name(ireq.name) if ireq.name else None,
|
|
|
|
version=None,
|
|
|
|
)
|
|
|
|
return self.make_requirement_from_candidate(cand)
|
2020-04-01 10:14:44 +02:00
|
|
|
|
2020-05-21 17:06:51 +02:00
|
|
|
def make_requirement_from_candidate(self, candidate):
|
2020-05-21 17:48:44 +02:00
|
|
|
# type: (Candidate) -> ExplicitRequirement
|
2020-05-21 17:06:51 +02:00
|
|
|
return ExplicitRequirement(candidate)
|
|
|
|
|
2020-06-02 05:59:03 +02:00
|
|
|
def make_requirement_from_spec(
|
2020-05-21 10:14:26 +02:00
|
|
|
self,
|
|
|
|
specifier, # type: str
|
|
|
|
comes_from, # type: InstallRequirement
|
|
|
|
requested_extras=(), # type: Iterable[str]
|
|
|
|
):
|
|
|
|
# type: (...) -> Optional[Requirement]
|
|
|
|
ireq = self._make_install_req_from_spec(specifier, comes_from)
|
2020-06-02 05:55:12 +02:00
|
|
|
return self.make_requirement_from_install_req(ireq, requested_extras)
|
2020-05-21 10:14:26 +02:00
|
|
|
|
2020-04-01 12:14:36 +02:00
|
|
|
def make_requires_python_requirement(self, specifier):
|
|
|
|
# type: (Optional[SpecifierSet]) -> Optional[Requirement]
|
|
|
|
if self._ignore_requires_python or specifier is None:
|
|
|
|
return None
|
2020-04-10 16:56:53 +02:00
|
|
|
return RequiresPythonRequirement(specifier, self._python_candidate)
|
2020-04-06 17:13:01 +02:00
|
|
|
|
2020-05-07 12:06:16 +02:00
|
|
|
def get_wheel_cache_entry(self, link, name):
|
|
|
|
# type: (Link, Optional[str]) -> Optional[CacheEntry]
|
|
|
|
"""Look up the link in the wheel cache.
|
|
|
|
|
|
|
|
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
|
2020-05-22 09:17:53 +02:00
|
|
|
nondeterministic contents due to file modification times.
|
2020-05-07 12:06:16 +02:00
|
|
|
"""
|
|
|
|
if self._wheel_cache is None or self.preparer.require_hashes:
|
|
|
|
return None
|
|
|
|
return self._wheel_cache.get_cache_entry(
|
|
|
|
link=link,
|
|
|
|
package_name=name,
|
|
|
|
supported_tags=get_supported(),
|
|
|
|
)
|
|
|
|
|
2020-06-23 15:39:12 +02:00
|
|
|
def get_dist_to_uninstall(self, candidate):
|
|
|
|
# type: (Candidate) -> Optional[Distribution]
|
2020-04-06 17:13:01 +02:00
|
|
|
# TODO: Are there more cases this needs to return True? Editable?
|
2020-04-08 11:13:13 +02:00
|
|
|
dist = self._installed_dists.get(candidate.name)
|
|
|
|
if dist is None: # Not installed, no uninstallation required.
|
2020-06-23 15:39:12 +02:00
|
|
|
return None
|
2020-04-08 11:13:13 +02:00
|
|
|
|
|
|
|
# We're installing into global site. The current installation must
|
|
|
|
# be uninstalled, no matter it's in global or user site, because the
|
|
|
|
# user site installation has precedence over global.
|
|
|
|
if not self._use_user_site:
|
2020-06-23 15:39:12 +02:00
|
|
|
return dist
|
2020-04-08 11:13:13 +02:00
|
|
|
|
|
|
|
# We're installing into user site. Remove the user site installation.
|
|
|
|
if dist_in_usersite(dist):
|
2020-06-23 15:39:12 +02:00
|
|
|
return dist
|
2020-04-08 11:13:13 +02:00
|
|
|
|
|
|
|
# We're installing into user site, but the installed incompatible
|
|
|
|
# package is in global site. We can't uninstall that, and would let
|
|
|
|
# the new user installation to "shadow" it. But shadowing won't work
|
|
|
|
# in virtual environments, so we error out.
|
|
|
|
if running_under_virtualenv() and dist_in_site_packages(dist):
|
|
|
|
raise InstallationError(
|
|
|
|
"Will not install to the user site because it will "
|
|
|
|
"lack sys.path precedence to {} in {}".format(
|
|
|
|
dist.project_name, dist.location,
|
|
|
|
)
|
|
|
|
)
|
2020-06-23 15:39:12 +02:00
|
|
|
return None
|
2020-04-10 16:56:53 +02:00
|
|
|
|
|
|
|
def _report_requires_python_error(
|
|
|
|
self,
|
|
|
|
requirement, # type: RequiresPythonRequirement
|
2020-05-27 17:54:00 +02:00
|
|
|
template, # type: Candidate
|
2020-04-10 16:56:53 +02:00
|
|
|
):
|
|
|
|
# type: (...) -> UnsupportedPythonVersion
|
2020-05-27 17:54:00 +02:00
|
|
|
message_format = (
|
2020-04-10 16:56:53 +02:00
|
|
|
"Package {package!r} requires a different Python: "
|
|
|
|
"{version} not in {specifier!r}"
|
|
|
|
)
|
2020-05-27 17:54:00 +02:00
|
|
|
message = message_format.format(
|
|
|
|
package=template.name,
|
2020-04-10 16:56:53 +02:00
|
|
|
version=self._python_candidate.version,
|
|
|
|
specifier=str(requirement.specifier),
|
|
|
|
)
|
|
|
|
return UnsupportedPythonVersion(message)
|
|
|
|
|
|
|
|
def get_installation_error(self, e):
|
2020-06-04 14:15:30 +02:00
|
|
|
# type: (ResolutionImpossible) -> InstallationError
|
|
|
|
|
|
|
|
assert e.causes, "Installation error reported with no cause"
|
|
|
|
|
|
|
|
# If one of the things we can't solve is "we need Python X.Y",
|
|
|
|
# that is what we report.
|
2020-04-10 16:56:53 +02:00
|
|
|
for cause in e.causes:
|
|
|
|
if isinstance(cause.requirement, RequiresPythonRequirement):
|
|
|
|
return self._report_requires_python_error(
|
|
|
|
cause.requirement,
|
|
|
|
cause.parent,
|
|
|
|
)
|
2020-06-04 14:15:30 +02:00
|
|
|
|
|
|
|
# Otherwise, we have a set of causes which can't all be satisfied
|
|
|
|
# at once.
|
|
|
|
|
|
|
|
# The simplest case is when we have *one* cause that can't be
|
|
|
|
# satisfied. We just report that case.
|
|
|
|
if len(e.causes) == 1:
|
|
|
|
req, parent = e.causes[0]
|
2020-06-17 17:53:30 +02:00
|
|
|
if parent is None:
|
|
|
|
req_disp = str(req)
|
|
|
|
else:
|
|
|
|
req_disp = '{} (from {})'.format(req, parent.name)
|
2020-06-04 14:15:30 +02:00
|
|
|
logger.critical(
|
2020-06-17 17:53:30 +02:00
|
|
|
"Could not find a version that satisfies the requirement %s",
|
|
|
|
req_disp,
|
2020-06-04 14:15:30 +02:00
|
|
|
)
|
|
|
|
return DistributionNotFound(
|
|
|
|
'No matching distribution found for {}'.format(req)
|
|
|
|
)
|
|
|
|
|
|
|
|
# OK, we now have a list of requirements that can't all be
|
|
|
|
# satisfied at once.
|
|
|
|
|
|
|
|
# A couple of formatting helpers
|
|
|
|
def text_join(parts):
|
|
|
|
# type: (List[str]) -> str
|
|
|
|
if len(parts) == 1:
|
|
|
|
return parts[0]
|
|
|
|
|
|
|
|
return ", ".join(parts[:-1]) + " and " + parts[-1]
|
|
|
|
|
|
|
|
def readable_form(cand):
|
|
|
|
# type: (Candidate) -> str
|
|
|
|
return "{} {}".format(cand.name, cand.version)
|
|
|
|
|
2020-06-11 15:22:12 +02:00
|
|
|
triggers = []
|
|
|
|
for req, parent in e.causes:
|
|
|
|
if parent is None:
|
|
|
|
# This is a root requirement, so we can report it directly
|
|
|
|
trigger = req.format_for_error()
|
|
|
|
else:
|
|
|
|
ireq = parent.get_install_requirement()
|
|
|
|
if ireq and ireq.comes_from:
|
|
|
|
trigger = "{}".format(
|
|
|
|
ireq.comes_from.name
|
|
|
|
)
|
|
|
|
else:
|
|
|
|
trigger = "{} {}".format(
|
|
|
|
parent.name,
|
|
|
|
parent.version
|
|
|
|
)
|
|
|
|
triggers.append(trigger)
|
|
|
|
|
|
|
|
if triggers:
|
|
|
|
info = text_join(triggers)
|
2020-06-05 13:56:43 +02:00
|
|
|
else:
|
2020-06-11 15:22:12 +02:00
|
|
|
info = "the requested packages"
|
2020-06-05 13:56:43 +02:00
|
|
|
|
2020-06-11 15:22:12 +02:00
|
|
|
msg = "Cannot install {} because these package versions " \
|
|
|
|
"have conflicting dependencies.".format(info)
|
|
|
|
logger.critical(msg)
|
|
|
|
msg = "\nThe conflict is caused by:"
|
2020-06-04 14:15:30 +02:00
|
|
|
for req, parent in e.causes:
|
|
|
|
msg = msg + "\n "
|
|
|
|
if parent:
|
2020-06-11 15:22:12 +02:00
|
|
|
msg = msg + "{} {} depends on ".format(
|
|
|
|
parent.name,
|
|
|
|
parent.version
|
|
|
|
)
|
2020-06-04 14:15:30 +02:00
|
|
|
else:
|
|
|
|
msg = msg + "The user requested "
|
2020-06-05 13:56:43 +02:00
|
|
|
msg = msg + req.format_for_error()
|
2020-06-04 14:15:30 +02:00
|
|
|
|
|
|
|
msg = msg + "\n\n" + \
|
2020-06-11 15:22:12 +02:00
|
|
|
"To fix this you could try to:\n" + \
|
|
|
|
"1. loosen the range of package versions you've specified\n" + \
|
|
|
|
"2. remove package versions to allow pip attempt to solve " + \
|
|
|
|
"the dependency conflict\n"
|
2020-06-04 14:15:30 +02:00
|
|
|
|
2020-06-11 15:22:12 +02:00
|
|
|
logger.info(msg)
|
2020-06-04 14:15:30 +02:00
|
|
|
|
|
|
|
return DistributionNotFound(
|
2020-06-11 15:22:12 +02:00
|
|
|
"ResolutionImpossible For help visit: "
|
|
|
|
"https://pip.pypa.io/en/stable/user_guide/"
|
2020-07-17 02:10:46 +02:00
|
|
|
"#fixing-conflicting-dependencies"
|
2020-06-04 14:15:30 +02:00
|
|
|
)
|