Skip to content

Commit b943f95

Browse files
henryiiiSpectre5
andauthored
fix: uv version is now uv self version, support UV (#955)
* fix: uv version is now uv self version, support UV Signed-off-by: Henry Schreiner <[email protected]> * uv version is not uv self version * fix: add tests and fix UV Signed-off-by: Henry Schreiner <[email protected]> --------- Signed-off-by: Henry Schreiner <[email protected]> Co-authored-by: Scott Barlow <[email protected]>
1 parent 1d52c8f commit b943f95

File tree

2 files changed

+186
-28
lines changed

2 files changed

+186
-28
lines changed

nox/virtualenv.py

Lines changed: 31 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -82,33 +82,39 @@ def __dir__() -> list[str]:
8282

8383

8484
def find_uv() -> tuple[bool, str, version.Version]:
85-
uv_on_path = shutil.which("uv")
85+
uv_name = os.environ.get("UV", None)
86+
uv_on_path = shutil.which(uv_name or "uv")
8687

8788
# Look for uv in Nox's environment, to handle `pipx install nox[uv]`.
88-
with contextlib.suppress(ImportError, FileNotFoundError):
89-
from uv import find_uv_bin
89+
if uv_name is None:
90+
with contextlib.suppress(ImportError, FileNotFoundError):
91+
from uv import find_uv_bin
9092

91-
uv_bin = find_uv_bin()
93+
uv_bin = find_uv_bin()
9294

93-
uv_vers = uv_version(uv_bin)
94-
if uv_vers > version.Version("0"):
95-
# If the returned value is the same as calling "uv" already, don't
96-
# expand (simpler logging)
97-
if uv_on_path and Path(uv_bin).samefile(uv_on_path):
98-
return True, "uv", uv_vers
95+
uv_vers = uv_version(uv_bin)
96+
if uv_vers > version.Version("0"):
97+
# If the returned value is the same as calling "uv" already, don't
98+
# expand (simpler logging)
99+
if uv_on_path and Path(uv_bin).samefile(uv_on_path):
100+
return True, "uv", uv_vers
99101

100-
return True, uv_bin, uv_vers
102+
return True, uv_bin, uv_vers
101103

102104
# Fall back to PATH.
103-
uv_vers = uv_version("uv")
104-
return uv_on_path is not None and uv_vers > version.Version("0"), "uv", uv_vers
105+
uv_vers = uv_version(uv_name or "uv")
106+
return (
107+
uv_on_path is not None and uv_vers > version.Version("0"),
108+
uv_name or "uv",
109+
uv_vers,
110+
)
105111

106112

107113
def uv_version(uv_bin: str) -> version.Version:
108114
"""Returns uv's version defaulting to 0.0 if uv is not available"""
109115
try:
110116
ret = subprocess.run(
111-
[uv_bin, "version", "--output-format", "json"],
117+
[uv_bin, "self", "version", "--output-format", "json"],
112118
check=False,
113119
text=True,
114120
capture_output=True,
@@ -118,8 +124,19 @@ def uv_version(uv_bin: str) -> version.Version:
118124
logger.info("uv binary not found.")
119125
return version.Version("0.0")
120126

127+
if ret.returncode == 2:
128+
# uv < 0.7
129+
ret = subprocess.run(
130+
[uv_bin, "version", "--output-format", "json"],
131+
check=False,
132+
text=True,
133+
capture_output=True,
134+
encoding="utf-8",
135+
)
136+
121137
if ret.returncode == 0 and ret.stdout:
122138
return version.Version(json.loads(ret.stdout).get("version"))
139+
123140
logger.info("Failed to establish uv's version.")
124141
return version.Version("0.0")
125142

tests/test_virtualenv.py

Lines changed: 155 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -722,43 +722,184 @@ def test_create_reuse_uv_environment(
722722

723723

724724
@pytest.mark.parametrize(
725-
("which_result", "find_uv_bin_result", "found", "path", "vers", "vers_rc"),
725+
(
726+
"uv_env",
727+
"which_result",
728+
"find_uv_bin_result",
729+
"found",
730+
"path",
731+
"vers",
732+
"vers_rc",
733+
),
726734
[
727-
("/usr/bin/uv", UV_IN_PIPX_VENV, True, UV_IN_PIPX_VENV, "0.5.0", 0),
728-
("/usr/bin/uv", UV_IN_PIPX_VENV, True, UV_IN_PIPX_VENV, "0.6.0", 0),
729-
("/usr/bin/uv", UV_IN_PIPX_VENV, False, "uv", "0.0.0", 0),
730-
("/usr/bin/uv", UV_IN_PIPX_VENV, False, "uv", "0.6.0", 1),
731-
("/usr/bin/uv", None, True, "uv", "0.6.0", 0),
732-
("/usr/bin/uv", None, False, "uv", "0.0.0", 0),
733-
("/usr/bin/uv", None, False, "uv", "0.6.0", 1),
734-
(None, UV_IN_PIPX_VENV, True, UV_IN_PIPX_VENV, "0.5.0", 0),
735-
(None, None, False, "uv", "0.5.0", 0),
735+
pytest.param(
736+
None,
737+
"/usr/bin/uv",
738+
UV_IN_PIPX_VENV,
739+
True,
740+
UV_IN_PIPX_VENV,
741+
"0.5.0",
742+
0,
743+
id="pkg_pipx_uv_0.5",
744+
),
745+
pytest.param(
746+
None,
747+
"/usr/bin/uv",
748+
UV_IN_PIPX_VENV,
749+
True,
750+
UV_IN_PIPX_VENV,
751+
"0.6.0",
752+
0,
753+
id="pkg_pipx_uv_0.6",
754+
),
755+
pytest.param(
756+
"custom",
757+
"/usr/bin/uv",
758+
UV_IN_PIPX_VENV,
759+
True,
760+
"custom",
761+
"0.6.0",
762+
0,
763+
id="UV_pkg_pipx_uv_0.6",
764+
),
765+
pytest.param(
766+
None,
767+
"/usr/bin/uv",
768+
UV_IN_PIPX_VENV,
769+
True,
770+
UV_IN_PIPX_VENV,
771+
"0.7.0",
772+
0,
773+
id="pkg_pipx_uv_0.7",
774+
),
775+
pytest.param(
776+
"custom",
777+
"/usr/bin/uv",
778+
UV_IN_PIPX_VENV,
779+
True,
780+
"custom",
781+
"0.7.0",
782+
0,
783+
id="UV_pkg_pipx_uv_0.7",
784+
),
785+
pytest.param(
786+
None,
787+
"/usr/bin/uv",
788+
UV_IN_PIPX_VENV,
789+
False,
790+
"uv",
791+
"0.0.0",
792+
0,
793+
id="pkg_system_uv_0.0",
794+
),
795+
pytest.param(
796+
None,
797+
"/usr/bin/uv",
798+
UV_IN_PIPX_VENV,
799+
False,
800+
"uv",
801+
"0.6.0",
802+
1,
803+
id="pkg_system_uv_0.6_broken",
804+
),
805+
pytest.param(
806+
None,
807+
"/usr/bin/uv",
808+
UV_IN_PIPX_VENV,
809+
False,
810+
"uv",
811+
"0.7.0",
812+
1,
813+
id="pkg_system_uv_0.7_broken",
814+
),
815+
pytest.param(
816+
None, "/usr/bin/uv", None, True, "uv", "0.7.0", 0, id="system_uv_0.7"
817+
),
818+
pytest.param(
819+
None, "/usr/bin/uv", None, True, "uv", "0.6.0", 0, id="system_uv_0.6"
820+
),
821+
pytest.param(
822+
None, "/usr/bin/uv", None, False, "uv", "0.0.0", 0, id="system_uv_0.0"
823+
),
824+
pytest.param(
825+
None,
826+
"/usr/bin/uv",
827+
None,
828+
False,
829+
"uv",
830+
"0.6.0",
831+
1,
832+
id="system_uv_0.6_broken",
833+
),
834+
pytest.param(
835+
None,
836+
"/usr/bin/uv",
837+
None,
838+
False,
839+
"uv",
840+
"0.7.0",
841+
1,
842+
id="system_uv_0.7_broken",
843+
),
844+
pytest.param(
845+
None,
846+
None,
847+
UV_IN_PIPX_VENV,
848+
True,
849+
UV_IN_PIPX_VENV,
850+
"0.5.0",
851+
0,
852+
id="pipx_uv_0.5",
853+
),
854+
pytest.param(
855+
None,
856+
None,
857+
UV_IN_PIPX_VENV,
858+
True,
859+
UV_IN_PIPX_VENV,
860+
"0.7.0",
861+
0,
862+
id="pipx_uv_0.7",
863+
),
864+
pytest.param(None, None, None, False, "uv", "0.5.0", 0, id="no_uv_0.5"),
736865
],
737866
)
738867
def test_find_uv(
739868
monkeypatch: pytest.MonkeyPatch,
869+
uv_env: str | None,
740870
which_result: str | None,
741871
find_uv_bin_result: str | None,
742872
found: bool,
743873
path: str,
744874
vers: str,
745875
vers_rc: int,
746876
) -> None:
877+
monkeypatch.delenv("UV", raising=False)
878+
if uv_env:
879+
monkeypatch.setenv("UV", uv_env)
880+
747881
def find_uv_bin() -> str:
748882
if find_uv_bin_result:
749883
return find_uv_bin_result
750884
raise FileNotFoundError()
751885

752-
def mock_run(*args: object, **kwargs: object) -> subprocess.CompletedProcess[str]:
886+
def mock_run(*args: Any, **kwargs: object) -> subprocess.CompletedProcess[str]:
887+
if version.Version(vers) < version.Version("0.7.0") and "self" in args[0]:
888+
return subprocess.CompletedProcess(
889+
args=args[0],
890+
returncode=2,
891+
)
753892
return subprocess.CompletedProcess(
754-
args=["uv", "version", "--output-format", "json"],
893+
args=args[0],
755894
stdout=f'{{"version": "{vers}", "commit_info": null}}',
756895
returncode=vers_rc,
757896
)
758897

759898
monkeypatch.setattr(subprocess, "run", mock_run)
760899

761-
monkeypatch.setattr(shutil, "which", lambda _: which_result)
900+
monkeypatch.setattr(
901+
shutil, "which", lambda x: which_result if x.endswith("uv") else uv_env
902+
)
762903
monkeypatch.setattr(Path, "samefile", lambda a, b: a == b)
763904
monkeypatch.setitem(
764905
sys.modules, "uv", types.SimpleNamespace(find_uv_bin=find_uv_bin)
@@ -787,7 +928,7 @@ def test_uv_version(
787928
) -> None:
788929
def mock_run(*args: object, **kwargs: object) -> subprocess.CompletedProcess[str]:
789930
return subprocess.CompletedProcess(
790-
args=["uv", "version", "--output-format", "json"],
931+
args=["uv", "self", "version", "--output-format", "json"],
791932
stdout=stdout,
792933
returncode=return_code,
793934
)

0 commit comments

Comments
 (0)