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
@@ -11,7 +11,6 @@ pass on state. To be consistent, all options will follow this design.
11
11
  # mypy: strict-optional=False
12
12
  from __future__ import annotations
13
13
 
14
- import importlib.util
15
14
  import logging
16
15
  import os
17
16
  import pathlib
@@ -28,7 +27,9 @@ from pip._internal.exceptions import CommandError
28
27
  from pip._internal.locations import USER_CACHE_DIR, get_src_prefix
29
28
  from pip._internal.models.format_control import FormatControl
30
29
  from pip._internal.models.index import PyPI
30
+ from pip._internal.models.release_control import ReleaseControl
31
31
  from pip._internal.models.target_python import TargetPython
32
+ from pip._internal.utils.datetime import parse_iso_datetime
32
33
  from pip._internal.utils.hashes import STRONG_HASHES
33
34
  from pip._internal.utils.misc import strtobool
34
35
 
@@ -101,6 +102,29 @@ def check_dist_restriction(options: Values, check_target: bool = False) -> None:
101
102
  )
102
103
 
103
104
 
105
+ def check_build_constraints(options: Values) -> None:
106
+ """Function for validating build constraints options.
107
+
108
+ :param options: The OptionParser options.
109
+ """
110
+ if hasattr(options, "build_constraints") and options.build_constraints:
111
+ if not options.build_isolation:
112
+ raise CommandError(
113
+ "--build-constraint cannot be used with --no-build-isolation."
114
+ )
115
+
116
+ # Import here to avoid circular imports
117
+ from pip._internal.network.session import PipSession
118
+ from pip._internal.req.req_file import get_file_content
119
+
120
+ # Eagerly check build constraints file contents
121
+ # is valid so that we don't fail in when trying
122
+ # to check constraints in isolated build process
123
+ with PipSession() as session:
124
+ for constraint_file in options.build_constraints:
125
+ get_file_content(constraint_file, session)
126
+
127
+
104
128
  def _path_option_check(option: Option, opt: str, value: str) -> str:
105
129
  return os.path.expanduser(value)
106
130
 
@@ -161,8 +185,7 @@ require_virtualenv: Callable[..., Option] = partial(
161
185
  action="store_true",
162
186
  default=False,
163
187
  help=(
164
- "Allow pip to only run in a virtual environment; "
165
- "exit with an error otherwise."
188
+ "Allow pip to only run in a virtual environment; exit with an error otherwise."
166
189
  ),
167
190
  )
168
191
 
@@ -405,6 +428,54 @@ def find_links() -> Option:
405
428
  )
406
429
 
407
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
+
408
479
  def trusted_host() -> Option:
409
480
  return Option(
410
481
  "--trusted-host",
@@ -430,6 +501,21 @@ def constraints() -> Option:
430
501
  )
431
502
 
432
503
 
504
+ def build_constraints() -> Option:
505
+ return Option(
506
+ "--build-constraint",
507
+ dest="build_constraints",
508
+ action="append",
509
+ type="str",
510
+ default=[],
511
+ metavar="file",
512
+ help=(
513
+ "Constrain build dependencies using the given constraints file. "
514
+ "This option can be used multiple times."
515
+ ),
516
+ )
517
+
518
+
433
519
  def requirements() -> Option:
434
520
  return Option(
435
521
  "-r",
@@ -443,6 +529,18 @@ def requirements() -> Option:
443
529
  )
444
530
 
445
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
+
446
544
  def editable() -> Option:
447
545
  return Option(
448
546
  "-e",
@@ -544,6 +642,86 @@ def only_binary() -> Option:
544
642
  )
545
643
 
546
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
+
547
725
  platforms: Callable[..., Option] = partial(
548
726
  Option,
549
727
  "--platform",
@@ -796,6 +974,7 @@ ignore_requires_python: Callable[..., Option] = partial(
796
974
  help="Ignore the Requires-Python information.",
797
975
  )
798
976
 
977
+
799
978
  no_build_isolation: Callable[..., Option] = partial(
800
979
  Option,
801
980
  "--no-build-isolation",
@@ -813,62 +992,16 @@ check_build_deps: Callable[..., Option] = partial(
813
992
  dest="check_build_deps",
814
993
  action="store_true",
815
994
  default=False,
816
- help="Check the build dependencies when PEP517 is used.",
995
+ help="Check the build dependencies.",
817
996
  )
818
997
 
819
998
 
820
- def _handle_no_use_pep517(
821
- option: Option, opt: str, value: str, parser: OptionParser
822
- ) -> None:
823
- """
824
- Process a value provided for the --no-use-pep517 option.
825
-
826
- This is an optparse.Option callback for the no_use_pep517 option.
827
- """
828
- # Since --no-use-pep517 doesn't accept arguments, the value argument
829
- # will be None if --no-use-pep517 is passed via the command-line.
830
- # However, the value can be non-None if the option is triggered e.g.
831
- # by an environment variable, for example "PIP_NO_USE_PEP517=true".
832
- if value is not None:
833
- msg = """A value was passed for --no-use-pep517,
834
- probably using either the PIP_NO_USE_PEP517 environment variable
835
- or the "no-use-pep517" config file option. Use an appropriate value
836
- of the PIP_USE_PEP517 environment variable or the "use-pep517"
837
- config file option instead.
838
- """
839
- raise_option_error(parser, option=option, msg=msg)
840
-
841
- # If user doesn't wish to use pep517, we check if setuptools is installed
842
- # and raise error if it is not.
843
- packages = ("setuptools",)
844
- if not all(importlib.util.find_spec(package) for package in packages):
845
- msg = (
846
- f"It is not possible to use --no-use-pep517 "
847
- f"without {' and '.join(packages)} installed."
848
- )
849
- raise_option_error(parser, option=option, msg=msg)
850
-
851
- # Otherwise, --no-use-pep517 was passed via the command-line.
852
- parser.values.use_pep517 = False
853
-
854
-
855
999
  use_pep517: Any = partial(
856
1000
  Option,
857
1001
  "--use-pep517",
858
1002
  dest="use_pep517",
859
1003
  action="store_true",
860
- default=None,
861
- help="Use PEP 517 for building source distributions "
862
- "(use --no-use-pep517 to force legacy behaviour).",
863
- )
864
-
865
- no_use_pep517: Any = partial(
866
- Option,
867
- "--no-use-pep517",
868
- dest="use_pep517",
869
- action="callback",
870
- callback=_handle_no_use_pep517,
871
- default=None,
1004
+ default=True,
872
1005
  help=SUPPRESS_HELP,
873
1006
  )
874
1007
 
@@ -901,30 +1034,11 @@ config_settings: Callable[..., Option] = partial(
901
1034
  action="callback",
902
1035
  callback=_handle_config_settings,
903
1036
  metavar="settings",
904
- help="Configuration settings to be passed to the PEP 517 build backend. "
1037
+ help="Configuration settings to be passed to the build backend. "
905
1038
  "Settings take the form KEY=VALUE. Use multiple --config-settings options "
906
1039
  "to pass multiple keys to the backend.",
907
1040
  )
908
1041
 
909
- build_options: Callable[..., Option] = partial(
910
- Option,
911
- "--build-option",
912
- dest="build_options",
913
- metavar="options",
914
- action="append",
915
- help="Extra arguments to be supplied to 'setup.py bdist_wheel'.",
916
- )
917
-
918
- global_options: Callable[..., Option] = partial(
919
- Option,
920
- "--global-option",
921
- dest="global_options",
922
- action="append",
923
- metavar="options",
924
- help="Extra global options to be supplied to the setup.py "
925
- "call before the install or bdist_wheel command.",
926
- )
927
-
928
1042
  no_clean: Callable[..., Option] = partial(
929
1043
  Option,
930
1044
  "--no-clean",
@@ -1072,6 +1186,8 @@ use_new_feature: Callable[..., Option] = partial(
1072
1186
  default=[],
1073
1187
  choices=[
1074
1188
  "fast-deps",
1189
+ "build-constraint",
1190
+ "inprocess-build-deps",
1075
1191
  ]
1076
1192
  + ALWAYS_ENABLED_FEATURES,
1077
1193
  help="Enable new functionality, that may be backward incompatible.",
@@ -1134,5 +1250,18 @@ index_group: dict[str, Any] = {
1134
1250
  extra_index_url,
1135
1251
  no_index,
1136
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,
1137
1266
  ],
1138
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)
@@ -4,6 +4,8 @@ from __future__ import annotations
4
4
 
5
5
  import logging
6
6
  import optparse
7
+ import os
8
+ import re
7
9
  import shutil
8
10
  import sys
9
11
  import textwrap
@@ -11,8 +13,12 @@ from collections.abc import Generator
11
13
  from contextlib import suppress
12
14
  from typing import Any, NoReturn
13
15
 
16
+ from pip._vendor.rich.markup import escape
17
+ from pip._vendor.rich.theme import Theme
18
+
14
19
  from pip._internal.cli.status_codes import UNKNOWN_ERROR
15
20
  from pip._internal.configuration import Configuration, ConfigurationError
21
+ from pip._internal.utils.logging import PipConsole
16
22
  from pip._internal.utils.misc import redact_auth_from_url, strtobool
17
23
 
18
24
  logger = logging.getLogger(__name__)
@@ -21,6 +27,17 @@ logger = logging.getLogger(__name__)
21
27
  class PrettyHelpFormatter(optparse.IndentedHelpFormatter):
22
28
  """A prettier/less verbose help formatter for optparse."""
23
29
 
30
+ styles = {
31
+ "optparse.shortargs": "green",
32
+ "optparse.longargs": "cyan",
33
+ "optparse.groups": "bold blue",
34
+ "optparse.metavar": "yellow",
35
+ }
36
+ highlights = {
37
+ r"\s(-{1}[\w]+[\w-]*)": "shortargs", # highlight -letter as short args
38
+ r"\s(-{2}[\w]+[\w-]*)": "longargs", # highlight --words as long args
39
+ }
40
+
24
41
  def __init__(self, *args: Any, **kwargs: Any) -> None:
25
42
  # help position must be aligned with __init__.parseopts.description
26
43
  kwargs["max_help_position"] = 30
@@ -29,61 +46,82 @@ class PrettyHelpFormatter(optparse.IndentedHelpFormatter):
29
46
  super().__init__(*args, **kwargs)
30
47
 
31
48
  def format_option_strings(self, option: optparse.Option) -> str:
32
- return self._format_option_strings(option)
33
-
34
- def _format_option_strings(
35
- self, option: optparse.Option, mvarfmt: str = " <{}>", optsep: str = ", "
36
- ) -> str:
37
- """
38
- Return a comma-separated list of option strings and metavars.
39
-
40
- :param option: tuple of (short opt, long opt), e.g: ('-f', '--format')
41
- :param mvarfmt: metavar format string
42
- :param optsep: separator
43
- """
49
+ """Return a comma-separated list of option strings and metavars."""
44
50
  opts = []
45
51
 
46
52
  if option._short_opts:
47
- opts.append(option._short_opts[0])
53
+ opts.append(f"[optparse.shortargs]{option._short_opts[0]}[/]")
48
54
  if option._long_opts:
49
- opts.append(option._long_opts[0])
55
+ opts.append(f"[optparse.longargs]{option._long_opts[0]}[/]")
50
56
  if len(opts) > 1:
51
- opts.insert(1, optsep)
57
+ opts.insert(1, ", ")
52
58
 
53
59
  if option.takes_value():
54
60
  assert option.dest is not None
55
61
  metavar = option.metavar or option.dest.lower()
56
- opts.append(mvarfmt.format(metavar.lower()))
62
+ opts.append(f" [optparse.metavar]<{escape(metavar.lower())}>[/]")
57
63
 
58
64
  return "".join(opts)
59
65
 
66
+ def format_option(self, option: optparse.Option) -> str:
67
+ """Overridden method with Rich support."""
68
+ # fmt: off
69
+ result = []
70
+ opts = self.option_strings[option]
71
+ opt_width = self.help_position - self.current_indent - 2
72
+ # Remove the rich style tags before calculating width during
73
+ # text wrap calculations. Also store the length removed to adjust
74
+ # the padding in the else branch.
75
+ stripped = re.sub(r"(\[[a-z.]+\])|(\[\/\])", "", opts)
76
+ style_tag_length = len(opts) - len(stripped)
77
+ if len(stripped) > opt_width:
78
+ opts = "%*s%s\n" % (self.current_indent, "", opts) # noqa: UP031
79
+ indent_first = self.help_position
80
+ else: # start help on same line as opts
81
+ opts = "%*s%-*s " % (self.current_indent, "", # noqa: UP031
82
+ opt_width + style_tag_length, opts)
83
+ indent_first = 0
84
+ result.append(opts)
85
+ if option.help:
86
+ help_text = self.expand_default(option)
87
+ help_lines = textwrap.wrap(help_text, self.help_width)
88
+ result.append("%*s%s\n" % (indent_first, "", help_lines[0])) # noqa: UP031
89
+ result.extend(["%*s%s\n" % (self.help_position, "", line) # noqa: UP031
90
+ for line in help_lines[1:]])
91
+ elif opts[-1] != "\n":
92
+ result.append("\n")
93
+ return "".join(result)
94
+ # fmt: on
95
+
60
96
  def format_heading(self, heading: str) -> str:
61
97
  if heading == "Options":
62
98
  return ""
63
- return heading + ":\n"
99
+ return "[optparse.groups]" + escape(heading) + ":[/]\n"
64
100
 
65
101
  def format_usage(self, usage: str) -> str:
66
102
  """
67
103
  Ensure there is only one newline between usage and the first heading
68
104
  if there is no description.
69
105
  """
70
- msg = "\nUsage: {}\n".format(self.indent_lines(textwrap.dedent(usage), " "))
106
+ contents = self.indent_lines(textwrap.dedent(usage), " ")
107
+ msg = f"\n[optparse.groups]Usage:[/] {escape(contents)}\n"
71
108
  return msg
72
109
 
73
110
  def format_description(self, description: str | None) -> str:
74
111
  # leave full control over description to us
75
112
  if description:
76
113
  if hasattr(self.parser, "main"):
77
- label = "Commands"
114
+ label = "[optparse.groups]Commands:[/]"
78
115
  else:
79
- label = "Description"
116
+ label = "[optparse.groups]Description:[/]"
117
+
80
118
  # some doc strings have initial newlines, some don't
81
119
  description = description.lstrip("\n")
82
120
  # some doc strings have final newlines and spaces, some don't
83
121
  description = description.rstrip()
84
122
  # dedent, then reindent
85
123
  description = self.indent_lines(textwrap.dedent(description), " ")
86
- description = f"{label}:\n{description}\n"
124
+ description = f"{label}\n{description}\n"
87
125
  return description
88
126
  else:
89
127
  return ""
@@ -91,10 +129,17 @@ class PrettyHelpFormatter(optparse.IndentedHelpFormatter):
91
129
  def format_epilog(self, epilog: str | None) -> str:
92
130
  # leave full control over epilog to us
93
131
  if epilog:
94
- return epilog
132
+ return escape(epilog)
95
133
  else:
96
134
  return ""
97
135
 
136
+ def expand_default(self, option: optparse.Option) -> str:
137
+ """Overridden HelpFormatter.expand_default() which colorizes flags."""
138
+ help = escape(super().expand_default(option))
139
+ for regex, style in self.highlights.items():
140
+ help = re.sub(regex, rf"[optparse.{style}] \1[/]", help)
141
+ return help
142
+
98
143
  def indent_lines(self, text: str, indent: str) -> str:
99
144
  new_lines = [indent + line for line in text.split("\n")]
100
145
  return "\n".join(new_lines)
@@ -185,27 +230,29 @@ class ConfigOptionParser(CustomOptionParser):
185
230
  override_order = ["global", self.name, ":env:"]
186
231
 
187
232
  # Pool the options into different groups
188
- section_items: dict[str, list[tuple[str, Any]]] = {
189
- name: [] for name in override_order
233
+ # Use a dict because we need to implement the fallthrough logic after PR 12201
234
+ # was merged which removed the fallthrough logic for options
235
+ section_items_dict: dict[str, dict[str, Any]] = {
236
+ name: {} for name in override_order
190
237
  }
191
238
 
192
- for _, value in self.config.items(): # noqa: PERF102
239
+ for _, value in self.config.items():
193
240
  for section_key, val in value.items():
194
- # ignore empty values
195
- if not val:
196
- logger.debug(
197
- "Ignoring configuration key '%s' as its value is empty.",
198
- section_key,
199
- )
200
- continue
201
241
 
202
242
  section, key = section_key.split(".", 1)
203
243
  if section in override_order:
204
- section_items[section].append((key, val))
244
+ section_items_dict[section][key] = val
205
245
 
206
- # Yield each group in their override order
207
- for section in override_order:
208
- yield from section_items[section]
246
+ # Now that we a dict of items per section, convert to list of tuples
247
+ # Make sure we completely remove empty values again
248
+ section_items = {
249
+ name: [(k, v) for k, v in section_items_dict[name].items() if v]
250
+ for name in override_order
251
+ }
252
+
253
+ # Yield each group in their override order
254
+ for section in override_order:
255
+ yield from section_items[section]
209
256
 
210
257
  def _update_defaults(self, defaults: dict[str, Any]) -> dict[str, Any]:
211
258
  """Updates the given defaults with values from the config files and
@@ -296,3 +343,16 @@ class ConfigOptionParser(CustomOptionParser):
296
343
  def error(self, msg: str) -> NoReturn:
297
344
  self.print_usage(sys.stderr)
298
345
  self.exit(UNKNOWN_ERROR, f"{msg}\n")
346
+
347
+ def print_help(self, file: Any = None) -> None:
348
+ # This is unfortunate but necessary since arguments may have not been
349
+ # parsed yet at this point, so detect --no-color manually.
350
+ no_color = (
351
+ "--no-color" in sys.argv
352
+ or bool(strtobool(os.environ.get("PIP_NO_COLOR", "no") or "no"))
353
+ or "NO_COLOR" in os.environ
354
+ )
355
+ console = PipConsole(
356
+ theme=Theme(PrettyHelpFormatter.styles), no_color=no_color, file=file
357
+ )
358
+ console.print(self.format_help().rstrip(), highlight=False)