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