def _report_single_requirement_conflict( self, req: Requirement, parent: Optional[Candidate]) -> DistributionNotFound: if parent is None: req_disp = str(req) else: req_disp = f"{req} (from {parent.name})" cands = self._finder.find_all_candidates(req.project_name) versions = [str(v) for v in sorted({c.version for c in cands})] logger.critical( "Could not find a version that satisfies the requirement %s " "(from versions: %s)", req_disp, ", ".join(versions) or "none", ) if str(req) == "requirements.txt": logger.info( "HINT: You are attempting to install a package literally " 'named "requirements.txt" (which cannot exist). Consider ' "using the '-r' flag to install the packages listed in " "requirements.txt") return DistributionNotFound( f"No matching distribution found for {req}")
def _report_single_requirement_conflict(self, req, parent): # type: (Requirement, Candidate) -> DistributionNotFound if parent is None: req_disp = str(req) else: req_disp = f"{req} (from {parent.name})" logger.critical( "Could not find a version that satisfies the requirement %s", req_disp, ) return DistributionNotFound( f"No matching distribution found for {req}")
def _report_single_requirement_conflict(self, req, parent): # type: (Requirement, Optional[Candidate]) -> DistributionNotFound if parent is None: req_disp = str(req) else: req_disp = f"{req} (from {parent.name})" cands = self._finder.find_all_candidates(req.project_name) versions = [str(v) for v in sorted({c.version for c in cands})] logger.critical( "Could not find a version that satisfies the requirement %s " "(from versions: %s)", req_disp, ", ".join(versions) or "none", ) return DistributionNotFound(f"No matching distribution found for {req}")
def get_available_package_versions(self, options, args): # type: (Values, List[Any]) -> None if len(args) != 1: raise CommandError('You need to specify exactly one argument') target_python = cmdoptions.make_target_python(options) query = args[0] with self._build_session(options) as session: finder = self._build_package_finder( options=options, session=session, target_python=target_python, ignore_requires_python=options.ignore_requires_python, ) versions: Iterable[Union[LegacyVersion, Version]] = ( candidate.version for candidate in finder.find_all_candidates(query)) if not options.pre: # Remove prereleases versions = (version for version in versions if not version.is_prerelease) versions = set(versions) if not versions: raise DistributionNotFound( 'No matching distribution found for {}'.format(query)) formatted_versions = [ str(ver) for ver in sorted(versions, reverse=True) ] latest = formatted_versions[0] write_output('{} ({})'.format(query, latest)) write_output('Available versions: {}'.format( ', '.join(formatted_versions))) print_dist_installation_info(query, latest)
def find_requirement(self, req, upgrade): """Try to find a Link matching req Expects req, an InstallRequirement and upgrade, a boolean Returns a Link if found, Raises DistributionNotFound or BestVersionAlreadyInstalled otherwise """ all_candidates = self.find_all_candidates(req.name) # Filter out anything which doesn't match our specifier compatible_versions = set( req.specifier.filter( # We turn the version object into a str here because otherwise # when we're debundled but setuptools isn't, Python will see # packaging.version.Version and # pkg_resources._vendor.packaging.version.Version as different # types. This way we'll use a str as a common data interchange # format. If we stop using the pkg_resources provided specifier # and start using our own, we can drop the cast to str(). [str(c.version) for c in all_candidates], prereleases=(self.allow_all_prereleases if self.allow_all_prereleases else None), )) applicable_candidates = [ # Again, converting to str to deal with debundling. c for c in all_candidates if str(c.version) in compatible_versions ] if applicable_candidates: best_candidate = max(applicable_candidates, key=self._candidate_sort_key) else: best_candidate = None if req.satisfied_by is not None: installed_version = parse_version(req.satisfied_by.version) else: installed_version = None if installed_version is None and best_candidate is None: logger.critical( 'Could not find a version that satisfies the requirement %s ' '(from versions: %s)', req, ', '.join( sorted( {str(c.version) for c in all_candidates}, key=parse_version, ))) raise DistributionNotFound( 'No matching distribution found for %s' % req) best_installed = False if installed_version and (best_candidate is None or best_candidate.version <= installed_version): best_installed = True if not upgrade and installed_version is not None: if best_installed: logger.debug( 'Existing installed version (%s) is most up-to-date and ' 'satisfies requirement', installed_version, ) else: logger.debug( 'Existing installed version (%s) satisfies requirement ' '(most up-to-date version is %s)', installed_version, best_candidate.version, ) return None if best_installed: # We have an existing version, and its the best version logger.debug( 'Installed version (%s) is most up-to-date (past versions: ' '%s)', installed_version, ', '.join(sorted(compatible_versions, key=parse_version)) or "none", ) raise BestVersionAlreadyInstalled logger.debug('Using version %s (newest of versions: %s)', best_candidate.version, ', '.join(sorted(compatible_versions, key=parse_version))) return best_candidate.location
def find_requirement(self, req, upgrade): # type: (InstallRequirement, bool) -> Optional[Link] """Try to find a Link matching req Expects req, an InstallRequirement and upgrade, a boolean Returns a Link if found, Raises DistributionNotFound or BestVersionAlreadyInstalled otherwise """ candidates = self.find_candidates(req.name, req.specifier) best_candidate = candidates.get_best() installed_version = None # type: Optional[_BaseVersion] if req.satisfied_by is not None: installed_version = parse_version(req.satisfied_by.version) def _format_versions(cand_iter): # This repeated parse_version and str() conversion is needed to # handle different vendoring sources from pip and pkg_resources. # If we stop using the pkg_resources provided specifier and start # using our own, we can drop the cast to str(). return ", ".join( sorted( {str(c.version) for c in cand_iter}, key=parse_version, )) or "none" if installed_version is None and best_candidate is None: logger.critical( 'Could not find a version that satisfies the requirement %s ' '(from versions: %s)', req, _format_versions(candidates.iter_all()), ) raise DistributionNotFound( 'No matching distribution found for %s' % req) best_installed = False if installed_version and (best_candidate is None or best_candidate.version <= installed_version): best_installed = True if not upgrade and installed_version is not None: if best_installed: logger.debug( 'Existing installed version (%s) is most up-to-date and ' 'satisfies requirement', installed_version, ) else: logger.debug( 'Existing installed version (%s) satisfies requirement ' '(most up-to-date version is %s)', installed_version, best_candidate.version, ) return None if best_installed: # We have an existing version, and its the best version logger.debug( 'Installed version (%s) is most up-to-date (past versions: ' '%s)', installed_version, _format_versions(candidates.iter_applicable()), ) raise BestVersionAlreadyInstalled logger.debug( 'Using version %s (newest of versions: %s)', best_candidate.version, _format_versions(candidates.iter_applicable()), ) return best_candidate.location
def get_installation_error(self, e): # 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. for cause in e.causes: if isinstance(cause.requirement, RequiresPythonRequirement): return self._report_requires_python_error( cause.requirement, cause.parent, ) # 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] if parent is None: req_disp = str(req) else: req_disp = f'{req} (from {parent.name})' logger.critical( "Could not find a version that satisfies the requirement %s", req_disp, ) return DistributionNotFound( f'No matching distribution found for {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 describe_trigger(parent): # type: (Candidate) -> str ireq = parent.get_install_requirement() if not ireq or not ireq.comes_from: return f"{parent.name}=={parent.version}" if isinstance(ireq.comes_from, InstallRequirement): return str(ireq.comes_from.name) return str(ireq.comes_from) triggers = set() 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: trigger = describe_trigger(parent) triggers.add(trigger) if triggers: info = text_join(sorted(triggers)) else: info = "the requested packages" msg = "Cannot install {} because these package versions " \ "have conflicting dependencies.".format(info) logger.critical(msg) msg = "\nThe conflict is caused by:" for req, parent in e.causes: msg = msg + "\n " if parent: msg = msg + "{} {} depends on ".format(parent.name, parent.version) else: msg = msg + "The user requested " msg = msg + req.format_for_error() msg = msg + "\n\n" + \ "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" logger.info(msg) return DistributionNotFound("ResolutionImpossible: for help visit " "https://pip.pypa.io/en/latest/user_guide/" "#fixing-conflicting-dependencies")
def find_requirement(self, req, upgrade): # type: (InstallRequirement, bool) -> Optional[Link] """Try to find a Link matching req Expects req, an InstallRequirement and upgrade, a boolean Returns a Link if found, Raises DistributionNotFound or BestVersionAlreadyInstalled otherwise """ hashes = req.hashes(trust_internet=False) best_candidate_result = self.find_best_candidate( req.name, specifier=req.specifier, hashes=hashes, ) best_candidate = best_candidate_result.best_candidate installed_version = None # type: Optional[_BaseVersion] if req.satisfied_by is not None: installed_version = parse_version(req.satisfied_by.version) def _format_versions(cand_iter): # type: (Iterable[InstallationCandidate]) -> str # This repeated parse_version and str() conversion is needed to # handle different vendoring sources from pip and pkg_resources. # If we stop using the pkg_resources provided specifier and start # using our own, we can drop the cast to str(). return (", ".join( sorted( {str(c.version) for c in cand_iter}, key=parse_version, )) or "none") if installed_version is None and best_candidate is None: logger.critical( "Could not find a version that satisfies the requirement %s " "(from versions: %s)", req, _format_versions(best_candidate_result.iter_all()), ) raise DistributionNotFound( "No matching distribution found for {}".format(req)) best_installed = False if installed_version and (best_candidate is None or best_candidate.version <= installed_version): best_installed = True if not upgrade and installed_version is not None: if best_installed: logger.debug( "Existing installed version (%s) is most up-to-date and " "satisfies requirement", installed_version, ) else: logger.debug( "Existing installed version (%s) satisfies requirement " "(most up-to-date version is %s)", installed_version, best_candidate.version, ) return None if best_installed: # We have an existing version, and its the best version logger.debug( "Installed version (%s) is most up-to-date (past versions: " "%s)", installed_version, _format_versions(best_candidate_result.iter_applicable()), ) raise BestVersionAlreadyInstalled logger.debug( "Using version %s (newest of versions: %s)", best_candidate.version, _format_versions(best_candidate_result.iter_applicable()), ) return best_candidate.link
def resolve(self, root_reqs, check_supported_wheels): # type: (List[InstallRequirement], bool) -> RequirementSet constraints = {} # type: Dict[str, SpecifierSet] user_requested = set() # type: Set[str] requirements = [] for req in root_reqs: if not req.match_markers(): continue if req.constraint: # Ensure we only accept valid constraints reject_invalid_constraint_types(req) name = canonicalize_name(req.name) if name in constraints: constraints[name] = constraints[name] & req.specifier else: constraints[name] = req.specifier else: if req.is_direct and req.name: user_requested.add(canonicalize_name(req.name)) requirements.append( self.factory.make_requirement_from_install_req(req)) provider = PipProvider( factory=self.factory, constraints=constraints, ignore_dependencies=self.ignore_dependencies, upgrade_strategy=self.upgrade_strategy, user_requested=user_requested, ) reporter = BaseReporter() resolver = RLResolver(provider, reporter) try: try_to_avoid_resolution_too_deep = 2000000 self._result = resolver.resolve( requirements, max_rounds=try_to_avoid_resolution_too_deep, ) except ResolutionImpossible as e: error = self.factory.get_installation_error(e) if not error: # TODO: This needs fixing, we need to look at the # factory.get_installation_error infrastructure, as that # doesn't really allow for the logger.critical calls I'm # using here. for req, parent in e.causes: logger.critical( "Could not find a version that satisfies " + "the requirement " + str(req) + ("" if parent is None else " (from {})".format(parent. name))) raise DistributionNotFound( "No matching distribution found for " + ", ".join([r.name for r, _ in e.causes])) six.raise_from(error, e) req_set = RequirementSet(check_supported_wheels=check_supported_wheels) for candidate in self._result.mapping.values(): ireq = candidate.get_install_requirement() if ireq is None: continue ireq.should_reinstall = self.factory.should_reinstall(candidate) req_set.add_named_requirement(ireq) return req_set
def get_installation_error( self, e: "ResolutionImpossible[Requirement, Candidate]", constraints: Dict[str, Constraint], ) -> 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. requires_python_causes = [ cause for cause in e.causes if isinstance(cause.requirement, RequiresPythonRequirement) and not cause.requirement.is_satisfied_by(self._python_candidate) ] if requires_python_causes: # The comprehension above makes sure all Requirement instances are # RequiresPythonRequirement, so let's cast for convinience. return self._report_requires_python_error( cast("Sequence[ConflictCause]", requires_python_causes), ) # 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] if req.name not in constraints: return self._report_single_requirement_conflict(req, parent) # 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: List[str]) -> str: if len(parts) == 1: return parts[0] return ", ".join(parts[:-1]) + " and " + parts[-1] def describe_trigger(parent: Candidate) -> str: ireq = parent.get_install_requirement() if not ireq or not ireq.comes_from: return f"{parent.name}=={parent.version}" if isinstance(ireq.comes_from, InstallRequirement): return str(ireq.comes_from.name) return str(ireq.comes_from) triggers = set() 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: trigger = describe_trigger(parent) triggers.add(trigger) if triggers: info = text_join(sorted(triggers)) else: info = "the requested packages" msg = ("Cannot install {} because these package versions " "have conflicting dependencies.".format(info)) logger.critical(msg) msg = "\nThe conflict is caused by:" relevant_constraints = set() for req, parent in e.causes: if req.name in constraints: relevant_constraints.add(req.name) msg = msg + "\n " if parent: msg = msg + f"{parent.name} {parent.version} depends on " else: msg = msg + "The user requested " msg = msg + req.format_for_error() for key in relevant_constraints: spec = constraints[key].specifier msg += f"\n The user requested (constraint) {key}{spec}" msg = (msg + "\n\n" + "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") logger.info(msg) return DistributionNotFound("ResolutionImpossible: for help visit " "https://pip.pypa.io/en/latest/user_guide/" "#fixing-conflicting-dependencies")
def get_installation_error(self, e): # 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. for cause in e.causes: if isinstance(cause.requirement, RequiresPythonRequirement): return self._report_requires_python_error( cause.requirement, cause.parent, ) # 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] if parent is None: req_disp = str(req) else: req_disp = f'{req} (from {parent.name})' logger.critical( "Could not find a version that satisfies the requirement %s", req_disp, ) return DistributionNotFound( f'No matching distribution found for {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 describe_trigger(parent): # type: (Candidate) -> str ireq = parent.get_install_requirement() if not ireq or not ireq.comes_from: return f"{parent.name}=={parent.version}" if isinstance(ireq.comes_from, InstallRequirement): return str(ireq.comes_from.name) return str(ireq.comes_from) triggers = set() 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: trigger = describe_trigger(parent) triggers.add(trigger) if triggers: info = text_join(sorted(triggers)) else: info = "the requested packages" msg = "Cannot install {} because these package versions " \ "have conflicting dependencies.".format(info)
info = "the requested packages" msg = "Cannot install {} because these package versions " \ "have conflicting dependencies.".format(info) logger.critical(msg) msg = "\nThe conflict is caused by:" for req, parent in e.causes: msg = msg + "\n " if parent: msg = msg + "{} {} depends on ".format( parent.name, parent.version ) else: msg = msg + "The user requested " msg = msg + req.format_for_error() msg = msg + "\n\n" + \ "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" logger.info(msg) return DistributionNotFound( "ResolutionImpossible: for help visit " "https://pip.pypa.io/en/latest/user_guide/" "#fixing-conflicting-dependencies" )
def resolve(self, root_reqs, check_supported_wheels): # type: (List[InstallRequirement], bool) -> RequirementSet constraints = {} # type: Dict[str, SpecifierSet] user_requested = set() # type: Set[str] requirements = [] for req in root_reqs: if req.constraint: # Ensure we only accept valid constraints reject_invalid_constraint_types(req) name = canonicalize_name(req.name) if name in constraints: constraints[name] = constraints[name] & req.specifier else: constraints[name] = req.specifier else: if req.is_direct and req.name: user_requested.add(canonicalize_name(req.name)) r = self.factory.make_requirement_from_install_req( req, requested_extras=(), ) if r is not None: requirements.append(r) provider = PipProvider( factory=self.factory, constraints=constraints, ignore_dependencies=self.ignore_dependencies, upgrade_strategy=self.upgrade_strategy, user_requested=user_requested, ) reporter = BaseReporter() resolver = RLResolver(provider, reporter) try: try_to_avoid_resolution_too_deep = 2000000 self._result = resolver.resolve( requirements, max_rounds=try_to_avoid_resolution_too_deep, ) except ResolutionImpossible as e: error = self.factory.get_installation_error(e) if not error: # TODO: This needs fixing, we need to look at the # factory.get_installation_error infrastructure, as that # doesn't really allow for the logger.critical calls I'm # using here. for req, parent in e.causes: logger.critical( "Could not find a version that satisfies " + "the requirement " + str(req) + ("" if parent is None else " (from {})".format(parent. name))) raise DistributionNotFound( "No matching distribution found for " + ", ".join([r.name for r, _ in e.causes])) six.raise_from(error, e) req_set = RequirementSet(check_supported_wheels=check_supported_wheels) for candidate in self._result.mapping.values(): ireq = candidate.get_install_requirement() if ireq is None: continue link = candidate.source_link if link and link.is_yanked: # The reason can contain non-ASCII characters, Unicode # is required for Python 2. msg = ( u'The candidate selected for download or install is a ' u'yanked version: {name!r} candidate (version {version} ' u'at {link})\nReason for being yanked: {reason}').format( name=candidate.name, version=candidate.version, link=link, reason=link.yanked_reason or u'<none given>', ) logger.warning(msg) ireq.should_reinstall = self.factory.should_reinstall(candidate) req_set.add_named_requirement(ireq) return req_set