Skip to content

Commit 8abd78d

Browse files
committed
Use uv venv instead of python venv when available
Adds a huge speed boost in creating virtualenv by taking advantage of uv when it is found as being installed.
1 parent fde5db1 commit 8abd78d

File tree

7 files changed

+69
-16
lines changed

7 files changed

+69
-16
lines changed

.config/requirements-test.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@ ruff
1212
toml-sort
1313
tox
1414
types-PyYAML
15+
uv

.pre-commit-config.yaml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,6 @@ repos:
2121
rev: v3.1.0
2222
hooks:
2323
- id: add-trailing-comma
24-
args:
25-
- --py36-plus
2624

2725
- repo: https://github.com/Lucas-C/pre-commit-hooks.git
2826
rev: v1.5.5
@@ -101,11 +99,12 @@ repos:
10199
hooks:
102100
- id: mypy
103101
additional_dependencies:
104-
- pytest
105102
- pip
103+
- pytest
106104
- subprocess_tee
107105
- types-pyyaml
108106
- types-setuptools
107+
- uv
109108
# Override default pre-commit '--ignore-missing-imports'
110109
args: [--strict]
111110

docs/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ For more information about communication, see the [Ansible communication guide](
2222
- Checks for missing system packages
2323
- Symlinks the current collection into the current python interpreter's site-packages
2424
- Install all collection collection dependencies into the current python interpreter's site-packages
25+
- Uses `uv env` instead of python's venv when available to boost performance. Can be disabled with `SKIP_UV=1`
2526

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

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ allow-init-docstring = true
7070
arg-type-hints-in-docstring = false
7171
baseline = ".config/pydoclint-baseline.txt"
7272
check-return-types = false
73-
exclude = '\.git|\.tox|build|out|venv'
73+
exclude = '\.git|\.tox|\.venv|build|out|venv'
7474
should-document-private-class-attributes = true
7575
show-filenames-in-every-violation-message = true
7676
skip-checking-short-docstrings = false

src/ansible_dev_environment/config.py

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from __future__ import annotations
44

55
import json
6+
import logging
67
import os
78
import shutil
89
import subprocess
@@ -21,10 +22,40 @@
2122
from .utils import TermFeatures
2223

2324

24-
class Config:
25-
"""The application configuration."""
25+
_logger = logging.getLogger(__name__)
26+
27+
28+
def use_uv() -> bool:
29+
"""Return whether to use uv commands like venv or pip.
30+
31+
Returns:
32+
True if uv is to be used.
33+
"""
34+
if int(os.environ.get("SKIP_UV", "0")):
35+
return False
36+
try:
37+
import uv # noqa: F401
38+
except ImportError:
39+
pass
40+
else:
41+
_logger.info(
42+
"UV detected and will be used instead of venv/pip. To disable that define SKIP_UP=1 in your environment.",
43+
)
44+
return True
45+
return False
46+
47+
48+
class Config: # pylint: disable=too-many-instance-attributes
49+
"""The application configuration.
50+
51+
Attributes:
52+
pip_cmd: The pip command.
53+
venv_cmd: The venv command.
54+
"""
55+
56+
pip_cmd: str
57+
venv_cmd: str
2658

27-
# pylint: disable=too-many-instance-attributes
2859
def __init__(
2960
self,
3061
args: Namespace,
@@ -144,11 +175,18 @@ def _set_interpreter(
144175
self,
145176
) -> None:
146177
"""Set the interpreter."""
178+
self.pip_cmd = f"{sys.executable} -m pip"
179+
self.venv_cmd = f"{sys.executable} -m venv"
180+
if use_uv():
181+
self.pip_cmd = f"{sys.executable} -m uv pip"
182+
# seed and python-preference make uv venv match python -m venv behavior:
183+
self.venv_cmd = f"{sys.executable} -m uv venv --seed --python-preference=system"
184+
147185
if not self.venv.exists():
148186
if self._create_venv:
149187
msg = f"Creating virtual environment: {self.venv}"
150188
self._output.debug(msg)
151-
command = f"python -m venv {self.venv}"
189+
command = f"{self.venv_cmd} {self.venv}"
152190
msg = f"Creating virtual environment: {self.venv}"
153191
if self.args.system_site_packages:
154192
command = f"{command} --system-site-packages"

src/ansible_dev_environment/subcommands/installer.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -505,10 +505,7 @@ def _pip_install(self) -> None:
505505
msg = "Installing python requirements."
506506
self._output.info(msg)
507507

508-
command = (
509-
f"{self._config.venv_interpreter} -m pip install"
510-
f" -r {self._config.discovered_python_reqs}"
511-
)
508+
command = f"{self._config.pip_cmd} install" f" -r {self._config.discovered_python_reqs}"
512509

513510
msg = f"Installing python requirements from {self._config.discovered_python_reqs}"
514511
self._output.debug(msg)

tests/unit/test_config.py

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,20 @@ def gen_args(
4242

4343

4444
@pytest.mark.parametrize(
45-
"system_site_packages",
46-
((True, False)),
47-
ids=["ssp_true", "ssp_false"],
45+
("system_site_packages", "uv"),
46+
(
47+
pytest.param(True, False, id="ssp1-uv0"),
48+
pytest.param(False, False, id="ssp0-uv0"),
49+
pytest.param(True, True, id="ssp1-uv1"),
50+
pytest.param(False, True, id="ssp0-uv1"),
51+
),
4852
)
4953
def test_paths(
54+
*,
5055
tmpdir: Path,
51-
system_site_packages: bool, # noqa: FBT001
56+
system_site_packages: bool,
57+
uv: bool,
58+
monkeypatch: pytest.MonkeyPatch,
5259
output: Output,
5360
) -> None:
5461
"""Test the paths.
@@ -58,8 +65,11 @@ def test_paths(
5865
Args:
5966
tmpdir: A temporary directory.
6067
system_site_packages: Whether to include system site packages.
68+
uv: Whether to use the uv module.
69+
monkeypatch: A pytest fixture for monkey patching.
6170
output: The output fixture.
6271
"""
72+
monkeypatch.setenv("SKIP_UV", "0" if uv else "1")
6373
venv = tmpdir / "test_venv"
6474
args = gen_args(
6575
venv=str(venv),
@@ -69,6 +79,13 @@ def test_paths(
6979
config = Config(args=args, output=output, term_features=output.term_features)
7080
config.init()
7181

82+
if uv:
83+
assert "-m uv venv" in config.venv_cmd
84+
assert "-m uv pip" in config.pip_cmd
85+
else:
86+
assert "-m venv" in config.venv_cmd
87+
assert "-m pip" in config.pip_cmd
88+
7289
assert config.venv == venv
7390
for attr in (
7491
"site_pkg_collections_path",

0 commit comments

Comments
 (0)