Source code for xonsh.lib.modules

import contextlib
import functools
import importlib
import importlib.util as im_util
import os
from collections import OrderedDict


[docs] class ModuleFinder: """Reusable module matcher. Can be used by other completers like Python to find matching script completions""" extensions = (".py", ".xsh") def __init__(self, *names: "str"): """Helper class to search and load Python modules Parameters ---------- names extra search paths/package-names to use if finding module on namespace package fails. paths should have a path literal to indicate it is a path. Otherwise it is treated as a package name. """ self.contextual = True # unique but maintain order self._pkgs: dict[str, None] = OrderedDict() self._paths: dict[str, None] = OrderedDict() for pk in names: if os.sep in pk: self._paths[pk] = None else: self._pkgs[pk] = None self._file_names_cache: dict[str, str] = {} self._path_st_mtimes: dict[str, float] = {} def _get_new_paths(self): for path in self._paths: if not os.path.isdir(path): continue # check if path is updated old_mtime = self._path_st_mtimes.get(path, 0) new_mtime = os.stat(path).st_mtime if old_mtime >= new_mtime: continue self._path_st_mtimes[path] = os.stat(path).st_mtime yield path def _find_file_path(self, name): # `importlib.machinery.FileFinder` wasn't useful as the findspec handles '.' differently if name in self._file_names_cache: return self._file_names_cache[name] found = None entries = {} for path in self._get_new_paths(): from xonsh.environ import scan_dir_for_source_files for file, entry in scan_dir_for_source_files(path): file_name = os.path.splitext(entry.name)[0] if file_name not in entries: # do not override. prefer the first one that appears on the path list entries[file_name] = file if file_name == name: found = file if found: # search a directory completely since we cache path-mtime break self._file_names_cache.update(entries) return found
[docs] @staticmethod def import_module(path, name: str): """given the file location import as module""" pkg = path.replace(os.sep, ".") spec = im_util.spec_from_file_location(f"{pkg}.{name}", path) if not spec: return module = im_util.module_from_spec(spec) if not spec.loader: return spec.loader.exec_module(module) # type: ignore return module
[docs] @functools.lru_cache(maxsize=None) # noqa def get_module(self, module: str): for name in [ module, f"_{module}", # naming convention to not clash with actual python package ]: for base in self._pkgs: with contextlib.suppress(ModuleNotFoundError): return importlib.import_module(f"{base}.{name}") file = self._find_file_path(module) if file: return self.import_module(file, module)