1"""
2shared options and groups
3
4The principle here is to define options once, but *not* instantiate them
5globally. One reason being that options with action='append' can carry state
6between parses. pip parses general options twice internally, and shouldn't
7pass on state. To be consistent, all options will follow this design.
8"""
9
10# The following comment should be removed at some point in the future.
11# mypy: strict-optional=False
12from __future__ import annotations
13
14import logging
15import os
16import pathlib
17import textwrap
18from functools import partial
19from optparse import SUPPRESS_HELP, Option, OptionGroup, OptionParser, Values
20from textwrap import dedent
21from typing import Any, Callable
22
23from pip._vendor.packaging.utils import canonicalize_name
24
25from pip._internal.cli.parser import ConfigOptionParser
26from pip._internal.exceptions import CommandError
27from pip._internal.locations import USER_CACHE_DIR, get_src_prefix
28from pip._internal.models.format_control import FormatControl
29from pip._internal.models.index import PyPI
30from pip._internal.models.release_control import ReleaseControl
31from pip._internal.models.target_python import TargetPython
32from pip._internal.utils.datetime import parse_iso_datetime
33from pip._internal.utils.hashes import STRONG_HASHES
34from pip._internal.utils.misc import strtobool
35
36logger = logging.getLogger(__name__)
37
38
39def raise_option_error(parser: OptionParser, option: Option, msg: str) -> None:
40 """
41 Raise an option parsing error using parser.error().
42
43 Args:
44 parser: an OptionParser instance.
45 option: an Option instance.
46 msg: the error text.
47 """
48 msg = f"{option} error: {msg}"
49 msg = textwrap.fill(" ".join(msg.split()))
50 parser.error(msg)
51
52
53def make_option_group(group: dict[str, Any], parser: ConfigOptionParser) -> OptionGroup:
54 """
55 Return an OptionGroup object
56 group -- assumed to be dict with 'name' and 'options' keys
57 parser -- an optparse Parser
58 """
59 option_group = OptionGroup(parser, group["name"])
60 for option in group["options"]:
61 option_group.add_option(option())
62 return option_group
63
64
65def check_dist_restriction(options: Values, check_target: bool = False) -> None:
66 """Function for determining if custom platform options are allowed.
67
68 :param options: The OptionParser options.
69 :param check_target: Whether or not to check if --target is being used.
70 """
71 dist_restriction_set = any(
72 [
73 options.python_version,
74 options.platforms,
75 options.abis,
76 options.implementation,
77 ]
78 )
79
80 binary_only = FormatControl(set(), {":all:"})
81 sdist_dependencies_allowed = (
82 options.format_control != binary_only and not options.ignore_dependencies
83 )
84
85 # Installations or downloads using dist restrictions must not combine
86 # source distributions and dist-specific wheels, as they are not
87 # guaranteed to be locally compatible.
88 if dist_restriction_set and sdist_dependencies_allowed:
89 raise CommandError(
90 "When restricting platform and interpreter constraints using "
91 "--python-version, --platform, --abi, or --implementation, "
92 "either --no-deps must be set, or --only-binary=:all: must be "
93 "set and --no-binary must not be set (or must be set to "
94 ":none:)."
95 )
96
97 if check_target:
98 if not options.dry_run and dist_restriction_set and not options.target_dir:
99 raise CommandError(
100 "Can not use any platform or abi specific options unless "
101 "installing via '--target' or using '--dry-run'"
102 )
103
104
105def 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
128def _path_option_check(option: Option, opt: str, value: str) -> str:
129 return os.path.expanduser(value)
130
131
132def _package_name_option_check(option: Option, opt: str, value: str) -> str:
133 return canonicalize_name(value)
134
135
136class PipOption(Option):
137 TYPES = Option.TYPES + ("path", "package_name")
138 TYPE_CHECKER = Option.TYPE_CHECKER.copy()
139 TYPE_CHECKER["package_name"] = _package_name_option_check
140 TYPE_CHECKER["path"] = _path_option_check
141
142
143###########
144# options #
145###########
146
147help_: Callable[..., Option] = partial(
148 Option,
149 "-h",
150 "--help",
151 dest="help",
152 action="help",
153 help="Show help.",
154)
155
156debug_mode: Callable[..., Option] = partial(
157 Option,
158 "--debug",
159 dest="debug_mode",
160 action="store_true",
161 default=False,
162 help=(
163 "Let unhandled exceptions propagate outside the main subroutine, "
164 "instead of logging them to stderr."
165 ),
166)
167
168isolated_mode: Callable[..., Option] = partial(
169 Option,
170 "--isolated",
171 dest="isolated_mode",
172 action="store_true",
173 default=False,
174 help=(
175 "Run pip in an isolated mode, ignoring environment variables and user "
176 "configuration."
177 ),
178)
179
180require_virtualenv: Callable[..., Option] = partial(
181 Option,
182 "--require-virtualenv",
183 "--require-venv",
184 dest="require_venv",
185 action="store_true",
186 default=False,
187 help=(
188 "Allow pip to only run in a virtual environment; exit with an error otherwise."
189 ),
190)
191
192override_externally_managed: Callable[..., Option] = partial(
193 Option,
194 "--break-system-packages",
195 dest="override_externally_managed",
196 action="store_true",
197 help="Allow pip to modify an EXTERNALLY-MANAGED Python installation",
198)
199
200python: Callable[..., Option] = partial(
201 Option,
202 "--python",
203 dest="python",
204 help="Run pip with the specified Python interpreter.",
205)
206
207verbose: Callable[..., Option] = partial(
208 Option,
209 "-v",
210 "--verbose",
211 dest="verbose",
212 action="count",
213 default=0,
214 help="Give more output. Option is additive, and can be used up to 3 times.",
215)
216
217no_color: Callable[..., Option] = partial(
218 Option,
219 "--no-color",
220 dest="no_color",
221 action="store_true",
222 default=False,
223 help="Suppress colored output.",
224)
225
226version: Callable[..., Option] = partial(
227 Option,
228 "-V",
229 "--version",
230 dest="version",
231 action="store_true",
232 help="Show version and exit.",
233)
234
235quiet: Callable[..., Option] = partial(
236 Option,
237 "-q",
238 "--quiet",
239 dest="quiet",
240 action="count",
241 default=0,
242 help=(
243 "Give less output. Option is additive, and can be used up to 3"
244 " times (corresponding to WARNING, ERROR, and CRITICAL logging"
245 " levels)."
246 ),
247)
248
249progress_bar: Callable[..., Option] = partial(
250 Option,
251 "--progress-bar",
252 dest="progress_bar",
253 type="choice",
254 choices=["auto", "on", "off", "raw"],
255 default="auto",
256 help=(
257 "Specify whether the progress bar should be used. In 'auto'"
258 " mode, --quiet will suppress all progress bars."
259 " [auto, on, off, raw] (default: auto)"
260 ),
261)
262
263log: Callable[..., Option] = partial(
264 PipOption,
265 "--log",
266 "--log-file",
267 "--local-log",
268 dest="log",
269 metavar="path",
270 type="path",
271 help="Path to a verbose appending log.",
272)
273
274no_input: Callable[..., Option] = partial(
275 Option,
276 # Don't ask for input
277 "--no-input",
278 dest="no_input",
279 action="store_true",
280 default=False,
281 help="Disable prompting for input.",
282)
283
284keyring_provider: Callable[..., Option] = partial(
285 Option,
286 "--keyring-provider",
287 dest="keyring_provider",
288 choices=["auto", "disabled", "import", "subprocess"],
289 default="auto",
290 help=(
291 "Enable the credential lookup via the keyring library if user input is allowed."
292 " Specify which mechanism to use [auto, disabled, import, subprocess]."
293 " (default: %default)"
294 ),
295)
296
297proxy: Callable[..., Option] = partial(
298 Option,
299 "--proxy",
300 dest="proxy",
301 type="str",
302 default="",
303 help="Specify a proxy in the form scheme://[user:passwd@]proxy.server:port.",
304)
305
306retries: Callable[..., Option] = partial(
307 Option,
308 "--retries",
309 dest="retries",
310 type="int",
311 default=5,
312 help="Maximum attempts to establish a new HTTP connection. (default: %default)",
313)
314
315resume_retries: Callable[..., Option] = partial(
316 Option,
317 "--resume-retries",
318 dest="resume_retries",
319 type="int",
320 default=5,
321 help="Maximum attempts to resume or restart an incomplete download. "
322 "(default: %default)",
323)
324
325timeout: Callable[..., Option] = partial(
326 Option,
327 "--timeout",
328 "--default-timeout",
329 metavar="sec",
330 dest="timeout",
331 type="float",
332 default=15,
333 help="Set the socket timeout (default %default seconds).",
334)
335
336
337def exists_action() -> Option:
338 return Option(
339 # Option when path already exist
340 "--exists-action",
341 dest="exists_action",
342 type="choice",
343 choices=["s", "i", "w", "b", "a"],
344 default=[],
345 action="append",
346 metavar="action",
347 help="Default action when a path already exists: "
348 "(s)witch, (i)gnore, (w)ipe, (b)ackup, (a)bort.",
349 )
350
351
352cert: Callable[..., Option] = partial(
353 PipOption,
354 "--cert",
355 dest="cert",
356 type="path",
357 metavar="path",
358 help=(
359 "Path to PEM-encoded CA certificate bundle. "
360 "If provided, overrides the default. "
361 "See 'SSL Certificate Verification' in pip documentation "
362 "for more information."
363 ),
364)
365
366client_cert: Callable[..., Option] = partial(
367 PipOption,
368 "--client-cert",
369 dest="client_cert",
370 type="path",
371 default=None,
372 metavar="path",
373 help="Path to SSL client certificate, a single file containing the "
374 "private key and the certificate in PEM format.",
375)
376
377index_url: Callable[..., Option] = partial(
378 Option,
379 "-i",
380 "--index-url",
381 "--pypi-url",
382 dest="index_url",
383 metavar="URL",
384 default=PyPI.simple_url,
385 help="Base URL of the Python Package Index (default %default). "
386 "This should point to a repository compliant with PEP 503 "
387 "(the simple repository API) or a local directory laid out "
388 "in the same format.",
389)
390
391
392def extra_index_url() -> Option:
393 return Option(
394 "--extra-index-url",
395 dest="extra_index_urls",
396 metavar="URL",
397 action="append",
398 default=[],
399 help="Extra URLs of package indexes to use in addition to "
400 "--index-url. Should follow the same rules as "
401 "--index-url.",
402 )
403
404
405no_index: Callable[..., Option] = partial(
406 Option,
407 "--no-index",
408 dest="no_index",
409 action="store_true",
410 default=False,
411 help="Ignore package index (only looking at --find-links URLs instead).",
412)
413
414
415def find_links() -> Option:
416 return Option(
417 "-f",
418 "--find-links",
419 dest="find_links",
420 action="append",
421 default=[],
422 metavar="url",
423 help="If a URL or path to an html file, then parse for links to "
424 "archives such as sdist (.tar.gz) or wheel (.whl) files. "
425 "If a local path or file:// URL that's a directory, "
426 "then look for archives in the directory listing. "
427 "Links to VCS project URLs are not supported.",
428 )
429
430
431def _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
462def 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
479def trusted_host() -> Option:
480 return Option(
481 "--trusted-host",
482 dest="trusted_hosts",
483 action="append",
484 metavar="HOSTNAME",
485 default=[],
486 help="Mark this host or host:port pair as trusted, even though it "
487 "does not have valid or any HTTPS.",
488 )
489
490
491def constraints() -> Option:
492 return Option(
493 "-c",
494 "--constraint",
495 dest="constraints",
496 action="append",
497 default=[],
498 metavar="file",
499 help="Constrain versions using the given constraints file. "
500 "This option can be used multiple times.",
501 )
502
503
504def 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
519def requirements() -> Option:
520 return Option(
521 "-r",
522 "--requirement",
523 dest="requirements",
524 action="append",
525 default=[],
526 metavar="file",
527 help="Install from the given requirements file. "
528 "This option can be used multiple times.",
529 )
530
531
532def 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
544def editable() -> Option:
545 return Option(
546 "-e",
547 "--editable",
548 dest="editables",
549 action="append",
550 default=[],
551 metavar="path/url",
552 help=(
553 "Install a project in editable mode (i.e. setuptools "
554 '"develop mode") from a local project path or a VCS url.'
555 ),
556 )
557
558
559def _handle_src(option: Option, opt_str: str, value: str, parser: OptionParser) -> None:
560 value = os.path.abspath(value)
561 setattr(parser.values, option.dest, value)
562
563
564src: Callable[..., Option] = partial(
565 PipOption,
566 "--src",
567 "--source",
568 "--source-dir",
569 "--source-directory",
570 dest="src_dir",
571 type="path",
572 metavar="dir",
573 default=get_src_prefix(),
574 action="callback",
575 callback=_handle_src,
576 help="Directory to check out editable projects into. "
577 'The default in a virtualenv is "<venv path>/src". '
578 'The default for global installs is "<current dir>/src".',
579)
580
581
582def _get_format_control(values: Values, option: Option) -> Any:
583 """Get a format_control object."""
584 return getattr(values, option.dest)
585
586
587def _handle_no_binary(
588 option: Option, opt_str: str, value: str, parser: OptionParser
589) -> None:
590 existing = _get_format_control(parser.values, option)
591 FormatControl.handle_mutual_excludes(
592 value,
593 existing.no_binary,
594 existing.only_binary,
595 )
596
597
598def _handle_only_binary(
599 option: Option, opt_str: str, value: str, parser: OptionParser
600) -> None:
601 existing = _get_format_control(parser.values, option)
602 FormatControl.handle_mutual_excludes(
603 value,
604 existing.only_binary,
605 existing.no_binary,
606 )
607
608
609def no_binary() -> Option:
610 format_control = FormatControl(set(), set())
611 return Option(
612 "--no-binary",
613 dest="format_control",
614 action="callback",
615 callback=_handle_no_binary,
616 type="str",
617 default=format_control,
618 help="Do not use binary packages. Can be supplied multiple times, and "
619 'each time adds to the existing value. Accepts either ":all:" to '
620 'disable all binary packages, ":none:" to empty the set (notice '
621 "the colons), or one or more package names with commas between "
622 "them (no colons). Note that some packages are tricky to compile "
623 "and may fail to install when this option is used on them.",
624 )
625
626
627def only_binary() -> Option:
628 format_control = FormatControl(set(), set())
629 return Option(
630 "--only-binary",
631 dest="format_control",
632 action="callback",
633 callback=_handle_only_binary,
634 type="str",
635 default=format_control,
636 help="Do not use source packages. Can be supplied multiple times, and "
637 'each time adds to the existing value. Accepts either ":all:" to '
638 'disable all source packages, ":none:" to empty the set, or one '
639 "or more package names with commas between them. Packages "
640 "without binary distributions will fail to install when this "
641 "option is used on them.",
642 )
643
644
645def _get_release_control(values: Values, option: Option) -> Any:
646 """Get a release_control object."""
647 return getattr(values, option.dest)
648
649
650def _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
662def _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
674def 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
692def 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
709def 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
725platforms: Callable[..., Option] = partial(
726 Option,
727 "--platform",
728 dest="platforms",
729 metavar="platform",
730 action="append",
731 default=None,
732 help=(
733 "Only use wheels compatible with <platform>. Defaults to the "
734 "platform of the running system. Use this option multiple times to "
735 "specify multiple platforms supported by the target interpreter."
736 ),
737)
738
739
740# This was made a separate function for unit-testing purposes.
741def _convert_python_version(value: str) -> tuple[tuple[int, ...], str | None]:
742 """
743 Convert a version string like "3", "37", or "3.7.3" into a tuple of ints.
744
745 :return: A 2-tuple (version_info, error_msg), where `error_msg` is
746 non-None if and only if there was a parsing error.
747 """
748 if not value:
749 # The empty string is the same as not providing a value.
750 return (None, None)
751
752 parts = value.split(".")
753 if len(parts) > 3:
754 return ((), "at most three version parts are allowed")
755
756 if len(parts) == 1:
757 # Then we are in the case of "3" or "37".
758 value = parts[0]
759 if len(value) > 1:
760 parts = [value[0], value[1:]]
761
762 try:
763 version_info = tuple(int(part) for part in parts)
764 except ValueError:
765 return ((), "each version part must be an integer")
766
767 return (version_info, None)
768
769
770def _handle_python_version(
771 option: Option, opt_str: str, value: str, parser: OptionParser
772) -> None:
773 """
774 Handle a provided --python-version value.
775 """
776 version_info, error_msg = _convert_python_version(value)
777 if error_msg is not None:
778 msg = f"invalid --python-version value: {value!r}: {error_msg}"
779 raise_option_error(parser, option=option, msg=msg)
780
781 parser.values.python_version = version_info
782
783
784python_version: Callable[..., Option] = partial(
785 Option,
786 "--python-version",
787 dest="python_version",
788 metavar="python_version",
789 action="callback",
790 callback=_handle_python_version,
791 type="str",
792 default=None,
793 help=dedent(
794 """\
795 The Python interpreter version to use for wheel and "Requires-Python"
796 compatibility checks. Defaults to a version derived from the running
797 interpreter. The version can be specified using up to three dot-separated
798 integers (e.g. "3" for 3.0.0, "3.7" for 3.7.0, or "3.7.3"). A major-minor
799 version can also be given as a string without dots (e.g. "37" for 3.7.0).
800 """
801 ),
802)
803
804
805implementation: Callable[..., Option] = partial(
806 Option,
807 "--implementation",
808 dest="implementation",
809 metavar="implementation",
810 default=None,
811 help=(
812 "Only use wheels compatible with Python "
813 "implementation <implementation>, e.g. 'pp', 'jy', 'cp', "
814 " or 'ip'. If not specified, then the current "
815 "interpreter implementation is used. Use 'py' to force "
816 "implementation-agnostic wheels."
817 ),
818)
819
820
821abis: Callable[..., Option] = partial(
822 Option,
823 "--abi",
824 dest="abis",
825 metavar="abi",
826 action="append",
827 default=None,
828 help=(
829 "Only use wheels compatible with Python abi <abi>, e.g. 'pypy_41'. "
830 "If not specified, then the current interpreter abi tag is used. "
831 "Use this option multiple times to specify multiple abis supported "
832 "by the target interpreter. Generally you will need to specify "
833 "--implementation, --platform, and --python-version when using this "
834 "option."
835 ),
836)
837
838
839def add_target_python_options(cmd_opts: OptionGroup) -> None:
840 cmd_opts.add_option(platforms())
841 cmd_opts.add_option(python_version())
842 cmd_opts.add_option(implementation())
843 cmd_opts.add_option(abis())
844
845
846def make_target_python(options: Values) -> TargetPython:
847 target_python = TargetPython(
848 platforms=options.platforms,
849 py_version_info=options.python_version,
850 abis=options.abis,
851 implementation=options.implementation,
852 )
853
854 return target_python
855
856
857def prefer_binary() -> Option:
858 return Option(
859 "--prefer-binary",
860 dest="prefer_binary",
861 action="store_true",
862 default=False,
863 help=(
864 "Prefer binary packages over source packages, even if the "
865 "source packages are newer."
866 ),
867 )
868
869
870cache_dir: Callable[..., Option] = partial(
871 PipOption,
872 "--cache-dir",
873 dest="cache_dir",
874 default=USER_CACHE_DIR,
875 metavar="dir",
876 type="path",
877 help="Store the cache data in <dir>.",
878)
879
880
881def _handle_no_cache_dir(
882 option: Option, opt: str, value: str, parser: OptionParser
883) -> None:
884 """
885 Process a value provided for the --no-cache-dir option.
886
887 This is an optparse.Option callback for the --no-cache-dir option.
888 """
889 # The value argument will be None if --no-cache-dir is passed via the
890 # command-line, since the option doesn't accept arguments. However,
891 # the value can be non-None if the option is triggered e.g. by an
892 # environment variable, like PIP_NO_CACHE_DIR=true.
893 if value is not None:
894 # Then parse the string value to get argument error-checking.
895 try:
896 strtobool(value)
897 except ValueError as exc:
898 raise_option_error(parser, option=option, msg=str(exc))
899
900 # Originally, setting PIP_NO_CACHE_DIR to a value that strtobool()
901 # converted to 0 (like "false" or "no") caused cache_dir to be disabled
902 # rather than enabled (logic would say the latter). Thus, we disable
903 # the cache directory not just on values that parse to True, but (for
904 # backwards compatibility reasons) also on values that parse to False.
905 # In other words, always set it to False if the option is provided in
906 # some (valid) form.
907 parser.values.cache_dir = False
908
909
910no_cache: Callable[..., Option] = partial(
911 Option,
912 "--no-cache-dir",
913 dest="cache_dir",
914 action="callback",
915 callback=_handle_no_cache_dir,
916 help="Disable the cache.",
917)
918
919no_deps: Callable[..., Option] = partial(
920 Option,
921 "--no-deps",
922 "--no-dependencies",
923 dest="ignore_dependencies",
924 action="store_true",
925 default=False,
926 help="Don't install package dependencies.",
927)
928
929
930def _handle_dependency_group(
931 option: Option, opt: str, value: str, parser: OptionParser
932) -> None:
933 """
934 Process a value provided for the --group option.
935
936 Splits on the rightmost ":", and validates that the path (if present) ends
937 in `pyproject.toml`. Defaults the path to `pyproject.toml` when one is not given.
938
939 `:` cannot appear in dependency group names, so this is a safe and simple parse.
940
941 This is an optparse.Option callback for the dependency_groups option.
942 """
943 path, sep, groupname = value.rpartition(":")
944 if not sep:
945 path = "pyproject.toml"
946 else:
947 # check for 'pyproject.toml' filenames using pathlib
948 if pathlib.PurePath(path).name != "pyproject.toml":
949 msg = "group paths use 'pyproject.toml' filenames"
950 raise_option_error(parser, option=option, msg=msg)
951
952 parser.values.dependency_groups.append((path, groupname))
953
954
955dependency_groups: Callable[..., Option] = partial(
956 Option,
957 "--group",
958 dest="dependency_groups",
959 default=[],
960 type=str,
961 action="callback",
962 callback=_handle_dependency_group,
963 metavar="[path:]group",
964 help='Install a named dependency-group from a "pyproject.toml" file. '
965 'If a path is given, the name of the file must be "pyproject.toml". '
966 'Defaults to using "pyproject.toml" in the current directory.',
967)
968
969ignore_requires_python: Callable[..., Option] = partial(
970 Option,
971 "--ignore-requires-python",
972 dest="ignore_requires_python",
973 action="store_true",
974 help="Ignore the Requires-Python information.",
975)
976
977
978no_build_isolation: Callable[..., Option] = partial(
979 Option,
980 "--no-build-isolation",
981 dest="build_isolation",
982 action="store_false",
983 default=True,
984 help="Disable isolation when building a modern source distribution. "
985 "Build dependencies specified by PEP 518 must be already installed "
986 "if this option is used.",
987)
988
989check_build_deps: Callable[..., Option] = partial(
990 Option,
991 "--check-build-dependencies",
992 dest="check_build_deps",
993 action="store_true",
994 default=False,
995 help="Check the build dependencies.",
996)
997
998
999use_pep517: Any = partial(
1000 Option,
1001 "--use-pep517",
1002 dest="use_pep517",
1003 action="store_true",
1004 default=True,
1005 help=SUPPRESS_HELP,
1006)
1007
1008
1009def _handle_config_settings(
1010 option: Option, opt_str: str, value: str, parser: OptionParser
1011) -> None:
1012 key, sep, val = value.partition("=")
1013 if sep != "=":
1014 parser.error(f"Arguments to {opt_str} must be of the form KEY=VAL")
1015 dest = getattr(parser.values, option.dest)
1016 if dest is None:
1017 dest = {}
1018 setattr(parser.values, option.dest, dest)
1019 if key in dest:
1020 if isinstance(dest[key], list):
1021 dest[key].append(val)
1022 else:
1023 dest[key] = [dest[key], val]
1024 else:
1025 dest[key] = val
1026
1027
1028config_settings: Callable[..., Option] = partial(
1029 Option,
1030 "-C",
1031 "--config-settings",
1032 dest="config_settings",
1033 type=str,
1034 action="callback",
1035 callback=_handle_config_settings,
1036 metavar="settings",
1037 help="Configuration settings to be passed to the build backend. "
1038 "Settings take the form KEY=VALUE. Use multiple --config-settings options "
1039 "to pass multiple keys to the backend.",
1040)
1041
1042no_clean: Callable[..., Option] = partial(
1043 Option,
1044 "--no-clean",
1045 action="store_true",
1046 default=False,
1047 help="Don't clean up build directories.",
1048)
1049
1050pre: Callable[..., Option] = partial(
1051 Option,
1052 "--pre",
1053 action="store_true",
1054 default=False,
1055 help="Include pre-release and development versions. By default, "
1056 "pip only finds stable versions.",
1057)
1058
1059json: Callable[..., Option] = partial(
1060 Option,
1061 "--json",
1062 action="store_true",
1063 default=False,
1064 help="Output data in a machine-readable JSON format.",
1065)
1066
1067disable_pip_version_check: Callable[..., Option] = partial(
1068 Option,
1069 "--disable-pip-version-check",
1070 dest="disable_pip_version_check",
1071 action="store_true",
1072 default=False,
1073 help="Don't periodically check PyPI to determine whether a new version "
1074 "of pip is available for download. Implied with --no-index.",
1075)
1076
1077root_user_action: Callable[..., Option] = partial(
1078 Option,
1079 "--root-user-action",
1080 dest="root_user_action",
1081 default="warn",
1082 choices=["warn", "ignore"],
1083 help="Action if pip is run as a root user [warn, ignore] (default: warn)",
1084)
1085
1086
1087def _handle_merge_hash(
1088 option: Option, opt_str: str, value: str, parser: OptionParser
1089) -> None:
1090 """Given a value spelled "algo:digest", append the digest to a list
1091 pointed to in a dict by the algo name."""
1092 if not parser.values.hashes:
1093 parser.values.hashes = {}
1094 try:
1095 algo, digest = value.split(":", 1)
1096 except ValueError:
1097 parser.error(
1098 f"Arguments to {opt_str} must be a hash name "
1099 "followed by a value, like --hash=sha256:"
1100 "abcde..."
1101 )
1102 if algo not in STRONG_HASHES:
1103 parser.error(
1104 "Allowed hash algorithms for {} are {}.".format(
1105 opt_str, ", ".join(STRONG_HASHES)
1106 )
1107 )
1108 parser.values.hashes.setdefault(algo, []).append(digest)
1109
1110
1111hash: Callable[..., Option] = partial(
1112 Option,
1113 "--hash",
1114 # Hash values eventually end up in InstallRequirement.hashes due to
1115 # __dict__ copying in process_line().
1116 dest="hashes",
1117 action="callback",
1118 callback=_handle_merge_hash,
1119 type="string",
1120 help="Verify that the package's archive matches this "
1121 "hash before installing. Example: --hash=sha256:abcdef...",
1122)
1123
1124
1125require_hashes: Callable[..., Option] = partial(
1126 Option,
1127 "--require-hashes",
1128 dest="require_hashes",
1129 action="store_true",
1130 default=False,
1131 help="Require a hash to check each requirement against, for "
1132 "repeatable installs. This option is implied when any package in a "
1133 "requirements file has a --hash option.",
1134)
1135
1136
1137list_path: Callable[..., Option] = partial(
1138 PipOption,
1139 "--path",
1140 dest="path",
1141 type="path",
1142 action="append",
1143 help="Restrict to the specified installation path for listing "
1144 "packages (can be used multiple times).",
1145)
1146
1147
1148def check_list_path_option(options: Values) -> None:
1149 if options.path and (options.user or options.local):
1150 raise CommandError("Cannot combine '--path' with '--user' or '--local'")
1151
1152
1153list_exclude: Callable[..., Option] = partial(
1154 PipOption,
1155 "--exclude",
1156 dest="excludes",
1157 action="append",
1158 metavar="package",
1159 type="package_name",
1160 help="Exclude specified package from the output",
1161)
1162
1163
1164no_python_version_warning: Callable[..., Option] = partial(
1165 Option,
1166 "--no-python-version-warning",
1167 dest="no_python_version_warning",
1168 action="store_true",
1169 default=False,
1170 help=SUPPRESS_HELP, # No-op, a hold-over from the Python 2->3 transition.
1171)
1172
1173
1174# Features that are now always on. A warning is printed if they are used.
1175ALWAYS_ENABLED_FEATURES = [
1176 "truststore", # always on since 24.2
1177 "no-binary-enable-wheel-cache", # always on since 23.1
1178]
1179
1180use_new_feature: Callable[..., Option] = partial(
1181 Option,
1182 "--use-feature",
1183 dest="features_enabled",
1184 metavar="feature",
1185 action="append",
1186 default=[],
1187 choices=[
1188 "fast-deps",
1189 "build-constraint",
1190 "inprocess-build-deps",
1191 ]
1192 + ALWAYS_ENABLED_FEATURES,
1193 help="Enable new functionality, that may be backward incompatible.",
1194)
1195
1196use_deprecated_feature: Callable[..., Option] = partial(
1197 Option,
1198 "--use-deprecated",
1199 dest="deprecated_features_enabled",
1200 metavar="feature",
1201 action="append",
1202 default=[],
1203 choices=[
1204 "legacy-resolver",
1205 "legacy-certs",
1206 ],
1207 help=("Enable deprecated functionality, that will be removed in the future."),
1208)
1209
1210##########
1211# groups #
1212##########
1213
1214general_group: dict[str, Any] = {
1215 "name": "General Options",
1216 "options": [
1217 help_,
1218 debug_mode,
1219 isolated_mode,
1220 require_virtualenv,
1221 python,
1222 verbose,
1223 version,
1224 quiet,
1225 log,
1226 no_input,
1227 keyring_provider,
1228 proxy,
1229 retries,
1230 timeout,
1231 exists_action,
1232 trusted_host,
1233 cert,
1234 client_cert,
1235 cache_dir,
1236 no_cache,
1237 disable_pip_version_check,
1238 no_color,
1239 no_python_version_warning,
1240 use_new_feature,
1241 use_deprecated_feature,
1242 resume_retries,
1243 ],
1244}
1245
1246index_group: dict[str, Any] = {
1247 "name": "Package Index Options",
1248 "options": [
1249 index_url,
1250 extra_index_url,
1251 no_index,
1252 find_links,
1253 uploaded_prior_to,
1254 ],
1255}
1256
1257package_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,
1266 ],
1267}