Skip to content

Commit 592f3f0

Browse files
authored
feat: add venv_backend property (#798)
* 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>
1 parent 419b98a commit 592f3f0

6 files changed

Lines changed: 59 additions & 9 deletions

File tree

docs/config.rst

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,14 +167,19 @@ You can also specify that the virtualenv should *always* be reused instead of re
167167
def tests(session):
168168
pass
169169
170-
You are not limited to virtualenv, there is a selection of backends you can choose from as venv, conda, mamba, or virtualenv (default):
170+
You are not limited to virtualenv, there is a selection of backends you can choose from as venv, uv, conda, mamba, or virtualenv (default):
171171

172172
.. code-block:: python
173173
174174
@nox.session(venv_backend='venv')
175175
def tests(session):
176176
pass
177177
178+
You can chain together optional backends with ``|``, such as ``uv|virtualenv``
179+
or ``mamba|conda``, and the first available backend will be selected. You
180+
cannot put anything after a backend that can't be missing like ``venv`` or
181+
``virtualenv``.
182+
178183
Finally, custom backend parameters are supported:
179184

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

187195
Passing arguments into sessions
188196
-------------------------------

docs/usage.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,9 @@ Note that using this option does not change the backend for sessions where ``ven
165165

166166
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.
167167

168+
If you need to check to see which backend was selected, you can access it via
169+
``session.venv_backend`` in your noxfile.
170+
168171
.. _opt-force-venv-backend:
169172

170173
Forcing the sessions backend

nox/sessions.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,14 @@ def virtualenv(self) -> ProcessEnv:
193193
raise ValueError("A virtualenv has not been created for this session")
194194
return venv
195195

196+
@property
197+
def venv_backend(self) -> str:
198+
"""The venv_backend selected."""
199+
venv = self._runner.venv
200+
if venv is None:
201+
return "none"
202+
return venv.venv_backend
203+
196204
@property
197205
def python(self) -> str | Sequence[str] | bool | None:
198206
"""The python version passed into ``@nox.session``."""

nox/virtualenv.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,13 @@ def create(self) -> bool:
9393
Returns True if the environment is new, and False if it was reused.
9494
"""
9595

96+
@property
97+
@abc.abstractmethod
98+
def venv_backend(self) -> str:
99+
"""
100+
Returns the string used to select this environment.
101+
"""
102+
96103

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

190+
@property
191+
def venv_backend(self) -> str:
192+
return "none"
193+
183194

184195
class CondaEnv(ProcessEnv):
185196
"""Conda environment management class.
@@ -303,6 +314,10 @@ def is_offline() -> bool:
303314
except BaseException: # pragma: no cover
304315
return True
305316

317+
@property
318+
def venv_backend(self) -> str:
319+
return self.conda_cmd
320+
306321

307322
class VirtualEnv(ProcessEnv):
308323
"""Virtualenv management class.
@@ -341,7 +356,7 @@ def __init__(
341356
self.interpreter = interpreter
342357
self._resolved: None | str | InterpreterNotFound = None
343358
self.reuse_existing = reuse_existing
344-
self.venv_backend = venv_backend
359+
self._venv_backend = venv_backend
345360
self.venv_params = venv_params or []
346361
if venv_backend not in {"virtualenv", "venv", "uv"}:
347362
msg = f"venv_backend {venv_backend} not recognized"
@@ -544,6 +559,10 @@ def create(self) -> bool:
544559

545560
return True
546561

562+
@property
563+
def venv_backend(self) -> str:
564+
return self._venv_backend
565+
547566

548567
ALL_VENVS: dict[str, Callable[..., ProcessEnv]] = {
549568
"conda": functools.partial(CondaEnv, conda_cmd="conda"),

tests/test_sessions.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,8 @@ def test_virtualenv_as_none(self):
139139
with pytest.raises(ValueError, match="virtualenv"):
140140
_ = session.virtualenv
141141

142+
assert session.venv_backend == "none"
143+
142144
def test_interactive(self):
143145
session, runner = self.make_session_and_runner()
144146

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

646648
session = SessionNoSlots(runner=runner)
647649

650+
assert session.venv_backend == "venv"
651+
648652
with mock.patch.object(session, "_run", autospec=True) as run:
649653
session.install("requests", "urllib3")
650654
run.assert_called_once_with(

tests/test_virtualenv.py

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
from __future__ import annotations
1616

17+
import functools
1718
import os
1819
import re
1920
import shutil
@@ -52,14 +53,13 @@ class TextProcessResult(NamedTuple):
5253
def make_one(tmpdir):
5354
def factory(*args, venv_backend: str = "virtualenv", **kwargs):
5455
location = tmpdir.join("venv")
55-
if venv_backend in {"mamba", "conda"}:
56-
venv = nox.virtualenv.CondaEnv(
57-
location.strpath, *args, conda_cmd=venv_backend, **kwargs
58-
)
59-
else:
60-
venv = nox.virtualenv.VirtualEnv(
61-
location.strpath, *args, venv_backend=venv_backend, **kwargs
56+
try:
57+
venv_fn = nox.virtualenv.ALL_VENVS[venv_backend]
58+
except KeyError:
59+
venv_fn = functools.partial(
60+
nox.virtualenv.VirtualEnv, venv_backend=venv_backend
6261
)
62+
venv = venv_fn(location.strpath, *args, **kwargs)
6363
return (venv, location)
6464

6565
return factory
@@ -490,13 +490,21 @@ def test_stale_environment(make_one, frm, to, result, monkeypatch):
490490
monkeypatch.setenv("NOX_ENABLE_STALENESS_CHECK", "1")
491491
venv, _ = make_one(reuse_existing=True, venv_backend=frm)
492492
venv.create()
493+
assert venv.venv_backend == frm
493494

494495
venv, _ = make_one(reuse_existing=True, venv_backend=to)
495496
reused = venv._check_reused_environment_type()
497+
assert venv.venv_backend == to
496498

497499
assert reused == result
498500

499501

502+
def test_passthrough_environment_venv_backend(make_one):
503+
venv, _ = make_one(venv_backend="none")
504+
venv.create()
505+
assert venv.venv_backend == "none"
506+
507+
500508
@has_uv
501509
def test_create_reuse_stale_virtualenv_environment(make_one, monkeypatch):
502510
monkeypatch.setenv("NOX_ENABLE_STALENESS_CHECK", "1")

0 commit comments

Comments
 (0)