From b36255233a4f8e87d30b36fa8382492436c7f45d Mon Sep 17 00:00:00 2001 From: Chad Smith Date: Sun, 4 Aug 2019 22:32:34 -0700 Subject: [PATCH 1/6] add venv as an option for venv_backend --- nox/sessions.py | 7 ++++++ nox/virtualenv.py | 58 +++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 55 insertions(+), 10 deletions(-) diff --git a/nox/sessions.py b/nox/sessions.py index 38cd20dc..d83e8dd1 100644 --- a/nox/sessions.py +++ b/nox/sessions.py @@ -375,6 +375,13 @@ def _create_venv(self): self.venv = CondaEnv( path, interpreter=self.func.python, reuse_existing=reuse_existing ) + elif self.func.venv_backend == "venv": + self.venv = VirtualEnv( + path, + interpreter=self.func.python, + reuse_existing=reuse_existing, + venv=True, + ) else: raise ValueError( "Expected venv_backend one of ('virtualenv', 'conda'), but got '{}'.".format( diff --git a/nox/virtualenv.py b/nox/virtualenv.py index ef65914f..cd7a8da1 100644 --- a/nox/virtualenv.py +++ b/nox/virtualenv.py @@ -13,6 +13,7 @@ # limitations under the License. import os +from pathlib import Path import platform import re import shutil @@ -174,6 +175,26 @@ def create(self): return True +def resolve_real_python_outside_venv(desired_intepreter: str) -> str: + """Return path to the real Python installation based + + See also: + https://docs.python.org/3/library/sys.html#sys.prefix + https://docs.python.org/3/library/sys.html#sys.base_prefix + """ + interpreter = ( + desired_intepreter + if desired_intepreter + else f"{sys.version_info.major}.{sys.version_info.minor}" + ) + python_str = f"python{interpreter}" # i.e. python3.6 + + base_python_installation = Path(sys.base_prefix) / "bin" / python_str + if base_python_installation.is_file(): + return str(base_python_installation) + raise InterpreterNotFound(interpreter) + + class VirtualEnv(ProcessEnv): """Virtualenv management class. @@ -196,12 +217,15 @@ class VirtualEnv(ProcessEnv): is_sandboxed = True - def __init__(self, location, interpreter=None, reuse_existing=False): + def __init__( + self, location, interpreter=None, reuse_existing=False, *, venv: bool = False + ): self.location_name = location self.location = os.path.abspath(location) self.interpreter = interpreter self._resolved = None self.reuse_existing = reuse_existing + self.venv_or_virtualenv = "venv" if venv else "virtualenv" super(VirtualEnv, self).__init__() _clean_location = _clean_location @@ -212,6 +236,7 @@ def _resolved_interpreter(self): Based heavily on tox's implementation (tox/interpreters.py). """ + # If there is no assigned interpreter, then use the same one used by # Nox. if isinstance(self._resolved, Exception): @@ -220,8 +245,13 @@ def _resolved_interpreter(self): if self._resolved is not None: return self._resolved + currently_in_virtual_environment = sys.prefix != sys.base_prefix + if self.interpreter is None: - self._resolved = sys.executable + if currently_in_virtual_environment: + self._resolved = resolve_real_python_outside_venv(self.interpreter) + else: + self._resolved = sys.executable return self._resolved # Otherwise we need to divine the path to the interpreter. This is @@ -238,7 +268,10 @@ def _resolved_interpreter(self): cleaned_interpreter = "python{}".format(xy_version) # If the cleaned interpreter is on the PATH, go ahead and return it. - if py.path.local.sysfind(cleaned_interpreter): + if currently_in_virtual_environment and _SYSTEM != "Windows": + self._resolved = resolve_real_python_outside_venv(self.interpreter) + return self._resolved + elif py.path.local.sysfind(cleaned_interpreter): self._resolved = cleaned_interpreter return self._resolved @@ -275,21 +308,26 @@ def bin(self): return os.path.join(self.location, "bin") def create(self): - """Create the virtualenv.""" + """Create the virtualenv or venv.""" if not self._clean_location(): logger.debug( - "Re-using existing virtualenv at {}.".format(self.location_name) + "Re-using existing virtual environment at {}.".format( + self.location_name + ) ) return False - cmd = [sys.executable, "-m", "virtualenv", self.location] - if self.interpreter: - cmd.extend(["-p", self._resolved_interpreter]) + cmd = [self._resolved_interpreter] + else: + cmd = [sys.executable] + cmd += ["-m", self.venv_or_virtualenv, self.location] logger.info( - "Creating virtualenv using {} in {}".format( - os.path.basename(self._resolved_interpreter), self.location_name + "Creating virtual environment ({}) using {} in {}".format( + self.venv_or_virtualenv, + os.path.basename(self._resolved_interpreter), + self.location_name, ) ) nox.command.run(cmd, silent=True, log=False) From 0cfb341e304879929102cbebc6fad73b305d47e3 Mon Sep 17 00:00:00 2001 From: Chad Smith Date: Mon, 5 Aug 2019 18:16:26 -0700 Subject: [PATCH 2/6] remove python resolution changes --- nox/virtualenv.py | 47 +++++++---------------------------------------- 1 file changed, 7 insertions(+), 40 deletions(-) diff --git a/nox/virtualenv.py b/nox/virtualenv.py index cd7a8da1..cb8fac8b 100644 --- a/nox/virtualenv.py +++ b/nox/virtualenv.py @@ -13,7 +13,6 @@ # limitations under the License. import os -from pathlib import Path import platform import re import shutil @@ -175,26 +174,6 @@ def create(self): return True -def resolve_real_python_outside_venv(desired_intepreter: str) -> str: - """Return path to the real Python installation based - - See also: - https://docs.python.org/3/library/sys.html#sys.prefix - https://docs.python.org/3/library/sys.html#sys.base_prefix - """ - interpreter = ( - desired_intepreter - if desired_intepreter - else f"{sys.version_info.major}.{sys.version_info.minor}" - ) - python_str = f"python{interpreter}" # i.e. python3.6 - - base_python_installation = Path(sys.base_prefix) / "bin" / python_str - if base_python_installation.is_file(): - return str(base_python_installation) - raise InterpreterNotFound(interpreter) - - class VirtualEnv(ProcessEnv): """Virtualenv management class. @@ -217,9 +196,7 @@ class VirtualEnv(ProcessEnv): is_sandboxed = True - def __init__( - self, location, interpreter=None, reuse_existing=False, *, venv: bool = False - ): + def __init__(self, location, interpreter=None, reuse_existing=False, *, venv=False): self.location_name = location self.location = os.path.abspath(location) self.interpreter = interpreter @@ -236,7 +213,6 @@ def _resolved_interpreter(self): Based heavily on tox's implementation (tox/interpreters.py). """ - # If there is no assigned interpreter, then use the same one used by # Nox. if isinstance(self._resolved, Exception): @@ -245,13 +221,8 @@ def _resolved_interpreter(self): if self._resolved is not None: return self._resolved - currently_in_virtual_environment = sys.prefix != sys.base_prefix - if self.interpreter is None: - if currently_in_virtual_environment: - self._resolved = resolve_real_python_outside_venv(self.interpreter) - else: - self._resolved = sys.executable + self._resolved = sys.executable return self._resolved # Otherwise we need to divine the path to the interpreter. This is @@ -268,10 +239,7 @@ def _resolved_interpreter(self): cleaned_interpreter = "python{}".format(xy_version) # If the cleaned interpreter is on the PATH, go ahead and return it. - if currently_in_virtual_environment and _SYSTEM != "Windows": - self._resolved = resolve_real_python_outside_venv(self.interpreter) - return self._resolved - elif py.path.local.sysfind(cleaned_interpreter): + if py.path.local.sysfind(cleaned_interpreter): self._resolved = cleaned_interpreter return self._resolved @@ -317,11 +285,10 @@ def create(self): ) return False - if self.interpreter: - cmd = [self._resolved_interpreter] - else: - cmd = [sys.executable] - cmd += ["-m", self.venv_or_virtualenv, self.location] + cmd = [sys.executable, "-m", self.venv_or_virtualenv, self.location] + + if self.interpreter and self.venv_or_virtualenv == "virtualenv": + cmd.extend(["-p", self._resolved_interpreter]) logger.info( "Creating virtual environment ({}) using {} in {}".format( From 06edcd850c4a3feed4cbc3177f628def91a2ae81 Mon Sep 17 00:00:00 2001 From: Chad Smith Date: Mon, 5 Aug 2019 18:37:56 -0700 Subject: [PATCH 3/6] update tests --- nox/sessions.py | 2 +- tests/test_sessions.py | 1 + tests/test_virtualenv.py | 19 +++++++++++++++++++ 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/nox/sessions.py b/nox/sessions.py index d83e8dd1..9dbdc649 100644 --- a/nox/sessions.py +++ b/nox/sessions.py @@ -384,7 +384,7 @@ def _create_venv(self): ) else: raise ValueError( - "Expected venv_backend one of ('virtualenv', 'conda'), but got '{}'.".format( + "Expected venv_backend one of ('virtualenv', 'conda', 'venv'), but got '{}'.".format( self.func.venv_backend ) ) diff --git a/tests/test_sessions.py b/tests/test_sessions.py index 0b9559aa..91013d7a 100644 --- a/tests/test_sessions.py +++ b/tests/test_sessions.py @@ -514,6 +514,7 @@ def test__create_venv(self, create): "virtualenv", nox.virtualenv.VirtualEnv, ), + ("nox.virtualenv.VirtualEnv.create", 'venv', nox.virtualenv.VirtualEnv), ("nox.virtualenv.CondaEnv.create", "conda", nox.virtualenv.CondaEnv), ], ) diff --git a/tests/test_virtualenv.py b/tests/test_virtualenv.py index 04296e23..4fd87dd8 100644 --- a/tests/test_virtualenv.py +++ b/tests/test_virtualenv.py @@ -37,6 +37,16 @@ def factory(*args, **kwargs): return factory +@pytest.fixture +def make_venv(tmpdir): + def factory(*args, **kwargs): + location = tmpdir.join("venv") + venv = nox.virtualenv.VirtualEnv(location.strpath, *args, venv=True, **kwargs) + return (venv, location) + + return factory + + @pytest.fixture def make_conda(tmpdir): def factory(*args, **kwargs): @@ -69,6 +79,14 @@ def test_condaenv_constructor_explicit(make_conda): assert venv.reuse_existing is True +def test_venv_constructor_defaults(make_venv): + venv, _ = make_venv() + assert venv.location + assert venv.interpreter is None + assert venv.reuse_existing is False + assert venv.venv_or_virtualenv == "venv" + + @pytest.mark.skipif(not HAS_CONDA, reason="Missing conda command.") def test_condaenv_create(make_conda): venv, dir_ = make_conda() @@ -117,6 +135,7 @@ def test_constructor_defaults(make_one): assert venv.location assert venv.interpreter is None assert venv.reuse_existing is False + assert venv.venv_or_virtualenv == "virtualenv" @pytest.mark.skipif(IS_WINDOWS, reason="Not testing multiple interpreters on Windows.") From db84d107bbc905186f27057e5a56ef8d22a0fba4 Mon Sep 17 00:00:00 2001 From: Chad Smith Date: Mon, 5 Aug 2019 21:45:29 -0700 Subject: [PATCH 4/6] run black --- tests/test_sessions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_sessions.py b/tests/test_sessions.py index 91013d7a..dbe72bb1 100644 --- a/tests/test_sessions.py +++ b/tests/test_sessions.py @@ -514,7 +514,7 @@ def test__create_venv(self, create): "virtualenv", nox.virtualenv.VirtualEnv, ), - ("nox.virtualenv.VirtualEnv.create", 'venv', nox.virtualenv.VirtualEnv), + ("nox.virtualenv.VirtualEnv.create", "venv", nox.virtualenv.VirtualEnv), ("nox.virtualenv.CondaEnv.create", "conda", nox.virtualenv.CondaEnv), ], ) From 5d5c737e9d87a89761a31d24d4adb139004fdd02 Mon Sep 17 00:00:00 2001 From: Chad Smith Date: Tue, 6 Aug 2019 09:36:38 -0700 Subject: [PATCH 5/6] use different interpreters for venv vs virtualenv --- nox/virtualenv.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/nox/virtualenv.py b/nox/virtualenv.py index cb8fac8b..8cc0f6d1 100644 --- a/nox/virtualenv.py +++ b/nox/virtualenv.py @@ -285,10 +285,12 @@ def create(self): ) return False - cmd = [sys.executable, "-m", self.venv_or_virtualenv, self.location] - - if self.interpreter and self.venv_or_virtualenv == "virtualenv": - cmd.extend(["-p", self._resolved_interpreter]) + if self.venv_or_virtualenv == "virtualenv": + cmd = [sys.executable, "-m", "virtualenv", self.location] + if self.interpreter: + cmd.extend(["-p", self._resolved_interpreter]) + else: + cmd = [self._resolved_interpreter, "-m", "venv", self.location] logger.info( "Creating virtual environment ({}) using {} in {}".format( From 70f467ea505326260f8d70fffcbc47b0b99a7cd0 Mon Sep 17 00:00:00 2001 From: Chad Smith Date: Sat, 10 Aug 2019 16:51:34 -0700 Subject: [PATCH 6/6] update test --- tests/test_virtualenv.py | 23 +++++------------------ 1 file changed, 5 insertions(+), 18 deletions(-) diff --git a/tests/test_virtualenv.py b/tests/test_virtualenv.py index 4fd87dd8..61b246d3 100644 --- a/tests/test_virtualenv.py +++ b/tests/test_virtualenv.py @@ -37,16 +37,6 @@ def factory(*args, **kwargs): return factory -@pytest.fixture -def make_venv(tmpdir): - def factory(*args, **kwargs): - location = tmpdir.join("venv") - venv = nox.virtualenv.VirtualEnv(location.strpath, *args, venv=True, **kwargs) - return (venv, location) - - return factory - - @pytest.fixture def make_conda(tmpdir): def factory(*args, **kwargs): @@ -79,14 +69,6 @@ def test_condaenv_constructor_explicit(make_conda): assert venv.reuse_existing is True -def test_venv_constructor_defaults(make_venv): - venv, _ = make_venv() - assert venv.location - assert venv.interpreter is None - assert venv.reuse_existing is False - assert venv.venv_or_virtualenv == "venv" - - @pytest.mark.skipif(not HAS_CONDA, reason="Missing conda command.") def test_condaenv_create(make_conda): venv, dir_ = make_conda() @@ -232,6 +214,11 @@ def test_create(make_one): assert dir_.join("test.txt").check() +def test_create_venv_backend(make_one): + venv, dir_ = make_one(venv=True) + venv.create() + + @pytest.mark.skipif(IS_WINDOWS, reason="Not testing multiple interpreters on Windows.") def test_create_interpreter(make_one): venv, dir_ = make_one(interpreter="python3")