33from __future__ import annotations
44
55import json
6- import os
76import sys
87from abc import ABC
98from functools import cached_property
10-
11- if sys .version_info >= (3 , 9 ): # pragma: no cover (py39+)
12- from importlib .resources import as_file , files
13- else : # pragma: no cover (py38+)
14- from importlib_resources import as_file , files
15-
16-
179from pathlib import Path
1810from platform import python_implementation
1911from typing import TYPE_CHECKING , Any , Literal , Optional , Type , cast
2214from tox .execute .request import StdinSource
2315from tox .tox_env .errors import Skip
2416from tox .tox_env .python .api import Python , PythonInfo , VersionInfo
17+ from tox .tox_env .python .virtual_env .api import VirtualEnv
18+ from uv import find_uv_bin
19+ from virtualenv .discovery .py_spec import PythonSpec
20+
21+ from ._installer import UvInstaller
2522
2623if sys .version_info >= (3 , 10 ): # pragma: no cover (py310+)
2724 from typing import TypeAlias
2825else : # pragma: no cover (<py310)
2926 from typing_extensions import TypeAlias
3027
31- from uv import find_uv_bin
32- from virtualenv import app_data
33- from virtualenv .discovery import cached_py_info
34- from virtualenv .discovery .py_spec import PythonSpec
35-
36- from ._installer import UvInstaller
28+ if sys .version_info >= (3 , 9 ): # pragma: no cover (py39+)
29+ from importlib .resources import as_file , files
30+ else : # pragma: no cover (py38+)
31+ from importlib_resources import as_file , files
3732
3833if TYPE_CHECKING :
3934 from tox .execute .api import Execute
@@ -64,8 +59,7 @@ def register_config(self) -> None:
6459 default = False ,
6560 desc = "add seed packages to the created venv" ,
6661 )
67- # The cast(...) might seems superfluous but removing it
68- # makes mypy crash. The problem is probably on tox's typing side
62+ # The cast(...) might seems superfluous but removing it makes mypy crash. The problem isy on tox typing side.
6963 self .conf .add_config (
7064 keys = ["uv_python_preference" ],
7165 of_type = cast (Type [Optional [PythonPreference ]], Optional [PythonPreference ]),
@@ -102,8 +96,7 @@ def installer(self) -> Installer[Any]:
10296 def runs_on_platform (self ) -> str :
10397 return sys .platform
10498
105- @staticmethod
106- def _get_python (base_python : list [str ]) -> PythonInfo | None :
99+ def _get_python (self , base_python : list [str ]) -> PythonInfo | None : # noqa: PLR6301
107100 for base in base_python : # pragma: no branch
108101 if base == sys .executable :
109102 version_info = sys .version_info
@@ -121,10 +114,9 @@ def _get_python(base_python: list[str]) -> PythonInfo | None:
121114 platform = sys .platform ,
122115 extra = {},
123116 )
124- if Path (base ).is_absolute ():
125- info = cached_py_info .from_exe (
126- cached_py_info .PythonInfo , app_data .make_app_data (None , read_only = False , env = os .environ ), base
127- )
117+ base_path = Path (base )
118+ if base_path .is_absolute ():
119+ info = VirtualEnv .get_virtualenv_py_info (base_path )
128120 return PythonInfo (
129121 implementation = info .implementation ,
130122 version_info = VersionInfo (* info .version_info ),
@@ -151,6 +143,16 @@ def _get_python(base_python: list[str]) -> PythonInfo | None:
151143
152144 return None # pragma: no cover
153145
146+ @classmethod
147+ def python_spec_for_path (cls , path : Path ) -> PythonSpec :
148+ """
149+ Get the spec for an absolute path to a Python executable.
150+
151+ :param path: the path investigated
152+ :return: the found spec
153+ """
154+ return VirtualEnv .python_spec_for_path (path )
155+
154156 @property
155157 def uv (self ) -> str :
156158 return find_uv_bin ()
0 commit comments