Skip to content

Commit dfdae19

Browse files
fix: Python in PATH takes precedence over uv-managed Pythons (#2952)
1 parent 1f4c76e commit dfdae19

File tree

3 files changed

+25
-14
lines changed

3 files changed

+25
-14
lines changed

docs/changelog/2952.bugfix.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Python in PATH takes precedence over uv-managed python. Contributed by :user:`edgarrmondragon`.

src/virtualenv/discovery/builtin.py

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -161,20 +161,7 @@ def propose_interpreters( # noqa: C901, PLR0912, PLR0915
161161
tested_exes.add(exe_id)
162162
yield interpreter, True
163163

164-
# 4. otherwise try uv-managed python (~/.local/share/uv/python or platform equivalent)
165-
if uv_python_dir := os.getenv("UV_PYTHON_INSTALL_DIR"):
166-
uv_python_path = Path(uv_python_dir).expanduser()
167-
elif xdg_data_home := os.getenv("XDG_DATA_HOME"):
168-
uv_python_path = Path(xdg_data_home).expanduser() / "uv" / "python"
169-
else:
170-
uv_python_path = user_data_path("uv") / "python"
171-
172-
for exe_path in uv_python_path.glob("*/bin/python"):
173-
interpreter = PathPythonInfo.from_exe(str(exe_path), app_data, raise_on_error=False, env=env)
174-
if interpreter is not None:
175-
yield interpreter, True
176-
177-
# finally just find on path, the path order matters (as the candidates are less easy to control by end user)
164+
# try to find on path, the path order matters (as the candidates are less easy to control by end user)
178165
find_candidates = path_exe_finder(spec)
179166
for pos, path in enumerate(get_paths(env)):
180167
LOGGER.debug(LazyPathDump(pos, path, env))
@@ -188,6 +175,19 @@ def propose_interpreters( # noqa: C901, PLR0912, PLR0915
188175
if interpreter is not None:
189176
yield interpreter, impl_must_match
190177

178+
# otherwise try uv-managed python (~/.local/share/uv/python or platform equivalent)
179+
if uv_python_dir := os.getenv("UV_PYTHON_INSTALL_DIR"):
180+
uv_python_path = Path(uv_python_dir).expanduser()
181+
elif xdg_data_home := os.getenv("XDG_DATA_HOME"):
182+
uv_python_path = Path(xdg_data_home).expanduser() / "uv" / "python"
183+
else:
184+
uv_python_path = user_data_path("uv") / "python"
185+
186+
for exe_path in uv_python_path.glob("*/bin/python"):
187+
interpreter = PathPythonInfo.from_exe(str(exe_path), app_data, raise_on_error=False, env=env)
188+
if interpreter is not None:
189+
yield interpreter, True
190+
191191

192192
def get_paths(env: Mapping[str, str]) -> Generator[Path, None, None]:
193193
path = env.get("PATH", None)

tests/unit/discovery/test_discovery.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,16 @@ def test_uv_python(monkeypatch, tmp_path_factory, mocker):
105105
mock_from_exe.assert_called_once()
106106
assert mock_from_exe.call_args[0][0] == str(bin_path / "python")
107107

108+
# PATH takes precedence
109+
mock_from_exe.reset_mock()
110+
python_exe = "python.exe" if IS_WIN else "python"
111+
dir_in_path = tmp_path_factory.mktemp("path_bin_dir")
112+
dir_in_path.joinpath(python_exe).touch()
113+
m.setenv("PATH", str(dir_in_path))
114+
get_interpreter("python", [])
115+
mock_from_exe.assert_called_once()
116+
assert mock_from_exe.call_args[0][0] == str(dir_in_path / python_exe)
117+
108118
# XDG_DATA_HOME
109119
xdg_data_home = tmp_path_factory.mktemp("xdg_data_home")
110120
with patch("virtualenv.discovery.builtin.PathPythonInfo.from_exe") as mock_from_exe, monkeypatch.context() as m:

0 commit comments

Comments
 (0)