Repository: wntrblm/nox
Pull request #920 (2025-01-31 19:30:05)

fix: handle uvx like uv
See https://github.com/wntrblm/nox/pull/818#issuecomment-2628118612.


henryiii: Oops, I thought about that when I started, but forgot about it by the time I added a test. I'll add it.
henryiii: Where was `nox.options.sessions` a tuple?
edgarrmondragon: > Where was `nox.options.sessions` a tuple?

In my `noxfile.py`. I see the examples in https://nox.thea.codes/en/stable/config.html all use lists, so maybe only I was in the wrong 😅 
henryiii: Can you try now? I'm looking into the over-validtion issue.
edgarrmondragon: > Can you try now? I'm looking into the over-validtion issue.

Aha, that did it!

Patch:
diff --git a/nox/project.py b/nox/project.py
index 627d1e6..4737d75 100644
--- a/nox/project.py
+++ b/nox/project.py
@@ -35,14 +35,14 @@ REGEX = re.compile(
 
 
 def load_toml(
-    filename: os.PathLike[str] | str, *, missing_ok: bool = False
+    filename: os.PathLike[str] | str = "pyproject.toml", *, missing_ok: bool = False
 ) -> dict[str, Any]:
     """
     Load a toml file or a script with a PEP 723 script block.
 
     The file must have a ``.toml`` extension to be considered a toml file or a
     ``.py`` extension / no extension to be considered a script. Other file
-    extensions are not valid in this function.
+    extensions are not valid in this function. The default is ``"pyproject.toml"``.
 
     If ``missing_ok``, this will return an empty dict if a script block was not
     found, otherwise it will raise a error.
diff --git a/nox/sessions.py b/nox/sessions.py
index 589910a..c278539 100644
--- a/nox/sessions.py
+++ b/nox/sessions.py
@@ -608,14 +608,17 @@ class Session:
         if callable(args[0]):
             return self._run_func(args[0], args[1:])  # type: ignore[unreachable]
 
-        # Using `"uv"` when `uv` is the backend is guaranteed to work, even if it was co-installed with nox.
-        if (
-            self.virtualenv.venv_backend == "uv"
-            and args[0] == "uv"
-            and nox.virtualenv.UV != "uv"
-            and shutil.which("uv", path=self.bin) is None  # Session uv takes priority
-        ):
-            args = (nox.virtualenv.UV, *args[1:])
+        # Using `"uv"` or `"uvx" when `uv` is the backend is guaranteed to
+        # work, even if it was co-installed with nox.
+        if self.virtualenv.venv_backend == "uv" and nox.virtualenv.UV != "uv":
+            if (
+                args[0] == "uv"
+                and shutil.which("uv", path=self.bin)
+                is None  # Session uv takes priority
+            ):
+                args = (nox.virtualenv.UV, *args[1:])
+            elif args[0] == "uvx" and shutil.which("uvx", path=self.bin) is None:
+                args = (f"{nox.virtualenv.UV}x", *args[1:])
 
         # Combine the env argument with our virtualenv's env vars.
         env = self.virtualenv._get_env(env or {}, include_outer_env=include_outer_env)
diff --git a/nox/virtualenv.py b/nox/virtualenv.py
index 66f81ea..bb29e60 100644
--- a/nox/virtualenv.py
+++ b/nox/virtualenv.py
@@ -461,7 +461,7 @@ class VirtualEnv(ProcessEnv):
     """
 
     is_sandboxed = True
-    allowed_globals = (UV,)
+    allowed_globals = (UV, f"{UV}x")
 
     def __init__(
         self,


Test patch:
diff --git a/tests/test_sessions.py b/tests/test_sessions.py
index abda550..50c072e 100644
--- a/tests/test_sessions.py
+++ b/tests/test_sessions.py
@@ -995,6 +995,17 @@ class TestSession:
                 "urllib3",
             )
 
+        # User runs uvx
+        with mock.patch.object(nox.command, "run", autospec=True) as run:
+            session.run("uvx", "cowsay")
+            run.assert_called_once()
+
+            ((call_args,), _) = run.call_args
+            assert call_args == (
+                "/some/uvx",
+                "cowsay",
+            )
+
         # user installs uv in the session venv
         monkeypatch.setattr(
             shutil, "which", lambda x, path="": path + "/uv" if x == "uv" else None