Coverage for src/importnb/finder.py: 98%

60 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2023-01-08 11:14 -0800

1# coding: utf-8 

2"""# `sys.path_hook` modifiers 

3 

4Many suggestions for importing notebooks use `sys.meta_paths`, but `importnb` relies on the `sys.path_hooks` to load any notebook in the path. `PathHooksContext` is a base class for the `importnb.Notebook` `SourceFileLoader`. 

5""" 

6 

7import inspect 

8import sys 

9from importlib._bootstrap_external import FileFinder 

10from importlib.machinery import ModuleSpec 

11from pathlib import Path 

12 

13class SpecSort: 

14 modified = "st_mtime" 

15 created = "st_ctime" 

16 

17class FileModuleSpec(ModuleSpec): 

18 def __init__(self, *args, **kwargs): 

19 super().__init__(*args, **kwargs) 

20 self._set_fileattr = True 

21 

22 

23class FuzzySpec(FileModuleSpec): 

24 def __init__( 

25 self, name, loader, *, alias=None, origin=None, loader_state=None, is_package=None 

26 ): 

27 super().__init__( 

28 name, 

29 loader, 

30 origin=origin, 

31 loader_state=loader_state, 

32 is_package=is_package, 

33 ) 

34 self.alias = alias 

35 

36 

37def fuzzy_query(str): 

38 new = "" 

39 for chr in str: 

40 new += (not new.endswith("__") or chr != "_") and chr or "" 

41 return new.replace("__", "*").replace("_", "?") 

42 

43 

44def fuzzy_file_search(path, fullname): 

45 results = [] 

46 id, details = get_loader_details() 

47 for ext in sum((list(object[1]) for object in details), []): 

48 results.extend(Path(path).glob(fullname + ext)) 

49 "_" in fullname and results.extend(Path(path).glob(fuzzy_query(fullname) + ext)) 

50 return results 

51 

52 

53class FuzzyFinder(FileFinder): 

54 """Adds the ability to open file names with special characters using underscores.""" 

55 

56 def find_spec(self, fullname, target=None): 

57 """Try to finder the spec and if it cannot be found, use the underscore starring syntax 

58 to identify potential matches. 

59 """ 

60 spec = super().find_spec(fullname, target=target) 

61 raw = fullname 

62 if spec is None: 

63 original = fullname 

64 

65 if "." in fullname: 

66 original, fullname = fullname.rsplit(".", 1) 

67 else: 

68 original, fullname = "", original 

69 

70 if "_" in fullname: 

71 # find any files using the fuzzy convention 

72 files = fuzzy_file_search(self.path, fullname) 

73 if files: 

74 # sort and create of a path of the chosen file 

75 file = sorted(files, key=lambda x: x.stat().st_mtime, reverse=True)[0] 

76 name = file.stem 

77 if original: 

78 name = ".".join((original, name)) 

79 name = (original + "." + file.stem).lstrip(".") 

80 spec = super().find_spec(name, target=target) 

81 spec = spec and FuzzySpec( 

82 spec.name, 

83 spec.loader, 

84 origin=spec.origin, 

85 loader_state=spec.loader_state, 

86 alias=raw, 

87 is_package=bool(spec.submodule_search_locations), 

88 ) 

89 return spec 

90 

91 

92def get_loader_details(): 

93 for id, path_hook in enumerate(sys.path_hooks): 

94 try: 

95 return ( 

96 id, 

97 list(inspect.getclosurevars(path_hook).nonlocals["loader_details"]), 

98 ) 

99 except: 

100 continue 

101 

102 

103def get_loader_index(ext): 

104 path_id, details = get_loader_details() 

105 for i, (loader, exts) in enumerate(details): 

106 if ext in exts: 

107 return path_id, i, details