Skip to content

Commit

Permalink
feat: add a uv backend
Browse files Browse the repository at this point in the history
Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com>
  • Loading branch information
henryiii committed Feb 17, 2024
1 parent 5c82dc5 commit c70b9d7
Show file tree
Hide file tree
Showing 5 changed files with 29 additions and 19 deletions.
18 changes: 8 additions & 10 deletions nox/sessions.py
Original file line number Diff line number Diff line change
Expand Up @@ -632,7 +632,12 @@ def install(self, *args: str, **kwargs: Any) -> None:
if "silent" not in kwargs:
kwargs["silent"] = True

self._run("python", "-m", "pip", "install", *args, external="error", **kwargs)
if getattr(venv, "venv_prog", "") == "uv":
self._run("uv", "pip", "install", *args, external="error", **kwargs)
else:
self._run(
"python", "-m", "pip", "install", *args, external="error", **kwargs
)

def notify(
self,
Expand Down Expand Up @@ -748,11 +753,12 @@ def _create_venv(self) -> None:
self.func.reuse_venv or self.global_config.reuse_existing_virtualenvs
)

if backend is None or backend == "virtualenv":
if backend is None or backend in {"virtualenv", "venv", "uv"}:
self.venv = VirtualEnv(
self.envdir,
interpreter=self.func.python, # type: ignore[arg-type]
reuse_existing=reuse_existing,
venv=backend or "virtualenv",
venv_params=self.func.venv_params,
)
elif backend in {"conda", "mamba"}:
Expand All @@ -763,14 +769,6 @@ def _create_venv(self) -> None:
venv_params=self.func.venv_params,
conda_cmd=backend,
)
elif backend == "venv":
self.venv = VirtualEnv(
self.envdir,
interpreter=self.func.python, # type: ignore[arg-type]
reuse_existing=reuse_existing,
venv=True,
venv_params=self.func.venv_params,
)
else:
raise ValueError(
"Expected venv_backend one of ('virtualenv', 'conda', 'mamba',"
Expand Down
19 changes: 14 additions & 5 deletions nox/virtualenv.py
Original file line number Diff line number Diff line change
Expand Up @@ -312,22 +312,23 @@ class VirtualEnv(ProcessEnv):
"""

is_sandboxed = True
allowed_globals = ("uv",)

def __init__(
self,
location: str,
interpreter: str | None = None,
reuse_existing: bool = False,
*,
venv: bool = False,
venv: str = "virtualenv",
venv_params: Any = None,
):
self.location_name = location
self.location = os.path.abspath(location)
self.interpreter = interpreter
self._resolved: None | str | InterpreterNotFound = None
self.reuse_existing = reuse_existing
self.venv_or_virtualenv = "venv" if venv else "virtualenv"
self.venv_prog = venv
self.venv_params = venv_params or []
super().__init__(env={"VIRTUAL_ENV": self.location})

Expand Down Expand Up @@ -359,7 +360,7 @@ def _check_reused_environment_type(self) -> bool:
old_env = (
"virtualenv" if any(pattern.match(line) for line in fp) else "venv"
)
return old_env == self.venv_or_virtualenv
return old_env == self.venv_prog

def _check_reused_environment_interpreter(self) -> bool:
"""Check if reused environment interpreter is the same."""
Expand Down Expand Up @@ -474,18 +475,26 @@ def create(self) -> bool:

return False

if self.venv_or_virtualenv == "virtualenv":
if self.venv_prog == "virtualenv":
cmd = [sys.executable, "-m", "virtualenv", self.location]
if self.interpreter:
cmd.extend(["-p", self._resolved_interpreter])
elif self.venv_prog == "uv":
cmd = [
"uv",
"virtualenv",
"-p",
self._resolved_interpreter if self.interpreter else sys.executable,
self.location,
]
else:
cmd = [self._resolved_interpreter, "-m", "venv", self.location]
cmd.extend(self.venv_params)

resolved_interpreter_name = os.path.basename(self._resolved_interpreter)

logger.info(
f"Creating virtual environment ({self.venv_or_virtualenv}) using"
f"Creating virtual environment ({self.venv_prog}) using"
f" {resolved_interpreter_name} in {self.location_name}"
)
nox.command.run(cmd, silent=True, log=nox.options.verbose or False)
Expand Down
4 changes: 2 additions & 2 deletions noxfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ def lint(session: nox.Session) -> None:
)


@nox.session
@nox.session(venv_backend="uv")
def docs(session: nox.Session) -> None:
"""Build the documentation."""
output_dir = os.path.join(session.create_tmp(), "output")
Expand All @@ -95,7 +95,7 @@ def docs(session: nox.Session) -> None:
)
shutil.rmtree(output_dir, ignore_errors=True)
session.install("-r", "requirements-test.txt")
session.install(".")
session.install("nox @ .")
session.cd("docs")
sphinx_args = ["-b", "html", "-W", "-d", doctrees, ".", html]

Expand Down
3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ tox_to_nox = [
"jinja2",
"tox<4",
]
uv = [
"uv"
]
[project.urls]
bug-tracker = "https://github.com/wntrblm/nox/issues"
documentation = "https://nox.thea.codes"
Expand Down
4 changes: 2 additions & 2 deletions tests/test_virtualenv.py
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ def test_constructor_defaults(make_one):
assert venv.location
assert venv.interpreter is None
assert venv.reuse_existing is False
assert venv.venv_or_virtualenv == "virtualenv"
assert venv.venv_prog == "virtualenv"


@pytest.mark.skipif(IS_WINDOWS, reason="Not testing multiple interpreters on Windows.")
Expand Down Expand Up @@ -442,7 +442,7 @@ def test_create_reuse_stale_virtualenv_environment(make_one):

@enable_staleness_check
def test_create_reuse_venv_environment(make_one):
venv, location = make_one(reuse_existing=True, venv=True)
venv, location = make_one(reuse_existing=True, venv="venv")
venv.create()

# Place a spurious occurrence of "virtualenv" in the pyvenv.cfg.
Expand Down

0 comments on commit c70b9d7

Please sign in to comment.