diff options
| author | Bernat Gabor <bgabor8@bloomberg.net> | 2020-01-10 09:18:59 +0000 |
|---|---|---|
| committer | Bernat Gabor <bgabor8@bloomberg.net> | 2020-01-10 15:38:39 +0000 |
| commit | 5f3580ee96ad948e5f4c66fc8358d0b81903da57 (patch) | |
| tree | 55312be9e4d62ec60464d5ae15e3606d6eb1fc34 | |
| parent | 7d964e3ce7bf13326a6b15497d8294fd8830e4a5 (diff) | |
| download | virtualenv-5f3580ee96ad948e5f4c66fc8358d0b81903da57.tar.gz | |
reorganize run.py - prefer inheritence based API over generators
Signed-off-by: Bernat Gabor <bgabor8@bloomberg.net>
31 files changed, 328 insertions, 292 deletions
@@ -76,6 +76,7 @@ console_scripts = virtualenv.discovery = builtin = virtualenv.interpreters.discovery.builtin:Builtin virtualenv.create = + venv = virtualenv.interpreters.create.venv:Venv cpython3-posix = virtualenv.interpreters.create.cpython.cpython3:CPython3Posix cpython3-win = virtualenv.interpreters.create.cpython.cpython3:CPython3Windows cpython2-posix = virtualenv.interpreters.create.cpython.cpython2:CPython2Posix @@ -84,7 +85,6 @@ virtualenv.create = pypy2-win = virtualenv.interpreters.create.pypy.pypy2:Pypy2Windows pypy3-posix = virtualenv.interpreters.create.pypy.pypy3:PyPy3Posix pypy3-win = virtualenv.interpreters.create.pypy.pypy3:Pypy3Windows - venv = virtualenv.interpreters.create.venv:Venv virtualenv.seed = none = virtualenv.seed.none:NoneSeeder pip = virtualenv.seed.embed.pip_invoke:PipInvoke diff --git a/src/virtualenv/activation/activator.py b/src/virtualenv/activation/activator.py index d5cd10c..7e69aa7 100644 --- a/src/virtualenv/activation/activator.py +++ b/src/virtualenv/activation/activator.py @@ -11,7 +11,7 @@ class Activator(object): self.flag_prompt = options.prompt @classmethod - def add_parser_arguments(cls, parser): + def add_parser_arguments(cls, parser, interpreter): """add activator options""" @classmethod diff --git a/src/virtualenv/interpreters/create/self_do.py b/src/virtualenv/interpreters/create/builtin_way.py index bf90319..4134d7b 100644 --- a/src/virtualenv/interpreters/create/self_do.py +++ b/src/virtualenv/interpreters/create/builtin_way.py @@ -9,7 +9,7 @@ from virtualenv.interpreters.create.via_global_ref.api import ViaGlobalRefApi @add_metaclass(ABCMeta) -class SelfDo(ViaGlobalRefApi): +class VirtualenvBuiltin(ViaGlobalRefApi): """A creator that does operations itself without delegation""" @property diff --git a/src/virtualenv/interpreters/create/cpython/common.py b/src/virtualenv/interpreters/create/cpython/common.py index be6b34c..490b0ad 100644 --- a/src/virtualenv/interpreters/create/cpython/common.py +++ b/src/virtualenv/interpreters/create/cpython/common.py @@ -5,12 +5,12 @@ import abc import six from virtualenv.interpreters.create.support import PosixSupports, WindowsSupports -from virtualenv.interpreters.create.via_global_ref.via_global_self_do import ViaGlobalRefSelfDo +from virtualenv.interpreters.create.via_global_ref.via_global_self_do import ViaGlobalRefVirtualenvBuiltin from virtualenv.util.path import Path @six.add_metaclass(abc.ABCMeta) -class CPython(ViaGlobalRefSelfDo): +class CPython(ViaGlobalRefVirtualenvBuiltin): @classmethod def supports(cls, interpreter): return interpreter.implementation == "CPython" and super(CPython, cls).supports(interpreter) diff --git a/src/virtualenv/interpreters/create/creator.py b/src/virtualenv/interpreters/create/creator.py index 783afce..09a4404 100644 --- a/src/virtualenv/interpreters/create/creator.py +++ b/src/virtualenv/interpreters/create/creator.py @@ -49,13 +49,15 @@ class Creator(object): @classmethod def add_parser_arguments(cls, parser, interpreter): parser.add_argument( + "dest_dir", help="directory to create virtualenv at", type=cls.validate_dest_dir, default="env", nargs="?", + ) + parser.add_argument( "--clear", dest="clear", action="store_true", help="clear out the non-root install and start from scratch", default=False, ) - parser.add_argument( "--system-site-packages", default=False, @@ -64,10 +66,6 @@ class Creator(object): help="Give the virtual environment access to the system site-packages dir.", ) - parser.add_argument( - "dest_dir", help="directory to create virtualenv at", type=cls.validate_dest_dir, default="env", nargs="?", - ) - @classmethod def validate_dest_dir(cls, raw_value): """No path separator in the path, valid chars and must be write-able""" diff --git a/src/virtualenv/interpreters/create/pypy/common.py b/src/virtualenv/interpreters/create/pypy/common.py index bc2d393..1c98064 100644 --- a/src/virtualenv/interpreters/create/pypy/common.py +++ b/src/virtualenv/interpreters/create/pypy/common.py @@ -4,12 +4,12 @@ import abc import six -from virtualenv.interpreters.create.via_global_ref.via_global_self_do import ViaGlobalRefSelfDo +from virtualenv.interpreters.create.via_global_ref.via_global_self_do import ViaGlobalRefVirtualenvBuiltin from virtualenv.util.path import Path @six.add_metaclass(abc.ABCMeta) -class PyPy(ViaGlobalRefSelfDo): +class PyPy(ViaGlobalRefVirtualenvBuiltin): @classmethod def supports(cls, interpreter): return interpreter.implementation == "PyPy" and super(PyPy, cls).supports(interpreter) diff --git a/src/virtualenv/interpreters/create/venv.py b/src/virtualenv/interpreters/create/venv.py index 4be2664..d378b6a 100644 --- a/src/virtualenv/interpreters/create/venv.py +++ b/src/virtualenv/interpreters/create/venv.py @@ -16,10 +16,12 @@ class Venv(ViaGlobalRefApi): super(Venv, self).__init__(options, interpreter) self.can_be_inline = interpreter is CURRENT and interpreter.executable == interpreter.system_executable self._context = None - self.self_do = options.self_do + self.builtin_way = options.builtin_way def _args(self): - return super(Venv, self)._args() + ([("self_do", self.self_do.__class__.__name__)] if self.self_do else []) + return super(Venv, self)._args() + ( + [("builtin_way", self.builtin_way.__class__.__name__)] if self.builtin_way else [] + ) @classmethod def supports(cls, interpreter): @@ -31,8 +33,8 @@ class Venv(ViaGlobalRefApi): else: self.create_via_sub_process() # TODO: cleanup activation scripts - if self.self_do is not None: - for site_package in self.self_do.site_packages: + if self.builtin_way is not None: + for site_package in self.builtin_way.site_packages: ensure_dir(site_package) def create_inline(self): @@ -68,27 +70,27 @@ class Venv(ViaGlobalRefApi): super(Venv, self).set_pyenv_cfg() self.pyenv_cfg.update(venv_content) - def _delegate_to_self_do(self, key): - if self.self_do is None: + def _delegate_to_builtin_way(self, key): + if self.builtin_way is None: return None - return getattr(self.self_do, key) + return getattr(self.builtin_way, key) @property def exe(self): - return self._delegate_to_self_do("exe") + return self._delegate_to_builtin_way("exe") @property def site_packages(self): - return self._delegate_to_self_do("site_packages") + return self._delegate_to_builtin_way("site_packages") @property def bin_dir(self): - return self._delegate_to_self_do("bin_dir") + return self._delegate_to_builtin_way("bin_dir") @property def bin_name(self): - return self._delegate_to_self_do("bin_name") + return self._delegate_to_builtin_way("bin_name") @property def lib_dir(self): - return self._delegate_to_self_do("lib_dir") + return self._delegate_to_builtin_way("lib_dir") diff --git a/src/virtualenv/interpreters/create/via_global_ref/python2.py b/src/virtualenv/interpreters/create/via_global_ref/python2.py index d49acdb..cf195a9 100644 --- a/src/virtualenv/interpreters/create/via_global_ref/python2.py +++ b/src/virtualenv/interpreters/create/via_global_ref/python2.py @@ -7,14 +7,14 @@ import os import six from virtualenv.interpreters.create.support import Python2Supports -from virtualenv.interpreters.create.via_global_ref.via_global_self_do import ViaGlobalRefSelfDo +from virtualenv.interpreters.create.via_global_ref.via_global_self_do import ViaGlobalRefVirtualenvBuiltin from virtualenv.util.path import Path, copy HERE = Path(__file__).absolute().parent @six.add_metaclass(abc.ABCMeta) -class Python2(ViaGlobalRefSelfDo, Python2Supports): +class Python2(ViaGlobalRefVirtualenvBuiltin, Python2Supports): def setup_python(self): super(Python2, self).setup_python() # install the core first self.fixup_python2() # now patch diff --git a/src/virtualenv/interpreters/create/via_global_ref/via_global_self_do.py b/src/virtualenv/interpreters/create/via_global_ref/via_global_self_do.py index eb1bd2d..f0202a9 100644 --- a/src/virtualenv/interpreters/create/via_global_ref/via_global_self_do.py +++ b/src/virtualenv/interpreters/create/via_global_ref/via_global_self_do.py @@ -10,14 +10,14 @@ import six from six import add_metaclass from virtualenv.info import is_fs_case_sensitive -from virtualenv.interpreters.create.self_do import SelfDo +from virtualenv.interpreters.create.builtin_way import VirtualenvBuiltin from virtualenv.util.path import Path, copy, ensure_dir, symlink @add_metaclass(ABCMeta) -class ViaGlobalRefSelfDo(SelfDo): +class ViaGlobalRefVirtualenvBuiltin(VirtualenvBuiltin): def __init__(self, options, interpreter): - super(ViaGlobalRefSelfDo, self).__init__(options, interpreter) + super(ViaGlobalRefVirtualenvBuiltin, self).__init__(options, interpreter) self.copier = symlink if self.symlinks is True else copy def create(self): @@ -38,7 +38,7 @@ class ViaGlobalRefSelfDo(SelfDo): We directly inject the base prefix and base exec prefix to avoid site.py needing to discover these from home (which usually is done within the interpreter itself) """ - super(ViaGlobalRefSelfDo, self).set_pyenv_cfg() + super(ViaGlobalRefVirtualenvBuiltin, self).set_pyenv_cfg() self.pyenv_cfg["base-prefix"] = self.interpreter.system_prefix self.pyenv_cfg["base-exec-prefix"] = self.interpreter.system_exec_prefix diff --git a/src/virtualenv/run.py b/src/virtualenv/run.py deleted file mode 100644 index ea5bf51..0000000 --- a/src/virtualenv/run.py +++ /dev/null @@ -1,236 +0,0 @@ -from __future__ import absolute_import, unicode_literals - -import logging -import sys -from argparse import ArgumentTypeError -from collections import OrderedDict - -from .config.cli.parser import VirtualEnvConfigParser -from .report import LEVELS, setup_report -from .session import Session -from .version import __version__ - -if sys.version_info >= (3, 8): - from importlib.metadata import entry_points -else: - from importlib_metadata import entry_points - - -def run_via_cli(args): - """Run the virtual environment creation via CLI arguments - - :param args: the command line arguments - :return: the creator used - """ - session = session_via_cli(args) - session.run() - return session - - -def session_via_cli(args): - parser = VirtualEnvConfigParser() - add_version_flag(parser) - options, verbosity = _do_report_setup(parser, args) - discover = _get_discover(parser, args, options) - interpreter = discover.interpreter - if interpreter is None: - raise RuntimeError("failed to find interpreter for {}".format(discover)) - elements = [ - _get_creator(interpreter, parser, options), - _get_seeder(parser, options), - _get_activation(interpreter, parser, options), - ] - [next(elem) for elem in elements] # add choice of types - parser.parse_known_args(args, namespace=options) - [next(elem) for elem in elements] # add type flags - parser.enable_help() - parser.parse_args(args, namespace=options) - creator, seeder, activators = tuple(next(e) for e in elements) # create types - session = Session(verbosity, interpreter, creator, seeder, activators) - return session - - -def add_version_flag(parser): - import virtualenv - - parser.add_argument( - "--version", action="version", version="%(prog)s {} from {}".format(__version__, virtualenv.__file__) - ) - - -def _do_report_setup(parser, args): - level_map = ", ".join("{}:{}".format(c, logging.getLevelName(l)) for c, l in sorted(list(LEVELS.items()))) - msg = "verbosity = verbose - quiet, default {}, count mapping = {{{}}}" - verbosity_group = parser.add_argument_group(msg.format(logging.getLevelName(LEVELS[3]), level_map)) - verbosity = verbosity_group.add_mutually_exclusive_group() - verbosity.add_argument("-v", "--verbose", action="count", dest="verbose", help="increase verbosity", default=2) - verbosity.add_argument("-q", "--quiet", action="count", dest="quiet", help="decrease verbosity", default=0) - options, _ = parser.parse_known_args(args) - verbosity_value = setup_report(options.verbose, options.quiet) - return options, verbosity_value - - -def _get_discover(parser, args, options): - discover_types = _collect_discovery_types() - discovery_parser = parser.add_argument_group("target interpreter identifier") - discovery_parser.add_argument( - "--discovery", - choices=list(discover_types.keys()), - default=next(i for i in discover_types.keys()), - required=False, - help="interpreter discovery method", - ) - options, _ = parser.parse_known_args(args, namespace=options) - discover_class = discover_types[options.discovery] - discover_class.add_parser_arguments(discovery_parser) - options, _ = parser.parse_known_args(args, namespace=options) - discover = discover_class(options) - return discover - - -_DISCOVERY = None -_ENTRY_POINTS = None - - -def plugins(key): - global _ENTRY_POINTS - if _ENTRY_POINTS is None: - _ENTRY_POINTS = entry_points() - return _ENTRY_POINTS[key] - - -def _collect_discovery_types(): - global _DISCOVERY - if _DISCOVERY is None: - _DISCOVERY = {e.name: e.load() for e in plugins("virtualenv.discovery")} - return _DISCOVERY - - -def _get_creator(interpreter, parser, options): - creators = _collect_creators(interpreter) - creator_parser = parser.add_argument_group("creator options") - choices = list(creators) - from virtualenv.interpreters.create.self_do import SelfDo - - if "self-do" in creators: - del creators["self-do"] - self_do = next((i for i, v in creators.items() if issubclass(v, SelfDo)), None) - if self_do is not None: - choices.append("self-do") - creator_parser.add_argument( - "--creator", - choices=choices, - # prefer the built-in venv if present, otherwise fallback to first defined type - default="venv" if "venv" in creators else next(iter(creators), None), - required=False, - help="create environment via{}".format("" if self_do is None else " (self-do = {})".format(self_do)), - ) - yield - selected = self_do if options.creator == "self-do" else options.creator - if selected not in creators: - raise RuntimeError("No virtualenv implementation for {}".format(interpreter)) - creator_class = creators[selected] - creator_class.add_parser_arguments(creator_parser, interpreter) - yield - if selected == "venv": - options.self_do = None if self_do is None else creators[self_do](options, interpreter) - creator = creator_class(options, interpreter) - yield creator - - -_CREATORS = None - - -def _collect_creators(interpreter): - global _CREATORS - if _CREATORS is None: - _CREATORS = {e.name: e.load() for e in plugins("virtualenv.create")} - creators = OrderedDict() - for name, class_type in _CREATORS.items(): - if class_type.supports(interpreter): - creators[name] = class_type - return creators - - -def _get_seeder(parser, options): - seed_parser = parser.add_argument_group("package seeder") - seeder_types = _collect_seeders() - seed_parser.add_argument( - "--seeder", - choices=list(seeder_types.keys()), - default="app-data", - required=False, - help="seed packages install method", - ) - seed_parser.add_argument( - "--without-pip", - help="if set forces the none seeder, used for compatibility with venv", - action="store_true", - dest="without_pip", - ) - yield - seeder_class = seeder_types["none" if options.without_pip is True else options.seeder] - seeder_class.add_parser_arguments(seed_parser) - yield - seeder = seeder_class(options) - yield seeder - - -_SEEDERS = None - - -def _collect_seeders(): - global _SEEDERS - if _SEEDERS is None: - _SEEDERS = {e.name: e.load() for e in plugins("virtualenv.seed")} - return _SEEDERS - - -def _get_activation(interpreter, parser, options): - activator_parser = parser.add_argument_group("activation script generator") - compatible = collect_activators(interpreter) - default = ",".join(compatible.keys()) - - def _extract_activators(entered_str): - elements = [e.strip() for e in entered_str.split(",") if e.strip()] - missing = [e for e in elements if e not in compatible] - if missing: - raise ArgumentTypeError("the following activators are not available {}".format(",".join(missing))) - return elements - - activator_parser.add_argument( - "--activators", - default=default, - metavar="comma_separated_list", - required=False, - help="activators to generate together with virtual environment - default is all available and compatible", - type=_extract_activators, - ) - yield - - selected_activators = _extract_activators(default) if options.activators is default else options.activators - active_activators = {k: v for k, v in compatible.items() if k in selected_activators} - activator_parser.add_argument( - "--prompt", - dest="prompt", - metavar="prompt", - help="provides an alternative prompt prefix for this environment", - default=None, - ) - for activator in active_activators.values(): - activator.add_parser_arguments(parser) - yield - - activator_instances = [activator_class(options) for activator_class in active_activators.values()] - yield activator_instances - - -_ACTIVATORS = None - - -def collect_activators(interpreter): - global _ACTIVATORS - if _ACTIVATORS is None: - _ACTIVATORS = {e.name: e.load() for e in plugins("virtualenv.activate")} - activators = {k: v for k, v in _ACTIVATORS.items() if v.supports(interpreter)} - return activators diff --git a/src/virtualenv/run/__init__.py b/src/virtualenv/run/__init__.py new file mode 100644 index 0000000..45a9662 --- /dev/null +++ b/src/virtualenv/run/__init__.py @@ -0,0 +1,66 @@ +from __future__ import absolute_import, unicode_literals + +import logging + +from ..config.cli.parser import VirtualEnvConfigParser +from ..report import LEVELS, setup_report +from ..session import Session +from ..version import __version__ +from .plugin.activators import ActivationSelector +from .plugin.creators import CreatorSelector +from .plugin.discovery import get_discover +from .plugin.seeders import SeederSelector + + +def run_via_cli(args): + """Run the virtual environment creation via CLI arguments + + :param args: the command line arguments + :return: the creator used + """ + session = session_via_cli(args) + session.run() + return session + + +def session_via_cli(args): + parser = VirtualEnvConfigParser() + add_version_flag(parser) + options, verbosity = _do_report_setup(parser, args) + discover = get_discover(parser, args, options) + interpreter = discover.interpreter + if interpreter is None: + raise RuntimeError("failed to find interpreter for {}".format(discover)) + elements = [ + CreatorSelector(interpreter, parser), + SeederSelector(interpreter, parser), + ActivationSelector(interpreter, parser), + ] + parser.parse_known_args(args, namespace=options) + for element in elements: + element.handle_selected_arg_parse(options) + parser.enable_help() + parser.parse_args(args, namespace=options) + creator, seeder, activators = tuple(e.create(options) for e in elements) # create types + session = Session(verbosity, interpreter, creator, seeder, activators) + return session + + +def add_version_flag(parser): + import virtualenv + + parser.add_argument( + "--version", action="version", version="%(prog)s {} from {}".format(__version__, virtualenv.__file__) + ) + + +def _do_report_setup(parser, args): + level_map = ", ".join("{}:{}".format(c, logging.getLevelName(l)) for c, l in sorted(list(LEVELS.items()))) + msg = "verbosity = verbose - quiet, default {}, count mapping = {{{}}}" + verbosity_group = parser.add_argument_group(msg.format(logging.getLevelName(LEVELS[3]), level_map)) + verbosity = verbosity_group.add_mutually_exclusive_group() + verbosity.add_argument("-v", "--verbose", action="count", dest="verbose", help="increase verbosity", default=2) + verbosity.add_argument("-q", "--quiet", action="count", dest="quiet", help="decrease verbosity", default=0) + options, _ = parser.parse_known_args(args) + verbosity_value = setup_report(options.verbose, options.quiet) + return options, verbosity_value diff --git a/src/virtualenv/run/plugin/__init__.py b/src/virtualenv/run/plugin/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/src/virtualenv/run/plugin/__init__.py diff --git a/src/virtualenv/run/plugin/activators.py b/src/virtualenv/run/plugin/activators.py new file mode 100644 index 0000000..8cd217f --- /dev/null +++ b/src/virtualenv/run/plugin/activators.py @@ -0,0 +1,49 @@ +from __future__ import absolute_import, unicode_literals + +from argparse import ArgumentTypeError + +from .base import ComponentBuilder + + +class ActivationSelector(ComponentBuilder): + def __init__(self, interpreter, parser): + self.default = None + super(ActivationSelector, self).__init__(interpreter, parser, "virtualenv.activate", "activators", True) + self.active = None + + def add_selector_arg_parse(self, name, choices): + self.default = ",".join(choices) + + self.parser.add_argument( + "--{}".format(name), + default=self.default, + metavar="comma_separated_list", + required=False, + help="activators to generate together with virtual environment - default is all available and compatible", + type=self._extract_activators, + ) + + def _extract_activators(self, entered_str): + elements = [e.strip() for e in entered_str.split(",") if e.strip()] + missing = [e for e in elements if e not in self.possible] + if missing: + raise ArgumentTypeError("the following activators are not available {}".format(",".join(missing))) + return elements + + def handle_selected_arg_parse(self, options): + selected_activators = ( + self._extract_activators(self.default) if options.activators is self.default else options.activators + ) + self.active = {k: v for k, v in self.possible.items() if k in selected_activators} + self.parser.add_argument( + "--prompt", + dest="prompt", + metavar="prompt", + help="provides an alternative prompt prefix for this environment", + default=None, + ) + for activator in self.active.values(): + activator.add_parser_arguments(self.parser, self.interpreter) + + def create(self, options): + return [activator_class(options) for activator_class in self.active.values()] diff --git a/src/virtualenv/run/plugin/base.py b/src/virtualenv/run/plugin/base.py new file mode 100644 index 0000000..5232b39 --- /dev/null +++ b/src/virtualenv/run/plugin/base.py @@ -0,0 +1,60 @@ +from __future__ import absolute_import, unicode_literals + +import sys +from collections import OrderedDict + +if sys.version_info >= (3, 8): + from importlib.metadata import entry_points +else: + from importlib_metadata import entry_points + + +class PluginLoader(object): + _OPTIONS = None + _ENTRY_POINTS = None + + @classmethod + def entry_points_for(cls, key): + return OrderedDict((e.name, e.load()) for e in cls.entry_points().get(key, {})) + + @staticmethod + def entry_points(): + if PluginLoader._ENTRY_POINTS is None: + PluginLoader._ENTRY_POINTS = entry_points() + return PluginLoader._ENTRY_POINTS + + +class ComponentBuilder(PluginLoader): + def __init__(self, interpreter, parser, key, name, needs_support): + self.interpreter = interpreter + self.name = name + self._impl_class = None + opts = self.options(key) + self.possible = self._build_options( + OrderedDict((k, v) for k, v in opts.items() if v.supports(interpreter)) if needs_support else opts + ) + self.parser = parser.add_argument_group("{} options".format(name)) + self.add_selector_arg_parse(name, list(self.possible)) + + @classmethod + def options(cls, key): + if cls._OPTIONS is None: + cls._OPTIONS = cls.entry_points_for(key) + return cls._OPTIONS + + def add_selector_arg_parse(self, name, choices): + raise NotImplementedError + + def _build_options(self, options): + return options + + def handle_selected_arg_parse(self, options): + selected = getattr(options, self.name) + if selected not in self.possible: + raise RuntimeError("No implementation for {}".format(self.interpreter)) + self._impl_class = self.possible[selected] + self._impl_class.add_parser_arguments(self.parser, self.interpreter) + return selected + + def create(self, options): + return self._impl_class(options, self.interpreter) diff --git a/src/virtualenv/run/plugin/creators.py b/src/virtualenv/run/plugin/creators.py new file mode 100644 index 0000000..a6d3cd3 --- /dev/null +++ b/src/virtualenv/run/plugin/creators.py @@ -0,0 +1,41 @@ +from __future__ import absolute_import, unicode_literals + +from virtualenv.interpreters.create.venv import Venv + +from .base import ComponentBuilder + + +class CreatorSelector(ComponentBuilder): + def __init__(self, interpreter, parser): + super(CreatorSelector, self).__init__(interpreter, parser, "virtualenv.create", "creator", True) + + def _build_options(self, options): + if not options: + raise RuntimeError("No virtualenv implementation for {}".format(self.interpreter)) + + from virtualenv.interpreters.create.builtin_way import VirtualenvBuiltin + + self.builtin_way = next((i for i, v in options.items() if issubclass(v, VirtualenvBuiltin)), None) + if self.builtin_way is not None: + options["builtin"] = options[self.builtin_way] # make the first builtin method the builtin alias + return options + + def add_selector_arg_parse(self, name, choices): + # prefer the built-in venv if present, otherwise fallback to first defined type + choices = sorted(choices, key=lambda a: 0 if a == "venv" else 1) + self.parser.add_argument( + "--{}".format(name), + choices=choices, + default=next(iter(choices)), + required=False, + help="create environment via{}".format( + "" if self.builtin_way is None else " (builtin = {})".format(self.builtin_way) + ), + ) + + def create(self, options): + if issubclass(self._impl_class, Venv): + options.builtin_way = ( + None if self.builtin_way is None else self.possible[self.builtin_way](options, self.interpreter) + ) + return super(CreatorSelector, self).create(options) diff --git a/src/virtualenv/run/plugin/discovery.py b/src/virtualenv/run/plugin/discovery.py new file mode 100644 index 0000000..aaed452 --- /dev/null +++ b/src/virtualenv/run/plugin/discovery.py @@ -0,0 +1,25 @@ +from __future__ import absolute_import, unicode_literals + +from .base import PluginLoader + + +class Discovery(PluginLoader): + """""" + + +def get_discover(parser, args, options): + discover_types = Discovery.entry_points_for("virtualenv.discovery") + discovery_parser = parser.add_argument_group("target interpreter identifier") + discovery_parser.add_argument( + "--discovery", + choices=list(discover_types.keys()), + default=next(i for i in discover_types.keys()), + required=False, + help="interpreter discovery method", + ) + options, _ = parser.parse_known_args(args, namespace=options) + discover_class = discover_types[options.discovery] + discover_class.add_parser_arguments(discovery_parser) + options, _ = parser.parse_known_args(args, namespace=options) + discover = discover_class(options) + return discover diff --git a/src/virtualenv/run/plugin/seeders.py b/src/virtualenv/run/plugin/seeders.py new file mode 100644 index 0000000..75cfad8 --- /dev/null +++ b/src/virtualenv/run/plugin/seeders.py @@ -0,0 +1,31 @@ +from __future__ import absolute_import, unicode_literals + +from .base import ComponentBuilder + + +class SeederSelector(ComponentBuilder): + def __init__(self, interpreter, parser): + super(SeederSelector, self).__init__(interpreter, parser, "virtualenv.seed", "seeder", False) + + def add_selector_arg_parse(self, name, choices): + self.parser.add_argument( + "--{}".format(name), + choices=choices, + default="app-data", + required=False, + help="seed packages install method", + ) + self.parser.add_argument( + "--without-pip", + help="if set forces the none seeder, used for compatibility with venv", + action="store_true", + dest="without_pip", + ) + + def handle_selected_arg_parse(self, options): + if options.without_pip is True: + setattr(options, self.name, "none") + return super(SeederSelector, self).handle_selected_arg_parse(options) + + def create(self, options): + return self._impl_class(options) diff --git a/src/virtualenv/seed/embed/base_embed.py b/src/virtualenv/seed/embed/base_embed.py index 5830516..66daece 100644 --- a/src/virtualenv/seed/embed/base_embed.py +++ b/src/virtualenv/seed/embed/base_embed.py @@ -22,7 +22,7 @@ class BaseEmbed(Seeder): self.no_setuptools = options.no_setuptools @classmethod - def add_parser_arguments(cls, parser): + def add_parser_arguments(cls, parser, interpreter): group = parser.add_mutually_exclusive_group() group.add_argument( "--download", diff --git a/src/virtualenv/seed/none.py b/src/virtualenv/seed/none.py index d3ccc8c..0bd12e4 100644 --- a/src/virtualenv/seed/none.py +++ b/src/virtualenv/seed/none.py @@ -8,7 +8,7 @@ class NoneSeeder(Seeder): super(NoneSeeder, self).__init__(options, False) @classmethod - def add_parser_arguments(cls, parser): + def add_parser_arguments(cls, parser, interpreter): pass def run(self, creator): diff --git a/src/virtualenv/seed/seeder.py b/src/virtualenv/seed/seeder.py index 1a79853..a679f03 100644 --- a/src/virtualenv/seed/seeder.py +++ b/src/virtualenv/seed/seeder.py @@ -11,7 +11,7 @@ class Seeder(object): self.enabled = enabled @classmethod - def add_parser_arguments(cls, parser): + def add_parser_arguments(cls, parser, interpreter): raise NotImplementedError @abstractmethod diff --git a/src/virtualenv/seed/via_app_data/pip_install/base.py b/src/virtualenv/seed/via_app_data/pip_install/base.py index b3c22c2..b74282c 100644 --- a/src/virtualenv/seed/via_app_data/pip_install/base.py +++ b/src/virtualenv/seed/via_app_data/pip_install/base.py @@ -175,7 +175,7 @@ class PipInstall(object): ): exe = to_folder / new_name exe.write_text(content, encoding="utf-8") - exe.chmod(exe.stat() | S_IXUSR | S_IXGRP | S_IXOTH) + exe.chmod(exe.stat().st_mode | S_IXUSR | S_IXGRP | S_IXOTH) result.append(exe) return result diff --git a/src/virtualenv/seed/via_app_data/via_app_data.py b/src/virtualenv/seed/via_app_data/via_app_data.py index 881e1c9..012e208 100644 --- a/src/virtualenv/seed/via_app_data/via_app_data.py +++ b/src/virtualenv/seed/via_app_data/via_app_data.py @@ -21,8 +21,8 @@ class FromAppData(BaseEmbed): self.app_data_dir = get_default_data_dir() / "seed-v1" @classmethod - def add_parser_arguments(cls, parser): - super(FromAppData, cls).add_parser_arguments(parser) + def add_parser_arguments(cls, parser, interpreter): + super(FromAppData, cls).add_parser_arguments(parser, interpreter) parser.add_argument( "--clear-app-data", dest="clear_app_data", diff --git a/src/virtualenv/util/__init__.py b/src/virtualenv/util/__init__.py index 9ec5427..32d0292 100644 --- a/src/virtualenv/util/__init__.py +++ b/src/virtualenv/util/__init__.py @@ -2,7 +2,7 @@ from __future__ import absolute_import, unicode_literals import sys -if sys.verison_info[0] == 3: +if sys.version_info[0] == 3: import configparser as ConfigParser else: import ConfigParser diff --git a/tests/unit/activation/conftest.py b/tests/unit/activation/conftest.py index 275be2d..9c65e1f 100644 --- a/tests/unit/activation/conftest.py +++ b/tests/unit/activation/conftest.py @@ -205,7 +205,7 @@ def raise_on_non_source_class(): @pytest.fixture(scope="session") def activation_python(tmp_path_factory, special_char_name): dest = os.path.join(six.ensure_text(str(tmp_path_factory.mktemp("activation-tester-env"))), special_char_name) - session = run_via_cli(["--seed", "none", dest, "--prompt", special_char_name, "--creator", "self-do"]) + session = run_via_cli(["--seed", "none", dest, "--prompt", special_char_name, "--creator", "builtin"]) pydoc_test = session.creator.site_packages[0] / "pydoc_test.py" with open(six.ensure_text(str(pydoc_test)), "wb") as file_handler: file_handler.write(b'"""This is pydoc_test.py"""') @@ -219,7 +219,7 @@ def activation_tester(activation_python, monkeypatch, tmp_path, is_inside_ci): def _tester(tester_class): tester = tester_class(activation_python) if not tester.of_class.supports(activation_python.creator.interpreter): - pytest.skip("{} not supported on current environment".format(tester.of_class.__name__)) + pytest.skip("{} not supported".format(tester.of_class.__name__)) version = tester.get_version(raise_on_fail=is_inside_ci) if not isinstance(version, six.string_types): pytest.skip(msg=six.text_type(version)) diff --git a/tests/unit/interpreters/boostrap/test_boostrap_link_via_app_data.py b/tests/unit/interpreters/boostrap/test_boostrap_link_via_app_data.py index ca0853f..a3dd036 100644 --- a/tests/unit/interpreters/boostrap/test_boostrap_link_via_app_data.py +++ b/tests/unit/interpreters/boostrap/test_boostrap_link_via_app_data.py @@ -27,7 +27,7 @@ def test_base_bootstrap_link_via_app_data(tmp_path, coverage_env): bundle_ver["setuptools"].split("-")[1], "--clear-app-data", "--creator", - "self-do", + "builtin", ] result = run_via_cli(create_cmd) coverage_env() diff --git a/tests/unit/interpreters/boostrap/test_pip_invoke.py b/tests/unit/interpreters/boostrap/test_pip_invoke.py index e8b48c6..cc94a59 100644 --- a/tests/unit/interpreters/boostrap/test_pip_invoke.py +++ b/tests/unit/interpreters/boostrap/test_pip_invoke.py @@ -17,7 +17,7 @@ def test_base_bootstrap_via_pip_invoke(tmp_path, coverage_env): "--setuptools", bundle_ver["setuptools"].split("-")[1], "--creator", - "self-do", + "builtin", ] result = run_via_cli(create_cmd) coverage_env() diff --git a/tests/unit/interpreters/create/conftest.py b/tests/unit/interpreters/create/conftest.py index d673a8a..3371df9 100644 --- a/tests/unit/interpreters/create/conftest.py +++ b/tests/unit/interpreters/create/conftest.py @@ -18,15 +18,15 @@ from virtualenv.util.subprocess import Popen # noinspection PyUnusedLocal -def get_root(tmp_path_factory): +def root(tmp_path_factory): return CURRENT.system_executable -def get_venv(tmp_path_factory): +def venv(tmp_path_factory): if CURRENT.is_venv: return sys.executable elif CURRENT.version_info.major == 3: - root_python = get_root(tmp_path_factory) + root_python = root(tmp_path_factory) dest = tmp_path_factory.mktemp("venv") process = Popen([str(root_python), "-m", "venv", "--without-pip", str(dest)]) process.communicate() @@ -35,7 +35,7 @@ def get_venv(tmp_path_factory): return CURRENT.find_exe_based_of(inside_folder=str(dest)) -def get_virtualenv(tmp_path_factory): +def virtualenv(tmp_path_factory): if CURRENT.is_old_virtualenv: return CURRENT.executable elif CURRENT.version_info.major == 3: @@ -59,12 +59,12 @@ def get_virtualenv(tmp_path_factory): return CURRENT.find_exe_based_of(inside_folder=virtualenv_python) -PYTHON = {"root": get_root, "venv": get_venv, "virtualenv": get_virtualenv} +PYTHON = {"root": root, "venv": venv, "virtualenv": virtualenv} @pytest.fixture(params=list(PYTHON.values()), ids=list(PYTHON.keys()), scope="session") def python(request, tmp_path_factory): result = request.param(tmp_path_factory) if result is None: - pytest.skip("could not resolve {}".format(request.param)) + pytest.skip("could not resolve interpreter based on {}".format(request.param.__name__)) return result diff --git a/tests/unit/interpreters/create/test_creator.py b/tests/unit/interpreters/create/test_creator.py index 6a07ed4..e850fe7 100644 --- a/tests/unit/interpreters/create/test_creator.py +++ b/tests/unit/interpreters/create/test_creator.py @@ -93,7 +93,7 @@ def test_create_no_seed(python, use_venv, global_access, system, coverage_env, s "--activators", "", "--creator", - "venv" if use_venv else "self-do", + "venv" if use_venv else "builtin", ] if global_access: cmd.append("--system-site-packages") @@ -141,7 +141,7 @@ def test_create_no_seed(python, use_venv, global_access, system, coverage_env, s assert last_from_system_path not in sys_path -@pytest.mark.skipif(not CURRENT.has_venv, reason="requires venv interpreter") +@pytest.mark.skipif(not CURRENT.has_venv, reason="requires interpreter with venv") def test_venv_fails_not_inline(tmp_path, capsys, mocker): def _session_via_cli(args): session = session_via_cli(args) @@ -188,7 +188,7 @@ def test_debug_bad_virtualenv(tmp_path): @pytest.mark.parametrize("clear", [True, False], ids=["clear", "no_clear"]) def test_create_clear_resets(tmp_path, use_venv, clear): marker = tmp_path / "magic" - cmd = [str(tmp_path), "--seeder", "none", "--creator", "venv" if use_venv else "self-do"] + cmd = [str(tmp_path), "--seeder", "none", "--creator", "venv" if use_venv else "builtin"] run_via_cli(cmd) marker.write_text("") # if we a marker file this should be gone on a clear run, remain otherwise @@ -203,7 +203,7 @@ def test_create_clear_resets(tmp_path, use_venv, clear): ) @pytest.mark.parametrize("prompt", [None, "magic"]) def test_prompt_set(tmp_path, use_venv, prompt): - cmd = [str(tmp_path), "--seeder", "none", "--creator", "venv" if use_venv else "self-do"] + cmd = [str(tmp_path), "--seeder", "none", "--creator", "venv" if use_venv else "builtin"] if prompt is not None: cmd.extend(["--prompt", "magic"]) @@ -242,7 +242,7 @@ def test_cross_major(cross_python, coverage_env, tmp_path): "--activators", "", "--creator", - "self-do", + "builtin", ] result = run_via_cli(cmd) coverage_env() diff --git a/tests/unit/interpreters/discovery/windows/test_windows_pep514.py b/tests/unit/interpreters/discovery/windows/test_windows_pep514.py index 604d0a9..3eaec39 100644 --- a/tests/unit/interpreters/discovery/windows/test_windows_pep514.py +++ b/tests/unit/interpreters/discovery/windows/test_windows_pep514.py @@ -11,7 +11,7 @@ import six from virtualenv.util.path import Path -@pytest.mark.skipif(sys.platform != "win32", reason="Windows registry only on Windows platform") +@pytest.mark.skipif(sys.platform != "win32", reason="no Windows registry") def test_pep517(_mock_registry): from virtualenv.interpreters.discovery.windows.pep514 import discover_pythons @@ -30,7 +30,7 @@ def test_pep517(_mock_registry): ] -@pytest.mark.skipif(sys.platform != "win32", reason="Windows registry only on Windows platform") +@pytest.mark.skipif(sys.platform != "win32", reason="no Windows registry") def test_pep517_run(_mock_registry, capsys, caplog): from virtualenv.interpreters.discovery.windows import pep514 diff --git a/tests/unit/interpreters/test_interpreters.py b/tests/unit/interpreters/test_interpreters.py index 0b28287..feb1bc6 100644 --- a/tests/unit/interpreters/test_interpreters.py +++ b/tests/unit/interpreters/test_interpreters.py @@ -19,7 +19,7 @@ def test_failed_to_find_bad_spec(): @pytest.mark.parametrize("of_id", [sys.executable, CURRENT.implementation]) def test_failed_to_find_implementation(of_id, mocker): - mocker.patch("virtualenv.run._collect_creators", return_value={}) + mocker.patch("virtualenv.run.plugin.creators.CreatorSelector._OPTIONS", return_value={}) with pytest.raises(RuntimeError) as context: run_via_cli(["-p", of_id]) assert repr(context.value) == repr(RuntimeError("No virtualenv implementation for {}".format(CURRENT))) diff --git a/tests/unit/test_util.py b/tests/unit/test_util.py index 7eb3dc8..7da9b57 100644 --- a/tests/unit/test_util.py +++ b/tests/unit/test_util.py @@ -35,7 +35,7 @@ def _try_symlink(caplog, tmp_path, level): return dst, src -@pytest.mark.skipif(hasattr(os, "symlink"), reason="requires no symlink") +@pytest.mark.skipif(hasattr(os, "symlink"), reason="requires lack of symlink") def test_os_no_symlink_use_copy(caplog, tmp_path): dst, src = _try_symlink(caplog, tmp_path, level=logging.DEBUG) assert caplog.messages == ["copy {} to {}".format(src, dst)] |
