Skip to content

Commit

Permalink
use uv for creating virtual environments and installing packages when…
Browse files Browse the repository at this point in the history
… available
  • Loading branch information
mbway committed Jun 16, 2024
1 parent b9be6ec commit efd473a
Show file tree
Hide file tree
Showing 3 changed files with 80 additions and 24 deletions.
2 changes: 1 addition & 1 deletion tests/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
-e ..
virtualenv
uv
maturin==1.5.0
pytest
junit2html
Expand Down
43 changes: 36 additions & 7 deletions tests/runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,16 +100,33 @@ def _run_test_in_environment(
sys.exit(proc.returncode)


def _pip_install_command(interpreter_path: Path) -> list[str]:
if shutil.which("uv") is not None:
log.info("using uv to install packages")
return [
"uv",
"pip",
"install",
"--python",
str(interpreter_path),
]
else:
log.info("using pip to install packages")
return [
str(interpreter_path),
"-m",
"pip",
"install",
"--disable-pip-version-check",
]


def _create_test_venv(python: Path, venv_dir: Path) -> VirtualEnv:
venv = VirtualEnv.new(venv_dir, python)
log.info("installing test requirements into virtualenv")
proc = subprocess.run(
[
str(venv.interpreter_path),
"-m",
"pip",
"install",
"--disable-pip-version-check",
*_pip_install_command(venv.interpreter_path),
"-r",
"requirements.txt",
],
Expand All @@ -120,13 +137,25 @@ def _create_test_venv(python: Path, venv_dir: Path) -> VirtualEnv:
if proc.returncode != 0:
log.error(proc.stdout.decode())
log.error(proc.stderr.decode())
msg = "pip install failed"
msg = "package installation failed"
raise RuntimeError(msg)
log.debug("%s", proc.stdout.decode())
log.info("test environment ready")
return venv


def _create_virtual_env_command(interpreter_path: Path, venv_path: Path) -> list[str]:
if shutil.which("uv") is not None:
log.info("using uv to create virtual environments")
return ["uv", "venv", "--seed", "--python", str(interpreter_path), str(venv_path)]
elif shutil.which("virtualenv") is not None:
log.info("using virtualenv to create virtual environments")
return ["virtualenv", "--python", str(interpreter_path), str(venv_path)]
else:
log.info("using venv to create virtual environments")
return [str(interpreter_path), "-m", "venv", str(venv_path)]


class VirtualEnv:
def __init__(self, root: Path) -> None:
self._root = root.resolve()
Expand All @@ -140,7 +169,7 @@ def new(root: Path, interpreter_path: Path) -> VirtualEnv:
if not interpreter_path.exists():
raise FileNotFoundError(interpreter_path)
log.info("creating test virtualenv at '%s' from '%s'", root, interpreter_path)
cmd = ["virtualenv", "--python", str(interpreter_path), str(root)]
cmd = _create_virtual_env_command(interpreter_path, root)
proc = subprocess.run(cmd, capture_output=True, check=True)
log.debug("%s", proc.stdout.decode())
assert root.is_dir()
Expand Down
59 changes: 43 additions & 16 deletions tests/test_import_hook/test_project_importer.py
Original file line number Diff line number Diff line change
Expand Up @@ -1342,30 +1342,47 @@ def _rebuilt_message(project_name: str) -> str:
return f'rebuilt and loaded package "{with_underscores(project_name)}"'


_UV_AVAILABLE = None


def uv_available() -> bool:
"""whether the `uv` command is installed"""
global _UV_AVAILABLE
if _UV_AVAILABLE is None:
_UV_AVAILABLE = shutil.which("uv") is not None
return _UV_AVAILABLE


def _uninstall(*project_names: str) -> None:
log.info("uninstalling %s", sorted(project_names))
subprocess.check_call([
sys.executable,
"-m",
"pip",
"uninstall",
"--disable-pip-version-check",
"-y",
*project_names,
])
if uv_available():
cmd = ["uv", "pip", "uninstall", "--python", str(sys.executable), *project_names]
else:
cmd = [
sys.executable,
"-m",
"pip",
"uninstall",
"--disable-pip-version-check",
"-y",
*project_names,
]
subprocess.check_call(cmd)


def _get_installed_package_names() -> set[str]:
packages = json.loads(
subprocess.check_output([
if uv_available():
cmd = ["uv", "pip", "list", "--python", sys.executable, "--format", "json"]
else:
cmd = [
sys.executable,
"-m",
"pip",
"--disable-pip-version-check",
"list",
"--format=json",
]).decode()
)
]
packages = json.loads(subprocess.check_output(cmd).decode())
return {package["name"] for package in packages}


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

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


def _is_installed_as_pth(project_name: str) -> bool:
Expand Down Expand Up @@ -1415,14 +1436,20 @@ def _is_editable_installed_correctly(project_name: str, project_dir: Path, is_mi
installed_editable_with_direct_url,
)

if uv_available():
# TODO(matt): use uv once the --files option is supported https://github.com/astral-sh/uv/issues/2526
cmd = [sys.executable, "-m", "pip", "show", "--disable-pip-version-check", "-f", project_name]
else:
cmd = [sys.executable, "-m", "pip", "show", "--disable-pip-version-check", "-f", project_name]

proc = subprocess.run(
[sys.executable, "-m", "pip", "show", "--disable-pip-version-check", "-f", project_name],
cmd,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
check=False,
)
output = "None" if proc.stdout is None else proc.stdout.decode()
log.info("pip output (returned %s):\n%s", proc.returncode, output)
log.info("command output (returned %s):\n%s", proc.returncode, output)
return installed_editable_with_direct_url and (installed_as_pth == is_mixed)


Expand Down

0 comments on commit efd473a

Please sign in to comment.