Skip to content

Commit 08813c3

Browse files
authored
fix: look for uv next to python if it's not on PATH (#795)
1 parent 592f3f0 commit 08813c3

File tree

6 files changed

+53
-7
lines changed

6 files changed

+53
-7
lines changed

.pre-commit-config.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ repos:
4040
- jinja2
4141
- packaging
4242
- importlib_metadata
43+
- uv
4344

4445
- repo: https://github.com/codespell-project/codespell
4546
rev: v2.2.6

nox/sessions.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
import nox.virtualenv
4242
from nox._decorators import Func
4343
from nox.logger import logger
44-
from nox.virtualenv import CondaEnv, PassthroughEnv, ProcessEnv, VirtualEnv
44+
from nox.virtualenv import UV, CondaEnv, PassthroughEnv, ProcessEnv, VirtualEnv
4545

4646
if TYPE_CHECKING:
4747
from nox.manifest import Manifest
@@ -664,7 +664,7 @@ def install(self, *args: str, **kwargs: Any) -> None:
664664
kwargs["silent"] = True
665665

666666
if isinstance(venv, VirtualEnv) and venv.venv_backend == "uv":
667-
self._run("uv", "pip", "install", *args, external="error", **kwargs)
667+
self._run(UV, "pip", "install", *args, external="error", **kwargs)
668668
else:
669669
self._run(
670670
"python", "-m", "pip", "install", *args, external="error", **kwargs

nox/virtualenv.py

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,24 @@
3939
_SYSTEM = platform.system()
4040

4141

42+
def find_uv() -> tuple[bool, str]:
43+
# Look for uv in Nox's environment, to handle `pipx install nox[uv]`.
44+
with contextlib.suppress(ImportError, FileNotFoundError):
45+
from uv import find_uv_bin
46+
47+
return True, find_uv_bin()
48+
49+
# Fall back to PATH.
50+
uv = shutil.which("uv")
51+
if uv is not None:
52+
return True, uv
53+
54+
return False, "uv"
55+
56+
57+
HAS_UV, UV = find_uv()
58+
59+
4260
class InterpreterNotFound(OSError):
4361
def __init__(self, interpreter: str) -> None:
4462
super().__init__(f"Python interpreter {interpreter} not found")
@@ -340,7 +358,7 @@ class VirtualEnv(ProcessEnv):
340358
"""
341359

342360
is_sandboxed = True
343-
allowed_globals = ("uv",)
361+
allowed_globals = (UV,)
344362

345363
def __init__(
346364
self,
@@ -539,7 +557,7 @@ def create(self) -> bool:
539557
cmd.extend(["-p", self._resolved_interpreter])
540558
elif self.venv_backend == "uv":
541559
cmd = [
542-
"uv",
560+
UV,
543561
"venv",
544562
"-p",
545563
self._resolved_interpreter if self.interpreter else sys.executable,
@@ -579,5 +597,5 @@ def venv_backend(self) -> str:
579597
OPTIONAL_VENVS = {
580598
"conda": shutil.which("conda") is not None,
581599
"mamba": shutil.which("mamba") is not None,
582-
"uv": shutil.which("uv") is not None,
600+
"uv": HAS_UV,
583601
}

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ tox_to_nox = [
5353
"tox",
5454
]
5555
uv = [
56-
"uv",
56+
"uv>=0.1.6",
5757
]
5858
[project.urls]
5959
bug-tracker = "https://github.com/wntrblm/nox/issues"

tests/test_sessions.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -829,7 +829,7 @@ class SessionNoSlots(nox.sessions.Session):
829829
with mock.patch.object(session, "_run", autospec=True) as run:
830830
session.install("requests", "urllib3", silent=False)
831831
run.assert_called_once_with(
832-
"uv",
832+
nox.virtualenv.UV,
833833
"pip",
834834
"install",
835835
"requests",

tests/test_virtualenv.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import shutil
2121
import subprocess
2222
import sys
23+
import types
2324
from textwrap import dedent
2425
from typing import NamedTuple
2526
from unittest import mock
@@ -546,6 +547,32 @@ def test_create_reuse_uv_environment(make_one):
546547
assert reused
547548

548549

550+
UV_IN_PIPX_VENV = "/home/user/.local/pipx/venvs/nox/bin/uv"
551+
552+
553+
@pytest.mark.parametrize(
554+
["which_result", "find_uv_bin_result", "expected"],
555+
[
556+
("/usr/bin/uv", UV_IN_PIPX_VENV, (True, UV_IN_PIPX_VENV)),
557+
("/usr/bin/uv", FileNotFoundError, (True, "/usr/bin/uv")),
558+
(None, UV_IN_PIPX_VENV, (True, UV_IN_PIPX_VENV)),
559+
(None, FileNotFoundError, (False, "uv")),
560+
],
561+
) # fmt: skip
562+
def test_find_uv(monkeypatch, which_result, find_uv_bin_result, expected):
563+
def find_uv_bin():
564+
if find_uv_bin_result is FileNotFoundError:
565+
raise FileNotFoundError
566+
return find_uv_bin_result
567+
568+
monkeypatch.setattr(shutil, "which", lambda _: which_result)
569+
monkeypatch.setitem(
570+
sys.modules, "uv", types.SimpleNamespace(find_uv_bin=find_uv_bin)
571+
)
572+
573+
assert nox.virtualenv.find_uv() == expected
574+
575+
549576
def test_create_reuse_venv_environment(make_one, monkeypatch):
550577
# Making the reuse requirement more strict
551578
monkeypatch.setenv("NOX_ENABLE_STALENESS_CHECK", "1")

0 commit comments

Comments
 (0)