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) or a duration in days "
543 "(e.g., 'P3D' for packages uploaded at least 3 days ago). "
544 "Only effective when using indexes that provide "
545 "upload-time metadata."
546 ),
547 )
548
549
550def trusted_host() -> Option:
551 return Option(
552 "--trusted-host",
553 dest="trusted_hosts",
554 action="append",
555 metavar="HOSTNAME",
556 default=[],
557 help="Mark this host or host:port pair as trusted, even though it "
558 "does not have valid or any HTTPS.",
559 )
560
561
562def constraints() -> Option:
563 return Option(
564 "-c",
565 "--constraint",
566 dest="constraints",
567 action="append",
568 default=[],
569 metavar="file",
570 help="Constrain versions using the given constraints file. "
571 "This option can be used multiple times.",
572 )
573
574
575def build_constraints() -> Option:
576 return Option(
577 "--build-constraint",
578 dest="build_constraints",
579 action="append",
580 type="str",
581 default=[],
582 metavar="file",
583 help=(
584 "Constrain build dependencies using the given constraints file. "
585 "This option can be used multiple times."
586 ),
587 )
588
589
590def requirements() -> Option:
591 return Option(
592 "-r",
593 "--requirement",
594 dest="requirements",
595 action="append",
596 default=[],
597 metavar="file",
598 help=(
599 "Install from the given requirements file. "
600 "The file or URL can be in pip's requirements.txt format, "
601 "or pylock.toml format. pylock.toml support is experimental. "
602 "This option can be used multiple times."
603 ),
604 )
605
606
607def requirements_from_scripts() -> Option:
608 return Option(
609 "--requirements-from-script",
610 action="append",
611 default=[],
612 dest="requirements_from_scripts",
613 metavar="file",
614 help="Install dependencies of the given script file "
615 "as defined by PEP 723 inline metadata. ",
616 )
617
618
619def editable() -> Option:
620 return Option(
621 "-e",
622 "--editable",
623 dest="editables",
624 action="append",
625 default=[],
626 metavar="path/url",
627 help=(
628 "Install a project in editable mode (i.e. setuptools "
629 '"develop mode") from a local project path or a VCS url.'
630 ),
631 )
632
633
634def _handle_src(option: Option, opt_str: str, value: str, parser: OptionParser) -> None:
635 value = os.path.abspath(value)
636 setattr(parser.values, option.dest, value)
637
638
639src: Callable[..., Option] = partial(
640 PipOption,
641 "--src",
642 "--source",
643 "--source-dir",
644 "--source-directory",
645 dest="src_dir",
646 type="path",
647 metavar="dir",
648 default=get_src_prefix(),
649 action="callback",
650 callback=_handle_src,
651 help="Directory to check out editable projects into. "
652 'The default in a virtualenv is "<venv path>/src". '
653 'The default for global installs is "<current dir>/src".',
654)
655
656
657def _get_format_control(values: Values, option: Option) -> Any:
658 """Get a format_control object."""
659 return getattr(values, option.dest)
660
661
662def _handle_no_binary(
663 option: Option, opt_str: str, value: str, parser: OptionParser
664) -> None:
665 existing = _get_format_control(parser.values, option)
666 FormatControl.handle_mutual_excludes(
667 value,
668 existing.no_binary,
669 existing.only_binary,
670 )
671
672
673def _handle_only_binary(
674 option: Option, opt_str: str, value: str, parser: OptionParser
675) -> None:
676 existing = _get_format_control(parser.values, option)
677 FormatControl.handle_mutual_excludes(
678 value,
679 existing.only_binary,
680 existing.no_binary,
681 )
682
683
684def no_binary() -> Option:
685 format_control = FormatControl(set(), set())
686 return Option(
687 "--no-binary",
688 dest="format_control",
689 action="callback",
690 callback=_handle_no_binary,
691 type="str",
692 default=format_control,
693 help="Do not download binary packages. Cached binary packages may still "
694 "be used. Can be supplied multiple times, and each time adds to "
695 "the existing value. Accepts either ':all:' to disable all binary "
696 "packages, ':none:' to empty the set (notice the colons), or one "
697 "or more package names with commas between them (no colons). "
698 "Note that some packages are tricky to compile and may fail to "
699 "install when this option is used on them.",
700 )
701
702
703def only_binary() -> Option:
704 format_control = FormatControl(set(), set())
705 return Option(
706 "--only-binary",
707 dest="format_control",
708 action="callback",
709 callback=_handle_only_binary,
710 type="str",
711 default=format_control,
712 help="Do not use source packages. Can be supplied multiple times, and "
713 'each time adds to the existing value. Accepts either ":all:" to '
714 'disable all source packages, ":none:" to empty the set, or one '
715 "or more package names with commas between them. Packages "
716 "without binary distributions will fail to install when this "
717 "option is used on them.",
718 )
719
720
721def _get_release_control(values: Values, option: Option) -> Any:
722 """Get a release_control object."""
723 return getattr(values, option.dest)
724
725
726def _handle_all_releases(
727 option: Option, opt_str: str, value: str, parser: OptionParser
728) -> None:
729 existing = _get_release_control(parser.values, option)
730 existing.handle_mutual_excludes(
731 value,
732 existing.all_releases,
733 existing.only_final,
734 "all_releases",
735 )
736
737
738def _handle_only_final(
739 option: Option, opt_str: str, value: str, parser: OptionParser
740) -> None:
741 existing = _get_release_control(parser.values, option)
742 existing.handle_mutual_excludes(
743 value,
744 existing.only_final,
745 existing.all_releases,
746 "only_final",
747 )
748
749
750def all_releases() -> Option:
751 release_control = ReleaseControl(set(), set())
752 return Option(
753 "--all-releases",
754 dest="release_control",
755 action="callback",
756 callback=_handle_all_releases,
757 type="str",
758 default=release_control,
759 help="Allow all release types (including pre-releases) for a package. "
760 "Can be supplied multiple times, and each time adds to the existing "
761 'value. Accepts either ":all:" to allow pre-releases for all '
762 'packages, ":none:" to empty the set (notice the colons), or one or '
763 "more package names with commas between them (no colons). Cannot be "
764 "used with --pre.",
765 )
766
767
768def only_final() -> Option:
769 release_control = ReleaseControl(set(), set())
770 return Option(
771 "--only-final",
772 dest="release_control",
773 action="callback",
774 callback=_handle_only_final,
775 type="str",
776 default=release_control,
777 help="Only allow final releases (no pre-releases) for a package. Can be "
778 "supplied multiple times, and each time adds to the existing value. "
779 'Accepts either ":all:" to disable pre-releases for all packages, '
780 '":none:" to empty the set, or one or more package names with commas '
781 "between them. Cannot be used with --pre.",
782 )
783
784
785def check_release_control_exclusive(options: Values) -> None:
786 """
787 Raise an error if --pre is used with --all-releases or --only-final,
788 and transform --pre into --all-releases :all: if used alone.
789 """
790 if not hasattr(options, "pre") or not options.pre:
791 return
792
793 release_control = options.release_control
794 if release_control.all_releases or release_control.only_final:
795 raise CommandError("--pre cannot be used with --all-releases or --only-final.")
796
797 # Transform --pre into --all-releases :all:
798 release_control.all_releases.add(":all:")
799
800
801platforms: Callable[..., Option] = partial(
802 Option,
803 "--platform",
804 dest="platforms",
805 metavar="platform",
806 action="append",
807 default=None,
808 help=(
809 "Only use wheels compatible with <platform>. Defaults to the "
810 "platform of the running system. Use this option multiple times to "
811 "specify multiple platforms supported by the target interpreter."
812 ),
813)
814
815
816# This was made a separate function for unit-testing purposes.
817def _convert_python_version(value: str) -> tuple[tuple[int, ...], str | None]:
818 """
819 Convert a version string like "3", "37", or "3.7.3" into a tuple of ints.
820
821 :return: A 2-tuple (version_info, error_msg), where `error_msg` is
822 non-None if and only if there was a parsing error.
823 """
824 if not value:
825 # The empty string is the same as not providing a value.
826 return (None, None)
827
828 parts = value.split(".")
829 if len(parts) > 3:
830 return ((), "at most three version parts are allowed")
831
832 if len(parts) == 1:
833 # Then we are in the case of "3" or "37".
834 value = parts[0]
835 if len(value) > 1:
836 parts = [value[0], value[1:]]
837
838 try:
839 version_info = tuple(int(part) for part in parts)
840 except ValueError:
841 return ((), "each version part must be an integer")
842
843 return (version_info, None)
844
845
846def _handle_python_version(
847 option: Option, opt_str: str, value: str, parser: OptionParser
848) -> None:
849 """
850 Handle a provided --python-version value.
851 """
852 version_info, error_msg = _convert_python_version(value)
853 if error_msg is not None:
854 msg = f"invalid --python-version value: {value!r}: {error_msg}"
855 raise_option_error(parser, option=option, msg=msg)
856
857 parser.values.python_version = version_info
858
859
860python_version: Callable[..., Option] = partial(
861 Option,
862 "--python-version",
863 dest="python_version",
864 metavar="python_version",
865 action="callback",
866 callback=_handle_python_version,
867 type="str",
868 default=None,
869 help=dedent("""\
870 The Python interpreter version to use for wheel and "Requires-Python"
871 compatibility checks. Defaults to a version derived from the running
872 interpreter. The version can be specified using up to three dot-separated
873 integers (e.g. "3" for 3.0.0, "3.7" for 3.7.0, or "3.7.3"). A major-minor
874 version can also be given as a string without dots (e.g. "37" for 3.7.0).
875 """),
876)
877
878
879implementation: Callable[..., Option] = partial(
880 Option,
881 "--implementation",
882 dest="implementation",
883 metavar="implementation",
884 default=None,
885 help=(
886 "Only use wheels compatible with Python "
887 "implementation <implementation>, e.g. 'pp', 'jy', 'cp', "
888 " or 'ip'. If not specified, then the current "
889 "interpreter implementation is used. Use 'py' to force "
890 "implementation-agnostic wheels."
891 ),
892)
893
894
895abis: Callable[..., Option] = partial(
896 Option,
897 "--abi",
898 dest="abis",
899 metavar="abi",
900 action="append",
901 default=None,
902 help=(
903 "Only use wheels compatible with Python abi <abi>, e.g. 'pypy_41'. "
904 "If not specified, then the current interpreter abi tag is used. "
905 "Use this option multiple times to specify multiple abis supported "
906 "by the target interpreter. Generally you will need to specify "
907 "--implementation, --platform, and --python-version when using this "
908 "option."
909 ),
910)
911
912
913def add_target_python_options(cmd_opts: OptionGroup) -> None:
914 cmd_opts.add_option(platforms())
915 cmd_opts.add_option(python_version())
916 cmd_opts.add_option(implementation())
917 cmd_opts.add_option(abis())
918
919
920def make_target_python(options: Values) -> TargetPython:
921 target_python = TargetPython(
922 platforms=options.platforms,
923 py_version_info=options.python_version,
924 abis=options.abis,
925 implementation=options.implementation,
926 )
927
928 return target_python
929
930
931def prefer_binary() -> Option:
932 return Option(
933 "--prefer-binary",
934 dest="prefer_binary",
935 action="store_true",
936 default=False,
937 help=(
938 "Prefer binary packages over source packages, even if the "
939 "source packages are newer."
940 ),
941 )
942
943
944cache_dir: Callable[..., Option] = partial(
945 PipOption,
946 "--cache-dir",
947 dest="cache_dir",
948 default=USER_CACHE_DIR,
949 metavar="dir",
950 type="path",
951 help="Store the cache data in <dir>.",
952)
953
954
955def _handle_no_cache_dir(
956 option: Option, opt: str, value: str, parser: OptionParser
957) -> None:
958 """
959 Process a value provided for the --no-cache-dir option.
960
961 This is an optparse.Option callback for the --no-cache-dir option.
962 """
963 # The value argument will be None if --no-cache-dir is passed via the
964 # command-line, since the option doesn't accept arguments. However,
965 # the value can be non-None if the option is triggered e.g. by an
966 # environment variable, like PIP_NO_CACHE_DIR=true.
967 if value is not None:
968 # Then parse the string value to get argument error-checking.
969 try:
970 strtobool(value)
971 except ValueError as exc:
972 raise_option_error(parser, option=option, msg=str(exc))
973
974 # Originally, setting PIP_NO_CACHE_DIR to a value that strtobool()
975 # converted to 0 (like "false" or "no") caused cache_dir to be disabled
976 # rather than enabled (logic would say the latter). Thus, we disable
977 # the cache directory not just on values that parse to True, but (for
978 # backwards compatibility reasons) also on values that parse to False.
979 # In other words, always set it to False if the option is provided in
980 # some (valid) form.
981 parser.values.cache_dir = False
982
983
984no_cache: Callable[..., Option] = partial(
985 Option,
986 "--no-cache-dir",
987 dest="cache_dir",
988 action="callback",
989 callback=_handle_no_cache_dir,
990 help="Disable the cache.",
991)
992
993no_deps: Callable[..., Option] = partial(
994 Option,
995 "--no-deps",
996 "--no-dependencies",
997 dest="ignore_dependencies",
998 action="store_true",
999 default=False,
1000 help="Don't install package dependencies.",
1001)
1002
1003
1004def _handle_refresh_package(
1005 option: Option, opt_str: str, value: str, parser: OptionParser
1006) -> None:
1007 if value.startswith("-"):
1008 raise CommandError("--refresh-package option requires 1 argument.")
1009
1010 existing: set[str] = getattr(parser.values, option.dest)
1011
1012 new = value.split(",")
1013 while ":all:" in new:
1014 existing.clear()
1015 existing.add(":all:")
1016 del new[: new.index(":all:") + 1]
1017 if ":none:" not in new:
1018 return
1019
1020 for name in new:
1021 if name == ":none:":
1022 existing.clear()
1023 else:
1024 existing.add(canonicalize_name(name))
1025
1026
1027def refresh_package() -> Option:
1028 return Option(
1029 "--refresh-package",
1030 dest="refresh_package",
1031 action="callback",
1032 callback=_handle_refresh_package,
1033 type="str",
1034 default=set(),
1035 help="Refresh package index information for the given packages instead "
1036 "of using cached responses. Accepts ':all:' to apply "
1037 "to all packages, or a comma-separated list of package names.",
1038 )
1039
1040
1041only_deps: Callable[..., Option] = partial(
1042 Option,
1043 "--only-deps",
1044 "--only-dependencies",
1045 dest="only_dependencies",
1046 action="store_true",
1047 default=False,
1048 help=(
1049 "Take only the dependencies of the provided requirements into account, "
1050 "not the requirements themselves. Cannot be used in combination with "
1051 "--no-deps, --group, --requirement, or --requirements-from-script. "
1052 "No user-supplied requirements will be handled, even if they were "
1053 "dependencies of other user-supplied requirements."
1054 ),
1055)
1056
1057
1058def _handle_dependency_group(
1059 option: Option, opt: str, value: str, parser: OptionParser
1060) -> None:
1061 """
1062 Process a value provided for the --group option.
1063
1064 Splits on the rightmost ":", and validates that the path (if present) ends
1065 in `pyproject.toml`. Defaults the path to `pyproject.toml` when one is not given.
1066
1067 `:` cannot appear in dependency group names, so this is a safe and simple parse.
1068
1069 This is an optparse.Option callback for the dependency_groups option.
1070 """
1071 path, sep, groupname = value.rpartition(":")
1072 if not sep:
1073 path = "pyproject.toml"
1074 else:
1075 # check for 'pyproject.toml' filenames using pathlib
1076 if pathlib.PurePath(path).name != "pyproject.toml":
1077 msg = "group paths use 'pyproject.toml' filenames"
1078 raise_option_error(parser, option=option, msg=msg)
1079
1080 parser.values.dependency_groups.append((path, groupname))
1081
1082
1083dependency_groups: Callable[..., Option] = partial(
1084 Option,
1085 "--group",
1086 dest="dependency_groups",
1087 default=[],
1088 type=str,
1089 action="callback",
1090 callback=_handle_dependency_group,
1091 metavar="[path:]group",
1092 help='Install a named dependency-group from a "pyproject.toml" file. '
1093 'If a path is given, the name of the file must be "pyproject.toml". '
1094 'Defaults to using "pyproject.toml" in the current directory.',
1095)
1096
1097ignore_requires_python: Callable[..., Option] = partial(
1098 Option,
1099 "--ignore-requires-python",
1100 dest="ignore_requires_python",
1101 action="store_true",
1102 help="Ignore the Requires-Python information.",
1103)
1104
1105
1106no_build_isolation: Callable[..., Option] = partial(
1107 Option,
1108 "--no-build-isolation",
1109 dest="build_isolation",
1110 action="store_false",
1111 default=True,
1112 help="Disable isolation when building a modern source distribution. "
1113 "Build dependencies specified by PEP 518 must be already installed "
1114 "if this option is used.",
1115)
1116
1117check_build_deps: Callable[..., Option] = partial(
1118 Option,
1119 "--check-build-dependencies",
1120 dest="check_build_deps",
1121 action="store_true",
1122 default=False,
1123 help="Check the build dependencies.",
1124)
1125
1126
1127use_pep517: Any = partial(
1128 Option,
1129 "--use-pep517",
1130 dest="use_pep517",
1131 action="store_true",
1132 default=True,
1133 help=SUPPRESS_HELP,
1134)
1135
1136
1137def _handle_config_settings(
1138 option: Option, opt_str: str, value: str, parser: OptionParser
1139) -> None:
1140 key, sep, val = value.partition("=")
1141 if sep != "=":
1142 parser.error(f"Arguments to {opt_str} must be of the form KEY=VAL")
1143 dest = getattr(parser.values, option.dest)
1144 if dest is None:
1145 dest = {}
1146 setattr(parser.values, option.dest, dest)
1147 if key in dest:
1148 if isinstance(dest[key], list):
1149 dest[key].append(val)
1150 else:
1151 dest[key] = [dest[key], val]
1152 else:
1153 dest[key] = val
1154
1155
1156config_settings: Callable[..., Option] = partial(
1157 Option,
1158 "-C",
1159 "--config-settings",
1160 dest="config_settings",
1161 type=str,
1162 action="callback",
1163 callback=_handle_config_settings,
1164 metavar="settings",
1165 help="Configuration settings to be passed to the build backend. "
1166 "Settings take the form KEY=VALUE. Use multiple --config-settings options "
1167 "to pass multiple keys to the backend.",
1168)
1169
1170no_clean: Callable[..., Option] = partial(
1171 Option,
1172 "--no-clean",
1173 action="store_true",
1174 default=False,
1175 help="Don't clean up build directories.",
1176)
1177
1178pre: Callable[..., Option] = partial(
1179 Option,
1180 "--pre",
1181 action="store_true",
1182 default=False,
1183 help="Include pre-release and development versions. By default, "
1184 "pip only finds stable versions.",
1185)
1186
1187json: Callable[..., Option] = partial(
1188 Option,
1189 "--json",
1190 action="store_true",
1191 default=False,
1192 help="Output data in a machine-readable JSON format.",
1193)
1194
1195disable_pip_version_check: Callable[..., Option] = partial(
1196 Option,
1197 "--disable-pip-version-check",
1198 dest="disable_pip_version_check",
1199 action="store_true",
1200 default=False,
1201 help="Don't periodically check PyPI to determine whether a new version "
1202 "of pip is available for download. Implied with --no-index.",
1203)
1204
1205root_user_action: Callable[..., Option] = partial(
1206 Option,
1207 "--root-user-action",
1208 dest="root_user_action",
1209 default="warn",
1210 choices=["warn", "ignore"],
1211 help="Action if pip is run as a root user [warn, ignore] (default: warn)",
1212)
1213
1214
1215def _handle_merge_hash(
1216 option: Option, opt_str: str, value: str, parser: OptionParser
1217) -> None:
1218 """Given a value spelled "algo:digest", append the digest to a list
1219 pointed to in a dict by the algo name."""
1220 if not parser.values.hashes:
1221 parser.values.hashes = {}
1222 try:
1223 algo, digest = value.split(":", 1)
1224 except ValueError:
1225 parser.error(
1226 f"Arguments to {opt_str} must be a hash name "
1227 "followed by a value, like --hash=sha256:"
1228 "abcde..."
1229 )
1230 if algo not in STRONG_HASHES:
1231 parser.error(
1232 "Allowed hash algorithms for {} are {}.".format(
1233 opt_str, ", ".join(STRONG_HASHES)
1234 )
1235 )
1236 parser.values.hashes.setdefault(algo, []).append(digest)
1237
1238
1239hash: Callable[..., Option] = partial(
1240 Option,
1241 "--hash",
1242 # Hash values eventually end up in InstallRequirement.hashes due to
1243 # __dict__ copying in process_line().
1244 dest="hashes",
1245 action="callback",
1246 callback=_handle_merge_hash,
1247 type="string",
1248 help="Verify that the package's archive matches this "
1249 "hash before installing. Example: --hash=sha256:abcdef...",
1250)
1251
1252
1253require_hashes: Callable[..., Option] = partial(
1254 Option,
1255 "--require-hashes",
1256 dest="require_hashes",
1257 action="store_true",
1258 default=False,
1259 help="Require a hash to check each requirement against, for "
1260 "repeatable installs. This option is implied when any package in a "
1261 "requirements file has a --hash option.",
1262)
1263
1264
1265no_require_hashes: Callable[..., Option] = partial(
1266 Option,
1267 "--no-require-hashes",
1268 dest="no_require_hashes",
1269 action="store_true",
1270 default=False,
1271 help="Do not automatically enable --require-hashes "
1272 "when encountering a requirement with hashes.",
1273)
1274
1275
1276list_path: Callable[..., Option] = partial(
1277 PipOption,
1278 "--path",
1279 dest="path",
1280 type="path",
1281 action="append",
1282 help="Restrict to the specified installation path for listing "
1283 "packages (can be used multiple times).",
1284)
1285
1286
1287def check_list_path_option(options: Values) -> None:
1288 if options.path and (options.user or options.local):
1289 raise CommandError("Cannot combine '--path' with '--user' or '--local'")
1290
1291
1292list_exclude: Callable[..., Option] = partial(
1293 PipOption,
1294 "--exclude",
1295 dest="excludes",
1296 action="append",
1297 metavar="package",
1298 type="package_name",
1299 help="Exclude specified package from the output",
1300)
1301
1302
1303no_python_version_warning: Callable[..., Option] = partial(
1304 Option,
1305 "--no-python-version-warning",
1306 dest="no_python_version_warning",
1307 action="store_true",
1308 default=False,
1309 help=SUPPRESS_HELP, # No-op, a hold-over from the Python 2->3 transition.
1310)
1311
1312
1313# Features that are now always on. A warning is printed if they are used.
1314ALWAYS_ENABLED_FEATURES = [
1315 "truststore", # always on since 24.2
1316 "no-binary-enable-wheel-cache", # always on since 23.1
1317 "build-constraint", # always on since 26.2
1318]
1319
1320use_new_feature: Callable[..., Option] = partial(
1321 Option,
1322 "--use-feature",
1323 dest="features_enabled",
1324 metavar="feature",
1325 action="append",
1326 default=[],
1327 choices=[
1328 "fast-deps",
1329 "inprocess-build-deps",
1330 "venv-isolation",
1331 ]
1332 + ALWAYS_ENABLED_FEATURES,
1333 help="Enable new functionality, that may be backward incompatible.",
1334)
1335
1336use_deprecated_feature: Callable[..., Option] = partial(
1337 Option,
1338 "--use-deprecated",
1339 dest="deprecated_features_enabled",
1340 metavar="feature",
1341 action="append",
1342 default=[],
1343 choices=[
1344 "legacy-resolver",
1345 "legacy-certs",
1346 ],
1347 help=("Enable deprecated functionality, that will be removed in the future."),
1348)
1349
1350##########
1351# groups #
1352##########
1353
1354general_group: dict[str, Any] = {
1355 "name": "General Options",
1356 "options": [
1357 help_,
1358 debug_mode,
1359 isolated_mode,
1360 require_virtualenv,
1361 python,
1362 verbose,
1363 version,
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}