diff --git a/docs/config.rst b/docs/config.rst index f3bb1b9d..7773da18 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -149,6 +149,14 @@ If you want to disable virtualenv creation altogether, you can set ``python`` to def tests(session): pass +Use of :func:`session.install()` is deprecated without a virtualenv since it modifies the global Python environment. If this is what you really want, use :func:`session.run()` and pip instead. + +.. code-block:: python + + @nox.session(python=False) + def tests(session): + session.run("pip", "install", "nox") + You can also specify that the virtualenv should *always* be reused instead of recreated every time: .. code-block:: python diff --git a/nox/sessions.py b/nox/sessions.py index f476bb2d..043a9e28 100644 --- a/nox/sessions.py +++ b/nox/sessions.py @@ -20,6 +20,7 @@ import re import sys import unicodedata +import warnings from typing import ( Any, Callable, @@ -488,6 +489,15 @@ def install(self, *args: str, **kwargs: Any) -> None: raise ValueError( "A session without a virtualenv can not install dependencies." ) + if isinstance(venv, PassthroughEnv): + warnings.warn( + f"Session {self.name} does not have a virtual environment, " + "so use of session.install() is deprecated since it would modify " + "the global Python environment. If you're really sure that is " + 'what you want to do, use session.run("pip", "install", ...) instead.', + category=FutureWarning, + stacklevel=2, + ) if not args: raise ValueError("At least one argument required to install().") diff --git a/tests/test_sessions.py b/tests/test_sessions.py index d5a895d2..4f31a826 100644 --- a/tests/test_sessions.py +++ b/tests/test_sessions.py @@ -578,6 +578,39 @@ class SessionNoSlots(nox.sessions.Session): external="error", ) + def test_install_no_venv_deprecated(self): + runner = nox.sessions.SessionRunner( + name="test", + signatures=["test"], + func=mock.sentinel.func, + global_config=_options.options.namespace(posargs=[]), + manifest=mock.create_autospec(nox.manifest.Manifest), + ) + runner.venv = mock.create_autospec(nox.virtualenv.PassthroughEnv) + runner.venv.env = {} + + class SessionNoSlots(nox.sessions.Session): + pass + + session = SessionNoSlots(runner=runner) + + with mock.patch.object(session, "_run", autospec=True) as run: + with pytest.warns( + FutureWarning, + match=r"use of session\.install\(\) is deprecated since it would modify the global Python environment", + ): + session.install("requests", "urllib3") + run.assert_called_once_with( + "python", + "-m", + "pip", + "install", + "requests", + "urllib3", + silent=True, + external="error", + ) + def test_notify(self): session, runner = self.make_session_and_runner()