Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/pip/_internal/utils/misc.py: 33%

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

359 statements  

1from __future__ import annotations 

2 

3import errno 

4import getpass 

5import hashlib 

6import logging 

7import os 

8import pathlib 

9import posixpath 

10import shutil 

11import stat 

12import sys 

13import sysconfig 

14import urllib.parse 

15from collections.abc import Callable, Generator, Iterable, Iterator, Mapping, Sequence 

16from dataclasses import dataclass 

17from functools import partial 

18from io import StringIO 

19from itertools import filterfalse, tee, zip_longest 

20from pathlib import Path 

21from types import FunctionType, TracebackType 

22from typing import ( 

23 Any, 

24 BinaryIO, 

25 TextIO, 

26 TypeVar, 

27 cast, 

28) 

29 

30from pip._vendor.packaging.requirements import Requirement 

31from pip._vendor.pyproject_hooks import BuildBackendHookCaller 

32 

33from pip import __file__ as pip_location 

34from pip import __version__ 

35from pip._internal.exceptions import CommandError, ExternallyManagedEnvironment 

36from pip._internal.locations import get_major_minor_version 

37from pip._internal.utils.compat import WINDOWS 

38from pip._internal.utils.retry import retry 

39from pip._internal.utils.virtualenv import running_under_virtualenv 

40 

41__all__ = [ 

42 "rmtree", 

43 "display_path", 

44 "backup_dir", 

45 "ask", 

46 "splitext", 

47 "format_size", 

48 "is_installable_dir", 

49 "normalize_path", 

50 "renames", 

51 "get_prog", 

52 "ensure_dir", 

53 "remove_auth_from_url", 

54 "check_externally_managed", 

55 "looks_like_ci", 

56 "ConfiguredBuildBackendHookCaller", 

57] 

58 

59logger = logging.getLogger(__name__) 

60 

61T = TypeVar("T") 

62ExcInfo = tuple[type[BaseException], BaseException, TracebackType] 

63VersionInfo = tuple[int, int, int] 

64NetlocTuple = tuple[str, tuple[str | None, str | None]] 

65OnExc = Callable[[FunctionType, Path, BaseException], Any] 

66OnErr = Callable[[FunctionType, Path, ExcInfo], Any] 

67 

68FILE_CHUNK_SIZE = 1024 * 1024 

69# These are environment variables present when running under various 

70# CI systems. For each variable, some CI systems that use the variable 

71# are indicated. The collection was chosen so that for each of a number 

72# of popular systems, at least one of the environment variables is used. 

73# This list is used to provide some indication of and lower bound for 

74# CI traffic to PyPI. Thus, it is okay if the list is not comprehensive. 

75# For more background, see: https://github.com/pypa/pip/issues/5499 

76CI_ENVIRONMENT_VARIABLES = ( 

77 # Azure Pipelines 

78 "BUILD_BUILDID", 

79 # Jenkins 

80 "BUILD_ID", 

81 # AppVeyor, CircleCI, Codeship, Gitlab CI, Shippable, Travis CI 

82 "CI", 

83 # Explicit environment variable. 

84 "PIP_IS_CI", 

85) 

86 

87 

88def get_pip_version() -> str: 

89 pip_pkg_dir = os.path.join(os.path.dirname(__file__), "..", "..") 

90 pip_pkg_dir = os.path.abspath(pip_pkg_dir) 

91 

92 return f"pip {__version__} from {pip_pkg_dir} (python {get_major_minor_version()})" 

93 

94 

95def get_runnable_pip() -> str: 

96 """Get a file to pass to a Python executable, to run the currently-running pip. 

97 

98 This is used to run a pip subprocess, for installing requirements into the build 

99 environment. 

100 """ 

101 source = pathlib.Path(pip_location).resolve().parent 

102 

103 if not source.is_dir(): 

104 # This would happen if someone is using pip from inside a zip file. In that 

105 # case, we can use that directly. 

106 return str(source) 

107 

108 return os.fsdecode(source / "__pip-runner__.py") 

109 

110 

111def normalize_version_info(py_version_info: tuple[int, ...]) -> tuple[int, int, int]: 

112 """ 

113 Convert a tuple of ints representing a Python version to one of length 

114 three. 

115 

116 :param py_version_info: a tuple of ints representing a Python version, 

117 or None to specify no version. The tuple can have any length. 

118 

119 :return: a tuple of length three if `py_version_info` is non-None. 

120 Otherwise, return `py_version_info` unchanged (i.e. None). 

121 """ 

122 if len(py_version_info) < 3: 

123 py_version_info += (3 - len(py_version_info)) * (0,) 

124 elif len(py_version_info) > 3: 

125 py_version_info = py_version_info[:3] 

126 

127 return cast("VersionInfo", py_version_info) 

128 

129 

130def ensure_dir(path: str) -> None: 

131 """os.path.makedirs without EEXIST.""" 

132 try: 

133 os.makedirs(path) 

134 except OSError as e: 

135 # Windows can raise spurious ENOTEMPTY errors. See #6426. 

136 if e.errno != errno.EEXIST and e.errno != errno.ENOTEMPTY: 

137 raise 

138 

139 

140def get_prog() -> str: 

141 try: 

142 prog = os.path.basename(sys.argv[0]) 

143 if prog in ("__main__.py", "-c"): 

144 return f"{sys.executable} -m pip" 

145 else: 

146 return prog 

147 except (AttributeError, TypeError, IndexError): 

148 pass 

149 return "pip" 

150 

151 

152# Retry every half second for up to 3 seconds 

153@retry(stop_after_delay=3, wait=0.5) 

154def rmtree(dir: str, ignore_errors: bool = False, onexc: OnExc | None = None) -> None: 

155 if ignore_errors: 

156 onexc = _onerror_ignore 

157 if onexc is None: 

158 onexc = _onerror_reraise 

159 handler: OnErr = partial(rmtree_errorhandler, onexc=onexc) 

160 if sys.version_info >= (3, 12): 

161 # See https://docs.python.org/3.12/whatsnew/3.12.html#shutil. 

162 shutil.rmtree(dir, onexc=handler) # type: ignore 

163 else: 

164 shutil.rmtree(dir, onerror=handler) # type: ignore 

165 

166 

167def _onerror_ignore(*_args: Any) -> None: 

168 pass 

169 

170 

171def _onerror_reraise(*_args: Any) -> None: 

172 raise # noqa: PLE0704 - Bare exception used to reraise existing exception 

173 

174 

175def rmtree_errorhandler( 

176 func: FunctionType, 

177 path: Path, 

178 exc_info: ExcInfo | BaseException, 

179 *, 

180 onexc: OnExc = _onerror_reraise, 

181) -> None: 

182 """ 

183 `rmtree` error handler to 'force' a file remove (i.e. like `rm -f`). 

184 

185 * If a file is readonly then it's write flag is set and operation is 

186 retried. 

187 

188 * `onerror` is the original callback from `rmtree(... onerror=onerror)` 

189 that is chained at the end if the "rm -f" still fails. 

190 """ 

191 try: 

192 st_mode = os.stat(path).st_mode 

193 except OSError: 

194 # it's equivalent to os.path.exists 

195 return 

196 

197 if not st_mode & stat.S_IWRITE: 

198 # convert to read/write 

199 try: 

200 os.chmod(path, st_mode | stat.S_IWRITE) 

201 except OSError: 

202 pass 

203 else: 

204 # use the original function to repeat the operation 

205 try: 

206 func(path) 

207 return 

208 except OSError: 

209 pass 

210 

211 if not isinstance(exc_info, BaseException): 

212 _, exc_info, _ = exc_info 

213 onexc(func, path, exc_info) 

214 

215 

216def display_path(path: str) -> str: 

217 """Gives the display value for a given path, making it relative to cwd 

218 if possible.""" 

219 try: 

220 relative = Path(path).relative_to(Path.cwd()) 

221 except ValueError: 

222 # If the path isn't relative to the CWD, leave it alone 

223 return path 

224 return os.path.join(".", relative) 

225 

226 

227def backup_dir(dir: str, ext: str = ".bak") -> str: 

228 """Figure out the name of a directory to back up the given dir to 

229 (adding .bak, .bak2, etc)""" 

230 n = 1 

231 extension = ext 

232 while os.path.exists(dir + extension): 

233 n += 1 

234 extension = ext + str(n) 

235 return dir + extension 

236 

237 

238def ask_path_exists(message: str, options: Iterable[str]) -> str: 

239 for action in os.environ.get("PIP_EXISTS_ACTION", "").split(): 

240 if action in options: 

241 return action 

242 return ask(message, options) 

243 

244 

245def _check_no_input(message: str) -> None: 

246 """Raise an error if no input is allowed.""" 

247 if os.environ.get("PIP_NO_INPUT"): 

248 raise Exception( 

249 f"No input was expected ($PIP_NO_INPUT set); question: {message}" 

250 ) 

251 

252 

253def ask(message: str, options: Iterable[str]) -> str: 

254 """Ask the message interactively, with the given possible responses""" 

255 while 1: 

256 _check_no_input(message) 

257 response = input(message) 

258 response = response.strip().lower() 

259 if response not in options: 

260 print( 

261 "Your response ({!r}) was not one of the expected responses: " 

262 "{}".format(response, ", ".join(options)) 

263 ) 

264 else: 

265 return response 

266 

267 

268def ask_input(message: str) -> str: 

269 """Ask for input interactively.""" 

270 _check_no_input(message) 

271 return input(message) 

272 

273 

274def ask_password(message: str) -> str: 

275 """Ask for a password interactively.""" 

276 _check_no_input(message) 

277 return getpass.getpass(message) 

278 

279 

280def strtobool(val: str) -> int: 

281 """Convert a string representation of truth to true (1) or false (0). 

282 

283 True values are 'y', 'yes', 't', 'true', 'on', and '1'; false values 

284 are 'n', 'no', 'f', 'false', 'off', and '0'. Raises ValueError if 

285 'val' is anything else. 

286 """ 

287 val = val.lower() 

288 if val in ("y", "yes", "t", "true", "on", "1"): 

289 return 1 

290 elif val in ("n", "no", "f", "false", "off", "0"): 

291 return 0 

292 else: 

293 raise ValueError(f"invalid truth value {val!r}") 

294 

295 

296def format_size(bytes: float) -> str: 

297 if bytes > 1000 * 1000: 

298 return f"{bytes / 1000.0 / 1000:.1f} MB" 

299 elif bytes > 10 * 1000: 

300 return f"{int(bytes / 1000)} kB" 

301 elif bytes > 1000: 

302 return f"{bytes / 1000.0:.1f} kB" 

303 else: 

304 return f"{int(bytes)} bytes" 

305 

306 

307def tabulate(rows: Iterable[Iterable[Any]]) -> tuple[list[str], list[int]]: 

308 """Return a list of formatted rows and a list of column sizes. 

309 

310 For example:: 

311 

312 >>> tabulate([['foobar', 2000], [0xdeadbeef]]) 

313 (['foobar 2000', '3735928559'], [10, 4]) 

314 """ 

315 rows = [tuple(map(str, row)) for row in rows] 

316 sizes = [max(map(len, col)) for col in zip_longest(*rows, fillvalue="")] 

317 table = [" ".join(map(str.ljust, row, sizes)).rstrip() for row in rows] 

318 return table, sizes 

319 

320 

321def is_installable_dir(path: str) -> bool: 

322 """Is path is a directory containing pyproject.toml or setup.py? 

323 

324 If pyproject.toml exists, this is a PEP 517 project. Otherwise we look for 

325 a legacy setuptools layout by identifying setup.py. We don't check for the 

326 setup.cfg because using it without setup.py is only available for PEP 517 

327 projects, which are already covered by the pyproject.toml check. 

328 """ 

329 if not os.path.isdir(path): 

330 return False 

331 if os.path.isfile(os.path.join(path, "pyproject.toml")): 

332 return True 

333 if os.path.isfile(os.path.join(path, "setup.py")): 

334 return True 

335 return False 

336 

337 

338def read_chunks( 

339 file: BinaryIO, size: int = FILE_CHUNK_SIZE 

340) -> Generator[bytes, None, None]: 

341 """Yield pieces of data from a file-like object until EOF.""" 

342 while True: 

343 chunk = file.read(size) 

344 if not chunk: 

345 break 

346 yield chunk 

347 

348 

349def normalize_path(path: str, resolve_symlinks: bool = True) -> str: 

350 """ 

351 Convert a path to its canonical, case-normalized, absolute version. 

352 

353 """ 

354 path = os.path.expanduser(path) 

355 if resolve_symlinks: 

356 path = os.path.realpath(path) 

357 else: 

358 path = os.path.abspath(path) 

359 return os.path.normcase(path) 

360 

361 

362def splitext(path: str) -> tuple[str, str]: 

363 """Like os.path.splitext, but take off .tar too""" 

364 base, ext = posixpath.splitext(path) 

365 if base.lower().endswith(".tar"): 

366 ext = base[-4:] + ext 

367 base = base[:-4] 

368 return base, ext 

369 

370 

371def renames(old: str, new: str) -> None: 

372 """Like os.renames(), but handles renaming across devices.""" 

373 # Implementation borrowed from os.renames(). 

374 head, tail = os.path.split(new) 

375 if head and tail and not os.path.exists(head): 

376 os.makedirs(head) 

377 

378 shutil.move(old, new) 

379 

380 head, tail = os.path.split(old) 

381 if head and tail: 

382 try: 

383 os.removedirs(head) 

384 except OSError: 

385 pass 

386 

387 

388def is_local(path: str) -> bool: 

389 """ 

390 Return True if path is within sys.prefix, if we're running in a virtualenv. 

391 

392 If we're not in a virtualenv, all paths are considered "local." 

393 

394 Caution: this function assumes the head of path has been normalized 

395 with normalize_path. 

396 """ 

397 if not running_under_virtualenv(): 

398 return True 

399 return path.startswith(normalize_path(sys.prefix)) 

400 

401 

402def write_output(msg: Any, *args: Any) -> None: 

403 logger.info(msg, *args) 

404 

405 

406class StreamWrapper(StringIO): 

407 orig_stream: TextIO 

408 

409 @classmethod 

410 def from_stream(cls, orig_stream: TextIO) -> StreamWrapper: 

411 ret = cls() 

412 ret.orig_stream = orig_stream 

413 return ret 

414 

415 # compileall.compile_dir() needs stdout.encoding to print to stdout 

416 # type ignore is because TextIOBase.encoding is writeable 

417 @property 

418 def encoding(self) -> str: # type: ignore 

419 return self.orig_stream.encoding 

420 

421 

422# Simulates an enum 

423def enum(*sequential: Any, **named: Any) -> type[Any]: 

424 enums = dict(zip(sequential, range(len(sequential))), **named) 

425 reverse = {value: key for key, value in enums.items()} 

426 enums["reverse_mapping"] = reverse 

427 return type("Enum", (), enums) 

428 

429 

430def build_netloc(host: str, port: int | None) -> str: 

431 """ 

432 Build a netloc from a host-port pair 

433 """ 

434 if port is None: 

435 return host 

436 if ":" in host: 

437 # Only wrap host with square brackets when it is IPv6 

438 host = f"[{host}]" 

439 return f"{host}:{port}" 

440 

441 

442def build_url_from_netloc(netloc: str, scheme: str = "https") -> str: 

443 """ 

444 Build a full URL from a netloc. 

445 """ 

446 if netloc.count(":") >= 2 and "@" not in netloc and "[" not in netloc: 

447 # It must be a bare IPv6 address, so wrap it with brackets. 

448 netloc = f"[{netloc}]" 

449 return f"{scheme}://{netloc}" 

450 

451 

452def parse_netloc(netloc: str) -> tuple[str | None, int | None]: 

453 """ 

454 Return the host-port pair from a netloc. 

455 """ 

456 url = build_url_from_netloc(netloc) 

457 parsed = urllib.parse.urlparse(url) 

458 return parsed.hostname, parsed.port 

459 

460 

461def split_auth_from_netloc(netloc: str) -> NetlocTuple: 

462 """ 

463 Parse out and remove the auth information from a netloc. 

464 

465 Returns: (netloc, (username, password)). 

466 """ 

467 if "@" not in netloc: 

468 return netloc, (None, None) 

469 

470 # Split from the right because that's how urllib.parse.urlsplit() 

471 # behaves if more than one @ is present (which can be checked using 

472 # the password attribute of urlsplit()'s return value). 

473 auth, netloc = netloc.rsplit("@", 1) 

474 pw: str | None = None 

475 if ":" in auth: 

476 # Split from the left because that's how urllib.parse.urlsplit() 

477 # behaves if more than one : is present (which again can be checked 

478 # using the password attribute of the return value) 

479 user, pw = auth.split(":", 1) 

480 else: 

481 user, pw = auth, None 

482 

483 user = urllib.parse.unquote(user) 

484 if pw is not None: 

485 pw = urllib.parse.unquote(pw) 

486 

487 return netloc, (user, pw) 

488 

489 

490def redact_netloc(netloc: str) -> str: 

491 """ 

492 Replace the sensitive data in a netloc with "****", if it exists. 

493 

494 For example: 

495 - "user:pass@example.com" returns "user:****@example.com" 

496 - "accesstoken@example.com" returns "****@example.com" 

497 """ 

498 netloc, (user, password) = split_auth_from_netloc(netloc) 

499 if user is None: 

500 return netloc 

501 if password is None: 

502 user = "****" 

503 password = "" 

504 else: 

505 user = urllib.parse.quote(user) 

506 password = ":****" 

507 return f"{user}{password}@{netloc}" 

508 

509 

510def _transform_url( 

511 url: str, transform_netloc: Callable[[str], tuple[Any, ...]] 

512) -> tuple[str, NetlocTuple]: 

513 """Transform and replace netloc in a url. 

514 

515 transform_netloc is a function taking the netloc and returning a 

516 tuple. The first element of this tuple is the new netloc. The 

517 entire tuple is returned. 

518 

519 Returns a tuple containing the transformed url as item 0 and the 

520 original tuple returned by transform_netloc as item 1. 

521 """ 

522 purl = urllib.parse.urlsplit(url) 

523 netloc_tuple = transform_netloc(purl.netloc) 

524 # stripped url 

525 url_pieces = (purl.scheme, netloc_tuple[0], purl.path, purl.query, purl.fragment) 

526 surl = urllib.parse.urlunsplit(url_pieces) 

527 return surl, cast("NetlocTuple", netloc_tuple) 

528 

529 

530def _get_netloc(netloc: str) -> NetlocTuple: 

531 return split_auth_from_netloc(netloc) 

532 

533 

534def _redact_netloc(netloc: str) -> tuple[str]: 

535 return (redact_netloc(netloc),) 

536 

537 

538def split_auth_netloc_from_url( 

539 url: str, 

540) -> tuple[str, str, tuple[str | None, str | None]]: 

541 """ 

542 Parse a url into separate netloc, auth, and url with no auth. 

543 

544 Returns: (url_without_auth, netloc, (username, password)) 

545 """ 

546 url_without_auth, (netloc, auth) = _transform_url(url, _get_netloc) 

547 return url_without_auth, netloc, auth 

548 

549 

550def remove_auth_from_url(url: str) -> str: 

551 """Return a copy of url with 'username:password@' removed.""" 

552 # username/pass params are passed to subversion through flags 

553 # and are not recognized in the url. 

554 return _transform_url(url, _get_netloc)[0] 

555 

556 

557def redact_auth_from_url(url: str) -> str: 

558 """Replace the password in a given url with ****.""" 

559 return _transform_url(url, _redact_netloc)[0] 

560 

561 

562def redact_auth_from_requirement(req: Requirement) -> str: 

563 """Replace the password in a given requirement url with ****.""" 

564 if not req.url: 

565 return str(req) 

566 return str(req).replace(req.url, redact_auth_from_url(req.url)) 

567 

568 

569@dataclass(frozen=True) 

570class HiddenText: 

571 secret: str 

572 redacted: str 

573 

574 def __repr__(self) -> str: 

575 return f"<HiddenText {str(self)!r}>" 

576 

577 def __str__(self) -> str: 

578 return self.redacted 

579 

580 def __eq__(self, other: object) -> bool: 

581 # Equality is particularly useful for testing. 

582 if type(self) is type(other): 

583 # The string being used for redaction doesn't also have to match, 

584 # just the raw, original string. 

585 return self.secret == other.secret 

586 return NotImplemented 

587 

588 # Disable hashing, since we have a custom __eq__ and don't need hash-ability 

589 # (yet). The only required property of hashing is that objects which compare 

590 # equal have the same hash value. 

591 __hash__ = None # type: ignore[assignment] 

592 

593 

594def hide_value(value: str) -> HiddenText: 

595 return HiddenText(value, redacted="****") 

596 

597 

598def hide_url(url: str) -> HiddenText: 

599 redacted = redact_auth_from_url(url) 

600 return HiddenText(url, redacted=redacted) 

601 

602 

603def protect_pip_from_modification_on_windows(modifying_pip: bool) -> None: 

604 """Protection of pip.exe from modification on Windows 

605 

606 On Windows, any operation modifying pip should be run as: 

607 python -m pip ... 

608 """ 

609 pip_names = [ 

610 "pip", 

611 f"pip{sys.version_info.major}", 

612 f"pip{sys.version_info.major}.{sys.version_info.minor}", 

613 ] 

614 

615 # See https://github.com/pypa/pip/issues/1299 for more discussion 

616 should_show_use_python_msg = ( 

617 modifying_pip and WINDOWS and os.path.basename(sys.argv[0]) in pip_names 

618 ) 

619 

620 if should_show_use_python_msg: 

621 new_command = [sys.executable, "-m", "pip"] + sys.argv[1:] 

622 raise CommandError( 

623 "To modify pip, please run the following command:\n{}".format( 

624 " ".join(new_command) 

625 ) 

626 ) 

627 

628 

629def check_externally_managed() -> None: 

630 """Check whether the current environment is externally managed. 

631 

632 If the ``EXTERNALLY-MANAGED`` config file is found, the current environment 

633 is considered externally managed, and an ExternallyManagedEnvironment is 

634 raised. 

635 """ 

636 if running_under_virtualenv(): 

637 return 

638 marker = os.path.join(sysconfig.get_path("stdlib"), "EXTERNALLY-MANAGED") 

639 if not os.path.isfile(marker): 

640 return 

641 raise ExternallyManagedEnvironment.from_config(marker) 

642 

643 

644def is_console_interactive() -> bool: 

645 """Is this console interactive?""" 

646 return sys.stdin is not None and sys.stdin.isatty() 

647 

648 

649def hash_file(path: str, blocksize: int = 1 << 20) -> tuple[Any, int]: 

650 """Return (hash, length) for path using hashlib.sha256()""" 

651 

652 h = hashlib.sha256() 

653 length = 0 

654 with open(path, "rb") as f: 

655 for block in read_chunks(f, size=blocksize): 

656 length += len(block) 

657 h.update(block) 

658 return h, length 

659 

660 

661def pairwise(iterable: Iterable[Any]) -> Iterator[tuple[Any, Any]]: 

662 """ 

663 Return paired elements. 

664 

665 For example: 

666 s -> (s0, s1), (s2, s3), (s4, s5), ... 

667 """ 

668 iterable = iter(iterable) 

669 return zip_longest(iterable, iterable) 

670 

671 

672def partition( 

673 pred: Callable[[T], bool], iterable: Iterable[T] 

674) -> tuple[Iterable[T], Iterable[T]]: 

675 """ 

676 Use a predicate to partition entries into false entries and true entries, 

677 like 

678 

679 partition(is_odd, range(10)) --> 0 2 4 6 8 and 1 3 5 7 9 

680 """ 

681 t1, t2 = tee(iterable) 

682 return filterfalse(pred, t1), filter(pred, t2) 

683 

684 

685class ConfiguredBuildBackendHookCaller(BuildBackendHookCaller): 

686 def __init__( 

687 self, 

688 config_holder: Any, 

689 source_dir: str, 

690 build_backend: str, 

691 backend_path: str | None = None, 

692 runner: Callable[..., None] | None = None, 

693 python_executable: str | None = None, 

694 ): 

695 super().__init__( 

696 source_dir, build_backend, backend_path, runner, python_executable 

697 ) 

698 self.config_holder = config_holder 

699 

700 def build_wheel( 

701 self, 

702 wheel_directory: str, 

703 config_settings: Mapping[str, Any] | None = None, 

704 metadata_directory: str | None = None, 

705 ) -> str: 

706 cs = self.config_holder.config_settings 

707 return super().build_wheel( 

708 wheel_directory, config_settings=cs, metadata_directory=metadata_directory 

709 ) 

710 

711 def build_sdist( 

712 self, 

713 sdist_directory: str, 

714 config_settings: Mapping[str, Any] | None = None, 

715 ) -> str: 

716 cs = self.config_holder.config_settings 

717 return super().build_sdist(sdist_directory, config_settings=cs) 

718 

719 def build_editable( 

720 self, 

721 wheel_directory: str, 

722 config_settings: Mapping[str, Any] | None = None, 

723 metadata_directory: str | None = None, 

724 ) -> str: 

725 cs = self.config_holder.config_settings 

726 return super().build_editable( 

727 wheel_directory, config_settings=cs, metadata_directory=metadata_directory 

728 ) 

729 

730 def get_requires_for_build_wheel( 

731 self, config_settings: Mapping[str, Any] | None = None 

732 ) -> Sequence[str]: 

733 cs = self.config_holder.config_settings 

734 return super().get_requires_for_build_wheel(config_settings=cs) 

735 

736 def get_requires_for_build_sdist( 

737 self, config_settings: Mapping[str, Any] | None = None 

738 ) -> Sequence[str]: 

739 cs = self.config_holder.config_settings 

740 return super().get_requires_for_build_sdist(config_settings=cs) 

741 

742 def get_requires_for_build_editable( 

743 self, config_settings: Mapping[str, Any] | None = None 

744 ) -> Sequence[str]: 

745 cs = self.config_holder.config_settings 

746 return super().get_requires_for_build_editable(config_settings=cs) 

747 

748 def prepare_metadata_for_build_wheel( 

749 self, 

750 metadata_directory: str, 

751 config_settings: Mapping[str, Any] | None = None, 

752 _allow_fallback: bool = True, 

753 ) -> str: 

754 cs = self.config_holder.config_settings 

755 return super().prepare_metadata_for_build_wheel( 

756 metadata_directory=metadata_directory, 

757 config_settings=cs, 

758 _allow_fallback=_allow_fallback, 

759 ) 

760 

761 def prepare_metadata_for_build_editable( 

762 self, 

763 metadata_directory: str, 

764 config_settings: Mapping[str, Any] | None = None, 

765 _allow_fallback: bool = True, 

766 ) -> str | None: 

767 cs = self.config_holder.config_settings 

768 return super().prepare_metadata_for_build_editable( 

769 metadata_directory=metadata_directory, 

770 config_settings=cs, 

771 _allow_fallback=_allow_fallback, 

772 ) 

773 

774 

775def warn_if_run_as_root() -> None: 

776 """Output a warning for sudo users on Unix. 

777 

778 In a virtual environment, sudo pip still writes to virtualenv. 

779 On Windows, users may run pip as Administrator without issues. 

780 This warning only applies to Unix root users outside of virtualenv. 

781 """ 

782 if running_under_virtualenv(): 

783 return 

784 if not hasattr(os, "getuid"): 

785 return 

786 # On Windows, there are no "system managed" Python packages. Installing as 

787 # Administrator via pip is the correct way of updating system environments. 

788 # 

789 # We choose sys.platform over utils.compat.WINDOWS here to enable Mypy platform 

790 # checks: https://mypy.readthedocs.io/en/stable/common_issues.html 

791 if sys.platform == "win32" or sys.platform == "cygwin": 

792 return 

793 

794 if os.getuid() != 0: 

795 return 

796 

797 logger.warning( 

798 "Running pip as the 'root' user can result in broken permissions and " 

799 "conflicting behaviour with the system package manager, possibly " 

800 "rendering your system unusable. " 

801 "It is recommended to use a virtual environment instead: " 

802 "https://pip.pypa.io/warnings/venv. " 

803 "Use the --root-user-action option if you know what you are doing and " 

804 "want to suppress this warning." 

805 ) 

806 

807 

808def looks_like_ci() -> bool: 

809 """ 

810 Return whether it looks like pip is running under CI. 

811 """ 

812 # We don't use the method of checking for a tty (e.g. using isatty()) 

813 # because some CI systems mimic a tty (e.g. Travis CI). Thus that 

814 # method doesn't provide definitive information in either direction. 

815 return any(name in os.environ for name in CI_ENVIRONMENT_VARIABLES)