1"""Dependency Resolution
3The dependency resolution in pip is performed as follows:
5for top-level requirements:
6 a. only one spec allowed per project, regardless of conflicts or not.
7 otherwise a "double requirement" exception is raised
8 b. they override sub-dependency requirements.
10 a. "first found, wins" (where the order is breadth first)
18from collections
import defaultdict
19from itertools
import chain
20from typing
import DefaultDict, Iterable, List, Optional, Set, Tuple
27 BestVersionAlreadyInstalled,
33 UnsupportedPythonVersion,
42 check_invalid_constraint_type,
55DiscoveredDependencies = DefaultDict[str, List[InstallRequirement]]
59 dist: BaseDistribution,
60 version_info: Tuple[int, int, int],
61 ignore_requires_python: bool =
False,
64 Check whether the given Python version is compatible with a distribution's
65 "Requires-Python" value.
67 :param version_info: A 3-tuple of ints representing the Python
68 major-minor-micro version to check.
69 :param ignore_requires_python: Whether to ignore the "Requires-Python"
70 value if the given Python version isn't compatible.
72 :raises UnsupportedPythonVersion: When the given Python version isn't
80 except FileNotFoundError
as e:
83 is_compatible = check_requires_python(
85 version_info=version_info,
89 "Package %r has an invalid Requires-Python: %s",
dist.raw_name, exc
96 version =
".".join(map(str, version_info))
97 if ignore_requires_python:
99 "Ignoring failed Requires-Python check for package %r: %s not in %r",
107 "Package {!r} requires a different Python: {} not in {!r}".format(
114 """Resolves which packages need to be installed/uninstalled to perform \
115 the requested operation without breaking the requirements of any package.
118 _allowed_strategies = {
"eager",
"only-if-needed",
"to-satisfy-only"}
122 preparer: RequirementPreparer,
123 finder: PackageFinder,
124 wheel_cache: Optional[WheelCache],
125 make_install_req: InstallRequirementProvider,
127 ignore_dependencies: bool,
128 ignore_installed: bool,
129 ignore_requires_python: bool,
130 force_reinstall: bool,
131 upgrade_strategy: str,
132 py_version_info: Optional[Tuple[int, ...]] =
None,
137 if py_version_info
is None:
140 py_version_info = normalize_version_info(py_version_info)
156 self._discovered_dependencies: DiscoveredDependencies = defaultdict(list)
159 self, root_reqs: List[InstallRequirement], check_supported_wheels: bool
161 """Resolve what operations need to be done
163 As a side-effect of this method, the packages (and their dependencies)
164 are downloaded, unpacked and prepared for installation. This
165 preparation is done by ``pip.operations.prepare``.
167 Once PyPI has static dependency metadata available, it would be
168 possible to move the preparation to become a step separated from
169 dependency resolution.
171 requirement_set =
RequirementSet(check_supported_wheels=check_supported_wheels)
172 for req
in root_reqs:
174 check_invalid_constraint_type(req)
181 discovered_reqs: List[InstallRequirement] = []
186 except HashError
as exc:
193 return requirement_set
197 requirement_set: RequirementSet,
198 install_req: InstallRequirement,
199 parent_req_name: Optional[str] =
None,
200 extras_requested: Optional[Iterable[str]] =
None,
201 ) -> Tuple[List[InstallRequirement], Optional[InstallRequirement]]:
202 """Add install_req as a requirement to install.
204 :param parent_req_name: The name of the requirement that needed this
205 added. The name is used because when multiple unnamed requirements
206 resolve to the same name, we could otherwise end up with dependency
207 links that point outside the Requirements set. parent_req must
208 already be added. Note that None implies that this is a user
209 supplied requirement, vs an inferred one.
210 :param extras_requested: an iterable of extras used to evaluate the
212 :return: Additional requirements to scan. That is either [] if
213 the requirement is not applicable, or [install_req] if the
214 requirement is applicable and has just been added.
219 "Ignoring %s: markers '%s' don't match your environment",
234 "{} is not a supported wheel on this platform.".format(
242 ),
"a user supplied req shouldn't have a parent"
248 return [install_req],
None
251 existing_req: Optional[
257 has_conflicting_requirement = (
258 parent_req_name
is None
266 if has_conflicting_requirement:
268 "Double requirement given: {} (already in {}, name={!r})".format(
278 return [install_req], install_req
283 return [], existing_req
288 if does_not_satisfy_constraint:
290 "Could not satisfy constraints for '{}': "
291 "installation from path or url cannot be "
305 "Setting %s extras to: %s",
311 return [existing_req], existing_req
324 Set a requirement to be installed.
333 self, req_to_install: InstallRequirement
335 """Check if req_to_install should be skipped.
337 This will check if the req is installed, and whether we should upgrade
338 or reinstall it, taking into account all the relevant user options.
340 After calling this req_to_install will only have satisfied_by set to
341 None if the req_to_install is to be upgraded/reinstalled etc. Any
342 other value will be a dist recording the current thing installed that
343 satisfies the requirement.
345 Note that for vcs urls and the like we can't assess skipping in this
346 routine - we simply identify that we need to pull the thing down,
347 then later on it is pulled down and introspected to assess upgrade/
350 :return: A text reason for why it was skipped, or None.
365 return "already satisfied, skipping upgrade"
366 return "already satisfied"
373 self.
finder.find_requirement(req_to_install, upgrade=
True)
374 except BestVersionAlreadyInstalled:
376 return "already up-to-date"
377 except DistributionNotFound:
388 best_candidate = self.
finder.find_requirement(req, upgrade)
389 if not best_candidate:
400 "The candidate selected for download or install is a "
401 "yanked version: {candidate}\n"
402 "Reason for being yanked: {reason}"
403 ).format(candidate=best_candidate, reason=reason)
409 """Ensure that if a link can be found for this, that it is found.
411 Note that req.link may still be None - if the requirement is already
412 installed and not needed to be upgraded based on the return value of
413 _is_upgrade_allowed().
415 If preparer.require_hashes is True, don't use the wheel cache, because
416 cached wheels, always built locally, have different hashes than the
417 files downloaded from the index server and thus throw false hash
418 mismatches. Furthermore, cached wheels at present have undeterministic
419 contents due to file modification times.
429 supported_tags=get_supported(),
431 if cache_entry
is not None:
446 """Takes a InstallRequirement and returns a single AbstractDist \
447 representing a prepared variant of the same.
450 return self.
preparer.prepare_editable_requirement(req)
458 return self.
preparer.prepare_installed_requirement(req, skip_reason)
462 dist = self.
preparer.prepare_linked_requirement(req)
486 "Requirement already satisfied (use --upgrade to upgrade): %s",
493 requirement_set: RequirementSet,
494 req_to_install: InstallRequirement,
495 ) -> List[InstallRequirement]:
496 """Prepare a single requirements file.
498 :return: A list of additional InstallRequirements to also install.
518 more_reqs: List[InstallRequirement] = []
520 def add_req(subreq: Requirement, extras_requested: Iterable[str]) ->
None:
529 parent_req_name=parent_req_name,
530 extras_requested=extras_requested,
532 if parent_req_name
and add_to_parent:
533 self._discovered_dependencies[parent_req_name].append(add_to_parent)
545 requirement_set, req_to_install, parent_req_name=
None
551 "Installing extra requirements: %r",
554 missing_requested = sorted(
557 for missing
in missing_requested:
559 "%s %s does not provide the extra '%s'",
565 available_requested = sorted(
569 add_req(subreq, extras_requested=available_requested)
574 self, req_set: RequirementSet
575 ) -> List[InstallRequirement]:
576 """Create the installation order.
578 The installation order is topological - requirements are installed
579 before the requiring thing. We break cycles at an arbitrary point,
580 and make no other guarantees.
586 ordered_reqs: Set[InstallRequirement] = set()
588 def schedule(req: InstallRequirement) ->
None:
594 for dep
in self._discovered_dependencies[
req.name]:
bool _is_upgrade_allowed(self, InstallRequirement req)
Tuple[List[InstallRequirement], Optional[InstallRequirement]] _add_requirement_to_set(self, RequirementSet requirement_set, InstallRequirement install_req, Optional[str] parent_req_name=None, Optional[Iterable[str]] extras_requested=None)
List[InstallRequirement] get_installation_order(self, RequirementSet req_set)
None _populate_link(self, InstallRequirement req)
Optional[Link] _find_requirement_link(self, InstallRequirement req)
Optional[str] _check_skip_installed(self, InstallRequirement req_to_install)
None _set_req_to_reinstall(self, InstallRequirement req)
RequirementSet resolve(self, List[InstallRequirement] root_reqs, bool check_supported_wheels)
BaseDistribution _get_dist_for(self, InstallRequirement req)
None __init__(self, RequirementPreparer preparer, PackageFinder finder, Optional[WheelCache] wheel_cache, InstallRequirementProvider make_install_req, bool use_user_site, bool ignore_dependencies, bool ignore_installed, bool ignore_requires_python, bool force_reinstall, str upgrade_strategy, Optional[Tuple[int,...]] py_version_info=None)
List[InstallRequirement] _resolve_one(self, RequirementSet requirement_set, InstallRequirement req_to_install)
None _check_dist_requires_python(BaseDistribution dist, Tuple[int, int, int] version_info, bool ignore_requires_python=False)