Path: blob/master/venv/Lib/site-packages/pip/_internal/operations/check.py
811 views
"""Validation of dependencies of packages1"""23# The following comment should be removed at some point in the future.4# mypy: strict-optional=False5# mypy: disallow-untyped-defs=False67import logging8from collections import namedtuple910from pip._vendor.packaging.utils import canonicalize_name11from pip._vendor.pkg_resources import RequirementParseError1213from pip._internal.distributions import (14make_distribution_for_install_requirement,15)16from pip._internal.utils.misc import get_installed_distributions17from pip._internal.utils.typing import MYPY_CHECK_RUNNING1819logger = logging.getLogger(__name__)2021if MYPY_CHECK_RUNNING:22from pip._internal.req.req_install import InstallRequirement23from typing import (24Any, Callable, Dict, Optional, Set, Tuple, List25)2627# Shorthands28PackageSet = Dict[str, 'PackageDetails']29Missing = Tuple[str, Any]30Conflicting = Tuple[str, str, Any]3132MissingDict = Dict[str, List[Missing]]33ConflictingDict = Dict[str, List[Conflicting]]34CheckResult = Tuple[MissingDict, ConflictingDict]3536PackageDetails = namedtuple('PackageDetails', ['version', 'requires'])373839def create_package_set_from_installed(**kwargs):40# type: (**Any) -> Tuple[PackageSet, bool]41"""Converts a list of distributions into a PackageSet.42"""43# Default to using all packages installed on the system44if kwargs == {}:45kwargs = {"local_only": False, "skip": ()}4647package_set = {}48problems = False49for dist in get_installed_distributions(**kwargs):50name = canonicalize_name(dist.project_name)51try:52package_set[name] = PackageDetails(dist.version, dist.requires())53except RequirementParseError as e:54# Don't crash on broken metadata55logger.warning("Error parsing requirements for %s: %s", name, e)56problems = True57return package_set, problems585960def check_package_set(package_set, should_ignore=None):61# type: (PackageSet, Optional[Callable[[str], bool]]) -> CheckResult62"""Check if a package set is consistent6364If should_ignore is passed, it should be a callable that takes a65package name and returns a boolean.66"""67if should_ignore is None:68def should_ignore(name):69return False7071missing = {}72conflicting = {}7374for package_name in package_set:75# Info about dependencies of package_name76missing_deps = set() # type: Set[Missing]77conflicting_deps = set() # type: Set[Conflicting]7879if should_ignore(package_name):80continue8182for req in package_set[package_name].requires:83name = canonicalize_name(req.project_name) # type: str8485# Check if it's missing86if name not in package_set:87missed = True88if req.marker is not None:89missed = req.marker.evaluate()90if missed:91missing_deps.add((name, req))92continue9394# Check if there's a conflict95version = package_set[name].version # type: str96if not req.specifier.contains(version, prereleases=True):97conflicting_deps.add((name, version, req))9899if missing_deps:100missing[package_name] = sorted(missing_deps, key=str)101if conflicting_deps:102conflicting[package_name] = sorted(conflicting_deps, key=str)103104return missing, conflicting105106107def check_install_conflicts(to_install):108# type: (List[InstallRequirement]) -> Tuple[PackageSet, CheckResult]109"""For checking if the dependency graph would be consistent after \110installing given requirements111"""112# Start from the current state113package_set, _ = create_package_set_from_installed()114# Install packages115would_be_installed = _simulate_installation_of(to_install, package_set)116117# Only warn about directly-dependent packages; create a whitelist of them118whitelist = _create_whitelist(would_be_installed, package_set)119120return (121package_set,122check_package_set(123package_set, should_ignore=lambda name: name not in whitelist124)125)126127128def _simulate_installation_of(to_install, package_set):129# type: (List[InstallRequirement], PackageSet) -> Set[str]130"""Computes the version of packages after installing to_install.131"""132133# Keep track of packages that were installed134installed = set()135136# Modify it as installing requirement_set would (assuming no errors)137for inst_req in to_install:138abstract_dist = make_distribution_for_install_requirement(inst_req)139dist = abstract_dist.get_pkg_resources_distribution()140141name = canonicalize_name(dist.key)142package_set[name] = PackageDetails(dist.version, dist.requires())143144installed.add(name)145146return installed147148149def _create_whitelist(would_be_installed, package_set):150# type: (Set[str], PackageSet) -> Set[str]151packages_affected = set(would_be_installed)152153for package_name in package_set:154if package_name in packages_affected:155continue156157for req in package_set[package_name].requires:158if canonicalize_name(req.name) in packages_affected:159packages_affected.add(package_name)160break161162return packages_affected163164165