Merge pull request #3430 from pre-commit/preferential-sys-impl

adjust python default_language_version to prefer versioned exe
This commit is contained in:
Anthony Sottile 2025-03-18 17:26:41 -04:00 committed by GitHub
commit bf6f11dc6c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 91 additions and 10 deletions

View file

@ -75,6 +75,13 @@ def _find_by_py_launcher(
return None return None
def _impl_exe_name() -> str:
if sys.implementation.name == 'cpython': # pragma: cpython cover
return 'python'
else: # pragma: cpython no cover
return sys.implementation.name # pypy mostly
def _find_by_sys_executable() -> str | None: def _find_by_sys_executable() -> str | None:
def _norm(path: str) -> str | None: def _norm(path: str) -> str | None:
_, exe = os.path.split(path.lower()) _, exe = os.path.split(path.lower())
@ -100,18 +107,25 @@ def _find_by_sys_executable() -> str | None:
@functools.lru_cache(maxsize=1) @functools.lru_cache(maxsize=1)
def get_default_version() -> str: # pragma: no cover (platform dependent) def get_default_version() -> str: # pragma: no cover (platform dependent)
# First attempt from `sys.executable` (or the realpath) v_major = f'{sys.version_info[0]}'
exe = _find_by_sys_executable() v_minor = f'{sys.version_info[0]}.{sys.version_info[1]}'
if exe:
return exe
# Next try the `pythonX.X` executable # attempt the likely implementation exe
exe = f'python{sys.version_info[0]}.{sys.version_info[1]}' for potential in (v_minor, v_major):
if find_executable(exe): exe = f'{_impl_exe_name()}{potential}'
return exe if find_executable(exe):
return exe
if _find_by_py_launcher(exe): # next try `sys.executable` (or the realpath)
return exe maybe_exe = _find_by_sys_executable()
if maybe_exe:
return maybe_exe
# maybe on windows we can find it via py launcher?
if sys.platform == 'win32': # pragma: win32 cover
exe = f'python{v_minor}'
if _find_by_py_launcher(exe):
return exe
# We tried! # We tried!
return C.DEFAULT return C.DEFAULT

View file

@ -12,6 +12,7 @@ from pre_commit.languages import python
from pre_commit.prefix import Prefix from pre_commit.prefix import Prefix
from pre_commit.util import make_executable from pre_commit.util import make_executable
from pre_commit.util import win_exe from pre_commit.util import win_exe
from testing.auto_namedtuple import auto_namedtuple
from testing.language_helpers import run_language from testing.language_helpers import run_language
@ -34,6 +35,72 @@ def test_read_pyvenv_cfg_non_utf8(tmpdir):
assert python._read_pyvenv_cfg(pyvenv_cfg) == expected assert python._read_pyvenv_cfg(pyvenv_cfg) == expected
def _get_default_version(
*,
impl: str,
exe: str,
found: set[str],
version: tuple[int, int],
) -> str:
sys_exe = f'/fake/path/{exe}'
sys_impl = auto_namedtuple(name=impl)
sys_ver = auto_namedtuple(major=version[0], minor=version[1])
def find_exe(s):
if s in found:
return f'/fake/path/found/{exe}'
else:
return None
with (
mock.patch.object(sys, 'implementation', sys_impl),
mock.patch.object(sys, 'executable', sys_exe),
mock.patch.object(sys, 'version_info', sys_ver),
mock.patch.object(python, 'find_executable', find_exe),
):
return python.get_default_version.__wrapped__()
def test_default_version_sys_executable_found():
ret = _get_default_version(
impl='cpython',
exe='python3.12',
found={'python3.12'},
version=(3, 12),
)
assert ret == 'python3.12'
def test_default_version_picks_specific_when_found():
ret = _get_default_version(
impl='cpython',
exe='python3',
found={'python3', 'python3.12'},
version=(3, 12),
)
assert ret == 'python3.12'
def test_default_version_picks_pypy_versioned_exe():
ret = _get_default_version(
impl='pypy',
exe='python',
found={'pypy3.12', 'python3'},
version=(3, 12),
)
assert ret == 'pypy3.12'
def test_default_version_picks_pypy_unversioned_exe():
ret = _get_default_version(
impl='pypy',
exe='python',
found={'pypy3', 'python3'},
version=(3, 12),
)
assert ret == 'pypy3'
def test_norm_version_expanduser(): def test_norm_version_expanduser():
home = os.path.expanduser('~') home = os.path.expanduser('~')
if sys.platform == 'win32': # pragma: win32 cover if sys.platform == 'win32': # pragma: win32 cover