Skip to content

Commit 1f398a0

Browse files
RomainBraultpre-commit-ci[bot]gaborbernat
authored
Python preference (#70)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Bernát Gábor <[email protected]>
1 parent 00cb9a6 commit 1f398a0

File tree

4 files changed

+70
-3
lines changed

4 files changed

+70
-3
lines changed

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,14 @@ This is a uv specific feature that may be used as an alternative to frozen const
4242
intention is to validate the lower bounds of your dependencies during test executions.
4343

4444
[resolution strategy]: https://github.com/astral-sh/uv/blob/0.1.20/README.md#resolution-strategy
45+
46+
### uv_python_preference
47+
48+
This flag, set on a tox environment level, controls how uv select the Python
49+
interpreter.
50+
51+
By default, uv will attempt to use Python versions found on the system and only
52+
download managed interpreters when necessary. However, It's possible to adjust
53+
uv's Python version selection preference with the
54+
[python-preference](https://docs.astral.sh/uv/python-versions/#adjusting-python-version-preferences)
55+
option.

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ dependencies = [
4343
"importlib-resources>=6.4; python_version<'3.9'",
4444
"packaging>=24",
4545
"tox<5,>=4.15",
46+
"typing-extensions>=4.12.2; python_version<'3.10'",
4647
"uv<1,>=0.2.5",
4748
]
4849
optional-dependencies.test = [

src/tox_uv/_venv.py

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,18 @@
1616

1717
from pathlib import Path
1818
from platform import python_implementation
19-
from typing import TYPE_CHECKING, Any, cast
19+
from typing import TYPE_CHECKING, Any, Literal, Optional, Type, cast
2020

2121
from tox.execute.local_sub_process import LocalSubProcessExecutor
2222
from tox.execute.request import StdinSource
2323
from tox.tox_env.errors import Skip
2424
from tox.tox_env.python.api import Python, PythonInfo, VersionInfo
25+
26+
if sys.version_info >= (3, 10): # pragma: no cover (py310+)
27+
from typing import TypeAlias
28+
else: # pragma: no cover (<py310)
29+
from typing_extensions import TypeAlias
30+
2531
from uv import find_uv_bin
2632
from virtualenv import app_data
2733
from virtualenv.discovery import cached_py_info
@@ -35,6 +41,9 @@
3541
from tox.tox_env.installer import Installer
3642

3743

44+
PythonPreference: TypeAlias = Literal["only-managed", "installed", "managed", "system", "only-system"]
45+
46+
3847
class UvVenv(Python, ABC):
3948
def __init__(self, create_args: ToxEnvCreateArgs) -> None:
4049
self._executor: Execute | None = None
@@ -44,12 +53,31 @@ def __init__(self, create_args: ToxEnvCreateArgs) -> None:
4453

4554
def register_config(self) -> None:
4655
super().register_config()
47-
desc = "add seed packages to the created venv"
48-
self.conf.add_config(keys=["uv_seed"], of_type=bool, default=False, desc=desc)
56+
self.conf.add_config(
57+
keys=["uv_seed"],
58+
of_type=bool,
59+
default=False,
60+
desc="add seed packages to the created venv",
61+
)
62+
# The cast(...) might seems superfluous but removing it
63+
# makes mypy crash. The problem is probably on tox's typing side
64+
self.conf.add_config(
65+
keys=["uv_python_preference"],
66+
of_type=cast(Type[Optional[PythonPreference]], Optional[PythonPreference]),
67+
default=None,
68+
desc=(
69+
"Whether to prefer using Python installations that are already"
70+
" present on the system, or those that are downloaded and"
71+
" installed by uv [possible values: only-managed, installed,"
72+
" managed, system, only-system]. Use none to use uv's"
73+
" default."
74+
),
75+
)
4976

5077
def python_cache(self) -> dict[str, Any]:
5178
result = super().python_cache()
5279
result["seed"] = self.conf["uv_seed"]
80+
result["python_preference"] = self.conf["uv_python_preference"]
5381
result["venv"] = str(self.venv_dir.relative_to(self.env_dir))
5482
return result
5583

@@ -158,6 +186,8 @@ def create_python_env(self) -> None:
158186
cmd.append("-v")
159187
if self.conf["uv_seed"]:
160188
cmd.append("--seed")
189+
if self.conf["uv_python_preference"]:
190+
cmd.extend(["--python-preference", self.conf["uv_python_preference"]])
161191
cmd.append(str(self.venv_dir))
162192
outcome = self.execute(cmd, stdin=StdinSource.OFF, run_id="venv", show=None)
163193

tests/test_tox_uv_venv.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,31 @@ def test_uv_env_python(tox_project: ToxProjectCreator) -> None:
176176
assert env_bin_dir in result.out
177177

178178

179+
@pytest.mark.parametrize(
180+
"preference",
181+
["only-managed", "installed", "managed", "system", "only-system"],
182+
)
183+
def test_uv_env_python_preference(
184+
tox_project: ToxProjectCreator,
185+
*,
186+
preference: str,
187+
) -> None:
188+
project = tox_project({
189+
"tox.ini": (
190+
"[testenv]\n"
191+
"package=skip\n"
192+
f"uv_python_preference={preference}\n"
193+
"commands=python -c 'print(\"{env_python}\")'"
194+
)
195+
})
196+
result = project.run("-vv")
197+
result.assert_success()
198+
199+
exe = "python.exe" if sys.platform == "win32" else "python"
200+
env_bin_dir = str(project.path / ".tox" / "py" / ("Scripts" if sys.platform == "win32" else "bin") / exe)
201+
assert env_bin_dir in result.out
202+
203+
179204
def test_uv_env_site_package_dir_run(tox_project: ToxProjectCreator) -> None:
180205
project = tox_project({"tox.ini": "[testenv]\npackage=skip\ncommands=python -c 'print(\"{envsitepackagesdir}\")'"})
181206
result = project.run("-vv")

0 commit comments

Comments
 (0)