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

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

262 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 datetime import datetime, timedelta, timezone 

20from functools import partial 

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

22from textwrap import dedent 

23from typing import Any, Callable 

24 

25from pip._vendor.packaging.utils import canonicalize_name 

26 

27from pip._internal.cli.parser import ConfigOptionParser 

28from pip._internal.exceptions import CommandError 

29from pip._internal.locations import USER_CACHE_DIR, get_src_prefix 

30from pip._internal.models.format_control import FormatControl 

31from pip._internal.models.index import PyPI 

32from pip._internal.models.release_control import ReleaseControl 

33from pip._internal.models.target_python import TargetPython 

34from pip._internal.utils import pylock as pylock_utils 

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_dist_restriction(options: Values, check_target: bool = False) -> None: 

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

70 

71 :param options: The OptionParser options. 

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

73 """ 

74 dist_restriction_set = any( 

75 [ 

76 options.python_version, 

77 options.platforms, 

78 options.abis, 

79 options.implementation, 

80 ] 

81 ) 

82 

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

84 sdist_dependencies_allowed = ( 

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

86 ) 

87 

88 # Installations or downloads using dist restrictions must not combine 

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

90 # guaranteed to be locally compatible. 

91 if dist_restriction_set and sdist_dependencies_allowed: 

92 raise CommandError( 

93 "When restricting platform and interpreter constraints using " 

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

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

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

97 ":none:)." 

98 ) 

99 

100 if check_target: 

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

102 raise CommandError( 

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

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

105 ) 

106 

107 for filename in options.requirements: 

108 if dist_restriction_set and pylock_utils.is_valid_pylock_filename(filename): 

109 raise CommandError( 

110 "Patform and interpreter constraints using " 

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

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

113 ) 

114 

115 

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

117 """Function for validating build constraints options. 

118 

119 :param options: The OptionParser options. 

120 """ 

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

122 if not options.build_isolation: 

123 raise CommandError( 

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

125 ) 

126 

127 # Import here to avoid circular imports 

128 from pip._internal.network.session import PipSession 

129 from pip._internal.req.req_file import get_file_content 

130 

131 # Eagerly check build constraints file contents 

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

133 # to check constraints in isolated build process 

134 with PipSession() as session: 

135 for constraint_file in options.build_constraints: 

136 get_file_content(constraint_file, session) 

137 

138 

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

140 return os.path.expanduser(value) 

141 

142 

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

144 return canonicalize_name(value) 

145 

146 

147class PipOption(Option): 

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

149 TYPE_CHECKER = Option.TYPE_CHECKER.copy() 

150 TYPE_CHECKER["package_name"] = _package_name_option_check 

151 TYPE_CHECKER["path"] = _path_option_check 

152 

153 

154########### 

155# options # 

156########### 

157 

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

159 Option, 

160 "-h", 

161 "--help", 

162 dest="help", 

163 action="help", 

164 help="Show help.", 

165) 

166 

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

168 Option, 

169 "--debug", 

170 dest="debug_mode", 

171 action="store_true", 

172 default=False, 

173 help=( 

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

175 "instead of logging them to stderr." 

176 ), 

177) 

178 

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

180 Option, 

181 "--isolated", 

182 dest="isolated_mode", 

183 action="store_true", 

184 default=False, 

185 help=( 

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

187 "configuration." 

188 ), 

189) 

190 

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

192 Option, 

193 "--require-virtualenv", 

194 "--require-venv", 

195 dest="require_venv", 

196 action="store_true", 

197 default=False, 

198 help=( 

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

200 ), 

201) 

202 

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

204 Option, 

205 "--break-system-packages", 

206 dest="override_externally_managed", 

207 action="store_true", 

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

209) 

210 

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

212 Option, 

213 "--python", 

214 dest="python", 

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

216) 

217 

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

219 Option, 

220 "-v", 

221 "--verbose", 

222 dest="verbose", 

223 action="count", 

224 default=0, 

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

226) 

227 

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

229 Option, 

230 "--no-color", 

231 dest="no_color", 

232 action="store_true", 

233 default=False, 

234 help="Suppress colored output.", 

235) 

236 

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

238 Option, 

239 "-V", 

240 "--version", 

241 dest="version", 

242 action="store_true", 

243 help="Show version and exit.", 

244) 

245 

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

247 Option, 

248 "-q", 

249 "--quiet", 

250 dest="quiet", 

251 action="count", 

252 default=0, 

253 help=( 

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

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

256 " levels)." 

257 ), 

258) 

259 

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

261 Option, 

262 "--progress-bar", 

263 dest="progress_bar", 

264 type="choice", 

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

266 default="auto", 

267 help=( 

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

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

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

271 ), 

272) 

273 

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

275 PipOption, 

276 "--log", 

277 "--log-file", 

278 "--local-log", 

279 dest="log", 

280 metavar="path", 

281 type="path", 

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

283) 

284 

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

286 Option, 

287 # Don't ask for input 

288 "--no-input", 

289 dest="no_input", 

290 action="store_true", 

291 default=False, 

292 help="Disable prompting for input.", 

293) 

294 

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

296 Option, 

297 "--keyring-provider", 

298 dest="keyring_provider", 

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

300 default="auto", 

301 help=( 

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

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

304 " (default: %default)" 

305 ), 

306) 

307 

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

309 Option, 

310 "--proxy", 

311 dest="proxy", 

312 type="str", 

313 default="", 

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

315) 

316 

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

318 Option, 

319 "--retries", 

320 dest="retries", 

321 type="int", 

322 default=5, 

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

324) 

325 

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

327 Option, 

328 "--resume-retries", 

329 dest="resume_retries", 

330 type="int", 

331 default=5, 

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

333 "(default: %default)", 

334) 

335 

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

337 Option, 

338 "--timeout", 

339 "--default-timeout", 

340 metavar="sec", 

341 dest="timeout", 

342 type="float", 

343 default=15, 

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

345) 

346 

347 

348def exists_action() -> Option: 

349 return Option( 

350 # Option when path already exist 

351 "--exists-action", 

352 dest="exists_action", 

353 type="choice", 

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

355 default=[], 

356 action="append", 

357 metavar="action", 

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

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

360 ) 

361 

362 

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

364 PipOption, 

365 "--cert", 

366 dest="cert", 

367 type="path", 

368 metavar="path", 

369 help=( 

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

371 "If provided, overrides the default. " 

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

373 "for more information." 

374 ), 

375) 

376 

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

378 PipOption, 

379 "--client-cert", 

380 dest="client_cert", 

381 type="path", 

382 default=None, 

383 metavar="path", 

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

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

386) 

387 

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

389 Option, 

390 "-i", 

391 "--index-url", 

392 "--pypi-url", 

393 dest="index_url", 

394 metavar="URL", 

395 default=PyPI.simple_url, 

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

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

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

399 "in the same format.", 

400) 

401 

402 

403def extra_index_url() -> Option: 

404 return Option( 

405 "--extra-index-url", 

406 dest="extra_index_urls", 

407 metavar="URL", 

408 action="append", 

409 default=[], 

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

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

412 "--index-url.", 

413 ) 

414 

415 

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

417 Option, 

418 "--no-index", 

419 dest="no_index", 

420 action="store_true", 

421 default=False, 

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

423) 

424 

425 

426def find_links() -> Option: 

427 return Option( 

428 "-f", 

429 "--find-links", 

430 dest="find_links", 

431 action="append", 

432 default=[], 

433 metavar="url", 

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

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

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

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

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

439 ) 

440 

441 

442def _handle_uploaded_prior_to( 

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

444) -> None: 

445 """ 

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

447 

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

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

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

451 

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

453 as specified in the simple repository API: 

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

455 """ 

456 if value is None: 

457 return None 

458 

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

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

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

462 # a real need is presented. 

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

464 if match: 

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

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

467 days=days 

468 ) 

469 return 

470 

471 try: 

472 uploaded_prior_to = parse_iso_datetime(value) 

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

474 if uploaded_prior_to.tzinfo is None: 

475 uploaded_prior_to = uploaded_prior_to.astimezone() 

476 parser.values.uploaded_prior_to = uploaded_prior_to 

477 except ValueError as exc: 

478 msg = ( 

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

480 f"Expected an ISO 8601 datetime string " 

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

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

483 ) 

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

485 

486 

487def uploaded_prior_to() -> Option: 

488 return Option( 

489 "--uploaded-prior-to", 

490 dest="uploaded_prior_to", 

491 metavar="datetime_or_duration", 

492 action="callback", 

493 callback=_handle_uploaded_prior_to, 

494 type="str", 

495 help=( 

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

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

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

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

500 "Only effective when installing from indexes that provide " 

501 "upload-time metadata." 

502 ), 

503 ) 

504 

505 

506def trusted_host() -> Option: 

507 return Option( 

508 "--trusted-host", 

509 dest="trusted_hosts", 

510 action="append", 

511 metavar="HOSTNAME", 

512 default=[], 

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

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

515 ) 

516 

517 

518def constraints() -> Option: 

519 return Option( 

520 "-c", 

521 "--constraint", 

522 dest="constraints", 

523 action="append", 

524 default=[], 

525 metavar="file", 

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

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

528 ) 

529 

530 

531def build_constraints() -> Option: 

532 return Option( 

533 "--build-constraint", 

534 dest="build_constraints", 

535 action="append", 

536 type="str", 

537 default=[], 

538 metavar="file", 

539 help=( 

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

541 "This option can be used multiple times." 

542 ), 

543 ) 

544 

545 

546def requirements() -> Option: 

547 return Option( 

548 "-r", 

549 "--requirement", 

550 dest="requirements", 

551 action="append", 

552 default=[], 

553 metavar="file", 

554 help=( 

555 "Install from the given requirements file. " 

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

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

558 "This option can be used multiple times." 

559 ), 

560 ) 

561 

562 

563def requirements_from_scripts() -> Option: 

564 return Option( 

565 "--requirements-from-script", 

566 action="append", 

567 default=[], 

568 dest="requirements_from_scripts", 

569 metavar="file", 

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

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

572 ) 

573 

574 

575def editable() -> Option: 

576 return Option( 

577 "-e", 

578 "--editable", 

579 dest="editables", 

580 action="append", 

581 default=[], 

582 metavar="path/url", 

583 help=( 

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

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

586 ), 

587 ) 

588 

589 

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

591 value = os.path.abspath(value) 

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

593 

594 

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

596 PipOption, 

597 "--src", 

598 "--source", 

599 "--source-dir", 

600 "--source-directory", 

601 dest="src_dir", 

602 type="path", 

603 metavar="dir", 

604 default=get_src_prefix(), 

605 action="callback", 

606 callback=_handle_src, 

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

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

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

610) 

611 

612 

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

614 """Get a format_control object.""" 

615 return getattr(values, option.dest) 

616 

617 

618def _handle_no_binary( 

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

620) -> None: 

621 existing = _get_format_control(parser.values, option) 

622 FormatControl.handle_mutual_excludes( 

623 value, 

624 existing.no_binary, 

625 existing.only_binary, 

626 ) 

627 

628 

629def _handle_only_binary( 

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

631) -> None: 

632 existing = _get_format_control(parser.values, option) 

633 FormatControl.handle_mutual_excludes( 

634 value, 

635 existing.only_binary, 

636 existing.no_binary, 

637 ) 

638 

639 

640def no_binary() -> Option: 

641 format_control = FormatControl(set(), set()) 

642 return Option( 

643 "--no-binary", 

644 dest="format_control", 

645 action="callback", 

646 callback=_handle_no_binary, 

647 type="str", 

648 default=format_control, 

649 help="Do not use binary packages. Can be supplied multiple times, and " 

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

651 'disable all binary packages, ":none:" to empty the set (notice ' 

652 "the colons), or one or more package names with commas between " 

653 "them (no colons). Note that some packages are tricky to compile " 

654 "and may fail to install when this option is used on them.", 

655 ) 

656 

657 

658def only_binary() -> Option: 

659 format_control = FormatControl(set(), set()) 

660 return Option( 

661 "--only-binary", 

662 dest="format_control", 

663 action="callback", 

664 callback=_handle_only_binary, 

665 type="str", 

666 default=format_control, 

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

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

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

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

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

672 "option is used on them.", 

673 ) 

674 

675 

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

677 """Get a release_control object.""" 

678 return getattr(values, option.dest) 

679 

680 

681def _handle_all_releases( 

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

683) -> None: 

684 existing = _get_release_control(parser.values, option) 

685 existing.handle_mutual_excludes( 

686 value, 

687 existing.all_releases, 

688 existing.only_final, 

689 "all_releases", 

690 ) 

691 

692 

693def _handle_only_final( 

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

695) -> None: 

696 existing = _get_release_control(parser.values, option) 

697 existing.handle_mutual_excludes( 

698 value, 

699 existing.only_final, 

700 existing.all_releases, 

701 "only_final", 

702 ) 

703 

704 

705def all_releases() -> Option: 

706 release_control = ReleaseControl(set(), set()) 

707 return Option( 

708 "--all-releases", 

709 dest="release_control", 

710 action="callback", 

711 callback=_handle_all_releases, 

712 type="str", 

713 default=release_control, 

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

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

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

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

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

719 "used with --pre.", 

720 ) 

721 

722 

723def only_final() -> Option: 

724 release_control = ReleaseControl(set(), set()) 

725 return Option( 

726 "--only-final", 

727 dest="release_control", 

728 action="callback", 

729 callback=_handle_only_final, 

730 type="str", 

731 default=release_control, 

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

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

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

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

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

737 ) 

738 

739 

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

741 """ 

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

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

744 """ 

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

746 return 

747 

748 release_control = options.release_control 

749 if release_control.all_releases or release_control.only_final: 

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

751 

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

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

754 

755 

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

757 Option, 

758 "--platform", 

759 dest="platforms", 

760 metavar="platform", 

761 action="append", 

762 default=None, 

763 help=( 

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

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

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

767 ), 

768) 

769 

770 

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

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

773 """ 

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

775 

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

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

778 """ 

779 if not value: 

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

781 return (None, None) 

782 

783 parts = value.split(".") 

784 if len(parts) > 3: 

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

786 

787 if len(parts) == 1: 

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

789 value = parts[0] 

790 if len(value) > 1: 

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

792 

793 try: 

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

795 except ValueError: 

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

797 

798 return (version_info, None) 

799 

800 

801def _handle_python_version( 

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

803) -> None: 

804 """ 

805 Handle a provided --python-version value. 

806 """ 

807 version_info, error_msg = _convert_python_version(value) 

808 if error_msg is not None: 

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

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

811 

812 parser.values.python_version = version_info 

813 

814 

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

816 Option, 

817 "--python-version", 

818 dest="python_version", 

819 metavar="python_version", 

820 action="callback", 

821 callback=_handle_python_version, 

822 type="str", 

823 default=None, 

824 help=dedent( 

825 """\ 

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

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

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

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

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

831 """ 

832 ), 

833) 

834 

835 

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

837 Option, 

838 "--implementation", 

839 dest="implementation", 

840 metavar="implementation", 

841 default=None, 

842 help=( 

843 "Only use wheels compatible with Python " 

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

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

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

847 "implementation-agnostic wheels." 

848 ), 

849) 

850 

851 

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

853 Option, 

854 "--abi", 

855 dest="abis", 

856 metavar="abi", 

857 action="append", 

858 default=None, 

859 help=( 

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

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

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

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

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

865 "option." 

866 ), 

867) 

868 

869 

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

871 cmd_opts.add_option(platforms()) 

872 cmd_opts.add_option(python_version()) 

873 cmd_opts.add_option(implementation()) 

874 cmd_opts.add_option(abis()) 

875 

876 

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

878 target_python = TargetPython( 

879 platforms=options.platforms, 

880 py_version_info=options.python_version, 

881 abis=options.abis, 

882 implementation=options.implementation, 

883 ) 

884 

885 return target_python 

886 

887 

888def prefer_binary() -> Option: 

889 return Option( 

890 "--prefer-binary", 

891 dest="prefer_binary", 

892 action="store_true", 

893 default=False, 

894 help=( 

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

896 "source packages are newer." 

897 ), 

898 ) 

899 

900 

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

902 PipOption, 

903 "--cache-dir", 

904 dest="cache_dir", 

905 default=USER_CACHE_DIR, 

906 metavar="dir", 

907 type="path", 

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

909) 

910 

911 

912def _handle_no_cache_dir( 

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

914) -> None: 

915 """ 

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

917 

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

919 """ 

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

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

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

923 # environment variable, like PIP_NO_CACHE_DIR=true. 

924 if value is not None: 

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

926 try: 

927 strtobool(value) 

928 except ValueError as exc: 

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

930 

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

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

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

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

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

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

937 # some (valid) form. 

938 parser.values.cache_dir = False 

939 

940 

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

942 Option, 

943 "--no-cache-dir", 

944 dest="cache_dir", 

945 action="callback", 

946 callback=_handle_no_cache_dir, 

947 help="Disable the cache.", 

948) 

949 

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

951 Option, 

952 "--no-deps", 

953 "--no-dependencies", 

954 dest="ignore_dependencies", 

955 action="store_true", 

956 default=False, 

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

958) 

959 

960 

961def _handle_dependency_group( 

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

963) -> None: 

964 """ 

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

966 

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

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

969 

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

971 

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

973 """ 

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

975 if not sep: 

976 path = "pyproject.toml" 

977 else: 

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

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

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

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

982 

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

984 

985 

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

987 Option, 

988 "--group", 

989 dest="dependency_groups", 

990 default=[], 

991 type=str, 

992 action="callback", 

993 callback=_handle_dependency_group, 

994 metavar="[path:]group", 

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

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

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

998) 

999 

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

1001 Option, 

1002 "--ignore-requires-python", 

1003 dest="ignore_requires_python", 

1004 action="store_true", 

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

1006) 

1007 

1008 

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

1010 Option, 

1011 "--no-build-isolation", 

1012 dest="build_isolation", 

1013 action="store_false", 

1014 default=True, 

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

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

1017 "if this option is used.", 

1018) 

1019 

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

1021 Option, 

1022 "--check-build-dependencies", 

1023 dest="check_build_deps", 

1024 action="store_true", 

1025 default=False, 

1026 help="Check the build dependencies.", 

1027) 

1028 

1029 

1030use_pep517: Any = partial( 

1031 Option, 

1032 "--use-pep517", 

1033 dest="use_pep517", 

1034 action="store_true", 

1035 default=True, 

1036 help=SUPPRESS_HELP, 

1037) 

1038 

1039 

1040def _handle_config_settings( 

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

1042) -> None: 

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

1044 if sep != "=": 

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

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

1047 if dest is None: 

1048 dest = {} 

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

1050 if key in dest: 

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

1052 dest[key].append(val) 

1053 else: 

1054 dest[key] = [dest[key], val] 

1055 else: 

1056 dest[key] = val 

1057 

1058 

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

1060 Option, 

1061 "-C", 

1062 "--config-settings", 

1063 dest="config_settings", 

1064 type=str, 

1065 action="callback", 

1066 callback=_handle_config_settings, 

1067 metavar="settings", 

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

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

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

1071) 

1072 

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

1074 Option, 

1075 "--no-clean", 

1076 action="store_true", 

1077 default=False, 

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

1079) 

1080 

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

1082 Option, 

1083 "--pre", 

1084 action="store_true", 

1085 default=False, 

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

1087 "pip only finds stable versions.", 

1088) 

1089 

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

1091 Option, 

1092 "--json", 

1093 action="store_true", 

1094 default=False, 

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

1096) 

1097 

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

1099 Option, 

1100 "--disable-pip-version-check", 

1101 dest="disable_pip_version_check", 

1102 action="store_true", 

1103 default=False, 

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

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

1106) 

1107 

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

1109 Option, 

1110 "--root-user-action", 

1111 dest="root_user_action", 

1112 default="warn", 

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

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

1115) 

1116 

1117 

1118def _handle_merge_hash( 

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

1120) -> None: 

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

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

1123 if not parser.values.hashes: 

1124 parser.values.hashes = {} 

1125 try: 

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

1127 except ValueError: 

1128 parser.error( 

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

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

1131 "abcde..." 

1132 ) 

1133 if algo not in STRONG_HASHES: 

1134 parser.error( 

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

1136 opt_str, ", ".join(STRONG_HASHES) 

1137 ) 

1138 ) 

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

1140 

1141 

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

1143 Option, 

1144 "--hash", 

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

1146 # __dict__ copying in process_line(). 

1147 dest="hashes", 

1148 action="callback", 

1149 callback=_handle_merge_hash, 

1150 type="string", 

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

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

1153) 

1154 

1155 

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

1157 Option, 

1158 "--require-hashes", 

1159 dest="require_hashes", 

1160 action="store_true", 

1161 default=False, 

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

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

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

1165) 

1166 

1167 

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

1169 PipOption, 

1170 "--path", 

1171 dest="path", 

1172 type="path", 

1173 action="append", 

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

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

1176) 

1177 

1178 

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

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

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

1182 

1183 

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

1185 PipOption, 

1186 "--exclude", 

1187 dest="excludes", 

1188 action="append", 

1189 metavar="package", 

1190 type="package_name", 

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

1192) 

1193 

1194 

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

1196 Option, 

1197 "--no-python-version-warning", 

1198 dest="no_python_version_warning", 

1199 action="store_true", 

1200 default=False, 

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

1202) 

1203 

1204 

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

1206ALWAYS_ENABLED_FEATURES = [ 

1207 "truststore", # always on since 24.2 

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

1209] 

1210 

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

1212 Option, 

1213 "--use-feature", 

1214 dest="features_enabled", 

1215 metavar="feature", 

1216 action="append", 

1217 default=[], 

1218 choices=[ 

1219 "fast-deps", 

1220 "build-constraint", 

1221 "inprocess-build-deps", 

1222 ] 

1223 + ALWAYS_ENABLED_FEATURES, 

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

1225) 

1226 

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

1228 Option, 

1229 "--use-deprecated", 

1230 dest="deprecated_features_enabled", 

1231 metavar="feature", 

1232 action="append", 

1233 default=[], 

1234 choices=[ 

1235 "legacy-resolver", 

1236 "legacy-certs", 

1237 ], 

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

1239) 

1240 

1241########## 

1242# groups # 

1243########## 

1244 

1245general_group: dict[str, Any] = { 

1246 "name": "General Options", 

1247 "options": [ 

1248 help_, 

1249 debug_mode, 

1250 isolated_mode, 

1251 require_virtualenv, 

1252 python, 

1253 verbose, 

1254 version, 

1255 quiet, 

1256 log, 

1257 no_input, 

1258 keyring_provider, 

1259 proxy, 

1260 retries, 

1261 timeout, 

1262 exists_action, 

1263 trusted_host, 

1264 cert, 

1265 client_cert, 

1266 cache_dir, 

1267 no_cache, 

1268 disable_pip_version_check, 

1269 no_color, 

1270 no_python_version_warning, 

1271 use_new_feature, 

1272 use_deprecated_feature, 

1273 resume_retries, 

1274 ], 

1275} 

1276 

1277index_group: dict[str, Any] = { 

1278 "name": "Package Index Options", 

1279 "options": [ 

1280 index_url, 

1281 extra_index_url, 

1282 no_index, 

1283 find_links, 

1284 uploaded_prior_to, 

1285 ], 

1286} 

1287 

1288package_selection_group: dict[str, Any] = { 

1289 "name": "Package Selection Options", 

1290 "options": [ 

1291 pre, 

1292 all_releases, 

1293 only_final, 

1294 no_binary, 

1295 only_binary, 

1296 prefer_binary, 

1297 ], 

1298}