diff --git a/docs/changelog/2972.bugfix.rst b/docs/changelog/2972.bugfix.rst new file mode 100644 index 000000000..5e38f237e --- /dev/null +++ b/docs/changelog/2972.bugfix.rst @@ -0,0 +1 @@ +Patch get_interpreter to handle missing cache and app_data - by :user:`esafak` diff --git a/src/virtualenv/discovery/builtin.py b/src/virtualenv/discovery/builtin.py index 3c6dcb2fd..1e4364db5 100644 --- a/src/virtualenv/discovery/builtin.py +++ b/src/virtualenv/discovery/builtin.py @@ -77,6 +77,25 @@ def get_interpreter( cache=None, env: Mapping[str, str] | None = None, ) -> PythonInfo | None: + """ + Find an interpreter that matches a given specification. + + :param key: the specification of the interpreter to find + :param try_first_with: a list of interpreters to try first + :param app_data: the application data folder + :param cache: a cache of python information + :param env: the environment to use + :return: the interpreter if found, otherwise None + """ + if cache is None: + # Import locally to avoid a circular dependency + from virtualenv.app_data import AppDataDisabled # noqa: PLC0415 + from virtualenv.cache import FileCache # noqa: PLC0415 + + if app_data is None: + app_data = AppDataDisabled() + cache = FileCache(store_factory=app_data.py_info, clearer=app_data.py_info_clear) + spec = PythonSpec.from_string_spec(key) LOGGER.info("find interpreter for spec %r", spec) proposed_paths = set() diff --git a/tests/unit/discovery/test_discovery.py b/tests/unit/discovery/test_discovery.py index f517101e2..68de1da56 100644 --- a/tests/unit/discovery/test_discovery.py +++ b/tests/unit/discovery/test_discovery.py @@ -224,6 +224,18 @@ def test_returns_second_python_specified_when_more_than_one_is_specified_and_env assert result == mocker.sentinel.python_from_cli +def test_get_interpreter_no_cache_no_app_data(): + """Test that get_interpreter can be called without cache and app_data.""" + # A call to a valid interpreter should succeed and return a PythonInfo object. + interpreter = get_interpreter(sys.executable, []) + assert interpreter is not None + assert Path(interpreter.executable).is_file() + + # A call to an invalid interpreter should not fail and should return None. + interpreter = get_interpreter("a-python-that-does-not-exist", []) + assert interpreter is None + + def test_discovery_absolute_path_with_try_first(tmp_path): good_env = tmp_path / "good" bad_env = tmp_path / "bad"