Skip to content

Commit

Permalink
fix: look for uv next to python if it's not on PATH (#795)
Browse files Browse the repository at this point in the history
  • Loading branch information
cjolowicz committed Mar 8, 2024
1 parent 592f3f0 commit 08813c3
Show file tree
Hide file tree
Showing 6 changed files with 53 additions and 7 deletions.
1 change: 1 addition & 0 deletions .pre-commit-config.yaml
Expand Up @@ -40,6 +40,7 @@ repos:
- jinja2
- packaging
- importlib_metadata
- uv

- repo: https://github.com/codespell-project/codespell
rev: v2.2.6
Expand Down
4 changes: 2 additions & 2 deletions nox/sessions.py
Expand Up @@ -41,7 +41,7 @@
import nox.virtualenv
from nox._decorators import Func
from nox.logger import logger
from nox.virtualenv import CondaEnv, PassthroughEnv, ProcessEnv, VirtualEnv
from nox.virtualenv import UV, CondaEnv, PassthroughEnv, ProcessEnv, VirtualEnv

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

if isinstance(venv, VirtualEnv) and venv.venv_backend == "uv":
self._run("uv", "pip", "install", *args, external="error", **kwargs)
self._run(UV, "pip", "install", *args, external="error", **kwargs)
else:
self._run(
"python", "-m", "pip", "install", *args, external="error", **kwargs
Expand Down
24 changes: 21 additions & 3 deletions nox/virtualenv.py
Expand Up @@ -39,6 +39,24 @@
_SYSTEM = platform.system()


def find_uv() -> tuple[bool, str]:
# Look for uv in Nox's environment, to handle `pipx install nox[uv]`.
with contextlib.suppress(ImportError, FileNotFoundError):
from uv import find_uv_bin

return True, find_uv_bin()

# Fall back to PATH.
uv = shutil.which("uv")
if uv is not None:
return True, uv

return False, "uv"


HAS_UV, UV = find_uv()


class InterpreterNotFound(OSError):
def __init__(self, interpreter: str) -> None:
super().__init__(f"Python interpreter {interpreter} not found")
Expand Down Expand Up @@ -340,7 +358,7 @@ class VirtualEnv(ProcessEnv):
"""

is_sandboxed = True
allowed_globals = ("uv",)
allowed_globals = (UV,)

def __init__(
self,
Expand Down Expand Up @@ -539,7 +557,7 @@ def create(self) -> bool:
cmd.extend(["-p", self._resolved_interpreter])
elif self.venv_backend == "uv":
cmd = [
"uv",
UV,
"venv",
"-p",
self._resolved_interpreter if self.interpreter else sys.executable,
Expand Down Expand Up @@ -579,5 +597,5 @@ def venv_backend(self) -> str:
OPTIONAL_VENVS = {
"conda": shutil.which("conda") is not None,
"mamba": shutil.which("mamba") is not None,
"uv": shutil.which("uv") is not None,
"uv": HAS_UV,
}
2 changes: 1 addition & 1 deletion pyproject.toml
Expand Up @@ -53,7 +53,7 @@ tox_to_nox = [
"tox",
]
uv = [
"uv",
"uv>=0.1.6",
]
[project.urls]
bug-tracker = "https://github.com/wntrblm/nox/issues"
Expand Down
2 changes: 1 addition & 1 deletion tests/test_sessions.py
Expand Up @@ -829,7 +829,7 @@ class SessionNoSlots(nox.sessions.Session):
with mock.patch.object(session, "_run", autospec=True) as run:
session.install("requests", "urllib3", silent=False)
run.assert_called_once_with(
"uv",
nox.virtualenv.UV,
"pip",
"install",
"requests",
Expand Down
27 changes: 27 additions & 0 deletions tests/test_virtualenv.py
Expand Up @@ -20,6 +20,7 @@
import shutil
import subprocess
import sys
import types
from textwrap import dedent
from typing import NamedTuple
from unittest import mock
Expand Down Expand Up @@ -546,6 +547,32 @@ def test_create_reuse_uv_environment(make_one):
assert reused


UV_IN_PIPX_VENV = "/home/user/.local/pipx/venvs/nox/bin/uv"


@pytest.mark.parametrize(
["which_result", "find_uv_bin_result", "expected"],
[
("/usr/bin/uv", UV_IN_PIPX_VENV, (True, UV_IN_PIPX_VENV)),
("/usr/bin/uv", FileNotFoundError, (True, "/usr/bin/uv")),
(None, UV_IN_PIPX_VENV, (True, UV_IN_PIPX_VENV)),
(None, FileNotFoundError, (False, "uv")),
],
) # fmt: skip
def test_find_uv(monkeypatch, which_result, find_uv_bin_result, expected):
def find_uv_bin():
if find_uv_bin_result is FileNotFoundError:
raise FileNotFoundError
return find_uv_bin_result

monkeypatch.setattr(shutil, "which", lambda _: which_result)
monkeypatch.setitem(
sys.modules, "uv", types.SimpleNamespace(find_uv_bin=find_uv_bin)
)

assert nox.virtualenv.find_uv() == expected


def test_create_reuse_venv_environment(make_one, monkeypatch):
# Making the reuse requirement more strict
monkeypatch.setenv("NOX_ENABLE_STALENESS_CHECK", "1")
Expand Down

0 comments on commit 08813c3

Please sign in to comment.