Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/networkx/lazy_imports.py: 22%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

54 statements  

1import importlib 

2import importlib.util 

3import inspect 

4import os 

5import sys 

6import types 

7 

8__all__ = ["attach", "_lazy_import"] 

9 

10 

11def attach(module_name, submodules=None, submod_attrs=None): 

12 """Attach lazily loaded submodules, and functions or other attributes. 

13 

14 Typically, modules import submodules and attributes as follows:: 

15 

16 import mysubmodule 

17 import anothersubmodule 

18 

19 from .foo import someattr 

20 

21 The idea of this function is to replace the `__init__.py` 

22 module's `__getattr__`, `__dir__`, and `__all__` attributes such that 

23 all imports work exactly the way they normally would, except that the 

24 actual import is delayed until the resulting module object is first used. 

25 

26 The typical way to call this function, replacing the above imports, is:: 

27 

28 __getattr__, __lazy_dir__, __all__ = lazy.attach( 

29 __name__, ["mysubmodule", "anothersubmodule"], {"foo": "someattr"} 

30 ) 

31 

32 This functionality requires Python 3.7 or higher. 

33 

34 Parameters 

35 ---------- 

36 module_name : str 

37 Typically use __name__. 

38 submodules : set 

39 List of submodules to lazily import. 

40 submod_attrs : dict 

41 Dictionary of submodule -> list of attributes / functions. 

42 These attributes are imported as they are used. 

43 

44 Returns 

45 ------- 

46 __getattr__, __dir__, __all__ 

47 

48 """ 

49 if submod_attrs is None: 

50 submod_attrs = {} 

51 

52 if submodules is None: 

53 submodules = set() 

54 else: 

55 submodules = set(submodules) 

56 

57 attr_to_modules = { 

58 attr: mod for mod, attrs in submod_attrs.items() for attr in attrs 

59 } 

60 

61 __all__ = list(submodules | attr_to_modules.keys()) 

62 

63 def __getattr__(name): 

64 if name in submodules: 

65 return importlib.import_module(f"{module_name}.{name}") 

66 elif name in attr_to_modules: 

67 submod = importlib.import_module(f"{module_name}.{attr_to_modules[name]}") 

68 return getattr(submod, name) 

69 else: 

70 raise AttributeError(f"No {module_name} attribute {name}") 

71 

72 def __dir__(): 

73 return __all__ 

74 

75 if os.environ.get("EAGER_IMPORT", ""): 

76 for attr in set(attr_to_modules.keys()) | submodules: 

77 __getattr__(attr) 

78 

79 return __getattr__, __dir__, list(__all__) 

80 

81 

82class DelayedImportErrorModule(types.ModuleType): 

83 def __init__(self, frame_data, *args, **kwargs): 

84 self.__frame_data = frame_data 

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

86 

87 def __getattr__(self, x): 

88 if x in ("__class__", "__file__", "__frame_data"): 

89 super().__getattr__(x) 

90 else: 

91 fd = self.__frame_data 

92 raise ModuleNotFoundError( 

93 f"No module named '{fd['spec']}'\n\n" 

94 "This error is lazily reported, having originally occurred in\n" 

95 f" File {fd['filename']}, line {fd['lineno']}, in {fd['function']}\n\n" 

96 f"----> {''.join(fd['code_context'] or '').strip()}" 

97 ) 

98 

99 

100def _lazy_import(fullname): 

101 """Return a lazily imported proxy for a module or library. 

102 

103 Warning 

104 ------- 

105 Importing using this function can currently cause trouble 

106 when the user tries to import from a subpackage of a module before 

107 the package is fully imported. In particular, this idiom may not work: 

108 

109 np = lazy_import("numpy") 

110 from numpy.lib import recfunctions 

111 

112 This is due to a difference in the way Python's LazyLoader handles 

113 subpackage imports compared to the normal import process. Hopefully 

114 we will get Python's LazyLoader to fix this, or find a workaround. 

115 In the meantime, this is a potential problem. 

116 

117 The workaround is to import numpy before importing from the subpackage. 

118 

119 Notes 

120 ----- 

121 We often see the following pattern:: 

122 

123 def myfunc(): 

124 import scipy as sp 

125 sp.argmin(...) 

126 .... 

127 

128 This is to prevent a library, in this case `scipy`, from being 

129 imported at function definition time, since that can be slow. 

130 

131 This function provides a proxy module that, upon access, imports 

132 the actual module. So the idiom equivalent to the above example is:: 

133 

134 sp = lazy.load("scipy") 

135 

136 def myfunc(): 

137 sp.argmin(...) 

138 .... 

139 

140 The initial import time is fast because the actual import is delayed 

141 until the first attribute is requested. The overall import time may 

142 decrease as well for users that don't make use of large portions 

143 of the library. 

144 

145 Parameters 

146 ---------- 

147 fullname : str 

148 The full name of the package or subpackage to import. For example:: 

149 

150 sp = lazy.load("scipy") # import scipy as sp 

151 spla = lazy.load("scipy.linalg") # import scipy.linalg as spla 

152 

153 Returns 

154 ------- 

155 pm : importlib.util._LazyModule 

156 Proxy module. Can be used like any regularly imported module. 

157 Actual loading of the module occurs upon first attribute request. 

158 

159 """ 

160 try: 

161 return sys.modules[fullname] 

162 except: 

163 pass 

164 

165 # Not previously loaded -- look it up 

166 spec = importlib.util.find_spec(fullname) 

167 

168 if spec is None: 

169 try: 

170 parent = inspect.stack()[1] 

171 frame_data = { 

172 "spec": fullname, 

173 "filename": parent.filename, 

174 "lineno": parent.lineno, 

175 "function": parent.function, 

176 "code_context": parent.code_context, 

177 } 

178 return DelayedImportErrorModule(frame_data, "DelayedImportErrorModule") 

179 finally: 

180 del parent 

181 

182 module = importlib.util.module_from_spec(spec) 

183 sys.modules[fullname] = module 

184 

185 loader = importlib.util.LazyLoader(spec.loader) 

186 loader.exec_module(module) 

187 

188 return module