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
4 changes: 3 additions & 1 deletion src/ducktools/pythonfinder/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,8 @@ def display_local_installs(

alternate_implementations = False

real_sys_executable = os.path.realpath(sys.executable)

# First collect the strings
for install in installs:
if min_ver and install.version < min_ver_tuple:
Expand All @@ -207,7 +209,7 @@ def display_local_installs(
if install.architecture == "32bit":
version_str = f"^{version_str}"

if install.executable == sys.executable:
if install.real_executable == real_sys_executable:
version_str = f"*{version_str}"
elif (
sys.prefix != sys.base_prefix
Expand Down
6 changes: 3 additions & 3 deletions src/ducktools/pythonfinder/darwin/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ def get_path_pythons(
finder: DetailFinder | None = None,
known_paths: dict[str, str] | None = None,
) -> Iterator[PythonInstall]:

known_paths = KNOWN_MANAGED_PATHS if known_paths is None else known_paths

return linux.get_path_pythons(finder=finder, known_paths=known_paths)
Expand All @@ -69,6 +69,6 @@ def get_python_installs(
]
with finder:
for py in itertools.chain.from_iterable(chain_commands):
if py.executable not in listed_pythons:
if py.real_executable not in listed_pythons:
yield py
listed_pythons.add(py.executable)
listed_pythons.add(py.real_executable)
8 changes: 4 additions & 4 deletions src/ducktools/pythonfinder/linux/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,17 +98,17 @@ def get_python_installs(
*,
finder: DetailFinder | None = None,
) -> Iterator[PythonInstall]:
listed_pythons = set()
listed_bins: set[str] = set()

finder = DetailFinder() if finder is None else finder

chain_commands = [
chain_commands: list[Iterator[PythonInstall]] = [
get_pyenv_pythons(finder=finder),
get_uv_pythons(finder=finder),
get_path_pythons(finder=finder),
]
with finder:
for py in itertools.chain.from_iterable(chain_commands):
if py.executable not in listed_pythons:
if py.real_executable not in listed_bins:
yield py
listed_pythons.add(py.executable)
listed_bins.add(py.real_executable)
28 changes: 25 additions & 3 deletions src/ducktools/pythonfinder/shared.py
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,7 @@ class PythonInstall(Prefab):
paths: dict[str, str] = attribute(default_factory=dict)
shadowed: bool = attribute(default=False, serialize=False)
_implementation_version: tuple[int, int, int, str, int] | None = attribute(default=None, private=True)
_real_executable: str | None = attribute(default=None, private=True)

def __prefab_post_init__(
self,
Expand All @@ -337,14 +338,34 @@ def __prefab_post_init__(
# Add the extras to avoid breaking
self.version = tuple([*version, "final", 0]) # type: ignore
else:
self.version = version
self.version = version # type: ignore

@property
def real_executable(self) -> str:
"""
:return: Path to the executable with any symlinks resolved
"""
if self._real_executable is None:
self._real_executable = os.path.realpath(self.executable)
return self._real_executable

@property
def version_str(self) -> str:
"""
:return: Python version as a string
"""
return version_tuple_to_str(self.version)

@property
def implementation_version(self) -> tuple[int, int, int, str, int] | None:
"""
The implementation version may differ from the 'Version' for implementations
other than CPython.

This specifically returns the version of the implementation

:return: Implementation version as tuple (major, minor, micro, releaselevel, serial)
"""
if self._implementation_version is None:
if implementation_ver := self.metadata.get(f"{self.implementation}_version"):
if len(implementation_ver) == 3:
Expand All @@ -358,10 +379,11 @@ def implementation_version(self) -> tuple[int, int, int, str, int] | None:

@property
def implementation_version_str(self) -> str:
"""
:return: Version of the implementation as a string
"""
return version_tuple_to_str(self.implementation_version)

# Typing these classmethods would require an import
# This is not acceptable for performance reasons
@classmethod
def from_str(
cls,
Expand Down