From b45d746d3e2fcc5026ab53b19693193329b3feaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miro=20Hron=C4=8Dok?= Date: Fri, 6 Mar 2026 17:01:58 +0100 Subject: [PATCH] Adjust for virtualenv 21 API changes --- python-tox.spec | 4 ++ virtualenv-21.patch | 136 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 140 insertions(+) create mode 100644 virtualenv-21.patch diff --git a/python-tox.spec b/python-tox.spec index 7d10c0a..815d3b8 100644 --- a/python-tox.spec +++ b/python-tox.spec @@ -28,6 +28,10 @@ License: MIT URL: https://tox.readthedocs.io/ Source: %{pypi_source tox} +# Adjust for virtualenv 21 API changes, from upsteam, rebased. +# https://github.com/tox-dev/tox/commit/a9533f575f +Patch: virtualenv-21.patch + # Remove usage of devpi-process. # Remove coverage options. # Adjust virtualenv environment variables to make it work with our patched virtualenv. diff --git a/virtualenv-21.patch b/virtualenv-21.patch new file mode 100644 index 0000000..d06b542 --- /dev/null +++ b/virtualenv-21.patch @@ -0,0 +1,136 @@ +From 200b647be443613ecff552e34a76c335ac990780 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Bern=C3=A1t=20G=C3=A1bor?= +Date: Fri, 27 Feb 2026 16:07:38 +0000 +Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20fix(ci):=20resolve=20ty=20type-c?= + =?UTF-8?q?heck=20failures=20(#3837)?= +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +--- + docs/changelog/3837.bugfix.rst | 3 +++ + src/tox/tox_env/python/virtual_env/api.py | 16 +++++++++--- + tests/session/cmd/test_sequential.py | 1 + + .../python/virtual_env/test_virtualenv_api.py | 26 +++++++++++++++++++ + 4 files changed, 42 insertions(+), 4 deletions(-) + create mode 100644 docs/changelog/3837.bugfix.rst + +diff --git a/docs/changelog/3837.bugfix.rst b/docs/changelog/3837.bugfix.rst +new file mode 100644 +index 0000000..5dc14d5 +--- /dev/null ++++ b/docs/changelog/3837.bugfix.rst +@@ -0,0 +1,3 @@ ++Fix type errors flagged by ``ty`` for ``virtualenv`` API changes (``system_executable`` nullability and ++``cached_py_info.PythonInfo`` removal), and correct the CI workflow matrix exclude for ``windows-2025`` - by ++:user:`gaborbernat`. +diff --git a/src/tox/tox_env/python/virtual_env/api.py b/src/tox/tox_env/python/virtual_env/api.py +index 7c56c14..b2a26e7 100644 +--- a/src/tox/tox_env/python/virtual_env/api.py ++++ b/src/tox/tox_env/python/virtual_env/api.py +@@ -10,7 +10,6 @@ from typing import TYPE_CHECKING, Any, cast + + from virtualenv import __version__ as virtualenv_version + from virtualenv import app_data, session_via_cli +-from virtualenv.discovery import cached_py_info + from virtualenv.discovery.py_spec import PythonSpec + + from tox.config.loader.str_convert import StrConvert +@@ -139,13 +138,15 @@ class VirtualEnv(Python, ABC): + interpreter = self.creator.interpreter + except (FileNotFoundError, RuntimeError): # Unable to find the interpreter + return None ++ if (sys_exe := interpreter.system_executable) is None: ++ return None + return PythonInfo( + implementation=interpreter.implementation, + version_info=interpreter.version_info, + version=interpreter.version, + is_64=(interpreter.architecture == 64), # noqa: PLR2004 + platform=interpreter.platform, +- extra={"executable": Path(interpreter.system_executable).resolve()}, ++ extra={"executable": Path(sys_exe).resolve()}, + free_threaded=interpreter.free_threaded, + ) + +@@ -194,8 +195,15 @@ class VirtualEnv(Python, ABC): + :param path: the path investigated + :return: the found information (cached) + """ +- return cached_py_info.from_exe( +- cached_py_info.PythonInfo, ++ from virtualenv.discovery import cached_py_info # noqa: PLC0415 ++ from virtualenv.discovery.py_info import PythonInfo as VirtualenvPythonInfo # noqa: PLC0415 ++ ++ result = cached_py_info.from_exe( ++ VirtualenvPythonInfo, + app_data.make_app_data(None, read_only=False, env=os.environ), + str(path), + ) ++ if result is None: ++ msg = f"could not query python information for {path}" ++ raise RuntimeError(msg) ++ return result +diff --git a/tests/session/cmd/test_sequential.py b/tests/session/cmd/test_sequential.py +index 16e177b..677ddcc 100644 +--- a/tests/session/cmd/test_sequential.py ++++ b/tests/session/cmd/test_sequential.py +@@ -81,6 +81,7 @@ def test_result_json_sequential( + log_report = json.load(file_handler) + + py_info = PythonInfo.current_system() ++ assert py_info.system_executable is not None + host_python = { + "executable": str(Path(py_info.system_executable).resolve()), + "extra_version_info": None, +diff --git a/tests/tox_env/python/virtual_env/test_virtualenv_api.py b/tests/tox_env/python/virtual_env/test_virtualenv_api.py +index 215f7ed..8960fa2 100644 +--- a/tests/tox_env/python/virtual_env/test_virtualenv_api.py ++++ b/tests/tox_env/python/virtual_env/test_virtualenv_api.py +@@ -2,13 +2,17 @@ from __future__ import annotations + + import os + import sys ++from pathlib import Path + from typing import TYPE_CHECKING ++from unittest.mock import MagicMock, PropertyMock + + import pytest + from virtualenv import __version__ as virtualenv_version + from virtualenv import session_via_cli + from virtualenv.config.cli.parser import VirtualEnvOptions + ++from tox.tox_env.python.virtual_env.api import VirtualEnv ++ + if TYPE_CHECKING: + from pytest_mock import MockerFixture + +@@ -171,3 +175,25 @@ def test_list_dependencies_command(tox_project: ToxProjectCreator) -> None: + result.assert_success() + request: ExecuteRequest = execute_calls.call_args[0][3] + assert request.cmd == ["python", "-m", "pip", "freeze"] ++ ++ ++def test_get_python_returns_none_when_system_executable_missing( ++ tox_project: ToxProjectCreator, ++ mocker: MockerFixture, ++) -> None: ++ mocker.patch.object( ++ VirtualEnv, ++ "creator", ++ new_callable=PropertyMock, ++ return_value=MagicMock(interpreter=MagicMock(system_executable=None)), ++ ) ++ proj = tox_project({"tox.ini": "[testenv]\npackage=skip\nbase_python=missing-interp"}) ++ result = proj.run("r") ++ result.assert_failed() ++ assert "could not find python interpreter" in result.out ++ ++ ++def test_get_virtualenv_py_info_raises_on_none(mocker: MockerFixture) -> None: ++ mocker.patch("virtualenv.discovery.cached_py_info.from_exe", return_value=None) ++ with pytest.raises(RuntimeError, match="could not query python information for"): ++ VirtualEnv.get_virtualenv_py_info(Path("/no/such/python")) +-- +2.53.0 +