__init__.py 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. from __future__ import annotations
  2. import logging
  3. import os
  4. from functools import partial
  5. from typing import TYPE_CHECKING
  6. from virtualenv.app_data import make_app_data
  7. from virtualenv.config.cli.parser import VirtualEnvConfigParser, VirtualEnvOptions
  8. from virtualenv.report import LEVELS, setup_report
  9. from virtualenv.run.session import Session
  10. from virtualenv.seed.wheels.periodic_update import manual_upgrade
  11. from virtualenv.version import __version__
  12. from .plugin.activators import ActivationSelector
  13. from .plugin.creators import CreatorSelector
  14. from .plugin.discovery import get_discover
  15. from .plugin.seeders import SeederSelector
  16. if TYPE_CHECKING:
  17. from collections.abc import MutableMapping
  18. from .plugin.base import ComponentBuilder
  19. def cli_run(
  20. args: list[str],
  21. options: VirtualEnvOptions | None = None,
  22. setup_logging: bool = True, # noqa: FBT002
  23. env: MutableMapping[str, str] | None = None,
  24. ) -> Session:
  25. """Create a virtual environment given some command line interface arguments.
  26. :param args: the command line arguments
  27. :param options: passing in a ``VirtualEnvOptions`` object allows return of the parsed options
  28. :param setup_logging: ``True`` if setup logging handlers, ``False`` to use handlers already registered
  29. :param env: environment variables to use
  30. :returns: the session object of the creation (its structure for now is experimental and might change on short
  31. notice)
  32. """
  33. env = os.environ if env is None else env
  34. of_session = session_via_cli(args, options, setup_logging, env)
  35. with of_session:
  36. of_session.run()
  37. return of_session
  38. def session_via_cli(
  39. args: list[str],
  40. options: VirtualEnvOptions | None = None,
  41. setup_logging: bool = True, # noqa: FBT002
  42. env: MutableMapping[str, str] | None = None,
  43. ) -> Session:
  44. """Create a virtualenv session (same as cli_run, but this does not perform the creation). Use this if you just want to query what the virtual environment would look like, but not actually create it.
  45. :param args: the command line arguments
  46. :param options: passing in a ``VirtualEnvOptions`` object allows return of the parsed options
  47. :param setup_logging: ``True`` if setup logging handlers, ``False`` to use handlers already registered
  48. :param env: environment variables to use
  49. :returns: the session object of the creation (its structure for now is experimental and might change on short
  50. notice)
  51. """
  52. env = os.environ if env is None else env
  53. parser, elements = build_parser(args, options, setup_logging, env)
  54. options = parser.parse_args(args) # ty: ignore[invalid-assignment]
  55. options.py_version = parser._interpreter.version_info # noqa: SLF001 # ty: ignore[invalid-assignment, unresolved-attribute]
  56. creator, seeder, activators = tuple(
  57. e.create(options) # ty: ignore[invalid-argument-type]
  58. for e in elements
  59. ) # create types
  60. return Session(
  61. options.verbosity, # ty: ignore[unresolved-attribute, invalid-argument-type]
  62. options.app_data, # ty: ignore[unresolved-attribute]
  63. parser._interpreter, # noqa: SLF001 # ty: ignore[invalid-argument-type]
  64. creator, # ty: ignore[invalid-argument-type]
  65. seeder, # ty: ignore[invalid-argument-type]
  66. activators, # ty: ignore[invalid-argument-type]
  67. )
  68. def build_parser(
  69. args: list[str] | None = None,
  70. options: VirtualEnvOptions | None = None,
  71. setup_logging: bool = True, # noqa: FBT002
  72. env: MutableMapping[str, str] | None = None,
  73. ) -> tuple[VirtualEnvConfigParser, list[ComponentBuilder]]:
  74. parser = VirtualEnvConfigParser(options, os.environ if env is None else env)
  75. add_version_flag(parser)
  76. parser.add_argument(
  77. "--with-traceback",
  78. dest="with_traceback",
  79. action="store_true",
  80. default=False,
  81. help="on failure also display the stacktrace internals of virtualenv",
  82. )
  83. _do_report_setup(parser, args, setup_logging)
  84. options = load_app_data(args, parser, options)
  85. handle_extra_commands(options)
  86. discover = get_discover(parser, args)
  87. parser._interpreter = interpreter = discover.interpreter # noqa: SLF001
  88. if interpreter is None:
  89. msg = f"failed to find interpreter for {discover}"
  90. raise RuntimeError(msg)
  91. elements = [
  92. CreatorSelector(interpreter, parser),
  93. SeederSelector(interpreter, parser),
  94. ActivationSelector(interpreter, parser),
  95. ]
  96. options, _ = parser.parse_known_args(args)
  97. for element in elements:
  98. element.handle_selected_arg_parse(options)
  99. parser.enable_help()
  100. return parser, elements
  101. def build_parser_only(args: list[str] | None = None) -> VirtualEnvConfigParser:
  102. """Used to provide a parser for the doc generation."""
  103. return build_parser(args)[0]
  104. def handle_extra_commands(options: VirtualEnvOptions) -> None:
  105. if options.upgrade_embed_wheels:
  106. result = manual_upgrade(options.app_data, options.env)
  107. raise SystemExit(result)
  108. def load_app_data(
  109. args: list[str] | None, parser: VirtualEnvConfigParser, options: VirtualEnvOptions | None
  110. ) -> VirtualEnvOptions:
  111. parser.add_argument(
  112. "--read-only-app-data",
  113. action="store_true",
  114. help="use app data folder in read-only mode (write operations will fail with error)",
  115. )
  116. options, _ = parser.parse_known_args(args, namespace=options)
  117. # here we need a write-able application data (e.g. the zipapp might need this for discovery cache)
  118. parser.add_argument(
  119. "--app-data",
  120. help="a data folder used as cache by the virtualenv",
  121. type=partial(make_app_data, read_only=options.read_only_app_data, env=options.env),
  122. default=make_app_data(None, read_only=options.read_only_app_data, env=options.env),
  123. )
  124. parser.add_argument(
  125. "--reset-app-data",
  126. action="store_true",
  127. help="start with empty app data folder",
  128. )
  129. parser.add_argument(
  130. "--upgrade-embed-wheels",
  131. action="store_true",
  132. help="trigger a manual update of the embedded wheels",
  133. )
  134. options, _ = parser.parse_known_args(args, namespace=options)
  135. if options.reset_app_data:
  136. options.app_data.reset()
  137. return options
  138. def add_version_flag(parser: VirtualEnvConfigParser) -> None:
  139. import virtualenv # noqa: PLC0415
  140. parser.add_argument(
  141. "--version",
  142. action="version",
  143. version=f"%(prog)s {__version__} from {virtualenv.__file__}",
  144. help="display the version of the virtualenv package and its location, then exit",
  145. )
  146. def _do_report_setup(parser: VirtualEnvConfigParser, args: list[str] | None, setup_logging: bool) -> None:
  147. level_map = ", ".join(f"{logging.getLevelName(line)}={c}" for c, line in sorted(LEVELS.items()))
  148. msg = "verbosity = verbose - quiet, default {}, mapping => {}"
  149. verbosity_group = parser.add_argument_group(
  150. title="verbosity",
  151. description=msg.format(logging.getLevelName(LEVELS[3]), level_map),
  152. )
  153. verbosity = verbosity_group.add_mutually_exclusive_group()
  154. verbosity.add_argument("-v", "--verbose", action="count", dest="verbose", help="increase verbosity", default=2)
  155. verbosity.add_argument("-q", "--quiet", action="count", dest="quiet", help="decrease verbosity", default=0)
  156. # do not configure logging if only help is requested, as no logging is required for this
  157. if args and any(i in args for i in ("-h", "--help")):
  158. return
  159. option, _ = parser.parse_known_args(args)
  160. if setup_logging:
  161. setup_report(option.verbosity) # ty: ignore[invalid-argument-type]
  162. __all__ = [
  163. "cli_run",
  164. "session_via_cli",
  165. ]