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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (236) hide show
  1. pip/__init__.py +3 -3
  2. pip/_internal/__init__.py +2 -2
  3. pip/_internal/build_env.py +186 -94
  4. pip/_internal/cache.py +17 -15
  5. pip/_internal/cli/autocompletion.py +13 -4
  6. pip/_internal/cli/base_command.py +18 -7
  7. pip/_internal/cli/cmdoptions.py +57 -80
  8. pip/_internal/cli/command_context.py +4 -3
  9. pip/_internal/cli/index_command.py +11 -9
  10. pip/_internal/cli/main.py +3 -2
  11. pip/_internal/cli/main_parser.py +4 -3
  12. pip/_internal/cli/parser.py +24 -20
  13. pip/_internal/cli/progress_bars.py +19 -12
  14. pip/_internal/cli/req_command.py +57 -33
  15. pip/_internal/cli/spinners.py +81 -5
  16. pip/_internal/commands/__init__.py +5 -3
  17. pip/_internal/commands/cache.py +18 -15
  18. pip/_internal/commands/check.py +1 -2
  19. pip/_internal/commands/completion.py +1 -2
  20. pip/_internal/commands/configuration.py +26 -18
  21. pip/_internal/commands/debug.py +8 -6
  22. pip/_internal/commands/download.py +6 -10
  23. pip/_internal/commands/freeze.py +2 -3
  24. pip/_internal/commands/hash.py +1 -2
  25. pip/_internal/commands/help.py +1 -2
  26. pip/_internal/commands/index.py +15 -9
  27. pip/_internal/commands/inspect.py +4 -4
  28. pip/_internal/commands/install.py +63 -53
  29. pip/_internal/commands/list.py +35 -26
  30. pip/_internal/commands/lock.py +4 -8
  31. pip/_internal/commands/search.py +14 -12
  32. pip/_internal/commands/show.py +14 -11
  33. pip/_internal/commands/uninstall.py +1 -2
  34. pip/_internal/commands/wheel.py +7 -13
  35. pip/_internal/configuration.py +40 -27
  36. pip/_internal/distributions/base.py +6 -4
  37. pip/_internal/distributions/installed.py +8 -4
  38. pip/_internal/distributions/sdist.py +33 -27
  39. pip/_internal/distributions/wheel.py +6 -4
  40. pip/_internal/exceptions.py +78 -42
  41. pip/_internal/index/collector.py +24 -29
  42. pip/_internal/index/package_finder.py +73 -64
  43. pip/_internal/index/sources.py +17 -14
  44. pip/_internal/locations/__init__.py +18 -16
  45. pip/_internal/locations/_distutils.py +12 -11
  46. pip/_internal/locations/_sysconfig.py +5 -4
  47. pip/_internal/locations/base.py +4 -3
  48. pip/_internal/main.py +2 -2
  49. pip/_internal/metadata/__init__.py +14 -7
  50. pip/_internal/metadata/_json.py +5 -4
  51. pip/_internal/metadata/base.py +22 -27
  52. pip/_internal/metadata/importlib/_compat.py +6 -4
  53. pip/_internal/metadata/importlib/_dists.py +20 -19
  54. pip/_internal/metadata/importlib/_envs.py +9 -6
  55. pip/_internal/metadata/pkg_resources.py +11 -14
  56. pip/_internal/models/direct_url.py +24 -21
  57. pip/_internal/models/format_control.py +5 -5
  58. pip/_internal/models/installation_report.py +4 -3
  59. pip/_internal/models/link.py +39 -34
  60. pip/_internal/models/pylock.py +27 -22
  61. pip/_internal/models/search_scope.py +6 -7
  62. pip/_internal/models/selection_prefs.py +3 -3
  63. pip/_internal/models/target_python.py +10 -9
  64. pip/_internal/models/wheel.py +12 -71
  65. pip/_internal/network/auth.py +20 -22
  66. pip/_internal/network/cache.py +28 -17
  67. pip/_internal/network/download.py +169 -141
  68. pip/_internal/network/lazy_wheel.py +15 -10
  69. pip/_internal/network/session.py +32 -27
  70. pip/_internal/network/utils.py +2 -2
  71. pip/_internal/network/xmlrpc.py +2 -2
  72. pip/_internal/operations/build/build_tracker.py +10 -8
  73. pip/_internal/operations/build/wheel.py +7 -6
  74. pip/_internal/operations/build/wheel_editable.py +7 -6
  75. pip/_internal/operations/check.py +21 -26
  76. pip/_internal/operations/freeze.py +12 -9
  77. pip/_internal/operations/install/wheel.py +49 -41
  78. pip/_internal/operations/prepare.py +42 -31
  79. pip/_internal/pyproject.py +7 -69
  80. pip/_internal/req/__init__.py +12 -12
  81. pip/_internal/req/constructors.py +68 -62
  82. pip/_internal/req/req_dependency_group.py +7 -11
  83. pip/_internal/req/req_file.py +32 -36
  84. pip/_internal/req/req_install.py +64 -170
  85. pip/_internal/req/req_set.py +4 -5
  86. pip/_internal/req/req_uninstall.py +20 -17
  87. pip/_internal/resolution/base.py +3 -3
  88. pip/_internal/resolution/legacy/resolver.py +21 -20
  89. pip/_internal/resolution/resolvelib/base.py +16 -13
  90. pip/_internal/resolution/resolvelib/candidates.py +49 -37
  91. pip/_internal/resolution/resolvelib/factory.py +72 -50
  92. pip/_internal/resolution/resolvelib/found_candidates.py +11 -9
  93. pip/_internal/resolution/resolvelib/provider.py +24 -20
  94. pip/_internal/resolution/resolvelib/reporter.py +26 -11
  95. pip/_internal/resolution/resolvelib/requirements.py +8 -6
  96. pip/_internal/resolution/resolvelib/resolver.py +41 -29
  97. pip/_internal/self_outdated_check.py +19 -9
  98. pip/_internal/utils/appdirs.py +1 -2
  99. pip/_internal/utils/compat.py +7 -1
  100. pip/_internal/utils/compatibility_tags.py +17 -16
  101. pip/_internal/utils/deprecation.py +11 -9
  102. pip/_internal/utils/direct_url_helpers.py +2 -2
  103. pip/_internal/utils/egg_link.py +6 -5
  104. pip/_internal/utils/entrypoints.py +3 -2
  105. pip/_internal/utils/filesystem.py +20 -5
  106. pip/_internal/utils/filetypes.py +4 -6
  107. pip/_internal/utils/glibc.py +6 -5
  108. pip/_internal/utils/hashes.py +9 -6
  109. pip/_internal/utils/logging.py +8 -5
  110. pip/_internal/utils/misc.py +37 -45
  111. pip/_internal/utils/packaging.py +3 -2
  112. pip/_internal/utils/retry.py +7 -4
  113. pip/_internal/utils/subprocess.py +20 -17
  114. pip/_internal/utils/temp_dir.py +10 -12
  115. pip/_internal/utils/unpacking.py +31 -4
  116. pip/_internal/utils/urls.py +1 -1
  117. pip/_internal/utils/virtualenv.py +3 -2
  118. pip/_internal/utils/wheel.py +3 -4
  119. pip/_internal/vcs/bazaar.py +26 -8
  120. pip/_internal/vcs/git.py +59 -24
  121. pip/_internal/vcs/mercurial.py +34 -11
  122. pip/_internal/vcs/subversion.py +27 -16
  123. pip/_internal/vcs/versioncontrol.py +56 -51
  124. pip/_internal/wheel_builder.py +30 -101
  125. pip/_vendor/README.rst +180 -0
  126. pip/_vendor/cachecontrol/LICENSE.txt +13 -0
  127. pip/_vendor/cachecontrol/__init__.py +1 -1
  128. pip/_vendor/certifi/LICENSE +20 -0
  129. pip/_vendor/certifi/__init__.py +1 -1
  130. pip/_vendor/certifi/cacert.pem +164 -261
  131. pip/_vendor/certifi/core.py +1 -32
  132. pip/_vendor/dependency_groups/LICENSE.txt +9 -0
  133. pip/_vendor/distlib/LICENSE.txt +284 -0
  134. pip/_vendor/distlib/__init__.py +2 -2
  135. pip/_vendor/distlib/scripts.py +1 -1
  136. pip/_vendor/distro/LICENSE +202 -0
  137. pip/_vendor/idna/LICENSE.md +31 -0
  138. pip/_vendor/msgpack/COPYING +14 -0
  139. pip/_vendor/msgpack/__init__.py +2 -2
  140. pip/_vendor/packaging/LICENSE +3 -0
  141. pip/_vendor/packaging/LICENSE.APACHE +177 -0
  142. pip/_vendor/packaging/LICENSE.BSD +23 -0
  143. pip/_vendor/pkg_resources/LICENSE +17 -0
  144. pip/_vendor/pkg_resources/__init__.py +1 -1
  145. pip/_vendor/platformdirs/LICENSE +21 -0
  146. pip/_vendor/platformdirs/api.py +1 -1
  147. pip/_vendor/platformdirs/macos.py +10 -8
  148. pip/_vendor/platformdirs/version.py +16 -3
  149. pip/_vendor/pygments/LICENSE +25 -0
  150. pip/_vendor/pygments/__init__.py +1 -1
  151. pip/_vendor/pyproject_hooks/LICENSE +21 -0
  152. pip/_vendor/requests/LICENSE +175 -0
  153. pip/_vendor/requests/__version__.py +2 -2
  154. pip/_vendor/requests/adapters.py +17 -40
  155. pip/_vendor/requests/compat.py +12 -0
  156. pip/_vendor/requests/models.py +3 -1
  157. pip/_vendor/requests/sessions.py +1 -1
  158. pip/_vendor/requests/utils.py +6 -16
  159. pip/_vendor/resolvelib/LICENSE +13 -0
  160. pip/_vendor/resolvelib/__init__.py +3 -3
  161. pip/_vendor/resolvelib/reporters.py +1 -1
  162. pip/_vendor/resolvelib/resolvers/__init__.py +4 -4
  163. pip/_vendor/resolvelib/resolvers/abstract.py +3 -3
  164. pip/_vendor/resolvelib/resolvers/resolution.py +96 -10
  165. pip/_vendor/rich/LICENSE +19 -0
  166. pip/_vendor/rich/__main__.py +12 -40
  167. pip/_vendor/rich/_inspect.py +1 -1
  168. pip/_vendor/rich/_ratio.py +1 -7
  169. pip/_vendor/rich/align.py +1 -7
  170. pip/_vendor/rich/box.py +1 -7
  171. pip/_vendor/rich/console.py +25 -20
  172. pip/_vendor/rich/control.py +1 -7
  173. pip/_vendor/rich/diagnose.py +1 -0
  174. pip/_vendor/rich/emoji.py +1 -6
  175. pip/_vendor/rich/live.py +32 -7
  176. pip/_vendor/rich/live_render.py +1 -7
  177. pip/_vendor/rich/logging.py +1 -1
  178. pip/_vendor/rich/panel.py +3 -4
  179. pip/_vendor/rich/progress.py +15 -15
  180. pip/_vendor/rich/spinner.py +7 -13
  181. pip/_vendor/rich/style.py +7 -11
  182. pip/_vendor/rich/syntax.py +24 -5
  183. pip/_vendor/rich/traceback.py +32 -17
  184. pip/_vendor/tomli/LICENSE +21 -0
  185. pip/_vendor/tomli/__init__.py +1 -1
  186. pip/_vendor/tomli/_parser.py +28 -21
  187. pip/_vendor/tomli/_re.py +8 -5
  188. pip/_vendor/tomli_w/LICENSE +21 -0
  189. pip/_vendor/truststore/LICENSE +21 -0
  190. pip/_vendor/truststore/__init__.py +1 -1
  191. pip/_vendor/truststore/_api.py +15 -7
  192. pip/_vendor/truststore/_openssl.py +3 -1
  193. pip/_vendor/urllib3/LICENSE.txt +21 -0
  194. pip/_vendor/vendor.txt +11 -12
  195. {pip-25.1.1.dist-info → pip-25.3.dist-info}/METADATA +32 -11
  196. {pip-25.1.1.dist-info → pip-25.3.dist-info}/RECORD +221 -192
  197. {pip-25.1.1.dist-info → pip-25.3.dist-info}/WHEEL +1 -2
  198. pip-25.3.dist-info/entry_points.txt +4 -0
  199. {pip-25.1.1.dist-info → pip-25.3.dist-info}/licenses/AUTHORS.txt +21 -0
  200. pip-25.3.dist-info/licenses/src/pip/_vendor/cachecontrol/LICENSE.txt +13 -0
  201. pip-25.3.dist-info/licenses/src/pip/_vendor/certifi/LICENSE +20 -0
  202. pip-25.3.dist-info/licenses/src/pip/_vendor/dependency_groups/LICENSE.txt +9 -0
  203. pip-25.3.dist-info/licenses/src/pip/_vendor/distlib/LICENSE.txt +284 -0
  204. pip-25.3.dist-info/licenses/src/pip/_vendor/distro/LICENSE +202 -0
  205. pip-25.3.dist-info/licenses/src/pip/_vendor/idna/LICENSE.md +31 -0
  206. pip-25.3.dist-info/licenses/src/pip/_vendor/msgpack/COPYING +14 -0
  207. pip-25.3.dist-info/licenses/src/pip/_vendor/packaging/LICENSE +3 -0
  208. pip-25.3.dist-info/licenses/src/pip/_vendor/packaging/LICENSE.APACHE +177 -0
  209. pip-25.3.dist-info/licenses/src/pip/_vendor/packaging/LICENSE.BSD +23 -0
  210. pip-25.3.dist-info/licenses/src/pip/_vendor/pkg_resources/LICENSE +17 -0
  211. pip-25.3.dist-info/licenses/src/pip/_vendor/platformdirs/LICENSE +21 -0
  212. pip-25.3.dist-info/licenses/src/pip/_vendor/pygments/LICENSE +25 -0
  213. pip-25.3.dist-info/licenses/src/pip/_vendor/pyproject_hooks/LICENSE +21 -0
  214. pip-25.3.dist-info/licenses/src/pip/_vendor/requests/LICENSE +175 -0
  215. pip-25.3.dist-info/licenses/src/pip/_vendor/resolvelib/LICENSE +13 -0
  216. pip-25.3.dist-info/licenses/src/pip/_vendor/rich/LICENSE +19 -0
  217. pip-25.3.dist-info/licenses/src/pip/_vendor/tomli/LICENSE +21 -0
  218. pip-25.3.dist-info/licenses/src/pip/_vendor/tomli_w/LICENSE +21 -0
  219. pip-25.3.dist-info/licenses/src/pip/_vendor/truststore/LICENSE +21 -0
  220. pip-25.3.dist-info/licenses/src/pip/_vendor/urllib3/LICENSE.txt +21 -0
  221. pip/_internal/operations/build/metadata_legacy.py +0 -73
  222. pip/_internal/operations/build/wheel_legacy.py +0 -118
  223. pip/_internal/operations/install/editable_legacy.py +0 -46
  224. pip/_internal/utils/setuptools_build.py +0 -147
  225. pip/_vendor/distlib/database.py +0 -1329
  226. pip/_vendor/distlib/index.py +0 -508
  227. pip/_vendor/distlib/locators.py +0 -1295
  228. pip/_vendor/distlib/manifest.py +0 -384
  229. pip/_vendor/distlib/markers.py +0 -162
  230. pip/_vendor/distlib/metadata.py +0 -1031
  231. pip/_vendor/distlib/version.py +0 -750
  232. pip/_vendor/distlib/wheel.py +0 -1100
  233. pip/_vendor/typing_extensions.py +0 -4584
  234. pip-25.1.1.dist-info/entry_points.txt +0 -3
  235. pip-25.1.1.dist-info/top_level.txt +0 -1
  236. {pip-25.1.1.dist-info → pip-25.3.dist-info}/licenses/LICENSE.txt +0 -0
@@ -1,5 +1,8 @@
1
+ from __future__ import annotations
2
+
1
3
  import hashlib
2
- from typing import TYPE_CHECKING, BinaryIO, Dict, Iterable, List, NoReturn, Optional
4
+ from collections.abc import Iterable
5
+ from typing import TYPE_CHECKING, BinaryIO, NoReturn
3
6
 
4
7
  from pip._internal.exceptions import HashMismatch, HashMissing, InstallationError
5
8
  from pip._internal.utils.misc import read_chunks
@@ -24,7 +27,7 @@ class Hashes:
24
27
 
25
28
  """
26
29
 
27
- def __init__(self, hashes: Optional[Dict[str, List[str]]] = None) -> None:
30
+ def __init__(self, hashes: dict[str, list[str]] | None = None) -> None:
28
31
  """
29
32
  :param hashes: A dict of algorithm names pointing to lists of allowed
30
33
  hex digests
@@ -36,7 +39,7 @@ class Hashes:
36
39
  allowed[alg] = [k.lower() for k in sorted(keys)]
37
40
  self._allowed = allowed
38
41
 
39
- def __and__(self, other: "Hashes") -> "Hashes":
42
+ def __and__(self, other: Hashes) -> Hashes:
40
43
  if not isinstance(other, Hashes):
41
44
  return NotImplemented
42
45
 
@@ -86,7 +89,7 @@ class Hashes:
86
89
  return
87
90
  self._raise(gots)
88
91
 
89
- def _raise(self, gots: Dict[str, "_Hash"]) -> "NoReturn":
92
+ def _raise(self, gots: dict[str, _Hash]) -> NoReturn:
90
93
  raise HashMismatch(self._allowed, gots)
91
94
 
92
95
  def check_against_file(self, file: BinaryIO) -> None:
@@ -101,7 +104,7 @@ class Hashes:
101
104
  with open(path, "rb") as file:
102
105
  return self.check_against_file(file)
103
106
 
104
- def has_one_of(self, hashes: Dict[str, str]) -> bool:
107
+ def has_one_of(self, hashes: dict[str, str]) -> bool:
105
108
  """Return whether any of the given hashes are allowed."""
106
109
  for hash_name, hex_digest in hashes.items():
107
110
  if self.is_hash_allowed(hash_name, hex_digest):
@@ -143,5 +146,5 @@ class MissingHashes(Hashes):
143
146
  # empty list, it will never match, so an error will always raise.
144
147
  super().__init__(hashes={FAVORITE_HASH: []})
145
148
 
146
- def _raise(self, gots: Dict[str, "_Hash"]) -> "NoReturn":
149
+ def _raise(self, gots: dict[str, _Hash]) -> NoReturn:
147
150
  raise HashMissing(gots[FAVORITE_HASH].hexdigest())
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  import contextlib
2
4
  import errno
3
5
  import logging
@@ -5,10 +7,11 @@ import logging.handlers
5
7
  import os
6
8
  import sys
7
9
  import threading
10
+ from collections.abc import Generator
8
11
  from dataclasses import dataclass
9
12
  from io import TextIOWrapper
10
13
  from logging import Filter
11
- from typing import Any, ClassVar, Generator, List, Optional, Type
14
+ from typing import Any, ClassVar
12
15
 
13
16
  from pip._vendor.rich.console import (
14
17
  Console,
@@ -40,7 +43,7 @@ class BrokenStdoutLoggingError(Exception):
40
43
  """
41
44
 
42
45
 
43
- def _is_broken_pipe_error(exc_class: Type[BaseException], exc: BaseException) -> bool:
46
+ def _is_broken_pipe_error(exc_class: type[BaseException], exc: BaseException) -> bool:
44
47
  if exc_class is BrokenPipeError:
45
48
  return True
46
49
 
@@ -156,7 +159,7 @@ def get_console(*, stderr: bool = False) -> Console:
156
159
 
157
160
 
158
161
  class RichPipStreamHandler(RichHandler):
159
- KEYWORDS: ClassVar[Optional[List[str]]] = []
162
+ KEYWORDS: ClassVar[list[str] | None] = []
160
163
 
161
164
  def __init__(self, console: Console) -> None:
162
165
  super().__init__(
@@ -169,7 +172,7 @@ class RichPipStreamHandler(RichHandler):
169
172
 
170
173
  # Our custom override on Rich's logger, to make things work as we need them to.
171
174
  def emit(self, record: logging.LogRecord) -> None:
172
- style: Optional[Style] = None
175
+ style: Style | None = None
173
176
 
174
177
  # If we are given a diagnostic error to present, present it with indentation.
175
178
  if getattr(record, "rich", False):
@@ -240,7 +243,7 @@ class ExcludeLoggerFilter(Filter):
240
243
  return not super().filter(record)
241
244
 
242
245
 
243
- def setup_logging(verbosity: int, no_color: bool, user_log_file: Optional[str]) -> int:
246
+ def setup_logging(verbosity: int, no_color: bool, user_log_file: str | None) -> int:
244
247
  """Configures and sets up all of the logging
245
248
 
246
249
  Returns the requested logging level, as its integer value.
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  import errno
2
4
  import getpass
3
5
  import hashlib
@@ -9,6 +11,7 @@ import stat
9
11
  import sys
10
12
  import sysconfig
11
13
  import urllib.parse
14
+ from collections.abc import Generator, Iterable, Iterator, Mapping, Sequence
12
15
  from dataclasses import dataclass
13
16
  from functools import partial
14
17
  from io import StringIO
@@ -19,18 +22,9 @@ from typing import (
19
22
  Any,
20
23
  BinaryIO,
21
24
  Callable,
22
- Generator,
23
- Iterable,
24
- Iterator,
25
- List,
26
- Mapping,
27
25
  Optional,
28
- Sequence,
29
26
  TextIO,
30
- Tuple,
31
- Type,
32
27
  TypeVar,
33
- Union,
34
28
  cast,
35
29
  )
36
30
 
@@ -64,9 +58,9 @@ __all__ = [
64
58
  logger = logging.getLogger(__name__)
65
59
 
66
60
  T = TypeVar("T")
67
- ExcInfo = Tuple[Type[BaseException], BaseException, TracebackType]
68
- VersionInfo = Tuple[int, int, int]
69
- NetlocTuple = Tuple[str, Tuple[Optional[str], Optional[str]]]
61
+ ExcInfo = tuple[type[BaseException], BaseException, TracebackType]
62
+ VersionInfo = tuple[int, int, int]
63
+ NetlocTuple = tuple[str, tuple[Optional[str], Optional[str]]]
70
64
  OnExc = Callable[[FunctionType, Path, BaseException], Any]
71
65
  OnErr = Callable[[FunctionType, Path, ExcInfo], Any]
72
66
 
@@ -80,7 +74,7 @@ def get_pip_version() -> str:
80
74
  return f"pip {__version__} from {pip_pkg_dir} (python {get_major_minor_version()})"
81
75
 
82
76
 
83
- def normalize_version_info(py_version_info: Tuple[int, ...]) -> Tuple[int, int, int]:
77
+ def normalize_version_info(py_version_info: tuple[int, ...]) -> tuple[int, int, int]:
84
78
  """
85
79
  Convert a tuple of ints representing a Python version to one of length
86
80
  three.
@@ -123,9 +117,7 @@ def get_prog() -> str:
123
117
 
124
118
  # Retry every half second for up to 3 seconds
125
119
  @retry(stop_after_delay=3, wait=0.5)
126
- def rmtree(
127
- dir: str, ignore_errors: bool = False, onexc: Optional[OnExc] = None
128
- ) -> None:
120
+ def rmtree(dir: str, ignore_errors: bool = False, onexc: OnExc | None = None) -> None:
129
121
  if ignore_errors:
130
122
  onexc = _onerror_ignore
131
123
  if onexc is None:
@@ -149,7 +141,7 @@ def _onerror_reraise(*_args: Any) -> None:
149
141
  def rmtree_errorhandler(
150
142
  func: FunctionType,
151
143
  path: Path,
152
- exc_info: Union[ExcInfo, BaseException],
144
+ exc_info: ExcInfo | BaseException,
153
145
  *,
154
146
  onexc: OnExc = _onerror_reraise,
155
147
  ) -> None:
@@ -276,7 +268,7 @@ def format_size(bytes: float) -> str:
276
268
  return f"{int(bytes)} bytes"
277
269
 
278
270
 
279
- def tabulate(rows: Iterable[Iterable[Any]]) -> Tuple[List[str], List[int]]:
271
+ def tabulate(rows: Iterable[Iterable[Any]]) -> tuple[list[str], list[int]]:
280
272
  """Return a list of formatted rows and a list of column sizes.
281
273
 
282
274
  For example::
@@ -331,7 +323,7 @@ def normalize_path(path: str, resolve_symlinks: bool = True) -> str:
331
323
  return os.path.normcase(path)
332
324
 
333
325
 
334
- def splitext(path: str) -> Tuple[str, str]:
326
+ def splitext(path: str) -> tuple[str, str]:
335
327
  """Like os.path.splitext, but take off .tar too"""
336
328
  base, ext = posixpath.splitext(path)
337
329
  if base.lower().endswith(".tar"):
@@ -379,7 +371,7 @@ class StreamWrapper(StringIO):
379
371
  orig_stream: TextIO
380
372
 
381
373
  @classmethod
382
- def from_stream(cls, orig_stream: TextIO) -> "StreamWrapper":
374
+ def from_stream(cls, orig_stream: TextIO) -> StreamWrapper:
383
375
  ret = cls()
384
376
  ret.orig_stream = orig_stream
385
377
  return ret
@@ -392,14 +384,14 @@ class StreamWrapper(StringIO):
392
384
 
393
385
 
394
386
  # Simulates an enum
395
- def enum(*sequential: Any, **named: Any) -> Type[Any]:
387
+ def enum(*sequential: Any, **named: Any) -> type[Any]:
396
388
  enums = dict(zip(sequential, range(len(sequential))), **named)
397
389
  reverse = {value: key for key, value in enums.items()}
398
390
  enums["reverse_mapping"] = reverse
399
391
  return type("Enum", (), enums)
400
392
 
401
393
 
402
- def build_netloc(host: str, port: Optional[int]) -> str:
394
+ def build_netloc(host: str, port: int | None) -> str:
403
395
  """
404
396
  Build a netloc from a host-port pair
405
397
  """
@@ -421,7 +413,7 @@ def build_url_from_netloc(netloc: str, scheme: str = "https") -> str:
421
413
  return f"{scheme}://{netloc}"
422
414
 
423
415
 
424
- def parse_netloc(netloc: str) -> Tuple[Optional[str], Optional[int]]:
416
+ def parse_netloc(netloc: str) -> tuple[str | None, int | None]:
425
417
  """
426
418
  Return the host-port pair from a netloc.
427
419
  """
@@ -443,7 +435,7 @@ def split_auth_from_netloc(netloc: str) -> NetlocTuple:
443
435
  # behaves if more than one @ is present (which can be checked using
444
436
  # the password attribute of urlsplit()'s return value).
445
437
  auth, netloc = netloc.rsplit("@", 1)
446
- pw: Optional[str] = None
438
+ pw: str | None = None
447
439
  if ":" in auth:
448
440
  # Split from the left because that's how urllib.parse.urlsplit()
449
441
  # behaves if more than one : is present (which again can be checked
@@ -480,8 +472,8 @@ def redact_netloc(netloc: str) -> str:
480
472
 
481
473
 
482
474
  def _transform_url(
483
- url: str, transform_netloc: Callable[[str], Tuple[Any, ...]]
484
- ) -> Tuple[str, NetlocTuple]:
475
+ url: str, transform_netloc: Callable[[str], tuple[Any, ...]]
476
+ ) -> tuple[str, NetlocTuple]:
485
477
  """Transform and replace netloc in a url.
486
478
 
487
479
  transform_netloc is a function taking the netloc and returning a
@@ -503,13 +495,13 @@ def _get_netloc(netloc: str) -> NetlocTuple:
503
495
  return split_auth_from_netloc(netloc)
504
496
 
505
497
 
506
- def _redact_netloc(netloc: str) -> Tuple[str]:
498
+ def _redact_netloc(netloc: str) -> tuple[str]:
507
499
  return (redact_netloc(netloc),)
508
500
 
509
501
 
510
502
  def split_auth_netloc_from_url(
511
503
  url: str,
512
- ) -> Tuple[str, str, Tuple[Optional[str], Optional[str]]]:
504
+ ) -> tuple[str, str, tuple[str | None, str | None]]:
513
505
  """
514
506
  Parse a url into separate netloc, auth, and url with no auth.
515
507
 
@@ -614,7 +606,7 @@ def is_console_interactive() -> bool:
614
606
  return sys.stdin is not None and sys.stdin.isatty()
615
607
 
616
608
 
617
- def hash_file(path: str, blocksize: int = 1 << 20) -> Tuple[Any, int]:
609
+ def hash_file(path: str, blocksize: int = 1 << 20) -> tuple[Any, int]:
618
610
  """Return (hash, length) for path using hashlib.sha256()"""
619
611
 
620
612
  h = hashlib.sha256()
@@ -626,7 +618,7 @@ def hash_file(path: str, blocksize: int = 1 << 20) -> Tuple[Any, int]:
626
618
  return h, length
627
619
 
628
620
 
629
- def pairwise(iterable: Iterable[Any]) -> Iterator[Tuple[Any, Any]]:
621
+ def pairwise(iterable: Iterable[Any]) -> Iterator[tuple[Any, Any]]:
630
622
  """
631
623
  Return paired elements.
632
624
 
@@ -639,7 +631,7 @@ def pairwise(iterable: Iterable[Any]) -> Iterator[Tuple[Any, Any]]:
639
631
 
640
632
  def partition(
641
633
  pred: Callable[[T], bool], iterable: Iterable[T]
642
- ) -> Tuple[Iterable[T], Iterable[T]]:
634
+ ) -> tuple[Iterable[T], Iterable[T]]:
643
635
  """
644
636
  Use a predicate to partition entries into false entries and true entries,
645
637
  like
@@ -656,9 +648,9 @@ class ConfiguredBuildBackendHookCaller(BuildBackendHookCaller):
656
648
  config_holder: Any,
657
649
  source_dir: str,
658
650
  build_backend: str,
659
- backend_path: Optional[str] = None,
660
- runner: Optional[Callable[..., None]] = None,
661
- python_executable: Optional[str] = None,
651
+ backend_path: str | None = None,
652
+ runner: Callable[..., None] | None = None,
653
+ python_executable: str | None = None,
662
654
  ):
663
655
  super().__init__(
664
656
  source_dir, build_backend, backend_path, runner, python_executable
@@ -668,8 +660,8 @@ class ConfiguredBuildBackendHookCaller(BuildBackendHookCaller):
668
660
  def build_wheel(
669
661
  self,
670
662
  wheel_directory: str,
671
- config_settings: Optional[Mapping[str, Any]] = None,
672
- metadata_directory: Optional[str] = None,
663
+ config_settings: Mapping[str, Any] | None = None,
664
+ metadata_directory: str | None = None,
673
665
  ) -> str:
674
666
  cs = self.config_holder.config_settings
675
667
  return super().build_wheel(
@@ -679,7 +671,7 @@ class ConfiguredBuildBackendHookCaller(BuildBackendHookCaller):
679
671
  def build_sdist(
680
672
  self,
681
673
  sdist_directory: str,
682
- config_settings: Optional[Mapping[str, Any]] = None,
674
+ config_settings: Mapping[str, Any] | None = None,
683
675
  ) -> str:
684
676
  cs = self.config_holder.config_settings
685
677
  return super().build_sdist(sdist_directory, config_settings=cs)
@@ -687,8 +679,8 @@ class ConfiguredBuildBackendHookCaller(BuildBackendHookCaller):
687
679
  def build_editable(
688
680
  self,
689
681
  wheel_directory: str,
690
- config_settings: Optional[Mapping[str, Any]] = None,
691
- metadata_directory: Optional[str] = None,
682
+ config_settings: Mapping[str, Any] | None = None,
683
+ metadata_directory: str | None = None,
692
684
  ) -> str:
693
685
  cs = self.config_holder.config_settings
694
686
  return super().build_editable(
@@ -696,19 +688,19 @@ class ConfiguredBuildBackendHookCaller(BuildBackendHookCaller):
696
688
  )
697
689
 
698
690
  def get_requires_for_build_wheel(
699
- self, config_settings: Optional[Mapping[str, Any]] = None
691
+ self, config_settings: Mapping[str, Any] | None = None
700
692
  ) -> Sequence[str]:
701
693
  cs = self.config_holder.config_settings
702
694
  return super().get_requires_for_build_wheel(config_settings=cs)
703
695
 
704
696
  def get_requires_for_build_sdist(
705
- self, config_settings: Optional[Mapping[str, Any]] = None
697
+ self, config_settings: Mapping[str, Any] | None = None
706
698
  ) -> Sequence[str]:
707
699
  cs = self.config_holder.config_settings
708
700
  return super().get_requires_for_build_sdist(config_settings=cs)
709
701
 
710
702
  def get_requires_for_build_editable(
711
- self, config_settings: Optional[Mapping[str, Any]] = None
703
+ self, config_settings: Mapping[str, Any] | None = None
712
704
  ) -> Sequence[str]:
713
705
  cs = self.config_holder.config_settings
714
706
  return super().get_requires_for_build_editable(config_settings=cs)
@@ -716,7 +708,7 @@ class ConfiguredBuildBackendHookCaller(BuildBackendHookCaller):
716
708
  def prepare_metadata_for_build_wheel(
717
709
  self,
718
710
  metadata_directory: str,
719
- config_settings: Optional[Mapping[str, Any]] = None,
711
+ config_settings: Mapping[str, Any] | None = None,
720
712
  _allow_fallback: bool = True,
721
713
  ) -> str:
722
714
  cs = self.config_holder.config_settings
@@ -729,9 +721,9 @@ class ConfiguredBuildBackendHookCaller(BuildBackendHookCaller):
729
721
  def prepare_metadata_for_build_editable(
730
722
  self,
731
723
  metadata_directory: str,
732
- config_settings: Optional[Mapping[str, Any]] = None,
724
+ config_settings: Mapping[str, Any] | None = None,
733
725
  _allow_fallback: bool = True,
734
- ) -> Optional[str]:
726
+ ) -> str | None:
735
727
  cs = self.config_holder.config_settings
736
728
  return super().prepare_metadata_for_build_editable(
737
729
  metadata_directory=metadata_directory,
@@ -1,6 +1,7 @@
1
+ from __future__ import annotations
2
+
1
3
  import functools
2
4
  import logging
3
- from typing import Optional, Tuple
4
5
 
5
6
  from pip._vendor.packaging import specifiers, version
6
7
  from pip._vendor.packaging.requirements import Requirement
@@ -10,7 +11,7 @@ logger = logging.getLogger(__name__)
10
11
 
11
12
  @functools.lru_cache(maxsize=32)
12
13
  def check_requires_python(
13
- requires_python: Optional[str], version_info: Tuple[int, ...]
14
+ requires_python: str | None, version_info: tuple[int, ...]
14
15
  ) -> bool:
15
16
  """
16
17
  Check if the given Python version matches a "Requires-Python" specifier.
@@ -1,11 +1,14 @@
1
+ from __future__ import annotations
2
+
1
3
  import functools
2
4
  from time import perf_counter, sleep
3
- from typing import Callable, TypeVar
5
+ from typing import TYPE_CHECKING, Callable, TypeVar
4
6
 
5
- from pip._vendor.typing_extensions import ParamSpec
7
+ if TYPE_CHECKING:
8
+ from typing_extensions import ParamSpec
6
9
 
7
- T = TypeVar("T")
8
- P = ParamSpec("P")
10
+ T = TypeVar("T")
11
+ P = ParamSpec("P")
9
12
 
10
13
 
11
14
  def retry(
@@ -1,8 +1,11 @@
1
+ from __future__ import annotations
2
+
1
3
  import logging
2
4
  import os
3
5
  import shlex
4
6
  import subprocess
5
- from typing import Any, Callable, Iterable, List, Literal, Mapping, Optional, Union
7
+ from collections.abc import Iterable, Mapping
8
+ from typing import Any, Callable, Literal, Union
6
9
 
7
10
  from pip._vendor.rich.markup import escape
8
11
 
@@ -11,10 +14,10 @@ from pip._internal.exceptions import InstallationSubprocessError
11
14
  from pip._internal.utils.logging import VERBOSE, subprocess_logger
12
15
  from pip._internal.utils.misc import HiddenText
13
16
 
14
- CommandArgs = List[Union[str, HiddenText]]
17
+ CommandArgs = list[Union[str, HiddenText]]
15
18
 
16
19
 
17
- def make_command(*args: Union[str, HiddenText, CommandArgs]) -> CommandArgs:
20
+ def make_command(*args: str | HiddenText | CommandArgs) -> CommandArgs:
18
21
  """
19
22
  Create a CommandArgs object.
20
23
  """
@@ -31,7 +34,7 @@ def make_command(*args: Union[str, HiddenText, CommandArgs]) -> CommandArgs:
31
34
  return command_args
32
35
 
33
36
 
34
- def format_command_args(args: Union[List[str], CommandArgs]) -> str:
37
+ def format_command_args(args: list[str] | CommandArgs) -> str:
35
38
  """
36
39
  Format command arguments for display.
37
40
  """
@@ -46,7 +49,7 @@ def format_command_args(args: Union[List[str], CommandArgs]) -> str:
46
49
  )
47
50
 
48
51
 
49
- def reveal_command_args(args: Union[List[str], CommandArgs]) -> List[str]:
52
+ def reveal_command_args(args: list[str] | CommandArgs) -> list[str]:
50
53
  """
51
54
  Return the arguments in their raw, unredacted form.
52
55
  """
@@ -54,16 +57,16 @@ def reveal_command_args(args: Union[List[str], CommandArgs]) -> List[str]:
54
57
 
55
58
 
56
59
  def call_subprocess(
57
- cmd: Union[List[str], CommandArgs],
60
+ cmd: list[str] | CommandArgs,
58
61
  show_stdout: bool = False,
59
- cwd: Optional[str] = None,
60
- on_returncode: 'Literal["raise", "warn", "ignore"]' = "raise",
61
- extra_ok_returncodes: Optional[Iterable[int]] = None,
62
- extra_environ: Optional[Mapping[str, Any]] = None,
63
- unset_environ: Optional[Iterable[str]] = None,
64
- spinner: Optional[SpinnerInterface] = None,
65
- log_failed_cmd: Optional[bool] = True,
66
- stdout_only: Optional[bool] = False,
62
+ cwd: str | None = None,
63
+ on_returncode: Literal["raise", "warn", "ignore"] = "raise",
64
+ extra_ok_returncodes: Iterable[int] | None = None,
65
+ extra_environ: Mapping[str, Any] | None = None,
66
+ unset_environ: Iterable[str] | None = None,
67
+ spinner: SpinnerInterface | None = None,
68
+ log_failed_cmd: bool | None = True,
69
+ stdout_only: bool | None = False,
67
70
  *,
68
71
  command_desc: str,
69
72
  ) -> str:
@@ -229,9 +232,9 @@ def runner_with_spinner_message(message: str) -> Callable[..., None]:
229
232
  """
230
233
 
231
234
  def runner(
232
- cmd: List[str],
233
- cwd: Optional[str] = None,
234
- extra_environ: Optional[Mapping[str, Any]] = None,
235
+ cmd: list[str],
236
+ cwd: str | None = None,
237
+ extra_environ: Mapping[str, Any] | None = None,
235
238
  ) -> None:
236
239
  with open_spinner(message) as spinner:
237
240
  call_subprocess(
@@ -1,20 +1,18 @@
1
+ from __future__ import annotations
2
+
1
3
  import errno
2
4
  import itertools
3
5
  import logging
4
6
  import os.path
5
7
  import tempfile
6
8
  import traceback
9
+ from collections.abc import Generator
7
10
  from contextlib import ExitStack, contextmanager
8
11
  from pathlib import Path
9
12
  from typing import (
10
13
  Any,
11
14
  Callable,
12
- Dict,
13
- Generator,
14
- List,
15
- Optional,
16
15
  TypeVar,
17
- Union,
18
16
  )
19
17
 
20
18
  from pip._internal.utils.misc import enum, rmtree
@@ -33,7 +31,7 @@ tempdir_kinds = enum(
33
31
  )
34
32
 
35
33
 
36
- _tempdir_manager: Optional[ExitStack] = None
34
+ _tempdir_manager: ExitStack | None = None
37
35
 
38
36
 
39
37
  @contextmanager
@@ -51,7 +49,7 @@ class TempDirectoryTypeRegistry:
51
49
  """Manages temp directory behavior"""
52
50
 
53
51
  def __init__(self) -> None:
54
- self._should_delete: Dict[str, bool] = {}
52
+ self._should_delete: dict[str, bool] = {}
55
53
 
56
54
  def set_delete(self, kind: str, value: bool) -> None:
57
55
  """Indicate whether a TempDirectory of the given kind should be
@@ -66,7 +64,7 @@ class TempDirectoryTypeRegistry:
66
64
  return self._should_delete.get(kind, True)
67
65
 
68
66
 
69
- _tempdir_registry: Optional[TempDirectoryTypeRegistry] = None
67
+ _tempdir_registry: TempDirectoryTypeRegistry | None = None
70
68
 
71
69
 
72
70
  @contextmanager
@@ -113,8 +111,8 @@ class TempDirectory:
113
111
 
114
112
  def __init__(
115
113
  self,
116
- path: Optional[str] = None,
117
- delete: Union[bool, None, _Default] = _default,
114
+ path: str | None = None,
115
+ delete: bool | None | _Default = _default,
118
116
  kind: str = "temp",
119
117
  globally_managed: bool = False,
120
118
  ignore_cleanup_errors: bool = True,
@@ -184,7 +182,7 @@ class TempDirectory:
184
182
  if not os.path.exists(self._path):
185
183
  return
186
184
 
187
- errors: List[BaseException] = []
185
+ errors: list[BaseException] = []
188
186
 
189
187
  def onerror(
190
188
  func: Callable[..., Any],
@@ -245,7 +243,7 @@ class AdjacentTempDirectory(TempDirectory):
245
243
  # with leading '-' and invalid metadata
246
244
  LEADING_CHARS = "-~.=%0123456789"
247
245
 
248
- def __init__(self, original: str, delete: Optional[bool] = None) -> None:
246
+ def __init__(self, original: str, delete: bool | None = None) -> None:
249
247
  self.original = original.rstrip("/\\")
250
248
  super().__init__(delete=delete)
251
249
 
@@ -1,5 +1,7 @@
1
1
  """Utilities related archives."""
2
2
 
3
+ from __future__ import annotations
4
+
3
5
  import logging
4
6
  import os
5
7
  import shutil
@@ -7,7 +9,7 @@ import stat
7
9
  import sys
8
10
  import tarfile
9
11
  import zipfile
10
- from typing import Iterable, List, Optional
12
+ from collections.abc import Iterable
11
13
  from zipfile import ZipInfo
12
14
 
13
15
  from pip._internal.exceptions import InstallationError
@@ -47,7 +49,7 @@ def current_umask() -> int:
47
49
  return mask
48
50
 
49
51
 
50
- def split_leading_dir(path: str) -> List[str]:
52
+ def split_leading_dir(path: str) -> list[str]:
51
53
  path = path.lstrip("/").lstrip("\\")
52
54
  if "/" in path and (
53
55
  ("\\" in path and path.find("/") < path.find("\\")) or "\\" not in path
@@ -131,7 +133,7 @@ def unzip_file(filename: str, location: str, flatten: bool = True) -> None:
131
133
  "outside target directory ({})"
132
134
  )
133
135
  raise InstallationError(message.format(filename, fn, location))
134
- if fn.endswith("/") or fn.endswith("\\"):
136
+ if fn.endswith(("/", "\\")):
135
137
  # A directory
136
138
  ensure_dir(fn)
137
139
  else:
@@ -246,6 +248,20 @@ def untar_file(filename: str, location: str) -> None:
246
248
  tar.close()
247
249
 
248
250
 
251
+ def is_symlink_target_in_tar(tar: tarfile.TarFile, tarinfo: tarfile.TarInfo) -> bool:
252
+ """Check if the file pointed to by the symbolic link is in the tar archive"""
253
+ linkname = os.path.join(os.path.dirname(tarinfo.name), tarinfo.linkname)
254
+
255
+ linkname = os.path.normpath(linkname)
256
+ linkname = linkname.replace("\\", "/")
257
+
258
+ try:
259
+ tar.getmember(linkname)
260
+ return True
261
+ except KeyError:
262
+ return False
263
+
264
+
249
265
  def _untar_without_filter(
250
266
  filename: str,
251
267
  location: str,
@@ -253,6 +269,9 @@ def _untar_without_filter(
253
269
  leading: bool,
254
270
  ) -> None:
255
271
  """Fallback for Python without tarfile.data_filter"""
272
+ # NOTE: This function can be removed once pip requires CPython ≥ 3.12.​
273
+ # PEP 706 added tarfile.data_filter, made tarfile extraction operations more secure.
274
+ # This feature is fully supported from CPython 3.12 onward.
256
275
  for member in tar.getmembers():
257
276
  fn = member.name
258
277
  if leading:
@@ -267,6 +286,14 @@ def _untar_without_filter(
267
286
  if member.isdir():
268
287
  ensure_dir(path)
269
288
  elif member.issym():
289
+ if not is_symlink_target_in_tar(tar, member):
290
+ message = (
291
+ "The tar file ({}) has a file ({}) trying to install "
292
+ "outside target directory ({})"
293
+ )
294
+ raise InstallationError(
295
+ message.format(filename, member.name, member.linkname)
296
+ )
270
297
  try:
271
298
  tar._extract_member(member, path)
272
299
  except Exception as exc:
@@ -307,7 +334,7 @@ def _untar_without_filter(
307
334
  def unpack_file(
308
335
  filename: str,
309
336
  location: str,
310
- content_type: Optional[str] = None,
337
+ content_type: str | None = None,
311
338
  ) -> None:
312
339
  filename = os.path.realpath(filename)
313
340
  if (
@@ -12,7 +12,7 @@ def path_to_url(path: str) -> str:
12
12
  quoted path parts.
13
13
  """
14
14
  path = os.path.normpath(os.path.abspath(path))
15
- url = urllib.parse.urljoin("file:", urllib.request.pathname2url(path))
15
+ url = urllib.parse.urljoin("file://", urllib.request.pathname2url(path))
16
16
  return url
17
17
 
18
18