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,5 +1,8 @@
1
+ from __future__ import annotations
2
+
1
3
  import logging
2
- from typing import TYPE_CHECKING, Iterable, Optional, Set, Tuple
4
+ from collections.abc import Iterable
5
+ from typing import TYPE_CHECKING
3
6
 
4
7
  from pip._internal.build_env import BuildEnvironment
5
8
  from pip._internal.distributions.base import AbstractDistribution
@@ -8,7 +11,7 @@ from pip._internal.metadata import BaseDistribution
8
11
  from pip._internal.utils.subprocess import runner_with_spinner_message
9
12
 
10
13
  if TYPE_CHECKING:
11
- from pip._internal.index.package_finder import PackageFinder
14
+ from pip._internal.build_env import BuildEnvironmentInstaller
12
15
 
13
16
  logger = logging.getLogger(__name__)
14
17
 
@@ -21,7 +24,7 @@ class SourceDistribution(AbstractDistribution):
21
24
  """
22
25
 
23
26
  @property
24
- def build_tracker_id(self) -> Optional[str]:
27
+ def build_tracker_id(self) -> str | None:
25
28
  """Identify this requirement uniquely by its link."""
26
29
  assert self.req.link
27
30
  return self.req.link.url_without_fragment
@@ -31,7 +34,7 @@ class SourceDistribution(AbstractDistribution):
31
34
 
32
35
  def prepare_distribution_metadata(
33
36
  self,
34
- finder: "PackageFinder",
37
+ build_env_installer: BuildEnvironmentInstaller,
35
38
  build_isolation: bool,
36
39
  check_build_deps: bool,
37
40
  ) -> None:
@@ -43,7 +46,7 @@ class SourceDistribution(AbstractDistribution):
43
46
  if should_isolate:
44
47
  # Setup an isolated environment and install the build backend static
45
48
  # requirements in it.
46
- self._prepare_build_backend(finder)
49
+ self._prepare_build_backend(build_env_installer)
47
50
  # Check that if the requirement is editable, it either supports PEP 660 or
48
51
  # has a setup.py or a setup.cfg. This cannot be done earlier because we need
49
52
  # to setup the build backend to verify it supports build_editable, nor can
@@ -53,7 +56,7 @@ class SourceDistribution(AbstractDistribution):
53
56
  # without setup.py nor setup.cfg.
54
57
  self.req.isolated_editable_sanity_check()
55
58
  # Install the dynamic build requirements.
56
- self._install_build_reqs(finder)
59
+ self._install_build_reqs(build_env_installer)
57
60
  # Check if the current environment provides build dependencies
58
61
  should_check_deps = self.req.use_pep517 and check_build_deps
59
62
  if should_check_deps:
@@ -68,15 +71,17 @@ class SourceDistribution(AbstractDistribution):
68
71
  self._raise_missing_reqs(missing)
69
72
  self.req.prepare_metadata()
70
73
 
71
- def _prepare_build_backend(self, finder: "PackageFinder") -> None:
74
+ def _prepare_build_backend(
75
+ self, build_env_installer: BuildEnvironmentInstaller
76
+ ) -> None:
72
77
  # Isolate in a BuildEnvironment and install the build-time
73
78
  # requirements.
74
79
  pyproject_requires = self.req.pyproject_requires
75
80
  assert pyproject_requires is not None
76
81
 
77
- self.req.build_env = BuildEnvironment()
82
+ self.req.build_env = BuildEnvironment(build_env_installer)
78
83
  self.req.build_env.install_requirements(
79
- finder, pyproject_requires, "overlay", kind="build dependencies"
84
+ pyproject_requires, "overlay", kind="build dependencies", for_req=self.req
80
85
  )
81
86
  conflicting, missing = self.req.build_env.check_requirements(
82
87
  self.req.requirements_to_check
@@ -112,7 +117,9 @@ class SourceDistribution(AbstractDistribution):
112
117
  with backend.subprocess_runner(runner):
113
118
  return backend.get_requires_for_build_editable()
114
119
 
115
- def _install_build_reqs(self, finder: "PackageFinder") -> None:
120
+ def _install_build_reqs(
121
+ self, build_env_installer: BuildEnvironmentInstaller
122
+ ) -> None:
116
123
  # Install any extra build dependencies that the backend requests.
117
124
  # This must be done in a second pass, as the pyproject.toml
118
125
  # dependencies must be installed before we can call the backend.
@@ -128,11 +135,11 @@ class SourceDistribution(AbstractDistribution):
128
135
  if conflicting:
129
136
  self._raise_conflicts("the backend dependencies", conflicting)
130
137
  self.req.build_env.install_requirements(
131
- finder, missing, "normal", kind="backend dependencies"
138
+ missing, "normal", kind="backend dependencies", for_req=self.req
132
139
  )
133
140
 
134
141
  def _raise_conflicts(
135
- self, conflicting_with: str, conflicting_reqs: Set[Tuple[str, str]]
142
+ self, conflicting_with: str, conflicting_reqs: set[tuple[str, str]]
136
143
  ) -> None:
137
144
  format_string = (
138
145
  "Some build dependencies for {requirement} "
@@ -148,7 +155,7 @@ class SourceDistribution(AbstractDistribution):
148
155
  )
149
156
  raise InstallationError(error_message)
150
157
 
151
- def _raise_missing_reqs(self, missing: Set[str]) -> None:
158
+ def _raise_missing_reqs(self, missing: set[str]) -> None:
152
159
  format_string = (
153
160
  "Some build dependencies for {requirement} are missing: {missing}."
154
161
  )
@@ -1,4 +1,6 @@
1
- from typing import TYPE_CHECKING, Optional
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
2
4
 
3
5
  from pip._vendor.packaging.utils import canonicalize_name
4
6
 
@@ -10,7 +12,7 @@ from pip._internal.metadata import (
10
12
  )
11
13
 
12
14
  if TYPE_CHECKING:
13
- from pip._internal.index.package_finder import PackageFinder
15
+ from pip._internal.build_env import BuildEnvironmentInstaller
14
16
 
15
17
 
16
18
  class WheelDistribution(AbstractDistribution):
@@ -20,7 +22,7 @@ class WheelDistribution(AbstractDistribution):
20
22
  """
21
23
 
22
24
  @property
23
- def build_tracker_id(self) -> Optional[str]:
25
+ def build_tracker_id(self) -> str | None:
24
26
  return None
25
27
 
26
28
  def get_metadata_distribution(self) -> BaseDistribution:
@@ -35,7 +37,7 @@ class WheelDistribution(AbstractDistribution):
35
37
 
36
38
  def prepare_distribution_metadata(
37
39
  self,
38
- finder: "PackageFinder",
40
+ build_env_installer: BuildEnvironmentInstaller,
39
41
  build_isolation: bool,
40
42
  check_build_deps: bool,
41
43
  ) -> None:
@@ -5,6 +5,8 @@ operate. This is expected to be importable from any/all files within the
5
5
  subpackage and, thus, should not depend on them.
6
6
  """
7
7
 
8
+ from __future__ import annotations
9
+
8
10
  import configparser
9
11
  import contextlib
10
12
  import locale
@@ -12,8 +14,9 @@ import logging
12
14
  import pathlib
13
15
  import re
14
16
  import sys
17
+ from collections.abc import Iterator
15
18
  from itertools import chain, groupby, repeat
16
- from typing import TYPE_CHECKING, Dict, Iterator, List, Literal, Optional, Union
19
+ from typing import TYPE_CHECKING, Literal
17
20
 
18
21
  from pip._vendor.packaging.requirements import InvalidRequirement
19
22
  from pip._vendor.packaging.version import InvalidVersion
@@ -27,7 +30,7 @@ if TYPE_CHECKING:
27
30
  from pip._vendor.requests.models import Request, Response
28
31
 
29
32
  from pip._internal.metadata import BaseDistribution
30
- from pip._internal.models.link import Link
33
+ from pip._internal.network.download import _FileDownload
31
34
  from pip._internal.req.req_install import InstallRequirement
32
35
 
33
36
  logger = logging.getLogger(__name__)
@@ -41,7 +44,7 @@ def _is_kebab_case(s: str) -> bool:
41
44
 
42
45
 
43
46
  def _prefix_with_indent(
44
- s: Union[Text, str],
47
+ s: Text | str,
45
48
  console: Console,
46
49
  *,
47
50
  prefix: str,
@@ -77,13 +80,13 @@ class DiagnosticPipError(PipError):
77
80
  def __init__(
78
81
  self,
79
82
  *,
80
- kind: 'Literal["error", "warning"]' = "error",
81
- reference: Optional[str] = None,
82
- message: Union[str, Text],
83
- context: Optional[Union[str, Text]],
84
- hint_stmt: Optional[Union[str, Text]],
85
- note_stmt: Optional[Union[str, Text]] = None,
86
- link: Optional[str] = None,
83
+ kind: Literal["error", "warning"] = "error",
84
+ reference: str | None = None,
85
+ message: str | Text,
86
+ context: str | Text | None,
87
+ hint_stmt: str | Text | None,
88
+ note_stmt: str | Text | None = None,
89
+ link: str | None = None,
87
90
  ) -> None:
88
91
  # Ensure a proper reference is provided.
89
92
  if reference is None:
@@ -232,7 +235,7 @@ class NoneMetadataError(PipError):
232
235
 
233
236
  def __init__(
234
237
  self,
235
- dist: "BaseDistribution",
238
+ dist: BaseDistribution,
236
239
  metadata_name: str,
237
240
  ) -> None:
238
241
  """
@@ -293,8 +296,8 @@ class NetworkConnectionError(PipError):
293
296
  def __init__(
294
297
  self,
295
298
  error_msg: str,
296
- response: Optional["Response"] = None,
297
- request: Optional["Request"] = None,
299
+ response: Response | None = None,
300
+ request: Request | None = None,
298
301
  ) -> None:
299
302
  """
300
303
  Initialize NetworkConnectionError with `request` and `response`
@@ -343,7 +346,7 @@ class MetadataInconsistent(InstallationError):
343
346
  """
344
347
 
345
348
  def __init__(
346
- self, ireq: "InstallRequirement", field: str, f_val: str, m_val: str
349
+ self, ireq: InstallRequirement, field: str, f_val: str, m_val: str
347
350
  ) -> None:
348
351
  self.ireq = ireq
349
352
  self.field = field
@@ -360,7 +363,7 @@ class MetadataInconsistent(InstallationError):
360
363
  class MetadataInvalid(InstallationError):
361
364
  """Metadata is invalid."""
362
365
 
363
- def __init__(self, ireq: "InstallRequirement", error: str) -> None:
366
+ def __init__(self, ireq: InstallRequirement, error: str) -> None:
364
367
  self.ireq = ireq
365
368
  self.error = error
366
369
 
@@ -378,7 +381,7 @@ class InstallationSubprocessError(DiagnosticPipError, InstallationError):
378
381
  *,
379
382
  command_description: str,
380
383
  exit_code: int,
381
- output_lines: Optional[List[str]],
384
+ output_lines: list[str] | None,
382
385
  ) -> None:
383
386
  if output_lines is None:
384
387
  output_prompt = Text("See above for output.")
@@ -432,9 +435,9 @@ class HashErrors(InstallationError):
432
435
  """Multiple HashError instances rolled into one for reporting"""
433
436
 
434
437
  def __init__(self) -> None:
435
- self.errors: List[HashError] = []
438
+ self.errors: list[HashError] = []
436
439
 
437
- def append(self, error: "HashError") -> None:
440
+ def append(self, error: HashError) -> None:
438
441
  self.errors.append(error)
439
442
 
440
443
  def __str__(self) -> str:
@@ -468,7 +471,7 @@ class HashError(InstallationError):
468
471
 
469
472
  """
470
473
 
471
- req: Optional["InstallRequirement"] = None
474
+ req: InstallRequirement | None = None
472
475
  head = ""
473
476
  order: int = -1
474
477
 
@@ -590,7 +593,7 @@ class HashMismatch(HashError):
590
593
  "someone may have tampered with them."
591
594
  )
592
595
 
593
- def __init__(self, allowed: Dict[str, List[str]], gots: Dict[str, "_Hash"]) -> None:
596
+ def __init__(self, allowed: dict[str, list[str]], gots: dict[str, _Hash]) -> None:
594
597
  """
595
598
  :param allowed: A dict of algorithm names pointing to lists of allowed
596
599
  hex digests
@@ -615,12 +618,12 @@ class HashMismatch(HashError):
615
618
 
616
619
  """
617
620
 
618
- def hash_then_or(hash_name: str) -> "chain[str]":
621
+ def hash_then_or(hash_name: str) -> chain[str]:
619
622
  # For now, all the decent hashes have 6-char names, so we can get
620
623
  # away with hard-coding space literals.
621
624
  return chain([hash_name], repeat(" or"))
622
625
 
623
- lines: List[str] = []
626
+ lines: list[str] = []
624
627
  for hash_name, expecteds in self.allowed.items():
625
628
  prefix = hash_then_or(hash_name)
626
629
  lines.extend((f" Expected {next(prefix)} {e}") for e in expecteds)
@@ -641,8 +644,8 @@ class ConfigurationFileCouldNotBeLoaded(ConfigurationError):
641
644
  def __init__(
642
645
  self,
643
646
  reason: str = "could not be loaded",
644
- fname: Optional[str] = None,
645
- error: Optional[configparser.Error] = None,
647
+ fname: str | None = None,
648
+ error: configparser.Error | None = None,
646
649
  ) -> None:
647
650
  super().__init__(error)
648
651
  self.reason = reason
@@ -677,7 +680,7 @@ class ExternallyManagedEnvironment(DiagnosticPipError):
677
680
 
678
681
  reference = "externally-managed-environment"
679
682
 
680
- def __init__(self, error: Optional[str]) -> None:
683
+ def __init__(self, error: str | None) -> None:
681
684
  if error is None:
682
685
  context = Text(_DEFAULT_EXTERNALLY_MANAGED_ERROR)
683
686
  else:
@@ -704,7 +707,7 @@ class ExternallyManagedEnvironment(DiagnosticPipError):
704
707
  try:
705
708
  category = locale.LC_MESSAGES
706
709
  except AttributeError:
707
- lang: Optional[str] = None
710
+ lang: str | None = None
708
711
  else:
709
712
  lang, _ = locale.getlocale(category)
710
713
  if lang is not None:
@@ -719,8 +722,8 @@ class ExternallyManagedEnvironment(DiagnosticPipError):
719
722
  @classmethod
720
723
  def from_config(
721
724
  cls,
722
- config: Union[pathlib.Path, str],
723
- ) -> "ExternallyManagedEnvironment":
725
+ config: pathlib.Path | str,
726
+ ) -> ExternallyManagedEnvironment:
724
727
  parser = configparser.ConfigParser(interpolation=None)
725
728
  try:
726
729
  parser.read(config, encoding="utf-8")
@@ -741,7 +744,7 @@ class ExternallyManagedEnvironment(DiagnosticPipError):
741
744
  class UninstallMissingRecord(DiagnosticPipError):
742
745
  reference = "uninstall-no-record-file"
743
746
 
744
- def __init__(self, *, distribution: "BaseDistribution") -> None:
747
+ def __init__(self, *, distribution: BaseDistribution) -> None:
745
748
  installer = distribution.installer
746
749
  if not installer or installer == "pip":
747
750
  dep = f"{distribution.raw_name}=={distribution.version}"
@@ -768,7 +771,7 @@ class UninstallMissingRecord(DiagnosticPipError):
768
771
  class LegacyDistutilsInstall(DiagnosticPipError):
769
772
  reference = "uninstall-distutils-installed-package"
770
773
 
771
- def __init__(self, *, distribution: "BaseDistribution") -> None:
774
+ def __init__(self, *, distribution: BaseDistribution) -> None:
772
775
  super().__init__(
773
776
  message=Text(f"Cannot uninstall {distribution}"),
774
777
  context=(
@@ -786,8 +789,8 @@ class InvalidInstalledPackage(DiagnosticPipError):
786
789
  def __init__(
787
790
  self,
788
791
  *,
789
- dist: "BaseDistribution",
790
- invalid_exc: Union[InvalidRequirement, InvalidVersion],
792
+ dist: BaseDistribution,
793
+ invalid_exc: InvalidRequirement | InvalidVersion,
791
794
  ) -> None:
792
795
  installed_location = dist.installed_location
793
796
 
@@ -816,17 +819,19 @@ class IncompleteDownloadError(DiagnosticPipError):
816
819
 
817
820
  reference = "incomplete-download"
818
821
 
819
- def __init__(
820
- self, link: "Link", received: int, expected: int, *, retries: int
821
- ) -> None:
822
+ def __init__(self, download: _FileDownload) -> None:
822
823
  # Dodge circular import.
823
824
  from pip._internal.utils.misc import format_size
824
825
 
825
- download_status = f"{format_size(received)}/{format_size(expected)}"
826
- if retries:
827
- retry_status = f"after {retries} attempts "
826
+ assert download.size is not None
827
+ download_status = (
828
+ f"{format_size(download.bytes_received)}/{format_size(download.size)}"
829
+ )
830
+ if download.reattempts:
831
+ retry_status = f"after {download.reattempts + 1} attempts "
828
832
  hint = "Use --resume-retries to configure resume attempt limit."
829
833
  else:
834
+ # Download retrying is not enabled.
830
835
  retry_status = ""
831
836
  hint = "Consider using --resume-retries to enable download resumption."
832
837
  message = Text(
@@ -836,7 +841,7 @@ class IncompleteDownloadError(DiagnosticPipError):
836
841
 
837
842
  super().__init__(
838
843
  message=message,
839
- context=f"URL: {link.redacted_url}",
844
+ context=f"URL: {download.link.redacted_url}",
840
845
  hint_stmt=hint,
841
846
  note_stmt="This is an issue with network connectivity, not pip.",
842
847
  )
@@ -860,3 +865,17 @@ class ResolutionTooDeepError(DiagnosticPipError):
860
865
  ),
861
866
  link="https://pip.pypa.io/en/stable/topics/dependency-resolution/#handling-resolution-too-deep-errors",
862
867
  )
868
+
869
+
870
+ class InstallWheelBuildError(DiagnosticPipError):
871
+ reference = "failed-wheel-build-for-install"
872
+
873
+ def __init__(self, failed: list[InstallRequirement]) -> None:
874
+ super().__init__(
875
+ message=(
876
+ "Failed to build installable wheels for some "
877
+ "pyproject.toml based projects"
878
+ ),
879
+ context=", ".join(r.name for r in failed), # type: ignore
880
+ hint_stmt=None,
881
+ )
@@ -2,6 +2,8 @@
2
2
  The main purpose of this module is to expose LinkCollector.collect_sources().
3
3
  """
4
4
 
5
+ from __future__ import annotations
6
+
5
7
  import collections
6
8
  import email.message
7
9
  import functools
@@ -11,21 +13,14 @@ import logging
11
13
  import os
12
14
  import urllib.parse
13
15
  import urllib.request
16
+ from collections.abc import Iterable, MutableMapping, Sequence
14
17
  from dataclasses import dataclass
15
18
  from html.parser import HTMLParser
16
19
  from optparse import Values
17
20
  from typing import (
18
21
  Callable,
19
- Dict,
20
- Iterable,
21
- List,
22
- MutableMapping,
23
22
  NamedTuple,
24
- Optional,
25
23
  Protocol,
26
- Sequence,
27
- Tuple,
28
- Union,
29
24
  )
30
25
 
31
26
  from pip._vendor import requests
@@ -48,7 +43,7 @@ logger = logging.getLogger(__name__)
48
43
  ResponseHeaders = MutableMapping[str, str]
49
44
 
50
45
 
51
- def _match_vcs_scheme(url: str) -> Optional[str]:
46
+ def _match_vcs_scheme(url: str) -> str | None:
52
47
  """Look for VCS schemes in the URL.
53
48
 
54
49
  Returns the matched VCS scheme, or None if there's no match.
@@ -173,7 +168,7 @@ def _get_simple_response(url: str, session: PipSession) -> Response:
173
168
  return resp
174
169
 
175
170
 
176
- def _get_encoding_from_headers(headers: ResponseHeaders) -> Optional[str]:
171
+ def _get_encoding_from_headers(headers: ResponseHeaders) -> str | None:
177
172
  """Determine if we have any encoding information in our headers."""
178
173
  if headers and "Content-Type" in headers:
179
174
  m = email.message.Message()
@@ -185,7 +180,7 @@ def _get_encoding_from_headers(headers: ResponseHeaders) -> Optional[str]:
185
180
 
186
181
 
187
182
  class CacheablePageContent:
188
- def __init__(self, page: "IndexContent") -> None:
183
+ def __init__(self, page: IndexContent) -> None:
189
184
  assert page.cache_link_parsing
190
185
  self.page = page
191
186
 
@@ -197,7 +192,7 @@ class CacheablePageContent:
197
192
 
198
193
 
199
194
  class ParseLinks(Protocol):
200
- def __call__(self, page: "IndexContent") -> Iterable[Link]: ...
195
+ def __call__(self, page: IndexContent) -> Iterable[Link]: ...
201
196
 
202
197
 
203
198
  def with_cached_index_content(fn: ParseLinks) -> ParseLinks:
@@ -207,12 +202,12 @@ def with_cached_index_content(fn: ParseLinks) -> ParseLinks:
207
202
  `page` has `page.cache_link_parsing == False`.
208
203
  """
209
204
 
210
- @functools.lru_cache(maxsize=None)
211
- def wrapper(cacheable_page: CacheablePageContent) -> List[Link]:
205
+ @functools.cache
206
+ def wrapper(cacheable_page: CacheablePageContent) -> list[Link]:
212
207
  return list(fn(cacheable_page.page))
213
208
 
214
209
  @functools.wraps(fn)
215
- def wrapper_wrapper(page: "IndexContent") -> List[Link]:
210
+ def wrapper_wrapper(page: IndexContent) -> list[Link]:
216
211
  if page.cache_link_parsing:
217
212
  return wrapper(CacheablePageContent(page))
218
213
  return list(fn(page))
@@ -221,7 +216,7 @@ def with_cached_index_content(fn: ParseLinks) -> ParseLinks:
221
216
 
222
217
 
223
218
  @with_cached_index_content
224
- def parse_links(page: "IndexContent") -> Iterable[Link]:
219
+ def parse_links(page: IndexContent) -> Iterable[Link]:
225
220
  """
226
221
  Parse a Simple API's Index Content, and yield its anchor elements as Link objects.
227
222
  """
@@ -262,7 +257,7 @@ class IndexContent:
262
257
 
263
258
  content: bytes
264
259
  content_type: str
265
- encoding: Optional[str]
260
+ encoding: str | None
266
261
  url: str
267
262
  cache_link_parsing: bool = True
268
263
 
@@ -280,10 +275,10 @@ class HTMLLinkParser(HTMLParser):
280
275
  super().__init__(convert_charrefs=True)
281
276
 
282
277
  self.url: str = url
283
- self.base_url: Optional[str] = None
284
- self.anchors: List[Dict[str, Optional[str]]] = []
278
+ self.base_url: str | None = None
279
+ self.anchors: list[dict[str, str | None]] = []
285
280
 
286
- def handle_starttag(self, tag: str, attrs: List[Tuple[str, Optional[str]]]) -> None:
281
+ def handle_starttag(self, tag: str, attrs: list[tuple[str, str | None]]) -> None:
287
282
  if tag == "base" and self.base_url is None:
288
283
  href = self.get_href(attrs)
289
284
  if href is not None:
@@ -291,7 +286,7 @@ class HTMLLinkParser(HTMLParser):
291
286
  elif tag == "a":
292
287
  self.anchors.append(dict(attrs))
293
288
 
294
- def get_href(self, attrs: List[Tuple[str, Optional[str]]]) -> Optional[str]:
289
+ def get_href(self, attrs: list[tuple[str, str | None]]) -> str | None:
295
290
  for name, value in attrs:
296
291
  if name == "href":
297
292
  return value
@@ -300,8 +295,8 @@ class HTMLLinkParser(HTMLParser):
300
295
 
301
296
  def _handle_get_simple_fail(
302
297
  link: Link,
303
- reason: Union[str, Exception],
304
- meth: Optional[Callable[..., None]] = None,
298
+ reason: str | Exception,
299
+ meth: Callable[..., None] | None = None,
305
300
  ) -> None:
306
301
  if meth is None:
307
302
  meth = logger.debug
@@ -321,7 +316,7 @@ def _make_index_content(
321
316
  )
322
317
 
323
318
 
324
- def _get_index_content(link: Link, *, session: PipSession) -> Optional["IndexContent"]:
319
+ def _get_index_content(link: Link, *, session: PipSession) -> IndexContent | None:
325
320
  url = link.url.split("#", 1)[0]
326
321
 
327
322
  # Check for VCS schemes that do not support lookup as web pages.
@@ -383,8 +378,8 @@ def _get_index_content(link: Link, *, session: PipSession) -> Optional["IndexCon
383
378
 
384
379
 
385
380
  class CollectedSources(NamedTuple):
386
- find_links: Sequence[Optional[LinkSource]]
387
- index_urls: Sequence[Optional[LinkSource]]
381
+ find_links: Sequence[LinkSource | None]
382
+ index_urls: Sequence[LinkSource | None]
388
383
 
389
384
 
390
385
  class LinkCollector:
@@ -409,7 +404,7 @@ class LinkCollector:
409
404
  session: PipSession,
410
405
  options: Values,
411
406
  suppress_no_index: bool = False,
412
- ) -> "LinkCollector":
407
+ ) -> LinkCollector:
413
408
  """
414
409
  :param session: The Session to use to make requests.
415
410
  :param suppress_no_index: Whether to ignore the --no-index option
@@ -438,10 +433,10 @@ class LinkCollector:
438
433
  return link_collector
439
434
 
440
435
  @property
441
- def find_links(self) -> List[str]:
436
+ def find_links(self) -> list[str]:
442
437
  return self.search_scope.find_links
443
438
 
444
- def fetch_response(self, location: Link) -> Optional[IndexContent]:
439
+ def fetch_response(self, location: Link) -> IndexContent | None:
445
440
  """
446
441
  Fetch an HTML page containing package links.
447
442
  """