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 datetime import datetime, timedelta, timezone
20from functools import partial
21from optparse import SUPPRESS_HELP, Option, OptionGroup, OptionParser, Values
22from textwrap import dedent
23from typing import Any, Callable
24
25from pip._vendor.packaging.utils import canonicalize_name
26
27from pip._internal.cli.parser import ConfigOptionParser
28from pip._internal.exceptions import CommandError
29from pip._internal.locations import USER_CACHE_DIR, get_src_prefix
30from pip._internal.models.format_control import FormatControl
31from pip._internal.models.index import PyPI
32from pip._internal.models.release_control import ReleaseControl
33from pip._internal.models.target_python import TargetPython
34from pip._internal.utils import pylock as pylock_utils
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_dist_restriction(options: Values, check_target: bool = False) -> None:
69 """Function for determining if custom platform options are allowed.
70
71 :param options: The OptionParser options.
72 :param check_target: Whether or not to check if --target is being used.
73 """
74 dist_restriction_set = any(
75 [
76 options.python_version,
77 options.platforms,
78 options.abis,
79 options.implementation,
80 ]
81 )
82
83 binary_only = FormatControl(set(), {":all:"})
84 sdist_dependencies_allowed = (
85 options.format_control != binary_only and not options.ignore_dependencies
86 )
87
88 # Installations or downloads using dist restrictions must not combine
89 # source distributions and dist-specific wheels, as they are not
90 # guaranteed to be locally compatible.
91 if dist_restriction_set and sdist_dependencies_allowed:
92 raise CommandError(
93 "When restricting platform and interpreter constraints using "
94 "--python-version, --platform, --abi, or --implementation, "
95 "either --no-deps must be set, or --only-binary=:all: must be "
96 "set and --no-binary must not be set (or must be set to "
97 ":none:)."
98 )
99
100 if check_target:
101 if not options.dry_run and dist_restriction_set and not options.target_dir:
102 raise CommandError(
103 "Can not use any platform or abi specific options unless "
104 "installing via '--target' or using '--dry-run'"
105 )
106
107 for filename in options.requirements:
108 if dist_restriction_set and pylock_utils.is_valid_pylock_filename(filename):
109 raise CommandError(
110 "Patform and interpreter constraints using "
111 "--python-version, --platform, --abi, or --implementation, "
112 f"are not supported when selecting requirements from {filename!r}"
113 )
114
115
116def check_build_constraints(options: Values) -> None:
117 """Function for validating build constraints options.
118
119 :param options: The OptionParser options.
120 """
121 if hasattr(options, "build_constraints") and options.build_constraints:
122 if not options.build_isolation:
123 raise CommandError(
124 "--build-constraint cannot be used with --no-build-isolation."
125 )
126
127 # Import here to avoid circular imports
128 from pip._internal.network.session import PipSession
129 from pip._internal.req.req_file import get_file_content
130
131 # Eagerly check build constraints file contents
132 # is valid so that we don't fail in when trying
133 # to check constraints in isolated build process
134 with PipSession() as session:
135 for constraint_file in options.build_constraints:
136 get_file_content(constraint_file, session)
137
138
139def _path_option_check(option: Option, opt: str, value: str) -> str:
140 return os.path.expanduser(value)
141
142
143def _package_name_option_check(option: Option, opt: str, value: str) -> str:
144 return canonicalize_name(value)
145
146
147class PipOption(Option):
148 TYPES = Option.TYPES + ("path", "package_name")
149 TYPE_CHECKER = Option.TYPE_CHECKER.copy()
150 TYPE_CHECKER["package_name"] = _package_name_option_check
151 TYPE_CHECKER["path"] = _path_option_check
152
153
154###########
155# options #
156###########
157
158help_: Callable[..., Option] = partial(
159 Option,
160 "-h",
161 "--help",
162 dest="help",
163 action="help",
164 help="Show help.",
165)
166
167debug_mode: Callable[..., Option] = partial(
168 Option,
169 "--debug",
170 dest="debug_mode",
171 action="store_true",
172 default=False,
173 help=(
174 "Let unhandled exceptions propagate outside the main subroutine, "
175 "instead of logging them to stderr."
176 ),
177)
178
179isolated_mode: Callable[..., Option] = partial(
180 Option,
181 "--isolated",
182 dest="isolated_mode",
183 action="store_true",
184 default=False,
185 help=(
186 "Run pip in an isolated mode, ignoring environment variables and user "
187 "configuration."
188 ),
189)
190
191require_virtualenv: Callable[..., Option] = partial(
192 Option,
193 "--require-virtualenv",
194 "--require-venv",
195 dest="require_venv",
196 action="store_true",
197 default=False,
198 help=(
199 "Allow pip to only run in a virtual environment; exit with an error otherwise."
200 ),
201)
202
203override_externally_managed: Callable[..., Option] = partial(
204 Option,
205 "--break-system-packages",
206 dest="override_externally_managed",
207 action="store_true",
208 help="Allow pip to modify an EXTERNALLY-MANAGED Python installation",
209)
210
211python: Callable[..., Option] = partial(
212 Option,
213 "--python",
214 dest="python",
215 help="Run pip with the specified Python interpreter.",
216)
217
218verbose: Callable[..., Option] = partial(
219 Option,
220 "-v",
221 "--verbose",
222 dest="verbose",
223 action="count",
224 default=0,
225 help="Give more output. Option is additive, and can be used up to 3 times.",
226)
227
228no_color: Callable[..., Option] = partial(
229 Option,
230 "--no-color",
231 dest="no_color",
232 action="store_true",
233 default=False,
234 help="Suppress colored output.",
235)
236
237version: Callable[..., Option] = partial(
238 Option,
239 "-V",
240 "--version",
241 dest="version",
242 action="store_true",
243 help="Show version and exit.",
244)
245
246quiet: Callable[..., Option] = partial(
247 Option,
248 "-q",
249 "--quiet",
250 dest="quiet",
251 action="count",
252 default=0,
253 help=(
254 "Give less output. Option is additive, and can be used up to 3"
255 " times (corresponding to WARNING, ERROR, and CRITICAL logging"
256 " levels)."
257 ),
258)
259
260progress_bar: Callable[..., Option] = partial(
261 Option,
262 "--progress-bar",
263 dest="progress_bar",
264 type="choice",
265 choices=["auto", "on", "off", "raw"],
266 default="auto",
267 help=(
268 "Specify whether the progress bar should be used. In 'auto'"
269 " mode, --quiet will suppress all progress bars."
270 " [auto, on, off, raw] (default: auto)"
271 ),
272)
273
274log: Callable[..., Option] = partial(
275 PipOption,
276 "--log",
277 "--log-file",
278 "--local-log",
279 dest="log",
280 metavar="path",
281 type="path",
282 help="Path to a verbose appending log.",
283)
284
285no_input: Callable[..., Option] = partial(
286 Option,
287 # Don't ask for input
288 "--no-input",
289 dest="no_input",
290 action="store_true",
291 default=False,
292 help="Disable prompting for input.",
293)
294
295keyring_provider: Callable[..., Option] = partial(
296 Option,
297 "--keyring-provider",
298 dest="keyring_provider",
299 choices=["auto", "disabled", "import", "subprocess"],
300 default="auto",
301 help=(
302 "Enable the credential lookup via the keyring library if user input is allowed."
303 " Specify which mechanism to use [auto, disabled, import, subprocess]."
304 " (default: %default)"
305 ),
306)
307
308proxy: Callable[..., Option] = partial(
309 Option,
310 "--proxy",
311 dest="proxy",
312 type="str",
313 default="",
314 help="Specify a proxy in the form scheme://[user:passwd@]proxy.server:port.",
315)
316
317retries: Callable[..., Option] = partial(
318 Option,
319 "--retries",
320 dest="retries",
321 type="int",
322 default=5,
323 help="Maximum attempts to establish a new HTTP connection. (default: %default)",
324)
325
326resume_retries: Callable[..., Option] = partial(
327 Option,
328 "--resume-retries",
329 dest="resume_retries",
330 type="int",
331 default=5,
332 help="Maximum attempts to resume or restart an incomplete download. "
333 "(default: %default)",
334)
335
336timeout: Callable[..., Option] = partial(
337 Option,
338 "--timeout",
339 "--default-timeout",
340 metavar="sec",
341 dest="timeout",
342 type="float",
343 default=15,
344 help="Set the socket timeout (default %default seconds).",
345)
346
347
348def exists_action() -> Option:
349 return Option(
350 # Option when path already exist
351 "--exists-action",
352 dest="exists_action",
353 type="choice",
354 choices=["s", "i", "w", "b", "a"],
355 default=[],
356 action="append",
357 metavar="action",
358 help="Default action when a path already exists: "
359 "(s)witch, (i)gnore, (w)ipe, (b)ackup, (a)bort.",
360 )
361
362
363cert: Callable[..., Option] = partial(
364 PipOption,
365 "--cert",
366 dest="cert",
367 type="path",
368 metavar="path",
369 help=(
370 "Path to PEM-encoded CA certificate bundle. "
371 "If provided, overrides the default. "
372 "See 'SSL Certificate Verification' in pip documentation "
373 "for more information."
374 ),
375)
376
377client_cert: Callable[..., Option] = partial(
378 PipOption,
379 "--client-cert",
380 dest="client_cert",
381 type="path",
382 default=None,
383 metavar="path",
384 help="Path to SSL client certificate, a single file containing the "
385 "private key and the certificate in PEM format.",
386)
387
388index_url: Callable[..., Option] = partial(
389 Option,
390 "-i",
391 "--index-url",
392 "--pypi-url",
393 dest="index_url",
394 metavar="URL",
395 default=PyPI.simple_url,
396 help="Base URL of the Python Package Index (default %default). "
397 "This should point to a repository compliant with PEP 503 "
398 "(the simple repository API) or a local directory laid out "
399 "in the same format.",
400)
401
402
403def extra_index_url() -> Option:
404 return Option(
405 "--extra-index-url",
406 dest="extra_index_urls",
407 metavar="URL",
408 action="append",
409 default=[],
410 help="Extra URLs of package indexes to use in addition to "
411 "--index-url. Should follow the same rules as "
412 "--index-url.",
413 )
414
415
416no_index: Callable[..., Option] = partial(
417 Option,
418 "--no-index",
419 dest="no_index",
420 action="store_true",
421 default=False,
422 help="Ignore package index (only looking at --find-links URLs instead).",
423)
424
425
426def find_links() -> Option:
427 return Option(
428 "-f",
429 "--find-links",
430 dest="find_links",
431 action="append",
432 default=[],
433 metavar="url",
434 help="If a URL or path to an html file, then parse for links to "
435 "archives such as sdist (.tar.gz) or wheel (.whl) files. "
436 "If a local path or file:// URL that's a directory, "
437 "then look for archives in the directory listing. "
438 "Links to VCS project URLs are not supported.",
439 )
440
441
442def _handle_uploaded_prior_to(
443 option: Option, opt: str, value: str, parser: OptionParser
444) -> None:
445 """
446 This is an optparse.Option callback for the --uploaded-prior-to option.
447
448 Accepts either an ISO 8601 datetime string (e.g., '2023-01-01T00:00:00Z')
449 or a strict subset of ISO 8601 durations: PnD where n is a number of days
450 (e.g., 'P7D' for 7 days ago).
451
452 Note: This option only works with indexes that provide upload-time metadata
453 as specified in the simple repository API:
454 https://packaging.python.org/en/latest/specifications/simple-repository-api/
455 """
456 if value is None:
457 return None
458
459 # Try ISO 8601 duration in PnD format. The leading 'P' disambiguates
460 # from absolute datetimes. Only whole days are supported; the format may
461 # be extended to more of the ISO 8601 duration syntax in the future if
462 # a real need is presented.
463 match = re.match(r"^P(\d+)D$", value, re.ASCII)
464 if match:
465 days = int(match.group(1))
466 parser.values.uploaded_prior_to = datetime.now(timezone.utc) - timedelta(
467 days=days
468 )
469 return
470
471 try:
472 uploaded_prior_to = parse_iso_datetime(value)
473 # Use local timezone if no offset is given in the ISO string.
474 if uploaded_prior_to.tzinfo is None:
475 uploaded_prior_to = uploaded_prior_to.astimezone()
476 parser.values.uploaded_prior_to = uploaded_prior_to
477 except ValueError as exc:
478 msg = (
479 f"invalid value: {value!r}: {exc}. "
480 f"Expected an ISO 8601 datetime string "
481 f"(e.g., '2023-01-01' or '2023-01-01T00:00:00Z') "
482 f"or a duration in days (e.g., 'P3D')"
483 )
484 raise_option_error(parser, option=option, msg=msg)
485
486
487def uploaded_prior_to() -> Option:
488 return Option(
489 "--uploaded-prior-to",
490 dest="uploaded_prior_to",
491 metavar="datetime_or_duration",
492 action="callback",
493 callback=_handle_uploaded_prior_to,
494 type="str",
495 help=(
496 "Only consider packages uploaded prior to the given value. "
497 "Accepts an ISO 8601 datetime (e.g., '2023-01-01T00:00:00Z', "
498 "uses local timezone if none specified) or a duration in days "
499 "(e.g., 'P3D' for packages uploaded at least 3 days ago). "
500 "Only effective when installing from indexes that provide "
501 "upload-time metadata."
502 ),
503 )
504
505
506def trusted_host() -> Option:
507 return Option(
508 "--trusted-host",
509 dest="trusted_hosts",
510 action="append",
511 metavar="HOSTNAME",
512 default=[],
513 help="Mark this host or host:port pair as trusted, even though it "
514 "does not have valid or any HTTPS.",
515 )
516
517
518def constraints() -> Option:
519 return Option(
520 "-c",
521 "--constraint",
522 dest="constraints",
523 action="append",
524 default=[],
525 metavar="file",
526 help="Constrain versions using the given constraints file. "
527 "This option can be used multiple times.",
528 )
529
530
531def build_constraints() -> Option:
532 return Option(
533 "--build-constraint",
534 dest="build_constraints",
535 action="append",
536 type="str",
537 default=[],
538 metavar="file",
539 help=(
540 "Constrain build dependencies using the given constraints file. "
541 "This option can be used multiple times."
542 ),
543 )
544
545
546def requirements() -> Option:
547 return Option(
548 "-r",
549 "--requirement",
550 dest="requirements",
551 action="append",
552 default=[],
553 metavar="file",
554 help=(
555 "Install from the given requirements file. "
556 "The file or URL can be in pip's requirements.txt format, "
557 "or pylock.toml format. pylock.toml support is experimental. "
558 "This option can be used multiple times."
559 ),
560 )
561
562
563def requirements_from_scripts() -> Option:
564 return Option(
565 "--requirements-from-script",
566 action="append",
567 default=[],
568 dest="requirements_from_scripts",
569 metavar="file",
570 help="Install dependencies of the given script file"
571 "as defined by PEP 723 inline metadata. ",
572 )
573
574
575def editable() -> Option:
576 return Option(
577 "-e",
578 "--editable",
579 dest="editables",
580 action="append",
581 default=[],
582 metavar="path/url",
583 help=(
584 "Install a project in editable mode (i.e. setuptools "
585 '"develop mode") from a local project path or a VCS url.'
586 ),
587 )
588
589
590def _handle_src(option: Option, opt_str: str, value: str, parser: OptionParser) -> None:
591 value = os.path.abspath(value)
592 setattr(parser.values, option.dest, value)
593
594
595src: Callable[..., Option] = partial(
596 PipOption,
597 "--src",
598 "--source",
599 "--source-dir",
600 "--source-directory",
601 dest="src_dir",
602 type="path",
603 metavar="dir",
604 default=get_src_prefix(),
605 action="callback",
606 callback=_handle_src,
607 help="Directory to check out editable projects into. "
608 'The default in a virtualenv is "<venv path>/src". '
609 'The default for global installs is "<current dir>/src".',
610)
611
612
613def _get_format_control(values: Values, option: Option) -> Any:
614 """Get a format_control object."""
615 return getattr(values, option.dest)
616
617
618def _handle_no_binary(
619 option: Option, opt_str: str, value: str, parser: OptionParser
620) -> None:
621 existing = _get_format_control(parser.values, option)
622 FormatControl.handle_mutual_excludes(
623 value,
624 existing.no_binary,
625 existing.only_binary,
626 )
627
628
629def _handle_only_binary(
630 option: Option, opt_str: str, value: str, parser: OptionParser
631) -> None:
632 existing = _get_format_control(parser.values, option)
633 FormatControl.handle_mutual_excludes(
634 value,
635 existing.only_binary,
636 existing.no_binary,
637 )
638
639
640def no_binary() -> Option:
641 format_control = FormatControl(set(), set())
642 return Option(
643 "--no-binary",
644 dest="format_control",
645 action="callback",
646 callback=_handle_no_binary,
647 type="str",
648 default=format_control,
649 help="Do not use binary packages. Can be supplied multiple times, and "
650 'each time adds to the existing value. Accepts either ":all:" to '
651 'disable all binary packages, ":none:" to empty the set (notice '
652 "the colons), or one or more package names with commas between "
653 "them (no colons). Note that some packages are tricky to compile "
654 "and may fail to install when this option is used on them.",
655 )
656
657
658def only_binary() -> Option:
659 format_control = FormatControl(set(), set())
660 return Option(
661 "--only-binary",
662 dest="format_control",
663 action="callback",
664 callback=_handle_only_binary,
665 type="str",
666 default=format_control,
667 help="Do not use source packages. Can be supplied multiple times, and "
668 'each time adds to the existing value. Accepts either ":all:" to '
669 'disable all source packages, ":none:" to empty the set, or one '
670 "or more package names with commas between them. Packages "
671 "without binary distributions will fail to install when this "
672 "option is used on them.",
673 )
674
675
676def _get_release_control(values: Values, option: Option) -> Any:
677 """Get a release_control object."""
678 return getattr(values, option.dest)
679
680
681def _handle_all_releases(
682 option: Option, opt_str: str, value: str, parser: OptionParser
683) -> None:
684 existing = _get_release_control(parser.values, option)
685 existing.handle_mutual_excludes(
686 value,
687 existing.all_releases,
688 existing.only_final,
689 "all_releases",
690 )
691
692
693def _handle_only_final(
694 option: Option, opt_str: str, value: str, parser: OptionParser
695) -> None:
696 existing = _get_release_control(parser.values, option)
697 existing.handle_mutual_excludes(
698 value,
699 existing.only_final,
700 existing.all_releases,
701 "only_final",
702 )
703
704
705def all_releases() -> Option:
706 release_control = ReleaseControl(set(), set())
707 return Option(
708 "--all-releases",
709 dest="release_control",
710 action="callback",
711 callback=_handle_all_releases,
712 type="str",
713 default=release_control,
714 help="Allow all release types (including pre-releases) for a package. "
715 "Can be supplied multiple times, and each time adds to the existing "
716 'value. Accepts either ":all:" to allow pre-releases for all '
717 'packages, ":none:" to empty the set (notice the colons), or one or '
718 "more package names with commas between them (no colons). Cannot be "
719 "used with --pre.",
720 )
721
722
723def only_final() -> Option:
724 release_control = ReleaseControl(set(), set())
725 return Option(
726 "--only-final",
727 dest="release_control",
728 action="callback",
729 callback=_handle_only_final,
730 type="str",
731 default=release_control,
732 help="Only allow final releases (no pre-releases) for a package. Can be "
733 "supplied multiple times, and each time adds to the existing value. "
734 'Accepts either ":all:" to disable pre-releases for all packages, '
735 '":none:" to empty the set, or one or more package names with commas '
736 "between them. Cannot be used with --pre.",
737 )
738
739
740def check_release_control_exclusive(options: Values) -> None:
741 """
742 Raise an error if --pre is used with --all-releases or --only-final,
743 and transform --pre into --all-releases :all: if used alone.
744 """
745 if not hasattr(options, "pre") or not options.pre:
746 return
747
748 release_control = options.release_control
749 if release_control.all_releases or release_control.only_final:
750 raise CommandError("--pre cannot be used with --all-releases or --only-final.")
751
752 # Transform --pre into --all-releases :all:
753 release_control.all_releases.add(":all:")
754
755
756platforms: Callable[..., Option] = partial(
757 Option,
758 "--platform",
759 dest="platforms",
760 metavar="platform",
761 action="append",
762 default=None,
763 help=(
764 "Only use wheels compatible with <platform>. Defaults to the "
765 "platform of the running system. Use this option multiple times to "
766 "specify multiple platforms supported by the target interpreter."
767 ),
768)
769
770
771# This was made a separate function for unit-testing purposes.
772def _convert_python_version(value: str) -> tuple[tuple[int, ...], str | None]:
773 """
774 Convert a version string like "3", "37", or "3.7.3" into a tuple of ints.
775
776 :return: A 2-tuple (version_info, error_msg), where `error_msg` is
777 non-None if and only if there was a parsing error.
778 """
779 if not value:
780 # The empty string is the same as not providing a value.
781 return (None, None)
782
783 parts = value.split(".")
784 if len(parts) > 3:
785 return ((), "at most three version parts are allowed")
786
787 if len(parts) == 1:
788 # Then we are in the case of "3" or "37".
789 value = parts[0]
790 if len(value) > 1:
791 parts = [value[0], value[1:]]
792
793 try:
794 version_info = tuple(int(part) for part in parts)
795 except ValueError:
796 return ((), "each version part must be an integer")
797
798 return (version_info, None)
799
800
801def _handle_python_version(
802 option: Option, opt_str: str, value: str, parser: OptionParser
803) -> None:
804 """
805 Handle a provided --python-version value.
806 """
807 version_info, error_msg = _convert_python_version(value)
808 if error_msg is not None:
809 msg = f"invalid --python-version value: {value!r}: {error_msg}"
810 raise_option_error(parser, option=option, msg=msg)
811
812 parser.values.python_version = version_info
813
814
815python_version: Callable[..., Option] = partial(
816 Option,
817 "--python-version",
818 dest="python_version",
819 metavar="python_version",
820 action="callback",
821 callback=_handle_python_version,
822 type="str",
823 default=None,
824 help=dedent(
825 """\
826 The Python interpreter version to use for wheel and "Requires-Python"
827 compatibility checks. Defaults to a version derived from the running
828 interpreter. The version can be specified using up to three dot-separated
829 integers (e.g. "3" for 3.0.0, "3.7" for 3.7.0, or "3.7.3"). A major-minor
830 version can also be given as a string without dots (e.g. "37" for 3.7.0).
831 """
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}