]> jfr.im git - dlqueue.git/blob - venv/lib/python3.11/site-packages/pip/_internal/commands/install.py
init: venv aand flask
[dlqueue.git] / venv / lib / python3.11 / site-packages / pip / _internal / commands / install.py
1 import errno
2 import json
3 import operator
4 import os
5 import shutil
6 import site
7 from optparse import SUPPRESS_HELP, Values
8 from typing import List, Optional
9
10 from pip._vendor.rich import print_json
11
12 from pip._internal.cache import WheelCache
13 from pip._internal.cli import cmdoptions
14 from pip._internal.cli.cmdoptions import make_target_python
15 from pip._internal.cli.req_command import (
16 RequirementCommand,
17 warn_if_run_as_root,
18 with_cleanup,
19 )
20 from pip._internal.cli.status_codes import ERROR, SUCCESS
21 from pip._internal.exceptions import CommandError, InstallationError
22 from pip._internal.locations import get_scheme
23 from pip._internal.metadata import get_environment
24 from pip._internal.models.installation_report import InstallationReport
25 from pip._internal.operations.build.build_tracker import get_build_tracker
26 from pip._internal.operations.check import ConflictDetails, check_install_conflicts
27 from pip._internal.req import install_given_reqs
28 from pip._internal.req.req_install import (
29 InstallRequirement,
30 check_legacy_setup_py_options,
31 )
32 from pip._internal.utils.compat import WINDOWS
33 from pip._internal.utils.filesystem import test_writable_dir
34 from pip._internal.utils.logging import getLogger
35 from pip._internal.utils.misc import (
36 check_externally_managed,
37 ensure_dir,
38 get_pip_version,
39 protect_pip_from_modification_on_windows,
40 write_output,
41 )
42 from pip._internal.utils.temp_dir import TempDirectory
43 from pip._internal.utils.virtualenv import (
44 running_under_virtualenv,
45 virtualenv_no_global,
46 )
47 from pip._internal.wheel_builder import build, should_build_for_install_command
48
49 logger = getLogger(__name__)
50
51
52 class InstallCommand(RequirementCommand):
53 """
54 Install packages from:
55
56 - PyPI (and other indexes) using requirement specifiers.
57 - VCS project urls.
58 - Local project directories.
59 - Local or remote source archives.
60
61 pip also supports installing from "requirements files", which provide
62 an easy way to specify a whole environment to be installed.
63 """
64
65 usage = """
66 %prog [options] <requirement specifier> [package-index-options] ...
67 %prog [options] -r <requirements file> [package-index-options] ...
68 %prog [options] [-e] <vcs project url> ...
69 %prog [options] [-e] <local project path> ...
70 %prog [options] <archive url/path> ..."""
71
72 def add_options(self) -> None:
73 self.cmd_opts.add_option(cmdoptions.requirements())
74 self.cmd_opts.add_option(cmdoptions.constraints())
75 self.cmd_opts.add_option(cmdoptions.no_deps())
76 self.cmd_opts.add_option(cmdoptions.pre())
77
78 self.cmd_opts.add_option(cmdoptions.editable())
79 self.cmd_opts.add_option(
80 "--dry-run",
81 action="store_true",
82 dest="dry_run",
83 default=False,
84 help=(
85 "Don't actually install anything, just print what would be. "
86 "Can be used in combination with --ignore-installed "
87 "to 'resolve' the requirements."
88 ),
89 )
90 self.cmd_opts.add_option(
91 "-t",
92 "--target",
93 dest="target_dir",
94 metavar="dir",
95 default=None,
96 help=(
97 "Install packages into <dir>. "
98 "By default this will not replace existing files/folders in "
99 "<dir>. Use --upgrade to replace existing packages in <dir> "
100 "with new versions."
101 ),
102 )
103 cmdoptions.add_target_python_options(self.cmd_opts)
104
105 self.cmd_opts.add_option(
106 "--user",
107 dest="use_user_site",
108 action="store_true",
109 help=(
110 "Install to the Python user install directory for your "
111 "platform. Typically ~/.local/, or %APPDATA%\\Python on "
112 "Windows. (See the Python documentation for site.USER_BASE "
113 "for full details.)"
114 ),
115 )
116 self.cmd_opts.add_option(
117 "--no-user",
118 dest="use_user_site",
119 action="store_false",
120 help=SUPPRESS_HELP,
121 )
122 self.cmd_opts.add_option(
123 "--root",
124 dest="root_path",
125 metavar="dir",
126 default=None,
127 help="Install everything relative to this alternate root directory.",
128 )
129 self.cmd_opts.add_option(
130 "--prefix",
131 dest="prefix_path",
132 metavar="dir",
133 default=None,
134 help=(
135 "Installation prefix where lib, bin and other top-level "
136 "folders are placed. Note that the resulting installation may "
137 "contain scripts and other resources which reference the "
138 "Python interpreter of pip, and not that of ``--prefix``. "
139 "See also the ``--python`` option if the intention is to "
140 "install packages into another (possibly pip-free) "
141 "environment."
142 ),
143 )
144
145 self.cmd_opts.add_option(cmdoptions.src())
146
147 self.cmd_opts.add_option(
148 "-U",
149 "--upgrade",
150 dest="upgrade",
151 action="store_true",
152 help=(
153 "Upgrade all specified packages to the newest available "
154 "version. The handling of dependencies depends on the "
155 "upgrade-strategy used."
156 ),
157 )
158
159 self.cmd_opts.add_option(
160 "--upgrade-strategy",
161 dest="upgrade_strategy",
162 default="only-if-needed",
163 choices=["only-if-needed", "eager"],
164 help=(
165 "Determines how dependency upgrading should be handled "
166 "[default: %default]. "
167 '"eager" - dependencies are upgraded regardless of '
168 "whether the currently installed version satisfies the "
169 "requirements of the upgraded package(s). "
170 '"only-if-needed" - are upgraded only when they do not '
171 "satisfy the requirements of the upgraded package(s)."
172 ),
173 )
174
175 self.cmd_opts.add_option(
176 "--force-reinstall",
177 dest="force_reinstall",
178 action="store_true",
179 help="Reinstall all packages even if they are already up-to-date.",
180 )
181
182 self.cmd_opts.add_option(
183 "-I",
184 "--ignore-installed",
185 dest="ignore_installed",
186 action="store_true",
187 help=(
188 "Ignore the installed packages, overwriting them. "
189 "This can break your system if the existing package "
190 "is of a different version or was installed "
191 "with a different package manager!"
192 ),
193 )
194
195 self.cmd_opts.add_option(cmdoptions.ignore_requires_python())
196 self.cmd_opts.add_option(cmdoptions.no_build_isolation())
197 self.cmd_opts.add_option(cmdoptions.use_pep517())
198 self.cmd_opts.add_option(cmdoptions.no_use_pep517())
199 self.cmd_opts.add_option(cmdoptions.check_build_deps())
200 self.cmd_opts.add_option(cmdoptions.override_externally_managed())
201
202 self.cmd_opts.add_option(cmdoptions.config_settings())
203 self.cmd_opts.add_option(cmdoptions.global_options())
204
205 self.cmd_opts.add_option(
206 "--compile",
207 action="store_true",
208 dest="compile",
209 default=True,
210 help="Compile Python source files to bytecode",
211 )
212
213 self.cmd_opts.add_option(
214 "--no-compile",
215 action="store_false",
216 dest="compile",
217 help="Do not compile Python source files to bytecode",
218 )
219
220 self.cmd_opts.add_option(
221 "--no-warn-script-location",
222 action="store_false",
223 dest="warn_script_location",
224 default=True,
225 help="Do not warn when installing scripts outside PATH",
226 )
227 self.cmd_opts.add_option(
228 "--no-warn-conflicts",
229 action="store_false",
230 dest="warn_about_conflicts",
231 default=True,
232 help="Do not warn about broken dependencies",
233 )
234 self.cmd_opts.add_option(cmdoptions.no_binary())
235 self.cmd_opts.add_option(cmdoptions.only_binary())
236 self.cmd_opts.add_option(cmdoptions.prefer_binary())
237 self.cmd_opts.add_option(cmdoptions.require_hashes())
238 self.cmd_opts.add_option(cmdoptions.progress_bar())
239 self.cmd_opts.add_option(cmdoptions.root_user_action())
240
241 index_opts = cmdoptions.make_option_group(
242 cmdoptions.index_group,
243 self.parser,
244 )
245
246 self.parser.insert_option_group(0, index_opts)
247 self.parser.insert_option_group(0, self.cmd_opts)
248
249 self.cmd_opts.add_option(
250 "--report",
251 dest="json_report_file",
252 metavar="file",
253 default=None,
254 help=(
255 "Generate a JSON file describing what pip did to install "
256 "the provided requirements. "
257 "Can be used in combination with --dry-run and --ignore-installed "
258 "to 'resolve' the requirements. "
259 "When - is used as file name it writes to stdout. "
260 "When writing to stdout, please combine with the --quiet option "
261 "to avoid mixing pip logging output with JSON output."
262 ),
263 )
264
265 @with_cleanup
266 def run(self, options: Values, args: List[str]) -> int:
267 if options.use_user_site and options.target_dir is not None:
268 raise CommandError("Can not combine '--user' and '--target'")
269
270 # Check whether the environment we're installing into is externally
271 # managed, as specified in PEP 668. Specifying --root, --target, or
272 # --prefix disables the check, since there's no reliable way to locate
273 # the EXTERNALLY-MANAGED file for those cases. An exception is also
274 # made specifically for "--dry-run --report" for convenience.
275 installing_into_current_environment = (
276 not (options.dry_run and options.json_report_file)
277 and options.root_path is None
278 and options.target_dir is None
279 and options.prefix_path is None
280 )
281 if (
282 installing_into_current_environment
283 and not options.override_externally_managed
284 ):
285 check_externally_managed()
286
287 upgrade_strategy = "to-satisfy-only"
288 if options.upgrade:
289 upgrade_strategy = options.upgrade_strategy
290
291 cmdoptions.check_dist_restriction(options, check_target=True)
292
293 logger.verbose("Using %s", get_pip_version())
294 options.use_user_site = decide_user_install(
295 options.use_user_site,
296 prefix_path=options.prefix_path,
297 target_dir=options.target_dir,
298 root_path=options.root_path,
299 isolated_mode=options.isolated_mode,
300 )
301
302 target_temp_dir: Optional[TempDirectory] = None
303 target_temp_dir_path: Optional[str] = None
304 if options.target_dir:
305 options.ignore_installed = True
306 options.target_dir = os.path.abspath(options.target_dir)
307 if (
308 # fmt: off
309 os.path.exists(options.target_dir) and
310 not os.path.isdir(options.target_dir)
311 # fmt: on
312 ):
313 raise CommandError(
314 "Target path exists but is not a directory, will not continue."
315 )
316
317 # Create a target directory for using with the target option
318 target_temp_dir = TempDirectory(kind="target")
319 target_temp_dir_path = target_temp_dir.path
320 self.enter_context(target_temp_dir)
321
322 global_options = options.global_options or []
323
324 session = self.get_default_session(options)
325
326 target_python = make_target_python(options)
327 finder = self._build_package_finder(
328 options=options,
329 session=session,
330 target_python=target_python,
331 ignore_requires_python=options.ignore_requires_python,
332 )
333 build_tracker = self.enter_context(get_build_tracker())
334
335 directory = TempDirectory(
336 delete=not options.no_clean,
337 kind="install",
338 globally_managed=True,
339 )
340
341 try:
342 reqs = self.get_requirements(args, options, finder, session)
343 check_legacy_setup_py_options(options, reqs)
344
345 wheel_cache = WheelCache(options.cache_dir)
346
347 # Only when installing is it permitted to use PEP 660.
348 # In other circumstances (pip wheel, pip download) we generate
349 # regular (i.e. non editable) metadata and wheels.
350 for req in reqs:
351 req.permit_editable_wheels = True
352
353 preparer = self.make_requirement_preparer(
354 temp_build_dir=directory,
355 options=options,
356 build_tracker=build_tracker,
357 session=session,
358 finder=finder,
359 use_user_site=options.use_user_site,
360 verbosity=self.verbosity,
361 )
362 resolver = self.make_resolver(
363 preparer=preparer,
364 finder=finder,
365 options=options,
366 wheel_cache=wheel_cache,
367 use_user_site=options.use_user_site,
368 ignore_installed=options.ignore_installed,
369 ignore_requires_python=options.ignore_requires_python,
370 force_reinstall=options.force_reinstall,
371 upgrade_strategy=upgrade_strategy,
372 use_pep517=options.use_pep517,
373 )
374
375 self.trace_basic_info(finder)
376
377 requirement_set = resolver.resolve(
378 reqs, check_supported_wheels=not options.target_dir
379 )
380
381 if options.json_report_file:
382 report = InstallationReport(requirement_set.requirements_to_install)
383 if options.json_report_file == "-":
384 print_json(data=report.to_dict())
385 else:
386 with open(options.json_report_file, "w", encoding="utf-8") as f:
387 json.dump(report.to_dict(), f, indent=2, ensure_ascii=False)
388
389 if options.dry_run:
390 # In non dry-run mode, the legacy versions and specifiers check
391 # will be done as part of conflict detection.
392 requirement_set.warn_legacy_versions_and_specifiers()
393 would_install_items = sorted(
394 (r.metadata["name"], r.metadata["version"])
395 for r in requirement_set.requirements_to_install
396 )
397 if would_install_items:
398 write_output(
399 "Would install %s",
400 " ".join("-".join(item) for item in would_install_items),
401 )
402 return SUCCESS
403
404 try:
405 pip_req = requirement_set.get_requirement("pip")
406 except KeyError:
407 modifying_pip = False
408 else:
409 # If we're not replacing an already installed pip,
410 # we're not modifying it.
411 modifying_pip = pip_req.satisfied_by is None
412 protect_pip_from_modification_on_windows(modifying_pip=modifying_pip)
413
414 reqs_to_build = [
415 r
416 for r in requirement_set.requirements.values()
417 if should_build_for_install_command(r)
418 ]
419
420 _, build_failures = build(
421 reqs_to_build,
422 wheel_cache=wheel_cache,
423 verify=True,
424 build_options=[],
425 global_options=global_options,
426 )
427
428 if build_failures:
429 raise InstallationError(
430 "Could not build wheels for {}, which is required to "
431 "install pyproject.toml-based projects".format(
432 ", ".join(r.name for r in build_failures) # type: ignore
433 )
434 )
435
436 to_install = resolver.get_installation_order(requirement_set)
437
438 # Check for conflicts in the package set we're installing.
439 conflicts: Optional[ConflictDetails] = None
440 should_warn_about_conflicts = (
441 not options.ignore_dependencies and options.warn_about_conflicts
442 )
443 if should_warn_about_conflicts:
444 conflicts = self._determine_conflicts(to_install)
445
446 # Don't warn about script install locations if
447 # --target or --prefix has been specified
448 warn_script_location = options.warn_script_location
449 if options.target_dir or options.prefix_path:
450 warn_script_location = False
451
452 installed = install_given_reqs(
453 to_install,
454 global_options,
455 root=options.root_path,
456 home=target_temp_dir_path,
457 prefix=options.prefix_path,
458 warn_script_location=warn_script_location,
459 use_user_site=options.use_user_site,
460 pycompile=options.compile,
461 )
462
463 lib_locations = get_lib_location_guesses(
464 user=options.use_user_site,
465 home=target_temp_dir_path,
466 root=options.root_path,
467 prefix=options.prefix_path,
468 isolated=options.isolated_mode,
469 )
470 env = get_environment(lib_locations)
471
472 installed.sort(key=operator.attrgetter("name"))
473 items = []
474 for result in installed:
475 item = result.name
476 try:
477 installed_dist = env.get_distribution(item)
478 if installed_dist is not None:
479 item = f"{item}-{installed_dist.version}"
480 except Exception:
481 pass
482 items.append(item)
483
484 if conflicts is not None:
485 self._warn_about_conflicts(
486 conflicts,
487 resolver_variant=self.determine_resolver_variant(options),
488 )
489
490 installed_desc = " ".join(items)
491 if installed_desc:
492 write_output(
493 "Successfully installed %s",
494 installed_desc,
495 )
496 except OSError as error:
497 show_traceback = self.verbosity >= 1
498
499 message = create_os_error_message(
500 error,
501 show_traceback,
502 options.use_user_site,
503 )
504 logger.error(message, exc_info=show_traceback) # noqa
505
506 return ERROR
507
508 if options.target_dir:
509 assert target_temp_dir
510 self._handle_target_dir(
511 options.target_dir, target_temp_dir, options.upgrade
512 )
513 if options.root_user_action == "warn":
514 warn_if_run_as_root()
515 return SUCCESS
516
517 def _handle_target_dir(
518 self, target_dir: str, target_temp_dir: TempDirectory, upgrade: bool
519 ) -> None:
520 ensure_dir(target_dir)
521
522 # Checking both purelib and platlib directories for installed
523 # packages to be moved to target directory
524 lib_dir_list = []
525
526 # Checking both purelib and platlib directories for installed
527 # packages to be moved to target directory
528 scheme = get_scheme("", home=target_temp_dir.path)
529 purelib_dir = scheme.purelib
530 platlib_dir = scheme.platlib
531 data_dir = scheme.data
532
533 if os.path.exists(purelib_dir):
534 lib_dir_list.append(purelib_dir)
535 if os.path.exists(platlib_dir) and platlib_dir != purelib_dir:
536 lib_dir_list.append(platlib_dir)
537 if os.path.exists(data_dir):
538 lib_dir_list.append(data_dir)
539
540 for lib_dir in lib_dir_list:
541 for item in os.listdir(lib_dir):
542 if lib_dir == data_dir:
543 ddir = os.path.join(data_dir, item)
544 if any(s.startswith(ddir) for s in lib_dir_list[:-1]):
545 continue
546 target_item_dir = os.path.join(target_dir, item)
547 if os.path.exists(target_item_dir):
548 if not upgrade:
549 logger.warning(
550 "Target directory %s already exists. Specify "
551 "--upgrade to force replacement.",
552 target_item_dir,
553 )
554 continue
555 if os.path.islink(target_item_dir):
556 logger.warning(
557 "Target directory %s already exists and is "
558 "a link. pip will not automatically replace "
559 "links, please remove if replacement is "
560 "desired.",
561 target_item_dir,
562 )
563 continue
564 if os.path.isdir(target_item_dir):
565 shutil.rmtree(target_item_dir)
566 else:
567 os.remove(target_item_dir)
568
569 shutil.move(os.path.join(lib_dir, item), target_item_dir)
570
571 def _determine_conflicts(
572 self, to_install: List[InstallRequirement]
573 ) -> Optional[ConflictDetails]:
574 try:
575 return check_install_conflicts(to_install)
576 except Exception:
577 logger.exception(
578 "Error while checking for conflicts. Please file an issue on "
579 "pip's issue tracker: https://github.com/pypa/pip/issues/new"
580 )
581 return None
582
583 def _warn_about_conflicts(
584 self, conflict_details: ConflictDetails, resolver_variant: str
585 ) -> None:
586 package_set, (missing, conflicting) = conflict_details
587 if not missing and not conflicting:
588 return
589
590 parts: List[str] = []
591 if resolver_variant == "legacy":
592 parts.append(
593 "pip's legacy dependency resolver does not consider dependency "
594 "conflicts when selecting packages. This behaviour is the "
595 "source of the following dependency conflicts."
596 )
597 else:
598 assert resolver_variant == "2020-resolver"
599 parts.append(
600 "pip's dependency resolver does not currently take into account "
601 "all the packages that are installed. This behaviour is the "
602 "source of the following dependency conflicts."
603 )
604
605 # NOTE: There is some duplication here, with commands/check.py
606 for project_name in missing:
607 version = package_set[project_name][0]
608 for dependency in missing[project_name]:
609 message = (
610 "{name} {version} requires {requirement}, "
611 "which is not installed."
612 ).format(
613 name=project_name,
614 version=version,
615 requirement=dependency[1],
616 )
617 parts.append(message)
618
619 for project_name in conflicting:
620 version = package_set[project_name][0]
621 for dep_name, dep_version, req in conflicting[project_name]:
622 message = (
623 "{name} {version} requires {requirement}, but {you} have "
624 "{dep_name} {dep_version} which is incompatible."
625 ).format(
626 name=project_name,
627 version=version,
628 requirement=req,
629 dep_name=dep_name,
630 dep_version=dep_version,
631 you=("you" if resolver_variant == "2020-resolver" else "you'll"),
632 )
633 parts.append(message)
634
635 logger.critical("\n".join(parts))
636
637
638 def get_lib_location_guesses(
639 user: bool = False,
640 home: Optional[str] = None,
641 root: Optional[str] = None,
642 isolated: bool = False,
643 prefix: Optional[str] = None,
644 ) -> List[str]:
645 scheme = get_scheme(
646 "",
647 user=user,
648 home=home,
649 root=root,
650 isolated=isolated,
651 prefix=prefix,
652 )
653 return [scheme.purelib, scheme.platlib]
654
655
656 def site_packages_writable(root: Optional[str], isolated: bool) -> bool:
657 return all(
658 test_writable_dir(d)
659 for d in set(get_lib_location_guesses(root=root, isolated=isolated))
660 )
661
662
663 def decide_user_install(
664 use_user_site: Optional[bool],
665 prefix_path: Optional[str] = None,
666 target_dir: Optional[str] = None,
667 root_path: Optional[str] = None,
668 isolated_mode: bool = False,
669 ) -> bool:
670 """Determine whether to do a user install based on the input options.
671
672 If use_user_site is False, no additional checks are done.
673 If use_user_site is True, it is checked for compatibility with other
674 options.
675 If use_user_site is None, the default behaviour depends on the environment,
676 which is provided by the other arguments.
677 """
678 # In some cases (config from tox), use_user_site can be set to an integer
679 # rather than a bool, which 'use_user_site is False' wouldn't catch.
680 if (use_user_site is not None) and (not use_user_site):
681 logger.debug("Non-user install by explicit request")
682 return False
683
684 if use_user_site:
685 if prefix_path:
686 raise CommandError(
687 "Can not combine '--user' and '--prefix' as they imply "
688 "different installation locations"
689 )
690 if virtualenv_no_global():
691 raise InstallationError(
692 "Can not perform a '--user' install. User site-packages "
693 "are not visible in this virtualenv."
694 )
695 logger.debug("User install by explicit request")
696 return True
697
698 # If we are here, user installs have not been explicitly requested/avoided
699 assert use_user_site is None
700
701 # user install incompatible with --prefix/--target
702 if prefix_path or target_dir:
703 logger.debug("Non-user install due to --prefix or --target option")
704 return False
705
706 # If user installs are not enabled, choose a non-user install
707 if not site.ENABLE_USER_SITE:
708 logger.debug("Non-user install because user site-packages disabled")
709 return False
710
711 # If we have permission for a non-user install, do that,
712 # otherwise do a user install.
713 if site_packages_writable(root=root_path, isolated=isolated_mode):
714 logger.debug("Non-user install because site-packages writeable")
715 return False
716
717 logger.info(
718 "Defaulting to user installation because normal site-packages "
719 "is not writeable"
720 )
721 return True
722
723
724 def create_os_error_message(
725 error: OSError, show_traceback: bool, using_user_site: bool
726 ) -> str:
727 """Format an error message for an OSError
728
729 It may occur anytime during the execution of the install command.
730 """
731 parts = []
732
733 # Mention the error if we are not going to show a traceback
734 parts.append("Could not install packages due to an OSError")
735 if not show_traceback:
736 parts.append(": ")
737 parts.append(str(error))
738 else:
739 parts.append(".")
740
741 # Spilt the error indication from a helper message (if any)
742 parts[-1] += "\n"
743
744 # Suggest useful actions to the user:
745 # (1) using user site-packages or (2) verifying the permissions
746 if error.errno == errno.EACCES:
747 user_option_part = "Consider using the `--user` option"
748 permissions_part = "Check the permissions"
749
750 if not running_under_virtualenv() and not using_user_site:
751 parts.extend(
752 [
753 user_option_part,
754 " or ",
755 permissions_part.lower(),
756 ]
757 )
758 else:
759 parts.append(permissions_part)
760 parts.append(".\n")
761
762 # Suggest the user to enable Long Paths if path length is
763 # more than 260
764 if (
765 WINDOWS
766 and error.errno == errno.ENOENT
767 and error.filename
768 and len(error.filename) > 260
769 ):
770 parts.append(
771 "HINT: This error might have occurred since "
772 "this system does not have Windows Long Path "
773 "support enabled. You can find information on "
774 "how to enable this at "
775 "https://pip.pypa.io/warnings/enable-long-paths\n"
776 )
777
778 return "".join(parts).strip() + "\n"