""" On some systems such as Mac and AppImage (Linux) the python extension suffix does not match the cpython suffix used by the embedded python interpreter. This class creates symlinks for all .so files in dest folder that match the current python embedded suffix. For example a file named ``indexers.cpython-38-x86_64-linux-gnu.so`` would be symlinked to ``indexers.cpython-3.8.so``. This renaming allows the python interpreter to find the import. """ from __future__ import annotations from typing import List from pathlib import Path from importlib import machinery import easymacro as app class LinkCPython: def __init__(self, pth: str) -> None: """ Constructor Args: pth (str): Path to site-packages folder. """ self._current_suffix = self._get_current_suffix() app.debug("CPythonLink.__init__") self._link_root = Path(pth) if not self._link_root.exists(): raise FileNotFoundError(f"Path does not exist {self._link_root}") app.debug("CPythonLink.__init__ done") def _get_current_suffix(self) -> str: """Gets suffix currently used by the embedded python interpreter such as ``cpython-3.8``""" for suffix in machinery.EXTENSION_SUFFIXES: if suffix.startswith(".cpython-") and suffix.endswith(".so"): # remove leading . and trailing .so return suffix[1:][:-3] return "" def _get_all_files(self, path: Path) -> List[Path]: return [p for p in path.glob(f"**/*{self.file_suffix}.so") if p.is_file()] def _get_all_links(self, path: Path) -> List[Path]: return [p for p in path.glob(f"**/*{self.current_suffix}.so") if p.is_symlink()] def _create_symlink(self, src: Path, dst: Path, overwrite: bool) -> None: if dst.is_symlink(): if overwrite: app.debug(f"Removing existing symlink {dst}") dst.unlink() else: app.debug(f"Symlink already exists {dst}") return dst.symlink_to(src) app.debug(f"Created symlink {dst} -> {src}") def _find_current_installed_suffix(self, path: Path) -> str: """ Finds the current suffix from the current installed python so files such as ``cpython-38-x86_64-linux-gnu``. Args: path (Path): Path to search in. Usually site-packages. Returns: str: suffix if found, otherwise empty string. """ return next( (str(p).rsplit(".", 2)[1] for p in path.glob("**/*.cpython-*.so") if not p.is_symlink()), "", ) def link(self, overwrite:bool = False) -> None: """ Creates symlinks for all .so files in site-packages that match the current suffix. Args: overwrite (bool, optional): Override any existing sys links. Defaults to False. """ app.debug("CPythonLink.link starting") if not self._link_root: app.debug("No site-packages found") return if not self.file_suffix: app.debug("No current file suffix found") return if not self._link_root.exists(): app.debug(f"Site-packages does not exist {self._link_root}") return app.debug(f"Python current suffix: {self._current_suffix}") app.debug(f"Found file suffix: {self.file_suffix}") files = self._get_all_files(self._link_root) if not files: app.debug(f"No files found in {self._link_root}") return cp_old = self.file_suffix cp_new = self._current_suffix if cp_old == cp_new: app.debug(f"Suffixes match, no need to link: {cp_old} == {cp_new}") return for file in files: ln_name = file.name.replace(cp_old, cp_new) src = file if not src.is_absolute(): src = file.resolve() dst = src.parent / ln_name self._create_symlink(src, dst, overwrite) app.debug("CPythonLink.link done") def unlink(self) -> None: """Unlinks all broken sys links""" links = self._get_all_links(self._link_root) if not links: app.debug(f"No links found in {self._link_root}") return for link in links: if not link.exists(): app.debug(f"Removing broken symlink {link}") link.unlink() # region Properties @property def cpy_name(self) -> str: """Gets/Sets CPython name, e.g. cpython-3.8""" return self._current_suffix @cpy_name.setter def cpy_name(self, value: str) -> None: self._current_suffix = value @property def current_suffix(self) -> str: """Current Suffix such as ``cpython-3.8``""" return self._current_suffix @property def file_suffix(self) -> str: """Current Suffix such as ``cpython-38-x86_64-linux-gnu``""" try: return self._file_suffix except AttributeError: self._file_suffix = self._find_current_installed_suffix(self._link_root) return self._file_suffix # endregion Properties