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) " 

543 "or duration in integer days with syntax `P<n>D` " 

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

545 "Only effective when using indexes that provide " 

546 "upload-time metadata." 

547 ), 

548 ) 

549 

550 

551def trusted_host() -> Option: 

552 return Option( 

553 "--trusted-host", 

554 dest="trusted_hosts", 

555 action="append", 

556 metavar="HOSTNAME", 

557 default=[], 

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

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

560 ) 

561 

562 

563def constraints() -> Option: 

564 return Option( 

565 "-c", 

566 "--constraint", 

567 dest="constraints", 

568 action="append", 

569 default=[], 

570 metavar="file", 

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

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

573 ) 

574 

575 

576def build_constraints() -> Option: 

577 return Option( 

578 "--build-constraint", 

579 dest="build_constraints", 

580 action="append", 

581 type="str", 

582 default=[], 

583 metavar="file", 

584 help=( 

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

586 "This option can be used multiple times." 

587 ), 

588 ) 

589 

590 

591def requirements() -> Option: 

592 return Option( 

593 "-r", 

594 "--requirement", 

595 dest="requirements", 

596 action="append", 

597 default=[], 

598 metavar="file", 

599 help=( 

600 "Install from the given requirements file. " 

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

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

603 "This option can be used multiple times." 

604 ), 

605 ) 

606 

607 

608def requirements_from_scripts() -> Option: 

609 return Option( 

610 "--requirements-from-script", 

611 action="append", 

612 default=[], 

613 dest="requirements_from_scripts", 

614 metavar="file", 

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

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

617 ) 

618 

619 

620def editable() -> Option: 

621 return Option( 

622 "-e", 

623 "--editable", 

624 dest="editables", 

625 action="append", 

626 default=[], 

627 metavar="path/url", 

628 help=( 

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

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

631 ), 

632 ) 

633 

634 

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

636 value = os.path.abspath(value) 

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

638 

639 

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

641 PipOption, 

642 "--src", 

643 "--source", 

644 "--source-dir", 

645 "--source-directory", 

646 dest="src_dir", 

647 type="path", 

648 metavar="dir", 

649 default=get_src_prefix(), 

650 action="callback", 

651 callback=_handle_src, 

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

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

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

655) 

656 

657 

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

659 """Get a format_control object.""" 

660 return getattr(values, option.dest) 

661 

662 

663def _handle_no_binary( 

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

665) -> None: 

666 existing = _get_format_control(parser.values, option) 

667 FormatControl.handle_mutual_excludes( 

668 value, 

669 existing.no_binary, 

670 existing.only_binary, 

671 ) 

672 

673 

674def _handle_only_binary( 

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

676) -> None: 

677 existing = _get_format_control(parser.values, option) 

678 FormatControl.handle_mutual_excludes( 

679 value, 

680 existing.only_binary, 

681 existing.no_binary, 

682 ) 

683 

684 

685def no_binary() -> Option: 

686 format_control = FormatControl(set(), set()) 

687 return Option( 

688 "--no-binary", 

689 dest="format_control", 

690 action="callback", 

691 callback=_handle_no_binary, 

692 type="str", 

693 default=format_control, 

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

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

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

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

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

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

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

701 ) 

702 

703 

704def only_binary() -> Option: 

705 format_control = FormatControl(set(), set()) 

706 return Option( 

707 "--only-binary", 

708 dest="format_control", 

709 action="callback", 

710 callback=_handle_only_binary, 

711 type="str", 

712 default=format_control, 

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

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

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

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

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

718 "option is used on them.", 

719 ) 

720 

721 

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

723 """Get a release_control object.""" 

724 return getattr(values, option.dest) 

725 

726 

727def _handle_all_releases( 

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

729) -> None: 

730 existing = _get_release_control(parser.values, option) 

731 existing.handle_mutual_excludes( 

732 value, 

733 existing.all_releases, 

734 existing.only_final, 

735 "all_releases", 

736 ) 

737 

738 

739def _handle_only_final( 

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

741) -> None: 

742 existing = _get_release_control(parser.values, option) 

743 existing.handle_mutual_excludes( 

744 value, 

745 existing.only_final, 

746 existing.all_releases, 

747 "only_final", 

748 ) 

749 

750 

751def all_releases() -> Option: 

752 release_control = ReleaseControl(set(), set()) 

753 return Option( 

754 "--all-releases", 

755 dest="release_control", 

756 action="callback", 

757 callback=_handle_all_releases, 

758 type="str", 

759 default=release_control, 

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

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

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

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

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

765 "used with --pre.", 

766 ) 

767 

768 

769def only_final() -> Option: 

770 release_control = ReleaseControl(set(), set()) 

771 return Option( 

772 "--only-final", 

773 dest="release_control", 

774 action="callback", 

775 callback=_handle_only_final, 

776 type="str", 

777 default=release_control, 

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

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

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

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

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

783 ) 

784 

785 

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

787 """ 

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

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

790 """ 

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

792 return 

793 

794 release_control = options.release_control 

795 if release_control.all_releases or release_control.only_final: 

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

797 

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

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

800 

801 

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

803 Option, 

804 "--platform", 

805 dest="platforms", 

806 metavar="platform", 

807 action="append", 

808 default=None, 

809 help=( 

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

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

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

813 ), 

814) 

815 

816 

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

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

819 """ 

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

821 

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

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

824 """ 

825 if not value: 

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

827 return (None, None) 

828 

829 parts = value.split(".") 

830 if len(parts) > 3: 

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

832 

833 if len(parts) == 1: 

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

835 value = parts[0] 

836 if len(value) > 1: 

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

838 

839 try: 

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

841 except ValueError: 

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

843 

844 return (version_info, None) 

845 

846 

847def _handle_python_version( 

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

849) -> None: 

850 """ 

851 Handle a provided --python-version value. 

852 """ 

853 version_info, error_msg = _convert_python_version(value) 

854 if error_msg is not None: 

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

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

857 

858 parser.values.python_version = version_info 

859 

860 

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

862 Option, 

863 "--python-version", 

864 dest="python_version", 

865 metavar="python_version", 

866 action="callback", 

867 callback=_handle_python_version, 

868 type="str", 

869 default=None, 

870 help=dedent("""\ 

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

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

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

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

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

876 """), 

877) 

878 

879 

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

881 Option, 

882 "--implementation", 

883 dest="implementation", 

884 metavar="implementation", 

885 default=None, 

886 help=( 

887 "Only use wheels compatible with Python " 

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

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

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

891 "implementation-agnostic wheels." 

892 ), 

893) 

894 

895 

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

897 Option, 

898 "--abi", 

899 dest="abis", 

900 metavar="abi", 

901 action="append", 

902 default=None, 

903 help=( 

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

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

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

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

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

909 "option." 

910 ), 

911) 

912 

913 

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

915 cmd_opts.add_option(platforms()) 

916 cmd_opts.add_option(python_version()) 

917 cmd_opts.add_option(implementation()) 

918 cmd_opts.add_option(abis()) 

919 

920 

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

922 target_python = TargetPython( 

923 platforms=options.platforms, 

924 py_version_info=options.python_version, 

925 abis=options.abis, 

926 implementation=options.implementation, 

927 ) 

928 

929 return target_python 

930 

931 

932def prefer_binary() -> Option: 

933 return Option( 

934 "--prefer-binary", 

935 dest="prefer_binary", 

936 action="store_true", 

937 default=False, 

938 help=( 

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

940 "source packages are newer." 

941 ), 

942 ) 

943 

944 

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

946 PipOption, 

947 "--cache-dir", 

948 dest="cache_dir", 

949 default=USER_CACHE_DIR, 

950 metavar="dir", 

951 type="path", 

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

953) 

954 

955 

956def _handle_no_cache_dir( 

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

958) -> None: 

959 """ 

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

961 

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

963 """ 

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

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

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

967 # environment variable, like PIP_NO_CACHE_DIR=true. 

968 if value is not None: 

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

970 try: 

971 strtobool(value) 

972 except ValueError as exc: 

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

974 

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

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

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

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

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

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

981 # some (valid) form. 

982 parser.values.cache_dir = False 

983 

984 

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

986 Option, 

987 "--no-cache-dir", 

988 dest="cache_dir", 

989 action="callback", 

990 callback=_handle_no_cache_dir, 

991 help="Disable the cache.", 

992) 

993 

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

995 Option, 

996 "--no-deps", 

997 "--no-dependencies", 

998 dest="ignore_dependencies", 

999 action="store_true", 

1000 default=False, 

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

1002) 

1003 

1004 

1005def _handle_refresh_package( 

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

1007) -> None: 

1008 if value.startswith("-"): 

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

1010 

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

1012 

1013 new = value.split(",") 

1014 while ":all:" in new: 

1015 existing.clear() 

1016 existing.add(":all:") 

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

1018 if ":none:" not in new: 

1019 return 

1020 

1021 for name in new: 

1022 if name == ":none:": 

1023 existing.clear() 

1024 else: 

1025 existing.add(canonicalize_name(name)) 

1026 

1027 

1028def refresh_package() -> Option: 

1029 return Option( 

1030 "--refresh-package", 

1031 dest="refresh_package", 

1032 action="callback", 

1033 callback=_handle_refresh_package, 

1034 type="str", 

1035 default=set(), 

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

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

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

1039 ) 

1040 

1041 

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

1043 Option, 

1044 "--only-deps", 

1045 "--only-dependencies", 

1046 dest="only_dependencies", 

1047 action="store_true", 

1048 default=False, 

1049 help=( 

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

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

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

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

1054 "dependencies of other user-supplied requirements." 

1055 ), 

1056) 

1057 

1058 

1059def _handle_dependency_group( 

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

1061) -> None: 

1062 """ 

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

1064 

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

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

1067 

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

1069 

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

1071 """ 

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

1073 if not sep: 

1074 path = "pyproject.toml" 

1075 else: 

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

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

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

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

1080 

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

1082 

1083 

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

1085 Option, 

1086 "--group", 

1087 dest="dependency_groups", 

1088 default=[], 

1089 type=str, 

1090 action="callback", 

1091 callback=_handle_dependency_group, 

1092 metavar="[path:]group", 

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

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

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

1096) 

1097 

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

1099 Option, 

1100 "--ignore-requires-python", 

1101 dest="ignore_requires_python", 

1102 action="store_true", 

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

1104) 

1105 

1106 

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

1108 Option, 

1109 "--no-build-isolation", 

1110 dest="build_isolation", 

1111 action="store_false", 

1112 default=True, 

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

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

1115 "if this option is used.", 

1116) 

1117 

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

1119 Option, 

1120 "--check-build-dependencies", 

1121 dest="check_build_deps", 

1122 action="store_true", 

1123 default=False, 

1124 help="Check the build dependencies.", 

1125) 

1126 

1127 

1128use_pep517: Any = partial( 

1129 Option, 

1130 "--use-pep517", 

1131 dest="use_pep517", 

1132 action="store_true", 

1133 default=True, 

1134 help=SUPPRESS_HELP, 

1135) 

1136 

1137 

1138def _handle_config_settings( 

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

1140) -> None: 

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

1142 if sep != "=": 

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

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

1145 if dest is None: 

1146 dest = {} 

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

1148 if key in dest: 

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

1150 dest[key].append(val) 

1151 else: 

1152 dest[key] = [dest[key], val] 

1153 else: 

1154 dest[key] = val 

1155 

1156 

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

1158 Option, 

1159 "-C", 

1160 "--config-settings", 

1161 dest="config_settings", 

1162 type=str, 

1163 action="callback", 

1164 callback=_handle_config_settings, 

1165 metavar="settings", 

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

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

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

1169) 

1170 

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

1172 Option, 

1173 "--no-clean", 

1174 action="store_true", 

1175 default=False, 

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

1177) 

1178 

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

1180 Option, 

1181 "--pre", 

1182 action="store_true", 

1183 default=False, 

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

1185 "pip only finds stable versions.", 

1186) 

1187 

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

1189 Option, 

1190 "--json", 

1191 action="store_true", 

1192 default=False, 

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

1194) 

1195 

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

1197 Option, 

1198 "--disable-pip-version-check", 

1199 dest="disable_pip_version_check", 

1200 action="store_true", 

1201 default=False, 

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

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

1204) 

1205 

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

1207 Option, 

1208 "--root-user-action", 

1209 dest="root_user_action", 

1210 default="warn", 

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

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

1213) 

1214 

1215 

1216def _handle_merge_hash( 

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

1218) -> None: 

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

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

1221 if not parser.values.hashes: 

1222 parser.values.hashes = {} 

1223 try: 

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

1225 except ValueError: 

1226 parser.error( 

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

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

1229 "abcde..." 

1230 ) 

1231 if algo not in STRONG_HASHES: 

1232 parser.error( 

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

1234 opt_str, ", ".join(STRONG_HASHES) 

1235 ) 

1236 ) 

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

1238 

1239 

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

1241 Option, 

1242 "--hash", 

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

1244 # __dict__ copying in process_line(). 

1245 dest="hashes", 

1246 action="callback", 

1247 callback=_handle_merge_hash, 

1248 type="string", 

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

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

1251) 

1252 

1253 

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

1255 Option, 

1256 "--require-hashes", 

1257 dest="require_hashes", 

1258 action="store_true", 

1259 default=False, 

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

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

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

1263) 

1264 

1265 

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

1267 Option, 

1268 "--no-require-hashes", 

1269 dest="no_require_hashes", 

1270 action="store_true", 

1271 default=False, 

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

1273 "when encountering a requirement with hashes.", 

1274) 

1275 

1276 

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

1278 PipOption, 

1279 "--path", 

1280 dest="path", 

1281 type="path", 

1282 action="append", 

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

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

1285) 

1286 

1287 

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

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

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

1291 

1292 

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

1294 PipOption, 

1295 "--exclude", 

1296 dest="excludes", 

1297 action="append", 

1298 metavar="package", 

1299 type="package_name", 

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

1301) 

1302 

1303 

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

1305 Option, 

1306 "--no-python-version-warning", 

1307 dest="no_python_version_warning", 

1308 action="store_true", 

1309 default=False, 

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

1311) 

1312 

1313 

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

1315ALWAYS_ENABLED_FEATURES = [ 

1316 "truststore", # always on since 24.2 

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

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

1319] 

1320 

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

1322 Option, 

1323 "--use-feature", 

1324 dest="features_enabled", 

1325 metavar="feature", 

1326 action="append", 

1327 default=[], 

1328 choices=[ 

1329 "fast-deps", 

1330 "inprocess-build-deps", 

1331 "venv-isolation", 

1332 ] 

1333 + ALWAYS_ENABLED_FEATURES, 

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

1335) 

1336 

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

1338 Option, 

1339 "--use-deprecated", 

1340 dest="deprecated_features_enabled", 

1341 metavar="feature", 

1342 action="append", 

1343 default=[], 

1344 choices=[ 

1345 "legacy-resolver", 

1346 "legacy-certs", 

1347 ], 

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

1349) 

1350 

1351########## 

1352# groups # 

1353########## 

1354 

1355general_group: dict[str, Any] = { 

1356 "name": "General Options", 

1357 "options": [ 

1358 help_, 

1359 debug_mode, 

1360 isolated_mode, 

1361 require_virtualenv, 

1362 python, 

1363 verbose, 

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}