Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/wrapt/importer.py: 21%

126 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-06-07 06:35 +0000

1"""This module implements a post import hook mechanism styled after what is 

2described in PEP-369. Note that it doesn't cope with modules being reloaded. 

3 

4""" 

5 

6import sys 

7import threading 

8 

9PY2 = sys.version_info[0] == 2 

10 

11if PY2: 

12 string_types = basestring, 

13 find_spec = None 

14else: 

15 string_types = str, 

16 from importlib.util import find_spec 

17 

18# The dictionary registering any post import hooks to be triggered once 

19# the target module has been imported. Once a module has been imported 

20# and the hooks fired, the list of hooks recorded against the target 

21# module will be truncated but the list left in the dictionary. This 

22# acts as a flag to indicate that the module had already been imported. 

23 

24_post_import_hooks = {} 

25_post_import_hooks_init = False 

26_post_import_hooks_lock = threading.RLock() 

27 

28# Register a new post import hook for the target module name. This 

29# differs from the PEP-369 implementation in that it also allows the 

30# hook function to be specified as a string consisting of the name of 

31# the callback in the form 'module:function'. This will result in a 

32# proxy callback being registered which will defer loading of the 

33# specified module containing the callback function until required. 

34 

35def _create_import_hook_from_string(name): 

36 def import_hook(module): 

37 module_name, function = name.split(':') 

38 attrs = function.split('.') 

39 __import__(module_name) 

40 callback = sys.modules[module_name] 

41 for attr in attrs: 

42 callback = getattr(callback, attr) 

43 return callback(module) 

44 return import_hook 

45 

46def register_post_import_hook(hook, name): 

47 # Create a deferred import hook if hook is a string name rather than 

48 # a callable function. 

49 

50 if isinstance(hook, string_types): 

51 hook = _create_import_hook_from_string(hook) 

52 

53 with _post_import_hooks_lock: 

54 # Automatically install the import hook finder if it has not already 

55 # been installed. 

56 

57 global _post_import_hooks_init 

58 

59 if not _post_import_hooks_init: 

60 _post_import_hooks_init = True 

61 sys.meta_path.insert(0, ImportHookFinder()) 

62 

63 # Check if the module is already imported. If not, register the hook 

64 # to be called after import. 

65 

66 module = sys.modules.get(name, None) 

67 

68 if module is None: 

69 _post_import_hooks.setdefault(name, []).append(hook) 

70 

71 # If the module is already imported, we fire the hook right away. Note that 

72 # the hook is called outside of the lock to avoid deadlocks if code run as a 

73 # consequence of calling the module import hook in turn triggers a separate 

74 # thread which tries to register an import hook. 

75 

76 if module is not None: 

77 hook(module) 

78 

79# Register post import hooks defined as package entry points. 

80 

81def _create_import_hook_from_entrypoint(entrypoint): 

82 def import_hook(module): 

83 __import__(entrypoint.module_name) 

84 callback = sys.modules[entrypoint.module_name] 

85 for attr in entrypoint.attrs: 

86 callback = getattr(callback, attr) 

87 return callback(module) 

88 return import_hook 

89 

90def discover_post_import_hooks(group): 

91 try: 

92 import pkg_resources 

93 except ImportError: 

94 return 

95 

96 for entrypoint in pkg_resources.iter_entry_points(group=group): 

97 callback = _create_import_hook_from_entrypoint(entrypoint) 

98 register_post_import_hook(callback, entrypoint.name) 

99 

100# Indicate that a module has been loaded. Any post import hooks which 

101# were registered against the target module will be invoked. If an 

102# exception is raised in any of the post import hooks, that will cause 

103# the import of the target module to fail. 

104 

105def notify_module_loaded(module): 

106 name = getattr(module, '__name__', None) 

107 

108 with _post_import_hooks_lock: 

109 hooks = _post_import_hooks.pop(name, ()) 

110 

111 # Note that the hook is called outside of the lock to avoid deadlocks if 

112 # code run as a consequence of calling the module import hook in turn 

113 # triggers a separate thread which tries to register an import hook. 

114 

115 for hook in hooks: 

116 hook(module) 

117 

118# A custom module import finder. This intercepts attempts to import 

119# modules and watches out for attempts to import target modules of 

120# interest. When a module of interest is imported, then any post import 

121# hooks which are registered will be invoked. 

122 

123class _ImportHookLoader: 

124 

125 def load_module(self, fullname): 

126 module = sys.modules[fullname] 

127 notify_module_loaded(module) 

128 

129 return module 

130 

131class _ImportHookChainedLoader: 

132 

133 def __init__(self, loader): 

134 self.loader = loader 

135 

136 if hasattr(loader, "load_module"): 

137 self.load_module = self._load_module 

138 if hasattr(loader, "create_module"): 

139 self.create_module = self._create_module 

140 if hasattr(loader, "exec_module"): 

141 self.exec_module = self._exec_module 

142 

143 def _set_loader(self, module): 

144 # Set module's loader to self.loader unless it's already set to 

145 # something else. Import machinery will set it to spec.loader if it is 

146 # None, so handle None as well. The module may not support attribute 

147 # assignment, in which case we simply skip it. Note that we also deal 

148 # with __loader__ not existing at all. This is to future proof things 

149 # due to proposal to remove the attribue as described in the GitHub 

150 # issue at https://github.com/python/cpython/issues/77458. Also prior 

151 # to Python 3.3, the __loader__ attribute was only set if a custom 

152 # module loader was used. It isn't clear whether the attribute still 

153 # existed in that case or was set to None. 

154 

155 class UNDEFINED: pass 

156 

157 if getattr(module, "__loader__", UNDEFINED) in (None, self): 

158 try: 

159 module.__loader__ = self.loader 

160 except AttributeError: 

161 pass 

162 

163 if (getattr(module, "__spec__", None) is not None 

164 and getattr(module.__spec__, "loader", None) is self): 

165 module.__spec__.loader = self.loader 

166 

167 def _load_module(self, fullname): 

168 module = self.loader.load_module(fullname) 

169 self._set_loader(module) 

170 notify_module_loaded(module) 

171 

172 return module 

173 

174 # Python 3.4 introduced create_module() and exec_module() instead of 

175 # load_module() alone. Splitting the two steps. 

176 

177 def _create_module(self, spec): 

178 return self.loader.create_module(spec) 

179 

180 def _exec_module(self, module): 

181 self._set_loader(module) 

182 self.loader.exec_module(module) 

183 notify_module_loaded(module) 

184 

185class ImportHookFinder: 

186 

187 def __init__(self): 

188 self.in_progress = {} 

189 

190 def find_module(self, fullname, path=None): 

191 # If the module being imported is not one we have registered 

192 # post import hooks for, we can return immediately. We will 

193 # take no further part in the importing of this module. 

194 

195 with _post_import_hooks_lock: 

196 if fullname not in _post_import_hooks: 

197 return None 

198 

199 # When we are interested in a specific module, we will call back 

200 # into the import system a second time to defer to the import 

201 # finder that is supposed to handle the importing of the module. 

202 # We set an in progress flag for the target module so that on 

203 # the second time through we don't trigger another call back 

204 # into the import system and cause a infinite loop. 

205 

206 if fullname in self.in_progress: 

207 return None 

208 

209 self.in_progress[fullname] = True 

210 

211 # Now call back into the import system again. 

212 

213 try: 

214 if not find_spec: 

215 # For Python 2 we don't have much choice but to 

216 # call back in to __import__(). This will 

217 # actually cause the module to be imported. If no 

218 # module could be found then ImportError will be 

219 # raised. Otherwise we return a loader which 

220 # returns the already loaded module and invokes 

221 # the post import hooks. 

222 

223 __import__(fullname) 

224 

225 return _ImportHookLoader() 

226 

227 else: 

228 # For Python 3 we need to use find_spec().loader 

229 # from the importlib.util module. It doesn't actually 

230 # import the target module and only finds the 

231 # loader. If a loader is found, we need to return 

232 # our own loader which will then in turn call the 

233 # real loader to import the module and invoke the 

234 # post import hooks. 

235 

236 loader = getattr(find_spec(fullname), "loader", None) 

237 

238 if loader and not isinstance(loader, _ImportHookChainedLoader): 

239 return _ImportHookChainedLoader(loader) 

240 

241 finally: 

242 del self.in_progress[fullname] 

243 

244 def find_spec(self, fullname, path=None, target=None): 

245 # Since Python 3.4, you are meant to implement find_spec() method 

246 # instead of find_module() and since Python 3.10 you get deprecation 

247 # warnings if you don't define find_spec(). 

248 

249 # If the module being imported is not one we have registered 

250 # post import hooks for, we can return immediately. We will 

251 # take no further part in the importing of this module. 

252 

253 with _post_import_hooks_lock: 

254 if fullname not in _post_import_hooks: 

255 return None 

256 

257 # When we are interested in a specific module, we will call back 

258 # into the import system a second time to defer to the import 

259 # finder that is supposed to handle the importing of the module. 

260 # We set an in progress flag for the target module so that on 

261 # the second time through we don't trigger another call back 

262 # into the import system and cause a infinite loop. 

263 

264 if fullname in self.in_progress: 

265 return None 

266 

267 self.in_progress[fullname] = True 

268 

269 # Now call back into the import system again. 

270 

271 try: 

272 # This should only be Python 3 so find_spec() should always 

273 # exist so don't need to check. 

274 

275 spec = find_spec(fullname) 

276 loader = getattr(spec, "loader", None) 

277 

278 if loader and not isinstance(loader, _ImportHookChainedLoader): 

279 spec.loader = _ImportHookChainedLoader(loader) 

280 

281 return spec 

282 

283 finally: 

284 del self.in_progress[fullname] 

285 

286# Decorator for marking that a function should be called as a post 

287# import hook when the target module is imported. 

288 

289def when_imported(name): 

290 def register(hook): 

291 register_post_import_hook(hook, name) 

292 return hook 

293 return register