pip 25.2__py3-none-any.whl → 26.0__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 (167) hide show
  1. pip/__init__.py +1 -1
  2. pip/_internal/__init__.py +0 -0
  3. pip/_internal/build_env.py +265 -8
  4. pip/_internal/cache.py +1 -1
  5. pip/_internal/cli/base_command.py +11 -0
  6. pip/_internal/cli/cmdoptions.py +200 -71
  7. pip/_internal/cli/index_command.py +20 -0
  8. pip/_internal/cli/main.py +11 -6
  9. pip/_internal/cli/main_parser.py +3 -1
  10. pip/_internal/cli/parser.py +96 -36
  11. pip/_internal/cli/progress_bars.py +4 -2
  12. pip/_internal/cli/req_command.py +126 -30
  13. pip/_internal/commands/cache.py +24 -0
  14. pip/_internal/commands/completion.py +2 -1
  15. pip/_internal/commands/download.py +12 -11
  16. pip/_internal/commands/index.py +13 -6
  17. pip/_internal/commands/install.py +55 -43
  18. pip/_internal/commands/list.py +14 -16
  19. pip/_internal/commands/lock.py +19 -14
  20. pip/_internal/commands/wheel.py +13 -23
  21. pip/_internal/configuration.py +1 -2
  22. pip/_internal/distributions/sdist.py +13 -14
  23. pip/_internal/exceptions.py +96 -6
  24. pip/_internal/index/collector.py +2 -3
  25. pip/_internal/index/package_finder.py +87 -21
  26. pip/_internal/locations/__init__.py +1 -2
  27. pip/_internal/locations/_sysconfig.py +4 -1
  28. pip/_internal/metadata/__init__.py +7 -2
  29. pip/_internal/metadata/importlib/_dists.py +8 -2
  30. pip/_internal/models/link.py +18 -14
  31. pip/_internal/models/release_control.py +92 -0
  32. pip/_internal/models/selection_prefs.py +6 -3
  33. pip/_internal/models/wheel.py +5 -66
  34. pip/_internal/network/auth.py +6 -2
  35. pip/_internal/network/cache.py +6 -11
  36. pip/_internal/network/download.py +4 -5
  37. pip/_internal/network/lazy_wheel.py +5 -3
  38. pip/_internal/network/session.py +14 -10
  39. pip/_internal/operations/build/wheel.py +4 -4
  40. pip/_internal/operations/build/wheel_editable.py +4 -4
  41. pip/_internal/operations/install/wheel.py +1 -2
  42. pip/_internal/operations/prepare.py +9 -4
  43. pip/_internal/pyproject.py +2 -61
  44. pip/_internal/req/__init__.py +1 -3
  45. pip/_internal/req/constructors.py +45 -39
  46. pip/_internal/req/pep723.py +41 -0
  47. pip/_internal/req/req_file.py +10 -2
  48. pip/_internal/req/req_install.py +32 -141
  49. pip/_internal/resolution/resolvelib/candidates.py +20 -11
  50. pip/_internal/resolution/resolvelib/factory.py +43 -1
  51. pip/_internal/resolution/resolvelib/provider.py +9 -0
  52. pip/_internal/resolution/resolvelib/reporter.py +21 -8
  53. pip/_internal/resolution/resolvelib/requirements.py +7 -3
  54. pip/_internal/resolution/resolvelib/resolver.py +2 -6
  55. pip/_internal/self_outdated_check.py +17 -16
  56. pip/_internal/utils/datetime.py +18 -0
  57. pip/_internal/utils/filesystem.py +52 -1
  58. pip/_internal/utils/logging.py +34 -2
  59. pip/_internal/utils/misc.py +18 -12
  60. pip/_internal/utils/pylock.py +116 -0
  61. pip/_internal/utils/unpacking.py +26 -1
  62. pip/_internal/vcs/versioncontrol.py +3 -1
  63. pip/_internal/wheel_builder.py +23 -96
  64. pip/_vendor/README.rst +180 -0
  65. pip/_vendor/cachecontrol/LICENSE.txt +13 -0
  66. pip/_vendor/cachecontrol/__init__.py +6 -3
  67. pip/_vendor/cachecontrol/adapter.py +0 -1
  68. pip/_vendor/cachecontrol/controller.py +1 -1
  69. pip/_vendor/cachecontrol/filewrapper.py +3 -1
  70. pip/_vendor/certifi/LICENSE +20 -0
  71. pip/_vendor/certifi/__init__.py +1 -1
  72. pip/_vendor/certifi/cacert.pem +62 -372
  73. pip/_vendor/dependency_groups/LICENSE.txt +9 -0
  74. pip/_vendor/distlib/LICENSE.txt +284 -0
  75. pip/_vendor/distro/LICENSE +202 -0
  76. pip/_vendor/idna/LICENSE.md +31 -0
  77. pip/_vendor/idna/codec.py +1 -1
  78. pip/_vendor/idna/core.py +1 -1
  79. pip/_vendor/idna/idnadata.py +72 -6
  80. pip/_vendor/idna/package_data.py +1 -1
  81. pip/_vendor/idna/uts46data.py +891 -731
  82. pip/_vendor/msgpack/COPYING +14 -0
  83. pip/_vendor/msgpack/__init__.py +2 -2
  84. pip/_vendor/packaging/LICENSE +3 -0
  85. pip/_vendor/packaging/LICENSE.APACHE +177 -0
  86. pip/_vendor/packaging/LICENSE.BSD +23 -0
  87. pip/_vendor/packaging/__init__.py +1 -1
  88. pip/_vendor/packaging/_elffile.py +0 -1
  89. pip/_vendor/packaging/_manylinux.py +36 -36
  90. pip/_vendor/packaging/_musllinux.py +1 -1
  91. pip/_vendor/packaging/_parser.py +22 -10
  92. pip/_vendor/packaging/_structures.py +8 -0
  93. pip/_vendor/packaging/_tokenizer.py +23 -25
  94. pip/_vendor/packaging/licenses/__init__.py +13 -11
  95. pip/_vendor/packaging/licenses/_spdx.py +41 -1
  96. pip/_vendor/packaging/markers.py +64 -38
  97. pip/_vendor/packaging/metadata.py +143 -27
  98. pip/_vendor/packaging/pylock.py +635 -0
  99. pip/_vendor/packaging/requirements.py +5 -10
  100. pip/_vendor/packaging/specifiers.py +219 -170
  101. pip/_vendor/packaging/tags.py +15 -20
  102. pip/_vendor/packaging/utils.py +19 -24
  103. pip/_vendor/packaging/version.py +315 -105
  104. pip/_vendor/pkg_resources/LICENSE +17 -0
  105. pip/_vendor/platformdirs/LICENSE +21 -0
  106. pip/_vendor/platformdirs/api.py +1 -1
  107. pip/_vendor/platformdirs/macos.py +10 -8
  108. pip/_vendor/platformdirs/version.py +16 -3
  109. pip/_vendor/platformdirs/windows.py +7 -1
  110. pip/_vendor/pygments/LICENSE +25 -0
  111. pip/_vendor/pyproject_hooks/LICENSE +21 -0
  112. pip/_vendor/requests/LICENSE +175 -0
  113. pip/_vendor/requests/__version__.py +2 -2
  114. pip/_vendor/requests/adapters.py +17 -40
  115. pip/_vendor/requests/sessions.py +1 -1
  116. pip/_vendor/resolvelib/LICENSE +13 -0
  117. pip/_vendor/resolvelib/__init__.py +1 -1
  118. pip/_vendor/resolvelib/resolvers/abstract.py +3 -3
  119. pip/_vendor/resolvelib/resolvers/resolution.py +5 -0
  120. pip/_vendor/rich/LICENSE +19 -0
  121. pip/_vendor/rich/style.py +7 -11
  122. pip/_vendor/tomli/LICENSE +21 -0
  123. pip/_vendor/tomli/__init__.py +1 -1
  124. pip/_vendor/tomli/_parser.py +28 -21
  125. pip/_vendor/tomli/_re.py +8 -5
  126. pip/_vendor/tomli_w/LICENSE +21 -0
  127. pip/_vendor/truststore/LICENSE +21 -0
  128. pip/_vendor/truststore/__init__.py +1 -1
  129. pip/_vendor/truststore/_api.py +14 -6
  130. pip/_vendor/truststore/_openssl.py +3 -1
  131. pip/_vendor/urllib3/LICENSE.txt +21 -0
  132. pip/_vendor/vendor.txt +11 -11
  133. {pip-25.2.dist-info → pip-26.0.dist-info}/METADATA +10 -11
  134. {pip-25.2.dist-info → pip-26.0.dist-info}/RECORD +158 -139
  135. {pip-25.2.dist-info → pip-26.0.dist-info}/WHEEL +1 -2
  136. pip-26.0.dist-info/entry_points.txt +4 -0
  137. {pip-25.2.dist-info → pip-26.0.dist-info}/licenses/AUTHORS.txt +27 -0
  138. {pip-25.2.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/idna/LICENSE.md +1 -1
  139. pip/_internal/models/pylock.py +0 -188
  140. pip/_internal/operations/build/metadata_legacy.py +0 -73
  141. pip/_internal/operations/build/wheel_legacy.py +0 -119
  142. pip/_internal/operations/install/editable_legacy.py +0 -48
  143. pip/_internal/utils/setuptools_build.py +0 -149
  144. pip-25.2.dist-info/entry_points.txt +0 -3
  145. pip-25.2.dist-info/licenses/src/pip/_vendor/tomli/LICENSE-HEADER +0 -3
  146. pip-25.2.dist-info/top_level.txt +0 -1
  147. {pip-25.2.dist-info → pip-26.0.dist-info}/licenses/LICENSE.txt +0 -0
  148. {pip-25.2.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/cachecontrol/LICENSE.txt +0 -0
  149. {pip-25.2.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/certifi/LICENSE +0 -0
  150. {pip-25.2.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/dependency_groups/LICENSE.txt +0 -0
  151. {pip-25.2.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/distlib/LICENSE.txt +0 -0
  152. {pip-25.2.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/distro/LICENSE +0 -0
  153. {pip-25.2.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/msgpack/COPYING +0 -0
  154. {pip-25.2.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/packaging/LICENSE +0 -0
  155. {pip-25.2.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/packaging/LICENSE.APACHE +0 -0
  156. {pip-25.2.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/packaging/LICENSE.BSD +0 -0
  157. {pip-25.2.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/pkg_resources/LICENSE +0 -0
  158. {pip-25.2.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/platformdirs/LICENSE +0 -0
  159. {pip-25.2.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/pygments/LICENSE +0 -0
  160. {pip-25.2.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/pyproject_hooks/LICENSE +0 -0
  161. {pip-25.2.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/requests/LICENSE +0 -0
  162. {pip-25.2.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/resolvelib/LICENSE +0 -0
  163. {pip-25.2.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/rich/LICENSE +0 -0
  164. {pip-25.2.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/tomli/LICENSE +0 -0
  165. {pip-25.2.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/tomli_w/LICENSE +0 -0
  166. {pip-25.2.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/truststore/LICENSE +0 -0
  167. {pip-25.2.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/urllib3/LICENSE.txt +0 -0
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import datetime
3
4
  import functools
4
5
  import itertools
5
6
  import logging
@@ -7,15 +8,16 @@ import os
7
8
  import posixpath
8
9
  import re
9
10
  import urllib.parse
11
+ import urllib.request
10
12
  from collections.abc import Mapping
11
13
  from dataclasses import dataclass
12
14
  from typing import (
13
- TYPE_CHECKING,
14
15
  Any,
15
16
  NamedTuple,
16
17
  )
17
18
 
18
- from pip._internal.utils.deprecation import deprecated
19
+ from pip._internal.exceptions import InvalidEggFragment
20
+ from pip._internal.utils.datetime import parse_iso_datetime
19
21
  from pip._internal.utils.filetypes import WHEEL_EXTENSION
20
22
  from pip._internal.utils.hashes import Hashes
21
23
  from pip._internal.utils.misc import (
@@ -26,9 +28,6 @@ from pip._internal.utils.misc import (
26
28
  )
27
29
  from pip._internal.utils.urls import path_to_url, url_to_path
28
30
 
29
- if TYPE_CHECKING:
30
- from pip._internal.index.collector import IndexContent
31
-
32
31
  logger = logging.getLogger(__name__)
33
32
 
34
33
 
@@ -207,6 +206,7 @@ class Link:
207
206
  "requires_python",
208
207
  "yanked_reason",
209
208
  "metadata_file_data",
209
+ "upload_time",
210
210
  "cache_link_parsing",
211
211
  "egg_fragment",
212
212
  ]
@@ -214,17 +214,17 @@ class Link:
214
214
  def __init__(
215
215
  self,
216
216
  url: str,
217
- comes_from: str | IndexContent | None = None,
217
+ comes_from: str | None = None,
218
218
  requires_python: str | None = None,
219
219
  yanked_reason: str | None = None,
220
220
  metadata_file_data: MetadataFile | None = None,
221
+ upload_time: datetime.datetime | None = None,
221
222
  cache_link_parsing: bool = True,
222
223
  hashes: Mapping[str, str] | None = None,
223
224
  ) -> None:
224
225
  """
225
226
  :param url: url of the resource pointed to (href of the link)
226
- :param comes_from: instance of IndexContent where the link was found,
227
- or string.
227
+ :param comes_from: URL or string indicating where the link was found.
228
228
  :param requires_python: String containing the `Requires-Python`
229
229
  metadata field, specified in PEP 345. This may be specified by
230
230
  a data-requires-python attribute in the HTML link tag, as
@@ -239,6 +239,8 @@ class Link:
239
239
  no such metadata is provided. This argument, if not None, indicates
240
240
  that a separate metadata file exists, and also optionally supplies
241
241
  hashes for that file.
242
+ :param upload_time: upload time of the file, or None if the information
243
+ is not available from the server.
242
244
  :param cache_link_parsing: A flag that is used elsewhere to determine
243
245
  whether resources retrieved from this link should be cached. PyPI
244
246
  URLs should generally have this set to False, for example.
@@ -272,6 +274,7 @@ class Link:
272
274
  self.requires_python = requires_python if requires_python else None
273
275
  self.yanked_reason = yanked_reason
274
276
  self.metadata_file_data = metadata_file_data
277
+ self.upload_time = upload_time
275
278
 
276
279
  self.cache_link_parsing = cache_link_parsing
277
280
  self.egg_fragment = self._egg_fragment()
@@ -300,6 +303,11 @@ class Link:
300
303
  if metadata_info is None:
301
304
  metadata_info = file_data.get("dist-info-metadata")
302
305
 
306
+ if upload_time_data := file_data.get("upload-time"):
307
+ upload_time = parse_iso_datetime(upload_time_data)
308
+ else:
309
+ upload_time = None
310
+
303
311
  # The metadata info value may be a boolean, or a dict of hashes.
304
312
  if isinstance(metadata_info, dict):
305
313
  # The file exists, and hashes have been supplied
@@ -325,6 +333,7 @@ class Link:
325
333
  yanked_reason=yanked_reason,
326
334
  hashes=hashes,
327
335
  metadata_file_data=metadata_file_data,
336
+ upload_time=upload_time,
328
337
  )
329
338
 
330
339
  @classmethod
@@ -474,12 +483,7 @@ class Link:
474
483
  # an optional extras specifier. Anything else is invalid.
475
484
  project_name = match.group(1)
476
485
  if not self._project_name_re.match(project_name):
477
- deprecated(
478
- reason=f"{self} contains an egg fragment with a non-PEP 508 name.",
479
- replacement="to use the req @ url syntax, and remove the egg fragment",
480
- gone_in="25.3",
481
- issue=13157,
482
- )
486
+ raise InvalidEggFragment(self, project_name)
483
487
 
484
488
  return project_name
485
489
 
@@ -0,0 +1,92 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, field
4
+
5
+ from pip._vendor.packaging.utils import NormalizedName, canonicalize_name
6
+
7
+ from pip._internal.exceptions import CommandError
8
+
9
+
10
+ # TODO: add slots=True when Python 3.9 is dropped
11
+ @dataclass
12
+ class ReleaseControl:
13
+ """Helper for managing which release types can be installed."""
14
+
15
+ all_releases: set[str] = field(default_factory=set)
16
+ only_final: set[str] = field(default_factory=set)
17
+ _order: list[tuple[str, str]] = field(
18
+ init=False, default_factory=list, compare=False, repr=False
19
+ )
20
+
21
+ def handle_mutual_excludes(
22
+ self, value: str, target: set[str], other: set[str], attr_name: str
23
+ ) -> None:
24
+ """Parse and apply release control option value.
25
+
26
+ Processes comma-separated package names or special values `:all:` and `:none:`.
27
+
28
+ When adding packages to target, they're removed from other to maintain mutual
29
+ exclusivity between all_releases and only_final. All operations are tracked in
30
+ order so that the original command-line argument sequence can be reconstructed
31
+ when passing options to build subprocesses.
32
+ """
33
+ if value.startswith("-"):
34
+ raise CommandError(
35
+ "--all-releases / --only-final option requires 1 argument."
36
+ )
37
+ new = value.split(",")
38
+ while ":all:" in new:
39
+ other.clear()
40
+ target.clear()
41
+ target.add(":all:")
42
+ # Track :all: in order
43
+ self._order.append((attr_name, ":all:"))
44
+ del new[: new.index(":all:") + 1]
45
+ # Without a none, we want to discard everything as :all: covers it
46
+ if ":none:" not in new:
47
+ return
48
+ for name in new:
49
+ if name == ":none:":
50
+ target.clear()
51
+ # Track :none: in order
52
+ self._order.append((attr_name, ":none:"))
53
+ continue
54
+ name = canonicalize_name(name)
55
+ other.discard(name)
56
+ target.add(name)
57
+ # Track package-specific setting in order
58
+ self._order.append((attr_name, name))
59
+
60
+ def get_ordered_args(self) -> list[tuple[str, str]]:
61
+ """
62
+ Get ordered list of (flag_name, value) tuples for reconstructing CLI args.
63
+
64
+ Returns:
65
+ List of tuples where each tuple is (attribute_name, value).
66
+ The attribute_name is either 'all_releases' or 'only_final'.
67
+
68
+ Example:
69
+ [("all_releases", ":all:"), ("only_final", "simple")]
70
+ would be reconstructed as:
71
+ ["--all-releases", ":all:", "--only-final", "simple"]
72
+ """
73
+ return self._order[:]
74
+
75
+ def allows_prereleases(self, canonical_name: NormalizedName) -> bool | None:
76
+ """
77
+ Determine if pre-releases are allowed for a package.
78
+
79
+ Returns:
80
+ True: Pre-releases are allowed (package in all_releases)
81
+ False: Only final releases allowed (package in only_final)
82
+ None: No specific setting, use default behavior
83
+ """
84
+ if canonical_name in self.all_releases:
85
+ return True
86
+ elif canonical_name in self.only_final:
87
+ return False
88
+ elif ":all:" in self.all_releases:
89
+ return True
90
+ elif ":all:" in self.only_final:
91
+ return False
92
+ return None
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from pip._internal.models.format_control import FormatControl
4
+ from pip._internal.models.release_control import ReleaseControl
4
5
 
5
6
 
6
7
  # TODO: This needs Python 3.10's improved slots support for dataclasses
@@ -13,7 +14,7 @@ class SelectionPreferences:
13
14
 
14
15
  __slots__ = [
15
16
  "allow_yanked",
16
- "allow_all_prereleases",
17
+ "release_control",
17
18
  "format_control",
18
19
  "prefer_binary",
19
20
  "ignore_requires_python",
@@ -26,7 +27,7 @@ class SelectionPreferences:
26
27
  def __init__(
27
28
  self,
28
29
  allow_yanked: bool,
29
- allow_all_prereleases: bool = False,
30
+ release_control: ReleaseControl | None = None,
30
31
  format_control: FormatControl | None = None,
31
32
  prefer_binary: bool = False,
32
33
  ignore_requires_python: bool | None = None,
@@ -35,6 +36,8 @@ class SelectionPreferences:
35
36
 
36
37
  :param allow_yanked: Whether files marked as yanked (in the sense
37
38
  of PEP 592) are permitted to be candidates for install.
39
+ :param release_control: A ReleaseControl object or None. Used to control
40
+ whether pre-releases are allowed for specific packages.
38
41
  :param format_control: A FormatControl object or None. Used to control
39
42
  the selection of source packages / binary packages when consulting
40
43
  the index and links.
@@ -47,7 +50,7 @@ class SelectionPreferences:
47
50
  ignore_requires_python = False
48
51
 
49
52
  self.allow_yanked = allow_yanked
50
- self.allow_all_prereleases = allow_all_prereleases
53
+ self.release_control = release_control
51
54
  self.format_control = format_control
52
55
  self.prefer_binary = prefer_binary
53
56
  self.ignore_requires_python = ignore_requires_python
@@ -4,91 +4,30 @@ name that have meaning.
4
4
 
5
5
  from __future__ import annotations
6
6
 
7
- import re
8
7
  from collections.abc import Iterable
9
8
 
10
9
  from pip._vendor.packaging.tags import Tag
11
- from pip._vendor.packaging.utils import BuildTag, parse_wheel_filename
12
10
  from pip._vendor.packaging.utils import (
13
11
  InvalidWheelFilename as _PackagingInvalidWheelFilename,
14
12
  )
13
+ from pip._vendor.packaging.utils import parse_wheel_filename
15
14
 
16
15
  from pip._internal.exceptions import InvalidWheelFilename
17
- from pip._internal.utils.deprecation import deprecated
18
16
 
19
17
 
20
18
  class Wheel:
21
19
  """A wheel file"""
22
20
 
23
- legacy_wheel_file_re = re.compile(
24
- r"""^(?P<namever>(?P<name>[^\s-]+?)-(?P<ver>[^\s-]*?))
25
- ((-(?P<build>\d[^-]*?))?-(?P<pyver>[^\s-]+?)-(?P<abi>[^\s-]+?)-(?P<plat>[^\s-]+?)
26
- \.whl|\.dist-info)$""",
27
- re.VERBOSE,
28
- )
29
-
30
21
  def __init__(self, filename: str) -> None:
31
22
  self.filename = filename
32
23
 
33
- # To make mypy happy specify type hints that can come from either
34
- # parse_wheel_filename or the legacy_wheel_file_re match.
35
- self.name: str
36
- self._build_tag: BuildTag | None = None
37
-
38
24
  try:
39
25
  wheel_info = parse_wheel_filename(filename)
40
- self.name, _version, self._build_tag, self.file_tags = wheel_info
41
- self.version = str(_version)
42
26
  except _PackagingInvalidWheelFilename as e:
43
- # Check if the wheel filename is in the legacy format
44
- legacy_wheel_info = self.legacy_wheel_file_re.match(filename)
45
- if not legacy_wheel_info:
46
- raise InvalidWheelFilename(e.args[0]) from None
47
-
48
- deprecated(
49
- reason=(
50
- f"Wheel filename {filename!r} is not correctly normalised. "
51
- "Future versions of pip will raise the following error:\n"
52
- f"{e.args[0]}\n\n"
53
- ),
54
- replacement=(
55
- "to rename the wheel to use a correctly normalised "
56
- "name (this may require updating the version in "
57
- "the project metadata)"
58
- ),
59
- gone_in="25.3",
60
- issue=12938,
61
- )
62
-
63
- self.name = legacy_wheel_info.group("name").replace("_", "-")
64
- self.version = legacy_wheel_info.group("ver").replace("_", "-")
65
-
66
- # Generate the file tags from the legacy wheel filename
67
- pyversions = legacy_wheel_info.group("pyver").split(".")
68
- abis = legacy_wheel_info.group("abi").split(".")
69
- plats = legacy_wheel_info.group("plat").split(".")
70
- self.file_tags = frozenset(
71
- Tag(interpreter=py, abi=abi, platform=plat)
72
- for py in pyversions
73
- for abi in abis
74
- for plat in plats
75
- )
76
-
77
- @property
78
- def build_tag(self) -> BuildTag:
79
- if self._build_tag is not None:
80
- return self._build_tag
81
-
82
- # Parse the build tag from the legacy wheel filename
83
- legacy_wheel_info = self.legacy_wheel_file_re.match(self.filename)
84
- assert legacy_wheel_info is not None, "guaranteed by filename validation"
85
- build_tag = legacy_wheel_info.group("build")
86
- match = re.match(r"^(\d+)(.*)$", build_tag)
87
- assert match is not None, "guaranteed by filename validation"
88
- build_tag_groups = match.groups()
89
- self._build_tag = (int(build_tag_groups[0]), build_tag_groups[1])
90
-
91
- return self._build_tag
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)
92
31
 
93
32
  def get_formatted_file_tags(self) -> list[str]:
94
33
  """Return the wheel's tags as a sorted list of strings."""
@@ -20,7 +20,6 @@ from pathlib import Path
20
20
  from typing import Any, NamedTuple
21
21
 
22
22
  from pip._vendor.requests.auth import AuthBase, HTTPBasicAuth
23
- from pip._vendor.requests.models import Request, Response
24
23
  from pip._vendor.requests.utils import get_netrc_auth
25
24
 
26
25
  from pip._internal.utils.logging import getLogger
@@ -33,6 +32,10 @@ from pip._internal.utils.misc import (
33
32
  )
34
33
  from pip._internal.vcs.versioncontrol import AuthInfo
35
34
 
35
+ if typing.TYPE_CHECKING:
36
+ from pip._vendor.requests import PreparedRequest
37
+ from pip._vendor.requests.models import Response
38
+
36
39
  logger = getLogger(__name__)
37
40
 
38
41
  KEYRING_DISABLED = False
@@ -437,8 +440,9 @@ class MultiDomainBasicAuth(AuthBase):
437
440
 
438
441
  return url, username, password
439
442
 
440
- def __call__(self, req: Request) -> Request:
443
+ def __call__(self, req: PreparedRequest) -> PreparedRequest:
441
444
  # Get credentials for this request
445
+ assert req.url is not None
442
446
  url, username, password = self._get_url_and_credentials(req.url)
443
447
 
444
448
  # Set the url of the request to the url without any credentials
@@ -13,7 +13,11 @@ from pip._vendor.cachecontrol.cache import SeparateBodyBaseCache
13
13
  from pip._vendor.cachecontrol.caches import SeparateBodyFileCache
14
14
  from pip._vendor.requests.models import Response
15
15
 
16
- 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
+ )
17
21
  from pip._internal.utils.misc import ensure_dir
18
22
 
19
23
 
@@ -82,16 +86,7 @@ class SafeFileCache(SeparateBodyBaseCache):
82
86
  writer_func(f)
83
87
  # Inherit the read/write permissions of the cache directory
84
88
  # to enable multi-user cache use-cases.
85
- mode = (
86
- os.stat(self.directory).st_mode
87
- & 0o666 # select read/write permissions of cache directory
88
- | 0o600 # set owner read/write permissions
89
- )
90
- # Change permissions only if there is no risk of following a symlink.
91
- if os.chmod in os.supports_fd:
92
- os.chmod(f.fileno(), mode)
93
- elif os.chmod in os.supports_follow_symlinks:
94
- os.chmod(f.name, mode, follow_symlinks=False)
89
+ copy_directory_permissions(self.directory, f)
95
90
 
96
91
  replace(f.name, path)
97
92
 
@@ -167,14 +167,13 @@ class Downloader:
167
167
  self,
168
168
  session: PipSession,
169
169
  progress_bar: BarType,
170
- resume_retries: int,
171
170
  ) -> None:
172
- assert (
173
- resume_retries >= 0
174
- ), "Number of max resume retries must be bigger or equal to zero"
175
171
  self._session = session
176
172
  self._progress_bar = progress_bar
177
- self._resume_retries = resume_retries
173
+ self._resume_retries = session.resume_retries
174
+ assert (
175
+ self._resume_retries >= 0
176
+ ), "Number of max resume retries must be bigger or equal to zero"
178
177
 
179
178
  def batch(
180
179
  self, links: Iterable[Link], location: str
@@ -11,7 +11,7 @@ from tempfile import NamedTemporaryFile
11
11
  from typing import Any
12
12
  from zipfile import BadZipFile, ZipFile
13
13
 
14
- from pip._vendor.packaging.utils import canonicalize_name
14
+ from pip._vendor.packaging.utils import NormalizedName
15
15
  from pip._vendor.requests.models import CONTENT_CHUNK_SIZE, Response
16
16
 
17
17
  from pip._internal.metadata import BaseDistribution, MemoryWheel, get_wheel_distribution
@@ -23,7 +23,9 @@ class HTTPRangeRequestUnsupported(Exception):
23
23
  pass
24
24
 
25
25
 
26
- def dist_from_wheel_url(name: str, url: str, session: PipSession) -> BaseDistribution:
26
+ def dist_from_wheel_url(
27
+ name: NormalizedName, url: str, session: PipSession
28
+ ) -> BaseDistribution:
27
29
  """Return a distribution object from the given wheel URL.
28
30
 
29
31
  This uses HTTP range requests to only fetch the portion of the wheel
@@ -37,7 +39,7 @@ def dist_from_wheel_url(name: str, url: str, session: PipSession) -> BaseDistrib
37
39
  wheel = MemoryWheel(zf.name, zf) # type: ignore
38
40
  # After context manager exit, wheel.name
39
41
  # is an invalid file by intention.
40
- return get_wheel_distribution(wheel, canonicalize_name(name))
42
+ return get_wheel_distribution(wheel, name)
41
43
 
42
44
 
43
45
  class LazyZipOverHTTP:
@@ -50,8 +50,8 @@ from pip._internal.utils.urls import url_to_path
50
50
  if TYPE_CHECKING:
51
51
  from ssl import SSLContext
52
52
 
53
+ from pip._vendor.urllib3 import ProxyManager
53
54
  from pip._vendor.urllib3.poolmanager import PoolManager
54
- from pip._vendor.urllib3.proxymanager import ProxyManager
55
55
 
56
56
 
57
57
  logger = logging.getLogger(__name__)
@@ -212,11 +212,12 @@ class LocalFSAdapter(BaseAdapter):
212
212
  self,
213
213
  request: PreparedRequest,
214
214
  stream: bool = False,
215
- timeout: float | tuple[float, float] | None = None,
215
+ timeout: float | tuple[float, float] | tuple[float, None] | None = None,
216
216
  verify: bool | str = True,
217
- cert: str | tuple[str, str] | None = None,
217
+ cert: bytes | str | tuple[bytes | str, bytes | str] | None = None,
218
218
  proxies: Mapping[str, str] | None = None,
219
219
  ) -> Response:
220
+ assert request.url is not None
220
221
  pathname = url_to_path(request.url)
221
222
 
222
223
  resp = Response()
@@ -237,13 +238,13 @@ class LocalFSAdapter(BaseAdapter):
237
238
  resp.headers = CaseInsensitiveDict(
238
239
  {
239
240
  "Content-Type": content_type,
240
- "Content-Length": stats.st_size,
241
+ "Content-Length": str(stats.st_size),
241
242
  "Last-Modified": modified,
242
243
  }
243
244
  )
244
245
 
245
246
  resp.raw = open(pathname, "rb")
246
- resp.close = resp.raw.close
247
+ resp.close = resp.raw.close # type: ignore[method-assign]
247
248
 
248
249
  return resp
249
250
 
@@ -277,7 +278,7 @@ class _SSLContextAdapterMixin:
277
278
  ) -> PoolManager:
278
279
  if self._ssl_context is not None:
279
280
  pool_kwargs.setdefault("ssl_context", self._ssl_context)
280
- return super().init_poolmanager( # type: ignore[misc]
281
+ return super().init_poolmanager( # type: ignore[misc, no-any-return]
281
282
  connections=connections,
282
283
  maxsize=maxsize,
283
284
  block=block,
@@ -289,7 +290,7 @@ class _SSLContextAdapterMixin:
289
290
  # context here too. https://github.com/pypa/pip/issues/13288
290
291
  if self._ssl_context is not None:
291
292
  proxy_kwargs.setdefault("ssl_context", self._ssl_context)
292
- return super().proxy_manager_for(proxy, **proxy_kwargs) # type: ignore[misc]
293
+ return super().proxy_manager_for(proxy, **proxy_kwargs) # type: ignore[misc, no-any-return]
293
294
 
294
295
 
295
296
  class HTTPAdapter(_SSLContextAdapterMixin, _BaseHTTPAdapter):
@@ -329,6 +330,7 @@ class PipSession(requests.Session):
329
330
  self,
330
331
  *args: Any,
331
332
  retries: int = 0,
333
+ resume_retries: int = 0,
332
334
  cache: str | None = None,
333
335
  trusted_hosts: Sequence[str] = (),
334
336
  index_urls: list[str] | None = None,
@@ -350,7 +352,7 @@ class PipSession(requests.Session):
350
352
  self.headers["User-Agent"] = user_agent()
351
353
 
352
354
  # Attach our Authentication handler to the session
353
- self.auth = MultiDomainBasicAuth(index_urls=index_urls)
355
+ self.auth: MultiDomainBasicAuth = MultiDomainBasicAuth(index_urls=index_urls)
354
356
 
355
357
  # Create our urllib3.Retry instance which will allow us to customize
356
358
  # how we handle retries.
@@ -370,6 +372,7 @@ class PipSession(requests.Session):
370
372
  # order to prevent hammering the service.
371
373
  backoff_factor=0.25,
372
374
  ) # type: ignore
375
+ self.resume_retries = resume_retries
373
376
 
374
377
  # Our Insecure HTTPAdapter disables HTTPS validation. It does not
375
378
  # support caching so we'll use it for all http:// URLs.
@@ -383,8 +386,9 @@ class PipSession(requests.Session):
383
386
  # we can't validate the response of an insecurely/untrusted fetched
384
387
  # origin, and we don't want someone to be able to poison the cache and
385
388
  # require manual eviction from the cache to fix it.
389
+ self._trusted_host_adapter: InsecureCacheControlAdapter | InsecureHTTPAdapter
386
390
  if cache:
387
- secure_adapter = CacheControlAdapter(
391
+ secure_adapter: _BaseHTTPAdapter = CacheControlAdapter(
388
392
  cache=SafeFileCache(cache),
389
393
  max_retries=retries,
390
394
  ssl_context=ssl_context,
@@ -518,7 +522,7 @@ class PipSession(requests.Session):
518
522
 
519
523
  return False
520
524
 
521
- def request(self, method: str, url: str, *args: Any, **kwargs: Any) -> Response:
525
+ def request(self, method: str, url: str, *args: Any, **kwargs: Any) -> Response: # type: ignore[override]
522
526
  # Allow setting a default timeout on a session
523
527
  kwargs.setdefault("timeout", self.timeout)
524
528
  # Allow setting a default proxies on a session
@@ -14,7 +14,7 @@ def build_wheel_pep517(
14
14
  name: str,
15
15
  backend: BuildBackendHookCaller,
16
16
  metadata_directory: str,
17
- tempd: str,
17
+ wheel_directory: str,
18
18
  ) -> str | None:
19
19
  """Build one InstallRequirement using the PEP 517 build process.
20
20
 
@@ -22,17 +22,17 @@ def build_wheel_pep517(
22
22
  """
23
23
  assert metadata_directory is not None
24
24
  try:
25
- logger.debug("Destination directory: %s", tempd)
25
+ logger.debug("Destination directory: %s", wheel_directory)
26
26
 
27
27
  runner = runner_with_spinner_message(
28
28
  f"Building wheel for {name} (pyproject.toml)"
29
29
  )
30
30
  with backend.subprocess_runner(runner):
31
31
  wheel_name = backend.build_wheel(
32
- tempd,
32
+ wheel_directory=wheel_directory,
33
33
  metadata_directory=metadata_directory,
34
34
  )
35
35
  except Exception:
36
36
  logger.error("Failed building wheel for %s", name)
37
37
  return None
38
- return os.path.join(tempd, wheel_name)
38
+ return os.path.join(wheel_directory, wheel_name)
@@ -14,7 +14,7 @@ def build_wheel_editable(
14
14
  name: str,
15
15
  backend: BuildBackendHookCaller,
16
16
  metadata_directory: str,
17
- tempd: str,
17
+ wheel_directory: str,
18
18
  ) -> str | None:
19
19
  """Build one InstallRequirement using the PEP 660 build process.
20
20
 
@@ -22,7 +22,7 @@ def build_wheel_editable(
22
22
  """
23
23
  assert metadata_directory is not None
24
24
  try:
25
- logger.debug("Destination directory: %s", tempd)
25
+ logger.debug("Destination directory: %s", wheel_directory)
26
26
 
27
27
  runner = runner_with_spinner_message(
28
28
  f"Building editable for {name} (pyproject.toml)"
@@ -30,7 +30,7 @@ def build_wheel_editable(
30
30
  with backend.subprocess_runner(runner):
31
31
  try:
32
32
  wheel_name = backend.build_editable(
33
- tempd,
33
+ wheel_directory=wheel_directory,
34
34
  metadata_directory=metadata_directory,
35
35
  )
36
36
  except HookMissing as e:
@@ -44,4 +44,4 @@ def build_wheel_editable(
44
44
  except Exception:
45
45
  logger.error("Failed building editable for %s", name)
46
46
  return None
47
- return os.path.join(tempd, wheel_name)
47
+ return os.path.join(wheel_directory, wheel_name)
@@ -411,8 +411,7 @@ class PipScriptMaker(ScriptMaker):
411
411
  import sys
412
412
  from %(module)s import %(import_name)s
413
413
  if __name__ == '__main__':
414
- if sys.argv[0].endswith('.exe'):
415
- sys.argv[0] = sys.argv[0][:-4]
414
+ sys.argv[0] = sys.argv[0].removesuffix('.exe')
416
415
  sys.exit(%(func)s())
417
416
  """
418
417
  )
@@ -225,7 +225,7 @@ def _check_download_dir(
225
225
  class RequirementPreparer:
226
226
  """Prepares a Requirement"""
227
227
 
228
- def __init__( # noqa: PLR0913 (too many parameters)
228
+ def __init__(
229
229
  self,
230
230
  *,
231
231
  build_dir: str,
@@ -243,7 +243,6 @@ class RequirementPreparer:
243
243
  lazy_wheel: bool,
244
244
  verbosity: int,
245
245
  legacy_resolver: bool,
246
- resume_retries: int,
247
246
  ) -> None:
248
247
  super().__init__()
249
248
 
@@ -251,7 +250,7 @@ class RequirementPreparer:
251
250
  self.build_dir = build_dir
252
251
  self.build_tracker = build_tracker
253
252
  self._session = session
254
- self._download = Downloader(session, progress_bar, resume_retries)
253
+ self._download = Downloader(session, progress_bar)
255
254
  self.finder = finder
256
255
 
257
256
  # Where still-packed archives should be written to. If None, they are
@@ -444,7 +443,7 @@ class RequirementPreparer:
444
443
  return None
445
444
 
446
445
  wheel = Wheel(link.filename)
447
- name = canonicalize_name(wheel.name)
446
+ name = wheel.name
448
447
  logger.info(
449
448
  "Obtaining dependency information from %s %s",
450
449
  name,
@@ -531,6 +530,12 @@ class RequirementPreparer:
531
530
  metadata_dist = self._fetch_metadata_only(req)
532
531
  if metadata_dist is not None:
533
532
  req.needs_more_preparation = True
533
+ req.set_dist(metadata_dist)
534
+ # Ensure download_info is available even in dry-run mode
535
+ if req.download_info is None:
536
+ req.download_info = direct_url_from_link(
537
+ req.link, req.source_dir
538
+ )
534
539
  return metadata_dist
535
540
 
536
541
  # None of the optimizations worked, fully prepare the requirement