pip 25.3__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 (104) hide show
  1. pip/__init__.py +1 -1
  2. pip/_internal/build_env.py +194 -5
  3. pip/_internal/cli/base_command.py +11 -0
  4. pip/_internal/cli/cmdoptions.py +157 -0
  5. pip/_internal/cli/index_command.py +20 -0
  6. pip/_internal/cli/main.py +11 -6
  7. pip/_internal/cli/main_parser.py +3 -1
  8. pip/_internal/cli/parser.py +93 -33
  9. pip/_internal/cli/progress_bars.py +4 -2
  10. pip/_internal/cli/req_command.py +99 -23
  11. pip/_internal/commands/cache.py +24 -0
  12. pip/_internal/commands/completion.py +2 -1
  13. pip/_internal/commands/download.py +8 -4
  14. pip/_internal/commands/index.py +13 -6
  15. pip/_internal/commands/install.py +36 -29
  16. pip/_internal/commands/list.py +14 -16
  17. pip/_internal/commands/lock.py +16 -8
  18. pip/_internal/commands/wheel.py +8 -13
  19. pip/_internal/exceptions.py +76 -3
  20. pip/_internal/index/collector.py +2 -3
  21. pip/_internal/index/package_finder.py +84 -18
  22. pip/_internal/locations/__init__.py +1 -2
  23. pip/_internal/locations/_sysconfig.py +4 -1
  24. pip/_internal/models/link.py +18 -14
  25. pip/_internal/models/release_control.py +92 -0
  26. pip/_internal/models/selection_prefs.py +6 -3
  27. pip/_internal/network/auth.py +6 -2
  28. pip/_internal/network/download.py +4 -5
  29. pip/_internal/network/session.py +14 -10
  30. pip/_internal/operations/install/wheel.py +1 -2
  31. pip/_internal/operations/prepare.py +2 -3
  32. pip/_internal/req/constructors.py +3 -1
  33. pip/_internal/req/pep723.py +41 -0
  34. pip/_internal/req/req_file.py +10 -1
  35. pip/_internal/resolution/resolvelib/factory.py +12 -1
  36. pip/_internal/resolution/resolvelib/requirements.py +7 -3
  37. pip/_internal/self_outdated_check.py +6 -13
  38. pip/_internal/utils/datetime.py +18 -0
  39. pip/_internal/utils/filesystem.py +40 -1
  40. pip/_internal/utils/logging.py +34 -2
  41. pip/_internal/utils/misc.py +18 -12
  42. pip/_internal/utils/pylock.py +116 -0
  43. pip/_internal/utils/unpacking.py +1 -1
  44. pip/_internal/vcs/versioncontrol.py +3 -1
  45. pip/_vendor/cachecontrol/__init__.py +6 -3
  46. pip/_vendor/cachecontrol/adapter.py +0 -1
  47. pip/_vendor/cachecontrol/controller.py +1 -1
  48. pip/_vendor/cachecontrol/filewrapper.py +3 -1
  49. pip/_vendor/certifi/__init__.py +1 -1
  50. pip/_vendor/certifi/cacert.pem +0 -332
  51. pip/_vendor/idna/LICENSE.md +1 -1
  52. pip/_vendor/idna/codec.py +1 -1
  53. pip/_vendor/idna/core.py +1 -1
  54. pip/_vendor/idna/idnadata.py +72 -6
  55. pip/_vendor/idna/package_data.py +1 -1
  56. pip/_vendor/idna/uts46data.py +891 -731
  57. pip/_vendor/packaging/__init__.py +1 -1
  58. pip/_vendor/packaging/_elffile.py +0 -1
  59. pip/_vendor/packaging/_manylinux.py +36 -36
  60. pip/_vendor/packaging/_musllinux.py +1 -1
  61. pip/_vendor/packaging/_parser.py +22 -10
  62. pip/_vendor/packaging/_structures.py +8 -0
  63. pip/_vendor/packaging/_tokenizer.py +23 -25
  64. pip/_vendor/packaging/licenses/__init__.py +13 -11
  65. pip/_vendor/packaging/licenses/_spdx.py +41 -1
  66. pip/_vendor/packaging/markers.py +64 -38
  67. pip/_vendor/packaging/metadata.py +143 -27
  68. pip/_vendor/packaging/pylock.py +635 -0
  69. pip/_vendor/packaging/requirements.py +5 -10
  70. pip/_vendor/packaging/specifiers.py +219 -170
  71. pip/_vendor/packaging/tags.py +15 -20
  72. pip/_vendor/packaging/utils.py +19 -24
  73. pip/_vendor/packaging/version.py +315 -105
  74. pip/_vendor/platformdirs/version.py +2 -2
  75. pip/_vendor/platformdirs/windows.py +7 -1
  76. pip/_vendor/vendor.txt +5 -5
  77. {pip-25.3.dist-info → pip-26.0.dist-info}/METADATA +2 -2
  78. {pip-25.3.dist-info → pip-26.0.dist-info}/RECORD +103 -100
  79. {pip-25.3.dist-info → pip-26.0.dist-info}/licenses/AUTHORS.txt +18 -0
  80. {pip-25.3.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/idna/LICENSE.md +1 -1
  81. pip/_internal/models/pylock.py +0 -188
  82. {pip-25.3.dist-info → pip-26.0.dist-info}/WHEEL +0 -0
  83. {pip-25.3.dist-info → pip-26.0.dist-info}/entry_points.txt +0 -0
  84. {pip-25.3.dist-info → pip-26.0.dist-info}/licenses/LICENSE.txt +0 -0
  85. {pip-25.3.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/cachecontrol/LICENSE.txt +0 -0
  86. {pip-25.3.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/certifi/LICENSE +0 -0
  87. {pip-25.3.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/dependency_groups/LICENSE.txt +0 -0
  88. {pip-25.3.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/distlib/LICENSE.txt +0 -0
  89. {pip-25.3.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/distro/LICENSE +0 -0
  90. {pip-25.3.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/msgpack/COPYING +0 -0
  91. {pip-25.3.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/packaging/LICENSE +0 -0
  92. {pip-25.3.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/packaging/LICENSE.APACHE +0 -0
  93. {pip-25.3.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/packaging/LICENSE.BSD +0 -0
  94. {pip-25.3.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/pkg_resources/LICENSE +0 -0
  95. {pip-25.3.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/platformdirs/LICENSE +0 -0
  96. {pip-25.3.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/pygments/LICENSE +0 -0
  97. {pip-25.3.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/pyproject_hooks/LICENSE +0 -0
  98. {pip-25.3.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/requests/LICENSE +0 -0
  99. {pip-25.3.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/resolvelib/LICENSE +0 -0
  100. {pip-25.3.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/rich/LICENSE +0 -0
  101. {pip-25.3.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/tomli/LICENSE +0 -0
  102. {pip-25.3.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/tomli_w/LICENSE +0 -0
  103. {pip-25.3.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/truststore/LICENSE +0 -0
  104. {pip-25.3.dist-info → pip-26.0.dist-info}/licenses/src/pip/_vendor/urllib3/LICENSE.txt +0 -0
@@ -2,6 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import datetime
5
6
  import enum
6
7
  import functools
7
8
  import itertools
@@ -18,19 +19,22 @@ from typing import (
18
19
  from pip._vendor.packaging import specifiers
19
20
  from pip._vendor.packaging.tags import Tag
20
21
  from pip._vendor.packaging.utils import NormalizedName, canonicalize_name
21
- from pip._vendor.packaging.version import InvalidVersion, _BaseVersion
22
+ from pip._vendor.packaging.version import InvalidVersion, Version, _BaseVersion
22
23
  from pip._vendor.packaging.version import parse as parse_version
23
24
 
24
25
  from pip._internal.exceptions import (
25
26
  BestVersionAlreadyInstalled,
26
27
  DistributionNotFound,
28
+ InstallationError,
27
29
  InvalidWheelFilename,
28
30
  UnsupportedWheel,
29
31
  )
30
32
  from pip._internal.index.collector import LinkCollector, parse_links
33
+ from pip._internal.metadata import select_backend
31
34
  from pip._internal.models.candidate import InstallationCandidate
32
35
  from pip._internal.models.format_control import FormatControl
33
36
  from pip._internal.models.link import Link
37
+ from pip._internal.models.release_control import ReleaseControl
34
38
  from pip._internal.models.search_scope import SearchScope
35
39
  from pip._internal.models.selection_prefs import SelectionPreferences
36
40
  from pip._internal.models.target_python import TargetPython
@@ -111,6 +115,8 @@ class LinkType(enum.Enum):
111
115
  format_invalid = enum.auto()
112
116
  platform_mismatch = enum.auto()
113
117
  requires_python_mismatch = enum.auto()
118
+ upload_too_late = enum.auto()
119
+ upload_time_missing = enum.auto()
114
120
 
115
121
 
116
122
  class LinkEvaluator:
@@ -132,6 +138,7 @@ class LinkEvaluator:
132
138
  target_python: TargetPython,
133
139
  allow_yanked: bool,
134
140
  ignore_requires_python: bool | None = None,
141
+ uploaded_prior_to: datetime.datetime | None = None,
135
142
  ) -> None:
136
143
  """
137
144
  :param project_name: The user supplied package name.
@@ -149,6 +156,8 @@ class LinkEvaluator:
149
156
  :param ignore_requires_python: Whether to ignore incompatible
150
157
  PEP 503 "data-requires-python" values in HTML links. Defaults
151
158
  to False.
159
+ :param uploaded_prior_to: If set, only allow links uploaded prior to
160
+ the given datetime.
152
161
  """
153
162
  if ignore_requires_python is None:
154
163
  ignore_requires_python = False
@@ -158,6 +167,7 @@ class LinkEvaluator:
158
167
  self._ignore_requires_python = ignore_requires_python
159
168
  self._formats = formats
160
169
  self._target_python = target_python
170
+ self._uploaded_prior_to = uploaded_prior_to
161
171
 
162
172
  self.project_name = project_name
163
173
 
@@ -218,6 +228,27 @@ class LinkEvaluator:
218
228
 
219
229
  version = wheel.version
220
230
 
231
+ # Check upload-time filter after verifying the link is a package file.
232
+ # Skip this check for local files, as --uploaded-prior-to only applies
233
+ # to packages from indexes.
234
+ if self._uploaded_prior_to is not None and not link.is_file:
235
+ if link.upload_time is None:
236
+ if link.comes_from:
237
+ index_info = f"Index {link.comes_from}"
238
+ else:
239
+ index_info = "Index"
240
+
241
+ return (
242
+ LinkType.upload_time_missing,
243
+ f"{index_info} does not provide upload-time metadata.",
244
+ )
245
+ elif link.upload_time >= self._uploaded_prior_to:
246
+ return (
247
+ LinkType.upload_too_late,
248
+ f"Upload time {link.upload_time} not "
249
+ f"prior to {self._uploaded_prior_to}",
250
+ )
251
+
221
252
  # This should be up by the self.ok_binary check, but see issue 2700.
222
253
  if "source" not in self._formats and ext != WHEEL_EXTENSION:
223
254
  reason = f"No sources permitted for {self.project_name}"
@@ -350,7 +381,7 @@ class CandidatePreferences:
350
381
  """
351
382
 
352
383
  prefer_binary: bool = False
353
- allow_all_prereleases: bool = False
384
+ release_control: ReleaseControl | None = None
354
385
 
355
386
 
356
387
  @dataclass(frozen=True)
@@ -391,7 +422,7 @@ class CandidateEvaluator:
391
422
  project_name: str,
392
423
  target_python: TargetPython | None = None,
393
424
  prefer_binary: bool = False,
394
- allow_all_prereleases: bool = False,
425
+ release_control: ReleaseControl | None = None,
395
426
  specifier: specifiers.BaseSpecifier | None = None,
396
427
  hashes: Hashes | None = None,
397
428
  ) -> CandidateEvaluator:
@@ -417,7 +448,7 @@ class CandidateEvaluator:
417
448
  supported_tags=supported_tags,
418
449
  specifier=specifier,
419
450
  prefer_binary=prefer_binary,
420
- allow_all_prereleases=allow_all_prereleases,
451
+ release_control=release_control,
421
452
  hashes=hashes,
422
453
  )
423
454
 
@@ -427,14 +458,14 @@ class CandidateEvaluator:
427
458
  supported_tags: list[Tag],
428
459
  specifier: specifiers.BaseSpecifier,
429
460
  prefer_binary: bool = False,
430
- allow_all_prereleases: bool = False,
461
+ release_control: ReleaseControl | None = None,
431
462
  hashes: Hashes | None = None,
432
463
  ) -> None:
433
464
  """
434
465
  :param supported_tags: The PEP 425 tags supported by the target
435
466
  Python in order of preference (most preferred first).
436
467
  """
437
- self._allow_all_prereleases = allow_all_prereleases
468
+ self._release_control = release_control
438
469
  self._hashes = hashes
439
470
  self._prefer_binary = prefer_binary
440
471
  self._project_name = project_name
@@ -455,17 +486,27 @@ class CandidateEvaluator:
455
486
  Return the applicable candidates from a list of candidates.
456
487
  """
457
488
  # Using None infers from the specifier instead.
458
- allow_prereleases = self._allow_all_prereleases or None
489
+ if self._release_control is not None:
490
+ allow_prereleases = self._release_control.allows_prereleases(
491
+ canonicalize_name(self._project_name)
492
+ )
493
+ else:
494
+ allow_prereleases = None
459
495
  specifier = self._specifier
460
496
 
461
- # We turn the version object into a str here because otherwise
462
- # when we're debundled but setuptools isn't, Python will see
463
- # packaging.version.Version and
497
+ # When using the pkg_resources backend we turn the version object into
498
+ # a str here because otherwise when we're debundled but setuptools isn't,
499
+ # Python will see packaging.version.Version and
464
500
  # pkg_resources._vendor.packaging.version.Version as different
465
501
  # types. This way we'll use a str as a common data interchange
466
502
  # format. If we stop using the pkg_resources provided specifier
467
503
  # and start using our own, we can drop the cast to str().
468
- candidates_and_versions = [(c, str(c.version)) for c in candidates]
504
+ if select_backend().NAME == "pkg_resources":
505
+ candidates_and_versions: list[
506
+ tuple[InstallationCandidate, str | Version]
507
+ ] = [(c, str(c.version)) for c in candidates]
508
+ else:
509
+ candidates_and_versions = [(c, c.version) for c in candidates]
469
510
  versions = set(
470
511
  specifier.filter(
471
512
  (v for _, v in candidates_and_versions),
@@ -593,6 +634,7 @@ class PackageFinder:
593
634
  format_control: FormatControl | None = None,
594
635
  candidate_prefs: CandidatePreferences | None = None,
595
636
  ignore_requires_python: bool | None = None,
637
+ uploaded_prior_to: datetime.datetime | None = None,
596
638
  ) -> None:
597
639
  """
598
640
  This constructor is primarily meant to be used by the create() class
@@ -614,6 +656,7 @@ class PackageFinder:
614
656
  self._ignore_requires_python = ignore_requires_python
615
657
  self._link_collector = link_collector
616
658
  self._target_python = target_python
659
+ self._uploaded_prior_to = uploaded_prior_to
617
660
 
618
661
  self.format_control = format_control
619
662
 
@@ -637,6 +680,7 @@ class PackageFinder:
637
680
  link_collector: LinkCollector,
638
681
  selection_prefs: SelectionPreferences,
639
682
  target_python: TargetPython | None = None,
683
+ uploaded_prior_to: datetime.datetime | None = None,
640
684
  ) -> PackageFinder:
641
685
  """Create a PackageFinder.
642
686
 
@@ -645,13 +689,15 @@ class PackageFinder:
645
689
  :param target_python: The target Python interpreter to use when
646
690
  checking compatibility. If None (the default), a TargetPython
647
691
  object will be constructed from the running Python.
692
+ :param uploaded_prior_to: If set, only find links uploaded prior
693
+ to the given datetime.
648
694
  """
649
695
  if target_python is None:
650
696
  target_python = TargetPython()
651
697
 
652
698
  candidate_prefs = CandidatePreferences(
653
699
  prefer_binary=selection_prefs.prefer_binary,
654
- allow_all_prereleases=selection_prefs.allow_all_prereleases,
700
+ release_control=selection_prefs.release_control,
655
701
  )
656
702
 
657
703
  return cls(
@@ -661,6 +707,7 @@ class PackageFinder:
661
707
  allow_yanked=selection_prefs.allow_yanked,
662
708
  format_control=selection_prefs.format_control,
663
709
  ignore_requires_python=selection_prefs.ignore_requires_python,
710
+ uploaded_prior_to=uploaded_prior_to,
664
711
  )
665
712
 
666
713
  @property
@@ -707,11 +754,11 @@ class PackageFinder:
707
754
  return cert
708
755
 
709
756
  @property
710
- def allow_all_prereleases(self) -> bool:
711
- return self._candidate_prefs.allow_all_prereleases
757
+ def release_control(self) -> ReleaseControl | None:
758
+ return self._candidate_prefs.release_control
712
759
 
713
- def set_allow_all_prereleases(self) -> None:
714
- self._candidate_prefs.allow_all_prereleases = True
760
+ def set_release_control(self, release_control: ReleaseControl) -> None:
761
+ self._candidate_prefs.release_control = release_control
715
762
 
716
763
  @property
717
764
  def prefer_binary(self) -> bool:
@@ -720,6 +767,10 @@ class PackageFinder:
720
767
  def set_prefer_binary(self) -> None:
721
768
  self._candidate_prefs.prefer_binary = True
722
769
 
770
+ @property
771
+ def uploaded_prior_to(self) -> datetime.datetime | None:
772
+ return self._uploaded_prior_to
773
+
723
774
  def requires_python_skipped_reasons(self) -> list[str]:
724
775
  reasons = {
725
776
  detail
@@ -739,6 +790,7 @@ class PackageFinder:
739
790
  target_python=self._target_python,
740
791
  allow_yanked=self._allow_yanked,
741
792
  ignore_requires_python=self._ignore_requires_python,
793
+ uploaded_prior_to=self._uploaded_prior_to,
742
794
  )
743
795
 
744
796
  def _sort_links(self, links: Iterable[Link]) -> list[Link]:
@@ -773,6 +825,10 @@ class PackageFinder:
773
825
  InstallationCandidate and return it. Otherwise, return None.
774
826
  """
775
827
  result, detail = link_evaluator.evaluate_link(link)
828
+ if result == LinkType.upload_time_missing:
829
+ # Fail immediately if the index doesn't provide upload-time
830
+ # when --uploaded-prior-to is specified
831
+ raise InstallationError(detail)
776
832
  if result != LinkType.candidate:
777
833
  self._log_skipped_link(link, result, detail)
778
834
  return None
@@ -890,7 +946,7 @@ class PackageFinder:
890
946
  project_name=project_name,
891
947
  target_python=self._target_python,
892
948
  prefer_binary=candidate_prefs.prefer_binary,
893
- allow_all_prereleases=candidate_prefs.allow_all_prereleases,
949
+ release_control=candidate_prefs.release_control,
894
950
  specifier=specifier,
895
951
  hashes=hashes,
896
952
  )
@@ -964,9 +1020,19 @@ class PackageFinder:
964
1020
  )
965
1021
 
966
1022
  if installed_version is None and best_candidate is None:
1023
+ # Check if only final releases are allowed for this package
1024
+ version_type = "version"
1025
+ if self.release_control is not None:
1026
+ allows_pre = self.release_control.allows_prereleases(
1027
+ canonicalize_name(name)
1028
+ )
1029
+ if allows_pre is False:
1030
+ version_type = "final version"
1031
+
967
1032
  logger.critical(
968
- "Could not find a version that satisfies the requirement %s "
1033
+ "Could not find a %s that satisfies the requirement %s "
969
1034
  "(from versions: %s)",
1035
+ version_type,
970
1036
  req,
971
1037
  _format_versions(best_candidate_result.all_candidates),
972
1038
  )
@@ -6,7 +6,6 @@ import os
6
6
  import pathlib
7
7
  import sys
8
8
  import sysconfig
9
- from typing import Any
10
9
 
11
10
  from pip._internal.models.scheme import SCHEME_KEYS, Scheme
12
11
  from pip._internal.utils.compat import WINDOWS
@@ -134,7 +133,7 @@ def _looks_like_red_hat_scheme() -> bool:
134
133
  from distutils.command.install import install
135
134
  from distutils.dist import Distribution
136
135
 
137
- cmd: Any = install(Distribution())
136
+ cmd = install(Distribution())
138
137
  cmd.finalize_options()
139
138
  return (
140
139
  cmd.exec_prefix == f"{os.path.normpath(sys.exec_prefix)}/local"
@@ -4,6 +4,7 @@ import logging
4
4
  import os
5
5
  import sys
6
6
  import sysconfig
7
+ from typing import Callable
7
8
 
8
9
  from pip._internal.exceptions import InvalidSchemeCombination, UserInstallationInvalid
9
10
  from pip._internal.models.scheme import SCHEME_KEYS, Scheme
@@ -24,7 +25,9 @@ logger = logging.getLogger(__name__)
24
25
 
25
26
  _AVAILABLE_SCHEMES = set(sysconfig.get_scheme_names())
26
27
 
27
- _PREFERRED_SCHEME_API = getattr(sysconfig, "get_preferred_scheme", None)
28
+ _PREFERRED_SCHEME_API: Callable[[str], str] | None = getattr(
29
+ sysconfig, "get_preferred_scheme", None
30
+ )
28
31
 
29
32
 
30
33
  def _should_use_osx_framework_prefix() -> bool:
@@ -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="26.0",
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
@@ -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
@@ -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