pip 25.1.1__py3-none-any.whl → 25.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (202) hide show
  1. pip/__init__.py +3 -3
  2. pip/_internal/__init__.py +2 -2
  3. pip/_internal/build_env.py +118 -94
  4. pip/_internal/cache.py +16 -14
  5. pip/_internal/cli/autocompletion.py +13 -4
  6. pip/_internal/cli/base_command.py +18 -7
  7. pip/_internal/cli/cmdoptions.py +14 -9
  8. pip/_internal/cli/command_context.py +4 -3
  9. pip/_internal/cli/index_command.py +11 -9
  10. pip/_internal/cli/main.py +3 -2
  11. pip/_internal/cli/main_parser.py +4 -3
  12. pip/_internal/cli/parser.py +26 -22
  13. pip/_internal/cli/progress_bars.py +19 -12
  14. pip/_internal/cli/req_command.py +16 -12
  15. pip/_internal/cli/spinners.py +81 -5
  16. pip/_internal/commands/__init__.py +5 -3
  17. pip/_internal/commands/cache.py +18 -15
  18. pip/_internal/commands/check.py +1 -2
  19. pip/_internal/commands/completion.py +1 -2
  20. pip/_internal/commands/configuration.py +26 -18
  21. pip/_internal/commands/debug.py +8 -6
  22. pip/_internal/commands/download.py +2 -3
  23. pip/_internal/commands/freeze.py +2 -3
  24. pip/_internal/commands/hash.py +1 -2
  25. pip/_internal/commands/help.py +1 -2
  26. pip/_internal/commands/index.py +15 -9
  27. pip/_internal/commands/inspect.py +4 -4
  28. pip/_internal/commands/install.py +44 -39
  29. pip/_internal/commands/list.py +35 -26
  30. pip/_internal/commands/lock.py +1 -2
  31. pip/_internal/commands/search.py +14 -12
  32. pip/_internal/commands/show.py +14 -11
  33. pip/_internal/commands/uninstall.py +1 -2
  34. pip/_internal/commands/wheel.py +2 -3
  35. pip/_internal/configuration.py +39 -25
  36. pip/_internal/distributions/base.py +6 -4
  37. pip/_internal/distributions/installed.py +8 -4
  38. pip/_internal/distributions/sdist.py +20 -13
  39. pip/_internal/distributions/wheel.py +6 -4
  40. pip/_internal/exceptions.py +58 -39
  41. pip/_internal/index/collector.py +24 -29
  42. pip/_internal/index/package_finder.py +70 -61
  43. pip/_internal/index/sources.py +17 -14
  44. pip/_internal/locations/__init__.py +18 -16
  45. pip/_internal/locations/_distutils.py +12 -11
  46. pip/_internal/locations/_sysconfig.py +5 -4
  47. pip/_internal/locations/base.py +4 -3
  48. pip/_internal/main.py +2 -2
  49. pip/_internal/metadata/__init__.py +8 -6
  50. pip/_internal/metadata/_json.py +5 -4
  51. pip/_internal/metadata/base.py +22 -27
  52. pip/_internal/metadata/importlib/_compat.py +6 -4
  53. pip/_internal/metadata/importlib/_dists.py +12 -17
  54. pip/_internal/metadata/importlib/_envs.py +9 -6
  55. pip/_internal/metadata/pkg_resources.py +11 -14
  56. pip/_internal/models/direct_url.py +24 -21
  57. pip/_internal/models/format_control.py +5 -5
  58. pip/_internal/models/installation_report.py +4 -3
  59. pip/_internal/models/link.py +39 -34
  60. pip/_internal/models/pylock.py +27 -22
  61. pip/_internal/models/search_scope.py +6 -7
  62. pip/_internal/models/selection_prefs.py +3 -3
  63. pip/_internal/models/target_python.py +10 -9
  64. pip/_internal/models/wheel.py +7 -5
  65. pip/_internal/network/auth.py +20 -22
  66. pip/_internal/network/cache.py +22 -6
  67. pip/_internal/network/download.py +169 -141
  68. pip/_internal/network/lazy_wheel.py +10 -7
  69. pip/_internal/network/session.py +32 -27
  70. pip/_internal/network/utils.py +2 -2
  71. pip/_internal/network/xmlrpc.py +2 -2
  72. pip/_internal/operations/build/build_tracker.py +10 -8
  73. pip/_internal/operations/build/wheel.py +3 -2
  74. pip/_internal/operations/build/wheel_editable.py +3 -2
  75. pip/_internal/operations/build/wheel_legacy.py +9 -8
  76. pip/_internal/operations/check.py +21 -26
  77. pip/_internal/operations/freeze.py +12 -9
  78. pip/_internal/operations/install/editable_legacy.py +5 -3
  79. pip/_internal/operations/install/wheel.py +49 -41
  80. pip/_internal/operations/prepare.py +35 -30
  81. pip/_internal/pyproject.py +7 -10
  82. pip/_internal/req/__init__.py +12 -10
  83. pip/_internal/req/constructors.py +33 -31
  84. pip/_internal/req/req_dependency_group.py +7 -11
  85. pip/_internal/req/req_file.py +32 -35
  86. pip/_internal/req/req_install.py +37 -34
  87. pip/_internal/req/req_set.py +4 -5
  88. pip/_internal/req/req_uninstall.py +20 -17
  89. pip/_internal/resolution/base.py +3 -3
  90. pip/_internal/resolution/legacy/resolver.py +21 -20
  91. pip/_internal/resolution/resolvelib/base.py +16 -13
  92. pip/_internal/resolution/resolvelib/candidates.py +29 -26
  93. pip/_internal/resolution/resolvelib/factory.py +41 -50
  94. pip/_internal/resolution/resolvelib/found_candidates.py +11 -9
  95. pip/_internal/resolution/resolvelib/provider.py +15 -20
  96. pip/_internal/resolution/resolvelib/reporter.py +5 -3
  97. pip/_internal/resolution/resolvelib/requirements.py +8 -6
  98. pip/_internal/resolution/resolvelib/resolver.py +39 -23
  99. pip/_internal/self_outdated_check.py +8 -6
  100. pip/_internal/utils/appdirs.py +1 -2
  101. pip/_internal/utils/compat.py +7 -1
  102. pip/_internal/utils/compatibility_tags.py +17 -16
  103. pip/_internal/utils/deprecation.py +11 -9
  104. pip/_internal/utils/direct_url_helpers.py +2 -2
  105. pip/_internal/utils/egg_link.py +6 -5
  106. pip/_internal/utils/entrypoints.py +3 -2
  107. pip/_internal/utils/filesystem.py +8 -5
  108. pip/_internal/utils/filetypes.py +4 -6
  109. pip/_internal/utils/glibc.py +6 -5
  110. pip/_internal/utils/hashes.py +9 -6
  111. pip/_internal/utils/logging.py +8 -5
  112. pip/_internal/utils/misc.py +37 -45
  113. pip/_internal/utils/packaging.py +3 -2
  114. pip/_internal/utils/retry.py +7 -4
  115. pip/_internal/utils/setuptools_build.py +12 -10
  116. pip/_internal/utils/subprocess.py +20 -17
  117. pip/_internal/utils/temp_dir.py +10 -12
  118. pip/_internal/utils/unpacking.py +6 -4
  119. pip/_internal/utils/urls.py +1 -1
  120. pip/_internal/utils/virtualenv.py +3 -2
  121. pip/_internal/utils/wheel.py +3 -4
  122. pip/_internal/vcs/bazaar.py +26 -8
  123. pip/_internal/vcs/git.py +59 -24
  124. pip/_internal/vcs/mercurial.py +34 -11
  125. pip/_internal/vcs/subversion.py +27 -16
  126. pip/_internal/vcs/versioncontrol.py +56 -51
  127. pip/_internal/wheel_builder.py +14 -12
  128. pip/_vendor/cachecontrol/__init__.py +1 -1
  129. pip/_vendor/certifi/__init__.py +1 -1
  130. pip/_vendor/certifi/cacert.pem +102 -221
  131. pip/_vendor/certifi/core.py +1 -32
  132. pip/_vendor/distlib/__init__.py +2 -2
  133. pip/_vendor/distlib/scripts.py +1 -1
  134. pip/_vendor/msgpack/__init__.py +2 -2
  135. pip/_vendor/pkg_resources/__init__.py +1 -1
  136. pip/_vendor/platformdirs/version.py +2 -2
  137. pip/_vendor/pygments/__init__.py +1 -1
  138. pip/_vendor/requests/__version__.py +2 -2
  139. pip/_vendor/requests/compat.py +12 -0
  140. pip/_vendor/requests/models.py +3 -1
  141. pip/_vendor/requests/utils.py +6 -16
  142. pip/_vendor/resolvelib/__init__.py +3 -3
  143. pip/_vendor/resolvelib/reporters.py +1 -1
  144. pip/_vendor/resolvelib/resolvers/__init__.py +4 -4
  145. pip/_vendor/resolvelib/resolvers/resolution.py +91 -10
  146. pip/_vendor/rich/__main__.py +12 -40
  147. pip/_vendor/rich/_inspect.py +1 -1
  148. pip/_vendor/rich/_ratio.py +1 -7
  149. pip/_vendor/rich/align.py +1 -7
  150. pip/_vendor/rich/box.py +1 -7
  151. pip/_vendor/rich/console.py +25 -20
  152. pip/_vendor/rich/control.py +1 -7
  153. pip/_vendor/rich/diagnose.py +1 -0
  154. pip/_vendor/rich/emoji.py +1 -6
  155. pip/_vendor/rich/live.py +32 -7
  156. pip/_vendor/rich/live_render.py +1 -7
  157. pip/_vendor/rich/logging.py +1 -1
  158. pip/_vendor/rich/panel.py +3 -4
  159. pip/_vendor/rich/progress.py +15 -15
  160. pip/_vendor/rich/spinner.py +7 -13
  161. pip/_vendor/rich/syntax.py +24 -5
  162. pip/_vendor/rich/traceback.py +32 -17
  163. pip/_vendor/truststore/_api.py +1 -1
  164. pip/_vendor/vendor.txt +9 -10
  165. {pip-25.1.1.dist-info → pip-25.2.dist-info}/METADATA +26 -4
  166. {pip-25.1.1.dist-info → pip-25.2.dist-info}/RECORD +193 -180
  167. {pip-25.1.1.dist-info → pip-25.2.dist-info}/WHEEL +1 -1
  168. {pip-25.1.1.dist-info → pip-25.2.dist-info}/licenses/AUTHORS.txt +12 -0
  169. pip-25.2.dist-info/licenses/src/pip/_vendor/cachecontrol/LICENSE.txt +13 -0
  170. pip-25.2.dist-info/licenses/src/pip/_vendor/certifi/LICENSE +20 -0
  171. pip-25.2.dist-info/licenses/src/pip/_vendor/dependency_groups/LICENSE.txt +9 -0
  172. pip-25.2.dist-info/licenses/src/pip/_vendor/distlib/LICENSE.txt +284 -0
  173. pip-25.2.dist-info/licenses/src/pip/_vendor/distro/LICENSE +202 -0
  174. pip-25.2.dist-info/licenses/src/pip/_vendor/idna/LICENSE.md +31 -0
  175. pip-25.2.dist-info/licenses/src/pip/_vendor/msgpack/COPYING +14 -0
  176. pip-25.2.dist-info/licenses/src/pip/_vendor/packaging/LICENSE +3 -0
  177. pip-25.2.dist-info/licenses/src/pip/_vendor/packaging/LICENSE.APACHE +177 -0
  178. pip-25.2.dist-info/licenses/src/pip/_vendor/packaging/LICENSE.BSD +23 -0
  179. pip-25.2.dist-info/licenses/src/pip/_vendor/pkg_resources/LICENSE +17 -0
  180. pip-25.2.dist-info/licenses/src/pip/_vendor/platformdirs/LICENSE +21 -0
  181. pip-25.2.dist-info/licenses/src/pip/_vendor/pygments/LICENSE +25 -0
  182. pip-25.2.dist-info/licenses/src/pip/_vendor/pyproject_hooks/LICENSE +21 -0
  183. pip-25.2.dist-info/licenses/src/pip/_vendor/requests/LICENSE +175 -0
  184. pip-25.2.dist-info/licenses/src/pip/_vendor/resolvelib/LICENSE +13 -0
  185. pip-25.2.dist-info/licenses/src/pip/_vendor/rich/LICENSE +19 -0
  186. pip-25.2.dist-info/licenses/src/pip/_vendor/tomli/LICENSE +21 -0
  187. pip-25.2.dist-info/licenses/src/pip/_vendor/tomli/LICENSE-HEADER +3 -0
  188. pip-25.2.dist-info/licenses/src/pip/_vendor/tomli_w/LICENSE +21 -0
  189. pip-25.2.dist-info/licenses/src/pip/_vendor/truststore/LICENSE +21 -0
  190. pip-25.2.dist-info/licenses/src/pip/_vendor/urllib3/LICENSE.txt +21 -0
  191. pip/_vendor/distlib/database.py +0 -1329
  192. pip/_vendor/distlib/index.py +0 -508
  193. pip/_vendor/distlib/locators.py +0 -1295
  194. pip/_vendor/distlib/manifest.py +0 -384
  195. pip/_vendor/distlib/markers.py +0 -162
  196. pip/_vendor/distlib/metadata.py +0 -1031
  197. pip/_vendor/distlib/version.py +0 -750
  198. pip/_vendor/distlib/wheel.py +0 -1100
  199. pip/_vendor/typing_extensions.py +0 -4584
  200. {pip-25.1.1.dist-info → pip-25.2.dist-info}/entry_points.txt +0 -0
  201. {pip-25.1.1.dist-info → pip-25.2.dist-info}/licenses/LICENSE.txt +0 -0
  202. {pip-25.1.1.dist-info → pip-25.2.dist-info}/top_level.txt +0 -0
@@ -1,21 +1,14 @@
1
+ from __future__ import annotations
2
+
1
3
  import contextlib
2
4
  import functools
3
5
  import logging
6
+ from collections.abc import Iterable, Iterator, Mapping, Sequence
4
7
  from typing import (
5
8
  TYPE_CHECKING,
6
9
  Callable,
7
- Dict,
8
- FrozenSet,
9
- Iterable,
10
- Iterator,
11
- List,
12
- Mapping,
13
10
  NamedTuple,
14
- Optional,
15
11
  Protocol,
16
- Sequence,
17
- Set,
18
- Tuple,
19
12
  TypeVar,
20
13
  cast,
21
14
  )
@@ -84,13 +77,13 @@ if TYPE_CHECKING:
84
77
  logger = logging.getLogger(__name__)
85
78
 
86
79
  C = TypeVar("C")
87
- Cache = Dict[Link, C]
80
+ Cache = dict[Link, C]
88
81
 
89
82
 
90
83
  class CollectedRootRequirements(NamedTuple):
91
- requirements: List[Requirement]
92
- constraints: Dict[str, Constraint]
93
- user_requested: Dict[str, int]
84
+ requirements: list[Requirement]
85
+ constraints: dict[str, Constraint]
86
+ user_requested: dict[str, int]
94
87
 
95
88
 
96
89
  class Factory:
@@ -99,12 +92,12 @@ class Factory:
99
92
  finder: PackageFinder,
100
93
  preparer: RequirementPreparer,
101
94
  make_install_req: InstallRequirementProvider,
102
- wheel_cache: Optional[WheelCache],
95
+ wheel_cache: WheelCache | None,
103
96
  use_user_site: bool,
104
97
  force_reinstall: bool,
105
98
  ignore_installed: bool,
106
99
  ignore_requires_python: bool,
107
- py_version_info: Optional[Tuple[int, ...]] = None,
100
+ py_version_info: tuple[int, ...] | None = None,
108
101
  ) -> None:
109
102
  self._finder = finder
110
103
  self.preparer = preparer
@@ -118,9 +111,9 @@ class Factory:
118
111
  self._build_failures: Cache[InstallationError] = {}
119
112
  self._link_candidate_cache: Cache[LinkCandidate] = {}
120
113
  self._editable_candidate_cache: Cache[EditableCandidate] = {}
121
- self._installed_candidate_cache: Dict[str, AlreadyInstalledCandidate] = {}
122
- self._extras_candidate_cache: Dict[
123
- Tuple[int, FrozenSet[NormalizedName]], ExtrasCandidate
114
+ self._installed_candidate_cache: dict[str, AlreadyInstalledCandidate] = {}
115
+ self._extras_candidate_cache: dict[
116
+ tuple[int, frozenset[NormalizedName]], ExtrasCandidate
124
117
  ] = {}
125
118
  self._supported_tags_cache = get_supported()
126
119
 
@@ -149,9 +142,9 @@ class Factory:
149
142
  def _make_extras_candidate(
150
143
  self,
151
144
  base: BaseCandidate,
152
- extras: FrozenSet[str],
145
+ extras: frozenset[str],
153
146
  *,
154
- comes_from: Optional[InstallRequirement] = None,
147
+ comes_from: InstallRequirement | None = None,
155
148
  ) -> ExtrasCandidate:
156
149
  cache_key = (id(base), frozenset(canonicalize_name(e) for e in extras))
157
150
  try:
@@ -164,7 +157,7 @@ class Factory:
164
157
  def _make_candidate_from_dist(
165
158
  self,
166
159
  dist: BaseDistribution,
167
- extras: FrozenSet[str],
160
+ extras: frozenset[str],
168
161
  template: InstallRequirement,
169
162
  ) -> Candidate:
170
163
  try:
@@ -179,12 +172,12 @@ class Factory:
179
172
  def _make_candidate_from_link(
180
173
  self,
181
174
  link: Link,
182
- extras: FrozenSet[str],
175
+ extras: frozenset[str],
183
176
  template: InstallRequirement,
184
- name: Optional[NormalizedName],
185
- version: Optional[Version],
186
- ) -> Optional[Candidate]:
187
- base: Optional[BaseCandidate] = self._make_base_candidate_from_link(
177
+ name: NormalizedName | None,
178
+ version: Version | None,
179
+ ) -> Candidate | None:
180
+ base: BaseCandidate | None = self._make_base_candidate_from_link(
188
181
  link, template, name, version
189
182
  )
190
183
  if not extras or base is None:
@@ -195,9 +188,9 @@ class Factory:
195
188
  self,
196
189
  link: Link,
197
190
  template: InstallRequirement,
198
- name: Optional[NormalizedName],
199
- version: Optional[Version],
200
- ) -> Optional[BaseCandidate]:
191
+ name: NormalizedName | None,
192
+ version: Version | None,
193
+ ) -> BaseCandidate | None:
201
194
  # TODO: Check already installed candidate, and use it if the link and
202
195
  # editable flag match.
203
196
 
@@ -254,7 +247,7 @@ class Factory:
254
247
  specifier: SpecifierSet,
255
248
  hashes: Hashes,
256
249
  prefers_installed: bool,
257
- incompatible_ids: Set[int],
250
+ incompatible_ids: set[int],
258
251
  ) -> Iterable[Candidate]:
259
252
  if not ireqs:
260
253
  return ()
@@ -267,14 +260,14 @@ class Factory:
267
260
  assert template.req, "Candidates found on index must be PEP 508"
268
261
  name = canonicalize_name(template.req.name)
269
262
 
270
- extras: FrozenSet[str] = frozenset()
263
+ extras: frozenset[str] = frozenset()
271
264
  for ireq in ireqs:
272
265
  assert ireq.req, "Candidates found on index must be PEP 508"
273
266
  specifier &= ireq.req.specifier
274
267
  hashes &= ireq.hashes(trust_internet=False)
275
268
  extras |= frozenset(ireq.extras)
276
269
 
277
- def _get_installed_candidate() -> Optional[Candidate]:
270
+ def _get_installed_candidate() -> Candidate | None:
278
271
  """Get the candidate for the currently-installed version."""
279
272
  # If --force-reinstall is set, we want the version from the index
280
273
  # instead, so we "pretend" there is nothing installed.
@@ -353,7 +346,7 @@ class Factory:
353
346
  def _iter_explicit_candidates_from_base(
354
347
  self,
355
348
  base_requirements: Iterable[Requirement],
356
- extras: FrozenSet[str],
349
+ extras: frozenset[str],
357
350
  ) -> Iterator[Candidate]:
358
351
  """Produce explicit candidates from the base given an extra-ed package.
359
352
 
@@ -404,8 +397,8 @@ class Factory:
404
397
  is_satisfied_by: Callable[[Requirement, Candidate], bool],
405
398
  ) -> Iterable[Candidate]:
406
399
  # Collect basic lookup information from the requirements.
407
- explicit_candidates: Set[Candidate] = set()
408
- ireqs: List[InstallRequirement] = []
400
+ explicit_candidates: set[Candidate] = set()
401
+ ireqs: list[InstallRequirement] = []
409
402
  for req in requirements[identifier]:
410
403
  cand, ireq = req.get_candidate_lookup()
411
404
  if cand is not None:
@@ -524,7 +517,7 @@ class Factory:
524
517
  )
525
518
 
526
519
  def collect_root_requirements(
527
- self, root_ireqs: List[InstallRequirement]
520
+ self, root_ireqs: list[InstallRequirement]
528
521
  ) -> CollectedRootRequirements:
529
522
  collected = CollectedRootRequirements([], {}, {})
530
523
  for i, ireq in enumerate(root_ireqs):
@@ -573,7 +566,7 @@ class Factory:
573
566
  def make_requirements_from_spec(
574
567
  self,
575
568
  specifier: str,
576
- comes_from: Optional[InstallRequirement],
569
+ comes_from: InstallRequirement | None,
577
570
  requested_extras: Iterable[str] = (),
578
571
  ) -> Iterator[Requirement]:
579
572
  """
@@ -591,7 +584,7 @@ class Factory:
591
584
  def make_requires_python_requirement(
592
585
  self,
593
586
  specifier: SpecifierSet,
594
- ) -> Optional[Requirement]:
587
+ ) -> Requirement | None:
595
588
  if self._ignore_requires_python:
596
589
  return None
597
590
  # Don't bother creating a dependency for an empty Requires-Python.
@@ -599,9 +592,7 @@ class Factory:
599
592
  return None
600
593
  return RequiresPythonRequirement(specifier, self._python_candidate)
601
594
 
602
- def get_wheel_cache_entry(
603
- self, link: Link, name: Optional[str]
604
- ) -> Optional[CacheEntry]:
595
+ def get_wheel_cache_entry(self, link: Link, name: str | None) -> CacheEntry | None:
605
596
  """Look up the link in the wheel cache.
606
597
 
607
598
  If ``preparer.require_hashes`` is True, don't use the wheel cache,
@@ -618,7 +609,7 @@ class Factory:
618
609
  supported_tags=self._supported_tags_cache,
619
610
  )
620
611
 
621
- def get_dist_to_uninstall(self, candidate: Candidate) -> Optional[BaseDistribution]:
612
+ def get_dist_to_uninstall(self, candidate: Candidate) -> BaseDistribution | None:
622
613
  # TODO: Are there more cases this needs to return True? Editable?
623
614
  dist = self._installed_dists.get(candidate.project_name)
624
615
  if dist is None: # Not installed, no uninstallation required.
@@ -647,7 +638,7 @@ class Factory:
647
638
  return None
648
639
 
649
640
  def _report_requires_python_error(
650
- self, causes: Sequence["ConflictCause"]
641
+ self, causes: Sequence[ConflictCause]
651
642
  ) -> UnsupportedPythonVersion:
652
643
  assert causes, "Requires-Python error reported with no cause"
653
644
 
@@ -669,7 +660,7 @@ class Factory:
669
660
  return UnsupportedPythonVersion(message)
670
661
 
671
662
  def _report_single_requirement_conflict(
672
- self, req: Requirement, parent: Optional[Candidate]
663
+ self, req: Requirement, parent: Candidate | None
673
664
  ) -> DistributionNotFound:
674
665
  if parent is None:
675
666
  req_disp = str(req)
@@ -679,8 +670,8 @@ class Factory:
679
670
  cands = self._finder.find_all_candidates(req.project_name)
680
671
  skipped_by_requires_python = self._finder.requires_python_skipped_reasons()
681
672
 
682
- versions_set: Set[Version] = set()
683
- yanked_versions_set: Set[Version] = set()
673
+ versions_set: set[Version] = set()
674
+ yanked_versions_set: set[Version] = set()
684
675
  for c in cands:
685
676
  is_yanked = c.link.is_yanked if c.link else False
686
677
  if is_yanked:
@@ -722,8 +713,8 @@ class Factory:
722
713
 
723
714
  def get_installation_error(
724
715
  self,
725
- e: "ResolutionImpossible[Requirement, Candidate]",
726
- constraints: Dict[str, Constraint],
716
+ e: ResolutionImpossible[Requirement, Candidate],
717
+ constraints: dict[str, Constraint],
727
718
  ) -> InstallationError:
728
719
  assert e.causes, "Installation error reported with no cause"
729
720
 
@@ -756,7 +747,7 @@ class Factory:
756
747
  # satisfied at once.
757
748
 
758
749
  # A couple of formatting helpers
759
- def text_join(parts: List[str]) -> str:
750
+ def text_join(parts: list[str]) -> str:
760
751
  if len(parts) == 1:
761
752
  return parts[0]
762
753
 
@@ -8,9 +8,11 @@ absolutely need, and not "download the world" when we only need one version of
8
8
  something.
9
9
  """
10
10
 
11
+ from __future__ import annotations
12
+
11
13
  import logging
12
- from collections.abc import Sequence
13
- from typing import Any, Callable, Iterator, Optional, Set, Tuple
14
+ from collections.abc import Iterator, Sequence
15
+ from typing import Any, Callable, Optional
14
16
 
15
17
  from pip._vendor.packaging.version import _BaseVersion
16
18
 
@@ -20,7 +22,7 @@ from .base import Candidate
20
22
 
21
23
  logger = logging.getLogger(__name__)
22
24
 
23
- IndexCandidateInfo = Tuple[_BaseVersion, Callable[[], Optional[Candidate]]]
25
+ IndexCandidateInfo = tuple[_BaseVersion, Callable[[], Optional[Candidate]]]
24
26
 
25
27
 
26
28
  def _iter_built(infos: Iterator[IndexCandidateInfo]) -> Iterator[Candidate]:
@@ -29,7 +31,7 @@ def _iter_built(infos: Iterator[IndexCandidateInfo]) -> Iterator[Candidate]:
29
31
  This iterator is used when the package is not already installed. Candidates
30
32
  from index come later in their normal ordering.
31
33
  """
32
- versions_found: Set[_BaseVersion] = set()
34
+ versions_found: set[_BaseVersion] = set()
33
35
  for version, func in infos:
34
36
  if version in versions_found:
35
37
  continue
@@ -65,7 +67,7 @@ def _iter_built_with_prepended(
65
67
  normal ordering, except skipped when the version is already installed.
66
68
  """
67
69
  yield installed
68
- versions_found: Set[_BaseVersion] = {installed.version}
70
+ versions_found: set[_BaseVersion] = {installed.version}
69
71
  for version, func in infos:
70
72
  if version in versions_found:
71
73
  continue
@@ -89,7 +91,7 @@ def _iter_built_with_inserted(
89
91
  the installed candidate exactly once before we start yielding older or
90
92
  equivalent candidates, or after all other candidates if they are all newer.
91
93
  """
92
- versions_found: Set[_BaseVersion] = set()
94
+ versions_found: set[_BaseVersion] = set()
93
95
  for version, func in infos:
94
96
  if version in versions_found:
95
97
  continue
@@ -120,15 +122,15 @@ class FoundCandidates(Sequence[Candidate]):
120
122
  def __init__(
121
123
  self,
122
124
  get_infos: Callable[[], Iterator[IndexCandidateInfo]],
123
- installed: Optional[Candidate],
125
+ installed: Candidate | None,
124
126
  prefers_installed: bool,
125
- incompatible_ids: Set[int],
127
+ incompatible_ids: set[int],
126
128
  ):
127
129
  self._get_infos = get_infos
128
130
  self._installed = installed
129
131
  self._prefers_installed = prefers_installed
130
132
  self._incompatible_ids = incompatible_ids
131
- self._bool: Optional[bool] = None
133
+ self._bool: bool | None = None
132
134
 
133
135
  def __getitem__(self, index: Any) -> Any:
134
136
  # Implemented to satisfy the ABC check. This is not needed by the
@@ -1,16 +1,11 @@
1
+ from __future__ import annotations
2
+
1
3
  import math
2
- from functools import lru_cache
4
+ from collections.abc import Iterable, Iterator, Mapping, Sequence
5
+ from functools import cache
3
6
  from typing import (
4
7
  TYPE_CHECKING,
5
- Dict,
6
- Iterable,
7
- Iterator,
8
- Mapping,
9
- Optional,
10
- Sequence,
11
- Tuple,
12
8
  TypeVar,
13
- Union,
14
9
  )
15
10
 
16
11
  from pip._vendor.resolvelib.providers import AbstractProvider
@@ -59,7 +54,7 @@ def _get_with_identifier(
59
54
  mapping: Mapping[str, V],
60
55
  identifier: str,
61
56
  default: D,
62
- ) -> Union[D, V]:
57
+ ) -> D | V:
63
58
  """Get item from a package name lookup mapping with a resolver identifier.
64
59
 
65
60
  This extra logic is needed when the target mapping is keyed by package
@@ -94,10 +89,10 @@ class PipProvider(_ProviderBase):
94
89
  def __init__(
95
90
  self,
96
91
  factory: Factory,
97
- constraints: Dict[str, Constraint],
92
+ constraints: dict[str, Constraint],
98
93
  ignore_dependencies: bool,
99
94
  upgrade_strategy: str,
100
- user_requested: Dict[str, int],
95
+ user_requested: dict[str, int],
101
96
  ) -> None:
102
97
  self._factory = factory
103
98
  self._constraints = constraints
@@ -105,7 +100,7 @@ class PipProvider(_ProviderBase):
105
100
  self._upgrade_strategy = upgrade_strategy
106
101
  self._user_requested = user_requested
107
102
 
108
- def identify(self, requirement_or_candidate: Union[Requirement, Candidate]) -> str:
103
+ def identify(self, requirement_or_candidate: Requirement | Candidate) -> str:
109
104
  return requirement_or_candidate.name
110
105
 
111
106
  def narrow_requirement_selection(
@@ -113,8 +108,8 @@ class PipProvider(_ProviderBase):
113
108
  identifiers: Iterable[str],
114
109
  resolutions: Mapping[str, Candidate],
115
110
  candidates: Mapping[str, Iterator[Candidate]],
116
- information: Mapping[str, Iterator["PreferenceInformation"]],
117
- backtrack_causes: Sequence["PreferenceInformation"],
111
+ information: Mapping[str, Iterator[PreferenceInformation]],
112
+ backtrack_causes: Sequence[PreferenceInformation],
118
113
  ) -> Iterable[str]:
119
114
  """Produce a subset of identifiers that should be considered before others.
120
115
 
@@ -156,9 +151,9 @@ class PipProvider(_ProviderBase):
156
151
  identifier: str,
157
152
  resolutions: Mapping[str, Candidate],
158
153
  candidates: Mapping[str, Iterator[Candidate]],
159
- information: Mapping[str, Iterable["PreferenceInformation"]],
160
- backtrack_causes: Sequence["PreferenceInformation"],
161
- ) -> "Preference":
154
+ information: Mapping[str, Iterable[PreferenceInformation]],
155
+ backtrack_causes: Sequence[PreferenceInformation],
156
+ ) -> Preference:
162
157
  """Produce a sort key for given requirement based on preference.
163
158
 
164
159
  The lower the return value is, the more preferred this group of
@@ -192,7 +187,7 @@ class PipProvider(_ProviderBase):
192
187
 
193
188
  if not has_information:
194
189
  direct = False
195
- ireqs: Tuple[Optional[InstallRequirement], ...] = ()
190
+ ireqs: tuple[InstallRequirement | None, ...] = ()
196
191
  else:
197
192
  # Go through the information and for each requirement,
198
193
  # check if it's explicit (e.g., a direct link) and get the
@@ -271,7 +266,7 @@ class PipProvider(_ProviderBase):
271
266
  )
272
267
 
273
268
  @staticmethod
274
- @lru_cache(maxsize=None)
269
+ @cache
275
270
  def is_satisfied_by(requirement: Requirement, candidate: Candidate) -> bool:
276
271
  return requirement.is_satisfied_by(candidate)
277
272
 
@@ -1,6 +1,8 @@
1
+ from __future__ import annotations
2
+
1
3
  from collections import defaultdict
2
4
  from logging import getLogger
3
- from typing import Any, DefaultDict, Optional
5
+ from typing import Any
4
6
 
5
7
  from pip._vendor.resolvelib.reporters import BaseReporter
6
8
 
@@ -11,7 +13,7 @@ logger = getLogger(__name__)
11
13
 
12
14
  class PipReporter(BaseReporter[Requirement, Candidate, str]):
13
15
  def __init__(self) -> None:
14
- self.reject_count_by_package: DefaultDict[str, int] = defaultdict(int)
16
+ self.reject_count_by_package: defaultdict[str, int] = defaultdict(int)
15
17
 
16
18
  self._messages_at_reject_count = {
17
19
  1: (
@@ -72,7 +74,7 @@ class PipDebuggingReporter(BaseReporter[Requirement, Candidate, str]):
72
74
  logger.info("Reporter.ending(%r)", state)
73
75
 
74
76
  def adding_requirement(
75
- self, requirement: Requirement, parent: Optional[Candidate]
77
+ self, requirement: Requirement, parent: Candidate | None
76
78
  ) -> None:
77
79
  logger.info("Reporter.adding_requirement(%r, %r)", requirement, parent)
78
80
 
@@ -1,4 +1,6 @@
1
- from typing import Any, Optional
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
2
4
 
3
5
  from pip._vendor.packaging.specifiers import SpecifierSet
4
6
  from pip._vendor.packaging.utils import NormalizedName, canonicalize_name
@@ -51,8 +53,8 @@ class SpecifierRequirement(Requirement):
51
53
  def __init__(self, ireq: InstallRequirement) -> None:
52
54
  assert ireq.link is None, "This is a link, not a specifier"
53
55
  self._ireq = ireq
54
- self._equal_cache: Optional[str] = None
55
- self._hash: Optional[int] = None
56
+ self._equal_cache: str | None = None
57
+ self._hash: int | None = None
56
58
  self._extras = frozenset(canonicalize_name(e) for e in self._ireq.extras)
57
59
 
58
60
  @property
@@ -128,8 +130,8 @@ class SpecifierWithoutExtrasRequirement(SpecifierRequirement):
128
130
  def __init__(self, ireq: InstallRequirement) -> None:
129
131
  assert ireq.link is None, "This is a link, not a specifier"
130
132
  self._ireq = install_req_drop_extras(ireq)
131
- self._equal_cache: Optional[str] = None
132
- self._hash: Optional[int] = None
133
+ self._equal_cache: str | None = None
134
+ self._hash: int | None = None
133
135
  self._extras = frozenset(canonicalize_name(e) for e in self._ireq.extras)
134
136
 
135
137
  @property
@@ -159,7 +161,7 @@ class RequiresPythonRequirement(Requirement):
159
161
  def __init__(self, specifier: SpecifierSet, match: Candidate) -> None:
160
162
  self.specifier = specifier
161
163
  self._specifier_string = str(specifier) # for faster __eq__
162
- self._hash: Optional[int] = None
164
+ self._hash: int | None = None
163
165
  self._candidate = match
164
166
 
165
167
  def __str__(self) -> str:
@@ -1,8 +1,10 @@
1
+ from __future__ import annotations
2
+
1
3
  import contextlib
2
4
  import functools
3
5
  import logging
4
6
  import os
5
- from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple, cast
7
+ from typing import TYPE_CHECKING, cast
6
8
 
7
9
  from pip._vendor.packaging.utils import canonicalize_name
8
10
  from pip._vendor.resolvelib import BaseReporter, ResolutionImpossible, ResolutionTooDeep
@@ -43,7 +45,7 @@ class Resolver(BaseResolver):
43
45
  self,
44
46
  preparer: RequirementPreparer,
45
47
  finder: PackageFinder,
46
- wheel_cache: Optional[WheelCache],
48
+ wheel_cache: WheelCache | None,
47
49
  make_install_req: InstallRequirementProvider,
48
50
  use_user_site: bool,
49
51
  ignore_dependencies: bool,
@@ -51,7 +53,7 @@ class Resolver(BaseResolver):
51
53
  ignore_requires_python: bool,
52
54
  force_reinstall: bool,
53
55
  upgrade_strategy: str,
54
- py_version_info: Optional[Tuple[int, ...]] = None,
56
+ py_version_info: tuple[int, ...] | None = None,
55
57
  ):
56
58
  super().__init__()
57
59
  assert upgrade_strategy in self._allowed_strategies
@@ -69,10 +71,10 @@ class Resolver(BaseResolver):
69
71
  )
70
72
  self.ignore_dependencies = ignore_dependencies
71
73
  self.upgrade_strategy = upgrade_strategy
72
- self._result: Optional[Result] = None
74
+ self._result: Result | None = None
73
75
 
74
76
  def resolve(
75
- self, root_reqs: List[InstallRequirement], check_supported_wheels: bool
77
+ self, root_reqs: list[InstallRequirement], check_supported_wheels: bool
76
78
  ) -> RequirementSet:
77
79
  collected = self.factory.collect_root_requirements(root_reqs)
78
80
  provider = PipProvider(
@@ -187,7 +189,7 @@ class Resolver(BaseResolver):
187
189
 
188
190
  def get_installation_order(
189
191
  self, req_set: RequirementSet
190
- ) -> List[InstallRequirement]:
192
+ ) -> list[InstallRequirement]:
191
193
  """Get order for installation of requirements in RequirementSet.
192
194
 
193
195
  The returned list contains a requirement before another that depends on
@@ -218,8 +220,8 @@ class Resolver(BaseResolver):
218
220
 
219
221
 
220
222
  def get_topological_weights(
221
- graph: "DirectedGraph[Optional[str]]", requirement_keys: Set[str]
222
- ) -> Dict[Optional[str], int]:
223
+ graph: DirectedGraph[str | None], requirement_keys: set[str]
224
+ ) -> dict[str | None, int]:
223
225
  """Assign weights to each node based on how "deep" they are.
224
226
 
225
227
  This implementation may change at any point in the future without prior
@@ -245,14 +247,25 @@ def get_topological_weights(
245
247
  We are only interested in the weights of packages that are in the
246
248
  requirement_keys.
247
249
  """
248
- path: Set[Optional[str]] = set()
249
- weights: Dict[Optional[str], int] = {}
250
+ path: set[str | None] = set()
251
+ weights: dict[str | None, list[int]] = {}
250
252
 
251
- def visit(node: Optional[str]) -> None:
253
+ def visit(node: str | None) -> None:
252
254
  if node in path:
253
255
  # We hit a cycle, so we'll break it here.
254
256
  return
255
257
 
258
+ # The walk is exponential and for pathologically connected graphs (which
259
+ # are the ones most likely to contain cycles in the first place) it can
260
+ # take until the heat-death of the universe. To counter this we limit
261
+ # the number of attempts to visit (i.e. traverse through) any given
262
+ # node. We choose a value here which gives decent enough coverage for
263
+ # fairly well behaved graphs, and still limits the walk complexity to be
264
+ # linear in nature.
265
+ cur_weights = weights.get(node, [])
266
+ if len(cur_weights) >= 5:
267
+ return
268
+
256
269
  # Time to visit the children!
257
270
  path.add(node)
258
271
  for child in graph.iter_children(node):
@@ -262,14 +275,14 @@ def get_topological_weights(
262
275
  if node not in requirement_keys:
263
276
  return
264
277
 
265
- last_known_parent_count = weights.get(node, 0)
266
- weights[node] = max(last_known_parent_count, len(path))
278
+ cur_weights.append(len(path))
279
+ weights[node] = cur_weights
267
280
 
268
- # Simplify the graph, pruning leaves that have no dependencies.
269
- # This is needed for large graphs (say over 200 packages) because the
270
- # `visit` function is exponentially slower then, taking minutes.
281
+ # Simplify the graph, pruning leaves that have no dependencies. This is
282
+ # needed for large graphs (say over 200 packages) because the `visit`
283
+ # function is slower for large/densely connected graphs, taking minutes.
271
284
  # See https://github.com/pypa/pip/issues/10557
272
- # We will loop until we explicitly break the loop.
285
+ # We repeat the pruning step until we have no more leaves to remove.
273
286
  while True:
274
287
  leaves = set()
275
288
  for key in graph:
@@ -289,12 +302,13 @@ def get_topological_weights(
289
302
  for leaf in leaves:
290
303
  if leaf not in requirement_keys:
291
304
  continue
292
- weights[leaf] = weight
305
+ weights[leaf] = [weight]
293
306
  # Remove the leaves from the graph, making it simpler.
294
307
  for leaf in leaves:
295
308
  graph.remove(leaf)
296
309
 
297
- # Visit the remaining graph.
310
+ # Visit the remaining graph, this will only have nodes to handle if the
311
+ # graph had a cycle in it, which the pruning step above could not handle.
298
312
  # `None` is guaranteed to be the root node by resolvelib.
299
313
  visit(None)
300
314
 
@@ -303,13 +317,15 @@ def get_topological_weights(
303
317
  difference = set(weights.keys()).difference(requirement_keys)
304
318
  assert not difference, difference
305
319
 
306
- return weights
320
+ # Now give back all the weights, choosing the largest ones from what we
321
+ # accumulated.
322
+ return {node: max(wgts) for (node, wgts) in weights.items()}
307
323
 
308
324
 
309
325
  def _req_set_item_sorter(
310
- item: Tuple[str, InstallRequirement],
311
- weights: Dict[Optional[str], int],
312
- ) -> Tuple[int, str]:
326
+ item: tuple[str, InstallRequirement],
327
+ weights: dict[str | None, int],
328
+ ) -> tuple[int, str]:
313
329
  """Key function used to sort install requirements for installation.
314
330
 
315
331
  Based on the "weight" mapping calculated in ``get_installation_order()``.
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  import datetime
2
4
  import functools
3
5
  import hashlib
@@ -7,7 +9,7 @@ import optparse
7
9
  import os.path
8
10
  import sys
9
11
  from dataclasses import dataclass
10
- from typing import Any, Callable, Dict, Optional
12
+ from typing import Any, Callable
11
13
 
12
14
  from pip._vendor.packaging.version import Version
13
15
  from pip._vendor.packaging.version import parse as parse_version
@@ -54,7 +56,7 @@ def _convert_date(isodate: str) -> datetime.datetime:
54
56
 
55
57
  class SelfCheckState:
56
58
  def __init__(self, cache_dir: str) -> None:
57
- self._state: Dict[str, Any] = {}
59
+ self._state: dict[str, Any] = {}
58
60
  self._statefile_path = None
59
61
 
60
62
  # Try to load the existing state
@@ -74,7 +76,7 @@ class SelfCheckState:
74
76
  def key(self) -> str:
75
77
  return sys.prefix
76
78
 
77
- def get(self, current_time: datetime.datetime) -> Optional[str]:
79
+ def get(self, current_time: datetime.datetime) -> str | None:
78
80
  """Check if we have a not-outdated version loaded already."""
79
81
  if not self._state:
80
82
  return None
@@ -165,7 +167,7 @@ def was_installed_by_pip(pkg: str) -> bool:
165
167
 
166
168
  def _get_current_remote_pip_version(
167
169
  session: PipSession, options: optparse.Values
168
- ) -> Optional[str]:
170
+ ) -> str | None:
169
171
  # Lets use PackageFinder to see what the latest pip version is
170
172
  link_collector = LinkCollector.create(
171
173
  session,
@@ -196,8 +198,8 @@ def _self_version_check_logic(
196
198
  state: SelfCheckState,
197
199
  current_time: datetime.datetime,
198
200
  local_version: Version,
199
- get_remote_version: Callable[[], Optional[str]],
200
- ) -> Optional[UpgradePrompt]:
201
+ get_remote_version: Callable[[], str | None],
202
+ ) -> UpgradePrompt | None:
201
203
  remote_version_str = state.get(current_time)
202
204
  if remote_version_str is None:
203
205
  remote_version_str = get_remote_version()