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