summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBernat Gabor <bgabor8@bloomberg.net>2020-01-10 09:18:59 +0000
committerBernat Gabor <bgabor8@bloomberg.net>2020-01-10 15:38:39 +0000
commit5f3580ee96ad948e5f4c66fc8358d0b81903da57 (patch)
tree55312be9e4d62ec60464d5ae15e3606d6eb1fc34
parent7d964e3ce7bf13326a6b15497d8294fd8830e4a5 (diff)
downloadvirtualenv-5f3580ee96ad948e5f4c66fc8358d0b81903da57.tar.gz
reorganize run.py - prefer inheritence based API over generators
Signed-off-by: Bernat Gabor <bgabor8@bloomberg.net>
-rw-r--r--setup.cfg2
-rw-r--r--src/virtualenv/activation/activator.py2
-rw-r--r--src/virtualenv/interpreters/create/builtin_way.py (renamed from src/virtualenv/interpreters/create/self_do.py)2
-rw-r--r--src/virtualenv/interpreters/create/cpython/common.py4
-rw-r--r--src/virtualenv/interpreters/create/creator.py8
-rw-r--r--src/virtualenv/interpreters/create/pypy/common.py4
-rw-r--r--src/virtualenv/interpreters/create/venv.py26
-rw-r--r--src/virtualenv/interpreters/create/via_global_ref/python2.py4
-rw-r--r--src/virtualenv/interpreters/create/via_global_ref/via_global_self_do.py8
-rw-r--r--src/virtualenv/run.py236
-rw-r--r--src/virtualenv/run/__init__.py66
-rw-r--r--src/virtualenv/run/plugin/__init__.py0
-rw-r--r--src/virtualenv/run/plugin/activators.py49
-rw-r--r--src/virtualenv/run/plugin/base.py60
-rw-r--r--src/virtualenv/run/plugin/creators.py41
-rw-r--r--src/virtualenv/run/plugin/discovery.py25
-rw-r--r--src/virtualenv/run/plugin/seeders.py31
-rw-r--r--src/virtualenv/seed/embed/base_embed.py2
-rw-r--r--src/virtualenv/seed/none.py2
-rw-r--r--src/virtualenv/seed/seeder.py2
-rw-r--r--src/virtualenv/seed/via_app_data/pip_install/base.py2
-rw-r--r--src/virtualenv/seed/via_app_data/via_app_data.py4
-rw-r--r--src/virtualenv/util/__init__.py2
-rw-r--r--tests/unit/activation/conftest.py4
-rw-r--r--tests/unit/interpreters/boostrap/test_boostrap_link_via_app_data.py2
-rw-r--r--tests/unit/interpreters/boostrap/test_pip_invoke.py2
-rw-r--r--tests/unit/interpreters/create/conftest.py12
-rw-r--r--tests/unit/interpreters/create/test_creator.py10
-rw-r--r--tests/unit/interpreters/discovery/windows/test_windows_pep514.py4
-rw-r--r--tests/unit/interpreters/test_interpreters.py2
-rw-r--r--tests/unit/test_util.py2
31 files changed, 328 insertions, 292 deletions
diff --git a/setup.cfg b/setup.cfg
index 500d20c..741d594 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -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)]