cmdoptions.py 35 KB

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