Skip to content

Commit 505b96c

Browse files
Makes --python command-line flag take precedence over VIRTUALENV_PYTHON env var. Fixes pypa#2285
1 parent 7f450c3 commit 505b96c

File tree

2 files changed

+56
-1
lines changed

2 files changed

+56
-1
lines changed

src/virtualenv/discovery/builtin.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import logging
44
import os
55
import sys
6+
from collections import deque
67
from contextlib import suppress
78
from pathlib import Path
89
from typing import TYPE_CHECKING
@@ -22,13 +23,16 @@
2223

2324

2425
class Builtin(Discover):
25-
python_spec: Sequence[str]
26+
python_spec: Sequence[str] | deque[str]
2627
app_data: AppData
2728
try_first_with: Sequence[str]
2829

2930
def __init__(self, options) -> None:
3031
super().__init__(options)
3132
self.python_spec = options.python or [sys.executable]
33+
if self._env.get("VIRTUALENV_PYTHON"):
34+
self.python_spec = deque(self.python_spec)
35+
self.python_spec.rotate(-1)
3236
self.app_data = options.app_data
3337
self.try_first_with = options.try_first_with
3438

tests/unit/discovery/test_discovery.py

+51
Original file line numberDiff line numberDiff line change
@@ -99,3 +99,54 @@ def test_discovery_fallback_ok(session_app_data, caplog):
9999
assert result.executable == sys.executable, caplog.text
100100

101101
assert "accepted" in caplog.text
102+
103+
104+
@pytest.fixture
105+
def mock_get_interpreter(mocker):
106+
return mocker.patch(
107+
"virtualenv.discovery.builtin.get_interpreter",
108+
lambda key, *args, **kwargs: getattr(mocker.sentinel, key)
109+
)
110+
111+
112+
def test_returns_first_python_specified_when_only_env_var_one_is_specified(
113+
mock_get_interpreter, mocker, monkeypatch, session_app_data
114+
):
115+
monkeypatch.setenv("VIRTUALENV_PYTHON", "python_from_env_var")
116+
builtin = Builtin(
117+
Namespace(app_data=session_app_data, try_first_with=[], python=["python_from_env_var"], env=os.environ),
118+
)
119+
120+
result = builtin.run()
121+
122+
assert result == mocker.sentinel.python_from_env_var
123+
124+
125+
def test_returns_second_python_specified_when_more_than_one_is_specified_and_env_var_is_specified(
126+
mock_get_interpreter, mocker, monkeypatch, session_app_data
127+
):
128+
monkeypatch.setenv("VIRTUALENV_PYTHON", "python_from_env_var")
129+
builtin = Builtin(
130+
Namespace(
131+
app_data=session_app_data,
132+
try_first_with=[],
133+
python=["python_from_env_var", "python_from_cli"],
134+
env=os.environ),
135+
)
136+
137+
result = builtin.run()
138+
139+
assert result == mocker.sentinel.python_from_cli
140+
141+
142+
def test_returns_first_python_specified_when_no_env_var_is_specified(
143+
mock_get_interpreter, mocker, monkeypatch, session_app_data
144+
):
145+
monkeypatch.delenv("VIRTUALENV_PYTHON", raising=False)
146+
builtin = Builtin(
147+
Namespace(app_data=session_app_data, try_first_with=[], python=["python_from_cli"], env=os.environ),
148+
)
149+
150+
result = builtin.run()
151+
152+
assert result == mocker.sentinel.python_from_cli

0 commit comments

Comments
 (0)