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,17 +1,22 @@
1
+ from __future__ import annotations
2
+
1
3
  import dataclasses
2
4
  import re
5
+ from collections.abc import Iterable
3
6
  from dataclasses import dataclass
4
7
  from pathlib import Path
5
- from typing import Any, Dict, Iterable, List, Optional, Tuple
8
+ from typing import TYPE_CHECKING, Any
6
9
 
7
10
  from pip._vendor import tomli_w
8
- from pip._vendor.typing_extensions import Self
9
11
 
10
12
  from pip._internal.models.direct_url import ArchiveInfo, DirInfo, VcsInfo
11
13
  from pip._internal.models.link import Link
12
14
  from pip._internal.req.req_install import InstallRequirement
13
15
  from pip._internal.utils.urls import url_to_path
14
16
 
17
+ if TYPE_CHECKING:
18
+ from typing_extensions import Self
19
+
15
20
  PYLOCK_FILE_NAME_RE = re.compile(r"^pylock\.([^.]+)\.toml$")
16
21
 
17
22
 
@@ -19,70 +24,70 @@ def is_valid_pylock_file_name(path: Path) -> bool:
19
24
  return path.name == "pylock.toml" or bool(re.match(PYLOCK_FILE_NAME_RE, path.name))
20
25
 
21
26
 
22
- def _toml_dict_factory(data: List[Tuple[str, Any]]) -> Dict[str, Any]:
27
+ def _toml_dict_factory(data: list[tuple[str, Any]]) -> dict[str, Any]:
23
28
  return {key.replace("_", "-"): value for key, value in data if value is not None}
24
29
 
25
30
 
26
31
  @dataclass
27
32
  class PackageVcs:
28
33
  type: str
29
- url: Optional[str]
34
+ url: str | None
30
35
  # (not supported) path: Optional[str]
31
- requested_revision: Optional[str]
36
+ requested_revision: str | None
32
37
  commit_id: str
33
- subdirectory: Optional[str]
38
+ subdirectory: str | None
34
39
 
35
40
 
36
41
  @dataclass
37
42
  class PackageDirectory:
38
43
  path: str
39
- editable: Optional[bool]
40
- subdirectory: Optional[str]
44
+ editable: bool | None
45
+ subdirectory: str | None
41
46
 
42
47
 
43
48
  @dataclass
44
49
  class PackageArchive:
45
- url: Optional[str]
50
+ url: str | None
46
51
  # (not supported) path: Optional[str]
47
52
  # (not supported) size: Optional[int]
48
53
  # (not supported) upload_time: Optional[datetime]
49
- hashes: Dict[str, str]
50
- subdirectory: Optional[str]
54
+ hashes: dict[str, str]
55
+ subdirectory: str | None
51
56
 
52
57
 
53
58
  @dataclass
54
59
  class PackageSdist:
55
60
  name: str
56
61
  # (not supported) upload_time: Optional[datetime]
57
- url: Optional[str]
62
+ url: str | None
58
63
  # (not supported) path: Optional[str]
59
64
  # (not supported) size: Optional[int]
60
- hashes: Dict[str, str]
65
+ hashes: dict[str, str]
61
66
 
62
67
 
63
68
  @dataclass
64
69
  class PackageWheel:
65
70
  name: str
66
71
  # (not supported) upload_time: Optional[datetime]
67
- url: Optional[str]
72
+ url: str | None
68
73
  # (not supported) path: Optional[str]
69
74
  # (not supported) size: Optional[int]
70
- hashes: Dict[str, str]
75
+ hashes: dict[str, str]
71
76
 
72
77
 
73
78
  @dataclass
74
79
  class Package:
75
80
  name: str
76
- version: Optional[str] = None
81
+ version: str | None = None
77
82
  # (not supported) marker: Optional[str]
78
83
  # (not supported) requires_python: Optional[str]
79
84
  # (not supported) dependencies
80
- vcs: Optional[PackageVcs] = None
81
- directory: Optional[PackageDirectory] = None
82
- archive: Optional[PackageArchive] = None
85
+ vcs: PackageVcs | None = None
86
+ directory: PackageDirectory | None = None
87
+ archive: PackageArchive | None = None
83
88
  # (not supported) index: Optional[str]
84
- sdist: Optional[PackageSdist] = None
85
- wheels: Optional[List[PackageWheel]] = None
89
+ sdist: PackageSdist | None = None
90
+ wheels: list[PackageWheel] | None = None
86
91
  # (not supported) attestation_identities: Optional[List[Dict[str, Any]]]
87
92
  # (not supported) tool: Optional[Dict[str, Any]]
88
93
 
@@ -162,7 +167,7 @@ class Pylock:
162
167
  # (not supported) extras: List[str] = []
163
168
  # (not supported) dependency_groups: List[str] = []
164
169
  created_by: str = "pip"
165
- packages: List[Package] = dataclasses.field(default_factory=list)
170
+ packages: list[Package] = dataclasses.field(default_factory=list)
166
171
  # (not supported) tool: Optional[Dict[str, Any]]
167
172
 
168
173
  def as_toml(self) -> str:
@@ -4,7 +4,6 @@ import os
4
4
  import posixpath
5
5
  import urllib.parse
6
6
  from dataclasses import dataclass
7
- from typing import List
8
7
 
9
8
  from pip._vendor.packaging.utils import canonicalize_name
10
9
 
@@ -23,15 +22,15 @@ class SearchScope:
23
22
 
24
23
  __slots__ = ["find_links", "index_urls", "no_index"]
25
24
 
26
- find_links: List[str]
27
- index_urls: List[str]
25
+ find_links: list[str]
26
+ index_urls: list[str]
28
27
  no_index: bool
29
28
 
30
29
  @classmethod
31
30
  def create(
32
31
  cls,
33
- find_links: List[str],
34
- index_urls: List[str],
32
+ find_links: list[str],
33
+ index_urls: list[str],
35
34
  no_index: bool,
36
35
  ) -> "SearchScope":
37
36
  """
@@ -42,7 +41,7 @@ class SearchScope:
42
41
  # it and if it exists, use the normalized version.
43
42
  # This is deliberately conservative - it might be fine just to
44
43
  # blindly normalize anything starting with a ~...
45
- built_find_links: List[str] = []
44
+ built_find_links: list[str] = []
46
45
  for link in find_links:
47
46
  if link.startswith("~"):
48
47
  new_link = normalize_path(link)
@@ -104,7 +103,7 @@ class SearchScope:
104
103
  )
105
104
  return "\n".join(lines)
106
105
 
107
- def get_index_urls_locations(self, project_name: str) -> List[str]:
106
+ def get_index_urls_locations(self, project_name: str) -> list[str]:
108
107
  """Returns the locations found via self.index_urls
109
108
 
110
109
  Checks the url_name on the main (first in the list) index and
@@ -1,4 +1,4 @@
1
- from typing import Optional
1
+ from __future__ import annotations
2
2
 
3
3
  from pip._internal.models.format_control import FormatControl
4
4
 
@@ -27,9 +27,9 @@ class SelectionPreferences:
27
27
  self,
28
28
  allow_yanked: bool,
29
29
  allow_all_prereleases: bool = False,
30
- format_control: Optional[FormatControl] = None,
30
+ format_control: FormatControl | None = None,
31
31
  prefer_binary: bool = False,
32
- ignore_requires_python: Optional[bool] = None,
32
+ ignore_requires_python: bool | None = None,
33
33
  ) -> None:
34
34
  """Create a SelectionPreferences object.
35
35
 
@@ -1,5 +1,6 @@
1
+ from __future__ import annotations
2
+
1
3
  import sys
2
- from typing import List, Optional, Set, Tuple
3
4
 
4
5
  from pip._vendor.packaging.tags import Tag
5
6
 
@@ -26,10 +27,10 @@ class TargetPython:
26
27
 
27
28
  def __init__(
28
29
  self,
29
- platforms: Optional[List[str]] = None,
30
- py_version_info: Optional[Tuple[int, ...]] = None,
31
- abis: Optional[List[str]] = None,
32
- implementation: Optional[str] = None,
30
+ platforms: list[str] | None = None,
31
+ py_version_info: tuple[int, ...] | None = None,
32
+ abis: list[str] | None = None,
33
+ implementation: str | None = None,
33
34
  ) -> None:
34
35
  """
35
36
  :param platforms: A list of strings or None. If None, searches for
@@ -62,8 +63,8 @@ class TargetPython:
62
63
  self.py_version_info = py_version_info
63
64
 
64
65
  # This is used to cache the return value of get_(un)sorted_tags.
65
- self._valid_tags: Optional[List[Tag]] = None
66
- self._valid_tags_set: Optional[Set[Tag]] = None
66
+ self._valid_tags: list[Tag] | None = None
67
+ self._valid_tags_set: set[Tag] | None = None
67
68
 
68
69
  def format_given(self) -> str:
69
70
  """
@@ -85,7 +86,7 @@ class TargetPython:
85
86
  f"{key}={value!r}" for key, value in key_values if value is not None
86
87
  )
87
88
 
88
- def get_sorted_tags(self) -> List[Tag]:
89
+ def get_sorted_tags(self) -> list[Tag]:
89
90
  """
90
91
  Return the supported PEP 425 tags to check wheel candidates against.
91
92
 
@@ -110,7 +111,7 @@ class TargetPython:
110
111
 
111
112
  return self._valid_tags
112
113
 
113
- def get_unsorted_tags(self) -> Set[Tag]:
114
+ def get_unsorted_tags(self) -> set[Tag]:
114
115
  """Exactly the same as get_sorted_tags, but returns a set.
115
116
 
116
117
  This is important for performance.
@@ -2,97 +2,38 @@
2
2
  name that have meaning.
3
3
  """
4
4
 
5
- import re
6
- from typing import Dict, Iterable, List, Optional
5
+ from __future__ import annotations
6
+
7
+ from collections.abc import Iterable
7
8
 
8
9
  from pip._vendor.packaging.tags import Tag
9
- from pip._vendor.packaging.utils import BuildTag, parse_wheel_filename
10
10
  from pip._vendor.packaging.utils import (
11
11
  InvalidWheelFilename as _PackagingInvalidWheelFilename,
12
12
  )
13
+ from pip._vendor.packaging.utils import parse_wheel_filename
13
14
 
14
15
  from pip._internal.exceptions import InvalidWheelFilename
15
- from pip._internal.utils.deprecation import deprecated
16
16
 
17
17
 
18
18
  class Wheel:
19
19
  """A wheel file"""
20
20
 
21
- legacy_wheel_file_re = re.compile(
22
- r"""^(?P<namever>(?P<name>[^\s-]+?)-(?P<ver>[^\s-]*?))
23
- ((-(?P<build>\d[^-]*?))?-(?P<pyver>[^\s-]+?)-(?P<abi>[^\s-]+?)-(?P<plat>[^\s-]+?)
24
- \.whl|\.dist-info)$""",
25
- re.VERBOSE,
26
- )
27
-
28
21
  def __init__(self, filename: str) -> None:
29
22
  self.filename = filename
30
23
 
31
- # To make mypy happy specify type hints that can come from either
32
- # parse_wheel_filename or the legacy_wheel_file_re match.
33
- self.name: str
34
- self._build_tag: Optional[BuildTag] = None
35
-
36
24
  try:
37
25
  wheel_info = parse_wheel_filename(filename)
38
- self.name, _version, self._build_tag, self.file_tags = wheel_info
39
- self.version = str(_version)
40
26
  except _PackagingInvalidWheelFilename as e:
41
- # Check if the wheel filename is in the legacy format
42
- legacy_wheel_info = self.legacy_wheel_file_re.match(filename)
43
- if not legacy_wheel_info:
44
- raise InvalidWheelFilename(e.args[0]) from None
45
-
46
- deprecated(
47
- reason=(
48
- f"Wheel filename {filename!r} is not correctly normalised. "
49
- "Future versions of pip will raise the following error:\n"
50
- f"{e.args[0]}\n\n"
51
- ),
52
- replacement=(
53
- "to rename the wheel to use a correctly normalised "
54
- "name (this may require updating the version in "
55
- "the project metadata)"
56
- ),
57
- gone_in="25.3",
58
- issue=12938,
59
- )
60
-
61
- self.name = legacy_wheel_info.group("name").replace("_", "-")
62
- self.version = legacy_wheel_info.group("ver").replace("_", "-")
63
-
64
- # Generate the file tags from the legacy wheel filename
65
- pyversions = legacy_wheel_info.group("pyver").split(".")
66
- abis = legacy_wheel_info.group("abi").split(".")
67
- plats = legacy_wheel_info.group("plat").split(".")
68
- self.file_tags = frozenset(
69
- Tag(interpreter=py, abi=abi, platform=plat)
70
- for py in pyversions
71
- for abi in abis
72
- for plat in plats
73
- )
74
-
75
- @property
76
- def build_tag(self) -> BuildTag:
77
- if self._build_tag is not None:
78
- return self._build_tag
79
-
80
- # Parse the build tag from the legacy wheel filename
81
- legacy_wheel_info = self.legacy_wheel_file_re.match(self.filename)
82
- assert legacy_wheel_info is not None, "guaranteed by filename validation"
83
- build_tag = legacy_wheel_info.group("build")
84
- match = re.match(r"^(\d+)(.*)$", build_tag)
85
- assert match is not None, "guaranteed by filename validation"
86
- build_tag_groups = match.groups()
87
- self._build_tag = (int(build_tag_groups[0]), build_tag_groups[1])
88
-
89
- return self._build_tag
90
-
91
- def get_formatted_file_tags(self) -> List[str]:
27
+ raise InvalidWheelFilename(e.args[0]) from None
28
+
29
+ self.name, _version, self.build_tag, self.file_tags = wheel_info
30
+ self.version = str(_version)
31
+
32
+ def get_formatted_file_tags(self) -> list[str]:
92
33
  """Return the wheel's tags as a sorted list of strings."""
93
34
  return sorted(str(tag) for tag in self.file_tags)
94
35
 
95
- def support_index_min(self, tags: List[Tag]) -> int:
36
+ def support_index_min(self, tags: list[Tag]) -> int:
96
37
  """Return the lowest index that one of the wheel's file_tag combinations
97
38
  achieves in the given list of supported tags.
98
39
 
@@ -111,7 +52,7 @@ class Wheel:
111
52
  raise ValueError()
112
53
 
113
54
  def find_most_preferred_tag(
114
- self, tags: List[Tag], tag_to_priority: Dict[Tag, int]
55
+ self, tags: list[Tag], tag_to_priority: dict[Tag, int]
115
56
  ) -> int:
116
57
  """Return the priority of the most preferred tag that one of the wheel's file
117
58
  tag combinations achieves in the given list of supported tags using the given
@@ -4,6 +4,8 @@ Contains interface (MultiDomainBasicAuth) and associated glue code for
4
4
  providing credentials in the context of network requests.
5
5
  """
6
6
 
7
+ from __future__ import annotations
8
+
7
9
  import logging
8
10
  import os
9
11
  import shutil
@@ -12,10 +14,10 @@ import sysconfig
12
14
  import typing
13
15
  import urllib.parse
14
16
  from abc import ABC, abstractmethod
15
- from functools import lru_cache
17
+ from functools import cache
16
18
  from os.path import commonprefix
17
19
  from pathlib import Path
18
- from typing import Any, Dict, List, NamedTuple, Optional, Tuple
20
+ from typing import Any, NamedTuple
19
21
 
20
22
  from pip._vendor.requests.auth import AuthBase, HTTPBasicAuth
21
23
  from pip._vendor.requests.models import Request, Response
@@ -48,9 +50,7 @@ class KeyRingBaseProvider(ABC):
48
50
  has_keyring: bool
49
51
 
50
52
  @abstractmethod
51
- def get_auth_info(
52
- self, url: str, username: Optional[str]
53
- ) -> Optional[AuthInfo]: ...
53
+ def get_auth_info(self, url: str, username: str | None) -> AuthInfo | None: ...
54
54
 
55
55
  @abstractmethod
56
56
  def save_auth_info(self, url: str, username: str, password: str) -> None: ...
@@ -61,7 +61,7 @@ class KeyRingNullProvider(KeyRingBaseProvider):
61
61
 
62
62
  has_keyring = False
63
63
 
64
- def get_auth_info(self, url: str, username: Optional[str]) -> Optional[AuthInfo]:
64
+ def get_auth_info(self, url: str, username: str | None) -> AuthInfo | None:
65
65
  return None
66
66
 
67
67
  def save_auth_info(self, url: str, username: str, password: str) -> None:
@@ -78,7 +78,7 @@ class KeyRingPythonProvider(KeyRingBaseProvider):
78
78
 
79
79
  self.keyring = keyring
80
80
 
81
- def get_auth_info(self, url: str, username: Optional[str]) -> Optional[AuthInfo]:
81
+ def get_auth_info(self, url: str, username: str | None) -> AuthInfo | None:
82
82
  # Support keyring's get_credential interface which supports getting
83
83
  # credentials without a username. This is only available for
84
84
  # keyring>=15.2.0.
@@ -114,7 +114,7 @@ class KeyRingCliProvider(KeyRingBaseProvider):
114
114
  def __init__(self, cmd: str) -> None:
115
115
  self.keyring = cmd
116
116
 
117
- def get_auth_info(self, url: str, username: Optional[str]) -> Optional[AuthInfo]:
117
+ def get_auth_info(self, url: str, username: str | None) -> AuthInfo | None:
118
118
  # This is the default implementation of keyring.get_credential
119
119
  # https://github.com/jaraco/keyring/blob/97689324abcf01bd1793d49063e7ca01e03d7d07/keyring/backend.py#L134-L139
120
120
  if username is not None:
@@ -126,7 +126,7 @@ class KeyRingCliProvider(KeyRingBaseProvider):
126
126
  def save_auth_info(self, url: str, username: str, password: str) -> None:
127
127
  return self._set_password(url, username, password)
128
128
 
129
- def _get_password(self, service_name: str, username: str) -> Optional[str]:
129
+ def _get_password(self, service_name: str, username: str) -> str | None:
130
130
  """Mirror the implementation of keyring.get_password using cli"""
131
131
  if self.keyring is None:
132
132
  return None
@@ -159,7 +159,7 @@ class KeyRingCliProvider(KeyRingBaseProvider):
159
159
  return None
160
160
 
161
161
 
162
- @lru_cache(maxsize=None)
162
+ @cache
163
163
  def get_keyring_provider(provider: str) -> KeyRingBaseProvider:
164
164
  logger.verbose("Keyring provider requested: %s", provider)
165
165
 
@@ -225,19 +225,19 @@ class MultiDomainBasicAuth(AuthBase):
225
225
  def __init__(
226
226
  self,
227
227
  prompting: bool = True,
228
- index_urls: Optional[List[str]] = None,
228
+ index_urls: list[str] | None = None,
229
229
  keyring_provider: str = "auto",
230
230
  ) -> None:
231
231
  self.prompting = prompting
232
232
  self.index_urls = index_urls
233
- self.keyring_provider = keyring_provider # type: ignore[assignment]
234
- self.passwords: Dict[str, AuthInfo] = {}
233
+ self.keyring_provider = keyring_provider
234
+ self.passwords: dict[str, AuthInfo] = {}
235
235
  # When the user is prompted to enter credentials and keyring is
236
236
  # available, we will offer to save them. If the user accepts,
237
237
  # this value is set to the credentials they entered. After the
238
238
  # request authenticates, the caller should call
239
239
  # ``save_credentials`` to save these.
240
- self._credentials_to_save: Optional[Credentials] = None
240
+ self._credentials_to_save: Credentials | None = None
241
241
 
242
242
  @property
243
243
  def keyring_provider(self) -> KeyRingBaseProvider:
@@ -260,9 +260,9 @@ class MultiDomainBasicAuth(AuthBase):
260
260
 
261
261
  def _get_keyring_auth(
262
262
  self,
263
- url: Optional[str],
264
- username: Optional[str],
265
- ) -> Optional[AuthInfo]:
263
+ url: str | None,
264
+ username: str | None,
265
+ ) -> AuthInfo | None:
266
266
  """Return the tuple auth for a given url from keyring."""
267
267
  # Do nothing if no url was provided
268
268
  if not url:
@@ -284,7 +284,7 @@ class MultiDomainBasicAuth(AuthBase):
284
284
  get_keyring_provider.cache_clear()
285
285
  return None
286
286
 
287
- def _get_index_url(self, url: str) -> Optional[str]:
287
+ def _get_index_url(self, url: str) -> str | None:
288
288
  """Return the original index URL matching the requested URL.
289
289
 
290
290
  Cached or dynamically generated credentials may work against
@@ -391,7 +391,7 @@ class MultiDomainBasicAuth(AuthBase):
391
391
 
392
392
  def _get_url_and_credentials(
393
393
  self, original_url: str
394
- ) -> Tuple[str, Optional[str], Optional[str]]:
394
+ ) -> tuple[str, str | None, str | None]:
395
395
  """Return the credentials to use for the provided URL.
396
396
 
397
397
  If allowed, netrc and keyring may be used to obtain the
@@ -454,9 +454,7 @@ class MultiDomainBasicAuth(AuthBase):
454
454
  return req
455
455
 
456
456
  # Factored out to allow for easy patching in tests
457
- def _prompt_for_password(
458
- self, netloc: str
459
- ) -> Tuple[Optional[str], Optional[str], bool]:
457
+ def _prompt_for_password(self, netloc: str) -> tuple[str | None, str | None, bool]:
460
458
  username = ask_input(f"User for {netloc}: ") if self.prompting else None
461
459
  if not username:
462
460
  return None, None, False
@@ -1,15 +1,23 @@
1
1
  """HTTP cache implementation."""
2
2
 
3
+ from __future__ import annotations
4
+
3
5
  import os
6
+ import shutil
7
+ from collections.abc import Generator
4
8
  from contextlib import contextmanager
5
9
  from datetime import datetime
6
- from typing import BinaryIO, Generator, Optional, Union
10
+ from typing import Any, BinaryIO, Callable
7
11
 
8
12
  from pip._vendor.cachecontrol.cache import SeparateBodyBaseCache
9
13
  from pip._vendor.cachecontrol.caches import SeparateBodyFileCache
10
14
  from pip._vendor.requests.models import Response
11
15
 
12
- from pip._internal.utils.filesystem import adjacent_tmp_file, replace
16
+ from pip._internal.utils.filesystem import (
17
+ adjacent_tmp_file,
18
+ copy_directory_permissions,
19
+ replace,
20
+ )
13
21
  from pip._internal.utils.misc import ensure_dir
14
22
 
15
23
 
@@ -59,7 +67,7 @@ class SafeFileCache(SeparateBodyBaseCache):
59
67
  parts = list(hashed[:5]) + [hashed]
60
68
  return os.path.join(self.directory, *parts)
61
69
 
62
- def get(self, key: str) -> Optional[bytes]:
70
+ def get(self, key: str) -> bytes | None:
63
71
  # The cache entry is only valid if both metadata and body exist.
64
72
  metadata_path = self._get_cache_path(key)
65
73
  body_path = metadata_path + ".body"
@@ -69,29 +77,27 @@ class SafeFileCache(SeparateBodyBaseCache):
69
77
  with open(metadata_path, "rb") as f:
70
78
  return f.read()
71
79
 
72
- def _write(self, path: str, data: bytes) -> None:
80
+ def _write_to_file(self, path: str, writer_func: Callable[[BinaryIO], Any]) -> None:
81
+ """Common file writing logic with proper permissions and atomic replacement."""
73
82
  with suppressed_cache_errors():
74
83
  ensure_dir(os.path.dirname(path))
75
84
 
76
85
  with adjacent_tmp_file(path) as f:
77
- f.write(data)
86
+ writer_func(f)
78
87
  # Inherit the read/write permissions of the cache directory
79
88
  # to enable multi-user cache use-cases.
80
- mode = (
81
- os.stat(self.directory).st_mode
82
- & 0o666 # select read/write permissions of cache directory
83
- | 0o600 # set owner read/write permissions
84
- )
85
- # Change permissions only if there is no risk of following a symlink.
86
- if os.chmod in os.supports_fd:
87
- os.chmod(f.fileno(), mode)
88
- elif os.chmod in os.supports_follow_symlinks:
89
- os.chmod(f.name, mode, follow_symlinks=False)
89
+ copy_directory_permissions(self.directory, f)
90
90
 
91
91
  replace(f.name, path)
92
92
 
93
+ def _write(self, path: str, data: bytes) -> None:
94
+ self._write_to_file(path, lambda f: f.write(data))
95
+
96
+ def _write_from_io(self, path: str, source_file: BinaryIO) -> None:
97
+ self._write_to_file(path, lambda f: shutil.copyfileobj(source_file, f))
98
+
93
99
  def set(
94
- self, key: str, value: bytes, expires: Union[int, datetime, None] = None
100
+ self, key: str, value: bytes, expires: int | datetime | None = None
95
101
  ) -> None:
96
102
  path = self._get_cache_path(key)
97
103
  self._write(path, value)
@@ -103,7 +109,7 @@ class SafeFileCache(SeparateBodyBaseCache):
103
109
  with suppressed_cache_errors():
104
110
  os.remove(path + ".body")
105
111
 
106
- def get_body(self, key: str) -> Optional[BinaryIO]:
112
+ def get_body(self, key: str) -> BinaryIO | None:
107
113
  # The cache entry is only valid if both metadata and body exist.
108
114
  metadata_path = self._get_cache_path(key)
109
115
  body_path = metadata_path + ".body"
@@ -115,3 +121,8 @@ class SafeFileCache(SeparateBodyBaseCache):
115
121
  def set_body(self, key: str, body: bytes) -> None:
116
122
  path = self._get_cache_path(key) + ".body"
117
123
  self._write(path, body)
124
+
125
+ def set_body_from_io(self, key: str, body_file: BinaryIO) -> None:
126
+ """Set the body of the cache entry from a file object."""
127
+ path = self._get_cache_path(key) + ".body"
128
+ self._write_from_io(path, body_file)