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

393 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, tempdir_kinds 

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 with self.build_env: 

232 runner = runner_with_spinner_message( 

233 "Checking if build backend supports build_editable" 

234 ) 

235 with self.pep517_backend.subprocess_runner(runner): 

236 return "build_editable" in self.pep517_backend._supported_features() 

237 

238 @property 

239 def specifier(self) -> SpecifierSet: 

240 assert self.req is not None 

241 return self.req.specifier 

242 

243 @property 

244 def is_direct(self) -> bool: 

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

246 return self.original_link is not None 

247 

248 @property 

249 def is_pinned(self) -> bool: 

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

251 

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

253 """ 

254 assert self.req is not None 

255 specifiers = self.req.specifier 

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

257 

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

259 if not extras_requested: 

260 # Provide an extra to safely evaluate the markers 

261 # without matching any extra 

262 extras_requested = ("",) 

263 if self.markers is not None: 

264 return any( 

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

266 ) 

267 else: 

268 return True 

269 

270 @property 

271 def has_hash_options(self) -> bool: 

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

273 

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

275 URL do not. 

276 

277 """ 

278 return bool(self.hash_options) 

279 

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

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

282 hashes to be known-good. 

283 

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

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

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

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

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

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

290 

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

292 downloaded from the internet, as by populate_link() 

293 

294 """ 

295 good_hashes = self.hash_options.copy() 

296 if trust_internet: 

297 link = self.link 

298 elif self.is_direct and self.user_supplied: 

299 link = self.original_link 

300 else: 

301 link = None 

302 if link and link.hash: 

303 assert link.hash_name is not None 

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

305 return Hashes(good_hashes) 

306 

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

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

309 if self.req is None: 

310 return None 

311 s = str(self.req) 

312 if self.comes_from: 

313 comes_from: str | None 

314 if isinstance(self.comes_from, str): 

315 comes_from = self.comes_from 

316 else: 

317 comes_from = self.comes_from.from_path() 

318 if comes_from: 

319 s += "->" + comes_from 

320 return s 

321 

322 def ensure_build_location( 

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

324 ) -> str: 

325 assert build_dir is not None 

326 if self._temp_build_dir is not None: 

327 assert self._temp_build_dir.path 

328 return self._temp_build_dir.path 

329 if self.req is None: 

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

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

332 # is returned. 

333 self._temp_build_dir = TempDirectory( 

334 kind=tempdir_kinds.REQ_BUILD, globally_managed=True 

335 ) 

336 

337 return self._temp_build_dir.path 

338 

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

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

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

342 

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

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

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

346 if parallel_builds: 

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

348 

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

350 # need this) 

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

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

353 os.makedirs(build_dir) 

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

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

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

357 delete_arg = None if autodelete else False 

358 return TempDirectory( 

359 path=actual_build_dir, 

360 delete=delete_arg, 

361 kind=tempdir_kinds.REQ_BUILD, 

362 globally_managed=True, 

363 ).path 

364 

365 def _set_requirement(self) -> None: 

366 """Set requirement after generating metadata.""" 

367 assert self.req is None 

368 assert self.metadata is not None 

369 assert self.source_dir is not None 

370 

371 # Construct a Requirement object from the generated metadata 

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

373 op = "==" 

374 else: 

375 op = "===" 

376 

377 self.req = get_requirement( 

378 "".join( 

379 [ 

380 self.metadata["Name"], 

381 op, 

382 self.metadata["Version"], 

383 ] 

384 ) 

385 ) 

386 

387 def warn_on_mismatching_name(self) -> None: 

388 assert self.req is not None 

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

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

391 # Everything is fine. 

392 return 

393 

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

395 logger.warning( 

396 "Generating metadata for package %s " 

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

398 "#egg=%s fragments.", 

399 self.name, 

400 metadata_name, 

401 self.name, 

402 ) 

403 self.req = get_requirement(metadata_name) 

404 

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

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

407 with this requirement, and set self.satisfied_by or 

408 self.should_reinstall appropriately. 

409 """ 

410 if self.req is None: 

411 return 

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

413 if not existing_dist: 

414 return 

415 

416 version_compatible = self.req.specifier.contains( 

417 existing_dist.version, 

418 prereleases=True, 

419 ) 

420 if not version_compatible: 

421 self.satisfied_by = None 

422 if use_user_site: 

423 if existing_dist.in_usersite: 

424 self.should_reinstall = True 

425 elif running_under_virtualenv() and existing_dist.in_site_packages: 

426 raise InstallationError( 

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

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

429 f"in {existing_dist.location}" 

430 ) 

431 else: 

432 self.should_reinstall = True 

433 else: 

434 if self.editable: 

435 self.should_reinstall = True 

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

437 # satisfy 

438 self.satisfied_by = None 

439 else: 

440 self.satisfied_by = existing_dist 

441 

442 # Things valid for wheels 

443 @property 

444 def is_wheel(self) -> bool: 

445 if not self.link: 

446 return False 

447 return self.link.is_wheel 

448 

449 @property 

450 def is_wheel_from_cache(self) -> bool: 

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

452 # cache of locally built wheels. 

453 return self.cached_wheel_source_link is not None 

454 

455 # Things valid for sdists 

456 @property 

457 def unpacked_source_directory(self) -> str: 

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

459 return os.path.join( 

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

461 ) 

462 

463 @property 

464 def setup_py_path(self) -> str: 

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

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

467 

468 return setup_py 

469 

470 @property 

471 def pyproject_toml_path(self) -> str: 

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

473 return make_pyproject_path(self.unpacked_source_directory) 

474 

475 def load_pyproject_toml(self) -> None: 

476 """Load the pyproject.toml file. 

477 

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

479 processing for this requirement have been set. 

480 """ 

481 pyproject_toml_data = load_pyproject_toml( 

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

483 ) 

484 assert pyproject_toml_data 

485 requires, backend, check, backend_path = pyproject_toml_data 

486 self.requirements_to_check = check 

487 self.pyproject_requires = requires 

488 self._pep517_backend_spec = backend 

489 self._pep517_backend_path = backend_path 

490 

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

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

493 

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

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

496 which can vary.""" 

497 self.pep517_backend = ConfiguredBuildBackendHookCaller( 

498 self, 

499 self.unpacked_source_directory, 

500 self._pep517_backend_spec, 

501 backend_path=self._pep517_backend_path, 

502 python_executable=python_executable, 

503 ) 

504 

505 def editable_sanity_check(self) -> None: 

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

507 

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

509 """ 

510 if self.editable and not self.supports_pyproject_editable: 

511 raise InstallationError( 

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

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

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

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

516 ) 

517 

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

519 """Ensure that project metadata is available. 

520 

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

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

523 """ 

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

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

526 

527 assert self.pep517_backend is not None 

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

529 self.metadata_directory = generate_editable_metadata( 

530 build_env=self.build_env, 

531 backend=self.pep517_backend, 

532 details=details, 

533 ) 

534 else: 

535 self.metadata_directory = generate_metadata( 

536 build_env=self.build_env, 

537 backend=self.pep517_backend, 

538 details=details, 

539 ) 

540 

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

542 if not self.name: 

543 self._set_requirement() 

544 else: 

545 self.warn_on_mismatching_name() 

546 

547 self.assert_source_matches_version() 

548 

549 @property 

550 def metadata(self) -> Any: 

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

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

553 

554 return self._metadata 

555 

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

557 self._distribution = distribution 

558 

559 def get_dist(self) -> BaseDistribution: 

560 if self._distribution is not None: 

561 return self._distribution 

562 elif self.metadata_directory: 

563 return get_directory_distribution(self.metadata_directory) 

564 elif self.local_file_path and self.is_wheel: 

565 assert self.req is not None 

566 return get_wheel_distribution( 

567 FilesystemWheel(self.local_file_path), 

568 canonicalize_name(self.req.name), 

569 ) 

570 raise AssertionError( 

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

572 f"can't make a distribution." 

573 ) 

574 

575 def assert_source_matches_version(self) -> None: 

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

577 version = self.metadata["version"] 

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

579 logger.warning( 

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

581 self, 

582 version, 

583 ) 

584 else: 

585 logger.debug( 

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

587 display_path(self.source_dir), 

588 version, 

589 self, 

590 ) 

591 

592 # For both source distributions and editables 

593 def ensure_has_source_dir( 

594 self, 

595 parent_dir: str, 

596 autodelete: bool = False, 

597 parallel_builds: bool = False, 

598 ) -> None: 

599 """Ensure that a source_dir is set. 

600 

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

602 isn't known yet. 

603 

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

605 Generally src_dir for editables and build_dir for sdists. 

606 :return: self.source_dir 

607 """ 

608 if self.source_dir is None: 

609 self.source_dir = self.ensure_build_location( 

610 parent_dir, 

611 autodelete=autodelete, 

612 parallel_builds=parallel_builds, 

613 ) 

614 

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

616 assert self._archive_source is None 

617 self._archive_source = archive_source 

618 

619 def ensure_pristine_source_checkout(self) -> None: 

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

621 assert self.source_dir is not None 

622 if self._archive_source is not None: 

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

624 elif is_installable_dir(self.source_dir): 

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

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

627 # the installation. 

628 raise PreviousBuildDirError( 

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

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

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

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

633 "Please delete it and try again." 

634 ) 

635 

636 # For editable installations 

637 def update_editable(self) -> None: 

638 if not self.link: 

639 logger.debug( 

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

641 self.source_dir, 

642 ) 

643 return 

644 assert self.editable 

645 assert self.source_dir 

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

647 # Static paths don't get updated 

648 return 

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

650 # Editable requirements are validated in Requirement constructors. 

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

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

653 hidden_url = hide_url(self.link.url) 

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

655 

656 # Top-level Actions 

657 def uninstall( 

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

659 ) -> UninstallPathSet | None: 

660 """ 

661 Uninstall the distribution currently satisfying this requirement. 

662 

663 Prompts before removing or modifying files unless 

664 ``auto_confirm`` is True. 

665 

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

667 thus uninstallation within a virtual environment can only 

668 modify that virtual environment, even if the virtualenv is 

669 linked to global site-packages. 

670 

671 """ 

672 assert self.req 

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

674 if not dist: 

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

676 return None 

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

678 

679 uninstalled_pathset = UninstallPathSet.from_dist(dist) 

680 uninstalled_pathset.remove(auto_confirm, verbose) 

681 return uninstalled_pathset 

682 

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

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

685 assert name.startswith( 

686 prefix + os.path.sep 

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

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

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

690 return name 

691 

692 assert self.req is not None 

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

694 name = _clean_zip_name(path, rootdir) 

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

696 

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

698 """Saves archive to provided build_dir. 

699 

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

701 """ 

702 assert self.source_dir 

703 if build_dir is None: 

704 return 

705 

706 create_archive = True 

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

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

709 

710 if os.path.exists(archive_path): 

711 response = ask_path_exists( 

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

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

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

715 ) 

716 if response == "i": 

717 create_archive = False 

718 elif response == "w": 

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

720 os.remove(archive_path) 

721 elif response == "b": 

722 dest_file = backup_dir(archive_path) 

723 logger.warning( 

724 "Backing up %s to %s", 

725 display_path(archive_path), 

726 display_path(dest_file), 

727 ) 

728 shutil.move(archive_path, dest_file) 

729 elif response == "a": 

730 sys.exit(-1) 

731 

732 if not create_archive: 

733 return 

734 

735 zip_output = zipfile.ZipFile( 

736 archive_path, 

737 "w", 

738 zipfile.ZIP_DEFLATED, 

739 allowZip64=True, 

740 ) 

741 with zip_output: 

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

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

744 for dirname in dirnames: 

745 dir_arcname = self._get_archive_name( 

746 dirname, 

747 parentdir=dirpath, 

748 rootdir=dir, 

749 ) 

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

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

752 zip_output.writestr(zipdir, "") 

753 for filename in filenames: 

754 file_arcname = self._get_archive_name( 

755 filename, 

756 parentdir=dirpath, 

757 rootdir=dir, 

758 ) 

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

760 zip_output.write(filename, file_arcname) 

761 

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

763 

764 def install( 

765 self, 

766 root: str | None = None, 

767 home: str | None = None, 

768 prefix: str | None = None, 

769 warn_script_location: bool = True, 

770 use_user_site: bool = False, 

771 pycompile: bool = True, 

772 script_executable: str | None = None, 

773 ) -> None: 

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

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

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

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

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

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

780 

781 assert self.req is not None 

782 scheme = get_scheme( 

783 self.req.name, 

784 user=use_user_site, 

785 home=home, 

786 root=root, 

787 isolated=self.isolated, 

788 prefix=prefix, 

789 ) 

790 

791 assert self.is_wheel 

792 assert self.local_file_path 

793 

794 install_wheel( 

795 self.req.name, 

796 self.local_file_path, 

797 scheme=scheme, 

798 req_description=str(self.req), 

799 pycompile=pycompile, 

800 warn_script_location=warn_script_location, 

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

802 requested=self.user_supplied, 

803 script_executable=script_executable, 

804 ) 

805 self.install_succeeded = True 

806 

807 

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

809 # Check for unsupported forms 

810 problem = "" 

811 if not req.name: 

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

813 elif req.editable: 

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

815 elif req.extras: 

816 problem = "Constraints cannot have extras" 

817 

818 if problem: 

819 deprecated( 

820 reason=( 

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

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

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

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

825 "longer supports these forms." 

826 ), 

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

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

829 gone_in=None, 

830 issue=8210, 

831 ) 

832 

833 return problem 

834 

835 

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

837 if getattr(options, option, None): 

838 return True 

839 for req in reqs: 

840 if getattr(req, option, None): 

841 return True 

842 return False