Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/pip/_internal/req/req_install.py: 24%

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

394 statements  

1from __future__ import annotations 

2 

3import functools 

4import logging 

5import os 

6import shutil 

7import sys 

8import uuid 

9import zipfile 

10from collections.abc import Collection, Iterable 

11from optparse import Values 

12from pathlib import Path 

13from typing import Any 

14 

15from pip._vendor.packaging.markers import Marker 

16from pip._vendor.packaging.requirements import Requirement 

17from pip._vendor.packaging.specifiers import SpecifierSet 

18from pip._vendor.packaging.utils import canonicalize_name 

19from pip._vendor.packaging.version import Version 

20from pip._vendor.packaging.version import parse as parse_version 

21from pip._vendor.pyproject_hooks import BuildBackendHookCaller 

22 

23from pip._internal.build_env import BuildEnvironment, NoOpBuildEnvironment 

24from pip._internal.exceptions import InstallationError, PreviousBuildDirError 

25from pip._internal.locations import get_scheme 

26from pip._internal.metadata import ( 

27 BaseDistribution, 

28 get_default_environment, 

29 get_directory_distribution, 

30 get_wheel_distribution, 

31) 

32from pip._internal.metadata.base import FilesystemWheel 

33from pip._internal.models.direct_url import DirectUrl 

34from pip._internal.models.link import Link 

35from pip._internal.operations.build.metadata import generate_metadata 

36from pip._internal.operations.build.metadata_editable import generate_editable_metadata 

37from pip._internal.pyproject import load_pyproject_toml, make_pyproject_path 

38from pip._internal.req.req_uninstall import UninstallPathSet 

39from pip._internal.utils.deprecation import deprecated 

40from pip._internal.utils.hashes import Hashes 

41from pip._internal.utils.misc import ( 

42 ConfiguredBuildBackendHookCaller, 

43 ask_path_exists, 

44 backup_dir, 

45 display_path, 

46 hide_url, 

47 is_installable_dir, 

48 redact_auth_from_requirement, 

49 redact_auth_from_url, 

50) 

51from pip._internal.utils.packaging import get_requirement 

52from pip._internal.utils.subprocess import runner_with_spinner_message 

53from pip._internal.utils.temp_dir import TempDirectory, TempDirectoryKind 

54from pip._internal.utils.unpacking import unpack_file 

55from pip._internal.utils.virtualenv import running_under_virtualenv 

56from pip._internal.vcs import vcs 

57 

58logger = logging.getLogger(__name__) 

59 

60 

61class InstallRequirement: 

62 """ 

63 Represents something that may be installed later on, may have information 

64 about where to fetch the relevant requirement and also contains logic for 

65 installing the said requirement. 

66 """ 

67 

68 def __init__( 

69 self, 

70 req: Requirement | None, 

71 comes_from: str | InstallRequirement | None, 

72 editable: bool = False, 

73 link: Link | None = None, 

74 markers: Marker | None = None, 

75 isolated: bool = False, 

76 *, 

77 hash_options: dict[str, list[str]] | None = None, 

78 config_settings: dict[str, str | list[str]] | None = None, 

79 constraint: bool = False, 

80 extras: Collection[str] = (), 

81 user_supplied: bool = False, 

82 ) -> None: 

83 assert req is None or isinstance(req, Requirement), req 

84 self.req = req 

85 self.comes_from = comes_from 

86 self.constraint = constraint 

87 self.editable = editable 

88 

89 # source_dir is the local directory where the linked requirement is 

90 # located, or unpacked. In case unpacking is needed, creating and 

91 # populating source_dir is done by the RequirementPreparer. Note this 

92 # is not necessarily the directory where pyproject.toml or setup.py is 

93 # located - that one is obtained via unpacked_source_directory. 

94 self.source_dir: str | None = None 

95 if self.editable: 

96 assert link 

97 if link.is_file: 

98 self.source_dir = os.path.normpath(os.path.abspath(link.file_path)) 

99 

100 # original_link is the direct URL that was provided by the user for the 

101 # requirement, either directly or via a constraints file. 

102 if link is None and req and req.url: 

103 # PEP 508 URL requirement 

104 link = Link(req.url) 

105 self.link = self.original_link = link 

106 

107 # When this InstallRequirement is a wheel obtained from the cache of locally 

108 # built wheels, this is the source link corresponding to the cache entry, which 

109 # was used to download and build the cached wheel. 

110 self.cached_wheel_source_link: Link | None = None 

111 

112 # Information about the location of the artifact that was downloaded . This 

113 # property is guaranteed to be set in resolver results. 

114 self.download_info: DirectUrl | None = None 

115 

116 # Path to any downloaded or already-existing package. 

117 self.local_file_path: str | None = None 

118 if self.link and self.link.is_file: 

119 self.local_file_path = self.link.file_path 

120 

121 if extras: 

122 self.extras = extras 

123 elif req: 

124 self.extras = req.extras 

125 else: 

126 self.extras = set() 

127 if markers is None and req: 

128 markers = req.marker 

129 self.markers = markers 

130 

131 # This holds the Distribution object if this requirement is already installed. 

132 self.satisfied_by: BaseDistribution | None = None 

133 # Whether the installation process should try to uninstall an existing 

134 # distribution before installing this requirement. 

135 self.should_reinstall = False 

136 # Temporary build location 

137 self._temp_build_dir: TempDirectory | None = None 

138 # Set to True after successful installation 

139 self.install_succeeded: bool | None = None 

140 # Supplied options 

141 self.hash_options = hash_options if hash_options else {} 

142 self.config_settings = config_settings 

143 # Set to True after successful preparation of this requirement 

144 self.prepared = False 

145 # User supplied requirement are explicitly requested for installation 

146 # by the user via CLI arguments or requirements files, as opposed to, 

147 # e.g. dependencies, extras or constraints. 

148 self.user_supplied = user_supplied 

149 

150 self.isolated = isolated 

151 self.build_env: BuildEnvironment = NoOpBuildEnvironment() 

152 

153 # For PEP 517, the directory where we request the project metadata 

154 # gets stored. We need this to pass to build_wheel, so the backend 

155 # can ensure that the wheel matches the metadata (see the PEP for 

156 # details). 

157 self.metadata_directory: str | None = None 

158 

159 # The cached metadata distribution that this requirement represents. 

160 # See get_dist / set_dist. 

161 self._distribution: BaseDistribution | None = None 

162 

163 # The static build requirements (from pyproject.toml) 

164 self.pyproject_requires: list[str] | None = None 

165 

166 # Build requirements that we will check are available 

167 self.requirements_to_check: list[str] = [] 

168 

169 # The PEP 517 backend we should use to build the project 

170 self._pep517_backend_spec: str 

171 self._pep517_backend_path: str | None 

172 self.pep517_backend: BuildBackendHookCaller | None = None 

173 

174 # This requirement needs more preparation before it can be built 

175 self.needs_more_preparation = False 

176 

177 # This requirement needs to be unpacked before it can be installed. 

178 self._archive_source: Path | None = None 

179 

180 def __str__(self) -> str: 

181 if self.req: 

182 s = redact_auth_from_requirement(self.req) 

183 if self.link: 

184 s += f" from {redact_auth_from_url(self.link.url)}" 

185 elif self.link: 

186 s = redact_auth_from_url(self.link.url) 

187 else: 

188 s = "<InstallRequirement>" 

189 if self.satisfied_by is not None: 

190 if self.satisfied_by.location is not None: 

191 location = display_path(self.satisfied_by.location) 

192 else: 

193 location = "<memory>" 

194 s += f" in {location}" 

195 if self.comes_from: 

196 if isinstance(self.comes_from, str): 

197 comes_from: str | None = self.comes_from 

198 else: 

199 comes_from = self.comes_from.from_path() 

200 if comes_from: 

201 s += f" (from {comes_from})" 

202 return s 

203 

204 def __repr__(self) -> str: 

205 return ( 

206 f"<{self.__class__.__name__} object: " 

207 f"{str(self)} editable={self.editable!r}>" 

208 ) 

209 

210 def format_debug(self) -> str: 

211 """An un-tested helper for getting state, for debugging.""" 

212 attributes = vars(self) 

213 names = sorted(attributes) 

214 

215 state = (f"{attr}={attributes[attr]!r}" for attr in sorted(names)) 

216 return "<{name} object: {{{state}}}>".format( 

217 name=self.__class__.__name__, 

218 state=", ".join(state), 

219 ) 

220 

221 # Things that are valid for all kinds of requirements? 

222 @property 

223 def name(self) -> str | None: 

224 if self.req is None: 

225 return None 

226 return self.req.name 

227 

228 @functools.cached_property 

229 def supports_pyproject_editable(self) -> bool: 

230 assert self.pep517_backend 

231 assert isinstance(self.pep517_backend, ConfiguredBuildBackendHookCaller) 

232 with self.build_env: 

233 runner = runner_with_spinner_message( 

234 "Checking if build backend supports build_editable" 

235 ) 

236 with self.pep517_backend.subprocess_runner(runner): 

237 return self.pep517_backend.supports_feature("build_editable") 

238 

239 @property 

240 def specifier(self) -> SpecifierSet: 

241 assert self.req is not None 

242 return self.req.specifier 

243 

244 @property 

245 def is_direct(self) -> bool: 

246 """Whether this requirement was specified as a direct URL.""" 

247 return self.original_link is not None 

248 

249 @property 

250 def is_pinned(self) -> bool: 

251 """Return whether I am pinned to an exact version. 

252 

253 For example, some-package==1.2 is pinned; some-package>1.2 is not. 

254 """ 

255 assert self.req is not None 

256 specifiers = self.req.specifier 

257 return len(specifiers) == 1 and next(iter(specifiers)).operator in {"==", "==="} 

258 

259 def match_markers(self, extras_requested: Iterable[str] | None = None) -> bool: 

260 if not extras_requested: 

261 # Provide an extra to safely evaluate the markers 

262 # without matching any extra 

263 extras_requested = ("",) 

264 if self.markers is not None: 

265 return any( 

266 self.markers.evaluate({"extra": extra}) for extra in extras_requested 

267 ) 

268 else: 

269 return True 

270 

271 @property 

272 def has_hash_options(self) -> bool: 

273 """Return whether any known-good hashes are specified as options. 

274 

275 These activate --require-hashes mode; hashes specified as part of a 

276 URL do not. 

277 

278 """ 

279 return bool(self.hash_options) 

280 

281 def hashes(self, trust_internet: bool = True) -> Hashes: 

282 """Return a hash-comparer that considers my option- and URL-based 

283 hashes to be known-good. 

284 

285 Hashes in URLs--ones embedded in the requirements file, not ones 

286 downloaded from an index server--are almost peers with ones from 

287 flags. They satisfy --require-hashes (whether it was implicitly or 

288 explicitly activated) but do not activate it. md5 and sha224 are not 

289 allowed in flags, which should nudge people toward good algos. We 

290 always OR all hashes together, even ones from URLs. 

291 

292 :param trust_internet: Whether to trust URL-based (#md5=...) hashes 

293 downloaded from the internet, as by populate_link() 

294 

295 """ 

296 good_hashes = self.hash_options.copy() 

297 if trust_internet: 

298 link = self.link 

299 elif self.is_direct and self.user_supplied: 

300 link = self.original_link 

301 else: 

302 link = None 

303 if link and link.hash: 

304 assert link.hash_name is not None 

305 good_hashes.setdefault(link.hash_name, []).append(link.hash) 

306 return Hashes(good_hashes) 

307 

308 def from_path(self) -> str | None: 

309 """Format a nice indicator to show where this "comes from" """ 

310 if self.req is None: 

311 return None 

312 s = str(self.req) 

313 if self.comes_from: 

314 comes_from: str | None 

315 if isinstance(self.comes_from, str): 

316 comes_from = self.comes_from 

317 else: 

318 comes_from = self.comes_from.from_path() 

319 if comes_from: 

320 s += "->" + comes_from 

321 return s 

322 

323 def ensure_build_location( 

324 self, build_dir: str, autodelete: bool, parallel_builds: bool 

325 ) -> str: 

326 assert build_dir is not None 

327 if self._temp_build_dir is not None: 

328 assert self._temp_build_dir.path 

329 return self._temp_build_dir.path 

330 if self.req is None: 

331 # Some systems have /tmp as a symlink which confuses custom 

332 # builds (such as numpy). Thus, we ensure that the real path 

333 # is returned. 

334 self._temp_build_dir = TempDirectory( 

335 kind=TempDirectoryKind.REQ_BUILD, globally_managed=True 

336 ) 

337 

338 return self._temp_build_dir.path 

339 

340 # This is the only remaining place where we manually determine the path 

341 # for the temporary directory. It is only needed for editables where 

342 # it is the value of the --src option. 

343 

344 # When parallel builds are enabled, add a UUID to the build directory 

345 # name so multiple builds do not interfere with each other. 

346 dir_name: str = canonicalize_name(self.req.name) 

347 if parallel_builds: 

348 dir_name = f"{dir_name}_{uuid.uuid4().hex}" 

349 

350 # FIXME: Is there a better place to create the build_dir? (hg and bzr 

351 # need this) 

352 if not os.path.exists(build_dir): 

353 logger.debug("Creating directory %s", build_dir) 

354 os.makedirs(build_dir) 

355 actual_build_dir = os.path.join(build_dir, dir_name) 

356 # `None` indicates that we respect the globally-configured deletion 

357 # settings, which is what we actually want when auto-deleting. 

358 delete_arg = None if autodelete else False 

359 return TempDirectory( 

360 path=actual_build_dir, 

361 delete=delete_arg, 

362 kind=TempDirectoryKind.REQ_BUILD, 

363 globally_managed=True, 

364 ).path 

365 

366 def _set_requirement(self) -> None: 

367 """Set requirement after generating metadata.""" 

368 assert self.req is None 

369 assert self.metadata is not None 

370 assert self.source_dir is not None 

371 

372 # Construct a Requirement object from the generated metadata 

373 if isinstance(parse_version(self.metadata["Version"]), Version): 

374 op = "==" 

375 else: 

376 op = "===" 

377 

378 self.req = get_requirement( 

379 "".join( 

380 [ 

381 self.metadata["Name"], 

382 op, 

383 self.metadata["Version"], 

384 ] 

385 ) 

386 ) 

387 

388 def warn_on_mismatching_name(self) -> None: 

389 assert self.req is not None 

390 metadata_name = canonicalize_name(self.metadata["Name"]) 

391 if canonicalize_name(self.req.name) == metadata_name: 

392 # Everything is fine. 

393 return 

394 

395 # If we're here, there's a mismatch. Log a warning about it. 

396 logger.warning( 

397 "Generating metadata for package %s " 

398 "produced metadata for project name %s. Fix your " 

399 "#egg=%s fragments.", 

400 self.name, 

401 metadata_name, 

402 self.name, 

403 ) 

404 self.req = get_requirement(metadata_name) 

405 

406 def check_if_exists(self, use_user_site: bool) -> None: 

407 """Find an installed distribution that satisfies or conflicts 

408 with this requirement, and set self.satisfied_by or 

409 self.should_reinstall appropriately. 

410 """ 

411 if self.req is None: 

412 return 

413 existing_dist = get_default_environment().get_distribution(self.req.name) 

414 if not existing_dist: 

415 return 

416 

417 version_compatible = self.req.specifier.contains( 

418 existing_dist.version, 

419 prereleases=True, 

420 ) 

421 if not version_compatible: 

422 self.satisfied_by = None 

423 if use_user_site: 

424 if existing_dist.in_usersite: 

425 self.should_reinstall = True 

426 elif running_under_virtualenv() and existing_dist.in_site_packages: 

427 raise InstallationError( 

428 f"Will not install to the user site because it will " 

429 f"lack sys.path precedence to {existing_dist.raw_name} " 

430 f"in {existing_dist.location}" 

431 ) 

432 else: 

433 self.should_reinstall = True 

434 else: 

435 if self.editable: 

436 self.should_reinstall = True 

437 # when installing editables, nothing pre-existing should ever 

438 # satisfy 

439 self.satisfied_by = None 

440 else: 

441 self.satisfied_by = existing_dist 

442 

443 # Things valid for wheels 

444 @property 

445 def is_wheel(self) -> bool: 

446 if not self.link: 

447 return False 

448 return self.link.is_wheel 

449 

450 @property 

451 def is_wheel_from_cache(self) -> bool: 

452 # When True, it means that this InstallRequirement is a local wheel file in the 

453 # cache of locally built wheels. 

454 return self.cached_wheel_source_link is not None 

455 

456 # Things valid for sdists 

457 @property 

458 def unpacked_source_directory(self) -> str: 

459 assert self.source_dir, f"No source dir for {self}" 

460 return os.path.join( 

461 self.source_dir, self.link and self.link.subdirectory_fragment or "" 

462 ) 

463 

464 @property 

465 def setup_py_path(self) -> str: 

466 assert self.source_dir, f"No source dir for {self}" 

467 setup_py = os.path.join(self.unpacked_source_directory, "setup.py") 

468 

469 return setup_py 

470 

471 @property 

472 def pyproject_toml_path(self) -> str: 

473 assert self.source_dir, f"No source dir for {self}" 

474 return make_pyproject_path(self.unpacked_source_directory) 

475 

476 def load_pyproject_toml(self) -> None: 

477 """Load the pyproject.toml file. 

478 

479 After calling this routine, all of the attributes related to PEP 517 

480 processing for this requirement have been set. 

481 """ 

482 pyproject_toml_data = load_pyproject_toml( 

483 self.pyproject_toml_path, self.setup_py_path, str(self) 

484 ) 

485 assert pyproject_toml_data 

486 requires, backend, check, backend_path = pyproject_toml_data 

487 self.requirements_to_check = check 

488 self.pyproject_requires = requires 

489 self._pep517_backend_spec = backend 

490 self._pep517_backend_path = backend_path 

491 

492 def configure_backend(self, python_executable: str) -> None: 

493 """Set up the build backend hook caller. 

494 

495 This is done separately after pyproject.toml loading as the backend 

496 need to be called with the build environment's Python executable, 

497 which can vary.""" 

498 self.pep517_backend = ConfiguredBuildBackendHookCaller( 

499 self, 

500 self.unpacked_source_directory, 

501 self._pep517_backend_spec, 

502 backend_path=self._pep517_backend_path, 

503 python_executable=python_executable, 

504 backend_warning_level=( 

505 logging.WARNING 

506 if self.link and self.link.is_existing_dir() 

507 else logging.DEBUG 

508 ), 

509 ) 

510 

511 def editable_sanity_check(self) -> None: 

512 """Check that an editable requirement if valid for use with PEP 517/518. 

513 

514 This verifies that an editable has a build backend that supports PEP 660. 

515 """ 

516 if self.editable and not self.supports_pyproject_editable: 

517 raise InstallationError( 

518 f"Project {self} uses a build backend " 

519 f"that is missing the 'build_editable' hook, so " 

520 f"it cannot be installed in editable mode. " 

521 f"Consider using a build backend that supports PEP 660." 

522 ) 

523 

524 def prepare_metadata(self, allow_editables: bool) -> None: 

525 """Ensure that project metadata is available. 

526 

527 Under PEP 517 and PEP 660, call the backend hook to prepare the metadata. 

528 Under legacy processing, call setup.py egg-info. 

529 """ 

530 assert self.source_dir, f"No source dir for {self}" 

531 details = self.name or f"from {self.link}" 

532 

533 assert self.pep517_backend is not None 

534 if self.editable and allow_editables and self.supports_pyproject_editable: 

535 self.metadata_directory = generate_editable_metadata( 

536 build_env=self.build_env, 

537 backend=self.pep517_backend, 

538 details=details, 

539 ) 

540 else: 

541 self.metadata_directory = generate_metadata( 

542 build_env=self.build_env, 

543 backend=self.pep517_backend, 

544 details=details, 

545 ) 

546 

547 # Act on the newly generated metadata, based on the name and version. 

548 if not self.name: 

549 self._set_requirement() 

550 else: 

551 self.warn_on_mismatching_name() 

552 

553 self.assert_source_matches_version() 

554 

555 @property 

556 def metadata(self) -> Any: 

557 if not hasattr(self, "_metadata"): 

558 self._metadata = self.get_dist().metadata 

559 

560 return self._metadata 

561 

562 def set_dist(self, distribution: BaseDistribution) -> None: 

563 self._distribution = distribution 

564 

565 def get_dist(self) -> BaseDistribution: 

566 if self._distribution is not None: 

567 return self._distribution 

568 elif self.metadata_directory: 

569 return get_directory_distribution(self.metadata_directory) 

570 elif self.local_file_path and self.is_wheel: 

571 assert self.req is not None 

572 return get_wheel_distribution( 

573 FilesystemWheel(self.local_file_path), 

574 canonicalize_name(self.req.name), 

575 ) 

576 raise AssertionError( 

577 f"InstallRequirement {self} has no metadata directory and no wheel: " 

578 f"can't make a distribution." 

579 ) 

580 

581 def assert_source_matches_version(self) -> None: 

582 assert self.source_dir, f"No source dir for {self}" 

583 version = self.metadata["version"] 

584 if self.req and self.req.specifier and version not in self.req.specifier: 

585 logger.warning( 

586 "Requested %s, but installing version %s", 

587 self, 

588 version, 

589 ) 

590 else: 

591 logger.debug( 

592 "Source in %s has version %s, which satisfies requirement %s", 

593 display_path(self.source_dir), 

594 version, 

595 self, 

596 ) 

597 

598 # For both source distributions and editables 

599 def ensure_has_source_dir( 

600 self, 

601 parent_dir: str, 

602 autodelete: bool = False, 

603 parallel_builds: bool = False, 

604 ) -> None: 

605 """Ensure that a source_dir is set. 

606 

607 This will create a temporary build dir if the name of the requirement 

608 isn't known yet. 

609 

610 :param parent_dir: The ideal pip parent_dir for the source_dir. 

611 Generally src_dir for editables and build_dir for sdists. 

612 :return: self.source_dir 

613 """ 

614 if self.source_dir is None: 

615 self.source_dir = self.ensure_build_location( 

616 parent_dir, 

617 autodelete=autodelete, 

618 parallel_builds=parallel_builds, 

619 ) 

620 

621 def needs_unpacked_archive(self, archive_source: Path) -> None: 

622 assert self._archive_source is None 

623 self._archive_source = archive_source 

624 

625 def ensure_pristine_source_checkout(self) -> None: 

626 """Ensure the source directory has not yet been built in.""" 

627 assert self.source_dir is not None 

628 if self._archive_source is not None: 

629 unpack_file(str(self._archive_source), self.source_dir) 

630 elif is_installable_dir(self.source_dir): 

631 # If a checkout exists, it's unwise to keep going. 

632 # version inconsistencies are logged later, but do not fail 

633 # the installation. 

634 raise PreviousBuildDirError( 

635 f"pip can't proceed with requirements '{self}' due to a " 

636 f"pre-existing build directory ({self.source_dir}). This is likely " 

637 "due to a previous installation that failed . pip is " 

638 "being responsible and not assuming it can delete this. " 

639 "Please delete it and try again." 

640 ) 

641 

642 # For editable installations 

643 def update_editable(self) -> None: 

644 if not self.link: 

645 logger.debug( 

646 "Cannot update repository at %s; repository location is unknown", 

647 self.source_dir, 

648 ) 

649 return 

650 assert self.editable 

651 assert self.source_dir 

652 if self.link.scheme == "file": 

653 # Static paths don't get updated 

654 return 

655 vcs_backend = vcs.get_backend_for_scheme(self.link.scheme) 

656 # Editable requirements are validated in Requirement constructors. 

657 # So here, if it's neither a path nor a valid VCS URL, it's a bug. 

658 assert vcs_backend, f"Unsupported VCS URL {self.link.url}" 

659 hidden_url = hide_url(self.link.url) 

660 vcs_backend.obtain(self.source_dir, url=hidden_url, verbosity=0) 

661 

662 # Top-level Actions 

663 def uninstall( 

664 self, auto_confirm: bool = False, verbose: bool = False 

665 ) -> UninstallPathSet | None: 

666 """ 

667 Uninstall the distribution currently satisfying this requirement. 

668 

669 Prompts before removing or modifying files unless 

670 ``auto_confirm`` is True. 

671 

672 Refuses to delete or modify files outside of ``sys.prefix`` - 

673 thus uninstallation within a virtual environment can only 

674 modify that virtual environment, even if the virtualenv is 

675 linked to global site-packages. 

676 

677 """ 

678 assert self.req 

679 dist = get_default_environment().get_distribution(self.req.name) 

680 if not dist: 

681 logger.warning("Skipping %s as it is not installed.", self.name) 

682 return None 

683 logger.info("Found existing installation: %s", dist) 

684 

685 uninstalled_pathset = UninstallPathSet.from_dist(dist) 

686 uninstalled_pathset.remove(auto_confirm, verbose) 

687 return uninstalled_pathset 

688 

689 def _get_archive_name(self, path: str, parentdir: str, rootdir: str) -> str: 

690 def _clean_zip_name(name: str, prefix: str) -> str: 

691 assert name.startswith( 

692 prefix + os.path.sep 

693 ), f"name {name!r} doesn't start with prefix {prefix!r}" 

694 name = name[len(prefix) + 1 :] 

695 name = name.replace(os.path.sep, "/") 

696 return name 

697 

698 assert self.req is not None 

699 path = os.path.join(parentdir, path) 

700 name = _clean_zip_name(path, rootdir) 

701 return self.req.name + "/" + name 

702 

703 def archive(self, build_dir: str | None) -> None: 

704 """Saves archive to provided build_dir. 

705 

706 Used for saving downloaded VCS requirements as part of `pip download`. 

707 """ 

708 assert self.source_dir 

709 if build_dir is None: 

710 return 

711 

712 create_archive = True 

713 archive_name = "{}-{}.zip".format(self.name, self.metadata["version"]) 

714 archive_path = os.path.join(build_dir, archive_name) 

715 

716 if os.path.exists(archive_path): 

717 response = ask_path_exists( 

718 f"The file {display_path(archive_path)} exists. (i)gnore, (w)ipe, " 

719 "(b)ackup, (a)bort ", 

720 ("i", "w", "b", "a"), 

721 ) 

722 if response == "i": 

723 create_archive = False 

724 elif response == "w": 

725 logger.warning("Deleting %s", display_path(archive_path)) 

726 os.remove(archive_path) 

727 elif response == "b": 

728 dest_file = backup_dir(archive_path) 

729 logger.warning( 

730 "Backing up %s to %s", 

731 display_path(archive_path), 

732 display_path(dest_file), 

733 ) 

734 shutil.move(archive_path, dest_file) 

735 elif response == "a": 

736 sys.exit(-1) 

737 

738 if not create_archive: 

739 return 

740 

741 zip_output = zipfile.ZipFile( 

742 archive_path, 

743 "w", 

744 zipfile.ZIP_DEFLATED, 

745 allowZip64=True, 

746 ) 

747 with zip_output: 

748 dir = os.path.normcase(os.path.abspath(self.unpacked_source_directory)) 

749 for dirpath, dirnames, filenames in os.walk(dir): 

750 for dirname in dirnames: 

751 dir_arcname = self._get_archive_name( 

752 dirname, 

753 parentdir=dirpath, 

754 rootdir=dir, 

755 ) 

756 zipdir = zipfile.ZipInfo(dir_arcname + "/") 

757 zipdir.external_attr = 0x1ED << 16 # 0o755 

758 zip_output.writestr(zipdir, "") 

759 for filename in filenames: 

760 file_arcname = self._get_archive_name( 

761 filename, 

762 parentdir=dirpath, 

763 rootdir=dir, 

764 ) 

765 filename = os.path.join(dirpath, filename) 

766 zip_output.write(filename, file_arcname) 

767 

768 logger.info("Saved %s", display_path(archive_path)) 

769 

770 def install( 

771 self, 

772 root: str | None = None, 

773 home: str | None = None, 

774 prefix: str | None = None, 

775 warn_script_location: bool = True, 

776 use_user_site: bool = False, 

777 pycompile: bool = True, 

778 script_executable: str | None = None, 

779 ) -> None: 

780 # Lazy import to avoid transitively importing `_vendor.distlib.compat` 

781 # which in turn imports `urllib.request` which is slow. 

782 # During an actual installation, `urllib.request` will end up imported anyway, 

783 # but `req.req_install` (this module) is also imported from commands that 

784 # don't actually install anything (e.g. `pip freeze` or `pip show`). 

785 from pip._internal.operations.install.wheel import install_wheel 

786 

787 assert self.req is not None 

788 scheme = get_scheme( 

789 self.req.name, 

790 user=use_user_site, 

791 home=home, 

792 root=root, 

793 isolated=self.isolated, 

794 prefix=prefix, 

795 ) 

796 

797 assert self.is_wheel 

798 assert self.local_file_path 

799 

800 install_wheel( 

801 self.req.name, 

802 self.local_file_path, 

803 scheme=scheme, 

804 req_description=str(self.req), 

805 pycompile=pycompile, 

806 warn_script_location=warn_script_location, 

807 direct_url=self.download_info if self.is_direct else None, 

808 requested=self.user_supplied, 

809 script_executable=script_executable, 

810 ) 

811 self.install_succeeded = True 

812 

813 

814def check_invalid_constraint_type(req: InstallRequirement) -> str: 

815 # Check for unsupported forms 

816 problem = "" 

817 if not req.name: 

818 problem = "Unnamed requirements are not allowed as constraints" 

819 elif req.editable: 

820 problem = "Editable requirements are not allowed as constraints" 

821 elif req.extras: 

822 problem = "Constraints cannot have extras" 

823 

824 if problem: 

825 deprecated( 

826 reason=( 

827 "Constraints are only allowed to take the form of a package " 

828 "name and a version specifier. Other forms were originally " 

829 "permitted as an accident of the implementation, but were " 

830 "undocumented. The new implementation of the resolver no " 

831 "longer supports these forms." 

832 ), 

833 replacement="replacing the constraint with a requirement", 

834 # No plan yet for when the new resolver becomes default 

835 gone_in=None, 

836 issue=8210, 

837 ) 

838 

839 return problem 

840 

841 

842def _has_option(options: Values, reqs: list[InstallRequirement], option: str) -> bool: 

843 if getattr(options, option, None): 

844 return True 

845 for req in reqs: 

846 if getattr(req, option, None): 

847 return True 

848 return False