Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/pip/_internal/exceptions.py: 41%

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

403 statements  

1"""Exceptions used throughout package. 

2 

3This module MUST NOT try to import from anything within `pip._internal` to 

4operate. This is expected to be importable from any/all files within the 

5subpackage and, thus, should not depend on them. 

6""" 

7 

8from __future__ import annotations 

9 

10import configparser 

11import contextlib 

12import locale 

13import logging 

14import os 

15import pathlib 

16import re 

17import sys 

18import traceback 

19from collections.abc import Iterable, Iterator 

20from itertools import chain, groupby, repeat 

21from typing import TYPE_CHECKING, Literal 

22 

23from pip._vendor.packaging.requirements import InvalidRequirement 

24from pip._vendor.packaging.version import InvalidVersion 

25from pip._vendor.rich.console import Console, ConsoleOptions, RenderResult 

26from pip._vendor.rich.markup import escape 

27from pip._vendor.rich.text import Text 

28 

29if TYPE_CHECKING: 

30 from hashlib import _Hash 

31 

32 from pip._vendor import urllib3 

33 from pip._vendor.requests.models import PreparedRequest, Request, Response 

34 

35 from pip._internal.metadata import BaseDistribution 

36 from pip._internal.models.link import Link 

37 from pip._internal.network.download import _FileDownload 

38 from pip._internal.req.req_install import InstallRequirement 

39 

40logger = logging.getLogger(__name__) 

41 

42 

43# 

44# Scaffolding 

45# 

46def _is_kebab_case(s: str) -> bool: 

47 return re.match(r"^[a-z]+(-[a-z]+)*$", s) is not None 

48 

49 

50def _prefix_with_indent( 

51 s: Text | str, 

52 console: Console, 

53 *, 

54 prefix: str, 

55 indent: str, 

56) -> Text: 

57 if isinstance(s, Text): 

58 text = s 

59 else: 

60 text = console.render_str(s) 

61 

62 return console.render_str(prefix, overflow="ignore") + console.render_str( 

63 f"\n{indent}", overflow="ignore" 

64 ).join(text.split(allow_blank=True)) 

65 

66 

67class PipError(Exception): 

68 """The base pip error.""" 

69 

70 

71class DiagnosticPipError(PipError): 

72 """An error, that presents diagnostic information to the user. 

73 

74 This contains a bunch of logic, to enable pretty presentation of our error 

75 messages. Each error gets a unique reference. Each error can also include 

76 additional context, a hint and/or a note -- which are presented with the 

77 main error message in a consistent style. 

78 

79 This is adapted from the error output styling in `sphinx-theme-builder`. 

80 """ 

81 

82 reference: str 

83 

84 def __init__( 

85 self, 

86 *, 

87 kind: Literal["error", "warning"] = "error", 

88 reference: str | None = None, 

89 message: str | Text, 

90 context: str | Text | None, 

91 hint_stmt: str | Text | None, 

92 note_stmt: str | Text | None = None, 

93 link: str | None = None, 

94 ) -> None: 

95 # Ensure a proper reference is provided. 

96 if reference is None: 

97 assert hasattr(self, "reference"), "error reference not provided!" 

98 reference = self.reference 

99 assert _is_kebab_case(reference), "error reference must be kebab-case!" 

100 

101 self.kind = kind 

102 self.reference = reference 

103 

104 self.message = message 

105 self.context = context 

106 

107 self.note_stmt = note_stmt 

108 self.hint_stmt = hint_stmt 

109 

110 self.link = link 

111 

112 super().__init__(f"<{self.__class__.__name__}: {self.reference}>") 

113 

114 def __repr__(self) -> str: 

115 return ( 

116 f"<{self.__class__.__name__}(" 

117 f"reference={self.reference!r}, " 

118 f"message={self.message!r}, " 

119 f"context={self.context!r}, " 

120 f"note_stmt={self.note_stmt!r}, " 

121 f"hint_stmt={self.hint_stmt!r}" 

122 ")>" 

123 ) 

124 

125 def __rich_console__( 

126 self, 

127 console: Console, 

128 options: ConsoleOptions, 

129 ) -> RenderResult: 

130 colour = "red" if self.kind == "error" else "yellow" 

131 

132 yield f"[{colour} bold]{self.kind}[/]: [bold]{self.reference}[/]" 

133 yield "" 

134 

135 if not options.ascii_only: 

136 # Present the main message, with relevant context indented. 

137 if self.context is not None: 

138 yield _prefix_with_indent( 

139 self.message, 

140 console, 

141 prefix=f"[{colour}]×[/] ", 

142 indent=f"[{colour}]│[/] ", 

143 ) 

144 yield _prefix_with_indent( 

145 self.context, 

146 console, 

147 prefix=f"[{colour}]╰─>[/] ", 

148 indent=f"[{colour}] [/] ", 

149 ) 

150 else: 

151 yield _prefix_with_indent( 

152 self.message, 

153 console, 

154 prefix="[red]×[/] ", 

155 indent=" ", 

156 ) 

157 else: 

158 yield self.message 

159 if self.context is not None: 

160 yield "" 

161 yield self.context 

162 

163 if self.note_stmt is not None or self.hint_stmt is not None: 

164 yield "" 

165 

166 if self.note_stmt is not None: 

167 yield _prefix_with_indent( 

168 self.note_stmt, 

169 console, 

170 prefix="[magenta bold]note[/]: ", 

171 indent=" ", 

172 ) 

173 if self.hint_stmt is not None: 

174 yield _prefix_with_indent( 

175 self.hint_stmt, 

176 console, 

177 prefix="[cyan bold]hint[/]: ", 

178 indent=" ", 

179 ) 

180 

181 if self.link is not None: 

182 yield "" 

183 yield f"Link: {self.link}" 

184 

185 

186# 

187# Actual Errors 

188# 

189class ConfigurationError(PipError): 

190 """General exception in configuration""" 

191 

192 

193class InstallationError(PipError): 

194 """General exception during installation""" 

195 

196 

197class FailedToPrepareCandidate(InstallationError): 

198 """Raised when we fail to prepare a candidate (i.e. fetch and generate metadata). 

199 

200 This is intentionally not a diagnostic error, since the output will be presented 

201 above this error, when this occurs. This should instead present information to the 

202 user. 

203 """ 

204 

205 def __init__( 

206 self, *, package_name: str, requirement_chain: str, failed_step: str 

207 ) -> None: 

208 super().__init__(f"Failed to build '{package_name}' when {failed_step.lower()}") 

209 self.package_name = package_name 

210 self.requirement_chain = requirement_chain 

211 self.failed_step = failed_step 

212 

213 

214class MissingPyProjectBuildRequires(DiagnosticPipError): 

215 """Raised when pyproject.toml has `build-system`, but no `build-system.requires`.""" 

216 

217 reference = "missing-pyproject-build-system-requires" 

218 

219 def __init__(self, *, package: str) -> None: 

220 super().__init__( 

221 message=f"Can not process {escape(package)}", 

222 context=Text( 

223 "This package has an invalid pyproject.toml file.\n" 

224 "The [build-system] table is missing the mandatory `requires` key." 

225 ), 

226 note_stmt="This is an issue with the package mentioned above, not pip.", 

227 hint_stmt=Text("See PEP 518 for the detailed specification."), 

228 ) 

229 

230 

231class InvalidPyProjectBuildRequires(DiagnosticPipError): 

232 """Raised when pyproject.toml an invalid `build-system.requires`.""" 

233 

234 reference = "invalid-pyproject-build-system-requires" 

235 

236 def __init__(self, *, package: str, reason: str) -> None: 

237 super().__init__( 

238 message=f"Can not process {escape(package)}", 

239 context=Text( 

240 "This package has an invalid `build-system.requires` key in " 

241 f"pyproject.toml.\n{reason}" 

242 ), 

243 note_stmt="This is an issue with the package mentioned above, not pip.", 

244 hint_stmt=Text("See PEP 518 for the detailed specification."), 

245 ) 

246 

247 

248class NoneMetadataError(PipError): 

249 """Raised when accessing a Distribution's "METADATA" or "PKG-INFO". 

250 

251 This signifies an inconsistency, when the Distribution claims to have 

252 the metadata file (if not, raise ``FileNotFoundError`` instead), but is 

253 not actually able to produce its content. This may be due to permission 

254 errors. 

255 """ 

256 

257 def __init__( 

258 self, 

259 dist: BaseDistribution, 

260 metadata_name: str, 

261 ) -> None: 

262 """ 

263 :param dist: A Distribution object. 

264 :param metadata_name: The name of the metadata being accessed 

265 (can be "METADATA" or "PKG-INFO"). 

266 """ 

267 self.dist = dist 

268 self.metadata_name = metadata_name 

269 

270 def __str__(self) -> str: 

271 # Use `dist` in the error message because its stringification 

272 # includes more information, like the version and location. 

273 return f"None {self.metadata_name} metadata found for distribution: {self.dist}" 

274 

275 

276class UserInstallationInvalid(InstallationError): 

277 """A --user install is requested on an environment without user site.""" 

278 

279 def __str__(self) -> str: 

280 return "User base directory is not specified" 

281 

282 

283class InvalidSchemeCombination(InstallationError): 

284 def __str__(self) -> str: 

285 before = ", ".join(str(a) for a in self.args[:-1]) 

286 return f"Cannot set {before} and {self.args[-1]} together" 

287 

288 

289class DistributionNotFound(InstallationError): 

290 """Raised when a distribution cannot be found to satisfy a requirement""" 

291 

292 

293class RequirementsFileParseError(InstallationError): 

294 """Raised when a general error occurs parsing a requirements file line.""" 

295 

296 

297class BestVersionAlreadyInstalled(PipError): 

298 """Raised when the most up-to-date version of a package is already 

299 installed.""" 

300 

301 

302class BadCommand(PipError): 

303 """Raised when virtualenv or a command is not found""" 

304 

305 

306class CommandError(PipError): 

307 """Raised when there is an error in command-line arguments""" 

308 

309 

310class PreviousBuildDirError(PipError): 

311 """Raised when there's a previous conflicting build directory""" 

312 

313 

314class NetworkConnectionError(PipError): 

315 """HTTP connection error""" 

316 

317 def __init__( 

318 self, 

319 error_msg: str, 

320 response: Response | None = None, 

321 request: Request | PreparedRequest | None = None, 

322 ) -> None: 

323 """ 

324 Initialize NetworkConnectionError with `request` and `response` 

325 objects. 

326 """ 

327 self.response = response 

328 self.request = request 

329 self.error_msg = error_msg 

330 if ( 

331 self.response is not None 

332 and not self.request 

333 and hasattr(response, "request") 

334 ): 

335 self.request = self.response.request 

336 super().__init__(error_msg, response, request) 

337 

338 def __str__(self) -> str: 

339 return str(self.error_msg) 

340 

341 

342class ConnectionFailedError(DiagnosticPipError): 

343 reference = "connection-failed" 

344 

345 def __init__(self, url: str, host: str, error: Exception) -> None: 

346 from http.client import RemoteDisconnected 

347 

348 from pip._vendor.urllib3.exceptions import ( 

349 NameResolutionError, 

350 NewConnectionError, 

351 ProtocolError, 

352 ) 

353 

354 details = str(error) 

355 if isinstance(error, NameResolutionError): 

356 parts = details.split("Failed to resolve ", maxsplit=1) 

357 if len(parts) == 2: 

358 details = "Failed to resolve IP address for " + parts[1] 

359 elif isinstance(error, NewConnectionError): 

360 parts = details.split("Failed to establish a new connection: ", maxsplit=1) 

361 if len(parts) == 2: 

362 _, details = parts 

363 elif isinstance(error, ProtocolError): 

364 try: 

365 reason = error.args[1] 

366 except IndexError: 

367 pass 

368 else: 

369 if isinstance(reason, (RemoteDisconnected, ConnectionResetError)): 

370 details = ( 

371 "the connection was closed without a reply from the server." 

372 ) 

373 

374 super().__init__( 

375 message=( 

376 f"Failed to connect to [magenta]{escape(host)}[/] while fetching " 

377 f"{escape(url)}" 

378 ), 

379 context=Text(details), 

380 hint_stmt=( 

381 "Are you connected to the Internet? If so, check whether your system " 

382 f"can connect to [magenta]{escape(host)}[/] before trying again. " 

383 "There may be a firewall or proxy that's preventing the connection." 

384 ), 

385 ) 

386 

387 

388class ConnectionTimeoutError(DiagnosticPipError): 

389 reference = "connection-timeout" 

390 

391 def __init__( 

392 self, 

393 url: str, 

394 host: str, 

395 *, 

396 kind: Literal["connect", "read"], 

397 timeout: float, 

398 ) -> None: 

399 context = Text.assemble( 

400 (host, "magenta"), f" didn't respond within {timeout} seconds" 

401 ) 

402 if kind == "connect": 

403 context.append(" (while establishing a connection)") 

404 super().__init__( 

405 message=f"Unable to fetch {escape(url)}", 

406 context=context, 

407 hint_stmt=( 

408 "This is probably a temporary issue with the remote server or the " 

409 "network connection. If this error persists, check the network " 

410 "configuration. There may be a firewall or proxy that's preventing " 

411 "the connection." 

412 ), 

413 ) 

414 

415 

416class SSLMissingError(DiagnosticPipError): 

417 reference = "ssl-missing" 

418 

419 def __init__(self, url: str) -> None: 

420 super().__init__( 

421 message=f"Failed to establish a secure connection for {escape(url)}", 

422 context="The 'ssl' module is unavailable but required for HTTPS URLs", 

423 hint_stmt=None, 

424 ) 

425 

426 

427class SSLVerificationError(DiagnosticPipError): 

428 reference = "ssl-verification-failed" 

429 

430 def __init__(self, url: str, host: str, error: urllib3.exceptions.SSLError) -> None: 

431 message = ( 

432 "Failed to establish a secure connection to " 

433 f"[magenta]{escape(host)}[/] while fetching {escape(url)}" 

434 ) 

435 hint = "You may need to use --cert or check your proxy/firewall configuration" 

436 super().__init__(message=message, context=Text(str(error)), hint_stmt=hint) 

437 

438 

439class ProxyConnectionError(DiagnosticPipError): 

440 reference = "proxy-connection-failed" 

441 

442 def __init__( 

443 self, url: str, proxy: str, error: urllib3.exceptions.ProxyError 

444 ) -> None: 

445 super().__init__( 

446 message=( 

447 "Failed to connect to proxy " 

448 f"[magenta]{escape(proxy)}[/] while fetching {escape(url)}" 

449 ), 

450 context=Text(str(error)), 

451 hint_stmt="This is likely a proxy configuration issue.", 

452 ) 

453 

454 

455class InvalidWheelFilename(InstallationError): 

456 """Invalid wheel filename.""" 

457 

458 

459class UnsupportedWheel(InstallationError): 

460 """Unsupported wheel.""" 

461 

462 

463class InvalidWheel(InstallationError): 

464 """Invalid (e.g. corrupt) wheel.""" 

465 

466 def __init__(self, location: str, name: str): 

467 self.location = location 

468 self.name = name 

469 

470 def __str__(self) -> str: 

471 return f"Wheel '{self.name}' located at {self.location} is invalid." 

472 

473 

474class MetadataInconsistent(InstallationError): 

475 """Built metadata contains inconsistent information. 

476 

477 This is raised when the metadata contains values (e.g. name and version) 

478 that do not match the information previously obtained from sdist filename, 

479 user-supplied ``#egg=`` value, or an install requirement name. 

480 """ 

481 

482 def __init__( 

483 self, ireq: InstallRequirement, field: str, f_val: str, m_val: str 

484 ) -> None: 

485 self.ireq = ireq 

486 self.field = field 

487 self.f_val = f_val 

488 self.m_val = m_val 

489 

490 def __str__(self) -> str: 

491 return ( 

492 f"Requested {self.ireq} has inconsistent {self.field}: " 

493 f"expected {self.f_val!r}, but metadata has {self.m_val!r}" 

494 ) 

495 

496 

497class SidecarMetadataInconsistent(MetadataInconsistent): 

498 """The wheel's METADATA disagrees with its PEP 658 ``.metadata`` file. 

499 

500 Raised after the wheel has been downloaded and hash-verified, when a 

501 resolver-affecting field in the wheel's embedded ``METADATA`` does not 

502 match the value taken from the remote ``.metadata`` sidecar that drove 

503 resolution. ``f_val`` is the sidecar value, ``m_val`` is the wheel value. 

504 """ 

505 

506 def __str__(self) -> str: 

507 return ( 

508 f"Requested {self.ireq} has inconsistent {self.field} between " 

509 f"its PEP 658 .metadata file and the wheel's METADATA: " 

510 f"sidecar has {self.f_val!r}, wheel has {self.m_val!r}" 

511 ) 

512 

513 

514class MetadataInvalid(InstallationError): 

515 """Metadata is invalid.""" 

516 

517 def __init__(self, ireq: InstallRequirement, error: str) -> None: 

518 self.ireq = ireq 

519 self.error = error 

520 

521 def __str__(self) -> str: 

522 return f"Requested {self.ireq} has invalid metadata: {self.error}" 

523 

524 

525class InstallationSubprocessError(DiagnosticPipError, InstallationError): 

526 """A subprocess call failed.""" 

527 

528 reference = "subprocess-exited-with-error" 

529 

530 def __init__( 

531 self, 

532 *, 

533 command_description: str, 

534 exit_code: int, 

535 output_lines: list[str] | None, 

536 ) -> None: 

537 if output_lines is None: 

538 output_prompt = Text("No available output.") 

539 else: 

540 output_prompt = ( 

541 Text.from_markup(f"[red][{len(output_lines)} lines of output][/]\n") 

542 + Text("".join(output_lines)) 

543 + Text.from_markup(R"[red]\[end of output][/]") 

544 ) 

545 

546 super().__init__( 

547 message=( 

548 f"[green]{escape(command_description)}[/] did not run successfully.\n" 

549 f"exit code: {exit_code}" 

550 ), 

551 context=output_prompt, 

552 hint_stmt=None, 

553 note_stmt=( 

554 "This error originates from a subprocess, and is likely not a " 

555 "problem with pip." 

556 ), 

557 ) 

558 

559 self.command_description = command_description 

560 self.exit_code = exit_code 

561 

562 def __str__(self) -> str: 

563 return f"{self.command_description} exited with {self.exit_code}" 

564 

565 

566class MetadataGenerationFailed(DiagnosticPipError, InstallationError): 

567 reference = "metadata-generation-failed" 

568 

569 def __init__( 

570 self, 

571 *, 

572 package_details: str, 

573 ) -> None: 

574 super().__init__( 

575 message="Encountered error while generating package metadata.", 

576 context=escape(package_details), 

577 hint_stmt="See above for details.", 

578 note_stmt="This is an issue with the package mentioned above, not pip.", 

579 ) 

580 

581 def __str__(self) -> str: 

582 return "metadata generation failed" 

583 

584 

585class HashErrors(InstallationError): 

586 """Multiple HashError instances rolled into one for reporting""" 

587 

588 def __init__(self) -> None: 

589 self.errors: list[HashError] = [] 

590 

591 def append(self, error: HashError) -> None: 

592 self.errors.append(error) 

593 

594 def __str__(self) -> str: 

595 lines = [] 

596 self.errors.sort(key=lambda e: e.order) 

597 for cls, errors_of_cls in groupby(self.errors, lambda e: e.__class__): 

598 lines.append(cls.head) 

599 lines.extend(e.body() for e in errors_of_cls) 

600 if lines: 

601 return "\n".join(lines) 

602 return "" 

603 

604 def __bool__(self) -> bool: 

605 return bool(self.errors) 

606 

607 

608class HashError(InstallationError): 

609 """ 

610 A failure to verify a package against known-good hashes 

611 

612 :cvar order: An int sorting hash exception classes by difficulty of 

613 recovery (lower being harder), so the user doesn't bother fretting 

614 about unpinned packages when he has deeper issues, like VCS 

615 dependencies, to deal with. Also keeps error reports in a 

616 deterministic order. 

617 :cvar head: A section heading for display above potentially many 

618 exceptions of this kind 

619 :ivar req: The InstallRequirement that triggered this error. This is 

620 pasted on after the exception is instantiated, because it's not 

621 typically available earlier. 

622 

623 """ 

624 

625 req: InstallRequirement | None = None 

626 head = "" 

627 order: int = -1 

628 

629 def body(self) -> str: 

630 """Return a summary of me for display under the heading. 

631 

632 This default implementation simply prints a description of the 

633 triggering requirement. 

634 

635 :param req: The InstallRequirement that provoked this error, with 

636 its link already populated by the resolver's _populate_link(). 

637 

638 """ 

639 return f" {self._requirement_name()}" 

640 

641 def __str__(self) -> str: 

642 return f"{self.head}\n{self.body()}" 

643 

644 def _requirement_name(self) -> str: 

645 """Return a description of the requirement that triggered me. 

646 

647 This default implementation returns long description of the req, with 

648 line numbers 

649 

650 """ 

651 return str(self.req) if self.req else "unknown package" 

652 

653 

654class VcsHashUnsupported(HashError): 

655 """A hash was provided for a version-control-system-based requirement, but 

656 we don't have a method for hashing those.""" 

657 

658 order = 0 

659 head = ( 

660 "Can't verify hashes for these requirements because we don't " 

661 "have a way to hash version control repositories:" 

662 ) 

663 

664 

665class DirectoryUrlHashUnsupported(HashError): 

666 """A hash was provided for a version-control-system-based requirement, but 

667 we don't have a method for hashing those.""" 

668 

669 order = 1 

670 head = ( 

671 "Can't verify hashes for these file:// requirements because they " 

672 "point to directories:" 

673 ) 

674 

675 

676class HashMissing(HashError): 

677 """A hash was needed for a requirement but is absent.""" 

678 

679 order = 2 

680 head = ( 

681 "Hashes are required in --require-hashes mode, but they are " 

682 "missing from some requirements. Here is a list of those " 

683 "requirements along with the hashes their downloaded archives " 

684 "actually had. Add lines like these to your requirements files to " 

685 "prevent tampering. (If you did not enable --require-hashes " 

686 "manually, note that it turns on automatically when any package " 

687 "has a hash.)" 

688 ) 

689 

690 def __init__(self, gotten_hash: str) -> None: 

691 """ 

692 :param gotten_hash: The hash of the (possibly malicious) archive we 

693 just downloaded 

694 """ 

695 self.gotten_hash = gotten_hash 

696 

697 def body(self) -> str: 

698 # Dodge circular import. 

699 from pip._internal.utils.hashes import FAVORITE_HASH 

700 

701 package = None 

702 if self.req: 

703 # In the case of URL-based requirements, display the original URL 

704 # seen in the requirements file rather than the package name, 

705 # so the output can be directly copied into the requirements file. 

706 package = ( 

707 self.req.original_link 

708 if self.req.is_direct 

709 # In case someone feeds something downright stupid 

710 # to InstallRequirement's constructor. 

711 else getattr(self.req, "req", None) 

712 ) 

713 return " {} --hash={}:{}".format( 

714 package or "unknown package", FAVORITE_HASH, self.gotten_hash 

715 ) 

716 

717 

718class HashUnpinned(HashError): 

719 """A requirement had a hash specified but was not pinned to a specific 

720 version.""" 

721 

722 order = 3 

723 head = ( 

724 "In --require-hashes mode, all requirements must have their " 

725 "versions pinned with ==. These do not:" 

726 ) 

727 

728 

729class HashMismatch(HashError): 

730 """ 

731 Distribution file hash values don't match. 

732 

733 :ivar package_name: The name of the package that triggered the hash 

734 mismatch. Feel free to write to this after the exception is raise to 

735 improve its error message. 

736 

737 """ 

738 

739 order = 4 

740 head = ( 

741 "THESE PACKAGES DO NOT MATCH THE HASHES FROM THE REQUIREMENTS " 

742 "FILE. If you have updated the package versions, please update " 

743 "the hashes. Otherwise, examine the package contents carefully; " 

744 "someone may have tampered with them." 

745 ) 

746 

747 def __init__(self, allowed: dict[str, list[str]], gots: dict[str, _Hash]) -> None: 

748 """ 

749 :param allowed: A dict of algorithm names pointing to lists of allowed 

750 hex digests 

751 :param gots: A dict of algorithm names pointing to hashes we 

752 actually got from the files under suspicion 

753 """ 

754 self.allowed = allowed 

755 self.gots = gots 

756 

757 def body(self) -> str: 

758 return f" {self._requirement_name()}:\n{self._hash_comparison()}" 

759 

760 def _hash_comparison(self) -> str: 

761 """ 

762 Return a comparison of actual and expected hash values. 

763 

764 Example:: 

765 

766 Expected sha256 abcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcde 

767 or 123451234512345123451234512345123451234512345 

768 Got bcdefbcdefbcdefbcdefbcdefbcdefbcdefbcdefbcdef 

769 

770 """ 

771 

772 def hash_then_or(hash_name: str) -> chain[str]: 

773 # For now, all the decent hashes have 6-char names, so we can get 

774 # away with hard-coding space literals. 

775 return chain([hash_name], repeat(" or")) 

776 

777 lines: list[str] = [] 

778 for hash_name, expecteds in self.allowed.items(): 

779 prefix = hash_then_or(hash_name) 

780 lines.extend((f" Expected {next(prefix)} {e}") for e in expecteds) 

781 lines.append( 

782 f" Got {self.gots[hash_name].hexdigest()}\n" 

783 ) 

784 return "\n".join(lines) 

785 

786 

787class UnsupportedPythonVersion(InstallationError): 

788 """Unsupported python version according to Requires-Python package 

789 metadata.""" 

790 

791 

792class ConfigurationFileCouldNotBeLoaded(ConfigurationError): 

793 """When there are errors while loading a configuration file""" 

794 

795 def __init__( 

796 self, 

797 reason: str = "could not be loaded", 

798 fname: str | None = None, 

799 error: configparser.Error | None = None, 

800 ) -> None: 

801 super().__init__(error) 

802 self.reason = reason 

803 self.fname = fname 

804 self.error = error 

805 

806 def __str__(self) -> str: 

807 if self.fname is not None: 

808 message_part = f" in {self.fname}." 

809 else: 

810 assert self.error is not None 

811 message_part = f".\n{self.error}\n" 

812 return f"Configuration file {self.reason}{message_part}" 

813 

814 

815_DEFAULT_EXTERNALLY_MANAGED_ERROR = f"""\ 

816The Python environment under {sys.prefix} is managed externally, and may not be 

817manipulated by the user. Please use specific tooling from the distributor of 

818the Python installation to interact with this environment instead. 

819""" 

820 

821 

822class ExternallyManagedEnvironment(DiagnosticPipError): 

823 """The current environment is externally managed. 

824 

825 This is raised when the current environment is externally managed, as 

826 defined by `PEP 668`_. The ``EXTERNALLY-MANAGED`` configuration is checked 

827 and displayed when the error is bubbled up to the user. 

828 

829 :param error: The error message read from ``EXTERNALLY-MANAGED``. 

830 """ 

831 

832 reference = "externally-managed-environment" 

833 

834 def __init__(self, error: str | None) -> None: 

835 if error is None: 

836 context = Text(_DEFAULT_EXTERNALLY_MANAGED_ERROR) 

837 else: 

838 context = Text(error) 

839 super().__init__( 

840 message="This environment is externally managed", 

841 context=context, 

842 note_stmt=( 

843 "If you believe this is a mistake, please contact your " 

844 "Python installation or OS distribution provider. " 

845 "You can override this, at the risk of breaking your Python " 

846 "installation or OS, by passing --break-system-packages." 

847 ), 

848 hint_stmt=Text("See PEP 668 for the detailed specification."), 

849 ) 

850 

851 @staticmethod 

852 def _iter_externally_managed_error_keys() -> Iterator[str]: 

853 # LC_MESSAGES is in POSIX, but not the C standard. The most common 

854 # platform that does not implement this category is Windows, where 

855 # using other categories for console message localization is equally 

856 # unreliable, so we fall back to the locale-less vendor message. This 

857 # can always be re-evaluated when a vendor proposes a new alternative. 

858 try: 

859 category = locale.LC_MESSAGES 

860 except AttributeError: 

861 lang: str | None = None 

862 else: 

863 lang, _ = locale.getlocale(category) 

864 if lang is not None: 

865 yield f"Error-{lang}" 

866 for sep in ("-", "_"): 

867 before, found, _ = lang.partition(sep) 

868 if not found: 

869 continue 

870 yield f"Error-{before}" 

871 yield "Error" 

872 

873 @classmethod 

874 def from_config( 

875 cls, 

876 config: pathlib.Path | str, 

877 ) -> ExternallyManagedEnvironment: 

878 parser = configparser.ConfigParser(interpolation=None) 

879 try: 

880 parser.read(config, encoding="utf-8") 

881 section = parser["externally-managed"] 

882 for key in cls._iter_externally_managed_error_keys(): 

883 with contextlib.suppress(KeyError): 

884 return cls(section[key]) 

885 except KeyError: 

886 pass 

887 except (OSError, UnicodeDecodeError, configparser.ParsingError): 

888 from pip._internal.utils._log import VERBOSE 

889 

890 exc_info = logger.isEnabledFor(VERBOSE) 

891 logger.warning("Failed to read %s", config, exc_info=exc_info) 

892 return cls(None) 

893 

894 

895class UninstallMissingRecord(DiagnosticPipError): 

896 reference = "uninstall-no-record-file" 

897 

898 def __init__(self, *, distribution: BaseDistribution) -> None: 

899 installer = distribution.installer 

900 if not installer or installer == "pip": 

901 dep = f"{distribution.raw_name}=={distribution.version}" 

902 hint = Text.assemble( 

903 "You might be able to recover from this via: ", 

904 (f"pip install --ignore-installed --no-deps {dep}", "green"), 

905 ) 

906 else: 

907 hint = Text( 

908 f"The package was installed by {installer}. " 

909 "You should check if it can uninstall the package." 

910 ) 

911 

912 super().__init__( 

913 message=Text(f"Cannot uninstall {distribution}"), 

914 context=( 

915 "The package's contents are unknown: " 

916 f"no RECORD file was found for {distribution.raw_name}." 

917 ), 

918 hint_stmt=hint, 

919 ) 

920 

921 

922class LegacyDistutilsInstall(DiagnosticPipError): 

923 reference = "uninstall-distutils-installed-package" 

924 

925 def __init__(self, *, distribution: BaseDistribution) -> None: 

926 super().__init__( 

927 message=Text(f"Cannot uninstall {distribution}"), 

928 context=( 

929 "It is a distutils installed project and thus we cannot accurately " 

930 "determine which files belong to it which would lead to only a partial " 

931 "uninstall." 

932 ), 

933 hint_stmt=None, 

934 ) 

935 

936 

937class InvalidInstalledPackage(DiagnosticPipError): 

938 reference = "invalid-installed-package" 

939 

940 def __init__( 

941 self, 

942 *, 

943 dist: BaseDistribution, 

944 invalid_exc: InvalidRequirement | InvalidVersion, 

945 ) -> None: 

946 installed_location = dist.installed_location 

947 

948 if isinstance(invalid_exc, InvalidRequirement): 

949 invalid_type = "requirement" 

950 else: 

951 invalid_type = "version" 

952 

953 super().__init__( 

954 message=Text( 

955 f"Cannot process installed package {dist} " 

956 + (f"in {installed_location!r} " if installed_location else "") 

957 + f"because it has an invalid {invalid_type}:\n{invalid_exc.args[0]}" 

958 ), 

959 context=( 

960 "Starting with pip 24.1, packages with invalid " 

961 f"{invalid_type}s can not be processed." 

962 ), 

963 hint_stmt="To proceed this package must be uninstalled.", 

964 ) 

965 

966 

967class IncompleteDownloadError(DiagnosticPipError): 

968 """Raised when the downloader receives fewer bytes than advertised 

969 in the Content-Length header.""" 

970 

971 reference = "incomplete-download" 

972 

973 def __init__(self, download: _FileDownload) -> None: 

974 # Dodge circular import. 

975 from pip._internal.utils.misc import format_size 

976 

977 assert download.size is not None 

978 download_status = ( 

979 f"{format_size(download.bytes_received)}/{format_size(download.size)}" 

980 ) 

981 if download.reattempts: 

982 retry_status = f"after {download.reattempts + 1} attempts " 

983 hint = "Use --resume-retries to configure resume attempt limit." 

984 else: 

985 # Download retrying is not enabled. 

986 retry_status = "" 

987 hint = "Consider using --resume-retries to enable download resumption." 

988 message = Text( 

989 f"Download failed {retry_status}because not enough bytes " 

990 f"were received ({download_status})" 

991 ) 

992 

993 super().__init__( 

994 message=message, 

995 context=f"URL: {download.link.redacted_url}", 

996 hint_stmt=hint, 

997 note_stmt="This is an issue with network connectivity, not pip.", 

998 ) 

999 

1000 

1001class ResolutionTooDeepError(DiagnosticPipError): 

1002 """Raised when the dependency resolver exceeds the maximum recursion depth.""" 

1003 

1004 reference = "resolution-too-deep" 

1005 

1006 def __init__(self) -> None: 

1007 super().__init__( 

1008 message="Dependency resolution exceeded maximum depth", 

1009 context=( 

1010 "Pip cannot resolve the current dependencies as the dependency graph " 

1011 "is too complex for pip to solve efficiently." 

1012 ), 

1013 hint_stmt=( 

1014 "Try adding lower bounds to constrain your dependencies, " 

1015 "for example: 'package>=2.0.0' instead of just 'package'. " 

1016 ), 

1017 link="https://pip.pypa.io/en/stable/topics/dependency-resolution/#handling-resolution-too-deep-errors", 

1018 ) 

1019 

1020 

1021class InstallWheelBuildError(DiagnosticPipError): 

1022 reference = "failed-wheel-build-for-install" 

1023 

1024 def __init__(self, failed: list[InstallRequirement]) -> None: 

1025 super().__init__( 

1026 message=( 

1027 "Failed to build installable wheels for some " 

1028 "pyproject.toml based projects" 

1029 ), 

1030 context=", ".join(r.name for r in failed), # type: ignore 

1031 hint_stmt=None, 

1032 ) 

1033 

1034 

1035class InvalidEggFragment(DiagnosticPipError): 

1036 reference = "invalid-egg-fragment" 

1037 

1038 def __init__(self, link: Link, fragment: str) -> None: 

1039 hint = "" 

1040 if ">" in fragment or "=" in fragment or "<" in fragment: 

1041 hint = ( 

1042 "Version specifiers are silently ignored for URL references. " 

1043 "Remove them. " 

1044 ) 

1045 if "[" in fragment and "]" in fragment: 

1046 hint += "Try using the Direct URL requirement syntax: 'name[extra] @ URL'" 

1047 

1048 if not hint: 

1049 hint = "Egg fragments can only be a valid project name." 

1050 

1051 super().__init__( 

1052 message=f"The '{escape(fragment)}' egg fragment is invalid", 

1053 context=f"from '{escape(str(link))}'", 

1054 hint_stmt=escape(hint), 

1055 ) 

1056 

1057 

1058class BuildDependencyInstallError(DiagnosticPipError): 

1059 """Raised when build dependencies cannot be installed.""" 

1060 

1061 reference = "failed-build-dependency-install" 

1062 

1063 def __init__( 

1064 self, 

1065 req: InstallRequirement | None, 

1066 build_reqs: Iterable[str], 

1067 *, 

1068 cause: Exception, 

1069 log_lines: list[str] | None, 

1070 ) -> None: 

1071 if isinstance(cause, PipError): 

1072 note = "This is likely not a problem with pip." 

1073 else: 

1074 note = ( 

1075 "pip crashed unexpectedly. Please file an issue on pip's issue " 

1076 "tracker: https://github.com/pypa/pip/issues/new" 

1077 ) 

1078 

1079 if log_lines is None: 

1080 # No logs are available, they must have been printed earlier. 

1081 context = Text("See above for more details.") 

1082 else: 

1083 if isinstance(cause, PipError): 

1084 log_lines.append(f"ERROR: {cause}") 

1085 else: 

1086 # Split rendered error into real lines without trailing newlines. 

1087 log_lines.extend( 

1088 "".join(traceback.format_exception(cause)).splitlines() 

1089 ) 

1090 

1091 context = Text.assemble( 

1092 f"Installing {' '.join(build_reqs)}\n", 

1093 (f"[{len(log_lines)} lines of output]\n", "red"), 

1094 "\n".join(log_lines), 

1095 ("\n[end of output]", "red"), 

1096 ) 

1097 

1098 message = Text("Cannot install build dependencies", "green") 

1099 if req: 

1100 message += Text(f" for {req}") 

1101 super().__init__( 

1102 message=message, context=context, hint_stmt=None, note_stmt=note 

1103 ) 

1104 

1105 

1106class VenvImportError(DiagnosticPipError): 

1107 """Raised when 'venv' can't be imported.""" 

1108 

1109 reference = "venv-import-error" 

1110 

1111 def __init__(self) -> None: 

1112 if sys.platform != "linux": 

1113 hint_stmt = None 

1114 else: 

1115 hint_stmt = ( 

1116 "If this is an OS-provided Python, it's likely that your OS " 

1117 "package maintainers have split Python's standard library across " 

1118 "multiple OS packages." 

1119 ) 

1120 super().__init__( 

1121 message="Cannot import the 'venv' module of the Python standard library", 

1122 context=( 

1123 "This is a symptom of a broken/modified Python, which cannot be used " 

1124 "with pip." 

1125 ), 

1126 note_stmt="This is an issue with the Python installation itself, not pip.", 

1127 hint_stmt=hint_stmt, 

1128 ) 

1129 

1130 

1131class VenvCreationError(DiagnosticPipError): 

1132 """Raised when a virtual environment can't be created.""" 

1133 

1134 reference = "venv-creation-error" 

1135 

1136 def __init__(self, context: str) -> None: 

1137 if os.name == "nt": 

1138 hint = "This may be caused by running antivirus software." 

1139 else: 

1140 hint = None 

1141 super().__init__( 

1142 message="Cannot create a virtual environment", 

1143 context=Text(context), 

1144 hint_stmt=hint, 

1145 )