Skip to content

Commit

Permalink
Use uv venv instead of python venv when available
Browse files Browse the repository at this point in the history
Adds a huge speed boost in creating virtualenv by taking advantage
of uv when it is found as being installed.
  • Loading branch information
ssbarnea committed Dec 20, 2024
1 parent fde5db1 commit 58936b9
Show file tree
Hide file tree
Showing 10 changed files with 87 additions and 25 deletions.
1 change: 1 addition & 0 deletions .config/dictionary.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ argvalues
bindep
bindir
bthornto
caplog
capsys
cauthor
cdescription
Expand Down
1 change: 1 addition & 0 deletions .config/requirements-test.in
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ ruff
toml-sort
tox
types-PyYAML
uv
5 changes: 2 additions & 3 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,6 @@ repos:
rev: v3.1.0
hooks:
- id: add-trailing-comma
args:
- --py36-plus

- repo: https://github.com/Lucas-C/pre-commit-hooks.git
rev: v1.5.5
Expand Down Expand Up @@ -101,11 +99,12 @@ repos:
hooks:
- id: mypy
additional_dependencies:
- pytest
- pip
- pytest
- subprocess_tee
- types-pyyaml
- types-setuptools
- uv
# Override default pre-commit '--ignore-missing-imports'
args: [--strict]

Expand Down
1 change: 1 addition & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ For more information about communication, see the [Ansible communication guide](
- Checks for missing system packages
- Symlinks the current collection into the current python interpreter's site-packages
- Install all collection collection dependencies into the current python interpreter's site-packages
- Uses `uv env` instead of python's venv when available to boost performance. Can be disabled with `SKIP_UV=1`

By placing collections into the python site-packages directory they are discoverable by ansible as well as python and pytest.

Expand Down
5 changes: 4 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ allow-init-docstring = true
arg-type-hints-in-docstring = false
baseline = ".config/pydoclint-baseline.txt"
check-return-types = false
exclude = '\.git|\.tox|build|out|venv'
exclude = '\.git|\.tox|\.venv|build|out|venv'
should-document-private-class-attributes = true
show-filenames-in-every-violation-message = true
skip-checking-short-docstrings = false
Expand Down Expand Up @@ -356,6 +356,9 @@ required-imports = ["from __future__ import annotations"]
[tool.ruff.lint.pydocstyle]
convention = "google"

[tool.ruff.lint.pylint]
max-args = 6 # 5 is the default

[tool.setuptools.dynamic]
dependencies = {file = [".config/requirements.in"]}
optional-dependencies.docs = {file = [".config/requirements-docs.in"]}
Expand Down
45 changes: 41 additions & 4 deletions src/ansible_dev_environment/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from __future__ import annotations

import json
import logging
import os
import shutil
import subprocess
Expand All @@ -21,10 +22,39 @@
from .utils import TermFeatures


class Config:
"""The application configuration."""
_logger = logging.getLogger(__name__)


def use_uv() -> bool:
"""Return whether to use uv commands like venv or pip.
Returns:
True if uv is to be used.
"""
if int(os.environ.get("SKIP_UV", "0")):
return False
try:
import uv # noqa: F401
except ImportError: # pragma: no cover
return False
else:
_logger.info(
"UV detected and will be used instead of venv/pip. To disable that define SKIP_UP=1 in your environment.",
)
return True


class Config: # pylint: disable=too-many-instance-attributes
"""The application configuration.
Attributes:
pip_cmd: The pip command.
venv_cmd: The venv command.
"""

pip_cmd: str
venv_cmd: str

# pylint: disable=too-many-instance-attributes
def __init__(
self,
args: Namespace,
Expand Down Expand Up @@ -144,11 +174,18 @@ def _set_interpreter(
self,
) -> None:
"""Set the interpreter."""
self.pip_cmd = f"{sys.executable} -m pip"
self.venv_cmd = f"{sys.executable} -m venv"
if use_uv():
self.pip_cmd = f"{sys.executable} -m uv pip"
# seed and python-preference make uv venv match python -m venv behavior:
self.venv_cmd = f"{sys.executable} -m uv venv --seed --python-preference=system"

if not self.venv.exists():
if self._create_venv:
msg = f"Creating virtual environment: {self.venv}"
self._output.debug(msg)
command = f"python -m venv {self.venv}"
command = f"{self.venv_cmd} {self.venv}"
msg = f"Creating virtual environment: {self.venv}"
if self.args.system_site_packages:
command = f"{command} --system-site-packages"
Expand Down
5 changes: 1 addition & 4 deletions src/ansible_dev_environment/subcommands/installer.py
Original file line number Diff line number Diff line change
Expand Up @@ -505,10 +505,7 @@ def _pip_install(self) -> None:
msg = "Installing python requirements."
self._output.info(msg)

command = (
f"{self._config.venv_interpreter} -m pip install"
f" -r {self._config.discovered_python_reqs}"
)
command = f"{self._config.pip_cmd} install" f" -r {self._config.discovered_python_reqs}"

msg = f"Installing python requirements from {self._config.discovered_python_reqs}"
self._output.debug(msg)
Expand Down
2 changes: 1 addition & 1 deletion src/ansible_dev_environment/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ class Ansi:
GREY = "\x1b[90m"


def subprocess_run( # noqa: PLR0913 # pylint: disable=too-many-positional-arguments
def subprocess_run( # pylint: disable=too-many-positional-arguments
command: str,
verbose: int,
msg: str,
Expand Down
45 changes: 34 additions & 11 deletions tests/unit/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,22 @@ def gen_args(


@pytest.mark.parametrize(
"system_site_packages",
((True, False)),
ids=["ssp_true", "ssp_false"],
("system_site_packages", "uv"),
(
pytest.param(True, False, id="ssp1-uv0"),
pytest.param(False, False, id="ssp0-uv0"),
pytest.param(True, True, id="ssp1-uv1"),
pytest.param(False, True, id="ssp0-uv1"),
),
)
def test_paths(
*,
tmpdir: Path,
system_site_packages: bool, # noqa: FBT001
system_site_packages: bool,
uv: bool,
monkeypatch: pytest.MonkeyPatch,
output: Output,
caplog: pytest.LogCaptureFixture,
) -> None:
"""Test the paths.
Expand All @@ -58,16 +66,31 @@ def test_paths(
Args:
tmpdir: A temporary directory.
system_site_packages: Whether to include system site packages.
uv: Whether to use the uv module.
monkeypatch: A pytest fixture for monkey patching.
output: The output fixture.
caplog: A pytest fixture for capturing logs.
"""
venv = tmpdir / "test_venv"
args = gen_args(
venv=str(venv),
system_site_packages=system_site_packages,
)
with caplog.at_level(10):
monkeypatch.setenv("SKIP_UV", "0" if uv else "1")
venv = tmpdir / "test_venv"
args = gen_args(
venv=str(venv),
system_site_packages=system_site_packages,
)

config = Config(args=args, output=output, term_features=output.term_features)
config.init()

config = Config(args=args, output=output, term_features=output.term_features)
config.init()
if uv:
assert len(caplog.messages) == 1
assert "UV detected" in caplog.records[0].msg
assert "-m uv venv" in config.venv_cmd
assert "-m uv pip" in config.pip_cmd
else:
assert len(caplog.messages) == 0
assert "-m venv" in config.venv_cmd
assert "-m pip" in config.pip_cmd

assert config.venv == venv
for attr in (
Expand Down
2 changes: 1 addition & 1 deletion tests/unit/test_installer.py
Original file line number Diff line number Diff line change
Expand Up @@ -739,7 +739,7 @@ def test_collection_pre_install(

@pytest.mark.parametrize("first", (True, False), ids=["editable", "not_editable"])
@pytest.mark.parametrize("second", (True, False), ids=["editable", "not_editable"])
def test_reinstall_local_collection( # noqa: PLR0913 # pylint: disable=too-many-positional-arguments
def test_reinstall_local_collection( # pylint: disable=too-many-positional-arguments
first: bool, # noqa: FBT001
second: bool, # noqa: FBT001
tmp_path: Path,
Expand Down

0 comments on commit 58936b9

Please sign in to comment.