From 2d0731faac098f42209ba575ce11923debbebd95 Mon Sep 17 00:00:00 2001 From: David C Ellis Date: Tue, 22 Jul 2025 18:14:07 +0100 Subject: [PATCH] Improve base python discovery logic, fix a test error if all Pythons are in /usr/bin --- src/ducktools/pythonfinder/venv.py | 22 ++++++++++++++-------- tests/conftest.py | 16 +++++++--------- tests/test_venv_finder.py | 27 ++++++++++++++++++++------- 3 files changed, 41 insertions(+), 24 deletions(-) diff --git a/src/ducktools/pythonfinder/venv.py b/src/ducktools/pythonfinder/venv.py index c630d3c..ef2ef18 100644 --- a/src/ducktools/pythonfinder/venv.py +++ b/src/ducktools/pythonfinder/venv.py @@ -1,18 +1,18 @@ # ducktools-pythonfinder # MIT License -# +# # Copyright (c) 2023-2025 David C Ellis -# +# # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: -# +# # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. -# +# # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -90,10 +90,16 @@ def parent_executable(self) -> str | None: if sys.platform == "win32": parent_exe = os.path.join(self.parent_path, "python.exe") else: - # try with additional numbers in order eg: python313, python3, python - for i in reversed(range(2)): - version_part = "".join(str(v) for v in self.version[:i]) - parent_exe = os.path.join(self.parent_path, f"python{version_part}") + # try with additional numbers in order eg: python3.13, python313, python3, python + suffixes = [ + f"{self.version[0]}.{self.version[1]}", + f"{self.version[0]}{self.version[1]}", + f"{self.version[0]}", + "" + ] + + for suffix in suffixes: + parent_exe = os.path.join(self.parent_path, f"python{suffix}") if os.path.exists(parent_exe): break diff --git a/tests/conftest.py b/tests/conftest.py index 270df66..93c171d 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,18 +1,18 @@ # ducktools-pythonfinder # MIT License -# +# # Copyright (c) 2023-2025 David C Ellis -# +# # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: -# +# # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. -# +# # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -60,7 +60,8 @@ def this_python(temp_finder): elif sys.platform == "win32": exename = "python.exe" else: - exename = "python" + ver = ".".join(str(v) for v in sys.version_info[:2]) + exename = f"python{ver}" if sys.platform == "win32": py_exe = Path(sys.base_prefix) / exename @@ -72,10 +73,7 @@ def this_python(temp_finder): @pytest.fixture(scope="function") def this_venv(temp_finder): - if sys.platform == "win32": - exe = sys.executable - else: - exe = str(Path(sys.executable).with_name("python")) + exe = sys.executable venv = temp_finder.query_install(exe) return venv diff --git a/tests/test_venv_finder.py b/tests/test_venv_finder.py index 5c1fbc5..a77d921 100644 --- a/tests/test_venv_finder.py +++ b/tests/test_venv_finder.py @@ -1,18 +1,18 @@ # ducktools-pythonfinder # MIT License -# +# # Copyright (c) 2023-2025 David C Ellis -# +# # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: -# +# # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. -# +# # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -23,7 +23,9 @@ import os import subprocess import sys +import sysconfig import tempfile +from pathlib import Path from ducktools.pythonfinder.venv import list_python_venvs @@ -35,15 +37,26 @@ def with_venvs(): with tempfile.TemporaryDirectory() as tmpdir: # We can't actually use venv directly here as # Older python on linux makes invalid venvs + + config_exe = sysconfig.get_config_var("EXENAME") + + if config_exe: + exename = os.path.basename(config_exe) + elif sys.platform == "win32": + exename = "python.exe" + else: + ver = ".".join(str(v) for v in sys.version_info[:2]) + exename = f"python{ver}" + if sys.platform == "win32": - python_exe = os.path.join(sys.base_prefix, "python.exe") + py_exe = Path(sys.base_prefix) / exename else: - python_exe = os.path.join(sys.base_prefix, "bin", "python") + py_exe = Path(sys.base_prefix) / "bin" / exename def make_venv(pth): subprocess.run( [ - python_exe, + py_exe, "-m", "venv", "--without-pip", os.path.join(tmpdir, pth),