Source code for pythonfinder.pythonfinder

from __future__ import annotations

import operator
from typing import Any, Optional

from .exceptions import InvalidPythonVersion
from .models.common import FinderBaseModel
from .models.path import PathEntry, SystemPath
from .models.python import PythonVersion
from .environment import set_asdf_paths, set_pyenv_paths
from .utils import Iterable, version_re


[docs]class Finder(FinderBaseModel): path_prepend: Optional[str] = None system: bool = False global_search: bool = True ignore_unsupported: bool = True sort_by_path: bool = False system_path: Optional[SystemPath] = None def __init__(self, **data) -> None: super().__init__(**data) self.system_path = self.create_system_path() @property def __hash__(self) -> int: return hash( (self.path_prepend, self.system, self.global_search, self.ignore_unsupported) ) def __eq__(self, other) -> bool: return self.__hash__ == other.__hash__
[docs] def create_system_path(self) -> SystemPath: set_asdf_paths() set_pyenv_paths() return SystemPath.create( path=self.path_prepend, system=self.system, global_search=self.global_search, ignore_unsupported=self.ignore_unsupported, )
[docs] def which(self, exe) -> PathEntry | None: return self.system_path.which(exe)
[docs] @classmethod def parse_major( cls, major: str | None, minor: int | None = None, patch: int | None = None, pre: bool | None = None, dev: bool | None = None, arch: str | None = None, ) -> dict[str, Any]: major_is_str = major and isinstance(major, str) is_num = ( major and major_is_str and all(part.isdigit() for part in major.split(".")[:2]) ) major_has_arch = ( arch is None and major and major_is_str and "-" in major and major[0].isdigit() ) name = None if major and major_has_arch: orig_string = f"{major!s}" major, _, arch = major.rpartition("-") if arch: arch = arch.lower().lstrip("x").replace("bit", "") if not (arch.isdigit() and (int(arch) & int(arch) - 1) == 0): major = orig_string arch = None else: arch = f"{arch}bit" try: version_dict = PythonVersion.parse(major) except (ValueError, InvalidPythonVersion): if name is None: name = f"{major!s}" major = None version_dict = {} elif major and major[0].isalpha(): return {"major": None, "name": major, "arch": arch} elif major and is_num: match = version_re.match(major) version_dict = match.groupdict() if match else {} version_dict.update( { "is_prerelease": bool(version_dict.get("prerel", False)), "is_devrelease": bool(version_dict.get("dev", False)), } ) else: version_dict = { "major": major, "minor": minor, "patch": patch, "pre": pre, "dev": dev, "arch": arch, } if not version_dict.get("arch") and arch: version_dict["arch"] = arch version_dict["minor"] = ( int(version_dict["minor"]) if version_dict.get("minor") is not None else minor ) version_dict["patch"] = ( int(version_dict["patch"]) if version_dict.get("patch") is not None else patch ) version_dict["major"] = ( int(version_dict["major"]) if version_dict.get("major") is not None else major ) if not (version_dict["major"] or version_dict.get("name")): version_dict["major"] = major if name: version_dict["name"] = name return version_dict
[docs] def find_python_version( self, major: str | int | None = None, minor: int | None = None, patch: int | None = None, pre: bool | None = None, dev: bool | None = None, arch: str | None = None, name: str | None = None, sort_by_path: bool = False, ) -> PathEntry | None: """ Find the python version which corresponds most closely to the version requested. :param major: The major version to look for, or the full version, or the name of the target version. :param minor: The minor version. If provided, disables string-based lookups from the major version field. :param patch: The patch version. :param pre: If provided, specifies whether to search pre-releases. :param dev: If provided, whether to search dev-releases. :param arch: If provided, which architecture to search. :param name: *Name* of the target python, e.g. ``anaconda3-5.3.0`` :param sort_by_path: Whether to sort by path -- default sort is by version(default: False) :return: A new *PathEntry* pointer at a matching python version, if one can be located. """ minor = int(minor) if minor is not None else minor patch = int(patch) if patch is not None else patch if ( isinstance(major, str) and pre is None and minor is None and dev is None and patch is None ): version_dict = self.parse_major(major, minor=minor, patch=patch, arch=arch) major = version_dict["major"] minor = version_dict.get("minor", minor) patch = version_dict.get("patch", patch) arch = version_dict.get("arch", arch) name = version_dict.get("name", name) _pre = version_dict.get("is_prerelease", pre) pre = bool(_pre) if _pre is not None else pre _dev = version_dict.get("is_devrelease", dev) dev = bool(_dev) if _dev is not None else dev if "architecture" in version_dict and isinstance( version_dict["architecture"], str ): arch = version_dict["architecture"] return self.system_path.find_python_version( major=major, minor=minor, patch=patch, pre=pre, dev=dev, arch=arch, name=name, sort_by_path=self.sort_by_path, )
[docs] def find_all_python_versions( self, major: str | int | None = None, minor: int | None = None, patch: int | None = None, pre: bool | None = None, dev: bool | None = None, arch: str | None = None, name: str | None = None, ) -> list[PathEntry]: version_sort = operator.attrgetter("as_python.version_sort") python_version_dict = getattr(self.system_path, "python_version_dict", {}) if python_version_dict: paths = ( path for version in python_version_dict.values() for path in version if path is not None and path.as_python ) path_list = sorted(paths, key=version_sort, reverse=True) return path_list versions = self.system_path.find_all_python_versions( major=major, minor=minor, patch=patch, pre=pre, dev=dev, arch=arch, name=name ) if not isinstance(versions, Iterable): versions = [versions] path_list = sorted( filter(lambda v: v and v.as_python, versions), key=version_sort, reverse=True ) path_map = {} for path in path_list: try: resolved_path = path.path.resolve() except OSError: resolved_path = path.path.absolute() if not path_map.get(resolved_path.as_posix()): path_map[resolved_path.as_posix()] = path return path_list