Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions docs/changelog/127.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
:attr:`~python_discovery.PythonInfo.system_exe` returns the executable of the system Python an interpreter is based on,
falling back to :attr:`~python_discovery.PythonInfo.executable` the way
:meth:`~python_discovery.PythonInfo.resolve_to_system` does. :attr:`~python_discovery.PythonInfo.system_executable` is
``None`` until resolution runs, so every consumer had to narrow a value discovery has already settled - by
:user:`gaborbernat`.
11 changes: 11 additions & 0 deletions src/python_discovery/_py_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,17 @@ def system_exec_prefix(self) -> str:
"""The exec prefix of the system Python this interpreter is based on."""
return self.real_prefix or self.base_exec_prefix or self.exec_prefix

@property
def system_exe(self) -> str:
"""
The executable of the system Python this interpreter is based on.

:attr:`system_executable` stays ``None`` until :meth:`resolve_to_system` walks the prefix chain, so reading it
forces every caller to narrow a value discovery has already settled. The fallback here matches what resolution
itself writes when a prefix links back to its own interpreter.
"""
return self.system_executable or self.executable

def __repr__(self) -> str:
return "{}({!r})".format(
self.__class__.__name__,
Expand Down
14 changes: 14 additions & 0 deletions tests/py_info/test_py_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -511,3 +511,17 @@ def test_select_most_likely_prefers_machine_match(
d.sysconfig_platform = plat
result = PythonInfo._select_most_likely(discovered, target)
assert result.sysconfig_platform == discovered_platforms[expected_idx]


@pytest.mark.parametrize(
("system_executable", "expected"),
[
pytest.param("/usr/bin/python3", "/usr/bin/python3", id="resolved"),
pytest.param(None, "/venv/bin/python", id="unresolved falls back to executable"),
],
)
def test_py_info_system_exe(system_executable: str | None, expected: str) -> None:
info = copy.deepcopy(CURRENT)
info.executable = "/venv/bin/python"
info.system_executable = system_executable
assert info.system_exe == expected