cmdoptions.py 31 KB

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