[docs]classModuleFinder:"""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 orderself._pkgs:dict[str,None]=OrderedDict()self._paths:dict[str,None]=OrderedDict()forpkinnames:ifos.sepinpk:self._paths[pk]=Noneelse:self._pkgs[pk]=Noneself._file_names_cache:dict[str,str]={}self._path_st_mtimes:dict[str,float]={}def_get_new_paths(self):forpathinself._paths:ifnotos.path.isdir(path):continue# check if path is updatedold_mtime=self._path_st_mtimes.get(path,0)new_mtime=os.stat(path).st_mtimeifold_mtime>=new_mtime:continueself._path_st_mtimes[path]=os.stat(path).st_mtimeyieldpathdef_find_file_path(self,name):# `importlib.machinery.FileFinder` wasn't useful as the findspec handles '.' differentlyifnameinself._file_names_cache:returnself._file_names_cache[name]found=Noneentries={}forpathinself._get_new_paths():fromxonsh.environimportscan_dir_for_source_filesforfile,entryinscan_dir_for_source_files(path):file_name=os.path.splitext(entry.name)[0]iffile_namenotinentries:# do not override. prefer the first one that appears on the path listentries[file_name]=fileiffile_name==name:found=fileiffound:# search a directory completely since we cache path-mtimebreakself._file_names_cache.update(entries)returnfound
[docs]@staticmethoddefimport_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)ifnotspec:returnmodule=im_util.module_from_spec(spec)ifnotspec.loader:returnspec.loader.exec_module(module)# type: ignorereturnmodule
[docs]@functools.lru_cache(maxsize=None)# noqadefget_module(self,module:str):fornamein[module,f"_{module}",# naming convention to not clash with actual python package]:forbaseinself._pkgs:withcontextlib.suppress(ModuleNotFoundError):returnimportlib.import_module(f"{base}.{name}")file=self._find_file_path(module)iffile:returnself.import_module(file,module)