Skip to content

Commit 3e8d0f5

Browse files
committed
adjust python default_language_version to prefer versioned exe
1 parent ff7256c commit 3e8d0f5

File tree

2 files changed

+93
-12
lines changed

2 files changed

+93
-12
lines changed

pre_commit/languages/python.py

+26-12
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,13 @@ def _find_by_py_launcher(
7575
return None
7676

7777

78+
def _impl_exe_name() -> str:
79+
if sys.implementation.name == 'cpython': # pragma: cpython cover
80+
return 'python'
81+
else: # pragma: cpython no cover
82+
return sys.implementation.name # pypy mostly
83+
84+
7885
def _find_by_sys_executable() -> str | None:
7986
def _norm(path: str) -> str | None:
8087
_, exe = os.path.split(path.lower())
@@ -100,18 +107,25 @@ def _norm(path: str) -> str | None:
100107

101108
@functools.lru_cache(maxsize=1)
102109
def get_default_version() -> str: # pragma: no cover (platform dependent)
103-
# First attempt from `sys.executable` (or the realpath)
104-
exe = _find_by_sys_executable()
105-
if exe:
106-
return exe
107-
108-
# Next try the `pythonX.X` executable
109-
exe = f'python{sys.version_info[0]}.{sys.version_info[1]}'
110-
if find_executable(exe):
111-
return exe
112-
113-
if _find_by_py_launcher(exe):
114-
return exe
110+
v_major = f'{sys.version_info[0]}'
111+
v_minor = f'{sys.version_info[0]}.{sys.version_info[1]}'
112+
113+
# attempt the likely implementation exe
114+
for potential in (v_minor, v_major):
115+
exe = f'{_impl_exe_name()}{potential}'
116+
if find_executable(exe):
117+
return exe
118+
119+
# next try `sys.executable` (or the realpath)
120+
maybe_exe = _find_by_sys_executable()
121+
if maybe_exe:
122+
return maybe_exe
123+
124+
# maybe on windows we can find it via py launcher?
125+
if sys.platform == 'win32': # pragma: win32 cover
126+
exe = f'python{v_minor}'
127+
if _find_by_py_launcher(exe):
128+
return exe
115129

116130
# We tried!
117131
return C.DEFAULT

tests/languages/python_test.py

+67
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from pre_commit.prefix import Prefix
1313
from pre_commit.util import make_executable
1414
from pre_commit.util import win_exe
15+
from testing.auto_namedtuple import auto_namedtuple
1516
from testing.language_helpers import run_language
1617

1718

@@ -34,6 +35,72 @@ def test_read_pyvenv_cfg_non_utf8(tmpdir):
3435
assert python._read_pyvenv_cfg(pyvenv_cfg) == expected
3536

3637

38+
def _get_default_version(
39+
*,
40+
impl: str,
41+
exe: str,
42+
found: set[str],
43+
version: tuple[int, int],
44+
) -> str:
45+
sys_exe = f'/fake/path/{exe}'
46+
sys_impl = auto_namedtuple(name=impl)
47+
sys_ver = auto_namedtuple(major=version[0], minor=version[1])
48+
49+
def find_exe(s):
50+
if s in found:
51+
return f'/fake/path/found/{exe}'
52+
else:
53+
return None
54+
55+
with (
56+
mock.patch.object(sys, 'implementation', sys_impl),
57+
mock.patch.object(sys, 'executable', sys_exe),
58+
mock.patch.object(sys, 'version_info', sys_ver),
59+
mock.patch.object(python, 'find_executable', find_exe),
60+
):
61+
return python.get_default_version.__wrapped__()
62+
63+
64+
def test_default_version_sys_executable_found():
65+
ret = _get_default_version(
66+
impl='cpython',
67+
exe='python3.12',
68+
found={'python3.12'},
69+
version=(3, 12),
70+
)
71+
assert ret == 'python3.12'
72+
73+
74+
def test_default_version_picks_specific_when_found():
75+
ret = _get_default_version(
76+
impl='cpython',
77+
exe='python3',
78+
found={'python3', 'python3.12'},
79+
version=(3, 12),
80+
)
81+
assert ret == 'python3.12'
82+
83+
84+
def test_default_version_picks_pypy_versioned_exe():
85+
ret = _get_default_version(
86+
impl='pypy',
87+
exe='python',
88+
found={'pypy3.12', 'python3'},
89+
version=(3, 12),
90+
)
91+
assert ret == 'pypy3.12'
92+
93+
94+
def test_default_version_picks_pypy_unversioned_exe():
95+
ret = _get_default_version(
96+
impl='pypy',
97+
exe='python',
98+
found={'pypy3', 'python3'},
99+
version=(3, 12),
100+
)
101+
assert ret == 'pypy3'
102+
103+
37104
def test_norm_version_expanduser():
38105
home = os.path.expanduser('~')
39106
if sys.platform == 'win32': # pragma: win32 cover

0 commit comments

Comments
 (0)