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
@@ -5,11 +5,15 @@ need PackageFinder capability don't unnecessarily import the
5
5
  PackageFinder machinery and all its vendored dependencies, etc.
6
6
  """
7
7
 
8
+ from __future__ import annotations
9
+
8
10
  import logging
11
+ import os
9
12
  from functools import partial
10
13
  from optparse import Values
11
- from typing import Any, List, Optional, Tuple
14
+ from typing import Any, Callable, TypeVar
12
15
 
16
+ from pip._internal.build_env import SubprocessBuildEnvironmentInstaller
13
17
  from pip._internal.cache import WheelCache
14
18
  from pip._internal.cli import cmdoptions
15
19
  from pip._internal.cli.index_command import IndexGroupCommand
@@ -41,6 +45,16 @@ from pip._internal.utils.temp_dir import (
41
45
  logger = logging.getLogger(__name__)
42
46
 
43
47
 
48
+ def should_ignore_regular_constraints(options: Values) -> bool:
49
+ """
50
+ Check if regular constraints should be ignored because
51
+ we are in a isolated build process and build constraints
52
+ feature is enabled but no build constraints were passed.
53
+ """
54
+
55
+ return os.environ.get("_PIP_IN_BUILD_IGNORE_CONSTRAINTS") == "1"
56
+
57
+
44
58
  KEEPABLE_TEMPDIR_TYPES = [
45
59
  tempdir_kinds.BUILD_ENV,
46
60
  tempdir_kinds.EPHEM_WHEEL_CACHE,
@@ -48,7 +62,12 @@ KEEPABLE_TEMPDIR_TYPES = [
48
62
  ]
49
63
 
50
64
 
51
- def with_cleanup(func: Any) -> Any:
65
+ _CommandT = TypeVar("_CommandT", bound="RequirementCommand")
66
+
67
+
68
+ def with_cleanup(
69
+ func: Callable[[_CommandT, Values, list[str]], int],
70
+ ) -> Callable[[_CommandT, Values, list[str]], int]:
52
71
  """Decorator for common logic related to managing temporary
53
72
  directories.
54
73
  """
@@ -57,9 +76,7 @@ def with_cleanup(func: Any) -> Any:
57
76
  for t in KEEPABLE_TEMPDIR_TYPES:
58
77
  registry.set_delete(t, False)
59
78
 
60
- def wrapper(
61
- self: RequirementCommand, options: Values, args: List[Any]
62
- ) -> Optional[int]:
79
+ def wrapper(self: _CommandT, options: Values, args: list[str]) -> int:
63
80
  assert self.tempdir_registry is not None
64
81
  if options.no_clean:
65
82
  configure_tempdir_registry(self.tempdir_registry)
@@ -100,7 +117,7 @@ class RequirementCommand(IndexGroupCommand):
100
117
  session: PipSession,
101
118
  finder: PackageFinder,
102
119
  use_user_site: bool,
103
- download_dir: Optional[str] = None,
120
+ download_dir: str | None = None,
104
121
  verbosity: int = 0,
105
122
  ) -> RequirementPreparer:
106
123
  """
@@ -129,11 +146,22 @@ class RequirementCommand(IndexGroupCommand):
129
146
  "fast-deps has no effect when used with the legacy resolver."
130
147
  )
131
148
 
149
+ # Handle build constraints
150
+ build_constraints = getattr(options, "build_constraints", [])
151
+ build_constraint_feature_enabled = (
152
+ "build-constraint" in options.features_enabled
153
+ )
154
+
132
155
  return RequirementPreparer(
133
156
  build_dir=temp_build_dir_path,
134
157
  src_dir=options.src_dir,
135
158
  download_dir=download_dir,
136
159
  build_isolation=options.build_isolation,
160
+ build_isolation_installer=SubprocessBuildEnvironmentInstaller(
161
+ finder,
162
+ build_constraints=build_constraints,
163
+ build_constraint_feature_enabled=build_constraint_feature_enabled,
164
+ ),
137
165
  check_build_deps=options.check_build_deps,
138
166
  build_tracker=build_tracker,
139
167
  session=session,
@@ -153,14 +181,13 @@ class RequirementCommand(IndexGroupCommand):
153
181
  preparer: RequirementPreparer,
154
182
  finder: PackageFinder,
155
183
  options: Values,
156
- wheel_cache: Optional[WheelCache] = None,
184
+ wheel_cache: WheelCache | None = None,
157
185
  use_user_site: bool = False,
158
186
  ignore_installed: bool = True,
159
187
  ignore_requires_python: bool = False,
160
188
  force_reinstall: bool = False,
161
189
  upgrade_strategy: str = "to-satisfy-only",
162
- use_pep517: Optional[bool] = None,
163
- py_version_info: Optional[Tuple[int, ...]] = None,
190
+ py_version_info: tuple[int, ...] | None = None,
164
191
  ) -> BaseResolver:
165
192
  """
166
193
  Create a Resolver instance for the given parameters.
@@ -168,7 +195,6 @@ class RequirementCommand(IndexGroupCommand):
168
195
  make_install_req = partial(
169
196
  install_req_from_req_string,
170
197
  isolated=options.isolated_mode,
171
- use_pep517=use_pep517,
172
198
  )
173
199
  resolver_variant = cls.determine_resolver_variant(options)
174
200
  # The long import name and duplicated invocation is needed to convince
@@ -208,36 +234,37 @@ class RequirementCommand(IndexGroupCommand):
208
234
 
209
235
  def get_requirements(
210
236
  self,
211
- args: List[str],
237
+ args: list[str],
212
238
  options: Values,
213
239
  finder: PackageFinder,
214
240
  session: PipSession,
215
- ) -> List[InstallRequirement]:
241
+ ) -> list[InstallRequirement]:
216
242
  """
217
243
  Parse command-line arguments into the corresponding requirements.
218
244
  """
219
- requirements: List[InstallRequirement] = []
220
- for filename in options.constraints:
221
- for parsed_req in parse_requirements(
222
- filename,
223
- constraint=True,
224
- finder=finder,
225
- options=options,
226
- session=session,
227
- ):
228
- req_to_add = install_req_from_parsed_requirement(
229
- parsed_req,
230
- isolated=options.isolated_mode,
231
- user_supplied=False,
232
- )
233
- requirements.append(req_to_add)
245
+ requirements: list[InstallRequirement] = []
246
+
247
+ if not should_ignore_regular_constraints(options):
248
+ for filename in options.constraints:
249
+ for parsed_req in parse_requirements(
250
+ filename,
251
+ constraint=True,
252
+ finder=finder,
253
+ options=options,
254
+ session=session,
255
+ ):
256
+ req_to_add = install_req_from_parsed_requirement(
257
+ parsed_req,
258
+ isolated=options.isolated_mode,
259
+ user_supplied=False,
260
+ )
261
+ requirements.append(req_to_add)
234
262
 
235
263
  for req in args:
236
264
  req_to_add = install_req_from_line(
237
265
  req,
238
266
  comes_from=None,
239
267
  isolated=options.isolated_mode,
240
- use_pep517=options.use_pep517,
241
268
  user_supplied=True,
242
269
  config_settings=getattr(options, "config_settings", None),
243
270
  )
@@ -248,7 +275,6 @@ class RequirementCommand(IndexGroupCommand):
248
275
  req_to_add = install_req_from_req_string(
249
276
  req,
250
277
  isolated=options.isolated_mode,
251
- use_pep517=options.use_pep517,
252
278
  user_supplied=True,
253
279
  )
254
280
  requirements.append(req_to_add)
@@ -258,7 +284,6 @@ class RequirementCommand(IndexGroupCommand):
258
284
  req,
259
285
  user_supplied=True,
260
286
  isolated=options.isolated_mode,
261
- use_pep517=options.use_pep517,
262
287
  config_settings=getattr(options, "config_settings", None),
263
288
  )
264
289
  requirements.append(req_to_add)
@@ -271,7 +296,6 @@ class RequirementCommand(IndexGroupCommand):
271
296
  req_to_add = install_req_from_parsed_requirement(
272
297
  parsed_req,
273
298
  isolated=options.isolated_mode,
274
- use_pep517=options.use_pep517,
275
299
  user_supplied=True,
276
300
  config_settings=(
277
301
  parsed_req.options.get("config_settings")
@@ -322,8 +346,8 @@ class RequirementCommand(IndexGroupCommand):
322
346
  self,
323
347
  options: Values,
324
348
  session: PipSession,
325
- target_python: Optional[TargetPython] = None,
326
- ignore_requires_python: Optional[bool] = None,
349
+ target_python: TargetPython | None = None,
350
+ ignore_requires_python: bool | None = None,
327
351
  ) -> PackageFinder:
328
352
  """
329
353
  Create a package finder appropriate to this requirement command.
@@ -1,15 +1,31 @@
1
+ from __future__ import annotations
2
+
1
3
  import contextlib
2
4
  import itertools
3
5
  import logging
4
6
  import sys
5
7
  import time
6
- from typing import IO, Generator, Optional
8
+ from collections.abc import Generator
9
+ from typing import IO, Final
10
+
11
+ from pip._vendor.rich.console import (
12
+ Console,
13
+ ConsoleOptions,
14
+ RenderableType,
15
+ RenderResult,
16
+ )
17
+ from pip._vendor.rich.live import Live
18
+ from pip._vendor.rich.measure import Measurement
19
+ from pip._vendor.rich.text import Text
7
20
 
8
21
  from pip._internal.utils.compat import WINDOWS
9
- from pip._internal.utils.logging import get_indentation
22
+ from pip._internal.utils.logging import get_console, get_indentation
10
23
 
11
24
  logger = logging.getLogger(__name__)
12
25
 
26
+ SPINNER_CHARS: Final = r"-\|/"
27
+ SPINS_PER_SECOND: Final = 8
28
+
13
29
 
14
30
  class SpinnerInterface:
15
31
  def spin(self) -> None:
@@ -23,10 +39,10 @@ class InteractiveSpinner(SpinnerInterface):
23
39
  def __init__(
24
40
  self,
25
41
  message: str,
26
- file: Optional[IO[str]] = None,
27
- spin_chars: str = "-\\|/",
42
+ file: IO[str] | None = None,
43
+ spin_chars: str = SPINNER_CHARS,
28
44
  # Empirically, 8 updates/second looks nice
29
- min_update_interval_seconds: float = 0.125,
45
+ min_update_interval_seconds: float = 1 / SPINS_PER_SECOND,
30
46
  ):
31
47
  self._message = message
32
48
  if file is None:
@@ -136,6 +152,66 @@ def open_spinner(message: str) -> Generator[SpinnerInterface, None, None]:
136
152
  spinner.finish("done")
137
153
 
138
154
 
155
+ class _PipRichSpinner:
156
+ """
157
+ Custom rich spinner that matches the style of the legacy spinners.
158
+
159
+ (*) Updates will be handled in a background thread by a rich live panel
160
+ which will call render() automatically at the appropriate time.
161
+ """
162
+
163
+ def __init__(self, label: str) -> None:
164
+ self.label = label
165
+ self._spin_cycle = itertools.cycle(SPINNER_CHARS)
166
+ self._spinner_text = ""
167
+ self._finished = False
168
+ self._indent = get_indentation() * " "
169
+
170
+ def __rich_console__(
171
+ self, console: Console, options: ConsoleOptions
172
+ ) -> RenderResult:
173
+ yield self.render()
174
+
175
+ def __rich_measure__(
176
+ self, console: Console, options: ConsoleOptions
177
+ ) -> Measurement:
178
+ text = self.render()
179
+ return Measurement.get(console, options, text)
180
+
181
+ def render(self) -> RenderableType:
182
+ if not self._finished:
183
+ self._spinner_text = next(self._spin_cycle)
184
+
185
+ return Text.assemble(self._indent, self.label, " ... ", self._spinner_text)
186
+
187
+ def finish(self, status: str) -> None:
188
+ """Stop spinning and set a final status message."""
189
+ self._spinner_text = status
190
+ self._finished = True
191
+
192
+
193
+ @contextlib.contextmanager
194
+ def open_rich_spinner(label: str, console: Console | None = None) -> Generator[None]:
195
+ if not logger.isEnabledFor(logging.INFO):
196
+ # Don't show spinner if --quiet is given.
197
+ yield
198
+ return
199
+
200
+ console = console or get_console()
201
+ spinner = _PipRichSpinner(label)
202
+ with Live(spinner, refresh_per_second=SPINS_PER_SECOND, console=console):
203
+ try:
204
+ yield
205
+ except KeyboardInterrupt:
206
+ spinner.finish("canceled")
207
+ raise
208
+ except Exception:
209
+ spinner.finish("error")
210
+ raise
211
+ else:
212
+ spinner.finish("done")
213
+
214
+
139
215
  HIDE_CURSOR = "\x1b[?25l"
140
216
  SHOW_CURSOR = "\x1b[?25h"
141
217
 
@@ -2,9 +2,11 @@
2
2
  Package containing all pip commands
3
3
  """
4
4
 
5
+ from __future__ import annotations
6
+
5
7
  import importlib
6
8
  from collections import namedtuple
7
- from typing import Any, Dict, Optional
9
+ from typing import Any
8
10
 
9
11
  from pip._internal.cli.base_command import Command
10
12
 
@@ -17,7 +19,7 @@ CommandInfo = namedtuple("CommandInfo", "module_path, class_name, summary")
17
19
  # Even though the module path starts with the same "pip._internal.commands"
18
20
  # prefix, the full path makes testing easier (specifically when modifying
19
21
  # `commands_dict` in test setup / teardown).
20
- commands_dict: Dict[str, CommandInfo] = {
22
+ commands_dict: dict[str, CommandInfo] = {
21
23
  "install": CommandInfo(
22
24
  "pip._internal.commands.install",
23
25
  "InstallCommand",
@@ -123,7 +125,7 @@ def create_command(name: str, **kwargs: Any) -> Command:
123
125
  return command
124
126
 
125
127
 
126
- def get_similar_commands(name: str) -> Optional[str]:
128
+ def get_similar_commands(name: str) -> str | None:
127
129
  """Command name auto-correct."""
128
130
  from difflib import get_close_matches
129
131
 
@@ -1,7 +1,7 @@
1
1
  import os
2
2
  import textwrap
3
3
  from optparse import Values
4
- from typing import Any, List
4
+ from typing import Callable
5
5
 
6
6
  from pip._internal.cli.base_command import Command
7
7
  from pip._internal.cli.status_codes import ERROR, SUCCESS
@@ -49,8 +49,8 @@ class CacheCommand(Command):
49
49
 
50
50
  self.parser.insert_option_group(0, self.cmd_opts)
51
51
 
52
- def run(self, options: Values, args: List[str]) -> int:
53
- handlers = {
52
+ def handler_map(self) -> dict[str, Callable[[Values, list[str]], None]]:
53
+ return {
54
54
  "dir": self.get_cache_dir,
55
55
  "info": self.get_cache_info,
56
56
  "list": self.list_cache_items,
@@ -58,15 +58,18 @@ class CacheCommand(Command):
58
58
  "purge": self.purge_cache,
59
59
  }
60
60
 
61
+ def run(self, options: Values, args: list[str]) -> int:
62
+ handler_map = self.handler_map()
63
+
61
64
  if not options.cache_dir:
62
65
  logger.error("pip cache commands can not function since cache is disabled.")
63
66
  return ERROR
64
67
 
65
68
  # Determine action
66
- if not args or args[0] not in handlers:
69
+ if not args or args[0] not in handler_map:
67
70
  logger.error(
68
71
  "Need an action (%s) to perform.",
69
- ", ".join(sorted(handlers)),
72
+ ", ".join(sorted(handler_map)),
70
73
  )
71
74
  return ERROR
72
75
 
@@ -74,20 +77,20 @@ class CacheCommand(Command):
74
77
 
75
78
  # Error handling happens here, not in the action-handlers.
76
79
  try:
77
- handlers[action](options, args[1:])
80
+ handler_map[action](options, args[1:])
78
81
  except PipError as e:
79
82
  logger.error(e.args[0])
80
83
  return ERROR
81
84
 
82
85
  return SUCCESS
83
86
 
84
- def get_cache_dir(self, options: Values, args: List[Any]) -> None:
87
+ def get_cache_dir(self, options: Values, args: list[str]) -> None:
85
88
  if args:
86
89
  raise CommandError("Too many arguments")
87
90
 
88
91
  logger.info(options.cache_dir)
89
92
 
90
- def get_cache_info(self, options: Values, args: List[Any]) -> None:
93
+ def get_cache_info(self, options: Values, args: list[str]) -> None:
91
94
  if args:
92
95
  raise CommandError("Too many arguments")
93
96
 
@@ -129,7 +132,7 @@ class CacheCommand(Command):
129
132
 
130
133
  logger.info(message)
131
134
 
132
- def list_cache_items(self, options: Values, args: List[Any]) -> None:
135
+ def list_cache_items(self, options: Values, args: list[str]) -> None:
133
136
  if len(args) > 1:
134
137
  raise CommandError("Too many arguments")
135
138
 
@@ -144,7 +147,7 @@ class CacheCommand(Command):
144
147
  else:
145
148
  self.format_for_abspath(files)
146
149
 
147
- def format_for_human(self, files: List[str]) -> None:
150
+ def format_for_human(self, files: list[str]) -> None:
148
151
  if not files:
149
152
  logger.info("No locally built wheels cached.")
150
153
  return
@@ -157,11 +160,11 @@ class CacheCommand(Command):
157
160
  logger.info("Cache contents:\n")
158
161
  logger.info("\n".join(sorted(results)))
159
162
 
160
- def format_for_abspath(self, files: List[str]) -> None:
163
+ def format_for_abspath(self, files: list[str]) -> None:
161
164
  if files:
162
165
  logger.info("\n".join(sorted(files)))
163
166
 
164
- def remove_cache_items(self, options: Values, args: List[Any]) -> None:
167
+ def remove_cache_items(self, options: Values, args: list[str]) -> None:
165
168
  if len(args) > 1:
166
169
  raise CommandError("Too many arguments")
167
170
 
@@ -188,7 +191,7 @@ class CacheCommand(Command):
188
191
  logger.verbose("Removed %s", filename)
189
192
  logger.info("Files removed: %s (%s)", len(files), format_size(bytes_removed))
190
193
 
191
- def purge_cache(self, options: Values, args: List[Any]) -> None:
194
+ def purge_cache(self, options: Values, args: list[str]) -> None:
192
195
  if args:
193
196
  raise CommandError("Too many arguments")
194
197
 
@@ -197,14 +200,14 @@ class CacheCommand(Command):
197
200
  def _cache_dir(self, options: Values, subdir: str) -> str:
198
201
  return os.path.join(options.cache_dir, subdir)
199
202
 
200
- def _find_http_files(self, options: Values) -> List[str]:
203
+ def _find_http_files(self, options: Values) -> list[str]:
201
204
  old_http_dir = self._cache_dir(options, "http")
202
205
  new_http_dir = self._cache_dir(options, "http-v2")
203
206
  return filesystem.find_files(old_http_dir, "*") + filesystem.find_files(
204
207
  new_http_dir, "*"
205
208
  )
206
209
 
207
- def _find_wheels(self, options: Values, pattern: str) -> List[str]:
210
+ def _find_wheels(self, options: Values, pattern: str) -> list[str]:
208
211
  wheel_dir = self._cache_dir(options, "wheels")
209
212
 
210
213
  # The wheel filename format, as specified in PEP 427, is:
@@ -1,6 +1,5 @@
1
1
  import logging
2
2
  from optparse import Values
3
- from typing import List
4
3
 
5
4
  from pip._internal.cli.base_command import Command
6
5
  from pip._internal.cli.status_codes import ERROR, SUCCESS
@@ -23,7 +22,7 @@ class CheckCommand(Command):
23
22
  usage = """
24
23
  %prog [options]"""
25
24
 
26
- def run(self, options: Values, args: List[str]) -> int:
25
+ def run(self, options: Values, args: list[str]) -> int:
27
26
  package_set, parsing_probs = create_package_set_from_installed()
28
27
  missing, conflicting = check_package_set(package_set)
29
28
  unsupported = list(
@@ -1,7 +1,6 @@
1
1
  import sys
2
2
  import textwrap
3
3
  from optparse import Values
4
- from typing import List
5
4
 
6
5
  from pip._internal.cli.base_command import Command
7
6
  from pip._internal.cli.status_codes import SUCCESS
@@ -119,7 +118,7 @@ class CompletionCommand(Command):
119
118
 
120
119
  self.parser.insert_option_group(0, self.cmd_opts)
121
120
 
122
- def run(self, options: Values, args: List[str]) -> int:
121
+ def run(self, options: Values, args: list[str]) -> int:
123
122
  """Prints the completion code of the given shell"""
124
123
  shells = COMPLETION_SCRIPTS.keys()
125
124
  shell_options = ["--" + shell for shell in sorted(shells)]
@@ -1,8 +1,10 @@
1
+ from __future__ import annotations
2
+
1
3
  import logging
2
4
  import os
3
5
  import subprocess
4
6
  from optparse import Values
5
- from typing import Any, List, Optional
7
+ from typing import Any, Callable
6
8
 
7
9
  from pip._internal.cli.base_command import Command
8
10
  from pip._internal.cli.status_codes import ERROR, SUCCESS
@@ -93,8 +95,8 @@ class ConfigurationCommand(Command):
93
95
 
94
96
  self.parser.insert_option_group(0, self.cmd_opts)
95
97
 
96
- def run(self, options: Values, args: List[str]) -> int:
97
- handlers = {
98
+ def handler_map(self) -> dict[str, Callable[[Values, list[str]], None]]:
99
+ return {
98
100
  "list": self.list_values,
99
101
  "edit": self.open_in_editor,
100
102
  "get": self.get_name,
@@ -103,11 +105,14 @@ class ConfigurationCommand(Command):
103
105
  "debug": self.list_config_values,
104
106
  }
105
107
 
108
+ def run(self, options: Values, args: list[str]) -> int:
109
+ handler_map = self.handler_map()
110
+
106
111
  # Determine action
107
- if not args or args[0] not in handlers:
112
+ if not args or args[0] not in handler_map:
108
113
  logger.error(
109
114
  "Need an action (%s) to perform.",
110
- ", ".join(sorted(handlers)),
115
+ ", ".join(sorted(handler_map)),
111
116
  )
112
117
  return ERROR
113
118
 
@@ -131,14 +136,14 @@ class ConfigurationCommand(Command):
131
136
 
132
137
  # Error handling happens here, not in the action-handlers.
133
138
  try:
134
- handlers[action](options, args[1:])
139
+ handler_map[action](options, args[1:])
135
140
  except PipError as e:
136
141
  logger.error(e.args[0])
137
142
  return ERROR
138
143
 
139
144
  return SUCCESS
140
145
 
141
- def _determine_file(self, options: Values, need_value: bool) -> Optional[Kind]:
146
+ def _determine_file(self, options: Values, need_value: bool) -> Kind | None:
142
147
  file_options = [
143
148
  key
144
149
  for key, value in (
@@ -168,31 +173,32 @@ class ConfigurationCommand(Command):
168
173
  "(--user, --site, --global) to perform."
169
174
  )
170
175
 
171
- def list_values(self, options: Values, args: List[str]) -> None:
176
+ def list_values(self, options: Values, args: list[str]) -> None:
172
177
  self._get_n_args(args, "list", n=0)
173
178
 
174
179
  for key, value in sorted(self.configuration.items()):
175
- write_output("%s=%r", key, value)
180
+ for key, value in sorted(value.items()):
181
+ write_output("%s=%r", key, value)
176
182
 
177
- def get_name(self, options: Values, args: List[str]) -> None:
183
+ def get_name(self, options: Values, args: list[str]) -> None:
178
184
  key = self._get_n_args(args, "get [name]", n=1)
179
185
  value = self.configuration.get_value(key)
180
186
 
181
187
  write_output("%s", value)
182
188
 
183
- def set_name_value(self, options: Values, args: List[str]) -> None:
189
+ def set_name_value(self, options: Values, args: list[str]) -> None:
184
190
  key, value = self._get_n_args(args, "set [name] [value]", n=2)
185
191
  self.configuration.set_value(key, value)
186
192
 
187
193
  self._save_configuration()
188
194
 
189
- def unset_name(self, options: Values, args: List[str]) -> None:
195
+ def unset_name(self, options: Values, args: list[str]) -> None:
190
196
  key = self._get_n_args(args, "unset [name]", n=1)
191
197
  self.configuration.unset_value(key)
192
198
 
193
199
  self._save_configuration()
194
200
 
195
- def list_config_values(self, options: Values, args: List[str]) -> None:
201
+ def list_config_values(self, options: Values, args: list[str]) -> None:
196
202
  """List config key-value pairs across different config files"""
197
203
  self._get_n_args(args, "debug", n=0)
198
204
 
@@ -206,13 +212,15 @@ class ConfigurationCommand(Command):
206
212
  file_exists = os.path.exists(fname)
207
213
  write_output("%s, exists: %r", fname, file_exists)
208
214
  if file_exists:
209
- self.print_config_file_values(variant)
215
+ self.print_config_file_values(variant, fname)
210
216
 
211
- def print_config_file_values(self, variant: Kind) -> None:
217
+ def print_config_file_values(self, variant: Kind, fname: str) -> None:
212
218
  """Get key-value pairs from the file of a variant"""
213
219
  for name, value in self.configuration.get_values_in_config(variant).items():
214
220
  with indent_log():
215
- write_output("%s: %s", name, value)
221
+ if name == fname:
222
+ for confname, confvalue in value.items():
223
+ write_output("%s: %s", confname, confvalue)
216
224
 
217
225
  def print_env_var_values(self) -> None:
218
226
  """Get key-values pairs present as environment variables"""
@@ -222,7 +230,7 @@ class ConfigurationCommand(Command):
222
230
  env_var = f"PIP_{key.upper()}"
223
231
  write_output("%s=%r", env_var, value)
224
232
 
225
- def open_in_editor(self, options: Values, args: List[str]) -> None:
233
+ def open_in_editor(self, options: Values, args: list[str]) -> None:
226
234
  editor = self._determine_editor(options)
227
235
 
228
236
  fname = self.configuration.get_file_to_edit()
@@ -244,7 +252,7 @@ class ConfigurationCommand(Command):
244
252
  except subprocess.CalledProcessError as e:
245
253
  raise PipError(f"Editor Subprocess exited with exit code {e.returncode}")
246
254
 
247
- def _get_n_args(self, args: List[str], example: str, n: int) -> Any:
255
+ def _get_n_args(self, args: list[str], example: str, n: int) -> Any:
248
256
  """Helper to make sure the command got the right number of arguments"""
249
257
  if len(args) != n:
250
258
  msg = (