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
pip/__init__.py CHANGED
@@ -1,6 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
- __version__ = "25.3"
3
+ __version__ = "26.0"
4
4
 
5
5
 
6
6
  def main(args: list[str] | None = None) -> int:
@@ -9,25 +9,37 @@ import site
9
9
  import sys
10
10
  import textwrap
11
11
  from collections import OrderedDict
12
- from collections.abc import Iterable
12
+ from collections.abc import Iterable, Sequence
13
+ from contextlib import AbstractContextManager as ContextManager
14
+ from contextlib import nullcontext
15
+ from io import StringIO
13
16
  from types import TracebackType
14
17
  from typing import TYPE_CHECKING, Protocol, TypedDict
15
18
 
16
19
  from pip._vendor.packaging.version import Version
17
20
 
18
21
  from pip import __file__ as pip_location
19
- from pip._internal.cli.spinners import open_spinner
22
+ from pip._internal.cli.spinners import open_rich_spinner, open_spinner
23
+ from pip._internal.exceptions import (
24
+ BuildDependencyInstallError,
25
+ DiagnosticPipError,
26
+ InstallWheelBuildError,
27
+ PipError,
28
+ )
20
29
  from pip._internal.locations import get_platlib, get_purelib, get_scheme
21
30
  from pip._internal.metadata import get_default_environment, get_environment
22
31
  from pip._internal.utils.deprecation import deprecated
23
- from pip._internal.utils.logging import VERBOSE
32
+ from pip._internal.utils.logging import VERBOSE, capture_logging
24
33
  from pip._internal.utils.packaging import get_requirement
25
34
  from pip._internal.utils.subprocess import call_subprocess
26
35
  from pip._internal.utils.temp_dir import TempDirectory, tempdir_kinds
27
36
 
28
37
  if TYPE_CHECKING:
38
+ from pip._internal.cache import WheelCache
29
39
  from pip._internal.index.package_finder import PackageFinder
40
+ from pip._internal.operations.build.build_tracker import BuildTracker
30
41
  from pip._internal.req.req_install import InstallRequirement
42
+ from pip._internal.resolution.base import BaseResolver
31
43
 
32
44
  class ExtraEnviron(TypedDict, total=False):
33
45
  extra_environ: dict[str, str]
@@ -188,6 +200,12 @@ class SubprocessBuildEnvironmentInstaller:
188
200
  )
189
201
  )
190
202
 
203
+ if finder.release_control is not None:
204
+ # Use ordered args to preserve the user's original command-line order
205
+ # This is important because later flags can override earlier ones
206
+ for attr_name, value in finder.release_control.get_ordered_args():
207
+ args.extend(("--" + attr_name.replace("_", "-"), value))
208
+
191
209
  index_urls = finder.index_urls
192
210
  if index_urls:
193
211
  args.extend(["-i", index_urls[0]])
@@ -206,8 +224,6 @@ class SubprocessBuildEnvironmentInstaller:
206
224
  args.extend(["--cert", finder.custom_cert])
207
225
  if finder.client_cert:
208
226
  args.extend(["--client-cert", finder.client_cert])
209
- if finder.allow_all_prereleases:
210
- args.append("--pre")
211
227
  if finder.prefer_binary:
212
228
  args.append("--prefer-binary")
213
229
 
@@ -230,6 +246,8 @@ class SubprocessBuildEnvironmentInstaller:
230
246
  # in the isolated build environment
231
247
  extra_environ = {"extra_environ": {"_PIP_IN_BUILD_IGNORE_CONSTRAINTS": "1"}}
232
248
 
249
+ if finder.uploaded_prior_to:
250
+ args.extend(["--uploaded-prior-to", finder.uploaded_prior_to.isoformat()])
233
251
  args.append("--")
234
252
  args.extend(requirements)
235
253
 
@@ -245,6 +263,177 @@ class SubprocessBuildEnvironmentInstaller:
245
263
  )
246
264
 
247
265
 
266
+ class InprocessBuildEnvironmentInstaller:
267
+ """
268
+ Build dependency installer that runs in the same pip process.
269
+
270
+ This contains a stripped down version of the install command with
271
+ only the logic necessary for installing build dependencies. The
272
+ finder, session, build tracker, and wheel cache are reused, but new
273
+ instances of everything else are created as needed.
274
+
275
+ Options are inherited from the parent install command unless
276
+ they don't make sense for build dependencies (in which case, they
277
+ are hard-coded, see comments below).
278
+ """
279
+
280
+ def __init__(
281
+ self,
282
+ *,
283
+ finder: PackageFinder,
284
+ build_tracker: BuildTracker,
285
+ wheel_cache: WheelCache,
286
+ build_constraints: Sequence[InstallRequirement] = (),
287
+ verbosity: int = 0,
288
+ ) -> None:
289
+ from pip._internal.operations.prepare import RequirementPreparer
290
+
291
+ self._finder = finder
292
+ self._build_constraints = build_constraints
293
+ self._wheel_cache = wheel_cache
294
+ self._level = 0
295
+
296
+ build_dir = TempDirectory(kind="build-env-install", globally_managed=True)
297
+ self._preparer = RequirementPreparer(
298
+ build_isolation_installer=self,
299
+ # Inherited options or state.
300
+ finder=finder,
301
+ session=finder._link_collector.session,
302
+ build_dir=build_dir.path,
303
+ build_tracker=build_tracker,
304
+ verbosity=verbosity,
305
+ # This is irrelevant as it only applies to editable requirements.
306
+ src_dir="",
307
+ # Hard-coded options (that should NOT be inherited).
308
+ download_dir=None,
309
+ build_isolation=True,
310
+ check_build_deps=False,
311
+ progress_bar="off",
312
+ # TODO: hash-checking should be extended to build deps, but that is
313
+ # deferred for later as it'd be a breaking change.
314
+ require_hashes=False,
315
+ use_user_site=False,
316
+ lazy_wheel=False,
317
+ legacy_resolver=False,
318
+ )
319
+
320
+ def install(
321
+ self,
322
+ requirements: Iterable[str],
323
+ prefix: _Prefix,
324
+ *,
325
+ kind: str,
326
+ for_req: InstallRequirement | None,
327
+ ) -> None:
328
+ """Install entrypoint. Manages output capturing and error handling."""
329
+ capture_logs = not logger.isEnabledFor(VERBOSE) and self._level == 0
330
+ if capture_logs:
331
+ # Hide the logs from the installation of build dependencies.
332
+ # They will be shown only if an error occurs.
333
+ capture_ctx: ContextManager[StringIO] = capture_logging()
334
+ spinner: ContextManager[None] = open_rich_spinner(f"Installing {kind}")
335
+ else:
336
+ # Otherwise, pass-through all logs (with a header).
337
+ capture_ctx, spinner = nullcontext(StringIO()), nullcontext()
338
+ logger.info("Installing %s ...", kind)
339
+
340
+ try:
341
+ self._level += 1
342
+ with spinner, capture_ctx as stream:
343
+ self._install_impl(requirements, prefix)
344
+
345
+ except DiagnosticPipError as exc:
346
+ # Format similar to a nested subprocess error, where the
347
+ # causing error is shown first, followed by the build error.
348
+ logger.info(textwrap.dedent(stream.getvalue()))
349
+ logger.error("%s", exc, extra={"rich": True})
350
+ logger.info("")
351
+ raise BuildDependencyInstallError(
352
+ for_req, requirements, cause=exc, log_lines=None
353
+ )
354
+
355
+ except Exception as exc:
356
+ logs: list[str] | None = textwrap.dedent(stream.getvalue()).splitlines()
357
+ if not capture_logs:
358
+ # If logs aren't being captured, then display the error inline
359
+ # with the rest of the logs.
360
+ logs = None
361
+ if isinstance(exc, PipError):
362
+ logger.error("%s", exc)
363
+ else:
364
+ logger.exception("pip crashed unexpectedly")
365
+ raise BuildDependencyInstallError(
366
+ for_req, requirements, cause=exc, log_lines=logs
367
+ )
368
+
369
+ finally:
370
+ self._level -= 1
371
+
372
+ def _install_impl(self, requirements: Iterable[str], prefix: _Prefix) -> None:
373
+ """Core build dependency install logic."""
374
+ from pip._internal.commands.install import installed_packages_summary
375
+ from pip._internal.req import install_given_reqs
376
+ from pip._internal.req.constructors import install_req_from_line
377
+ from pip._internal.wheel_builder import build
378
+
379
+ ireqs = [install_req_from_line(req, user_supplied=True) for req in requirements]
380
+ ireqs.extend(self._build_constraints)
381
+
382
+ resolver = self._make_resolver()
383
+ resolved_set = resolver.resolve(ireqs, check_supported_wheels=True)
384
+ self._preparer.prepare_linked_requirements_more(
385
+ resolved_set.requirements.values()
386
+ )
387
+
388
+ reqs_to_build = [
389
+ r for r in resolved_set.requirements_to_install if not r.is_wheel
390
+ ]
391
+ _, build_failures = build(reqs_to_build, self._wheel_cache, verify=True)
392
+ if build_failures:
393
+ raise InstallWheelBuildError(build_failures)
394
+
395
+ installed = install_given_reqs(
396
+ resolver.get_installation_order(resolved_set),
397
+ prefix=prefix.path,
398
+ # Hard-coded options (that should NOT be inherited).
399
+ root=None,
400
+ home=None,
401
+ warn_script_location=False,
402
+ use_user_site=False,
403
+ # As the build environment is ephemeral, it's wasteful to
404
+ # pre-compile everything since not all modules will be used.
405
+ pycompile=False,
406
+ progress_bar="off",
407
+ )
408
+
409
+ env = get_environment(list(prefix.lib_dirs))
410
+ if summary := installed_packages_summary(installed, env):
411
+ logger.info(summary)
412
+
413
+ def _make_resolver(self) -> BaseResolver:
414
+ """Create a new resolver for one time use."""
415
+ # Legacy installer never used the legacy resolver so create a
416
+ # resolvelib resolver directly. Yuck.
417
+ from pip._internal.req.constructors import install_req_from_req_string
418
+ from pip._internal.resolution.resolvelib.resolver import Resolver
419
+
420
+ return Resolver(
421
+ make_install_req=install_req_from_req_string,
422
+ # Inherited state.
423
+ preparer=self._preparer,
424
+ finder=self._finder,
425
+ wheel_cache=self._wheel_cache,
426
+ # Hard-coded options (that should NOT be inherited).
427
+ ignore_requires_python=False,
428
+ use_user_site=False,
429
+ ignore_dependencies=False,
430
+ ignore_installed=True,
431
+ force_reinstall=False,
432
+ upgrade_strategy="to-satisfy-only",
433
+ py_version_info=None,
434
+ )
435
+
436
+
248
437
  class BuildEnvironment:
249
438
  """Creates and manages an isolated environment to install build deps"""
250
439
 
@@ -235,6 +235,17 @@ class Command(CommandContextMixIn):
235
235
  )
236
236
  options.cache_dir = None
237
237
 
238
+ if (
239
+ "inprocess-build-deps" in options.features_enabled
240
+ and os.environ.get("PIP_CONSTRAINT", "")
241
+ and "build-constraint" not in options.features_enabled
242
+ ):
243
+ logger.warning(
244
+ "In-process build dependencies are enabled, "
245
+ "PIP_CONSTRAINT will have no effect for build dependencies"
246
+ )
247
+ options.features_enabled.append("build-constraint")
248
+
238
249
  return self._run_wrapper(level_number, options, args)
239
250
 
240
251
  def handler_map(self) -> dict[str, Callable[[Values, list[str]], None]]:
@@ -27,7 +27,9 @@ from pip._internal.exceptions import CommandError
27
27
  from pip._internal.locations import USER_CACHE_DIR, get_src_prefix
28
28
  from pip._internal.models.format_control import FormatControl
29
29
  from pip._internal.models.index import PyPI
30
+ from pip._internal.models.release_control import ReleaseControl
30
31
  from pip._internal.models.target_python import TargetPython
32
+ from pip._internal.utils.datetime import parse_iso_datetime
31
33
  from pip._internal.utils.hashes import STRONG_HASHES
32
34
  from pip._internal.utils.misc import strtobool
33
35
 
@@ -426,6 +428,54 @@ def find_links() -> Option:
426
428
  )
427
429
 
428
430
 
431
+ def _handle_uploaded_prior_to(
432
+ option: Option, opt: str, value: str, parser: OptionParser
433
+ ) -> None:
434
+ """
435
+ This is an optparse.Option callback for the --uploaded-prior-to option.
436
+
437
+ Parses an ISO 8601 datetime string. If no timezone is specified in the string,
438
+ local timezone is used.
439
+
440
+ Note: This option only works with indexes that provide upload-time metadata
441
+ as specified in the simple repository API:
442
+ https://packaging.python.org/en/latest/specifications/simple-repository-api/
443
+ """
444
+ if value is None:
445
+ return None
446
+
447
+ try:
448
+ uploaded_prior_to = parse_iso_datetime(value)
449
+ # Use local timezone if no offset is given in the ISO string.
450
+ if uploaded_prior_to.tzinfo is None:
451
+ uploaded_prior_to = uploaded_prior_to.astimezone()
452
+ parser.values.uploaded_prior_to = uploaded_prior_to
453
+ except ValueError as exc:
454
+ msg = (
455
+ f"invalid value: {value!r}: {exc}. "
456
+ f"Expected an ISO 8601 datetime string, "
457
+ f"e.g '2023-01-01' or '2023-01-01T00:00:00Z'"
458
+ )
459
+ raise_option_error(parser, option=option, msg=msg)
460
+
461
+
462
+ def uploaded_prior_to() -> Option:
463
+ return Option(
464
+ "--uploaded-prior-to",
465
+ dest="uploaded_prior_to",
466
+ metavar="datetime",
467
+ action="callback",
468
+ callback=_handle_uploaded_prior_to,
469
+ type="str",
470
+ help=(
471
+ "Only consider packages uploaded prior to the given date time. "
472
+ "Accepts ISO 8601 strings (e.g., '2023-01-01T00:00:00Z'). "
473
+ "Uses local timezone if none specified. Only effective when "
474
+ "installing from indexes that provide upload-time metadata."
475
+ ),
476
+ )
477
+
478
+
429
479
  def trusted_host() -> Option:
430
480
  return Option(
431
481
  "--trusted-host",
@@ -479,6 +529,18 @@ def requirements() -> Option:
479
529
  )
480
530
 
481
531
 
532
+ def requirements_from_scripts() -> Option:
533
+ return Option(
534
+ "--requirements-from-script",
535
+ action="append",
536
+ default=[],
537
+ dest="requirements_from_scripts",
538
+ metavar="file",
539
+ help="Install dependencies of the given script file"
540
+ "as defined by PEP 723 inline metadata. ",
541
+ )
542
+
543
+
482
544
  def editable() -> Option:
483
545
  return Option(
484
546
  "-e",
@@ -580,6 +642,86 @@ def only_binary() -> Option:
580
642
  )
581
643
 
582
644
 
645
+ def _get_release_control(values: Values, option: Option) -> Any:
646
+ """Get a release_control object."""
647
+ return getattr(values, option.dest)
648
+
649
+
650
+ def _handle_all_releases(
651
+ option: Option, opt_str: str, value: str, parser: OptionParser
652
+ ) -> None:
653
+ existing = _get_release_control(parser.values, option)
654
+ existing.handle_mutual_excludes(
655
+ value,
656
+ existing.all_releases,
657
+ existing.only_final,
658
+ "all_releases",
659
+ )
660
+
661
+
662
+ def _handle_only_final(
663
+ option: Option, opt_str: str, value: str, parser: OptionParser
664
+ ) -> None:
665
+ existing = _get_release_control(parser.values, option)
666
+ existing.handle_mutual_excludes(
667
+ value,
668
+ existing.only_final,
669
+ existing.all_releases,
670
+ "only_final",
671
+ )
672
+
673
+
674
+ def all_releases() -> Option:
675
+ release_control = ReleaseControl(set(), set())
676
+ return Option(
677
+ "--all-releases",
678
+ dest="release_control",
679
+ action="callback",
680
+ callback=_handle_all_releases,
681
+ type="str",
682
+ default=release_control,
683
+ help="Allow all release types (including pre-releases) for a package. "
684
+ "Can be supplied multiple times, and each time adds to the existing "
685
+ 'value. Accepts either ":all:" to allow pre-releases for all '
686
+ 'packages, ":none:" to empty the set (notice the colons), or one or '
687
+ "more package names with commas between them (no colons). Cannot be "
688
+ "used with --pre.",
689
+ )
690
+
691
+
692
+ def only_final() -> Option:
693
+ release_control = ReleaseControl(set(), set())
694
+ return Option(
695
+ "--only-final",
696
+ dest="release_control",
697
+ action="callback",
698
+ callback=_handle_only_final,
699
+ type="str",
700
+ default=release_control,
701
+ help="Only allow final releases (no pre-releases) for a package. Can be "
702
+ "supplied multiple times, and each time adds to the existing value. "
703
+ 'Accepts either ":all:" to disable pre-releases for all packages, '
704
+ '":none:" to empty the set, or one or more package names with commas '
705
+ "between them. Cannot be used with --pre.",
706
+ )
707
+
708
+
709
+ def check_release_control_exclusive(options: Values) -> None:
710
+ """
711
+ Raise an error if --pre is used with --all-releases or --only-final,
712
+ and transform --pre into --all-releases :all: if used alone.
713
+ """
714
+ if not hasattr(options, "pre") or not options.pre:
715
+ return
716
+
717
+ release_control = options.release_control
718
+ if release_control.all_releases or release_control.only_final:
719
+ raise CommandError("--pre cannot be used with --all-releases or --only-final.")
720
+
721
+ # Transform --pre into --all-releases :all:
722
+ release_control.all_releases.add(":all:")
723
+
724
+
583
725
  platforms: Callable[..., Option] = partial(
584
726
  Option,
585
727
  "--platform",
@@ -832,6 +974,7 @@ ignore_requires_python: Callable[..., Option] = partial(
832
974
  help="Ignore the Requires-Python information.",
833
975
  )
834
976
 
977
+
835
978
  no_build_isolation: Callable[..., Option] = partial(
836
979
  Option,
837
980
  "--no-build-isolation",
@@ -1044,6 +1187,7 @@ use_new_feature: Callable[..., Option] = partial(
1044
1187
  choices=[
1045
1188
  "fast-deps",
1046
1189
  "build-constraint",
1190
+ "inprocess-build-deps",
1047
1191
  ]
1048
1192
  + ALWAYS_ENABLED_FEATURES,
1049
1193
  help="Enable new functionality, that may be backward incompatible.",
@@ -1106,5 +1250,18 @@ index_group: dict[str, Any] = {
1106
1250
  extra_index_url,
1107
1251
  no_index,
1108
1252
  find_links,
1253
+ uploaded_prior_to,
1254
+ ],
1255
+ }
1256
+
1257
+ package_selection_group: dict[str, Any] = {
1258
+ "name": "Package Selection Options",
1259
+ "options": [
1260
+ pre,
1261
+ all_releases,
1262
+ only_final,
1263
+ no_binary,
1264
+ only_binary,
1265
+ prefer_binary,
1109
1266
  ],
1110
1267
  }
@@ -23,6 +23,8 @@ from pip._internal.cli.command_context import CommandContextMixIn
23
23
  if TYPE_CHECKING:
24
24
  from ssl import SSLContext
25
25
 
26
+ from pip._vendor.packaging.utils import NormalizedName
27
+
26
28
  from pip._internal.network.session import PipSession
27
29
 
28
30
  logger = logging.getLogger(__name__)
@@ -103,6 +105,7 @@ class SessionCommandMixin(CommandContextMixIn):
103
105
  session = PipSession(
104
106
  cache=os.path.join(cache_dir, "http-v2") if cache_dir else None,
105
107
  retries=retries if retries is not None else options.retries,
108
+ resume_retries=options.resume_retries,
106
109
  trusted_hosts=options.trusted_hosts,
107
110
  index_urls=self._get_index_urls(options),
108
111
  ssl_context=ssl_context,
@@ -149,6 +152,23 @@ class IndexGroupCommand(Command, SessionCommandMixin):
149
152
  This also corresponds to the commands that permit the pip version check.
150
153
  """
151
154
 
155
+ def should_exclude_prerelease(
156
+ self, options: Values, package_name: NormalizedName
157
+ ) -> bool:
158
+ """
159
+ Determine if pre-releases should be excluded for a package.
160
+ """
161
+ # Check per-package release control settings
162
+ if options.release_control:
163
+ allow_prereleases = options.release_control.allows_prereleases(package_name)
164
+ if allow_prereleases is True:
165
+ return False # Include pre-releases
166
+ elif allow_prereleases is False:
167
+ return True # Exclude pre-releases
168
+
169
+ # No specific setting: exclude prereleases by default
170
+ return True
171
+
152
172
  def handle_pip_version_check(self, options: Values) -> None:
153
173
  """
154
174
  Do the pip version check if not disabled.
pip/_internal/cli/main.py CHANGED
@@ -8,12 +8,6 @@ import os
8
8
  import sys
9
9
  import warnings
10
10
 
11
- from pip._internal.cli.autocompletion import autocomplete
12
- from pip._internal.cli.main_parser import parse_command
13
- from pip._internal.commands import create_command
14
- from pip._internal.exceptions import PipError
15
- from pip._internal.utils import deprecation
16
-
17
11
  logger = logging.getLogger(__name__)
18
12
 
19
13
 
@@ -45,6 +39,17 @@ logger = logging.getLogger(__name__)
45
39
 
46
40
 
47
41
  def main(args: list[str] | None = None) -> int:
42
+ # NOTE: Lazy imports to speed up import of this module,
43
+ # which is imported from the pip console script. This doesn't
44
+ # speed up normal pip execution, but might be important in the future
45
+ # if we use ``multiprocessing`` module,
46
+ # which imports __main__ for each spawned subprocess.
47
+ from pip._internal.cli.autocompletion import autocomplete
48
+ from pip._internal.cli.main_parser import parse_command
49
+ from pip._internal.commands import create_command
50
+ from pip._internal.exceptions import PipError
51
+ from pip._internal.utils import deprecation
52
+
48
53
  if args is None:
49
54
  args = sys.argv[1:]
50
55
 
@@ -6,6 +6,8 @@ import os
6
6
  import subprocess
7
7
  import sys
8
8
 
9
+ from pip._vendor.rich.markup import escape
10
+
9
11
  from pip._internal.build_env import get_runnable_pip
10
12
  from pip._internal.cli import cmdoptions
11
13
  from pip._internal.cli.parser import ConfigOptionParser, UpdatingDefaultsHelpFormatter
@@ -39,7 +41,7 @@ def create_main_parser() -> ConfigOptionParser:
39
41
 
40
42
  # create command listing for description
41
43
  description = [""] + [
42
- f"{name:27} {command_info.summary}"
44
+ f"[optparse.longargs]{name:27}[/] {escape(command_info.summary)}"
43
45
  for name, command_info in commands_dict.items()
44
46
  ]
45
47
  parser.description = "\n".join(description)