Skip to content

Commit 51e6b3f

Browse files
committed
use uv for creating virtual environments and installing packages when available
1 parent b9be6ec commit 51e6b3f

File tree

3 files changed

+80
-24
lines changed

3 files changed

+80
-24
lines changed

tests/requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
-e ..
2-
virtualenv
2+
uv
33
maturin==1.5.0
44
pytest
55
junit2html

tests/runner.py

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -100,16 +100,33 @@ def _run_test_in_environment(
100100
sys.exit(proc.returncode)
101101

102102

103+
def _pip_install_command(interpreter_path: Path) -> list[str]:
104+
if shutil.which("uv") is not None:
105+
log.info("using uv to install packages")
106+
return [
107+
"uv",
108+
"pip",
109+
"install",
110+
"--python",
111+
str(interpreter_path),
112+
]
113+
else:
114+
log.info("using pip to install packages")
115+
return [
116+
str(interpreter_path),
117+
"-m",
118+
"pip",
119+
"install",
120+
"--disable-pip-version-check",
121+
]
122+
123+
103124
def _create_test_venv(python: Path, venv_dir: Path) -> VirtualEnv:
104125
venv = VirtualEnv.new(venv_dir, python)
105126
log.info("installing test requirements into virtualenv")
106127
proc = subprocess.run(
107128
[
108-
str(venv.interpreter_path),
109-
"-m",
110-
"pip",
111-
"install",
112-
"--disable-pip-version-check",
129+
*_pip_install_command(venv.interpreter_path),
113130
"-r",
114131
"requirements.txt",
115132
],
@@ -120,13 +137,25 @@ def _create_test_venv(python: Path, venv_dir: Path) -> VirtualEnv:
120137
if proc.returncode != 0:
121138
log.error(proc.stdout.decode())
122139
log.error(proc.stderr.decode())
123-
msg = "pip install failed"
140+
msg = "package installation failed"
124141
raise RuntimeError(msg)
125142
log.debug("%s", proc.stdout.decode())
126143
log.info("test environment ready")
127144
return venv
128145

129146

147+
def _create_virtual_env_command(interpreter_path: Path, venv_path: Path) -> list[str]:
148+
if shutil.which("uv") is not None:
149+
log.info("using uv to create virtual environments")
150+
return ["uv", "venv", "--seed", "--python", str(interpreter_path), str(venv_path)]
151+
elif shutil.which("virtualenv") is not None:
152+
log.info("using virtualenv to create virtual environments")
153+
return ["virtualenv", "--python", str(interpreter_path), str(venv_path)]
154+
else:
155+
log.info("using venv to create virtual environments")
156+
return [str(interpreter_path), "-m", "venv", str(venv_path)]
157+
158+
130159
class VirtualEnv:
131160
def __init__(self, root: Path) -> None:
132161
self._root = root.resolve()
@@ -140,7 +169,7 @@ def new(root: Path, interpreter_path: Path) -> VirtualEnv:
140169
if not interpreter_path.exists():
141170
raise FileNotFoundError(interpreter_path)
142171
log.info("creating test virtualenv at '%s' from '%s'", root, interpreter_path)
143-
cmd = ["virtualenv", "--python", str(interpreter_path), str(root)]
172+
cmd = _create_virtual_env_command(interpreter_path, root)
144173
proc = subprocess.run(cmd, capture_output=True, check=True)
145174
log.debug("%s", proc.stdout.decode())
146175
assert root.is_dir()

tests/test_import_hook/test_project_importer.py

Lines changed: 43 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1342,30 +1342,47 @@ def _rebuilt_message(project_name: str) -> str:
13421342
return f'rebuilt and loaded package "{with_underscores(project_name)}"'
13431343

13441344

1345+
_UV_AVAILABLE = None
1346+
1347+
1348+
def uv_available() -> bool:
1349+
"""whether the `uv` command is installed"""
1350+
global _UV_AVAILABLE
1351+
if _UV_AVAILABLE is None:
1352+
_UV_AVAILABLE = shutil.which("uv") is not None
1353+
return _UV_AVAILABLE
1354+
1355+
13451356
def _uninstall(*project_names: str) -> None:
13461357
log.info("uninstalling %s", sorted(project_names))
1347-
subprocess.check_call([
1348-
sys.executable,
1349-
"-m",
1350-
"pip",
1351-
"uninstall",
1352-
"--disable-pip-version-check",
1353-
"-y",
1354-
*project_names,
1355-
])
1358+
if uv_available():
1359+
cmd = ["uv", "pip", "uninstall", "--python", str(sys.executable), "-y", *project_names]
1360+
else:
1361+
cmd = [
1362+
sys.executable,
1363+
"-m",
1364+
"pip",
1365+
"uninstall",
1366+
"--disable-pip-version-check",
1367+
"-y",
1368+
*project_names,
1369+
]
1370+
subprocess.check_call(cmd)
13561371

13571372

13581373
def _get_installed_package_names() -> set[str]:
1359-
packages = json.loads(
1360-
subprocess.check_output([
1374+
if uv_available():
1375+
cmd = ["uv", "pip", "list", "--python", sys.executable, "--format", "json"]
1376+
else:
1377+
cmd = [
13611378
sys.executable,
13621379
"-m",
13631380
"pip",
13641381
"--disable-pip-version-check",
13651382
"list",
13661383
"--format=json",
1367-
]).decode()
1368-
)
1384+
]
1385+
packages = json.loads(subprocess.check_output(cmd).decode())
13691386
return {package["name"] for package in packages}
13701387

13711388

@@ -1382,7 +1399,11 @@ def _install_editable(project_dir: Path) -> None:
13821399

13831400
def _install_non_editable(project_dir: Path) -> None:
13841401
log.info("installing %s in non-editable mode", project_dir.name)
1385-
subprocess.check_call([sys.executable, "-m", "pip", "install", "--disable-pip-version-check", str(project_dir)])
1402+
if uv_available():
1403+
cmd = ["uv", "pip", "install", "--python", sys.executable, str(project_dir)]
1404+
else:
1405+
cmd = [sys.executable, "-m", "pip", "install", "--disable-pip-version-check", str(project_dir)]
1406+
subprocess.check_call(cmd)
13861407

13871408

13881409
def _is_installed_as_pth(project_name: str) -> bool:
@@ -1415,14 +1436,20 @@ def _is_editable_installed_correctly(project_name: str, project_dir: Path, is_mi
14151436
installed_editable_with_direct_url,
14161437
)
14171438

1439+
if uv_available():
1440+
# TODO(matt): use uv once the --files option is supported https://github.com/astral-sh/uv/issues/2526
1441+
cmd = [sys.executable, "-m", "pip", "show", "--disable-pip-version-check", "-f", project_name]
1442+
else:
1443+
cmd = [sys.executable, "-m", "pip", "show", "--disable-pip-version-check", "-f", project_name]
1444+
14181445
proc = subprocess.run(
1419-
[sys.executable, "-m", "pip", "show", "--disable-pip-version-check", "-f", project_name],
1446+
cmd,
14201447
stdout=subprocess.PIPE,
14211448
stderr=subprocess.STDOUT,
14221449
check=False,
14231450
)
14241451
output = "None" if proc.stdout is None else proc.stdout.decode()
1425-
log.info("pip output (returned %s):\n%s", proc.returncode, output)
1452+
log.info("command output (returned %s):\n%s", proc.returncode, output)
14261453
return installed_editable_with_direct_url and (installed_as_pth == is_mixed)
14271454

14281455

0 commit comments

Comments
 (0)