22
33from __future__ import annotations
44
5+ import json
56import sys
67from abc import ABC
8+ from functools import cached_property
9+
10+ if sys .version_info >= (3 , 9 ): # pragma: no cover (py39+)
11+ from importlib .resources import as_file , files
12+ else : # pragma: no cover (py38+)
13+ from importlib_resources import as_file , files
14+
15+
716from pathlib import Path
817from platform import python_implementation
918from typing import TYPE_CHECKING , Any , cast
@@ -26,6 +35,7 @@ class UvVenv(Python, ABC):
2635 def __init__ (self , create_args : ToxEnvCreateArgs ) -> None :
2736 self ._executor : Execute | None = None
2837 self ._installer : UvInstaller | None = None
38+ self ._created = False
2939 super ().__init__ (create_args )
3040
3141 def register_config (self ) -> None :
@@ -112,14 +122,12 @@ def _default_pass_env(self) -> list[str]:
112122 return env
113123
114124 def create_python_env (self ) -> None :
115- base = self .base_python .version_info
116- version_spec = (
117- sys .executable
118- if (base .major , base .minor ) == sys .version_info [:2 ]
119- else f"{ base .major } .{ base .minor } "
120- if base .minor
121- else f"{ base .major } "
122- )
125+ base , imp = self .base_python .version_info , self .base_python .impl_lower
126+ if (base .major , base .minor ) == sys .version_info [:2 ] and (sys .implementation .name .lower () == imp ):
127+ version_spec = sys .executable
128+ else :
129+ uv_imp = "python" if (imp and imp == "cpython" ) else imp
130+ version_spec = f"{ uv_imp or '' } { base .major } .{ base .minor } " if base .minor else f"{ uv_imp or '' } { base .major } "
123131 cmd : list [str ] = [self .uv , "venv" , "-p" , version_spec ]
124132 if self .options .verbosity > 2 : # noqa: PLR2004
125133 cmd .append ("-v" )
@@ -128,6 +136,7 @@ def create_python_env(self) -> None:
128136 cmd .append (str (self .venv_dir ))
129137 outcome = self .execute (cmd , stdin = StdinSource .OFF , run_id = "venv" , show = None )
130138 outcome .assert_success ()
139+ self ._created = True
131140
132141 @property
133142 def _allow_externals (self ) -> list [str ]:
@@ -152,9 +161,34 @@ def env_site_package_dir(self) -> Path:
152161 if sys .platform == "win32" : # pragma: win32 cover
153162 return self .venv_dir / "Lib" / "site-packages"
154163 else : # pragma: win32 no cover # noqa: RET505
155- assert self .base_python .version_info .major is not None # noqa: S101
156- assert self .base_python .version_info .minor is not None # noqa: S101
157- return self .venv_dir / "lib" / f"python{ self .base_python .version_dot } " / "site-packages"
164+ py = self ._py_info
165+ impl = "pypy" if py .implementation == "pypy" else "python"
166+ return self .venv_dir / "lib" / f"{ impl } { py .version_dot } " / "site-packages"
167+
168+ @cached_property
169+ def _py_info (self ) -> PythonInfo : # pragma: win32 no cover
170+ if not (self ._created or self .env_dir .exists ()): # called during config, no environment setup
171+ self .create_python_env ()
172+ self ._paths = self .prepend_env_var_path ()
173+ with as_file (files ("tox_uv" ) / "_venv_query.py" ) as filename :
174+ cmd = [str (self .env_python ()), str (filename )]
175+ outcome = self .execute (cmd , stdin = StdinSource .OFF , run_id = "venv-query" , show = False )
176+ outcome .assert_success ()
177+ res = json .loads (outcome .out )
178+ return PythonInfo (
179+ implementation = res ["implementation" ],
180+ version_info = VersionInfo (
181+ major = res ["version_info" ][0 ],
182+ minor = res ["version_info" ][1 ],
183+ micro = res ["version_info" ][2 ],
184+ releaselevel = res ["version_info" ][3 ],
185+ serial = res ["version_info" ][4 ],
186+ ),
187+ version = res ["version" ],
188+ is_64 = res ["is_64" ],
189+ platform = sys .platform ,
190+ extra = {},
191+ )
158192
159193
160194__all__ = [
0 commit comments