Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/pip/_internal/cli/cmdoptions.py: 57%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

303 statements  

1""" 

2shared options and groups 

3 

4The principle here is to define options once, but *not* instantiate them 

5globally. One reason being that options with action='append' can carry state 

6between parses. pip parses general options twice internally, and shouldn't 

7pass on state. To be consistent, all options will follow this design. 

8""" 

9 

10# The following comment should be removed at some point in the future. 

11# mypy: strict-optional=False 

12from __future__ import annotations 

13 

14import logging 

15import os 

16import pathlib 

17import re 

18import textwrap 

19from collections.abc import Callable 

20from datetime import datetime, timedelta, timezone 

21from functools import partial 

22from optparse import SUPPRESS_HELP, Option, OptionGroup, OptionParser, Values 

23from textwrap import dedent 

24from typing import Any 

25 

26from pip._vendor.packaging.utils import canonicalize_name 

27 

28from pip._internal.cli.parser import ConfigOptionParser 

29from pip._internal.exceptions import CommandError 

30from pip._internal.locations import USER_CACHE_DIR, get_src_prefix 

31from pip._internal.models.format_control import FormatControl 

32from pip._internal.models.index import PyPI 

33from pip._internal.models.release_control import ReleaseControl 

34from pip._internal.models.target_python import TargetPython 

35from pip._internal.utils.datetime import parse_iso_datetime 

36from pip._internal.utils.hashes import STRONG_HASHES 

37from pip._internal.utils.misc import strtobool 

38 

39logger = logging.getLogger(__name__) 

40 

41 

42def raise_option_error(parser: OptionParser, option: Option, msg: str) -> None: 

43 """ 

44 Raise an option parsing error using parser.error(). 

45 

46 Args: 

47 parser: an OptionParser instance. 

48 option: an Option instance. 

49 msg: the error text. 

50 """ 

51 msg = f"{option} error: {msg}" 

52 msg = textwrap.fill(" ".join(msg.split())) 

53 parser.error(msg) 

54 

55 

56def make_option_group(group: dict[str, Any], parser: ConfigOptionParser) -> OptionGroup: 

57 """ 

58 Return an OptionGroup object 

59 group -- assumed to be dict with 'name' and 'options' keys 

60 parser -- an optparse Parser 

61 """ 

62 option_group = OptionGroup(parser, group["name"]) 

63 for option in group["options"]: 

64 option_group.add_option(option()) 

65 return option_group 

66 

67 

68def check_only_deps_option_does_not_conflict(options: Values) -> None: 

69 """Function for determining if --only-deps and other incompatible options are 

70 specified. 

71 

72 :param options: The OptionParser options. 

73 """ 

74 if not options.only_dependencies: 

75 return 

76 conflicts = [] 

77 if options.ignore_dependencies: 

78 conflicts.append("'--no-deps'") 

79 if "legacy-resolver" in options.deprecated_features_enabled: 

80 conflicts.append("'--use-deprecated legacy-resolver'") 

81 if options.requirements: 

82 conflicts.append("'--requirement'") 

83 if options.requirements_from_scripts: 

84 conflicts.append("'--requirements-from-script'") 

85 if options.dependency_groups: 

86 conflicts.append("'--group'") 

87 if conflicts: 

88 if len(conflicts) > 1: 

89 conflicts[-1] = "or " + conflicts[-1] 

90 conflict_message = ", ".join(conflicts) 

91 raise CommandError( 

92 f"Cannot use '--only-dependencies' in combination with {conflict_message}. " 

93 "If this is unexpected, please refer to the user guide:\n" 

94 "\n" 

95 " https://pip.pypa.io/en/stable/user_guide/#installing-only-dependencies" 

96 ) 

97 

98 

99def check_dist_restriction(options: Values, check_target: bool = False) -> None: 

100 """Function for determining if custom platform options are allowed. 

101 

102 :param options: The OptionParser options. 

103 :param check_target: Whether or not to check if --target is being used. 

104 """ 

105 dist_restriction_set = any( 

106 [ 

107 options.python_version, 

108 options.platforms, 

109 options.abis, 

110 options.implementation, 

111 ] 

112 ) 

113 

114 binary_only = FormatControl(set(), {":all:"}) 

115 sdist_dependencies_allowed = ( 

116 options.format_control != binary_only and not options.ignore_dependencies 

117 ) 

118 

119 # Installations or downloads using dist restrictions must not combine 

120 # source distributions and dist-specific wheels, as they are not 

121 # guaranteed to be locally compatible. 

122 if dist_restriction_set and sdist_dependencies_allowed: 

123 raise CommandError( 

124 "When restricting platform and interpreter constraints using " 

125 "--python-version, --platform, --abi, or --implementation, " 

126 "either --no-deps must be set, or --only-binary=:all: must be " 

127 "set and --no-binary must not be set (or must be set to " 

128 ":none:)." 

129 ) 

130 

131 if check_target: 

132 if not options.dry_run and dist_restriction_set and not options.target_dir: 

133 raise CommandError( 

134 "Can not use any platform or abi specific options unless " 

135 "installing via '--target' or using '--dry-run'" 

136 ) 

137 

138 if dist_restriction_set: 

139 # Lazy import to keep CLI startup fast 

140 from pip._internal.utils import pylock as pylock_utils 

141 

142 for filename in options.requirements: 

143 if pylock_utils.is_valid_pylock_filename(filename): 

144 raise CommandError( 

145 "Platform and interpreter constraints using " 

146 "--python-version, --platform, --abi, or --implementation, " 

147 f"are not supported when selecting requirements from {filename!r}" 

148 ) 

149 

150 

151def check_build_constraints(options: Values) -> None: 

152 """Function for validating build constraints options. 

153 

154 :param options: The OptionParser options. 

155 """ 

156 if hasattr(options, "build_constraints") and options.build_constraints: 

157 if not options.build_isolation: 

158 raise CommandError( 

159 "--build-constraint cannot be used with --no-build-isolation." 

160 ) 

161 

162 # Import here to avoid circular imports 

163 from pip._internal.network.session import PipSession 

164 from pip._internal.req.req_file import get_file_content 

165 

166 # Eagerly check build constraints file contents 

167 # is valid so that we don't fail in when trying 

168 # to check constraints in isolated build process 

169 with PipSession() as session: 

170 for constraint_file in options.build_constraints: 

171 get_file_content(constraint_file, session) 

172 

173 

174def _path_option_check(option: Option, opt: str, value: str) -> str: 

175 return os.path.expanduser(value) 

176 

177 

178def _package_name_option_check(option: Option, opt: str, value: str) -> str: 

179 return canonicalize_name(value) 

180 

181 

182class PipOption(Option): 

183 TYPES = Option.TYPES + ("path", "package_name") 

184 TYPE_CHECKER = Option.TYPE_CHECKER.copy() 

185 TYPE_CHECKER["package_name"] = _package_name_option_check 

186 TYPE_CHECKER["path"] = _path_option_check 

187 

188 

189########### 

190# options # 

191########### 

192 

193help_: Callable[..., Option] = partial( 

194 Option, 

195 "-h", 

196 "--help", 

197 dest="help", 

198 action="help", 

199 help="Show help.", 

200) 

201 

202debug_mode: Callable[..., Option] = partial( 

203 Option, 

204 "--debug", 

205 dest="debug_mode", 

206 action="store_true", 

207 default=False, 

208 help=( 

209 "Let unhandled exceptions propagate outside the main subroutine, " 

210 "instead of logging them to stderr." 

211 ), 

212) 

213 

214isolated_mode: Callable[..., Option] = partial( 

215 Option, 

216 "--isolated", 

217 dest="isolated_mode", 

218 action="store_true", 

219 default=False, 

220 help=( 

221 "Run pip in an isolated mode, ignoring environment variables and user " 

222 "configuration." 

223 ), 

224) 

225 

226require_virtualenv: Callable[..., Option] = partial( 

227 Option, 

228 "--require-virtualenv", 

229 "--require-venv", 

230 dest="require_venv", 

231 action="store_true", 

232 default=False, 

233 help=( 

234 "Allow pip to only run in a virtual environment; exit with an error otherwise." 

235 ), 

236) 

237 

238override_externally_managed: Callable[..., Option] = partial( 

239 Option, 

240 "--break-system-packages", 

241 dest="override_externally_managed", 

242 action="store_true", 

243 help="Allow pip to modify an EXTERNALLY-MANAGED Python installation", 

244) 

245 

246python: Callable[..., Option] = partial( 

247 Option, 

248 "--python", 

249 dest="python", 

250 help="Run pip with the specified Python interpreter.", 

251) 

252 

253verbose: Callable[..., Option] = partial( 

254 Option, 

255 "-v", 

256 "--verbose", 

257 dest="verbose", 

258 action="count", 

259 default=0, 

260 help="Give more output. Option is additive, and can be used up to 3 times.", 

261) 

262 

263no_color: Callable[..., Option] = partial( 

264 Option, 

265 "--no-color", 

266 dest="no_color", 

267 action="store_true", 

268 default=False, 

269 help="Suppress colored output.", 

270) 

271 

272version: Callable[..., Option] = partial( 

273 Option, 

274 "-V", 

275 "--version", 

276 dest="version", 

277 action="store_true", 

278 help="Show version and exit.", 

279) 

280 

281quiet: Callable[..., Option] = partial( 

282 Option, 

283 "-q", 

284 "--quiet", 

285 dest="quiet", 

286 action="count", 

287 default=0, 

288 help=( 

289 "Give less output. Option is additive, and can be used up to 3" 

290 " times (corresponding to WARNING, ERROR, and CRITICAL logging" 

291 " levels)." 

292 ), 

293) 

294 

295progress_bar: Callable[..., Option] = partial( 

296 Option, 

297 "--progress-bar", 

298 dest="progress_bar", 

299 type="choice", 

300 choices=["auto", "on", "off", "raw"], 

301 default="auto", 

302 help=( 

303 "Specify whether the progress bar should be used. In 'auto'" 

304 " mode, --quiet will suppress all progress bars." 

305 " [auto, on, off, raw] (default: auto)" 

306 ), 

307) 

308 

309log: Callable[..., Option] = partial( 

310 PipOption, 

311 "--log", 

312 "--log-file", 

313 "--local-log", 

314 dest="log", 

315 metavar="path", 

316 type="path", 

317 help="Path to a verbose appending log.", 

318) 

319 

320no_input: Callable[..., Option] = partial( 

321 Option, 

322 # Don't ask for input 

323 "--no-input", 

324 dest="no_input", 

325 action="store_true", 

326 default=False, 

327 help="Disable prompting for input.", 

328) 

329 

330keyring_provider: Callable[..., Option] = partial( 

331 Option, 

332 "--keyring-provider", 

333 dest="keyring_provider", 

334 choices=["auto", "disabled", "import", "subprocess"], 

335 default="auto", 

336 help=( 

337 "Enable the credential lookup via the keyring library if user input is allowed." 

338 " Specify which mechanism to use [auto, disabled, import, subprocess]." 

339 " (default: %default)" 

340 ), 

341) 

342 

343proxy: Callable[..., Option] = partial( 

344 Option, 

345 "--proxy", 

346 dest="proxy", 

347 type="str", 

348 default=None, 

349 help="Specify a proxy in the form scheme://[user:passwd@]proxy.server:port.", 

350) 

351 

352no_proxy_env: Callable[..., Option] = partial( 

353 Option, 

354 "--no-proxy-env", 

355 dest="no_proxy_env", 

356 action="store_true", 

357 default=False, 

358 help="Do not read proxy configuration from environment variables.", 

359) 

360 

361retries: Callable[..., Option] = partial( 

362 Option, 

363 "--retries", 

364 dest="retries", 

365 type="int", 

366 default=5, 

367 help="Maximum attempts to establish a new HTTP connection. (default: %default)", 

368) 

369 

370resume_retries: Callable[..., Option] = partial( 

371 Option, 

372 "--resume-retries", 

373 dest="resume_retries", 

374 type="int", 

375 default=5, 

376 help="Maximum attempts to resume or restart an incomplete download. " 

377 "(default: %default)", 

378) 

379 

380timeout: Callable[..., Option] = partial( 

381 Option, 

382 "--timeout", 

383 "--default-timeout", 

384 metavar="sec", 

385 dest="timeout", 

386 type="float", 

387 default=15, 

388 help="Set the socket timeout (default %default seconds).", 

389) 

390 

391 

392def exists_action() -> Option: 

393 return Option( 

394 # Option when path already exist 

395 "--exists-action", 

396 dest="exists_action", 

397 type="choice", 

398 choices=["s", "i", "w", "b", "a"], 

399 default=[], 

400 action="append", 

401 metavar="action", 

402 help="Default action when a path already exists: " 

403 "(s)witch, (i)gnore, (w)ipe, (b)ackup, (a)bort.", 

404 ) 

405 

406 

407cert: Callable[..., Option] = partial( 

408 PipOption, 

409 "--cert", 

410 dest="cert", 

411 type="path", 

412 metavar="path", 

413 help=( 

414 "Path to PEM-encoded CA certificate bundle. " 

415 "If provided, overrides the default. " 

416 "See 'SSL Certificate Verification' in pip documentation " 

417 "for more information." 

418 ), 

419) 

420 

421client_cert: Callable[..., Option] = partial( 

422 PipOption, 

423 "--client-cert", 

424 dest="client_cert", 

425 type="path", 

426 default=None, 

427 metavar="path", 

428 help="Path to SSL client certificate, a single file containing the " 

429 "private key and the certificate in PEM format.", 

430) 

431 

432index_url: Callable[..., Option] = partial( 

433 Option, 

434 "-i", 

435 "--index-url", 

436 "--pypi-url", 

437 dest="index_url", 

438 metavar="URL", 

439 default=PyPI.simple_url, 

440 help="Base URL of the Python Package Index (default %default). " 

441 "This should point to a repository compliant with PEP 503 " 

442 "(the simple repository API) or a local directory laid out " 

443 "in the same format.", 

444) 

445 

446 

447def extra_index_url() -> Option: 

448 return Option( 

449 "--extra-index-url", 

450 dest="extra_index_urls", 

451 metavar="URL", 

452 action="append", 

453 default=[], 

454 help="Extra URLs of package indexes to use in addition to " 

455 "--index-url. Should follow the same rules as " 

456 "--index-url.", 

457 ) 

458 

459 

460no_index: Callable[..., Option] = partial( 

461 Option, 

462 "--no-index", 

463 dest="no_index", 

464 action="store_true", 

465 default=False, 

466 help="Ignore package index (only looking at --find-links URLs instead).", 

467) 

468 

469 

470def find_links() -> Option: 

471 return Option( 

472 "-f", 

473 "--find-links", 

474 dest="find_links", 

475 action="append", 

476 default=[], 

477 metavar="url", 

478 help="If a URL or path to an html file, then parse for links to " 

479 "archives such as sdist (.tar.gz) or wheel (.whl) files. " 

480 "If a local path or file:// URL that's a directory, " 

481 "then look for archives in the directory listing. " 

482 "Links to VCS project URLs are not supported.", 

483 ) 

484 

485 

486def _handle_uploaded_prior_to( 

487 option: Option, opt: str, value: str, parser: OptionParser 

488) -> None: 

489 """ 

490 This is an optparse.Option callback for the --uploaded-prior-to option. 

491 

492 Accepts either an ISO 8601 datetime string (e.g., '2023-01-01T00:00:00Z') 

493 or a strict subset of ISO 8601 durations: PnD where n is a number of days 

494 (e.g., 'P7D' for 7 days ago). 

495 

496 Note: This option only works with indexes that provide upload-time metadata 

497 as specified in the simple repository API: 

498 https://packaging.python.org/en/latest/specifications/simple-repository-api/ 

499 """ 

500 if value is None: 

501 return None 

502 

503 # Try ISO 8601 duration in PnD format. The leading 'P' disambiguates 

504 # from absolute datetimes. Only whole days are supported; the format may 

505 # be extended to more of the ISO 8601 duration syntax in the future if 

506 # a real need is presented. 

507 match = re.match(r"^P(\d+)D$", value, re.ASCII) 

508 if match: 

509 days = int(match.group(1)) 

510 parser.values.uploaded_prior_to = datetime.now(timezone.utc) - timedelta( 

511 days=days 

512 ) 

513 return 

514 

515 try: 

516 uploaded_prior_to = parse_iso_datetime(value) 

517 # Use local timezone if no offset is given in the ISO string. 

518 if uploaded_prior_to.tzinfo is None: 

519 uploaded_prior_to = uploaded_prior_to.astimezone() 

520 parser.values.uploaded_prior_to = uploaded_prior_to 

521 except ValueError as exc: 

522 msg = ( 

523 f"invalid value: {value!r}: {exc}. " 

524 f"Expected an ISO 8601 datetime string " 

525 f"(e.g., '2023-01-01' or '2023-01-01T00:00:00Z') " 

526 f"or a duration in days (e.g., 'P3D')" 

527 ) 

528 raise_option_error(parser, option=option, msg=msg) 

529 

530 

531def uploaded_prior_to() -> Option: 

532 return Option( 

533 "--uploaded-prior-to", 

534 dest="uploaded_prior_to", 

535 metavar="datetime_or_duration", 

536 action="callback", 

537 callback=_handle_uploaded_prior_to, 

538 type="str", 

539 help=( 

540 "Only consider packages uploaded prior to the given value. " 

541 "Accepts an ISO 8601 datetime (e.g., '2023-01-01T00:00:00Z', " 

542 "uses local timezone if none specified) or a duration in days " 

543 "(e.g., 'P3D' for packages uploaded at least 3 days ago). " 

544 "Only effective when using indexes that provide " 

545 "upload-time metadata." 

546 ), 

547 ) 

548 

549 

550def trusted_host() -> Option: 

551 return Option( 

552 "--trusted-host", 

553 dest="trusted_hosts", 

554 action="append", 

555 metavar="HOSTNAME", 

556 default=[], 

557 help="Mark this host or host:port pair as trusted, even though it " 

558 "does not have valid or any HTTPS.", 

559 ) 

560 

561 

562def constraints() -> Option: 

563 return Option( 

564 "-c", 

565 "--constraint", 

566 dest="constraints", 

567 action="append", 

568 default=[], 

569 metavar="file", 

570 help="Constrain versions using the given constraints file. " 

571 "This option can be used multiple times.", 

572 ) 

573 

574 

575def build_constraints() -> Option: 

576 return Option( 

577 "--build-constraint", 

578 dest="build_constraints", 

579 action="append", 

580 type="str", 

581 default=[], 

582 metavar="file", 

583 help=( 

584 "Constrain build dependencies using the given constraints file. " 

585 "This option can be used multiple times." 

586 ), 

587 ) 

588 

589 

590def requirements() -> Option: 

591 return Option( 

592 "-r", 

593 "--requirement", 

594 dest="requirements", 

595 action="append", 

596 default=[], 

597 metavar="file", 

598 help=( 

599 "Install from the given requirements file. " 

600 "The file or URL can be in pip's requirements.txt format, " 

601 "or pylock.toml format. pylock.toml support is experimental. " 

602 "This option can be used multiple times." 

603 ), 

604 ) 

605 

606 

607def requirements_from_scripts() -> Option: 

608 return Option( 

609 "--requirements-from-script", 

610 action="append", 

611 default=[], 

612 dest="requirements_from_scripts", 

613 metavar="file", 

614 help="Install dependencies of the given script file " 

615 "as defined by PEP 723 inline metadata. ", 

616 ) 

617 

618 

619def editable() -> Option: 

620 return Option( 

621 "-e", 

622 "--editable", 

623 dest="editables", 

624 action="append", 

625 default=[], 

626 metavar="path/url", 

627 help=( 

628 "Install a project in editable mode (i.e. setuptools " 

629 '"develop mode") from a local project path or a VCS url.' 

630 ), 

631 ) 

632 

633 

634def _handle_src(option: Option, opt_str: str, value: str, parser: OptionParser) -> None: 

635 value = os.path.abspath(value) 

636 setattr(parser.values, option.dest, value) 

637 

638 

639src: Callable[..., Option] = partial( 

640 PipOption, 

641 "--src", 

642 "--source", 

643 "--source-dir", 

644 "--source-directory", 

645 dest="src_dir", 

646 type="path", 

647 metavar="dir", 

648 default=get_src_prefix(), 

649 action="callback", 

650 callback=_handle_src, 

651 help="Directory to check out editable projects into. " 

652 'The default in a virtualenv is "<venv path>/src". ' 

653 'The default for global installs is "<current dir>/src".', 

654) 

655 

656 

657def _get_format_control(values: Values, option: Option) -> Any: 

658 """Get a format_control object.""" 

659 return getattr(values, option.dest) 

660 

661 

662def _handle_no_binary( 

663 option: Option, opt_str: str, value: str, parser: OptionParser 

664) -> None: 

665 existing = _get_format_control(parser.values, option) 

666 FormatControl.handle_mutual_excludes( 

667 value, 

668 existing.no_binary, 

669 existing.only_binary, 

670 ) 

671 

672 

673def _handle_only_binary( 

674 option: Option, opt_str: str, value: str, parser: OptionParser 

675) -> None: 

676 existing = _get_format_control(parser.values, option) 

677 FormatControl.handle_mutual_excludes( 

678 value, 

679 existing.only_binary, 

680 existing.no_binary, 

681 ) 

682 

683 

684def no_binary() -> Option: 

685 format_control = FormatControl(set(), set()) 

686 return Option( 

687 "--no-binary", 

688 dest="format_control", 

689 action="callback", 

690 callback=_handle_no_binary, 

691 type="str", 

692 default=format_control, 

693 help="Do not download binary packages. Cached binary packages may still " 

694 "be used. Can be supplied multiple times, and each time adds to " 

695 "the existing value. Accepts either ':all:' to disable all binary " 

696 "packages, ':none:' to empty the set (notice the colons), or one " 

697 "or more package names with commas between them (no colons). " 

698 "Note that some packages are tricky to compile and may fail to " 

699 "install when this option is used on them.", 

700 ) 

701 

702 

703def only_binary() -> Option: 

704 format_control = FormatControl(set(), set()) 

705 return Option( 

706 "--only-binary", 

707 dest="format_control", 

708 action="callback", 

709 callback=_handle_only_binary, 

710 type="str", 

711 default=format_control, 

712 help="Do not use source packages. Can be supplied multiple times, and " 

713 'each time adds to the existing value. Accepts either ":all:" to ' 

714 'disable all source packages, ":none:" to empty the set, or one ' 

715 "or more package names with commas between them. Packages " 

716 "without binary distributions will fail to install when this " 

717 "option is used on them.", 

718 ) 

719 

720 

721def _get_release_control(values: Values, option: Option) -> Any: 

722 """Get a release_control object.""" 

723 return getattr(values, option.dest) 

724 

725 

726def _handle_all_releases( 

727 option: Option, opt_str: str, value: str, parser: OptionParser 

728) -> None: 

729 existing = _get_release_control(parser.values, option) 

730 existing.handle_mutual_excludes( 

731 value, 

732 existing.all_releases, 

733 existing.only_final, 

734 "all_releases", 

735 ) 

736 

737 

738def _handle_only_final( 

739 option: Option, opt_str: str, value: str, parser: OptionParser 

740) -> None: 

741 existing = _get_release_control(parser.values, option) 

742 existing.handle_mutual_excludes( 

743 value, 

744 existing.only_final, 

745 existing.all_releases, 

746 "only_final", 

747 ) 

748 

749 

750def all_releases() -> Option: 

751 release_control = ReleaseControl(set(), set()) 

752 return Option( 

753 "--all-releases", 

754 dest="release_control", 

755 action="callback", 

756 callback=_handle_all_releases, 

757 type="str", 

758 default=release_control, 

759 help="Allow all release types (including pre-releases) for a package. " 

760 "Can be supplied multiple times, and each time adds to the existing " 

761 'value. Accepts either ":all:" to allow pre-releases for all ' 

762 'packages, ":none:" to empty the set (notice the colons), or one or ' 

763 "more package names with commas between them (no colons). Cannot be " 

764 "used with --pre.", 

765 ) 

766 

767 

768def only_final() -> Option: 

769 release_control = ReleaseControl(set(), set()) 

770 return Option( 

771 "--only-final", 

772 dest="release_control", 

773 action="callback", 

774 callback=_handle_only_final, 

775 type="str", 

776 default=release_control, 

777 help="Only allow final releases (no pre-releases) for a package. Can be " 

778 "supplied multiple times, and each time adds to the existing value. " 

779 'Accepts either ":all:" to disable pre-releases for all packages, ' 

780 '":none:" to empty the set, or one or more package names with commas ' 

781 "between them. Cannot be used with --pre.", 

782 ) 

783 

784 

785def check_release_control_exclusive(options: Values) -> None: 

786 """ 

787 Raise an error if --pre is used with --all-releases or --only-final, 

788 and transform --pre into --all-releases :all: if used alone. 

789 """ 

790 if not hasattr(options, "pre") or not options.pre: 

791 return 

792 

793 release_control = options.release_control 

794 if release_control.all_releases or release_control.only_final: 

795 raise CommandError("--pre cannot be used with --all-releases or --only-final.") 

796 

797 # Transform --pre into --all-releases :all: 

798 release_control.all_releases.add(":all:") 

799 

800 

801platforms: Callable[..., Option] = partial( 

802 Option, 

803 "--platform", 

804 dest="platforms", 

805 metavar="platform", 

806 action="append", 

807 default=None, 

808 help=( 

809 "Only use wheels compatible with <platform>. Defaults to the " 

810 "platform of the running system. Use this option multiple times to " 

811 "specify multiple platforms supported by the target interpreter." 

812 ), 

813) 

814 

815 

816# This was made a separate function for unit-testing purposes. 

817def _convert_python_version(value: str) -> tuple[tuple[int, ...], str | None]: 

818 """ 

819 Convert a version string like "3", "37", or "3.7.3" into a tuple of ints. 

820 

821 :return: A 2-tuple (version_info, error_msg), where `error_msg` is 

822 non-None if and only if there was a parsing error. 

823 """ 

824 if not value: 

825 # The empty string is the same as not providing a value. 

826 return (None, None) 

827 

828 parts = value.split(".") 

829 if len(parts) > 3: 

830 return ((), "at most three version parts are allowed") 

831 

832 if len(parts) == 1: 

833 # Then we are in the case of "3" or "37". 

834 value = parts[0] 

835 if len(value) > 1: 

836 parts = [value[0], value[1:]] 

837 

838 try: 

839 version_info = tuple(int(part) for part in parts) 

840 except ValueError: 

841 return ((), "each version part must be an integer") 

842 

843 return (version_info, None) 

844 

845 

846def _handle_python_version( 

847 option: Option, opt_str: str, value: str, parser: OptionParser 

848) -> None: 

849 """ 

850 Handle a provided --python-version value. 

851 """ 

852 version_info, error_msg = _convert_python_version(value) 

853 if error_msg is not None: 

854 msg = f"invalid --python-version value: {value!r}: {error_msg}" 

855 raise_option_error(parser, option=option, msg=msg) 

856 

857 parser.values.python_version = version_info 

858 

859 

860python_version: Callable[..., Option] = partial( 

861 Option, 

862 "--python-version", 

863 dest="python_version", 

864 metavar="python_version", 

865 action="callback", 

866 callback=_handle_python_version, 

867 type="str", 

868 default=None, 

869 help=dedent("""\ 

870 The Python interpreter version to use for wheel and "Requires-Python" 

871 compatibility checks. Defaults to a version derived from the running 

872 interpreter. The version can be specified using up to three dot-separated 

873 integers (e.g. "3" for 3.0.0, "3.7" for 3.7.0, or "3.7.3"). A major-minor 

874 version can also be given as a string without dots (e.g. "37" for 3.7.0). 

875 """), 

876) 

877 

878 

879implementation: Callable[..., Option] = partial( 

880 Option, 

881 "--implementation", 

882 dest="implementation", 

883 metavar="implementation", 

884 default=None, 

885 help=( 

886 "Only use wheels compatible with Python " 

887 "implementation <implementation>, e.g. 'pp', 'jy', 'cp', " 

888 " or 'ip'. If not specified, then the current " 

889 "interpreter implementation is used. Use 'py' to force " 

890 "implementation-agnostic wheels." 

891 ), 

892) 

893 

894 

895abis: Callable[..., Option] = partial( 

896 Option, 

897 "--abi", 

898 dest="abis", 

899 metavar="abi", 

900 action="append", 

901 default=None, 

902 help=( 

903 "Only use wheels compatible with Python abi <abi>, e.g. 'pypy_41'. " 

904 "If not specified, then the current interpreter abi tag is used. " 

905 "Use this option multiple times to specify multiple abis supported " 

906 "by the target interpreter. Generally you will need to specify " 

907 "--implementation, --platform, and --python-version when using this " 

908 "option." 

909 ), 

910) 

911 

912 

913def add_target_python_options(cmd_opts: OptionGroup) -> None: 

914 cmd_opts.add_option(platforms()) 

915 cmd_opts.add_option(python_version()) 

916 cmd_opts.add_option(implementation()) 

917 cmd_opts.add_option(abis()) 

918 

919 

920def make_target_python(options: Values) -> TargetPython: 

921 target_python = TargetPython( 

922 platforms=options.platforms, 

923 py_version_info=options.python_version, 

924 abis=options.abis, 

925 implementation=options.implementation, 

926 ) 

927 

928 return target_python 

929 

930 

931def prefer_binary() -> Option: 

932 return Option( 

933 "--prefer-binary", 

934 dest="prefer_binary", 

935 action="store_true", 

936 default=False, 

937 help=( 

938 "Prefer binary packages over source packages, even if the " 

939 "source packages are newer." 

940 ), 

941 ) 

942 

943 

944cache_dir: Callable[..., Option] = partial( 

945 PipOption, 

946 "--cache-dir", 

947 dest="cache_dir", 

948 default=USER_CACHE_DIR, 

949 metavar="dir", 

950 type="path", 

951 help="Store the cache data in <dir>.", 

952) 

953 

954 

955def _handle_no_cache_dir( 

956 option: Option, opt: str, value: str, parser: OptionParser 

957) -> None: 

958 """ 

959 Process a value provided for the --no-cache-dir option. 

960 

961 This is an optparse.Option callback for the --no-cache-dir option. 

962 """ 

963 # The value argument will be None if --no-cache-dir is passed via the 

964 # command-line, since the option doesn't accept arguments. However, 

965 # the value can be non-None if the option is triggered e.g. by an 

966 # environment variable, like PIP_NO_CACHE_DIR=true. 

967 if value is not None: 

968 # Then parse the string value to get argument error-checking. 

969 try: 

970 strtobool(value) 

971 except ValueError as exc: 

972 raise_option_error(parser, option=option, msg=str(exc)) 

973 

974 # Originally, setting PIP_NO_CACHE_DIR to a value that strtobool() 

975 # converted to 0 (like "false" or "no") caused cache_dir to be disabled 

976 # rather than enabled (logic would say the latter). Thus, we disable 

977 # the cache directory not just on values that parse to True, but (for 

978 # backwards compatibility reasons) also on values that parse to False. 

979 # In other words, always set it to False if the option is provided in 

980 # some (valid) form. 

981 parser.values.cache_dir = False 

982 

983 

984no_cache: Callable[..., Option] = partial( 

985 Option, 

986 "--no-cache-dir", 

987 dest="cache_dir", 

988 action="callback", 

989 callback=_handle_no_cache_dir, 

990 help="Disable the cache.", 

991) 

992 

993no_deps: Callable[..., Option] = partial( 

994 Option, 

995 "--no-deps", 

996 "--no-dependencies", 

997 dest="ignore_dependencies", 

998 action="store_true", 

999 default=False, 

1000 help="Don't install package dependencies.", 

1001) 

1002 

1003 

1004def _handle_refresh_package( 

1005 option: Option, opt_str: str, value: str, parser: OptionParser 

1006) -> None: 

1007 if value.startswith("-"): 

1008 raise CommandError("--refresh-package option requires 1 argument.") 

1009 

1010 existing: set[str] = getattr(parser.values, option.dest) 

1011 

1012 new = value.split(",") 

1013 while ":all:" in new: 

1014 existing.clear() 

1015 existing.add(":all:") 

1016 del new[: new.index(":all:") + 1] 

1017 if ":none:" not in new: 

1018 return 

1019 

1020 for name in new: 

1021 if name == ":none:": 

1022 existing.clear() 

1023 else: 

1024 existing.add(canonicalize_name(name)) 

1025 

1026 

1027def refresh_package() -> Option: 

1028 return Option( 

1029 "--refresh-package", 

1030 dest="refresh_package", 

1031 action="callback", 

1032 callback=_handle_refresh_package, 

1033 type="str", 

1034 default=set(), 

1035 help="Refresh package index information for the given packages instead " 

1036 "of using cached responses. Accepts ':all:' to apply " 

1037 "to all packages, or a comma-separated list of package names.", 

1038 ) 

1039 

1040 

1041only_deps: Callable[..., Option] = partial( 

1042 Option, 

1043 "--only-deps", 

1044 "--only-dependencies", 

1045 dest="only_dependencies", 

1046 action="store_true", 

1047 default=False, 

1048 help=( 

1049 "Take only the dependencies of the provided requirements into account, " 

1050 "not the requirements themselves. Cannot be used in combination with " 

1051 "--no-deps, --group, --requirement, or --requirements-from-script. " 

1052 "No user-supplied requirements will be handled, even if they were " 

1053 "dependencies of other user-supplied requirements." 

1054 ), 

1055) 

1056 

1057 

1058def _handle_dependency_group( 

1059 option: Option, opt: str, value: str, parser: OptionParser 

1060) -> None: 

1061 """ 

1062 Process a value provided for the --group option. 

1063 

1064 Splits on the rightmost ":", and validates that the path (if present) ends 

1065 in `pyproject.toml`. Defaults the path to `pyproject.toml` when one is not given. 

1066 

1067 `:` cannot appear in dependency group names, so this is a safe and simple parse. 

1068 

1069 This is an optparse.Option callback for the dependency_groups option. 

1070 """ 

1071 path, sep, groupname = value.rpartition(":") 

1072 if not sep: 

1073 path = "pyproject.toml" 

1074 else: 

1075 # check for 'pyproject.toml' filenames using pathlib 

1076 if pathlib.PurePath(path).name != "pyproject.toml": 

1077 msg = "group paths use 'pyproject.toml' filenames" 

1078 raise_option_error(parser, option=option, msg=msg) 

1079 

1080 parser.values.dependency_groups.append((path, groupname)) 

1081 

1082 

1083dependency_groups: Callable[..., Option] = partial( 

1084 Option, 

1085 "--group", 

1086 dest="dependency_groups", 

1087 default=[], 

1088 type=str, 

1089 action="callback", 

1090 callback=_handle_dependency_group, 

1091 metavar="[path:]group", 

1092 help='Install a named dependency-group from a "pyproject.toml" file. ' 

1093 'If a path is given, the name of the file must be "pyproject.toml". ' 

1094 'Defaults to using "pyproject.toml" in the current directory.', 

1095) 

1096 

1097ignore_requires_python: Callable[..., Option] = partial( 

1098 Option, 

1099 "--ignore-requires-python", 

1100 dest="ignore_requires_python", 

1101 action="store_true", 

1102 help="Ignore the Requires-Python information.", 

1103) 

1104 

1105 

1106no_build_isolation: Callable[..., Option] = partial( 

1107 Option, 

1108 "--no-build-isolation", 

1109 dest="build_isolation", 

1110 action="store_false", 

1111 default=True, 

1112 help="Disable isolation when building a modern source distribution. " 

1113 "Build dependencies specified by PEP 518 must be already installed " 

1114 "if this option is used.", 

1115) 

1116 

1117check_build_deps: Callable[..., Option] = partial( 

1118 Option, 

1119 "--check-build-dependencies", 

1120 dest="check_build_deps", 

1121 action="store_true", 

1122 default=False, 

1123 help="Check the build dependencies.", 

1124) 

1125 

1126 

1127use_pep517: Any = partial( 

1128 Option, 

1129 "--use-pep517", 

1130 dest="use_pep517", 

1131 action="store_true", 

1132 default=True, 

1133 help=SUPPRESS_HELP, 

1134) 

1135 

1136 

1137def _handle_config_settings( 

1138 option: Option, opt_str: str, value: str, parser: OptionParser 

1139) -> None: 

1140 key, sep, val = value.partition("=") 

1141 if sep != "=": 

1142 parser.error(f"Arguments to {opt_str} must be of the form KEY=VAL") 

1143 dest = getattr(parser.values, option.dest) 

1144 if dest is None: 

1145 dest = {} 

1146 setattr(parser.values, option.dest, dest) 

1147 if key in dest: 

1148 if isinstance(dest[key], list): 

1149 dest[key].append(val) 

1150 else: 

1151 dest[key] = [dest[key], val] 

1152 else: 

1153 dest[key] = val 

1154 

1155 

1156config_settings: Callable[..., Option] = partial( 

1157 Option, 

1158 "-C", 

1159 "--config-settings", 

1160 dest="config_settings", 

1161 type=str, 

1162 action="callback", 

1163 callback=_handle_config_settings, 

1164 metavar="settings", 

1165 help="Configuration settings to be passed to the build backend. " 

1166 "Settings take the form KEY=VALUE. Use multiple --config-settings options " 

1167 "to pass multiple keys to the backend.", 

1168) 

1169 

1170no_clean: Callable[..., Option] = partial( 

1171 Option, 

1172 "--no-clean", 

1173 action="store_true", 

1174 default=False, 

1175 help="Don't clean up build directories.", 

1176) 

1177 

1178pre: Callable[..., Option] = partial( 

1179 Option, 

1180 "--pre", 

1181 action="store_true", 

1182 default=False, 

1183 help="Include pre-release and development versions. By default, " 

1184 "pip only finds stable versions.", 

1185) 

1186 

1187json: Callable[..., Option] = partial( 

1188 Option, 

1189 "--json", 

1190 action="store_true", 

1191 default=False, 

1192 help="Output data in a machine-readable JSON format.", 

1193) 

1194 

1195disable_pip_version_check: Callable[..., Option] = partial( 

1196 Option, 

1197 "--disable-pip-version-check", 

1198 dest="disable_pip_version_check", 

1199 action="store_true", 

1200 default=False, 

1201 help="Don't periodically check PyPI to determine whether a new version " 

1202 "of pip is available for download. Implied with --no-index.", 

1203) 

1204 

1205root_user_action: Callable[..., Option] = partial( 

1206 Option, 

1207 "--root-user-action", 

1208 dest="root_user_action", 

1209 default="warn", 

1210 choices=["warn", "ignore"], 

1211 help="Action if pip is run as a root user [warn, ignore] (default: warn)", 

1212) 

1213 

1214 

1215def _handle_merge_hash( 

1216 option: Option, opt_str: str, value: str, parser: OptionParser 

1217) -> None: 

1218 """Given a value spelled "algo:digest", append the digest to a list 

1219 pointed to in a dict by the algo name.""" 

1220 if not parser.values.hashes: 

1221 parser.values.hashes = {} 

1222 try: 

1223 algo, digest = value.split(":", 1) 

1224 except ValueError: 

1225 parser.error( 

1226 f"Arguments to {opt_str} must be a hash name " 

1227 "followed by a value, like --hash=sha256:" 

1228 "abcde..." 

1229 ) 

1230 if algo not in STRONG_HASHES: 

1231 parser.error( 

1232 "Allowed hash algorithms for {} are {}.".format( 

1233 opt_str, ", ".join(STRONG_HASHES) 

1234 ) 

1235 ) 

1236 parser.values.hashes.setdefault(algo, []).append(digest) 

1237 

1238 

1239hash: Callable[..., Option] = partial( 

1240 Option, 

1241 "--hash", 

1242 # Hash values eventually end up in InstallRequirement.hashes due to 

1243 # __dict__ copying in process_line(). 

1244 dest="hashes", 

1245 action="callback", 

1246 callback=_handle_merge_hash, 

1247 type="string", 

1248 help="Verify that the package's archive matches this " 

1249 "hash before installing. Example: --hash=sha256:abcdef...", 

1250) 

1251 

1252 

1253require_hashes: Callable[..., Option] = partial( 

1254 Option, 

1255 "--require-hashes", 

1256 dest="require_hashes", 

1257 action="store_true", 

1258 default=False, 

1259 help="Require a hash to check each requirement against, for " 

1260 "repeatable installs. This option is implied when any package in a " 

1261 "requirements file has a --hash option.", 

1262) 

1263 

1264 

1265no_require_hashes: Callable[..., Option] = partial( 

1266 Option, 

1267 "--no-require-hashes", 

1268 dest="no_require_hashes", 

1269 action="store_true", 

1270 default=False, 

1271 help="Do not automatically enable --require-hashes " 

1272 "when encountering a requirement with hashes.", 

1273) 

1274 

1275 

1276list_path: Callable[..., Option] = partial( 

1277 PipOption, 

1278 "--path", 

1279 dest="path", 

1280 type="path", 

1281 action="append", 

1282 help="Restrict to the specified installation path for listing " 

1283 "packages (can be used multiple times).", 

1284) 

1285 

1286 

1287def check_list_path_option(options: Values) -> None: 

1288 if options.path and (options.user or options.local): 

1289 raise CommandError("Cannot combine '--path' with '--user' or '--local'") 

1290 

1291 

1292list_exclude: Callable[..., Option] = partial( 

1293 PipOption, 

1294 "--exclude", 

1295 dest="excludes", 

1296 action="append", 

1297 metavar="package", 

1298 type="package_name", 

1299 help="Exclude specified package from the output", 

1300) 

1301 

1302 

1303no_python_version_warning: Callable[..., Option] = partial( 

1304 Option, 

1305 "--no-python-version-warning", 

1306 dest="no_python_version_warning", 

1307 action="store_true", 

1308 default=False, 

1309 help=SUPPRESS_HELP, # No-op, a hold-over from the Python 2->3 transition. 

1310) 

1311 

1312 

1313# Features that are now always on. A warning is printed if they are used. 

1314ALWAYS_ENABLED_FEATURES = [ 

1315 "truststore", # always on since 24.2 

1316 "no-binary-enable-wheel-cache", # always on since 23.1 

1317 "build-constraint", # always on since 26.2 

1318] 

1319 

1320use_new_feature: Callable[..., Option] = partial( 

1321 Option, 

1322 "--use-feature", 

1323 dest="features_enabled", 

1324 metavar="feature", 

1325 action="append", 

1326 default=[], 

1327 choices=[ 

1328 "fast-deps", 

1329 "inprocess-build-deps", 

1330 "venv-isolation", 

1331 ] 

1332 + ALWAYS_ENABLED_FEATURES, 

1333 help="Enable new functionality, that may be backward incompatible.", 

1334) 

1335 

1336use_deprecated_feature: Callable[..., Option] = partial( 

1337 Option, 

1338 "--use-deprecated", 

1339 dest="deprecated_features_enabled", 

1340 metavar="feature", 

1341 action="append", 

1342 default=[], 

1343 choices=[ 

1344 "legacy-resolver", 

1345 "legacy-certs", 

1346 ], 

1347 help=("Enable deprecated functionality, that will be removed in the future."), 

1348) 

1349 

1350########## 

1351# groups # 

1352########## 

1353 

1354general_group: dict[str, Any] = { 

1355 "name": "General Options", 

1356 "options": [ 

1357 help_, 

1358 debug_mode, 

1359 isolated_mode, 

1360 require_virtualenv, 

1361 python, 

1362 verbose, 

1363 version, 

1364 quiet, 

1365 log, 

1366 no_input, 

1367 keyring_provider, 

1368 proxy, 

1369 no_proxy_env, 

1370 retries, 

1371 timeout, 

1372 exists_action, 

1373 trusted_host, 

1374 cert, 

1375 client_cert, 

1376 cache_dir, 

1377 no_cache, 

1378 disable_pip_version_check, 

1379 no_color, 

1380 no_python_version_warning, 

1381 use_new_feature, 

1382 use_deprecated_feature, 

1383 resume_retries, 

1384 ], 

1385} 

1386 

1387index_group: dict[str, Any] = { 

1388 "name": "Package Index Options", 

1389 "options": [ 

1390 index_url, 

1391 extra_index_url, 

1392 no_index, 

1393 refresh_package, 

1394 find_links, 

1395 uploaded_prior_to, 

1396 ], 

1397} 

1398 

1399package_selection_group: dict[str, Any] = { 

1400 "name": "Package Selection Options", 

1401 "options": [ 

1402 pre, 

1403 all_releases, 

1404 only_final, 

1405 no_binary, 

1406 only_binary, 

1407 prefer_binary, 

1408 ], 

1409}