From 00266d0280ac60a75cdd662e4220074a1f9b9252 Mon Sep 17 00:00:00 2001 From: Sylvain MARIE Date: Tue, 21 Apr 2020 17:43:50 +0200 Subject: [PATCH 01/28] New global option `nox.options.venv_backend` to set the default backend. Fixes #315 --- nox/_options.py | 31 +++++++++++++++++++++++++++++++ nox/sessions.py | 9 +++++---- 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/nox/_options.py b/nox/_options.py index 782a512f..0bed100e 100644 --- a/nox/_options.py +++ b/nox/_options.py @@ -60,6 +60,20 @@ def _session_filters_merge_func( return getattr(command_args, key) +def _venv_backend_merge_func( + command_args: argparse.Namespace, noxfile_args: argparse.Namespace +) -> str: + """Ensure that there is always some venv_backend. + + Args: + command_args (_option_set.Namespace): The options specified on the + command-line. + noxfile_Args (_option_set.Namespace): The options specified in the + Noxfile. + """ + return command_args.venv_backend or noxfile_args.venv_backend or "virtualenv" + + def _envdir_merge_func( command_args: argparse.Namespace, noxfile_args: argparse.Namespace ) -> str: @@ -145,6 +159,12 @@ def _session_completer( ] +def _venv_backend_completer( + prefix: str, parsed_args: argparse.Namespace, **kwargs: Any +) -> List[str]: + return ['virtualenv', 'conda', 'venv'] + + options.add_options( _option_set.Option( "help", @@ -221,6 +241,17 @@ def _session_completer( help="Logs the output of all commands run including commands marked silent.", noxfile=True, ), + _option_set.Option( + "venv_backend", + "-vb", + "--venv_backend", + group=options.groups["secondary"], + noxfile=True, + merge_func=_venv_backend_merge_func, + help="Virtual environment backend to use by default for nox sessions, this is ``'virtualenv'`` by default but " + "any of ``('virtualenv', 'conda', 'venv')`` are accepted.", + completer=_venv_backend_completer, + ), *_option_set.make_flag_pair( "reuse_existing_virtualenvs", ("-r", "--reuse-existing-virtualenvs"), diff --git a/nox/sessions.py b/nox/sessions.py index 59a5cf4b..9768b097 100644 --- a/nox/sessions.py +++ b/nox/sessions.py @@ -398,21 +398,22 @@ def _create_venv(self) -> None: self.func.reuse_venv or self.global_config.reuse_existing_virtualenvs ) - if not self.func.venv_backend or self.func.venv_backend == "virtualenv": + backend = self.func.venv_backend or self.global_config.venv_backend + if backend == "virtualenv": self.venv = VirtualEnv( path, interpreter=self.func.python, # type: ignore reuse_existing=reuse_existing, venv_params=self.func.venv_params, ) - elif self.func.venv_backend == "conda": + elif backend == "conda": self.venv = CondaEnv( path, interpreter=self.func.python, # type: ignore reuse_existing=reuse_existing, venv_params=self.func.venv_params, ) - elif self.func.venv_backend == "venv": + elif backend == "venv": self.venv = VirtualEnv( path, interpreter=self.func.python, # type: ignore @@ -423,7 +424,7 @@ def _create_venv(self) -> None: else: raise ValueError( "Expected venv_backend one of ('virtualenv', 'conda', 'venv'), but got '{}'.".format( - self.func.venv_backend + backend ) ) From 977aae2fbf946d892fd0352a1cc5b272083e1fc0 Mon Sep 17 00:00:00 2001 From: Sylvain MARIE Date: Tue, 21 Apr 2020 17:55:51 +0200 Subject: [PATCH 02/28] Added doc about the new option --- docs/config.rst | 1 + docs/usage.rst | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/docs/config.rst b/docs/config.rst index e2a83386..5223070c 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -375,6 +375,7 @@ The following options can be specified in the Noxfile: * ``nox.options.sessions`` is equivalent to specifying :ref:`-s or --sessions `. * ``nox.options.pythons`` is equivalent to specifying :ref:`-p or --pythons `. * ``nox.options.keywords`` is equivalent to specifying :ref:`-k or --keywords `. +* ``nox.options.venv_backend`` is equivalent to specifying :ref:`-vb or --venv_backend `. * ``nox.options.reuse_existing_virtualenvs`` is equivalent to specifying :ref:`--reuse-existing-virtualenvs `. You can force this off by specifying ``--no-reuse-existing-virtualenvs`` during invocation. * ``nox.options.stop_on_first_error`` is equivalent to specifying :ref:`--stop-on-first-error `. You can force this off by specifying ``--no-stop-on-first-error`` during invocation. * ``nox.options.error_on_missing_interpreters`` is equivalent to specifying :ref:`--error-on-missing-interpreters `. You can force this off by specifying ``--no-error-on-missing-interpreters`` during invocation. diff --git a/docs/usage.rst b/docs/usage.rst index 6f0e3418..ed7b03ba 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -105,6 +105,24 @@ Then running ``nox --session tests`` will actually run all parametrized versions nox --session "tests(django='2.0')" +.. opt-default-venv_backend: + +Changing the sessions default backend +------------------------------------- + +By default nox uses ``virtualenv`` as the virtual environment backend for the sessions, but it also supports ``conda`` and ``venv``. You can change the default behaviour by using ``-vb `` or ``--venv_backend ``. Supported names are ``('virtualenv', 'conda', 'venv')``. + +.. code-block:: console + + nox -vb conda + nox --venv_backend conda + + +You can also set this option in the Noxfile with ``nox.options.venv_backend``. In case both are provided, the commandline argument takes precedence. + +Note that using this option does not change the backend for sessions where ``venv_backend`` is explicitly set. + + .. _opt-reuse-existing-virtualenvs: Re-using virtualenvs From 7ff8cf3b13f57fa018026edb2936651be342c58c Mon Sep 17 00:00:00 2001 From: Sylvain MARIE Date: Tue, 21 Apr 2020 17:56:29 +0200 Subject: [PATCH 03/28] Blackened --- nox/_options.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nox/_options.py b/nox/_options.py index 0bed100e..39a2fb30 100644 --- a/nox/_options.py +++ b/nox/_options.py @@ -162,7 +162,7 @@ def _session_completer( def _venv_backend_completer( prefix: str, parsed_args: argparse.Namespace, **kwargs: Any ) -> List[str]: - return ['virtualenv', 'conda', 'venv'] + return ["virtualenv", "conda", "venv"] options.add_options( @@ -249,7 +249,7 @@ def _venv_backend_completer( noxfile=True, merge_func=_venv_backend_merge_func, help="Virtual environment backend to use by default for nox sessions, this is ``'virtualenv'`` by default but " - "any of ``('virtualenv', 'conda', 'venv')`` are accepted.", + "any of ``('virtualenv', 'conda', 'venv')`` are accepted.", completer=_venv_backend_completer, ), *_option_set.make_flag_pair( From 10951c3fc263791b2953687edd23b762b1dc9d1b Mon Sep 17 00:00:00 2001 From: Sylvain MARIE Date: Tue, 21 Apr 2020 19:10:41 +0200 Subject: [PATCH 04/28] fixed tests --- nox/sessions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nox/sessions.py b/nox/sessions.py index 9768b097..3434b877 100644 --- a/nox/sessions.py +++ b/nox/sessions.py @@ -399,7 +399,7 @@ def _create_venv(self) -> None: ) backend = self.func.venv_backend or self.global_config.venv_backend - if backend == "virtualenv": + if backend is None or backend == "virtualenv": self.venv = VirtualEnv( path, interpreter=self.func.python, # type: ignore From 0b5a11c0dc9ae9d6c48bc3185534289de1c7e653 Mon Sep 17 00:00:00 2001 From: Sylvain MARIE Date: Tue, 21 Apr 2020 19:12:23 +0200 Subject: [PATCH 05/28] Fixed docs --- docs/config.rst | 2 +- docs/usage.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/config.rst b/docs/config.rst index 5223070c..a1507356 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -375,7 +375,7 @@ The following options can be specified in the Noxfile: * ``nox.options.sessions`` is equivalent to specifying :ref:`-s or --sessions `. * ``nox.options.pythons`` is equivalent to specifying :ref:`-p or --pythons `. * ``nox.options.keywords`` is equivalent to specifying :ref:`-k or --keywords `. -* ``nox.options.venv_backend`` is equivalent to specifying :ref:`-vb or --venv_backend `. +* ``nox.options.venv_backend`` is equivalent to specifying :ref:`-vb or --venv_backend `. * ``nox.options.reuse_existing_virtualenvs`` is equivalent to specifying :ref:`--reuse-existing-virtualenvs `. You can force this off by specifying ``--no-reuse-existing-virtualenvs`` during invocation. * ``nox.options.stop_on_first_error`` is equivalent to specifying :ref:`--stop-on-first-error `. You can force this off by specifying ``--no-stop-on-first-error`` during invocation. * ``nox.options.error_on_missing_interpreters`` is equivalent to specifying :ref:`--error-on-missing-interpreters `. You can force this off by specifying ``--no-error-on-missing-interpreters`` during invocation. diff --git a/docs/usage.rst b/docs/usage.rst index ed7b03ba..f203f120 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -105,7 +105,7 @@ Then running ``nox --session tests`` will actually run all parametrized versions nox --session "tests(django='2.0')" -.. opt-default-venv_backend: +.. _opt-default-venv-backend: Changing the sessions default backend ------------------------------------- From ea39d5f414c232769b04239fa344fa0b472d8af2 Mon Sep 17 00:00:00 2001 From: Sylvain MARIE Date: Wed, 22 Apr 2020 10:10:42 +0200 Subject: [PATCH 06/28] Fixed coverage by adding tests for the venv_backend_completer --- tests/test__option_set.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/test__option_set.py b/tests/test__option_set.py index 3ad8b593..ffc9e3bd 100644 --- a/tests/test__option_set.py +++ b/tests/test__option_set.py @@ -70,3 +70,17 @@ def test_session_completer_invalid_sessions(self): prefix=None, parsed_args=parsed_args ) assert len(all_nox_sessions) == 0 + + def test_venv_backend_completer(self): + parsed_args = _options.options.namespace(venv_backend=(), keywords=()) + all_nox_venv_backends = _options._venv_backend_completer( + prefix=None, parsed_args=parsed_args + ) + assert set(all_nox_venv_backends) == {'venv', 'conda', 'virtualenv'} + + def test_venv_backend_completer_invalid_venv_backends(self): + parsed_args = _options.options.namespace(venv_backend=("baz",), keywords=()) + all_nox_venv_backends = _options._session_completer( + prefix=None, parsed_args=parsed_args + ) + assert len(all_nox_venv_backends) == 0 From 35330bfc14501658d74af13817eb02d1b5940e1e Mon Sep 17 00:00:00 2001 From: Sylvain MARIE Date: Wed, 22 Apr 2020 10:21:25 +0200 Subject: [PATCH 07/28] fixed test --- tests/test__option_set.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test__option_set.py b/tests/test__option_set.py index ffc9e3bd..e99792bd 100644 --- a/tests/test__option_set.py +++ b/tests/test__option_set.py @@ -72,15 +72,15 @@ def test_session_completer_invalid_sessions(self): assert len(all_nox_sessions) == 0 def test_venv_backend_completer(self): - parsed_args = _options.options.namespace(venv_backend=(), keywords=()) + parsed_args = _options.options.namespace(venv_backend=()) all_nox_venv_backends = _options._venv_backend_completer( prefix=None, parsed_args=parsed_args ) assert set(all_nox_venv_backends) == {'venv', 'conda', 'virtualenv'} def test_venv_backend_completer_invalid_venv_backends(self): - parsed_args = _options.options.namespace(venv_backend=("baz",), keywords=()) - all_nox_venv_backends = _options._session_completer( + parsed_args = _options.options.namespace(venv_backend=("baz",)) + all_nox_venv_backends = _options._venv_backend_completer( prefix=None, parsed_args=parsed_args ) assert len(all_nox_venv_backends) == 0 From 1d3727c152306340d29169ff34987ffb0fefb619 Mon Sep 17 00:00:00 2001 From: Sylvain MARIE Date: Wed, 22 Apr 2020 10:29:45 +0200 Subject: [PATCH 08/28] Added tests for short and long versions of the new option. --- tests/test_main.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/test_main.py b/tests/test_main.py index 78002581..d1693efb 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -70,6 +70,8 @@ def test_main_long_form_args(): "--sessions", "1", "2", + "--venv_backend", + "venv", "--reuse-existing-virtualenvs", "--stop-on-first-error", ] @@ -87,6 +89,7 @@ def test_main_long_form_args(): assert config.noxfile == "noxfile.py" assert config.envdir.endswith(".other") assert config.sessions == ["1", "2"] + assert config.venv_backend == "venv" assert config.reuse_existing_virtualenvs is True assert config.stop_on_first_error is True assert config.posargs == [] @@ -94,7 +97,7 @@ def test_main_long_form_args(): def test_main_short_form_args(monkeypatch): monkeypatch.setattr( - sys, "argv", [sys.executable, "-f", "noxfile.py", "-s", "1", "2", "-r"] + sys, "argv", [sys.executable, "-f", "noxfile.py", "-s", "1", "2", "-vb", "venv", "-r"] ) with mock.patch("nox.workflow.execute") as execute: execute.return_value = 0 @@ -109,6 +112,7 @@ def test_main_short_form_args(monkeypatch): config = execute.call_args[1]["global_config"] assert config.noxfile == "noxfile.py" assert config.sessions == ["1", "2"] + assert config.venv_backend == "venv" assert config.reuse_existing_virtualenvs is True From 9bd7e2acf93aa9f9e189e952808b2a83b6ac4d60 Mon Sep 17 00:00:00 2001 From: Sylvain MARIE Date: Wed, 22 Apr 2020 10:44:55 +0200 Subject: [PATCH 09/28] Replaced the venv_backend completer with a simple `choices` from argparse :) --- nox/_options.py | 8 +------- tests/test__option_set.py | 14 -------------- 2 files changed, 1 insertion(+), 21 deletions(-) diff --git a/nox/_options.py b/nox/_options.py index 39a2fb30..08af2d20 100644 --- a/nox/_options.py +++ b/nox/_options.py @@ -159,12 +159,6 @@ def _session_completer( ] -def _venv_backend_completer( - prefix: str, parsed_args: argparse.Namespace, **kwargs: Any -) -> List[str]: - return ["virtualenv", "conda", "venv"] - - options.add_options( _option_set.Option( "help", @@ -250,7 +244,7 @@ def _venv_backend_completer( merge_func=_venv_backend_merge_func, help="Virtual environment backend to use by default for nox sessions, this is ``'virtualenv'`` by default but " "any of ``('virtualenv', 'conda', 'venv')`` are accepted.", - completer=_venv_backend_completer, + choices=["virtualenv", "conda", "venv"] ), *_option_set.make_flag_pair( "reuse_existing_virtualenvs", diff --git a/tests/test__option_set.py b/tests/test__option_set.py index e99792bd..3ad8b593 100644 --- a/tests/test__option_set.py +++ b/tests/test__option_set.py @@ -70,17 +70,3 @@ def test_session_completer_invalid_sessions(self): prefix=None, parsed_args=parsed_args ) assert len(all_nox_sessions) == 0 - - def test_venv_backend_completer(self): - parsed_args = _options.options.namespace(venv_backend=()) - all_nox_venv_backends = _options._venv_backend_completer( - prefix=None, parsed_args=parsed_args - ) - assert set(all_nox_venv_backends) == {'venv', 'conda', 'virtualenv'} - - def test_venv_backend_completer_invalid_venv_backends(self): - parsed_args = _options.options.namespace(venv_backend=("baz",)) - all_nox_venv_backends = _options._venv_backend_completer( - prefix=None, parsed_args=parsed_args - ) - assert len(all_nox_venv_backends) == 0 From b9ad1caacb669a8e4f0c34499efab9e748231341 Mon Sep 17 00:00:00 2001 From: Sylvain MARIE Date: Wed, 22 Apr 2020 11:06:45 +0200 Subject: [PATCH 10/28] Renamed venv_backend to default_venv_backend, and created new force_venv_backend --- nox/_options.py | 40 +++++++++++++++++++++++++++++++++------- nox/sessions.py | 2 +- tests/test_main.py | 12 ++++++++---- 3 files changed, 42 insertions(+), 12 deletions(-) diff --git a/nox/_options.py b/nox/_options.py index 08af2d20..ff1d1b41 100644 --- a/nox/_options.py +++ b/nox/_options.py @@ -60,10 +60,10 @@ def _session_filters_merge_func( return getattr(command_args, key) -def _venv_backend_merge_func( +def _default_venv_backend_merge_func( command_args: argparse.Namespace, noxfile_args: argparse.Namespace ) -> str: - """Ensure that there is always some venv_backend. + """Merge default_venv_backend from command args and nox file. Default is "virtualenv". Args: command_args (_option_set.Namespace): The options specified on the @@ -71,7 +71,21 @@ def _venv_backend_merge_func( noxfile_Args (_option_set.Namespace): The options specified in the Noxfile. """ - return command_args.venv_backend or noxfile_args.venv_backend or "virtualenv" + return command_args.default_venv_backend or noxfile_args.default_venv_backend or "virtualenv" + + +def _force_venv_backend_merge_func( + command_args: argparse.Namespace, noxfile_args: argparse.Namespace +) -> str: + """Merge force_venv_backend from command args and nox file. Default is None. + + Args: + command_args (_option_set.Namespace): The options specified on the + command-line. + noxfile_Args (_option_set.Namespace): The options specified in the + Noxfile. + """ + return command_args.force_venv_backend or noxfile_args.force_venv_backend def _envdir_merge_func( @@ -236,16 +250,28 @@ def _session_completer( noxfile=True, ), _option_set.Option( - "venv_backend", - "-vb", - "--venv_backend", + "default_venv_backend", + "-db", + "--default-venv-backend", group=options.groups["secondary"], noxfile=True, - merge_func=_venv_backend_merge_func, + merge_func=_default_venv_backend_merge_func, help="Virtual environment backend to use by default for nox sessions, this is ``'virtualenv'`` by default but " "any of ``('virtualenv', 'conda', 'venv')`` are accepted.", choices=["virtualenv", "conda", "venv"] ), + _option_set.Option( + "force_venv_backend", + "-fb", + "--force-venv-backend", + group=options.groups["secondary"], + noxfile=True, + merge_func=_force_venv_backend_merge_func, + help="Virtual environment backend to force-use for all nox sessions in this run, overriding any other venv " + "backend declared in the nox file and ignoring the default backend. Any of ``('virtualenv', 'conda', 'venv')`` " + "are accepted.", + choices=["virtualenv", "conda", "venv"] + ), *_option_set.make_flag_pair( "reuse_existing_virtualenvs", ("-r", "--reuse-existing-virtualenvs"), diff --git a/nox/sessions.py b/nox/sessions.py index 3434b877..58d5bc85 100644 --- a/nox/sessions.py +++ b/nox/sessions.py @@ -398,7 +398,7 @@ def _create_venv(self) -> None: self.func.reuse_venv or self.global_config.reuse_existing_virtualenvs ) - backend = self.func.venv_backend or self.global_config.venv_backend + backend = self.global_config.force_venv_backend or self.func.venv_backend or self.global_config.default_venv_backend if backend is None or backend == "virtualenv": self.venv = VirtualEnv( path, diff --git a/tests/test_main.py b/tests/test_main.py index d1693efb..ea1a8c77 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -70,8 +70,10 @@ def test_main_long_form_args(): "--sessions", "1", "2", - "--venv_backend", + "--default-venv-backend", "venv", + "--force-venv-backend", + "conda", "--reuse-existing-virtualenvs", "--stop-on-first-error", ] @@ -89,7 +91,8 @@ def test_main_long_form_args(): assert config.noxfile == "noxfile.py" assert config.envdir.endswith(".other") assert config.sessions == ["1", "2"] - assert config.venv_backend == "venv" + assert config.default_venv_backend == "venv" + assert config.force_venv_backend == "conda" assert config.reuse_existing_virtualenvs is True assert config.stop_on_first_error is True assert config.posargs == [] @@ -97,7 +100,7 @@ def test_main_long_form_args(): def test_main_short_form_args(monkeypatch): monkeypatch.setattr( - sys, "argv", [sys.executable, "-f", "noxfile.py", "-s", "1", "2", "-vb", "venv", "-r"] + sys, "argv", [sys.executable, "-f", "noxfile.py", "-s", "1", "2", "-db", "venv", "-fb", "conda", "-r"] ) with mock.patch("nox.workflow.execute") as execute: execute.return_value = 0 @@ -112,7 +115,8 @@ def test_main_short_form_args(monkeypatch): config = execute.call_args[1]["global_config"] assert config.noxfile == "noxfile.py" assert config.sessions == ["1", "2"] - assert config.venv_backend == "venv" + assert config.default_venv_backend == "venv" + assert config.force_venv_backend == "conda" assert config.reuse_existing_virtualenvs is True From a1f854720ba45cbd0ac3244492c4ad6015860ca5 Mon Sep 17 00:00:00 2001 From: Sylvain MARIE Date: Wed, 22 Apr 2020 11:19:29 +0200 Subject: [PATCH 11/28] New "none" choice for venv_backends, equivalent to python=False --- nox/_options.py | 4 ++-- nox/manifest.py | 7 +++++++ nox/sessions.py | 5 +++-- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/nox/_options.py b/nox/_options.py index ff1d1b41..3717a9ea 100644 --- a/nox/_options.py +++ b/nox/_options.py @@ -258,7 +258,7 @@ def _session_completer( merge_func=_default_venv_backend_merge_func, help="Virtual environment backend to use by default for nox sessions, this is ``'virtualenv'`` by default but " "any of ``('virtualenv', 'conda', 'venv')`` are accepted.", - choices=["virtualenv", "conda", "venv"] + choices=["none", "virtualenv", "conda", "venv"] ), _option_set.Option( "force_venv_backend", @@ -270,7 +270,7 @@ def _session_completer( help="Virtual environment backend to force-use for all nox sessions in this run, overriding any other venv " "backend declared in the nox file and ignoring the default backend. Any of ``('virtualenv', 'conda', 'venv')`` " "are accepted.", - choices=["virtualenv", "conda", "venv"] + choices=["none", "virtualenv", "conda", "venv"] ), *_option_set.make_flag_pair( "reuse_existing_virtualenvs", diff --git a/nox/manifest.py b/nox/manifest.py index d90ba346..af49a83f 100644 --- a/nox/manifest.py +++ b/nox/manifest.py @@ -18,6 +18,7 @@ from typing import Any, Iterable, Iterator, List, Mapping, Sequence, Set, Tuple, Union from nox._decorators import Call, Func +from nox.logger import logger from nox.sessions import Session, SessionRunner @@ -170,6 +171,12 @@ def make_session( """ sessions = [] + # if backend is none we wont parametrize the pythons + backend = self._config.force_venv_backend or func.venv_backend or self._config.default_venv_backend + if backend == "none" and isinstance(func.python, (list, tuple, set)): + logger.warning("Running session '{}' with venv_backend='none', ignoring python={}".format(name, func.python)) + func.python = False + # If the func has the python attribute set to a list, we'll need # to expand them. if isinstance(func.python, (list, tuple, set)): diff --git a/nox/sessions.py b/nox/sessions.py index 58d5bc85..fbb60c2a 100644 --- a/nox/sessions.py +++ b/nox/sessions.py @@ -389,7 +389,9 @@ def friendly_name(self) -> str: return self.signatures[0] if self.signatures else self.name def _create_venv(self) -> None: - if self.func.python is False: + backend = self.global_config.force_venv_backend or self.func.venv_backend or self.global_config.default_venv_backend + + if backend == "none" or self.func.python is False: self.venv = ProcessEnv() return @@ -398,7 +400,6 @@ def _create_venv(self) -> None: self.func.reuse_venv or self.global_config.reuse_existing_virtualenvs ) - backend = self.global_config.force_venv_backend or self.func.venv_backend or self.global_config.default_venv_backend if backend is None or backend == "virtualenv": self.venv = VirtualEnv( path, From f3939028de6fbd327e2e15450ed8d0be6e15c0d0 Mon Sep 17 00:00:00 2001 From: Sylvain MARIE Date: Wed, 22 Apr 2020 11:30:56 +0200 Subject: [PATCH 12/28] Updated doc concerning default_venv_backend and force_venv_backend, as well as the new 'none' backend --- docs/config.rst | 5 +++-- docs/usage.rst | 24 ++++++++++++++++++++---- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/docs/config.rst b/docs/config.rst index a1507356..d04b8a50 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -132,7 +132,7 @@ Will produce these sessions: Note that this expansion happens *before* parameterization occurs, so you can still parametrize sessions with multiple interpreters. -If you want to disable virtualenv creation altogether, you can set ``python`` to ``False``: +If you want to disable virtualenv creation altogether, you can set ``python`` to ``False``, or set `venv_backend` to ``"none"``, both are equivalent. .. code-block:: python @@ -375,7 +375,8 @@ The following options can be specified in the Noxfile: * ``nox.options.sessions`` is equivalent to specifying :ref:`-s or --sessions `. * ``nox.options.pythons`` is equivalent to specifying :ref:`-p or --pythons `. * ``nox.options.keywords`` is equivalent to specifying :ref:`-k or --keywords `. -* ``nox.options.venv_backend`` is equivalent to specifying :ref:`-vb or --venv_backend `. +* ``nox.options.default_venv_backend`` is equivalent to specifying :ref:`-db or --default-venv-backend `. +* ``nox.options.force_venv_backend`` is equivalent to specifying :ref:`-fb or --force-venv-backend `. * ``nox.options.reuse_existing_virtualenvs`` is equivalent to specifying :ref:`--reuse-existing-virtualenvs `. You can force this off by specifying ``--no-reuse-existing-virtualenvs`` during invocation. * ``nox.options.stop_on_first_error`` is equivalent to specifying :ref:`--stop-on-first-error `. You can force this off by specifying ``--no-stop-on-first-error`` during invocation. * ``nox.options.error_on_missing_interpreters`` is equivalent to specifying :ref:`--error-on-missing-interpreters `. You can force this off by specifying ``--no-error-on-missing-interpreters`` during invocation. diff --git a/docs/usage.rst b/docs/usage.rst index f203f120..135e0a14 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -110,19 +110,35 @@ Then running ``nox --session tests`` will actually run all parametrized versions Changing the sessions default backend ------------------------------------- -By default nox uses ``virtualenv`` as the virtual environment backend for the sessions, but it also supports ``conda`` and ``venv``. You can change the default behaviour by using ``-vb `` or ``--venv_backend ``. Supported names are ``('virtualenv', 'conda', 'venv')``. +By default nox uses ``virtualenv`` as the virtual environment backend for the sessions, but it also supports ``conda`` and ``venv`` as well as no backend (passthrough to whatever python environment nox is running on). You can change the default behaviour by using ``-db `` or ``--default-venv-backend ``. Supported names are ``('none', 'virtualenv', 'conda', 'venv')``. .. code-block:: console - nox -vb conda - nox --venv_backend conda + nox -db conda + nox --default-venv-backend conda -You can also set this option in the Noxfile with ``nox.options.venv_backend``. In case both are provided, the commandline argument takes precedence. +You can also set this option in the Noxfile with ``nox.options.default_venv_backend``. In case both are provided, the commandline argument takes precedence. Note that using this option does not change the backend for sessions where ``venv_backend`` is explicitly set. +.. _opt-force-venv-backend: + +Forcing the sessions backend +---------------------------- + +You might work in a different environment than a project's default continuous integration setttings, and might wish to get a quick way to execute the same tasks but on a different venv backend. For this purpose, you can temporarily force the backend used by **all** sessions in the current nox execution by using ``-fb `` or ``--force-venv-backend ``. No exceptions are made, the backend will be forced for all sessions run whatever the other options values and nox file configuration. Supported names are ``('none', 'virtualenv', 'conda', 'venv')``. + +.. code-block:: console + + nox -fb conda + nox --force-venv-backend conda + + +You can also set this option in the Noxfile with ``nox.options.force_venv_backend``. In case both are provided, the commandline argument takes precedence. + + .. _opt-reuse-existing-virtualenvs: Re-using virtualenvs From 87326030fe187f74c45d5e765bb230f674f714c3 Mon Sep 17 00:00:00 2001 From: Sylvain MARIE Date: Wed, 22 Apr 2020 11:45:28 +0200 Subject: [PATCH 13/28] Fixed all manifest tests --- tests/test_manifest.py | 59 +++++++++++++++++++++++++----------------- 1 file changed, 35 insertions(+), 24 deletions(-) diff --git a/tests/test_manifest.py b/tests/test_manifest.py index 9864191c..0f18109b 100644 --- a/tests/test_manifest.py +++ b/tests/test_manifest.py @@ -23,14 +23,21 @@ def create_mock_sessions(): sessions = collections.OrderedDict() - sessions["foo"] = mock.Mock(spec=(), python=None) - sessions["bar"] = mock.Mock(spec=(), python=None) + sessions["foo"] = mock.Mock(spec=(), python=None, venv_backend=None) + sessions["bar"] = mock.Mock(spec=(), python=None, venv_backend=None) return sessions +def create_mock_config(): + cfg = mock.sentinel.CONFIG + cfg.force_venv_backend = None + cfg.default_venv_backend = None + return cfg + + def test_init(): sessions = create_mock_sessions() - manifest = Manifest(sessions, mock.sentinel.CONFIG) + manifest = Manifest(sessions, create_mock_config()) # Assert that basic properties look correctly. assert len(manifest) == 2 @@ -40,7 +47,7 @@ def test_init(): def test_contains(): sessions = create_mock_sessions() - manifest = Manifest(sessions, mock.sentinel.CONFIG) + manifest = Manifest(sessions, create_mock_config()) # Establish that contains works pre-iteration. assert "foo" in manifest @@ -60,7 +67,7 @@ def test_contains(): def test_getitem(): sessions = create_mock_sessions() - manifest = Manifest(sessions, mock.sentinel.CONFIG) + manifest = Manifest(sessions, create_mock_config()) # Establish that each session is present, and a made-up session # is not. @@ -79,7 +86,7 @@ def test_getitem(): def test_iteration(): sessions = create_mock_sessions() - manifest = Manifest(sessions, mock.sentinel.CONFIG) + manifest = Manifest(sessions, create_mock_config()) # There should be two sessions in the queue. assert len(manifest._queue) == 2 @@ -109,7 +116,7 @@ def test_iteration(): def test_len(): sessions = create_mock_sessions() - manifest = Manifest(sessions, mock.sentinel.CONFIG) + manifest = Manifest(sessions, create_mock_config()) assert len(manifest) == 2 for session in manifest: assert len(manifest) == 2 @@ -117,7 +124,7 @@ def test_len(): def test_filter_by_name(): sessions = create_mock_sessions() - manifest = Manifest(sessions, mock.sentinel.CONFIG) + manifest = Manifest(sessions, create_mock_config()) manifest.filter_by_name(("foo",)) assert "foo" in manifest assert "bar" not in manifest @@ -125,21 +132,21 @@ def test_filter_by_name(): def test_filter_by_name_maintains_order(): sessions = create_mock_sessions() - manifest = Manifest(sessions, mock.sentinel.CONFIG) + manifest = Manifest(sessions, create_mock_config()) manifest.filter_by_name(("bar", "foo")) assert [session.name for session in manifest] == ["bar", "foo"] def test_filter_by_name_not_found(): sessions = create_mock_sessions() - manifest = Manifest(sessions, mock.sentinel.CONFIG) + manifest = Manifest(sessions, create_mock_config()) with pytest.raises(KeyError): manifest.filter_by_name(("baz",)) def test_filter_by_python_interpreter(): sessions = create_mock_sessions() - manifest = Manifest(sessions, mock.sentinel.CONFIG) + manifest = Manifest(sessions, create_mock_config()) manifest["foo"].func.python = "3.8" manifest["bar"].func.python = "3.7" manifest.filter_by_python_interpreter(("3.8",)) @@ -149,7 +156,7 @@ def test_filter_by_python_interpreter(): def test_filter_by_keyword(): sessions = create_mock_sessions() - manifest = Manifest(sessions, mock.sentinel.CONFIG) + manifest = Manifest(sessions, create_mock_config()) assert len(manifest) == 2 manifest.filter_by_keywords("foo or bar") assert len(manifest) == 2 @@ -159,7 +166,7 @@ def test_filter_by_keyword(): def test_list_all_sessions_with_filter(): sessions = create_mock_sessions() - manifest = Manifest(sessions, mock.sentinel.CONFIG) + manifest = Manifest(sessions, create_mock_config()) assert len(manifest) == 2 manifest.filter_by_keywords("foo") assert len(manifest) == 1 @@ -171,15 +178,15 @@ def test_list_all_sessions_with_filter(): def test_add_session_plain(): - manifest = Manifest({}, mock.sentinel.CONFIG) - session_func = mock.Mock(spec=(), python=None) + manifest = Manifest({}, create_mock_config()) + session_func = mock.Mock(spec=(), python=None, venv_backend=None) for session in manifest.make_session("my_session", session_func): manifest.add_session(session) assert len(manifest) == 1 def test_add_session_multiple_pythons(): - manifest = Manifest({}, mock.sentinel.CONFIG) + manifest = Manifest({}, create_mock_config()) def session_func(): pass @@ -192,7 +199,7 @@ def session_func(): def test_add_session_parametrized(): - manifest = Manifest({}, mock.sentinel.CONFIG) + manifest = Manifest({}, create_mock_config()) # Define a session with parameters. @nox.parametrize("param", ("a", "b", "c")) @@ -208,7 +215,7 @@ def my_session(session, param): def test_add_session_parametrized_multiple_pythons(): - manifest = Manifest({}, mock.sentinel.CONFIG) + manifest = Manifest({}, create_mock_config()) # Define a session with parameters. @nox.parametrize("param", ("a", "b")) @@ -224,7 +231,7 @@ def my_session(session, param): def test_add_session_parametrized_noop(): - manifest = Manifest({}, mock.sentinel.CONFIG) + manifest = Manifest({}, create_mock_config()) # Define a session without any parameters. @nox.parametrize("param", ()) @@ -232,6 +239,7 @@ def my_session(session, param): pass my_session.python = None + my_session.venv_backend = None # Add the session to the manifest. for session in manifest.make_session("my_session", my_session): @@ -244,18 +252,20 @@ def my_session(session, param): def test_notify(): - manifest = Manifest({}, mock.sentinel.CONFIG) + manifest = Manifest({}, create_mock_config()) # Define a session. def my_session(session): pass my_session.python = None + my_session.venv_backend = None def notified(session): pass notified.python = None + notified.venv_backend = None # Add the sessions to the manifest. for session in manifest.make_session("my_session", my_session): @@ -274,13 +284,14 @@ def notified(session): def test_notify_noop(): - manifest = Manifest({}, mock.sentinel.CONFIG) + manifest = Manifest({}, create_mock_config()) # Define a session and add it to the manifest. def my_session(session): pass my_session.python = None + my_session.venv_backend = None for session in manifest.make_session("my_session", my_session): manifest.add_session(session) @@ -293,14 +304,14 @@ def my_session(session): def test_notify_error(): - manifest = Manifest({}, mock.sentinel.CONFIG) + manifest = Manifest({}, create_mock_config()) with pytest.raises(ValueError): manifest.notify("does_not_exist") def test_add_session_idempotent(): - manifest = Manifest({}, mock.sentinel.CONFIG) - session_func = mock.Mock(spec=(), python=None) + manifest = Manifest({}, create_mock_config()) + session_func = mock.Mock(spec=(), python=None, venv_backend=None) for session in manifest.make_session("my_session", session_func): manifest.add_session(session) manifest.add_session(session) From 61cc44392bc4f13c4d6813cec78a6efbcf9d01a9 Mon Sep 17 00:00:00 2001 From: Sylvain MARIE Date: Wed, 22 Apr 2020 11:47:09 +0200 Subject: [PATCH 14/28] Fixed test_tasks for venv_backend --- tests/test_tasks.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test_tasks.py b/tests/test_tasks.py index e9143136..79fdbe19 100644 --- a/tests/test_tasks.py +++ b/tests/test_tasks.py @@ -33,6 +33,7 @@ def session_func(): session_func.python = None +session_func.venv_backend = None def session_func_with_python(): @@ -40,6 +41,7 @@ def session_func_with_python(): session_func_with_python.python = "3.8" +session_func_with_python.venv_backend = None def test_load_nox_module(): From 147969a0f21111caa3d7d73ab1d4562a97888ecf Mon Sep 17 00:00:00 2001 From: Sylvain MARIE Date: Wed, 22 Apr 2020 11:57:03 +0200 Subject: [PATCH 15/28] Fixed coverage --- tests/test_manifest.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/test_manifest.py b/tests/test_manifest.py index 0f18109b..3defee85 100644 --- a/tests/test_manifest.py +++ b/tests/test_manifest.py @@ -333,3 +333,20 @@ def test_keyword_locals_iter(): values = ["foo", "bar"] kw = KeywordLocals(values) assert list(kw) == values + + +def test_no_venv_backend_but_some_pythons(): + manifest = Manifest({}, create_mock_config()) + + # Define a session and add it to the manifest. + def my_session(session): + pass + + # the session sets "no venv backend" but declares some pythons + my_session.python = ["3.7", "3.8"] + my_session.venv_backend = "none" + + sessions = manifest.make_session("my_session", my_session) + + # check that the pythons were correctly removed (a log warning is also emitted) + assert sessions[0].func.python is False From e657e4f307093af39219203420fd4b027a8c3124 Mon Sep 17 00:00:00 2001 From: Sylvain MARIE Date: Wed, 22 Apr 2020 11:57:57 +0200 Subject: [PATCH 16/28] Blackened code --- nox/_options.py | 10 +++++++--- nox/manifest.py | 12 ++++++++++-- nox/sessions.py | 6 +++++- tests/test_main.py | 16 +++++++++++++++- 4 files changed, 37 insertions(+), 7 deletions(-) diff --git a/nox/_options.py b/nox/_options.py index 3717a9ea..1e6d1673 100644 --- a/nox/_options.py +++ b/nox/_options.py @@ -71,7 +71,11 @@ def _default_venv_backend_merge_func( noxfile_Args (_option_set.Namespace): The options specified in the Noxfile. """ - return command_args.default_venv_backend or noxfile_args.default_venv_backend or "virtualenv" + return ( + command_args.default_venv_backend + or noxfile_args.default_venv_backend + or "virtualenv" + ) def _force_venv_backend_merge_func( @@ -258,7 +262,7 @@ def _session_completer( merge_func=_default_venv_backend_merge_func, help="Virtual environment backend to use by default for nox sessions, this is ``'virtualenv'`` by default but " "any of ``('virtualenv', 'conda', 'venv')`` are accepted.", - choices=["none", "virtualenv", "conda", "venv"] + choices=["none", "virtualenv", "conda", "venv"], ), _option_set.Option( "force_venv_backend", @@ -270,7 +274,7 @@ def _session_completer( help="Virtual environment backend to force-use for all nox sessions in this run, overriding any other venv " "backend declared in the nox file and ignoring the default backend. Any of ``('virtualenv', 'conda', 'venv')`` " "are accepted.", - choices=["none", "virtualenv", "conda", "venv"] + choices=["none", "virtualenv", "conda", "venv"], ), *_option_set.make_flag_pair( "reuse_existing_virtualenvs", diff --git a/nox/manifest.py b/nox/manifest.py index af49a83f..9de736f6 100644 --- a/nox/manifest.py +++ b/nox/manifest.py @@ -172,9 +172,17 @@ def make_session( sessions = [] # if backend is none we wont parametrize the pythons - backend = self._config.force_venv_backend or func.venv_backend or self._config.default_venv_backend + backend = ( + self._config.force_venv_backend + or func.venv_backend + or self._config.default_venv_backend + ) if backend == "none" and isinstance(func.python, (list, tuple, set)): - logger.warning("Running session '{}' with venv_backend='none', ignoring python={}".format(name, func.python)) + logger.warning( + "Running session '{}' with venv_backend='none', ignoring python={}".format( + name, func.python + ) + ) func.python = False # If the func has the python attribute set to a list, we'll need diff --git a/nox/sessions.py b/nox/sessions.py index fbb60c2a..3ff42f1c 100644 --- a/nox/sessions.py +++ b/nox/sessions.py @@ -389,7 +389,11 @@ def friendly_name(self) -> str: return self.signatures[0] if self.signatures else self.name def _create_venv(self) -> None: - backend = self.global_config.force_venv_backend or self.func.venv_backend or self.global_config.default_venv_backend + backend = ( + self.global_config.force_venv_backend + or self.func.venv_backend + or self.global_config.default_venv_backend + ) if backend == "none" or self.func.python is False: self.venv = ProcessEnv() diff --git a/tests/test_main.py b/tests/test_main.py index ea1a8c77..2394391a 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -100,7 +100,21 @@ def test_main_long_form_args(): def test_main_short_form_args(monkeypatch): monkeypatch.setattr( - sys, "argv", [sys.executable, "-f", "noxfile.py", "-s", "1", "2", "-db", "venv", "-fb", "conda", "-r"] + sys, + "argv", + [ + sys.executable, + "-f", + "noxfile.py", + "-s", + "1", + "2", + "-db", + "venv", + "-fb", + "conda", + "-r", + ], ) with mock.patch("nox.workflow.execute") as execute: execute.return_value = 0 From e116facce55297fb22165f64906b03f1d0f69ce2 Mon Sep 17 00:00:00 2001 From: Sylvain MARIE Date: Wed, 22 Apr 2020 16:24:58 +0200 Subject: [PATCH 17/28] The warning message was appearing for all sessions, even those deselected. It is now only logged when session is run. --- nox/_decorators.py | 4 +++- nox/manifest.py | 12 ++++++------ nox/tasks.py | 10 +++++++++- 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/nox/_decorators.py b/nox/_decorators.py index 32773f83..79adf43f 100644 --- a/nox/_decorators.py +++ b/nox/_decorators.py @@ -1,7 +1,7 @@ import copy import functools import types -from typing import Any, Callable, Iterable, List, Optional, cast +from typing import Any, Callable, Iterable, List, Dict, Optional, cast from . import _typing @@ -40,12 +40,14 @@ def __init__( name: Optional[str] = None, venv_backend: Any = None, venv_params: Any = None, + should_warn: Dict[str, Any] = None, ): self.func = func self.python = python self.reuse_venv = reuse_venv self.venv_backend = venv_backend self.venv_params = venv_params + self.should_warn = should_warn or dict() def __call__(self, *args: Any, **kwargs: Any) -> Any: return self.func(*args, **kwargs) diff --git a/nox/manifest.py b/nox/manifest.py index 9de736f6..f2a008f0 100644 --- a/nox/manifest.py +++ b/nox/manifest.py @@ -18,10 +18,12 @@ from typing import Any, Iterable, Iterator, List, Mapping, Sequence, Set, Tuple, Union from nox._decorators import Call, Func -from nox.logger import logger from nox.sessions import Session, SessionRunner +WARN_PYTHONS_IGNORED = "python_ignored" + + class Manifest: """Session manifest. @@ -178,11 +180,9 @@ def make_session( or self._config.default_venv_backend ) if backend == "none" and isinstance(func.python, (list, tuple, set)): - logger.warning( - "Running session '{}' with venv_backend='none', ignoring python={}".format( - name, func.python - ) - ) + # we can not log a warning here since the session is maybe deselected. + # instead let's set a flag, to warn later when session is actually run. + func.should_warn[WARN_PYTHONS_IGNORED] = func.python func.python = False # If the func has the python attribute set to a list, we'll need diff --git a/nox/tasks.py b/nox/tasks.py index 3cbbf3ef..8d0c7794 100644 --- a/nox/tasks.py +++ b/nox/tasks.py @@ -24,7 +24,7 @@ from colorlog.escape_codes import parse_colors from nox import _options, registry from nox.logger import logger -from nox.manifest import Manifest +from nox.manifest import Manifest, WARN_PYTHONS_IGNORED from nox.sessions import Result @@ -233,6 +233,14 @@ def run_manifest(manifest: Manifest, global_config: Namespace) -> List[Result]: # Note that it is possible for the manifest to be altered in any given # iteration. for session in manifest: + # possibly raise warnings associated with this session + if WARN_PYTHONS_IGNORED in session.func.should_warn: + logger.warning( + "Session {} is set to run with venv_backend='none', IGNORING its python={} parametrization. ".format( + session.name, session.func.should_warn['python_ignored'] + ) + ) + result = session.execute() result.log( "Session {name} {status}.".format( From 821c2d905dfb78cb9f1017b42c950422b3c5d2e0 Mon Sep 17 00:00:00 2001 From: Sylvain MARIE Date: Wed, 22 Apr 2020 16:32:53 +0200 Subject: [PATCH 18/28] Added `--no-venv` option. Fixes #301 --- docs/config.rst | 2 +- docs/usage.rst | 5 +++++ nox/_options.py | 17 ++++++++++++++++- tests/test_main.py | 21 +++++++++++++++++++-- 4 files changed, 41 insertions(+), 4 deletions(-) diff --git a/docs/config.rst b/docs/config.rst index d04b8a50..39628b08 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -132,7 +132,7 @@ Will produce these sessions: Note that this expansion happens *before* parameterization occurs, so you can still parametrize sessions with multiple interpreters. -If you want to disable virtualenv creation altogether, you can set ``python`` to ``False``, or set `venv_backend` to ``"none"``, both are equivalent. +If you want to disable virtualenv creation altogether, you can set ``python`` to ``False``, or set ``venv_backend`` to ``"none"``, both are equivalent. Note that this can be done temporarily through the :ref:`--no-venv ` commandline flag, too. .. code-block:: python diff --git a/docs/usage.rst b/docs/usage.rst index 135e0a14..3ac6445a 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -138,6 +138,11 @@ You might work in a different environment than a project's default continuous in You can also set this option in the Noxfile with ``nox.options.force_venv_backend``. In case both are provided, the commandline argument takes precedence. +Finally note that the ``--no-venv`` flag is a shortcut for ``--force-venv-backend none`` and allows to temporarily run all selected sessions on the current python interpreter (the one running nox). + +.. code-block:: console + + nox --no-venv .. _opt-reuse-existing-virtualenvs: diff --git a/nox/_options.py b/nox/_options.py index 1e6d1673..fa9d5108 100644 --- a/nox/_options.py +++ b/nox/_options.py @@ -89,7 +89,13 @@ def _force_venv_backend_merge_func( noxfile_Args (_option_set.Namespace): The options specified in the Noxfile. """ - return command_args.force_venv_backend or noxfile_args.force_venv_backend + if command_args.no_venv: + if command_args.force_venv_backend is not None and command_args.force_venv_backend != "none": + raise ValueError("You can not use `--no-venv` with a non-none `--force-venv-backend`") + else: + return "none" + else: + return command_args.force_venv_backend or noxfile_args.force_venv_backend def _envdir_merge_func( @@ -276,6 +282,15 @@ def _session_completer( "are accepted.", choices=["none", "virtualenv", "conda", "venv"], ), + _option_set.Option( + "no_venv", + "--no-venv", + group=options.groups["secondary"], + default=False, + action="store_true", + help="Runs the selected sessions directly on the current interpreter, without creating a venv. This is an alias " + "for '--force-venv-backend none'.", + ), *_option_set.make_flag_pair( "reuse_existing_virtualenvs", ("-r", "--reuse-existing-virtualenvs"), diff --git a/tests/test_main.py b/tests/test_main.py index 2394391a..bbf58cf3 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -55,6 +55,7 @@ def test_main_no_args(monkeypatch): config = execute.call_args[1]["global_config"] assert config.noxfile == "noxfile.py" assert config.sessions is None + assert not config.no_venv assert not config.reuse_existing_virtualenvs assert not config.stop_on_first_error assert config.posargs == [] @@ -73,7 +74,8 @@ def test_main_long_form_args(): "--default-venv-backend", "venv", "--force-venv-backend", - "conda", + "none", + "--no-venv", "--reuse-existing-virtualenvs", "--stop-on-first-error", ] @@ -92,12 +94,27 @@ def test_main_long_form_args(): assert config.envdir.endswith(".other") assert config.sessions == ["1", "2"] assert config.default_venv_backend == "venv" - assert config.force_venv_backend == "conda" + assert config.force_venv_backend == "none" + assert config.no_venv is True assert config.reuse_existing_virtualenvs is True assert config.stop_on_first_error is True assert config.posargs == [] +def test_main_no_venv_error(): + # Check that -no-venv can not be set together with a non-none --force-venv-backend + sys.argv = [ + sys.executable, + "--noxfile", + "noxfile.py", + "--force-venv-backend", + "conda", + "--no-venv", + ] + with pytest.raises(ValueError, match="You can not use"): + nox.__main__.main() + + def test_main_short_form_args(monkeypatch): monkeypatch.setattr( sys, From 6ca6fa6f17debaf1a3f43b3c0070bddfe3dc5bf0 Mon Sep 17 00:00:00 2001 From: Sylvain MARIE Date: Wed, 22 Apr 2020 16:34:07 +0200 Subject: [PATCH 19/28] Blackened --- nox/_options.py | 9 +++++++-- nox/tasks.py | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/nox/_options.py b/nox/_options.py index fa9d5108..a46943a5 100644 --- a/nox/_options.py +++ b/nox/_options.py @@ -90,8 +90,13 @@ def _force_venv_backend_merge_func( Noxfile. """ if command_args.no_venv: - if command_args.force_venv_backend is not None and command_args.force_venv_backend != "none": - raise ValueError("You can not use `--no-venv` with a non-none `--force-venv-backend`") + if ( + command_args.force_venv_backend is not None + and command_args.force_venv_backend != "none" + ): + raise ValueError( + "You can not use `--no-venv` with a non-none `--force-venv-backend`" + ) else: return "none" else: diff --git a/nox/tasks.py b/nox/tasks.py index 8d0c7794..bd5e1c37 100644 --- a/nox/tasks.py +++ b/nox/tasks.py @@ -237,7 +237,7 @@ def run_manifest(manifest: Manifest, global_config: Namespace) -> List[Result]: if WARN_PYTHONS_IGNORED in session.func.should_warn: logger.warning( "Session {} is set to run with venv_backend='none', IGNORING its python={} parametrization. ".format( - session.name, session.func.should_warn['python_ignored'] + session.name, session.func.should_warn[WARN_PYTHONS_IGNORED] ) ) From fde8f01a3b5442323b626d39851bd37a2841a7b7 Mon Sep 17 00:00:00 2001 From: Sylvain MARIE Date: Wed, 22 Apr 2020 16:49:25 +0200 Subject: [PATCH 20/28] Fixed tests --- tests/test_manifest.py | 1 + tests/test_tasks.py | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/tests/test_manifest.py b/tests/test_manifest.py index 3defee85..2943386e 100644 --- a/tests/test_manifest.py +++ b/tests/test_manifest.py @@ -345,6 +345,7 @@ def my_session(session): # the session sets "no venv backend" but declares some pythons my_session.python = ["3.7", "3.8"] my_session.venv_backend = "none" + my_session.should_warn = dict() sessions = manifest.make_session("my_session", my_session) diff --git a/tests/test_tasks.py b/tests/test_tasks.py index 79fdbe19..f0b1f09e 100644 --- a/tests/test_tasks.py +++ b/tests/test_tasks.py @@ -34,6 +34,7 @@ def session_func(): session_func.python = None session_func.venv_backend = None +session_func.should_warn = dict() def session_func_with_python(): @@ -202,6 +203,8 @@ def test_run_manifest(): mock_session.execute.return_value = sessions.Result( session=mock_session, status=sessions.Status.SUCCESS ) + # we need the should_warn attribute, add some func + mock_session.func = session_func # Run the manifest. results = tasks.run_manifest(manifest, global_config=config) @@ -230,6 +233,8 @@ def test_run_manifest_abort_on_first_failure(): mock_session.execute.return_value = sessions.Result( session=mock_session, status=sessions.Status.FAILED ) + # we need the should_warn attribute, add some func + mock_session.func = session_func # Run the manifest. results = tasks.run_manifest(manifest, global_config=config) From 2e808c2b37a266edee5cf75f610ff9131ab5abe8 Mon Sep 17 00:00:00 2001 From: Sylvain MARIE Date: Wed, 22 Apr 2020 17:10:31 +0200 Subject: [PATCH 21/28] Improved coverage --- tests/test_manifest.py | 3 ++- tests/test_tasks.py | 20 +++++++++++++++++--- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/tests/test_manifest.py b/tests/test_manifest.py index 2943386e..47997071 100644 --- a/tests/test_manifest.py +++ b/tests/test_manifest.py @@ -18,7 +18,7 @@ import nox import pytest from nox._decorators import Func -from nox.manifest import KeywordLocals, Manifest, _null_session_func +from nox.manifest import KeywordLocals, Manifest, _null_session_func, WARN_PYTHONS_IGNORED def create_mock_sessions(): @@ -351,3 +351,4 @@ def my_session(session): # check that the pythons were correctly removed (a log warning is also emitted) assert sessions[0].func.python is False + assert sessions[0].func.should_warn == {WARN_PYTHONS_IGNORED: ["3.7", "3.8"]} diff --git a/tests/test_tasks.py b/tests/test_tasks.py index f0b1f09e..c81490ad 100644 --- a/tests/test_tasks.py +++ b/tests/test_tasks.py @@ -23,7 +23,7 @@ import nox import pytest from nox import _options, sessions, tasks -from nox.manifest import Manifest +from nox.manifest import Manifest, WARN_PYTHONS_IGNORED RESOURCES = os.path.join(os.path.dirname(__file__), "resources") @@ -45,6 +45,15 @@ def session_func_with_python(): session_func_with_python.venv_backend = None +def session_func_venv_pythons_warning(): + pass + + +session_func_venv_pythons_warning.python = ["3.7"] +session_func_venv_pythons_warning.venv_backend = "none" +session_func_venv_pythons_warning.should_warn = {WARN_PYTHONS_IGNORED: ["3.7"]} + + def test_load_nox_module(): config = _options.options.namespace(noxfile=os.path.join(RESOURCES, "noxfile.py")) noxfile_module = tasks.load_nox_module(config) @@ -188,7 +197,8 @@ def test_verify_manifest_nonempty(): assert return_value == manifest -def test_run_manifest(): +@pytest.mark.parametrize("with_warnings", [False, True], ids="with_warnings={}".format) +def test_run_manifest(with_warnings): # Set up a valid manifest. config = _options.options.namespace(stop_on_first_error=False) sessions_ = [ @@ -204,7 +214,11 @@ def test_run_manifest(): session=mock_session, status=sessions.Status.SUCCESS ) # we need the should_warn attribute, add some func - mock_session.func = session_func + if with_warnings: + mock_session.name = "hello" + mock_session.func = session_func_venv_pythons_warning + else: + mock_session.func = session_func # Run the manifest. results = tasks.run_manifest(manifest, global_config=config) From 7d6c7c632ac705ef97f0bbf1cca6f825a2698aed Mon Sep 17 00:00:00 2001 From: Sylvain MARIE Date: Wed, 22 Apr 2020 17:10:54 +0200 Subject: [PATCH 22/28] Blackened --- tests/test_manifest.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/test_manifest.py b/tests/test_manifest.py index 47997071..491627a5 100644 --- a/tests/test_manifest.py +++ b/tests/test_manifest.py @@ -18,7 +18,12 @@ import nox import pytest from nox._decorators import Func -from nox.manifest import KeywordLocals, Manifest, _null_session_func, WARN_PYTHONS_IGNORED +from nox.manifest import ( + KeywordLocals, + Manifest, + _null_session_func, + WARN_PYTHONS_IGNORED, +) def create_mock_sessions(): From 242219df9700fcacd1d1e123c76f42ffcc38a9ad Mon Sep 17 00:00:00 2001 From: Sylvain MARIE Date: Wed, 22 Apr 2020 17:40:41 +0200 Subject: [PATCH 23/28] Fixed an issue with parametrization: warning would not be issued. Added corresponding tests. This should make coverage happy, too. --- nox/_decorators.py | 2 ++ tests/resources/noxfile_pythons.py | 7 +++++++ tests/test_main.py | 26 +++++++++++++++++++++++++- 3 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 tests/resources/noxfile_pythons.py diff --git a/nox/_decorators.py b/nox/_decorators.py index 79adf43f..14edd550 100644 --- a/nox/_decorators.py +++ b/nox/_decorators.py @@ -60,6 +60,7 @@ def copy(self, name: str = None) -> "Func": name, self.venv_backend, self.venv_params, + self.should_warn ) @@ -72,6 +73,7 @@ def __init__(self, func: Func, param_spec: "Param") -> None: None, func.venv_backend, func.venv_params, + func.should_warn ) self.param_spec = param_spec self.session_signature = "({})".format(param_spec) diff --git a/tests/resources/noxfile_pythons.py b/tests/resources/noxfile_pythons.py new file mode 100644 index 00000000..7bb63041 --- /dev/null +++ b/tests/resources/noxfile_pythons.py @@ -0,0 +1,7 @@ +import nox + + +@nox.session(python=['3.6']) +@nox.parametrize("cheese", ["cheddar", "jack", "brie"]) +def snack(unused_session, cheese): + print("Noms, {} so good!".format(cheese)) diff --git a/tests/test_main.py b/tests/test_main.py index bbf58cf3..642c89f6 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -101,8 +101,32 @@ def test_main_long_form_args(): assert config.posargs == [] +def test_main_no_venv(monkeypatch, capsys): + # Check that --no-venv overrides force_venv_backend + monkeypatch.setattr( + sys, + "argv", + [ + "nox", + "--noxfile", + os.path.join(RESOURCES, "noxfile_pythons.py"), + "--no-venv", + "-s", + "snack(cheese='cheddar')", + ], + ) + + with mock.patch("sys.exit") as sys_exit: + nox.__main__.main() + stdout, stderr = capsys.readouterr() + assert stdout == "Noms, cheddar so good!\n" + assert "Session snack is set to run with venv_backend='none', IGNORING its python" in stderr + assert "Session snack(cheese='cheddar') was successful." in stderr + sys_exit.assert_called_once_with(0) + + def test_main_no_venv_error(): - # Check that -no-venv can not be set together with a non-none --force-venv-backend + # Check that --no-venv can not be set together with a non-none --force-venv-backend sys.argv = [ sys.executable, "--noxfile", From 37818bd9b91e93e7ddd1d676dc78215a68e6640d Mon Sep 17 00:00:00 2001 From: Sylvain MARIE Date: Wed, 22 Apr 2020 17:41:12 +0200 Subject: [PATCH 24/28] Blackened --- nox/_decorators.py | 4 ++-- tests/resources/noxfile_pythons.py | 2 +- tests/test_main.py | 5 ++++- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/nox/_decorators.py b/nox/_decorators.py index 14edd550..a4f031f5 100644 --- a/nox/_decorators.py +++ b/nox/_decorators.py @@ -60,7 +60,7 @@ def copy(self, name: str = None) -> "Func": name, self.venv_backend, self.venv_params, - self.should_warn + self.should_warn, ) @@ -73,7 +73,7 @@ def __init__(self, func: Func, param_spec: "Param") -> None: None, func.venv_backend, func.venv_params, - func.should_warn + func.should_warn, ) self.param_spec = param_spec self.session_signature = "({})".format(param_spec) diff --git a/tests/resources/noxfile_pythons.py b/tests/resources/noxfile_pythons.py index 7bb63041..bfba5a08 100644 --- a/tests/resources/noxfile_pythons.py +++ b/tests/resources/noxfile_pythons.py @@ -1,7 +1,7 @@ import nox -@nox.session(python=['3.6']) +@nox.session(python=["3.6"]) @nox.parametrize("cheese", ["cheddar", "jack", "brie"]) def snack(unused_session, cheese): print("Noms, {} so good!".format(cheese)) diff --git a/tests/test_main.py b/tests/test_main.py index 642c89f6..c9ef5467 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -120,7 +120,10 @@ def test_main_no_venv(monkeypatch, capsys): nox.__main__.main() stdout, stderr = capsys.readouterr() assert stdout == "Noms, cheddar so good!\n" - assert "Session snack is set to run with venv_backend='none', IGNORING its python" in stderr + assert ( + "Session snack is set to run with venv_backend='none', IGNORING its python" + in stderr + ) assert "Session snack(cheese='cheddar') was successful." in stderr sys_exit.assert_called_once_with(0) From bdf3bdf1cd0130d59ed062453621c199376d7216 Mon Sep 17 00:00:00 2001 From: Sylvain MARIE Date: Wed, 22 Apr 2020 18:15:49 +0200 Subject: [PATCH 25/28] Now `install` and `conda_install` work when there is no venv backend (or python=False). Previously a `ValueError` was raised. Fixes #318 --- nox/sessions.py | 25 +++++++++++++------------ nox/virtualenv.py | 10 ++++++++++ 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/nox/sessions.py b/nox/sessions.py index 3ff42f1c..2f2b0eee 100644 --- a/nox/sessions.py +++ b/nox/sessions.py @@ -24,6 +24,7 @@ Callable, Dict, Iterable, + Tuple, List, Mapping, Optional, @@ -36,7 +37,7 @@ from nox import _typing from nox._decorators import Func from nox.logger import logger -from nox.virtualenv import CondaEnv, ProcessEnv, VirtualEnv +from nox.virtualenv import CondaEnv, ProcessEnv, VirtualEnv, PassthroughEnv if _typing.TYPE_CHECKING: from nox.manifest import Manifest @@ -272,10 +273,15 @@ def conda_install(self, *args: str, **kwargs: Any) -> None: .. _conda install: """ venv = self._runner.venv - if not isinstance(venv, CondaEnv): + + prefix_args = () # type: Tuple[str, ...] + if isinstance(venv, CondaEnv): + prefix_args = ("--prefix", venv.location) + elif not isinstance(venv, PassthroughEnv): # pragma: no cover raise ValueError( "A session without a conda environment can not install dependencies from conda." ) + if not args: raise ValueError("At least one argument required to install().") @@ -283,14 +289,7 @@ def conda_install(self, *args: str, **kwargs: Any) -> None: kwargs["silent"] = True self._run( - "conda", - "install", - "--yes", - "--prefix", - venv.location, - *args, - external="error", - **kwargs + "conda", "install", "--yes", *prefix_args, *args, external="error", **kwargs ) def install(self, *args: str, **kwargs: Any) -> None: @@ -318,7 +317,9 @@ def install(self, *args: str, **kwargs: Any) -> None: .. _pip: https://pip.readthedocs.org """ - if not isinstance(self._runner.venv, (CondaEnv, VirtualEnv)): + if not isinstance( + self._runner.venv, (CondaEnv, VirtualEnv, PassthroughEnv) + ): # pragma: no cover raise ValueError( "A session without a virtualenv can not install dependencies." ) @@ -396,7 +397,7 @@ def _create_venv(self) -> None: ) if backend == "none" or self.func.python is False: - self.venv = ProcessEnv() + self.venv = PassthroughEnv() return path = _normalize_path(self.global_config.envdir, self.friendly_name) diff --git a/nox/virtualenv.py b/nox/virtualenv.py index 918525e4..c5e23db4 100644 --- a/nox/virtualenv.py +++ b/nox/virtualenv.py @@ -141,6 +141,16 @@ def _clean_location(self: "Union[CondaEnv, VirtualEnv]") -> bool: return True +class PassthroughEnv(ProcessEnv): + """Represents the environment used to run nox itself + + For now, this class is empty but it might contain tools to grasp some + hints about the actual env. + """ + + pass + + class CondaEnv(ProcessEnv): """Conda environemnt management class. From c02fe9b7a0fe247411d1ad18d731da4b1dfe5da1 Mon Sep 17 00:00:00 2001 From: Sylvain MARIE Date: Wed, 22 Apr 2020 18:27:34 +0200 Subject: [PATCH 26/28] Fixed test --- tests/test_sessions.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_sessions.py b/tests/test_sessions.py index 8af3f858..ebdf2016 100644 --- a/tests/test_sessions.py +++ b/tests/test_sessions.py @@ -246,6 +246,7 @@ def test_run_external_with_error_on_external_run_condaenv(self): def test_conda_install_bad_args(self): session, runner = self.make_session_and_runner() runner.venv = mock.create_autospec(nox.virtualenv.CondaEnv) + runner.venv.location = "dummy" with pytest.raises(ValueError, match="arg"): session.conda_install() From 9197d033a28edd51fc515bbacda3008348a8ebef Mon Sep 17 00:00:00 2001 From: Sylvain MARIE Date: Thu, 23 Apr 2020 15:44:46 +0200 Subject: [PATCH 27/28] Minor edit to trigger CI build again as it seems stuck. --- docs/usage.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/usage.rst b/docs/usage.rst index 3ac6445a..d9b4a11f 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -149,7 +149,7 @@ Finally note that the ``--no-venv`` flag is a shortcut for ``--force-venv-backen Re-using virtualenvs -------------------- -By default nox deletes and recreates virtualenvs every time it is run. This is usually fine for most projects and continuous integration environments as `pip's caching `_ makes re-install rather quick. However, there are some situations where it is advantageous to re-use the virtualenvs between runs. Use ``-r`` or ``--reuse-existing-virtualenvs``: +By default, nox deletes and recreates virtualenvs every time it is run. This is usually fine for most projects and continuous integration environments as `pip's caching `_ makes re-install rather quick. However, there are some situations where it is advantageous to re-use the virtualenvs between runs. Use ``-r`` or ``--reuse-existing-virtualenvs``: .. code-block:: console From 2dae1be4f42d8c3b6c9c520b8fdc5f93a4441542 Mon Sep 17 00:00:00 2001 From: Sylvain MARIE Date: Fri, 15 May 2020 17:30:45 +0200 Subject: [PATCH 28/28] Minor doc fix to trigger the CI again (appveyor false fail) --- docs/usage.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/usage.rst b/docs/usage.rst index d9b4a11f..591277ea 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -149,7 +149,7 @@ Finally note that the ``--no-venv`` flag is a shortcut for ``--force-venv-backen Re-using virtualenvs -------------------- -By default, nox deletes and recreates virtualenvs every time it is run. This is usually fine for most projects and continuous integration environments as `pip's caching `_ makes re-install rather quick. However, there are some situations where it is advantageous to re-use the virtualenvs between runs. Use ``-r`` or ``--reuse-existing-virtualenvs``: +By default, Nox deletes and recreates virtualenvs every time it is run. This is usually fine for most projects and continuous integration environments as `pip's caching `_ makes re-install rather quick. However, there are some situations where it is advantageous to re-use the virtualenvs between runs. Use ``-r`` or ``--reuse-existing-virtualenvs``: .. code-block:: console