Skip to content

Commit

Permalink
feat: add venv_backend property (#798)
Browse files Browse the repository at this point in the history
* feat: add venv_backend property

Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com>

* tests: add a couple of checks

Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com>

* docs: update for venv_backend

Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com>

* tests: add one more test for coverage

Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com>

* Update test_virtualenv.py

* Update test_virtualenv.py

* Update test_virtualenv.py

---------

Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com>
  • Loading branch information
henryiii authored Mar 8, 2024
1 parent 419b98a commit 592f3f0
Show file tree
Hide file tree
Showing 6 changed files with 59 additions and 9 deletions.
10 changes: 9 additions & 1 deletion docs/config.rst
Original file line number Diff line number Diff line change
Expand Up @@ -167,14 +167,19 @@ You can also specify that the virtualenv should *always* be reused instead of re
def tests(session):
pass
You are not limited to virtualenv, there is a selection of backends you can choose from as venv, conda, mamba, or virtualenv (default):
You are not limited to virtualenv, there is a selection of backends you can choose from as venv, uv, conda, mamba, or virtualenv (default):

.. code-block:: python
@nox.session(venv_backend='venv')
def tests(session):
pass
You can chain together optional backends with ``|``, such as ``uv|virtualenv``
or ``mamba|conda``, and the first available backend will be selected. You
cannot put anything after a backend that can't be missing like ``venv`` or
``virtualenv``.

Finally, custom backend parameters are supported:

.. code-block:: python
Expand All @@ -183,6 +188,9 @@ Finally, custom backend parameters are supported:
def tests(session):
pass
If you need to check to see which backend was selected, you can access it via
``session.venv_backend``.


Passing arguments into sessions
-------------------------------
Expand Down
3 changes: 3 additions & 0 deletions docs/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,9 @@ Note that using this option does not change the backend for sessions where ``ven

Backends that could be missing (``uv``, ``conda``, and ``mamba``) can have a fallback using ``|``, such as ``uv|virtualenv`` or ``mamba|conda``. This will use the first item that is available on the users system.

If you need to check to see which backend was selected, you can access it via
``session.venv_backend`` in your noxfile.

.. _opt-force-venv-backend:

Forcing the sessions backend
Expand Down
8 changes: 8 additions & 0 deletions nox/sessions.py
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,14 @@ def virtualenv(self) -> ProcessEnv:
raise ValueError("A virtualenv has not been created for this session")
return venv

@property
def venv_backend(self) -> str:
"""The venv_backend selected."""
venv = self._runner.venv
if venv is None:
return "none"
return venv.venv_backend

@property
def python(self) -> str | Sequence[str] | bool | None:
"""The python version passed into ``@nox.session``."""
Expand Down
21 changes: 20 additions & 1 deletion nox/virtualenv.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,13 @@ def create(self) -> bool:
Returns True if the environment is new, and False if it was reused.
"""

@property
@abc.abstractmethod
def venv_backend(self) -> str:
"""
Returns the string used to select this environment.
"""


def locate_via_py(version: str) -> str | None:
"""Find the Python executable using the Windows Launcher.
Expand Down Expand Up @@ -180,6 +187,10 @@ def create(self) -> bool:
False since it's always reused."""
return False

@property
def venv_backend(self) -> str:
return "none"


class CondaEnv(ProcessEnv):
"""Conda environment management class.
Expand Down Expand Up @@ -303,6 +314,10 @@ def is_offline() -> bool:
except BaseException: # pragma: no cover
return True

@property
def venv_backend(self) -> str:
return self.conda_cmd


class VirtualEnv(ProcessEnv):
"""Virtualenv management class.
Expand Down Expand Up @@ -341,7 +356,7 @@ def __init__(
self.interpreter = interpreter
self._resolved: None | str | InterpreterNotFound = None
self.reuse_existing = reuse_existing
self.venv_backend = venv_backend
self._venv_backend = venv_backend
self.venv_params = venv_params or []
if venv_backend not in {"virtualenv", "venv", "uv"}:
msg = f"venv_backend {venv_backend} not recognized"
Expand Down Expand Up @@ -544,6 +559,10 @@ def create(self) -> bool:

return True

@property
def venv_backend(self) -> str:
return self._venv_backend


ALL_VENVS: dict[str, Callable[..., ProcessEnv]] = {
"conda": functools.partial(CondaEnv, conda_cmd="conda"),
Expand Down
4 changes: 4 additions & 0 deletions tests/test_sessions.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,8 @@ def test_virtualenv_as_none(self):
with pytest.raises(ValueError, match="virtualenv"):
_ = session.virtualenv

assert session.venv_backend == "none"

def test_interactive(self):
session, runner = self.make_session_and_runner()

Expand Down Expand Up @@ -645,6 +647,8 @@ class SessionNoSlots(nox.sessions.Session):

session = SessionNoSlots(runner=runner)

assert session.venv_backend == "venv"

with mock.patch.object(session, "_run", autospec=True) as run:
session.install("requests", "urllib3")
run.assert_called_once_with(
Expand Down
22 changes: 15 additions & 7 deletions tests/test_virtualenv.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

from __future__ import annotations

import functools
import os
import re
import shutil
Expand Down Expand Up @@ -52,14 +53,13 @@ class TextProcessResult(NamedTuple):
def make_one(tmpdir):
def factory(*args, venv_backend: str = "virtualenv", **kwargs):
location = tmpdir.join("venv")
if venv_backend in {"mamba", "conda"}:
venv = nox.virtualenv.CondaEnv(
location.strpath, *args, conda_cmd=venv_backend, **kwargs
)
else:
venv = nox.virtualenv.VirtualEnv(
location.strpath, *args, venv_backend=venv_backend, **kwargs
try:
venv_fn = nox.virtualenv.ALL_VENVS[venv_backend]
except KeyError:
venv_fn = functools.partial(
nox.virtualenv.VirtualEnv, venv_backend=venv_backend
)
venv = venv_fn(location.strpath, *args, **kwargs)
return (venv, location)

return factory
Expand Down Expand Up @@ -490,13 +490,21 @@ def test_stale_environment(make_one, frm, to, result, monkeypatch):
monkeypatch.setenv("NOX_ENABLE_STALENESS_CHECK", "1")
venv, _ = make_one(reuse_existing=True, venv_backend=frm)
venv.create()
assert venv.venv_backend == frm

venv, _ = make_one(reuse_existing=True, venv_backend=to)
reused = venv._check_reused_environment_type()
assert venv.venv_backend == to

assert reused == result


def test_passthrough_environment_venv_backend(make_one):
venv, _ = make_one(venv_backend="none")
venv.create()
assert venv.venv_backend == "none"


@has_uv
def test_create_reuse_stale_virtualenv_environment(make_one, monkeypatch):
monkeypatch.setenv("NOX_ENABLE_STALENESS_CHECK", "1")
Expand Down

0 comments on commit 592f3f0

Please sign in to comment.