pip 25.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 (203) 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 +45 -40
  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 +53 -44
  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 +9 -8
  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 +54 -44
  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/dependency_groups/_implementation.py +7 -11
  133. pip/_vendor/distlib/__init__.py +2 -2
  134. pip/_vendor/distlib/scripts.py +1 -1
  135. pip/_vendor/msgpack/__init__.py +2 -2
  136. pip/_vendor/pkg_resources/__init__.py +1 -1
  137. pip/_vendor/platformdirs/version.py +2 -2
  138. pip/_vendor/pygments/__init__.py +1 -1
  139. pip/_vendor/requests/__version__.py +2 -2
  140. pip/_vendor/requests/compat.py +12 -0
  141. pip/_vendor/requests/models.py +3 -1
  142. pip/_vendor/requests/utils.py +6 -16
  143. pip/_vendor/resolvelib/__init__.py +3 -3
  144. pip/_vendor/resolvelib/reporters.py +1 -1
  145. pip/_vendor/resolvelib/resolvers/__init__.py +4 -4
  146. pip/_vendor/resolvelib/resolvers/resolution.py +91 -10
  147. pip/_vendor/rich/__main__.py +12 -40
  148. pip/_vendor/rich/_inspect.py +1 -1
  149. pip/_vendor/rich/_ratio.py +1 -7
  150. pip/_vendor/rich/align.py +1 -7
  151. pip/_vendor/rich/box.py +1 -7
  152. pip/_vendor/rich/console.py +25 -20
  153. pip/_vendor/rich/control.py +1 -7
  154. pip/_vendor/rich/diagnose.py +1 -0
  155. pip/_vendor/rich/emoji.py +1 -6
  156. pip/_vendor/rich/live.py +32 -7
  157. pip/_vendor/rich/live_render.py +1 -7
  158. pip/_vendor/rich/logging.py +1 -1
  159. pip/_vendor/rich/panel.py +3 -4
  160. pip/_vendor/rich/progress.py +15 -15
  161. pip/_vendor/rich/spinner.py +7 -13
  162. pip/_vendor/rich/syntax.py +24 -5
  163. pip/_vendor/rich/traceback.py +32 -17
  164. pip/_vendor/truststore/_api.py +1 -1
  165. pip/_vendor/vendor.txt +10 -11
  166. {pip-25.1.dist-info → pip-25.2.dist-info}/METADATA +26 -4
  167. {pip-25.1.dist-info → pip-25.2.dist-info}/RECORD +194 -181
  168. {pip-25.1.dist-info → pip-25.2.dist-info}/WHEEL +1 -1
  169. {pip-25.1.dist-info → pip-25.2.dist-info}/licenses/AUTHORS.txt +12 -0
  170. pip-25.2.dist-info/licenses/src/pip/_vendor/cachecontrol/LICENSE.txt +13 -0
  171. pip-25.2.dist-info/licenses/src/pip/_vendor/certifi/LICENSE +20 -0
  172. pip-25.2.dist-info/licenses/src/pip/_vendor/dependency_groups/LICENSE.txt +9 -0
  173. pip-25.2.dist-info/licenses/src/pip/_vendor/distlib/LICENSE.txt +284 -0
  174. pip-25.2.dist-info/licenses/src/pip/_vendor/distro/LICENSE +202 -0
  175. pip-25.2.dist-info/licenses/src/pip/_vendor/idna/LICENSE.md +31 -0
  176. pip-25.2.dist-info/licenses/src/pip/_vendor/msgpack/COPYING +14 -0
  177. pip-25.2.dist-info/licenses/src/pip/_vendor/packaging/LICENSE +3 -0
  178. pip-25.2.dist-info/licenses/src/pip/_vendor/packaging/LICENSE.APACHE +177 -0
  179. pip-25.2.dist-info/licenses/src/pip/_vendor/packaging/LICENSE.BSD +23 -0
  180. pip-25.2.dist-info/licenses/src/pip/_vendor/pkg_resources/LICENSE +17 -0
  181. pip-25.2.dist-info/licenses/src/pip/_vendor/platformdirs/LICENSE +21 -0
  182. pip-25.2.dist-info/licenses/src/pip/_vendor/pygments/LICENSE +25 -0
  183. pip-25.2.dist-info/licenses/src/pip/_vendor/pyproject_hooks/LICENSE +21 -0
  184. pip-25.2.dist-info/licenses/src/pip/_vendor/requests/LICENSE +175 -0
  185. pip-25.2.dist-info/licenses/src/pip/_vendor/resolvelib/LICENSE +13 -0
  186. pip-25.2.dist-info/licenses/src/pip/_vendor/rich/LICENSE +19 -0
  187. pip-25.2.dist-info/licenses/src/pip/_vendor/tomli/LICENSE +21 -0
  188. pip-25.2.dist-info/licenses/src/pip/_vendor/tomli/LICENSE-HEADER +3 -0
  189. pip-25.2.dist-info/licenses/src/pip/_vendor/tomli_w/LICENSE +21 -0
  190. pip-25.2.dist-info/licenses/src/pip/_vendor/truststore/LICENSE +21 -0
  191. pip-25.2.dist-info/licenses/src/pip/_vendor/urllib3/LICENSE.txt +21 -0
  192. pip/_vendor/distlib/database.py +0 -1329
  193. pip/_vendor/distlib/index.py +0 -508
  194. pip/_vendor/distlib/locators.py +0 -1295
  195. pip/_vendor/distlib/manifest.py +0 -384
  196. pip/_vendor/distlib/markers.py +0 -162
  197. pip/_vendor/distlib/metadata.py +0 -1031
  198. pip/_vendor/distlib/version.py +0 -750
  199. pip/_vendor/distlib/wheel.py +0 -1100
  200. pip/_vendor/typing_extensions.py +0 -4584
  201. {pip-25.1.dist-info → pip-25.2.dist-info}/entry_points.txt +0 -0
  202. {pip-25.1.dist-info → pip-25.2.dist-info}/licenses/LICENSE.txt +0 -0
  203. {pip-25.1.dist-info → pip-25.2.dist-info}/top_level.txt +0 -0
@@ -6,12 +6,14 @@ so commands which don't always hit the network (e.g. list w/o --outdated or
6
6
  --uptodate) don't need waste time importing PipSession and friends.
7
7
  """
8
8
 
9
+ from __future__ import annotations
10
+
9
11
  import logging
10
12
  import os
11
13
  import sys
12
14
  from functools import lru_cache
13
15
  from optparse import Values
14
- from typing import TYPE_CHECKING, List, Optional
16
+ from typing import TYPE_CHECKING
15
17
 
16
18
  from pip._vendor import certifi
17
19
 
@@ -27,7 +29,7 @@ logger = logging.getLogger(__name__)
27
29
 
28
30
 
29
31
  @lru_cache
30
- def _create_truststore_ssl_context() -> Optional["SSLContext"]:
32
+ def _create_truststore_ssl_context() -> SSLContext | None:
31
33
  if sys.version_info < (3, 10):
32
34
  logger.debug("Disabling truststore because Python version isn't 3.10+")
33
35
  return None
@@ -56,10 +58,10 @@ class SessionCommandMixin(CommandContextMixIn):
56
58
 
57
59
  def __init__(self) -> None:
58
60
  super().__init__()
59
- self._session: Optional[PipSession] = None
61
+ self._session: PipSession | None = None
60
62
 
61
63
  @classmethod
62
- def _get_index_urls(cls, options: Values) -> Optional[List[str]]:
64
+ def _get_index_urls(cls, options: Values) -> list[str] | None:
63
65
  """Return a list of index urls from user-provided options."""
64
66
  index_urls = []
65
67
  if not getattr(options, "no_index", False):
@@ -72,7 +74,7 @@ class SessionCommandMixin(CommandContextMixIn):
72
74
  # Return None rather than an empty list
73
75
  return index_urls or None
74
76
 
75
- def get_default_session(self, options: Values) -> "PipSession":
77
+ def get_default_session(self, options: Values) -> PipSession:
76
78
  """Get a default-managed session."""
77
79
  if self._session is None:
78
80
  self._session = self.enter_context(self._build_session(options))
@@ -85,9 +87,9 @@ class SessionCommandMixin(CommandContextMixIn):
85
87
  def _build_session(
86
88
  self,
87
89
  options: Values,
88
- retries: Optional[int] = None,
89
- timeout: Optional[int] = None,
90
- ) -> "PipSession":
90
+ retries: int | None = None,
91
+ timeout: int | None = None,
92
+ ) -> PipSession:
91
93
  from pip._internal.network.session import PipSession
92
94
 
93
95
  cache_dir = options.cache_dir
@@ -134,7 +136,7 @@ class SessionCommandMixin(CommandContextMixIn):
134
136
  return session
135
137
 
136
138
 
137
- def _pip_self_version_check(session: "PipSession", options: Values) -> None:
139
+ def _pip_self_version_check(session: PipSession, options: Values) -> None:
138
140
  from pip._internal.self_outdated_check import pip_self_version_check as check
139
141
 
140
142
  check(session, options)
pip/_internal/cli/main.py CHANGED
@@ -1,11 +1,12 @@
1
1
  """Primary application entrypoint."""
2
2
 
3
+ from __future__ import annotations
4
+
3
5
  import locale
4
6
  import logging
5
7
  import os
6
8
  import sys
7
9
  import warnings
8
- from typing import List, Optional
9
10
 
10
11
  from pip._internal.cli.autocompletion import autocomplete
11
12
  from pip._internal.cli.main_parser import parse_command
@@ -43,7 +44,7 @@ logger = logging.getLogger(__name__)
43
44
  # main, this should not be an issue in practice.
44
45
 
45
46
 
46
- def main(args: Optional[List[str]] = None) -> int:
47
+ def main(args: list[str] | None = None) -> int:
47
48
  if args is None:
48
49
  args = sys.argv[1:]
49
50
 
@@ -1,9 +1,10 @@
1
1
  """A single place for constructing and exposing the main parser"""
2
2
 
3
+ from __future__ import annotations
4
+
3
5
  import os
4
6
  import subprocess
5
7
  import sys
6
- from typing import List, Optional, Tuple
7
8
 
8
9
  from pip._internal.build_env import get_runnable_pip
9
10
  from pip._internal.cli import cmdoptions
@@ -46,7 +47,7 @@ def create_main_parser() -> ConfigOptionParser:
46
47
  return parser
47
48
 
48
49
 
49
- def identify_python_interpreter(python: str) -> Optional[str]:
50
+ def identify_python_interpreter(python: str) -> str | None:
50
51
  # If the named file exists, use it.
51
52
  # If it's a directory, assume it's a virtual environment and
52
53
  # look for the environment's Python executable.
@@ -65,7 +66,7 @@ def identify_python_interpreter(python: str) -> Optional[str]:
65
66
  return None
66
67
 
67
68
 
68
- def parse_command(args: List[str]) -> Tuple[str, List[str]]:
69
+ def parse_command(args: list[str]) -> tuple[str, list[str]]:
69
70
  parser = create_main_parser()
70
71
 
71
72
  # Note: parser calls disable_interspersed_args(), so the result of this
@@ -1,12 +1,15 @@
1
1
  """Base option parser setup"""
2
2
 
3
+ from __future__ import annotations
4
+
3
5
  import logging
4
6
  import optparse
5
7
  import shutil
6
8
  import sys
7
9
  import textwrap
10
+ from collections.abc import Generator
8
11
  from contextlib import suppress
9
- from typing import Any, Dict, Generator, List, NoReturn, Optional, Tuple
12
+ from typing import Any, NoReturn
10
13
 
11
14
  from pip._internal.cli.status_codes import UNKNOWN_ERROR
12
15
  from pip._internal.configuration import Configuration, ConfigurationError
@@ -67,7 +70,7 @@ class PrettyHelpFormatter(optparse.IndentedHelpFormatter):
67
70
  msg = "\nUsage: {}\n".format(self.indent_lines(textwrap.dedent(usage), " "))
68
71
  return msg
69
72
 
70
- def format_description(self, description: Optional[str]) -> str:
73
+ def format_description(self, description: str | None) -> str:
71
74
  # leave full control over description to us
72
75
  if description:
73
76
  if hasattr(self.parser, "main"):
@@ -85,7 +88,7 @@ class PrettyHelpFormatter(optparse.IndentedHelpFormatter):
85
88
  else:
86
89
  return ""
87
90
 
88
- def format_epilog(self, epilog: Optional[str]) -> str:
91
+ def format_epilog(self, epilog: str | None) -> str:
89
92
  # leave full control over epilog to us
90
93
  if epilog:
91
94
  return epilog
@@ -142,7 +145,7 @@ class CustomOptionParser(optparse.OptionParser):
142
145
  return group
143
146
 
144
147
  @property
145
- def option_list_all(self) -> List[optparse.Option]:
148
+ def option_list_all(self) -> list[optparse.Option]:
146
149
  """Get a list of all options, including those in option groups."""
147
150
  res = self.option_list[:]
148
151
  for i in self.option_groups:
@@ -177,33 +180,34 @@ class ConfigOptionParser(CustomOptionParser):
177
180
 
178
181
  def _get_ordered_configuration_items(
179
182
  self,
180
- ) -> Generator[Tuple[str, Any], None, None]:
183
+ ) -> Generator[tuple[str, Any], None, None]:
181
184
  # Configuration gives keys in an unordered manner. Order them.
182
185
  override_order = ["global", self.name, ":env:"]
183
186
 
184
187
  # Pool the options into different groups
185
- section_items: Dict[str, List[Tuple[str, Any]]] = {
188
+ section_items: dict[str, list[tuple[str, Any]]] = {
186
189
  name: [] for name in override_order
187
190
  }
188
- for section_key, val in self.config.items():
189
- # ignore empty values
190
- if not val:
191
- logger.debug(
192
- "Ignoring configuration key '%s' as it's value is empty.",
193
- section_key,
194
- )
195
- continue
196
191
 
197
- section, key = section_key.split(".", 1)
198
- if section in override_order:
199
- section_items[section].append((key, val))
192
+ for _, value in self.config.items(): # noqa: PERF102
193
+ for section_key, val in value.items():
194
+ # ignore empty values
195
+ if not val:
196
+ logger.debug(
197
+ "Ignoring configuration key '%s' as its value is empty.",
198
+ section_key,
199
+ )
200
+ continue
201
+
202
+ section, key = section_key.split(".", 1)
203
+ if section in override_order:
204
+ section_items[section].append((key, val))
200
205
 
201
- # Yield each group in their override order
202
- for section in override_order:
203
- for key, val in section_items[section]:
204
- yield key, val
206
+ # Yield each group in their override order
207
+ for section in override_order:
208
+ yield from section_items[section]
205
209
 
206
- def _update_defaults(self, defaults: Dict[str, Any]) -> Dict[str, Any]:
210
+ def _update_defaults(self, defaults: dict[str, Any]) -> dict[str, Any]:
207
211
  """Updates the given defaults with values from the config files and
208
212
  the environ. Does a little special handling for certain types of
209
213
  options (lists)."""
@@ -1,6 +1,9 @@
1
+ from __future__ import annotations
2
+
1
3
  import functools
2
4
  import sys
3
- from typing import Callable, Generator, Iterable, Iterator, Optional, Tuple, TypeVar
5
+ from collections.abc import Generator, Iterable, Iterator
6
+ from typing import Callable, Literal, TypeVar
4
7
 
5
8
  from pip._vendor.rich.progress import (
6
9
  BarColumn,
@@ -22,20 +25,21 @@ from pip._internal.utils.logging import get_console, get_indentation
22
25
 
23
26
  T = TypeVar("T")
24
27
  ProgressRenderer = Callable[[Iterable[T]], Iterator[T]]
28
+ BarType = Literal["on", "off", "raw"]
25
29
 
26
30
 
27
31
  def _rich_download_progress_bar(
28
32
  iterable: Iterable[bytes],
29
33
  *,
30
- bar_type: str,
31
- size: Optional[int],
32
- initial_progress: Optional[int] = None,
34
+ bar_type: BarType,
35
+ size: int | None,
36
+ initial_progress: int | None = None,
33
37
  ) -> Generator[bytes, None, None]:
34
38
  assert bar_type == "on", "This should only be used in the default mode."
35
39
 
36
40
  if not size:
37
41
  total = float("inf")
38
- columns: Tuple[ProgressColumn, ...] = (
42
+ columns: tuple[ProgressColumn, ...] = (
39
43
  TextColumn("[progress.description]{task.description}"),
40
44
  SpinnerColumn("line", speed=1.5),
41
45
  FileSizeColumn(),
@@ -49,18 +53,21 @@ def _rich_download_progress_bar(
49
53
  BarColumn(),
50
54
  DownloadColumn(),
51
55
  TransferSpeedColumn(),
52
- TextColumn("eta"),
53
- TimeRemainingColumn(),
56
+ TextColumn("{task.fields[time_description]}"),
57
+ TimeRemainingColumn(elapsed_when_finished=True),
54
58
  )
55
59
 
56
60
  progress = Progress(*columns, refresh_per_second=5)
57
- task_id = progress.add_task(" " * (get_indentation() + 2), total=total)
61
+ task_id = progress.add_task(
62
+ " " * (get_indentation() + 2), total=total, time_description="eta"
63
+ )
58
64
  if initial_progress is not None:
59
65
  progress.update(task_id, advance=initial_progress)
60
66
  with progress:
61
67
  for chunk in iterable:
62
68
  yield chunk
63
69
  progress.update(task_id, advance=len(chunk))
70
+ progress.update(task_id, time_description="")
64
71
 
65
72
 
66
73
  def _rich_install_progress_bar(
@@ -88,8 +95,8 @@ def _rich_install_progress_bar(
88
95
  def _raw_progress_bar(
89
96
  iterable: Iterable[bytes],
90
97
  *,
91
- size: Optional[int],
92
- initial_progress: Optional[int] = None,
98
+ size: int | None,
99
+ initial_progress: int | None = None,
93
100
  ) -> Generator[bytes, None, None]:
94
101
  def write_progress(current: int, total: int) -> None:
95
102
  sys.stdout.write(f"Progress {current} of {total}\n")
@@ -109,7 +116,7 @@ def _raw_progress_bar(
109
116
 
110
117
 
111
118
  def get_download_progress_renderer(
112
- *, bar_type: str, size: Optional[int] = None, initial_progress: Optional[int] = None
119
+ *, bar_type: BarType, size: int | None = None, initial_progress: int | None = None
113
120
  ) -> ProgressRenderer[bytes]:
114
121
  """Get an object that can be used to render the download progress.
115
122
 
@@ -133,7 +140,7 @@ def get_download_progress_renderer(
133
140
 
134
141
 
135
142
  def get_install_progress_renderer(
136
- *, bar_type: str, total: int
143
+ *, bar_type: BarType, total: int
137
144
  ) -> ProgressRenderer[InstallRequirement]:
138
145
  """Get an object that can be used to render the install progress.
139
146
  Returns a callable, that takes an iterable to "wrap".
@@ -5,11 +5,14 @@ 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
9
11
  from functools import partial
10
12
  from optparse import Values
11
- from typing import Any, List, Optional, Tuple
13
+ from typing import Any
12
14
 
15
+ from pip._internal.build_env import SubprocessBuildEnvironmentInstaller
13
16
  from pip._internal.cache import WheelCache
14
17
  from pip._internal.cli import cmdoptions
15
18
  from pip._internal.cli.index_command import IndexGroupCommand
@@ -58,8 +61,8 @@ def with_cleanup(func: Any) -> Any:
58
61
  registry.set_delete(t, False)
59
62
 
60
63
  def wrapper(
61
- self: RequirementCommand, options: Values, args: List[Any]
62
- ) -> Optional[int]:
64
+ self: RequirementCommand, options: Values, args: list[Any]
65
+ ) -> int | None:
63
66
  assert self.tempdir_registry is not None
64
67
  if options.no_clean:
65
68
  configure_tempdir_registry(self.tempdir_registry)
@@ -100,7 +103,7 @@ class RequirementCommand(IndexGroupCommand):
100
103
  session: PipSession,
101
104
  finder: PackageFinder,
102
105
  use_user_site: bool,
103
- download_dir: Optional[str] = None,
106
+ download_dir: str | None = None,
104
107
  verbosity: int = 0,
105
108
  ) -> RequirementPreparer:
106
109
  """
@@ -134,6 +137,7 @@ class RequirementCommand(IndexGroupCommand):
134
137
  src_dir=options.src_dir,
135
138
  download_dir=download_dir,
136
139
  build_isolation=options.build_isolation,
140
+ build_isolation_installer=SubprocessBuildEnvironmentInstaller(finder),
137
141
  check_build_deps=options.check_build_deps,
138
142
  build_tracker=build_tracker,
139
143
  session=session,
@@ -153,14 +157,14 @@ class RequirementCommand(IndexGroupCommand):
153
157
  preparer: RequirementPreparer,
154
158
  finder: PackageFinder,
155
159
  options: Values,
156
- wheel_cache: Optional[WheelCache] = None,
160
+ wheel_cache: WheelCache | None = None,
157
161
  use_user_site: bool = False,
158
162
  ignore_installed: bool = True,
159
163
  ignore_requires_python: bool = False,
160
164
  force_reinstall: bool = False,
161
165
  upgrade_strategy: str = "to-satisfy-only",
162
- use_pep517: Optional[bool] = None,
163
- py_version_info: Optional[Tuple[int, ...]] = None,
166
+ use_pep517: bool | None = None,
167
+ py_version_info: tuple[int, ...] | None = None,
164
168
  ) -> BaseResolver:
165
169
  """
166
170
  Create a Resolver instance for the given parameters.
@@ -208,15 +212,15 @@ class RequirementCommand(IndexGroupCommand):
208
212
 
209
213
  def get_requirements(
210
214
  self,
211
- args: List[str],
215
+ args: list[str],
212
216
  options: Values,
213
217
  finder: PackageFinder,
214
218
  session: PipSession,
215
- ) -> List[InstallRequirement]:
219
+ ) -> list[InstallRequirement]:
216
220
  """
217
221
  Parse command-line arguments into the corresponding requirements.
218
222
  """
219
- requirements: List[InstallRequirement] = []
223
+ requirements: list[InstallRequirement] = []
220
224
  for filename in options.constraints:
221
225
  for parsed_req in parse_requirements(
222
226
  filename,
@@ -322,8 +326,8 @@ class RequirementCommand(IndexGroupCommand):
322
326
  self,
323
327
  options: Values,
324
328
  session: PipSession,
325
- target_python: Optional[TargetPython] = None,
326
- ignore_requires_python: Optional[bool] = None,
329
+ target_python: TargetPython | None = None,
330
+ ignore_requires_python: bool | None = None,
327
331
  ) -> PackageFinder:
328
332
  """
329
333
  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(