python (3.11.7)

(root)/
lib/
python3.11/
site-packages/
pip/
_internal/
cli/
cmdoptions.py
       1  """
       2  shared options and groups
       3  
       4  The principle here is to define options once, but *not* instantiate them
       5  globally. One reason being that options with action='append' can carry state
       6  between parses. pip parses general options twice internally, and shouldn't
       7  pass 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
      12  
      13  import importlib.util
      14  import logging
      15  import os
      16  import textwrap
      17  from functools import partial
      18  from optparse import SUPPRESS_HELP, Option, OptionGroup, OptionParser, Values
      19  from textwrap import dedent
      20  from typing import Any, Callable, Dict, Optional, Tuple
      21  
      22  from pip._vendor.packaging.utils import canonicalize_name
      23  
      24  from pip._internal.cli.parser import ConfigOptionParser
      25  from pip._internal.exceptions import CommandError
      26  from pip._internal.locations import USER_CACHE_DIR, get_src_prefix
      27  from pip._internal.models.format_control import FormatControl
      28  from pip._internal.models.index import PyPI
      29  from pip._internal.models.target_python import TargetPython
      30  from pip._internal.utils.hashes import STRONG_HASHES
      31  from pip._internal.utils.misc import strtobool
      32  
      33  logger = logging.getLogger(__name__)
      34  
      35  
      36  def raise_option_error(parser: OptionParser, option: Option, msg: str) -> None:
      37      """
      38      Raise an option parsing error using parser.error().
      39  
      40      Args:
      41        parser: an OptionParser instance.
      42        option: an Option instance.
      43        msg: the error text.
      44      """
      45      msg = f"{option} error: {msg}"
      46      msg = textwrap.fill(" ".join(msg.split()))
      47      parser.error(msg)
      48  
      49  
      50  def make_option_group(group: Dict[str, Any], parser: ConfigOptionParser) -> OptionGroup:
      51      """
      52      Return an OptionGroup object
      53      group  -- assumed to be dict with 'name' and 'options' keys
      54      parser -- an optparse Parser
      55      """
      56      option_group = OptionGroup(parser, group["name"])
      57      for option in group["options"]:
      58          option_group.add_option(option())
      59      return option_group
      60  
      61  
      62  def check_dist_restriction(options: Values, check_target: bool = False) -> None:
      63      """Function for determining if custom platform options are allowed.
      64  
      65      :param options: The OptionParser options.
      66      :param check_target: Whether or not to check if --target is being used.
      67      """
      68      dist_restriction_set = any(
      69          [
      70              options.python_version,
      71              options.platforms,
      72              options.abis,
      73              options.implementation,
      74          ]
      75      )
      76  
      77      binary_only = FormatControl(set(), {":all:"})
      78      sdist_dependencies_allowed = (
      79          options.format_control != binary_only and not options.ignore_dependencies
      80      )
      81  
      82      # Installations or downloads using dist restrictions must not combine
      83      # source distributions and dist-specific wheels, as they are not
      84      # guaranteed to be locally compatible.
      85      if dist_restriction_set and sdist_dependencies_allowed:
      86          raise CommandError(
      87              "When restricting platform and interpreter constraints using "
      88              "--python-version, --platform, --abi, or --implementation, "
      89              "either --no-deps must be set, or --only-binary=:all: must be "
      90              "set and --no-binary must not be set (or must be set to "
      91              ":none:)."
      92          )
      93  
      94      if check_target:
      95          if dist_restriction_set and not options.target_dir:
      96              raise CommandError(
      97                  "Can not use any platform or abi specific options unless "
      98                  "installing via '--target'"
      99              )
     100  
     101  
     102  def _path_option_check(option: Option, opt: str, value: str) -> str:
     103      return os.path.expanduser(value)
     104  
     105  
     106  def _package_name_option_check(option: Option, opt: str, value: str) -> str:
     107      return canonicalize_name(value)
     108  
     109  
     110  class ESC[4;38;5;81mPipOption(ESC[4;38;5;149mOption):
     111      TYPES = Option.TYPES + ("path", "package_name")
     112      TYPE_CHECKER = Option.TYPE_CHECKER.copy()
     113      TYPE_CHECKER["package_name"] = _package_name_option_check
     114      TYPE_CHECKER["path"] = _path_option_check
     115  
     116  
     117  ###########
     118  # options #
     119  ###########
     120  
     121  help_: Callable[..., Option] = partial(
     122      Option,
     123      "-h",
     124      "--help",
     125      dest="help",
     126      action="help",
     127      help="Show help.",
     128  )
     129  
     130  debug_mode: Callable[..., Option] = partial(
     131      Option,
     132      "--debug",
     133      dest="debug_mode",
     134      action="store_true",
     135      default=False,
     136      help=(
     137          "Let unhandled exceptions propagate outside the main subroutine, "
     138          "instead of logging them to stderr."
     139      ),
     140  )
     141  
     142  isolated_mode: Callable[..., Option] = partial(
     143      Option,
     144      "--isolated",
     145      dest="isolated_mode",
     146      action="store_true",
     147      default=False,
     148      help=(
     149          "Run pip in an isolated mode, ignoring environment variables and user "
     150          "configuration."
     151      ),
     152  )
     153  
     154  require_virtualenv: Callable[..., Option] = partial(
     155      Option,
     156      "--require-virtualenv",
     157      "--require-venv",
     158      dest="require_venv",
     159      action="store_true",
     160      default=False,
     161      help=(
     162          "Allow pip to only run in a virtual environment; "
     163          "exit with an error otherwise."
     164      ),
     165  )
     166  
     167  override_externally_managed: Callable[..., Option] = partial(
     168      Option,
     169      "--break-system-packages",
     170      dest="override_externally_managed",
     171      action="store_true",
     172      help="Allow pip to modify an EXTERNALLY-MANAGED Python installation",
     173  )
     174  
     175  python: Callable[..., Option] = partial(
     176      Option,
     177      "--python",
     178      dest="python",
     179      help="Run pip with the specified Python interpreter.",
     180  )
     181  
     182  verbose: Callable[..., Option] = partial(
     183      Option,
     184      "-v",
     185      "--verbose",
     186      dest="verbose",
     187      action="count",
     188      default=0,
     189      help="Give more output. Option is additive, and can be used up to 3 times.",
     190  )
     191  
     192  no_color: Callable[..., Option] = partial(
     193      Option,
     194      "--no-color",
     195      dest="no_color",
     196      action="store_true",
     197      default=False,
     198      help="Suppress colored output.",
     199  )
     200  
     201  version: Callable[..., Option] = partial(
     202      Option,
     203      "-V",
     204      "--version",
     205      dest="version",
     206      action="store_true",
     207      help="Show version and exit.",
     208  )
     209  
     210  quiet: Callable[..., Option] = partial(
     211      Option,
     212      "-q",
     213      "--quiet",
     214      dest="quiet",
     215      action="count",
     216      default=0,
     217      help=(
     218          "Give less output. Option is additive, and can be used up to 3"
     219          " times (corresponding to WARNING, ERROR, and CRITICAL logging"
     220          " levels)."
     221      ),
     222  )
     223  
     224  progress_bar: Callable[..., Option] = partial(
     225      Option,
     226      "--progress-bar",
     227      dest="progress_bar",
     228      type="choice",
     229      choices=["on", "off"],
     230      default="on",
     231      help="Specify whether the progress bar should be used [on, off] (default: on)",
     232  )
     233  
     234  log: Callable[..., Option] = partial(
     235      PipOption,
     236      "--log",
     237      "--log-file",
     238      "--local-log",
     239      dest="log",
     240      metavar="path",
     241      type="path",
     242      help="Path to a verbose appending log.",
     243  )
     244  
     245  no_input: Callable[..., Option] = partial(
     246      Option,
     247      # Don't ask for input
     248      "--no-input",
     249      dest="no_input",
     250      action="store_true",
     251      default=False,
     252      help="Disable prompting for input.",
     253  )
     254  
     255  keyring_provider: Callable[..., Option] = partial(
     256      Option,
     257      "--keyring-provider",
     258      dest="keyring_provider",
     259      choices=["auto", "disabled", "import", "subprocess"],
     260      default="auto",
     261      help=(
     262          "Enable the credential lookup via the keyring library if user input is allowed."
     263          " Specify which mechanism to use [disabled, import, subprocess]."
     264          " (default: disabled)"
     265      ),
     266  )
     267  
     268  proxy: Callable[..., Option] = partial(
     269      Option,
     270      "--proxy",
     271      dest="proxy",
     272      type="str",
     273      default="",
     274      help="Specify a proxy in the form scheme://[user:passwd@]proxy.server:port.",
     275  )
     276  
     277  retries: Callable[..., Option] = partial(
     278      Option,
     279      "--retries",
     280      dest="retries",
     281      type="int",
     282      default=5,
     283      help="Maximum number of retries each connection should attempt "
     284      "(default %default times).",
     285  )
     286  
     287  timeout: Callable[..., Option] = partial(
     288      Option,
     289      "--timeout",
     290      "--default-timeout",
     291      metavar="sec",
     292      dest="timeout",
     293      type="float",
     294      default=15,
     295      help="Set the socket timeout (default %default seconds).",
     296  )
     297  
     298  
     299  def exists_action() -> Option:
     300      return Option(
     301          # Option when path already exist
     302          "--exists-action",
     303          dest="exists_action",
     304          type="choice",
     305          choices=["s", "i", "w", "b", "a"],
     306          default=[],
     307          action="append",
     308          metavar="action",
     309          help="Default action when a path already exists: "
     310          "(s)witch, (i)gnore, (w)ipe, (b)ackup, (a)bort.",
     311      )
     312  
     313  
     314  cert: Callable[..., Option] = partial(
     315      PipOption,
     316      "--cert",
     317      dest="cert",
     318      type="path",
     319      metavar="path",
     320      help=(
     321          "Path to PEM-encoded CA certificate bundle. "
     322          "If provided, overrides the default. "
     323          "See 'SSL Certificate Verification' in pip documentation "
     324          "for more information."
     325      ),
     326  )
     327  
     328  client_cert: Callable[..., Option] = partial(
     329      PipOption,
     330      "--client-cert",
     331      dest="client_cert",
     332      type="path",
     333      default=None,
     334      metavar="path",
     335      help="Path to SSL client certificate, a single file containing the "
     336      "private key and the certificate in PEM format.",
     337  )
     338  
     339  index_url: Callable[..., Option] = partial(
     340      Option,
     341      "-i",
     342      "--index-url",
     343      "--pypi-url",
     344      dest="index_url",
     345      metavar="URL",
     346      default=PyPI.simple_url,
     347      help="Base URL of the Python Package Index (default %default). "
     348      "This should point to a repository compliant with PEP 503 "
     349      "(the simple repository API) or a local directory laid out "
     350      "in the same format.",
     351  )
     352  
     353  
     354  def extra_index_url() -> Option:
     355      return Option(
     356          "--extra-index-url",
     357          dest="extra_index_urls",
     358          metavar="URL",
     359          action="append",
     360          default=[],
     361          help="Extra URLs of package indexes to use in addition to "
     362          "--index-url. Should follow the same rules as "
     363          "--index-url.",
     364      )
     365  
     366  
     367  no_index: Callable[..., Option] = partial(
     368      Option,
     369      "--no-index",
     370      dest="no_index",
     371      action="store_true",
     372      default=False,
     373      help="Ignore package index (only looking at --find-links URLs instead).",
     374  )
     375  
     376  
     377  def find_links() -> Option:
     378      return Option(
     379          "-f",
     380          "--find-links",
     381          dest="find_links",
     382          action="append",
     383          default=[],
     384          metavar="url",
     385          help="If a URL or path to an html file, then parse for links to "
     386          "archives such as sdist (.tar.gz) or wheel (.whl) files. "
     387          "If a local path or file:// URL that's a directory, "
     388          "then look for archives in the directory listing. "
     389          "Links to VCS project URLs are not supported.",
     390      )
     391  
     392  
     393  def trusted_host() -> Option:
     394      return Option(
     395          "--trusted-host",
     396          dest="trusted_hosts",
     397          action="append",
     398          metavar="HOSTNAME",
     399          default=[],
     400          help="Mark this host or host:port pair as trusted, even though it "
     401          "does not have valid or any HTTPS.",
     402      )
     403  
     404  
     405  def constraints() -> Option:
     406      return Option(
     407          "-c",
     408          "--constraint",
     409          dest="constraints",
     410          action="append",
     411          default=[],
     412          metavar="file",
     413          help="Constrain versions using the given constraints file. "
     414          "This option can be used multiple times.",
     415      )
     416  
     417  
     418  def requirements() -> Option:
     419      return Option(
     420          "-r",
     421          "--requirement",
     422          dest="requirements",
     423          action="append",
     424          default=[],
     425          metavar="file",
     426          help="Install from the given requirements file. "
     427          "This option can be used multiple times.",
     428      )
     429  
     430  
     431  def editable() -> Option:
     432      return Option(
     433          "-e",
     434          "--editable",
     435          dest="editables",
     436          action="append",
     437          default=[],
     438          metavar="path/url",
     439          help=(
     440              "Install a project in editable mode (i.e. setuptools "
     441              '"develop mode") from a local project path or a VCS url.'
     442          ),
     443      )
     444  
     445  
     446  def _handle_src(option: Option, opt_str: str, value: str, parser: OptionParser) -> None:
     447      value = os.path.abspath(value)
     448      setattr(parser.values, option.dest, value)
     449  
     450  
     451  src: Callable[..., Option] = partial(
     452      PipOption,
     453      "--src",
     454      "--source",
     455      "--source-dir",
     456      "--source-directory",
     457      dest="src_dir",
     458      type="path",
     459      metavar="dir",
     460      default=get_src_prefix(),
     461      action="callback",
     462      callback=_handle_src,
     463      help="Directory to check out editable projects into. "
     464      'The default in a virtualenv is "<venv path>/src". '
     465      'The default for global installs is "<current dir>/src".',
     466  )
     467  
     468  
     469  def _get_format_control(values: Values, option: Option) -> Any:
     470      """Get a format_control object."""
     471      return getattr(values, option.dest)
     472  
     473  
     474  def _handle_no_binary(
     475      option: Option, opt_str: str, value: str, parser: OptionParser
     476  ) -> None:
     477      existing = _get_format_control(parser.values, option)
     478      FormatControl.handle_mutual_excludes(
     479          value,
     480          existing.no_binary,
     481          existing.only_binary,
     482      )
     483  
     484  
     485  def _handle_only_binary(
     486      option: Option, opt_str: str, value: str, parser: OptionParser
     487  ) -> None:
     488      existing = _get_format_control(parser.values, option)
     489      FormatControl.handle_mutual_excludes(
     490          value,
     491          existing.only_binary,
     492          existing.no_binary,
     493      )
     494  
     495  
     496  def no_binary() -> Option:
     497      format_control = FormatControl(set(), set())
     498      return Option(
     499          "--no-binary",
     500          dest="format_control",
     501          action="callback",
     502          callback=_handle_no_binary,
     503          type="str",
     504          default=format_control,
     505          help="Do not use binary packages. Can be supplied multiple times, and "
     506          'each time adds to the existing value. Accepts either ":all:" to '
     507          'disable all binary packages, ":none:" to empty the set (notice '
     508          "the colons), or one or more package names with commas between "
     509          "them (no colons). Note that some packages are tricky to compile "
     510          "and may fail to install when this option is used on them.",
     511      )
     512  
     513  
     514  def only_binary() -> Option:
     515      format_control = FormatControl(set(), set())
     516      return Option(
     517          "--only-binary",
     518          dest="format_control",
     519          action="callback",
     520          callback=_handle_only_binary,
     521          type="str",
     522          default=format_control,
     523          help="Do not use source packages. Can be supplied multiple times, and "
     524          'each time adds to the existing value. Accepts either ":all:" to '
     525          'disable all source packages, ":none:" to empty the set, or one '
     526          "or more package names with commas between them. Packages "
     527          "without binary distributions will fail to install when this "
     528          "option is used on them.",
     529      )
     530  
     531  
     532  platforms: Callable[..., Option] = partial(
     533      Option,
     534      "--platform",
     535      dest="platforms",
     536      metavar="platform",
     537      action="append",
     538      default=None,
     539      help=(
     540          "Only use wheels compatible with <platform>. Defaults to the "
     541          "platform of the running system. Use this option multiple times to "
     542          "specify multiple platforms supported by the target interpreter."
     543      ),
     544  )
     545  
     546  
     547  # This was made a separate function for unit-testing purposes.
     548  def _convert_python_version(value: str) -> Tuple[Tuple[int, ...], Optional[str]]:
     549      """
     550      Convert a version string like "3", "37", or "3.7.3" into a tuple of ints.
     551  
     552      :return: A 2-tuple (version_info, error_msg), where `error_msg` is
     553          non-None if and only if there was a parsing error.
     554      """
     555      if not value:
     556          # The empty string is the same as not providing a value.
     557          return (None, None)
     558  
     559      parts = value.split(".")
     560      if len(parts) > 3:
     561          return ((), "at most three version parts are allowed")
     562  
     563      if len(parts) == 1:
     564          # Then we are in the case of "3" or "37".
     565          value = parts[0]
     566          if len(value) > 1:
     567              parts = [value[0], value[1:]]
     568  
     569      try:
     570          version_info = tuple(int(part) for part in parts)
     571      except ValueError:
     572          return ((), "each version part must be an integer")
     573  
     574      return (version_info, None)
     575  
     576  
     577  def _handle_python_version(
     578      option: Option, opt_str: str, value: str, parser: OptionParser
     579  ) -> None:
     580      """
     581      Handle a provided --python-version value.
     582      """
     583      version_info, error_msg = _convert_python_version(value)
     584      if error_msg is not None:
     585          msg = "invalid --python-version value: {!r}: {}".format(
     586              value,
     587              error_msg,
     588          )
     589          raise_option_error(parser, option=option, msg=msg)
     590  
     591      parser.values.python_version = version_info
     592  
     593  
     594  python_version: Callable[..., Option] = partial(
     595      Option,
     596      "--python-version",
     597      dest="python_version",
     598      metavar="python_version",
     599      action="callback",
     600      callback=_handle_python_version,
     601      type="str",
     602      default=None,
     603      help=dedent(
     604          """\
     605      The Python interpreter version to use for wheel and "Requires-Python"
     606      compatibility checks. Defaults to a version derived from the running
     607      interpreter. The version can be specified using up to three dot-separated
     608      integers (e.g. "3" for 3.0.0, "3.7" for 3.7.0, or "3.7.3"). A major-minor
     609      version can also be given as a string without dots (e.g. "37" for 3.7.0).
     610      """
     611      ),
     612  )
     613  
     614  
     615  implementation: Callable[..., Option] = partial(
     616      Option,
     617      "--implementation",
     618      dest="implementation",
     619      metavar="implementation",
     620      default=None,
     621      help=(
     622          "Only use wheels compatible with Python "
     623          "implementation <implementation>, e.g. 'pp', 'jy', 'cp', "
     624          " or 'ip'. If not specified, then the current "
     625          "interpreter implementation is used.  Use 'py' to force "
     626          "implementation-agnostic wheels."
     627      ),
     628  )
     629  
     630  
     631  abis: Callable[..., Option] = partial(
     632      Option,
     633      "--abi",
     634      dest="abis",
     635      metavar="abi",
     636      action="append",
     637      default=None,
     638      help=(
     639          "Only use wheels compatible with Python abi <abi>, e.g. 'pypy_41'. "
     640          "If not specified, then the current interpreter abi tag is used. "
     641          "Use this option multiple times to specify multiple abis supported "
     642          "by the target interpreter. Generally you will need to specify "
     643          "--implementation, --platform, and --python-version when using this "
     644          "option."
     645      ),
     646  )
     647  
     648  
     649  def add_target_python_options(cmd_opts: OptionGroup) -> None:
     650      cmd_opts.add_option(platforms())
     651      cmd_opts.add_option(python_version())
     652      cmd_opts.add_option(implementation())
     653      cmd_opts.add_option(abis())
     654  
     655  
     656  def make_target_python(options: Values) -> TargetPython:
     657      target_python = TargetPython(
     658          platforms=options.platforms,
     659          py_version_info=options.python_version,
     660          abis=options.abis,
     661          implementation=options.implementation,
     662      )
     663  
     664      return target_python
     665  
     666  
     667  def prefer_binary() -> Option:
     668      return Option(
     669          "--prefer-binary",
     670          dest="prefer_binary",
     671          action="store_true",
     672          default=False,
     673          help="Prefer older binary packages over newer source packages.",
     674      )
     675  
     676  
     677  cache_dir: Callable[..., Option] = partial(
     678      PipOption,
     679      "--cache-dir",
     680      dest="cache_dir",
     681      default=USER_CACHE_DIR,
     682      metavar="dir",
     683      type="path",
     684      help="Store the cache data in <dir>.",
     685  )
     686  
     687  
     688  def _handle_no_cache_dir(
     689      option: Option, opt: str, value: str, parser: OptionParser
     690  ) -> None:
     691      """
     692      Process a value provided for the --no-cache-dir option.
     693  
     694      This is an optparse.Option callback for the --no-cache-dir option.
     695      """
     696      # The value argument will be None if --no-cache-dir is passed via the
     697      # command-line, since the option doesn't accept arguments.  However,
     698      # the value can be non-None if the option is triggered e.g. by an
     699      # environment variable, like PIP_NO_CACHE_DIR=true.
     700      if value is not None:
     701          # Then parse the string value to get argument error-checking.
     702          try:
     703              strtobool(value)
     704          except ValueError as exc:
     705              raise_option_error(parser, option=option, msg=str(exc))
     706  
     707      # Originally, setting PIP_NO_CACHE_DIR to a value that strtobool()
     708      # converted to 0 (like "false" or "no") caused cache_dir to be disabled
     709      # rather than enabled (logic would say the latter).  Thus, we disable
     710      # the cache directory not just on values that parse to True, but (for
     711      # backwards compatibility reasons) also on values that parse to False.
     712      # In other words, always set it to False if the option is provided in
     713      # some (valid) form.
     714      parser.values.cache_dir = False
     715  
     716  
     717  no_cache: Callable[..., Option] = partial(
     718      Option,
     719      "--no-cache-dir",
     720      dest="cache_dir",
     721      action="callback",
     722      callback=_handle_no_cache_dir,
     723      help="Disable the cache.",
     724  )
     725  
     726  no_deps: Callable[..., Option] = partial(
     727      Option,
     728      "--no-deps",
     729      "--no-dependencies",
     730      dest="ignore_dependencies",
     731      action="store_true",
     732      default=False,
     733      help="Don't install package dependencies.",
     734  )
     735  
     736  ignore_requires_python: Callable[..., Option] = partial(
     737      Option,
     738      "--ignore-requires-python",
     739      dest="ignore_requires_python",
     740      action="store_true",
     741      help="Ignore the Requires-Python information.",
     742  )
     743  
     744  no_build_isolation: Callable[..., Option] = partial(
     745      Option,
     746      "--no-build-isolation",
     747      dest="build_isolation",
     748      action="store_false",
     749      default=True,
     750      help="Disable isolation when building a modern source distribution. "
     751      "Build dependencies specified by PEP 518 must be already installed "
     752      "if this option is used.",
     753  )
     754  
     755  check_build_deps: Callable[..., Option] = partial(
     756      Option,
     757      "--check-build-dependencies",
     758      dest="check_build_deps",
     759      action="store_true",
     760      default=False,
     761      help="Check the build dependencies when PEP517 is used.",
     762  )
     763  
     764  
     765  def _handle_no_use_pep517(
     766      option: Option, opt: str, value: str, parser: OptionParser
     767  ) -> None:
     768      """
     769      Process a value provided for the --no-use-pep517 option.
     770  
     771      This is an optparse.Option callback for the no_use_pep517 option.
     772      """
     773      # Since --no-use-pep517 doesn't accept arguments, the value argument
     774      # will be None if --no-use-pep517 is passed via the command-line.
     775      # However, the value can be non-None if the option is triggered e.g.
     776      # by an environment variable, for example "PIP_NO_USE_PEP517=true".
     777      if value is not None:
     778          msg = """A value was passed for --no-use-pep517,
     779          probably using either the PIP_NO_USE_PEP517 environment variable
     780          or the "no-use-pep517" config file option. Use an appropriate value
     781          of the PIP_USE_PEP517 environment variable or the "use-pep517"
     782          config file option instead.
     783          """
     784          raise_option_error(parser, option=option, msg=msg)
     785  
     786      # If user doesn't wish to use pep517, we check if setuptools and wheel are installed
     787      # and raise error if it is not.
     788      packages = ("setuptools", "wheel")
     789      if not all(importlib.util.find_spec(package) for package in packages):
     790          msg = (
     791              f"It is not possible to use --no-use-pep517 "
     792              f"without {' and '.join(packages)} installed."
     793          )
     794          raise_option_error(parser, option=option, msg=msg)
     795  
     796      # Otherwise, --no-use-pep517 was passed via the command-line.
     797      parser.values.use_pep517 = False
     798  
     799  
     800  use_pep517: Any = partial(
     801      Option,
     802      "--use-pep517",
     803      dest="use_pep517",
     804      action="store_true",
     805      default=None,
     806      help="Use PEP 517 for building source distributions "
     807      "(use --no-use-pep517 to force legacy behaviour).",
     808  )
     809  
     810  no_use_pep517: Any = partial(
     811      Option,
     812      "--no-use-pep517",
     813      dest="use_pep517",
     814      action="callback",
     815      callback=_handle_no_use_pep517,
     816      default=None,
     817      help=SUPPRESS_HELP,
     818  )
     819  
     820  
     821  def _handle_config_settings(
     822      option: Option, opt_str: str, value: str, parser: OptionParser
     823  ) -> None:
     824      key, sep, val = value.partition("=")
     825      if sep != "=":
     826          parser.error(f"Arguments to {opt_str} must be of the form KEY=VAL")  # noqa
     827      dest = getattr(parser.values, option.dest)
     828      if dest is None:
     829          dest = {}
     830          setattr(parser.values, option.dest, dest)
     831      if key in dest:
     832          if isinstance(dest[key], list):
     833              dest[key].append(val)
     834          else:
     835              dest[key] = [dest[key], val]
     836      else:
     837          dest[key] = val
     838  
     839  
     840  config_settings: Callable[..., Option] = partial(
     841      Option,
     842      "-C",
     843      "--config-settings",
     844      dest="config_settings",
     845      type=str,
     846      action="callback",
     847      callback=_handle_config_settings,
     848      metavar="settings",
     849      help="Configuration settings to be passed to the PEP 517 build backend. "
     850      "Settings take the form KEY=VALUE. Use multiple --config-settings options "
     851      "to pass multiple keys to the backend.",
     852  )
     853  
     854  build_options: Callable[..., Option] = partial(
     855      Option,
     856      "--build-option",
     857      dest="build_options",
     858      metavar="options",
     859      action="append",
     860      help="Extra arguments to be supplied to 'setup.py bdist_wheel'.",
     861  )
     862  
     863  global_options: Callable[..., Option] = partial(
     864      Option,
     865      "--global-option",
     866      dest="global_options",
     867      action="append",
     868      metavar="options",
     869      help="Extra global options to be supplied to the setup.py "
     870      "call before the install or bdist_wheel command.",
     871  )
     872  
     873  no_clean: Callable[..., Option] = partial(
     874      Option,
     875      "--no-clean",
     876      action="store_true",
     877      default=False,
     878      help="Don't clean up build directories.",
     879  )
     880  
     881  pre: Callable[..., Option] = partial(
     882      Option,
     883      "--pre",
     884      action="store_true",
     885      default=False,
     886      help="Include pre-release and development versions. By default, "
     887      "pip only finds stable versions.",
     888  )
     889  
     890  disable_pip_version_check: Callable[..., Option] = partial(
     891      Option,
     892      "--disable-pip-version-check",
     893      dest="disable_pip_version_check",
     894      action="store_true",
     895      default=False,
     896      help="Don't periodically check PyPI to determine whether a new version "
     897      "of pip is available for download. Implied with --no-index.",
     898  )
     899  
     900  root_user_action: Callable[..., Option] = partial(
     901      Option,
     902      "--root-user-action",
     903      dest="root_user_action",
     904      default="warn",
     905      choices=["warn", "ignore"],
     906      help="Action if pip is run as a root user. By default, a warning message is shown.",
     907  )
     908  
     909  
     910  def _handle_merge_hash(
     911      option: Option, opt_str: str, value: str, parser: OptionParser
     912  ) -> None:
     913      """Given a value spelled "algo:digest", append the digest to a list
     914      pointed to in a dict by the algo name."""
     915      if not parser.values.hashes:
     916          parser.values.hashes = {}
     917      try:
     918          algo, digest = value.split(":", 1)
     919      except ValueError:
     920          parser.error(
     921              "Arguments to {} must be a hash name "  # noqa
     922              "followed by a value, like --hash=sha256:"
     923              "abcde...".format(opt_str)
     924          )
     925      if algo not in STRONG_HASHES:
     926          parser.error(
     927              "Allowed hash algorithms for {} are {}.".format(  # noqa
     928                  opt_str, ", ".join(STRONG_HASHES)
     929              )
     930          )
     931      parser.values.hashes.setdefault(algo, []).append(digest)
     932  
     933  
     934  hash: Callable[..., Option] = partial(
     935      Option,
     936      "--hash",
     937      # Hash values eventually end up in InstallRequirement.hashes due to
     938      # __dict__ copying in process_line().
     939      dest="hashes",
     940      action="callback",
     941      callback=_handle_merge_hash,
     942      type="string",
     943      help="Verify that the package's archive matches this "
     944      "hash before installing. Example: --hash=sha256:abcdef...",
     945  )
     946  
     947  
     948  require_hashes: Callable[..., Option] = partial(
     949      Option,
     950      "--require-hashes",
     951      dest="require_hashes",
     952      action="store_true",
     953      default=False,
     954      help="Require a hash to check each requirement against, for "
     955      "repeatable installs. This option is implied when any package in a "
     956      "requirements file has a --hash option.",
     957  )
     958  
     959  
     960  list_path: Callable[..., Option] = partial(
     961      PipOption,
     962      "--path",
     963      dest="path",
     964      type="path",
     965      action="append",
     966      help="Restrict to the specified installation path for listing "
     967      "packages (can be used multiple times).",
     968  )
     969  
     970  
     971  def check_list_path_option(options: Values) -> None:
     972      if options.path and (options.user or options.local):
     973          raise CommandError("Cannot combine '--path' with '--user' or '--local'")
     974  
     975  
     976  list_exclude: Callable[..., Option] = partial(
     977      PipOption,
     978      "--exclude",
     979      dest="excludes",
     980      action="append",
     981      metavar="package",
     982      type="package_name",
     983      help="Exclude specified package from the output",
     984  )
     985  
     986  
     987  no_python_version_warning: Callable[..., Option] = partial(
     988      Option,
     989      "--no-python-version-warning",
     990      dest="no_python_version_warning",
     991      action="store_true",
     992      default=False,
     993      help="Silence deprecation warnings for upcoming unsupported Pythons.",
     994  )
     995  
     996  
     997  # Features that are now always on. A warning is printed if they are used.
     998  ALWAYS_ENABLED_FEATURES = [
     999      "no-binary-enable-wheel-cache",  # always on since 23.1
    1000  ]
    1001  
    1002  use_new_feature: Callable[..., Option] = partial(
    1003      Option,
    1004      "--use-feature",
    1005      dest="features_enabled",
    1006      metavar="feature",
    1007      action="append",
    1008      default=[],
    1009      choices=[
    1010          "fast-deps",
    1011          "truststore",
    1012      ]
    1013      + ALWAYS_ENABLED_FEATURES,
    1014      help="Enable new functionality, that may be backward incompatible.",
    1015  )
    1016  
    1017  use_deprecated_feature: Callable[..., Option] = partial(
    1018      Option,
    1019      "--use-deprecated",
    1020      dest="deprecated_features_enabled",
    1021      metavar="feature",
    1022      action="append",
    1023      default=[],
    1024      choices=[
    1025          "legacy-resolver",
    1026      ],
    1027      help=("Enable deprecated functionality, that will be removed in the future."),
    1028  )
    1029  
    1030  
    1031  ##########
    1032  # groups #
    1033  ##########
    1034  
    1035  general_group: Dict[str, Any] = {
    1036      "name": "General Options",
    1037      "options": [
    1038          help_,
    1039          debug_mode,
    1040          isolated_mode,
    1041          require_virtualenv,
    1042          python,
    1043          verbose,
    1044          version,
    1045          quiet,
    1046          log,
    1047          no_input,
    1048          keyring_provider,
    1049          proxy,
    1050          retries,
    1051          timeout,
    1052          exists_action,
    1053          trusted_host,
    1054          cert,
    1055          client_cert,
    1056          cache_dir,
    1057          no_cache,
    1058          disable_pip_version_check,
    1059          no_color,
    1060          no_python_version_warning,
    1061          use_new_feature,
    1062          use_deprecated_feature,
    1063      ],
    1064  }
    1065  
    1066  index_group: Dict[str, Any] = {
    1067      "name": "Package Index Options",
    1068      "options": [
    1069          index_url,
    1070          extra_index_url,
    1071          no_index,
    1072          find_links,
    1073      ],
    1074  }