2222# SOFTWARE.
2323from __future__ import annotations
2424
25-
2625"""
2726Discover python installs that have been created with pyenv
2827"""
3332
3433from ducktools .lazyimporter import LazyImporter , FromImport , ModuleImport
3534
36- from ..shared import PythonInstall , get_install_details , FULL_PY_VER_RE , version_str_to_tuple
35+ from ..shared import PythonInstall , DetailFinder , FULL_PY_VER_RE , INSTALLER_CACHE_PATH , version_str_to_tuple
3736
3837_laz = LazyImporter (
3938 [
39+ ModuleImport ("json" ),
4040 ModuleImport ("re" ),
4141 FromImport ("subprocess" , "run" ),
4242 ]
@@ -52,11 +52,24 @@ def get_pyenv_root() -> str | None:
5252 pyenv_root = os .environ .get ("PYENV_ROOT" )
5353 if not pyenv_root :
5454 try :
55- output = _laz .run (["pyenv" , "root" ], capture_output = True , text = True )
56- except FileNotFoundError :
57- return None
55+ with open (INSTALLER_CACHE_PATH ) as f :
56+ installer_cache = _laz .json .load (f )
57+ except (FileNotFoundError , _laz .json .JSONDecodeError ):
58+ installer_cache = {}
59+
60+ pyenv_root = installer_cache .get ("pyenv" )
61+ if pyenv_root is None or not os .path .exists (pyenv_root ):
62+ try :
63+ output = _laz .run (["pyenv" , "root" ], capture_output = True , text = True )
64+ except FileNotFoundError :
65+ return None
66+
67+ pyenv_root = output .stdout .strip ()
5868
59- pyenv_root = output .stdout .strip ()
69+ installer_cache ["pyenv" ] = pyenv_root
70+ os .makedirs (os .path .dirname (INSTALLER_CACHE_PATH ), exist_ok = True )
71+ with open (INSTALLER_CACHE_PATH , 'w' ) as f :
72+ _laz .json .dump (installer_cache , f )
6073
6174 return pyenv_root
6275
@@ -65,6 +78,7 @@ def get_pyenv_pythons(
6578 versions_folder : str | os .PathLike | None = None ,
6679 * ,
6780 query_executables : bool = True ,
81+ finder : DetailFinder = None ,
6882) -> Iterator [PythonInstall ]:
6983 if versions_folder is None :
7084 if pyenv_root := get_pyenv_root ():
@@ -77,26 +91,32 @@ def get_pyenv_pythons(
7791 # This can lead to much faster returns by potentially yielding
7892 # the required python version before checking pypy/graalpy/micropython
7993
80- for p in sorted (os .scandir (versions_folder ), key = lambda x : x .path ):
81- executable = os .path .join (p .path , "bin/python" )
82-
83- if os .path .exists (executable ):
84- if p .name .endswith ("t" ):
85- freethreaded = True
86- version = p .name [:- 1 ]
87- else :
88- freethreaded = False
89- version = p .name
90- if _laz .re .fullmatch (FULL_PY_VER_RE , version ):
91- version_tuple = version_str_to_tuple (version )
92- metadata = {}
93- if version_tuple >= (3 , 13 ):
94- metadata ["freethreaded" ] = freethreaded
95- yield PythonInstall (
96- version = version_tuple ,
97- executable = executable ,
98- metadata = metadata ,
99- managed_by = "pyenv" ,
100- )
101- elif query_executables and (install := get_install_details (executable , managed_by = "pyenv" )):
102- yield install
94+ finder = DetailFinder () if finder is None else finder
95+
96+ with finder :
97+ for p in sorted (os .scandir (versions_folder ), key = lambda x : x .path ):
98+ executable = os .path .join (p .path , "bin/python" )
99+
100+ if os .path .exists (executable ):
101+ if p .name .endswith ("t" ):
102+ freethreaded = True
103+ version = p .name [:- 1 ]
104+ else :
105+ freethreaded = False
106+ version = p .name
107+ if _laz .re .fullmatch (FULL_PY_VER_RE , version ):
108+ version_tuple = version_str_to_tuple (version )
109+ metadata = {}
110+ if version_tuple >= (3 , 13 ):
111+ metadata ["freethreaded" ] = freethreaded
112+ yield PythonInstall (
113+ version = version_tuple ,
114+ executable = executable ,
115+ metadata = metadata ,
116+ managed_by = "pyenv" ,
117+ )
118+ elif (
119+ query_executables
120+ and (install := finder .get_install_details (executable , managed_by = "pyenv" ))
121+ ):
122+ yield install
0 commit comments