pip 25.1.1__py3-none-any.whl → 25.3__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 (236) hide show
  1. pip/__init__.py +3 -3
  2. pip/_internal/__init__.py +2 -2
  3. pip/_internal/build_env.py +186 -94
  4. pip/_internal/cache.py +17 -15
  5. pip/_internal/cli/autocompletion.py +13 -4
  6. pip/_internal/cli/base_command.py +18 -7
  7. pip/_internal/cli/cmdoptions.py +57 -80
  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 +24 -20
  13. pip/_internal/cli/progress_bars.py +19 -12
  14. pip/_internal/cli/req_command.py +57 -33
  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 +6 -10
  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 +63 -53
  29. pip/_internal/commands/list.py +35 -26
  30. pip/_internal/commands/lock.py +4 -8
  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 +7 -13
  35. pip/_internal/configuration.py +40 -27
  36. pip/_internal/distributions/base.py +6 -4
  37. pip/_internal/distributions/installed.py +8 -4
  38. pip/_internal/distributions/sdist.py +33 -27
  39. pip/_internal/distributions/wheel.py +6 -4
  40. pip/_internal/exceptions.py +78 -42
  41. pip/_internal/index/collector.py +24 -29
  42. pip/_internal/index/package_finder.py +73 -64
  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 +14 -7
  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 +20 -19
  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 +12 -71
  65. pip/_internal/network/auth.py +20 -22
  66. pip/_internal/network/cache.py +28 -17
  67. pip/_internal/network/download.py +169 -141
  68. pip/_internal/network/lazy_wheel.py +15 -10
  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 +7 -6
  74. pip/_internal/operations/build/wheel_editable.py +7 -6
  75. pip/_internal/operations/check.py +21 -26
  76. pip/_internal/operations/freeze.py +12 -9
  77. pip/_internal/operations/install/wheel.py +49 -41
  78. pip/_internal/operations/prepare.py +42 -31
  79. pip/_internal/pyproject.py +7 -69
  80. pip/_internal/req/__init__.py +12 -12
  81. pip/_internal/req/constructors.py +68 -62
  82. pip/_internal/req/req_dependency_group.py +7 -11
  83. pip/_internal/req/req_file.py +32 -36
  84. pip/_internal/req/req_install.py +64 -170
  85. pip/_internal/req/req_set.py +4 -5
  86. pip/_internal/req/req_uninstall.py +20 -17
  87. pip/_internal/resolution/base.py +3 -3
  88. pip/_internal/resolution/legacy/resolver.py +21 -20
  89. pip/_internal/resolution/resolvelib/base.py +16 -13
  90. pip/_internal/resolution/resolvelib/candidates.py +49 -37
  91. pip/_internal/resolution/resolvelib/factory.py +72 -50
  92. pip/_internal/resolution/resolvelib/found_candidates.py +11 -9
  93. pip/_internal/resolution/resolvelib/provider.py +24 -20
  94. pip/_internal/resolution/resolvelib/reporter.py +26 -11
  95. pip/_internal/resolution/resolvelib/requirements.py +8 -6
  96. pip/_internal/resolution/resolvelib/resolver.py +41 -29
  97. pip/_internal/self_outdated_check.py +19 -9
  98. pip/_internal/utils/appdirs.py +1 -2
  99. pip/_internal/utils/compat.py +7 -1
  100. pip/_internal/utils/compatibility_tags.py +17 -16
  101. pip/_internal/utils/deprecation.py +11 -9
  102. pip/_internal/utils/direct_url_helpers.py +2 -2
  103. pip/_internal/utils/egg_link.py +6 -5
  104. pip/_internal/utils/entrypoints.py +3 -2
  105. pip/_internal/utils/filesystem.py +20 -5
  106. pip/_internal/utils/filetypes.py +4 -6
  107. pip/_internal/utils/glibc.py +6 -5
  108. pip/_internal/utils/hashes.py +9 -6
  109. pip/_internal/utils/logging.py +8 -5
  110. pip/_internal/utils/misc.py +37 -45
  111. pip/_internal/utils/packaging.py +3 -2
  112. pip/_internal/utils/retry.py +7 -4
  113. pip/_internal/utils/subprocess.py +20 -17
  114. pip/_internal/utils/temp_dir.py +10 -12
  115. pip/_internal/utils/unpacking.py +31 -4
  116. pip/_internal/utils/urls.py +1 -1
  117. pip/_internal/utils/virtualenv.py +3 -2
  118. pip/_internal/utils/wheel.py +3 -4
  119. pip/_internal/vcs/bazaar.py +26 -8
  120. pip/_internal/vcs/git.py +59 -24
  121. pip/_internal/vcs/mercurial.py +34 -11
  122. pip/_internal/vcs/subversion.py +27 -16
  123. pip/_internal/vcs/versioncontrol.py +56 -51
  124. pip/_internal/wheel_builder.py +30 -101
  125. pip/_vendor/README.rst +180 -0
  126. pip/_vendor/cachecontrol/LICENSE.txt +13 -0
  127. pip/_vendor/cachecontrol/__init__.py +1 -1
  128. pip/_vendor/certifi/LICENSE +20 -0
  129. pip/_vendor/certifi/__init__.py +1 -1
  130. pip/_vendor/certifi/cacert.pem +164 -261
  131. pip/_vendor/certifi/core.py +1 -32
  132. pip/_vendor/dependency_groups/LICENSE.txt +9 -0
  133. pip/_vendor/distlib/LICENSE.txt +284 -0
  134. pip/_vendor/distlib/__init__.py +2 -2
  135. pip/_vendor/distlib/scripts.py +1 -1
  136. pip/_vendor/distro/LICENSE +202 -0
  137. pip/_vendor/idna/LICENSE.md +31 -0
  138. pip/_vendor/msgpack/COPYING +14 -0
  139. pip/_vendor/msgpack/__init__.py +2 -2
  140. pip/_vendor/packaging/LICENSE +3 -0
  141. pip/_vendor/packaging/LICENSE.APACHE +177 -0
  142. pip/_vendor/packaging/LICENSE.BSD +23 -0
  143. pip/_vendor/pkg_resources/LICENSE +17 -0
  144. pip/_vendor/pkg_resources/__init__.py +1 -1
  145. pip/_vendor/platformdirs/LICENSE +21 -0
  146. pip/_vendor/platformdirs/api.py +1 -1
  147. pip/_vendor/platformdirs/macos.py +10 -8
  148. pip/_vendor/platformdirs/version.py +16 -3
  149. pip/_vendor/pygments/LICENSE +25 -0
  150. pip/_vendor/pygments/__init__.py +1 -1
  151. pip/_vendor/pyproject_hooks/LICENSE +21 -0
  152. pip/_vendor/requests/LICENSE +175 -0
  153. pip/_vendor/requests/__version__.py +2 -2
  154. pip/_vendor/requests/adapters.py +17 -40
  155. pip/_vendor/requests/compat.py +12 -0
  156. pip/_vendor/requests/models.py +3 -1
  157. pip/_vendor/requests/sessions.py +1 -1
  158. pip/_vendor/requests/utils.py +6 -16
  159. pip/_vendor/resolvelib/LICENSE +13 -0
  160. pip/_vendor/resolvelib/__init__.py +3 -3
  161. pip/_vendor/resolvelib/reporters.py +1 -1
  162. pip/_vendor/resolvelib/resolvers/__init__.py +4 -4
  163. pip/_vendor/resolvelib/resolvers/abstract.py +3 -3
  164. pip/_vendor/resolvelib/resolvers/resolution.py +96 -10
  165. pip/_vendor/rich/LICENSE +19 -0
  166. pip/_vendor/rich/__main__.py +12 -40
  167. pip/_vendor/rich/_inspect.py +1 -1
  168. pip/_vendor/rich/_ratio.py +1 -7
  169. pip/_vendor/rich/align.py +1 -7
  170. pip/_vendor/rich/box.py +1 -7
  171. pip/_vendor/rich/console.py +25 -20
  172. pip/_vendor/rich/control.py +1 -7
  173. pip/_vendor/rich/diagnose.py +1 -0
  174. pip/_vendor/rich/emoji.py +1 -6
  175. pip/_vendor/rich/live.py +32 -7
  176. pip/_vendor/rich/live_render.py +1 -7
  177. pip/_vendor/rich/logging.py +1 -1
  178. pip/_vendor/rich/panel.py +3 -4
  179. pip/_vendor/rich/progress.py +15 -15
  180. pip/_vendor/rich/spinner.py +7 -13
  181. pip/_vendor/rich/style.py +7 -11
  182. pip/_vendor/rich/syntax.py +24 -5
  183. pip/_vendor/rich/traceback.py +32 -17
  184. pip/_vendor/tomli/LICENSE +21 -0
  185. pip/_vendor/tomli/__init__.py +1 -1
  186. pip/_vendor/tomli/_parser.py +28 -21
  187. pip/_vendor/tomli/_re.py +8 -5
  188. pip/_vendor/tomli_w/LICENSE +21 -0
  189. pip/_vendor/truststore/LICENSE +21 -0
  190. pip/_vendor/truststore/__init__.py +1 -1
  191. pip/_vendor/truststore/_api.py +15 -7
  192. pip/_vendor/truststore/_openssl.py +3 -1
  193. pip/_vendor/urllib3/LICENSE.txt +21 -0
  194. pip/_vendor/vendor.txt +11 -12
  195. {pip-25.1.1.dist-info → pip-25.3.dist-info}/METADATA +32 -11
  196. {pip-25.1.1.dist-info → pip-25.3.dist-info}/RECORD +221 -192
  197. {pip-25.1.1.dist-info → pip-25.3.dist-info}/WHEEL +1 -2
  198. pip-25.3.dist-info/entry_points.txt +4 -0
  199. {pip-25.1.1.dist-info → pip-25.3.dist-info}/licenses/AUTHORS.txt +21 -0
  200. pip-25.3.dist-info/licenses/src/pip/_vendor/cachecontrol/LICENSE.txt +13 -0
  201. pip-25.3.dist-info/licenses/src/pip/_vendor/certifi/LICENSE +20 -0
  202. pip-25.3.dist-info/licenses/src/pip/_vendor/dependency_groups/LICENSE.txt +9 -0
  203. pip-25.3.dist-info/licenses/src/pip/_vendor/distlib/LICENSE.txt +284 -0
  204. pip-25.3.dist-info/licenses/src/pip/_vendor/distro/LICENSE +202 -0
  205. pip-25.3.dist-info/licenses/src/pip/_vendor/idna/LICENSE.md +31 -0
  206. pip-25.3.dist-info/licenses/src/pip/_vendor/msgpack/COPYING +14 -0
  207. pip-25.3.dist-info/licenses/src/pip/_vendor/packaging/LICENSE +3 -0
  208. pip-25.3.dist-info/licenses/src/pip/_vendor/packaging/LICENSE.APACHE +177 -0
  209. pip-25.3.dist-info/licenses/src/pip/_vendor/packaging/LICENSE.BSD +23 -0
  210. pip-25.3.dist-info/licenses/src/pip/_vendor/pkg_resources/LICENSE +17 -0
  211. pip-25.3.dist-info/licenses/src/pip/_vendor/platformdirs/LICENSE +21 -0
  212. pip-25.3.dist-info/licenses/src/pip/_vendor/pygments/LICENSE +25 -0
  213. pip-25.3.dist-info/licenses/src/pip/_vendor/pyproject_hooks/LICENSE +21 -0
  214. pip-25.3.dist-info/licenses/src/pip/_vendor/requests/LICENSE +175 -0
  215. pip-25.3.dist-info/licenses/src/pip/_vendor/resolvelib/LICENSE +13 -0
  216. pip-25.3.dist-info/licenses/src/pip/_vendor/rich/LICENSE +19 -0
  217. pip-25.3.dist-info/licenses/src/pip/_vendor/tomli/LICENSE +21 -0
  218. pip-25.3.dist-info/licenses/src/pip/_vendor/tomli_w/LICENSE +21 -0
  219. pip-25.3.dist-info/licenses/src/pip/_vendor/truststore/LICENSE +21 -0
  220. pip-25.3.dist-info/licenses/src/pip/_vendor/urllib3/LICENSE.txt +21 -0
  221. pip/_internal/operations/build/metadata_legacy.py +0 -73
  222. pip/_internal/operations/build/wheel_legacy.py +0 -118
  223. pip/_internal/operations/install/editable_legacy.py +0 -46
  224. pip/_internal/utils/setuptools_build.py +0 -147
  225. pip/_vendor/distlib/database.py +0 -1329
  226. pip/_vendor/distlib/index.py +0 -508
  227. pip/_vendor/distlib/locators.py +0 -1295
  228. pip/_vendor/distlib/manifest.py +0 -384
  229. pip/_vendor/distlib/markers.py +0 -162
  230. pip/_vendor/distlib/metadata.py +0 -1031
  231. pip/_vendor/distlib/version.py +0 -750
  232. pip/_vendor/distlib/wheel.py +0 -1100
  233. pip/_vendor/typing_extensions.py +0 -4584
  234. pip-25.1.1.dist-info/entry_points.txt +0 -3
  235. pip-25.1.1.dist-info/top_level.txt +0 -1
  236. {pip-25.1.1.dist-info → pip-25.3.dist-info}/licenses/LICENSE.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:
@@ -720,10 +711,25 @@ class Factory:
720
711
 
721
712
  return DistributionNotFound(f"No matching distribution found for {req}")
722
713
 
714
+ def _has_any_candidates(self, project_name: str) -> bool:
715
+ """
716
+ Check if there are any candidates available for the project name.
717
+ """
718
+ return any(
719
+ self.find_candidates(
720
+ project_name,
721
+ requirements={project_name: []},
722
+ incompatibilities={},
723
+ constraint=Constraint.empty(),
724
+ prefers_installed=True,
725
+ is_satisfied_by=lambda r, c: True,
726
+ )
727
+ )
728
+
723
729
  def get_installation_error(
724
730
  self,
725
- e: "ResolutionImpossible[Requirement, Candidate]",
726
- constraints: Dict[str, Constraint],
731
+ e: ResolutionImpossible[Requirement, Candidate],
732
+ constraints: dict[str, Constraint],
727
733
  ) -> InstallationError:
728
734
  assert e.causes, "Installation error reported with no cause"
729
735
 
@@ -756,7 +762,7 @@ class Factory:
756
762
  # satisfied at once.
757
763
 
758
764
  # A couple of formatting helpers
759
- def text_join(parts: List[str]) -> str:
765
+ def text_join(parts: list[str]) -> str:
760
766
  if len(parts) == 1:
761
767
  return parts[0]
762
768
 
@@ -805,6 +811,22 @@ class Factory:
805
811
  spec = constraints[key].specifier
806
812
  msg += f"\n The user requested (constraint) {key}{spec}"
807
813
 
814
+ # Check for causes that had no candidates
815
+ causes = set()
816
+ for req, _ in e.causes:
817
+ causes.add(req.name)
818
+
819
+ no_candidates = {c for c in causes if not self._has_any_candidates(c)}
820
+ if no_candidates:
821
+ msg = (
822
+ msg
823
+ + "\n\n"
824
+ + "Additionally, some packages in these conflicts have no "
825
+ + "matching distributions available for your environment:"
826
+ + "\n "
827
+ + "\n ".join(sorted(no_candidates))
828
+ )
829
+
808
830
  msg = (
809
831
  msg
810
832
  + "\n\n"
@@ -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,16 @@ 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
+ @property
104
+ def constraints(self) -> dict[str, Constraint]:
105
+ """Public view of user-specified constraints.
106
+
107
+ Exposes the provider's constraints mapping without encouraging
108
+ external callers to reach into private attributes.
109
+ """
110
+ return self._constraints
111
+
112
+ def identify(self, requirement_or_candidate: Requirement | Candidate) -> str:
109
113
  return requirement_or_candidate.name
110
114
 
111
115
  def narrow_requirement_selection(
@@ -113,8 +117,8 @@ class PipProvider(_ProviderBase):
113
117
  identifiers: Iterable[str],
114
118
  resolutions: Mapping[str, Candidate],
115
119
  candidates: Mapping[str, Iterator[Candidate]],
116
- information: Mapping[str, Iterator["PreferenceInformation"]],
117
- backtrack_causes: Sequence["PreferenceInformation"],
120
+ information: Mapping[str, Iterator[PreferenceInformation]],
121
+ backtrack_causes: Sequence[PreferenceInformation],
118
122
  ) -> Iterable[str]:
119
123
  """Produce a subset of identifiers that should be considered before others.
120
124
 
@@ -156,9 +160,9 @@ class PipProvider(_ProviderBase):
156
160
  identifier: str,
157
161
  resolutions: Mapping[str, Candidate],
158
162
  candidates: Mapping[str, Iterator[Candidate]],
159
- information: Mapping[str, Iterable["PreferenceInformation"]],
160
- backtrack_causes: Sequence["PreferenceInformation"],
161
- ) -> "Preference":
163
+ information: Mapping[str, Iterable[PreferenceInformation]],
164
+ backtrack_causes: Sequence[PreferenceInformation],
165
+ ) -> Preference:
162
166
  """Produce a sort key for given requirement based on preference.
163
167
 
164
168
  The lower the return value is, the more preferred this group of
@@ -192,7 +196,7 @@ class PipProvider(_ProviderBase):
192
196
 
193
197
  if not has_information:
194
198
  direct = False
195
- ireqs: Tuple[Optional[InstallRequirement], ...] = ()
199
+ ireqs: tuple[InstallRequirement | None, ...] = ()
196
200
  else:
197
201
  # Go through the information and for each requirement,
198
202
  # check if it's explicit (e.g., a direct link) and get the
@@ -271,7 +275,7 @@ class PipProvider(_ProviderBase):
271
275
  )
272
276
 
273
277
  @staticmethod
274
- @lru_cache(maxsize=None)
278
+ @cache
275
279
  def is_satisfied_by(requirement: Requirement, candidate: Candidate) -> bool:
276
280
  return requirement.is_satisfied_by(candidate)
277
281
 
@@ -1,17 +1,21 @@
1
+ from __future__ import annotations
2
+
1
3
  from collections import defaultdict
4
+ from collections.abc import Mapping
2
5
  from logging import getLogger
3
- from typing import Any, DefaultDict, Optional
6
+ from typing import Any
4
7
 
5
8
  from pip._vendor.resolvelib.reporters import BaseReporter
6
9
 
7
- from .base import Candidate, Requirement
10
+ from .base import Candidate, Constraint, Requirement
8
11
 
9
12
  logger = getLogger(__name__)
10
13
 
11
14
 
12
15
  class PipReporter(BaseReporter[Requirement, Candidate, str]):
13
- def __init__(self) -> None:
14
- self.reject_count_by_package: DefaultDict[str, int] = defaultdict(int)
16
+ def __init__(self, constraints: Mapping[str, Constraint] | None = None) -> None:
17
+ self.reject_count_by_package: defaultdict[str, int] = defaultdict(int)
18
+ self._constraints = constraints or {}
15
19
 
16
20
  self._messages_at_reject_count = {
17
21
  1: (
@@ -33,25 +37,36 @@ class PipReporter(BaseReporter[Requirement, Candidate, str]):
33
37
  }
34
38
 
35
39
  def rejecting_candidate(self, criterion: Any, candidate: Candidate) -> None:
40
+ """Report a candidate being rejected.
41
+
42
+ Logs both the rejection count message (if applicable) and details about
43
+ the requirements and constraints that caused the rejection.
44
+ """
36
45
  self.reject_count_by_package[candidate.name] += 1
37
46
 
38
47
  count = self.reject_count_by_package[candidate.name]
39
- if count not in self._messages_at_reject_count:
40
- return
41
-
42
- message = self._messages_at_reject_count[count]
43
- logger.info("INFO: %s", message.format(package_name=candidate.name))
48
+ if count in self._messages_at_reject_count:
49
+ message = self._messages_at_reject_count[count]
50
+ logger.info("INFO: %s", message.format(package_name=candidate.name))
44
51
 
45
52
  msg = "Will try a different candidate, due to conflict:"
46
53
  for req_info in criterion.information:
47
54
  req, parent = req_info.requirement, req_info.parent
48
- # Inspired by Factory.get_installation_error
49
55
  msg += "\n "
50
56
  if parent:
51
57
  msg += f"{parent.name} {parent.version} depends on "
52
58
  else:
53
59
  msg += "The user requested "
54
60
  msg += req.format_for_error()
61
+
62
+ # Add any relevant constraints
63
+ if self._constraints:
64
+ name = candidate.name
65
+ constraint = self._constraints.get(name)
66
+ if constraint and constraint.specifier:
67
+ constraint_text = f"{name}{constraint.specifier}"
68
+ msg += f"\n The user requested (constraint) {constraint_text}"
69
+
55
70
  logger.debug(msg)
56
71
 
57
72
 
@@ -72,7 +87,7 @@ class PipDebuggingReporter(BaseReporter[Requirement, Candidate, str]):
72
87
  logger.info("Reporter.ending(%r)", state)
73
88
 
74
89
  def adding_requirement(
75
- self, requirement: Requirement, parent: Optional[Candidate]
90
+ self, requirement: Requirement, parent: Candidate | None
76
91
  ) -> None:
77
92
  logger.info("Reporter.adding_requirement(%r, %r)", requirement, parent)
78
93
 
@@ -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: