]> jfr.im git - dlqueue.git/blob - venv/lib/python3.11/site-packages/pip/_internal/operations/check.py
init: venv aand flask
[dlqueue.git] / venv / lib / python3.11 / site-packages / pip / _internal / operations / check.py
1 """Validation of dependencies of packages
2 """
3
4 import logging
5 from typing import Callable, Dict, List, NamedTuple, Optional, Set, Tuple
6
7 from pip._vendor.packaging.requirements import Requirement
8 from pip._vendor.packaging.specifiers import LegacySpecifier
9 from pip._vendor.packaging.utils import NormalizedName, canonicalize_name
10 from pip._vendor.packaging.version import LegacyVersion
11
12 from pip._internal.distributions import make_distribution_for_install_requirement
13 from pip._internal.metadata import get_default_environment
14 from pip._internal.metadata.base import DistributionVersion
15 from pip._internal.req.req_install import InstallRequirement
16 from pip._internal.utils.deprecation import deprecated
17
18 logger = logging.getLogger(__name__)
19
20
21 class PackageDetails(NamedTuple):
22 version: DistributionVersion
23 dependencies: List[Requirement]
24
25
26 # Shorthands
27 PackageSet = Dict[NormalizedName, PackageDetails]
28 Missing = Tuple[NormalizedName, Requirement]
29 Conflicting = Tuple[NormalizedName, DistributionVersion, Requirement]
30
31 MissingDict = Dict[NormalizedName, List[Missing]]
32 ConflictingDict = Dict[NormalizedName, List[Conflicting]]
33 CheckResult = Tuple[MissingDict, ConflictingDict]
34 ConflictDetails = Tuple[PackageSet, CheckResult]
35
36
37 def create_package_set_from_installed() -> Tuple[PackageSet, bool]:
38 """Converts a list of distributions into a PackageSet."""
39 package_set = {}
40 problems = False
41 env = get_default_environment()
42 for dist in env.iter_installed_distributions(local_only=False, skip=()):
43 name = dist.canonical_name
44 try:
45 dependencies = list(dist.iter_dependencies())
46 package_set[name] = PackageDetails(dist.version, dependencies)
47 except (OSError, ValueError) as e:
48 # Don't crash on unreadable or broken metadata.
49 logger.warning("Error parsing requirements for %s: %s", name, e)
50 problems = True
51 return package_set, problems
52
53
54 def check_package_set(
55 package_set: PackageSet, should_ignore: Optional[Callable[[str], bool]] = None
56 ) -> CheckResult:
57 """Check if a package set is consistent
58
59 If should_ignore is passed, it should be a callable that takes a
60 package name and returns a boolean.
61 """
62
63 warn_legacy_versions_and_specifiers(package_set)
64
65 missing = {}
66 conflicting = {}
67
68 for package_name, package_detail in package_set.items():
69 # Info about dependencies of package_name
70 missing_deps: Set[Missing] = set()
71 conflicting_deps: Set[Conflicting] = set()
72
73 if should_ignore and should_ignore(package_name):
74 continue
75
76 for req in package_detail.dependencies:
77 name = canonicalize_name(req.name)
78
79 # Check if it's missing
80 if name not in package_set:
81 missed = True
82 if req.marker is not None:
83 missed = req.marker.evaluate({"extra": ""})
84 if missed:
85 missing_deps.add((name, req))
86 continue
87
88 # Check if there's a conflict
89 version = package_set[name].version
90 if not req.specifier.contains(version, prereleases=True):
91 conflicting_deps.add((name, version, req))
92
93 if missing_deps:
94 missing[package_name] = sorted(missing_deps, key=str)
95 if conflicting_deps:
96 conflicting[package_name] = sorted(conflicting_deps, key=str)
97
98 return missing, conflicting
99
100
101 def check_install_conflicts(to_install: List[InstallRequirement]) -> ConflictDetails:
102 """For checking if the dependency graph would be consistent after \
103 installing given requirements
104 """
105 # Start from the current state
106 package_set, _ = create_package_set_from_installed()
107 # Install packages
108 would_be_installed = _simulate_installation_of(to_install, package_set)
109
110 # Only warn about directly-dependent packages; create a whitelist of them
111 whitelist = _create_whitelist(would_be_installed, package_set)
112
113 return (
114 package_set,
115 check_package_set(
116 package_set, should_ignore=lambda name: name not in whitelist
117 ),
118 )
119
120
121 def _simulate_installation_of(
122 to_install: List[InstallRequirement], package_set: PackageSet
123 ) -> Set[NormalizedName]:
124 """Computes the version of packages after installing to_install."""
125 # Keep track of packages that were installed
126 installed = set()
127
128 # Modify it as installing requirement_set would (assuming no errors)
129 for inst_req in to_install:
130 abstract_dist = make_distribution_for_install_requirement(inst_req)
131 dist = abstract_dist.get_metadata_distribution()
132 name = dist.canonical_name
133 package_set[name] = PackageDetails(dist.version, list(dist.iter_dependencies()))
134
135 installed.add(name)
136
137 return installed
138
139
140 def _create_whitelist(
141 would_be_installed: Set[NormalizedName], package_set: PackageSet
142 ) -> Set[NormalizedName]:
143 packages_affected = set(would_be_installed)
144
145 for package_name in package_set:
146 if package_name in packages_affected:
147 continue
148
149 for req in package_set[package_name].dependencies:
150 if canonicalize_name(req.name) in packages_affected:
151 packages_affected.add(package_name)
152 break
153
154 return packages_affected
155
156
157 def warn_legacy_versions_and_specifiers(package_set: PackageSet) -> None:
158 for project_name, package_details in package_set.items():
159 if isinstance(package_details.version, LegacyVersion):
160 deprecated(
161 reason=(
162 f"{project_name} {package_details.version} "
163 f"has a non-standard version number."
164 ),
165 replacement=(
166 f"to upgrade to a newer version of {project_name} "
167 f"or contact the author to suggest that they "
168 f"release a version with a conforming version number"
169 ),
170 issue=12063,
171 gone_in="23.3",
172 )
173 for dep in package_details.dependencies:
174 if any(isinstance(spec, LegacySpecifier) for spec in dep.specifier):
175 deprecated(
176 reason=(
177 f"{project_name} {package_details.version} "
178 f"has a non-standard dependency specifier {dep}."
179 ),
180 replacement=(
181 f"to upgrade to a newer version of {project_name} "
182 f"or contact the author to suggest that they "
183 f"release a version with a conforming dependency specifiers"
184 ),
185 issue=12063,
186 gone_in="23.3",
187 )