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,10 +1,13 @@
1
+ from __future__ import annotations
2
+
1
3
  import importlib.metadata
2
4
  import logging
3
5
  import os
4
6
  import pathlib
5
7
  import sys
6
8
  import zipfile
7
- from typing import Iterator, List, Optional, Sequence, Set, Tuple
9
+ from collections.abc import Iterator, Sequence
10
+ from typing import Optional
8
11
 
9
12
  from pip._vendor.packaging.utils import (
10
13
  InvalidWheelFilename,
@@ -47,10 +50,10 @@ class _DistributionFinder:
47
50
  installations as well. It's useful feature, after all.
48
51
  """
49
52
 
50
- FoundResult = Tuple[importlib.metadata.Distribution, Optional[BasePath]]
53
+ FoundResult = tuple[importlib.metadata.Distribution, Optional[BasePath]]
51
54
 
52
55
  def __init__(self) -> None:
53
- self._found_names: Set[NormalizedName] = set()
56
+ self._found_names: set[NormalizedName] = set()
54
57
 
55
58
  def _find_impl(self, location: str) -> Iterator[FoundResult]:
56
59
  """Find distributions in a location."""
@@ -80,7 +83,7 @@ class _DistributionFinder:
80
83
  """
81
84
  for dist, info_location in self._find_impl(location):
82
85
  if info_location is None:
83
- installed_location: Optional[BasePath] = None
86
+ installed_location: BasePath | None = None
84
87
  else:
85
88
  installed_location = info_location.parent
86
89
  yield Distribution(dist, info_location, installed_location)
@@ -119,7 +122,7 @@ class Environment(BaseEnvironment):
119
122
  return cls(sys.path)
120
123
 
121
124
  @classmethod
122
- def from_paths(cls, paths: Optional[List[str]]) -> BaseEnvironment:
125
+ def from_paths(cls, paths: list[str] | None) -> BaseEnvironment:
123
126
  if paths is None:
124
127
  return cls(sys.path)
125
128
  return cls(paths)
@@ -130,7 +133,7 @@ class Environment(BaseEnvironment):
130
133
  yield from finder.find(location)
131
134
  yield from finder.find_legacy_editables(location)
132
135
 
133
- def get_distribution(self, name: str) -> Optional[BaseDistribution]:
136
+ def get_distribution(self, name: str) -> BaseDistribution | None:
134
137
  canonical_name = canonicalize_name(name)
135
138
  matches = (
136
139
  distribution
@@ -1,16 +1,13 @@
1
+ from __future__ import annotations
2
+
1
3
  import email.message
2
4
  import email.parser
3
5
  import logging
4
6
  import os
5
7
  import zipfile
8
+ from collections.abc import Collection, Iterable, Iterator, Mapping
6
9
  from typing import (
7
- Collection,
8
- Iterable,
9
- Iterator,
10
- List,
11
- Mapping,
12
10
  NamedTuple,
13
- Optional,
14
11
  )
15
12
 
16
13
  from pip._vendor import pkg_resources
@@ -73,7 +70,7 @@ class InMemoryMetadata:
73
70
  def metadata_isdir(self, name: str) -> bool:
74
71
  return False
75
72
 
76
- def metadata_listdir(self, name: str) -> List[str]:
73
+ def metadata_listdir(self, name: str) -> list[str]:
77
74
  return []
78
75
 
79
76
  def run_script(self, script_name: str, namespace: str) -> None:
@@ -85,7 +82,7 @@ class Distribution(BaseDistribution):
85
82
  self._dist = dist
86
83
  # This is populated lazily, to avoid loading metadata for all possible
87
84
  # distributions eagerly.
88
- self.__extra_mapping: Optional[Mapping[NormalizedName, str]] = None
85
+ self.__extra_mapping: Mapping[NormalizedName, str] | None = None
89
86
 
90
87
  @property
91
88
  def _extra_mapping(self) -> Mapping[NormalizedName, str]:
@@ -155,11 +152,11 @@ class Distribution(BaseDistribution):
155
152
  return cls(dist)
156
153
 
157
154
  @property
158
- def location(self) -> Optional[str]:
155
+ def location(self) -> str | None:
159
156
  return self._dist.location
160
157
 
161
158
  @property
162
- def installed_location(self) -> Optional[str]:
159
+ def installed_location(self) -> str | None:
163
160
  egg_link = egg_link_path_from_location(self.raw_name)
164
161
  if egg_link:
165
162
  location = egg_link
@@ -170,7 +167,7 @@ class Distribution(BaseDistribution):
170
167
  return normalize_path(location)
171
168
 
172
169
  @property
173
- def info_location(self) -> Optional[str]:
170
+ def info_location(self) -> str | None:
174
171
  return self._dist.egg_info
175
172
 
176
173
  @property
@@ -259,14 +256,14 @@ class Environment(BaseEnvironment):
259
256
  return cls(pkg_resources.working_set)
260
257
 
261
258
  @classmethod
262
- def from_paths(cls, paths: Optional[List[str]]) -> BaseEnvironment:
259
+ def from_paths(cls, paths: list[str] | None) -> BaseEnvironment:
263
260
  return cls(pkg_resources.WorkingSet(paths))
264
261
 
265
262
  def _iter_distributions(self) -> Iterator[BaseDistribution]:
266
263
  for dist in self._ws:
267
264
  yield Distribution(dist)
268
265
 
269
- def _search_distribution(self, name: str) -> Optional[BaseDistribution]:
266
+ def _search_distribution(self, name: str) -> BaseDistribution | None:
270
267
  """Find a distribution matching the ``name`` in the environment.
271
268
 
272
269
  This searches from *all* distributions available in the environment, to
@@ -278,7 +275,7 @@ class Environment(BaseEnvironment):
278
275
  return dist
279
276
  return None
280
277
 
281
- def get_distribution(self, name: str) -> Optional[BaseDistribution]:
278
+ def get_distribution(self, name: str) -> BaseDistribution | None:
282
279
  # Search the distribution by looking through the working set.
283
280
  dist = self._search_distribution(name)
284
281
  if dist:
@@ -1,10 +1,13 @@
1
1
  """PEP 610"""
2
2
 
3
+ from __future__ import annotations
4
+
3
5
  import json
4
6
  import re
5
7
  import urllib.parse
8
+ from collections.abc import Iterable
6
9
  from dataclasses import dataclass
7
- from typing import Any, ClassVar, Dict, Iterable, Optional, Type, TypeVar, Union
10
+ from typing import Any, ClassVar, TypeVar, Union
8
11
 
9
12
  __all__ = [
10
13
  "DirectUrl",
@@ -25,8 +28,8 @@ class DirectUrlValidationError(Exception):
25
28
 
26
29
 
27
30
  def _get(
28
- d: Dict[str, Any], expected_type: Type[T], key: str, default: Optional[T] = None
29
- ) -> Optional[T]:
31
+ d: dict[str, Any], expected_type: type[T], key: str, default: T | None = None
32
+ ) -> T | None:
30
33
  """Get value from dictionary and verify expected type."""
31
34
  if key not in d:
32
35
  return default
@@ -39,7 +42,7 @@ def _get(
39
42
 
40
43
 
41
44
  def _get_required(
42
- d: Dict[str, Any], expected_type: Type[T], key: str, default: Optional[T] = None
45
+ d: dict[str, Any], expected_type: type[T], key: str, default: T | None = None
43
46
  ) -> T:
44
47
  value = _get(d, expected_type, key, default)
45
48
  if value is None:
@@ -47,7 +50,7 @@ def _get_required(
47
50
  return value
48
51
 
49
52
 
50
- def _exactly_one_of(infos: Iterable[Optional["InfoType"]]) -> "InfoType":
53
+ def _exactly_one_of(infos: Iterable[InfoType | None]) -> InfoType:
51
54
  infos = [info for info in infos if info is not None]
52
55
  if not infos:
53
56
  raise DirectUrlValidationError(
@@ -61,7 +64,7 @@ def _exactly_one_of(infos: Iterable[Optional["InfoType"]]) -> "InfoType":
61
64
  return infos[0]
62
65
 
63
66
 
64
- def _filter_none(**kwargs: Any) -> Dict[str, Any]:
67
+ def _filter_none(**kwargs: Any) -> dict[str, Any]:
65
68
  """Make dict excluding None values."""
66
69
  return {k: v for k, v in kwargs.items() if v is not None}
67
70
 
@@ -72,10 +75,10 @@ class VcsInfo:
72
75
 
73
76
  vcs: str
74
77
  commit_id: str
75
- requested_revision: Optional[str] = None
78
+ requested_revision: str | None = None
76
79
 
77
80
  @classmethod
78
- def _from_dict(cls, d: Optional[Dict[str, Any]]) -> Optional["VcsInfo"]:
81
+ def _from_dict(cls, d: dict[str, Any] | None) -> VcsInfo | None:
79
82
  if d is None:
80
83
  return None
81
84
  return cls(
@@ -84,7 +87,7 @@ class VcsInfo:
84
87
  requested_revision=_get(d, str, "requested_revision"),
85
88
  )
86
89
 
87
- def _to_dict(self) -> Dict[str, Any]:
90
+ def _to_dict(self) -> dict[str, Any]:
88
91
  return _filter_none(
89
92
  vcs=self.vcs,
90
93
  requested_revision=self.requested_revision,
@@ -97,19 +100,19 @@ class ArchiveInfo:
97
100
 
98
101
  def __init__(
99
102
  self,
100
- hash: Optional[str] = None,
101
- hashes: Optional[Dict[str, str]] = None,
103
+ hash: str | None = None,
104
+ hashes: dict[str, str] | None = None,
102
105
  ) -> None:
103
106
  # set hashes before hash, since the hash setter will further populate hashes
104
107
  self.hashes = hashes
105
108
  self.hash = hash
106
109
 
107
110
  @property
108
- def hash(self) -> Optional[str]:
111
+ def hash(self) -> str | None:
109
112
  return self._hash
110
113
 
111
114
  @hash.setter
112
- def hash(self, value: Optional[str]) -> None:
115
+ def hash(self, value: str | None) -> None:
113
116
  if value is not None:
114
117
  # Auto-populate the hashes key to upgrade to the new format automatically.
115
118
  # We don't back-populate the legacy hash key from hashes.
@@ -127,12 +130,12 @@ class ArchiveInfo:
127
130
  self._hash = value
128
131
 
129
132
  @classmethod
130
- def _from_dict(cls, d: Optional[Dict[str, Any]]) -> Optional["ArchiveInfo"]:
133
+ def _from_dict(cls, d: dict[str, Any] | None) -> ArchiveInfo | None:
131
134
  if d is None:
132
135
  return None
133
136
  return cls(hash=_get(d, str, "hash"), hashes=_get(d, dict, "hashes"))
134
137
 
135
- def _to_dict(self) -> Dict[str, Any]:
138
+ def _to_dict(self) -> dict[str, Any]:
136
139
  return _filter_none(hash=self.hash, hashes=self.hashes)
137
140
 
138
141
 
@@ -143,12 +146,12 @@ class DirInfo:
143
146
  editable: bool = False
144
147
 
145
148
  @classmethod
146
- def _from_dict(cls, d: Optional[Dict[str, Any]]) -> Optional["DirInfo"]:
149
+ def _from_dict(cls, d: dict[str, Any] | None) -> DirInfo | None:
147
150
  if d is None:
148
151
  return None
149
152
  return cls(editable=_get_required(d, bool, "editable", default=False))
150
153
 
151
- def _to_dict(self) -> Dict[str, Any]:
154
+ def _to_dict(self) -> dict[str, Any]:
152
155
  return _filter_none(editable=self.editable or None)
153
156
 
154
157
 
@@ -159,7 +162,7 @@ InfoType = Union[ArchiveInfo, DirInfo, VcsInfo]
159
162
  class DirectUrl:
160
163
  url: str
161
164
  info: InfoType
162
- subdirectory: Optional[str] = None
165
+ subdirectory: str | None = None
163
166
 
164
167
  def _remove_auth_from_netloc(self, netloc: str) -> str:
165
168
  if "@" not in netloc:
@@ -192,7 +195,7 @@ class DirectUrl:
192
195
  self.from_dict(self.to_dict())
193
196
 
194
197
  @classmethod
195
- def from_dict(cls, d: Dict[str, Any]) -> "DirectUrl":
198
+ def from_dict(cls, d: dict[str, Any]) -> DirectUrl:
196
199
  return DirectUrl(
197
200
  url=_get_required(d, str, "url"),
198
201
  subdirectory=_get(d, str, "subdirectory"),
@@ -205,7 +208,7 @@ class DirectUrl:
205
208
  ),
206
209
  )
207
210
 
208
- def to_dict(self) -> Dict[str, Any]:
211
+ def to_dict(self) -> dict[str, Any]:
209
212
  res = _filter_none(
210
213
  url=self.redacted_url,
211
214
  subdirectory=self.subdirectory,
@@ -214,7 +217,7 @@ class DirectUrl:
214
217
  return res
215
218
 
216
219
  @classmethod
217
- def from_json(cls, s: str) -> "DirectUrl":
220
+ def from_json(cls, s: str) -> DirectUrl:
218
221
  return cls.from_dict(json.loads(s))
219
222
 
220
223
  def to_json(self) -> str:
@@ -1,4 +1,4 @@
1
- from typing import FrozenSet, Optional, Set
1
+ from __future__ import annotations
2
2
 
3
3
  from pip._vendor.packaging.utils import canonicalize_name
4
4
 
@@ -12,8 +12,8 @@ class FormatControl:
12
12
 
13
13
  def __init__(
14
14
  self,
15
- no_binary: Optional[Set[str]] = None,
16
- only_binary: Optional[Set[str]] = None,
15
+ no_binary: set[str] | None = None,
16
+ only_binary: set[str] | None = None,
17
17
  ) -> None:
18
18
  if no_binary is None:
19
19
  no_binary = set()
@@ -36,7 +36,7 @@ class FormatControl:
36
36
  return f"{self.__class__.__name__}({self.no_binary}, {self.only_binary})"
37
37
 
38
38
  @staticmethod
39
- def handle_mutual_excludes(value: str, target: Set[str], other: Set[str]) -> None:
39
+ def handle_mutual_excludes(value: str, target: set[str], other: set[str]) -> None:
40
40
  if value.startswith("-"):
41
41
  raise CommandError(
42
42
  "--no-binary / --only-binary option requires 1 argument."
@@ -58,7 +58,7 @@ class FormatControl:
58
58
  other.discard(name)
59
59
  target.add(name)
60
60
 
61
- def get_allowed_formats(self, canonical_name: str) -> FrozenSet[str]:
61
+ def get_allowed_formats(self, canonical_name: str) -> frozenset[str]:
62
62
  result = {"binary", "source"}
63
63
  if canonical_name in self.only_binary:
64
64
  result.discard("source")
@@ -1,4 +1,5 @@
1
- from typing import Any, Dict, Sequence
1
+ from collections.abc import Sequence
2
+ from typing import Any
2
3
 
3
4
  from pip._vendor.packaging.markers import default_environment
4
5
 
@@ -11,7 +12,7 @@ class InstallationReport:
11
12
  self._install_requirements = install_requirements
12
13
 
13
14
  @classmethod
14
- def _install_req_to_dict(cls, ireq: InstallRequirement) -> Dict[str, Any]:
15
+ def _install_req_to_dict(cls, ireq: InstallRequirement) -> dict[str, Any]:
15
16
  assert ireq.download_info, f"No download_info for {ireq}"
16
17
  res = {
17
18
  # PEP 610 json for the download URL. download_info.archive_info.hashes may
@@ -39,7 +40,7 @@ class InstallationReport:
39
40
  res["requested_extras"] = sorted(ireq.extras)
40
41
  return res
41
42
 
42
- def to_dict(self) -> Dict[str, Any]:
43
+ def to_dict(self) -> dict[str, Any]:
43
44
  return {
44
45
  "version": "1",
45
46
  "pip_version": __version__,
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  import functools
2
4
  import itertools
3
5
  import logging
@@ -5,17 +7,12 @@ import os
5
7
  import posixpath
6
8
  import re
7
9
  import urllib.parse
10
+ from collections.abc import Mapping
8
11
  from dataclasses import dataclass
9
12
  from typing import (
10
13
  TYPE_CHECKING,
11
14
  Any,
12
- Dict,
13
- List,
14
- Mapping,
15
15
  NamedTuple,
16
- Optional,
17
- Tuple,
18
- Union,
19
16
  )
20
17
 
21
18
  from pip._internal.utils.deprecation import deprecated
@@ -69,8 +66,8 @@ class LinkHash:
69
66
  assert self.name in _SUPPORTED_HASHES
70
67
 
71
68
  @classmethod
72
- @functools.lru_cache(maxsize=None)
73
- def find_hash_url_fragment(cls, url: str) -> Optional["LinkHash"]:
69
+ @functools.cache
70
+ def find_hash_url_fragment(cls, url: str) -> LinkHash | None:
74
71
  """Search a string for a checksum algorithm name and encoded output value."""
75
72
  match = cls._hash_url_fragment_re.search(url)
76
73
  if match is None:
@@ -78,14 +75,14 @@ class LinkHash:
78
75
  name, value = match.groups()
79
76
  return cls(name=name, value=value)
80
77
 
81
- def as_dict(self) -> Dict[str, str]:
78
+ def as_dict(self) -> dict[str, str]:
82
79
  return {self.name: self.value}
83
80
 
84
81
  def as_hashes(self) -> Hashes:
85
82
  """Return a Hashes instance which checks only for the current hash."""
86
83
  return Hashes({self.name: [self.value]})
87
84
 
88
- def is_hash_allowed(self, hashes: Optional[Hashes]) -> bool:
85
+ def is_hash_allowed(self, hashes: Hashes | None) -> bool:
89
86
  """
90
87
  Return True if the current hash is allowed by `hashes`.
91
88
  """
@@ -98,14 +95,14 @@ class LinkHash:
98
95
  class MetadataFile:
99
96
  """Information about a core metadata file associated with a distribution."""
100
97
 
101
- hashes: Optional[Dict[str, str]]
98
+ hashes: dict[str, str] | None
102
99
 
103
100
  def __post_init__(self) -> None:
104
101
  if self.hashes is not None:
105
102
  assert all(name in _SUPPORTED_HASHES for name in self.hashes)
106
103
 
107
104
 
108
- def supported_hashes(hashes: Optional[Dict[str, str]]) -> Optional[Dict[str, str]]:
105
+ def supported_hashes(hashes: dict[str, str] | None) -> dict[str, str] | None:
109
106
  # Remove any unsupported hash types from the mapping. If this leaves no
110
107
  # supported hashes, return None
111
108
  if hashes is None:
@@ -134,7 +131,11 @@ def _clean_file_url_path(part: str) -> str:
134
131
  # should not be quoted. On Linux where drive letters do not
135
132
  # exist, the colon should be quoted. We rely on urllib.request
136
133
  # to do the right thing here.
137
- return urllib.request.pathname2url(urllib.request.url2pathname(part))
134
+ ret = urllib.request.pathname2url(urllib.request.url2pathname(part))
135
+ if ret.startswith("///"):
136
+ # Remove any URL authority section, leaving only the URL path.
137
+ ret = ret.removeprefix("//")
138
+ return ret
138
139
 
139
140
 
140
141
  # percent-encoded: /
@@ -175,7 +176,11 @@ def _ensure_quoted_url(url: str) -> str:
175
176
  # If the netloc is empty, then the URL refers to a local filesystem path.
176
177
  is_local_path = not result.netloc
177
178
  path = _clean_url_path(result.path, is_local_path=is_local_path)
178
- return urllib.parse.urlunsplit(result._replace(path=path))
179
+ # Temporarily replace scheme with file to ensure the URL generated by
180
+ # urlunsplit() contains an empty netloc (file://) as per RFC 1738.
181
+ ret = urllib.parse.urlunsplit(result._replace(scheme="file", path=path))
182
+ ret = result.scheme + ret[4:] # Restore original scheme.
183
+ return ret
179
184
 
180
185
 
181
186
  def _absolute_link_url(base_url: str, url: str) -> str:
@@ -209,12 +214,12 @@ class Link:
209
214
  def __init__(
210
215
  self,
211
216
  url: str,
212
- comes_from: Optional[Union[str, "IndexContent"]] = None,
213
- requires_python: Optional[str] = None,
214
- yanked_reason: Optional[str] = None,
215
- metadata_file_data: Optional[MetadataFile] = None,
217
+ comes_from: str | IndexContent | None = None,
218
+ requires_python: str | None = None,
219
+ yanked_reason: str | None = None,
220
+ metadata_file_data: MetadataFile | None = None,
216
221
  cache_link_parsing: bool = True,
217
- hashes: Optional[Mapping[str, str]] = None,
222
+ hashes: Mapping[str, str] | None = None,
218
223
  ) -> None:
219
224
  """
220
225
  :param url: url of the resource pointed to (href of the link)
@@ -274,9 +279,9 @@ class Link:
274
279
  @classmethod
275
280
  def from_json(
276
281
  cls,
277
- file_data: Dict[str, Any],
282
+ file_data: dict[str, Any],
278
283
  page_url: str,
279
- ) -> Optional["Link"]:
284
+ ) -> Link | None:
280
285
  """
281
286
  Convert an pypi json document from a simple repository page into a Link.
282
287
  """
@@ -325,10 +330,10 @@ class Link:
325
330
  @classmethod
326
331
  def from_element(
327
332
  cls,
328
- anchor_attribs: Dict[str, Optional[str]],
333
+ anchor_attribs: dict[str, str | None],
329
334
  page_url: str,
330
335
  base_url: str,
331
- ) -> Optional["Link"]:
336
+ ) -> Link | None:
332
337
  """
333
338
  Convert an anchor element's attributes in a simple repository page to a Link.
334
339
  """
@@ -441,7 +446,7 @@ class Link:
441
446
  def path(self) -> str:
442
447
  return self._path
443
448
 
444
- def splitext(self) -> Tuple[str, str]:
449
+ def splitext(self) -> tuple[str, str]:
445
450
  return splitext(posixpath.basename(self.path.rstrip("/")))
446
451
 
447
452
  @property
@@ -460,7 +465,7 @@ class Link:
460
465
  r"^([A-Z0-9]|[A-Z0-9][A-Z0-9._-]*[A-Z0-9])$", re.IGNORECASE
461
466
  )
462
467
 
463
- def _egg_fragment(self) -> Optional[str]:
468
+ def _egg_fragment(self) -> str | None:
464
469
  match = self._egg_fragment_re.search(self._url)
465
470
  if not match:
466
471
  return None
@@ -472,7 +477,7 @@ class Link:
472
477
  deprecated(
473
478
  reason=f"{self} contains an egg fragment with a non-PEP 508 name.",
474
479
  replacement="to use the req @ url syntax, and remove the egg fragment",
475
- gone_in="25.2",
480
+ gone_in="26.0",
476
481
  issue=13157,
477
482
  )
478
483
 
@@ -481,13 +486,13 @@ class Link:
481
486
  _subdirectory_fragment_re = re.compile(r"[#&]subdirectory=([^&]*)")
482
487
 
483
488
  @property
484
- def subdirectory_fragment(self) -> Optional[str]:
489
+ def subdirectory_fragment(self) -> str | None:
485
490
  match = self._subdirectory_fragment_re.search(self._url)
486
491
  if not match:
487
492
  return None
488
493
  return match.group(1)
489
494
 
490
- def metadata_link(self) -> Optional["Link"]:
495
+ def metadata_link(self) -> Link | None:
491
496
  """Return a link to the associated core metadata file (if any)."""
492
497
  if self.metadata_file_data is None:
493
498
  return None
@@ -500,11 +505,11 @@ class Link:
500
505
  return Hashes({k: [v] for k, v in self._hashes.items()})
501
506
 
502
507
  @property
503
- def hash(self) -> Optional[str]:
508
+ def hash(self) -> str | None:
504
509
  return next(iter(self._hashes.values()), None)
505
510
 
506
511
  @property
507
- def hash_name(self) -> Optional[str]:
512
+ def hash_name(self) -> str | None:
508
513
  return next(iter(self._hashes), None)
509
514
 
510
515
  @property
@@ -536,7 +541,7 @@ class Link:
536
541
  def has_hash(self) -> bool:
537
542
  return bool(self._hashes)
538
543
 
539
- def is_hash_allowed(self, hashes: Optional[Hashes]) -> bool:
544
+ def is_hash_allowed(self, hashes: Hashes | None) -> bool:
540
545
  """
541
546
  Return True if the link has a hash and it is allowed by `hashes`.
542
547
  """
@@ -572,9 +577,9 @@ class _CleanResult(NamedTuple):
572
577
  """
573
578
 
574
579
  parsed: urllib.parse.SplitResult
575
- query: Dict[str, List[str]]
580
+ query: dict[str, list[str]]
576
581
  subdirectory: str
577
- hashes: Dict[str, str]
582
+ hashes: dict[str, str]
578
583
 
579
584
 
580
585
  def _clean_link(link: Link) -> _CleanResult:
@@ -603,6 +608,6 @@ def _clean_link(link: Link) -> _CleanResult:
603
608
  )
604
609
 
605
610
 
606
- @functools.lru_cache(maxsize=None)
611
+ @functools.cache
607
612
  def links_equivalent(link1: Link, link2: Link) -> bool:
608
613
  return _clean_link(link1) == _clean_link(link2)