Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add venv_backend property #798

Merged
merged 7 commits into from
Mar 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -135,6 +135,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 @@ -641,6 +643,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