Mercurial > repos > shellac > sam_consensus_v3
comparison env/lib/python3.9/site-packages/pluggy/manager.py @ 0:4f3585e2f14b draft default tip
"planemo upload commit 60cee0fc7c0cda8592644e1aad72851dec82c959"
author | shellac |
---|---|
date | Mon, 22 Mar 2021 18:12:50 +0000 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:4f3585e2f14b |
---|---|
1 import inspect | |
2 import sys | |
3 from . import _tracing | |
4 from .callers import _Result | |
5 from .hooks import HookImpl, _HookRelay, _HookCaller, normalize_hookimpl_opts | |
6 import warnings | |
7 | |
8 if sys.version_info >= (3, 8): | |
9 from importlib import metadata as importlib_metadata | |
10 else: | |
11 import importlib_metadata | |
12 | |
13 | |
14 def _warn_for_function(warning, function): | |
15 warnings.warn_explicit( | |
16 warning, | |
17 type(warning), | |
18 lineno=function.__code__.co_firstlineno, | |
19 filename=function.__code__.co_filename, | |
20 ) | |
21 | |
22 | |
23 class PluginValidationError(Exception): | |
24 """ plugin failed validation. | |
25 | |
26 :param object plugin: the plugin which failed validation, | |
27 may be a module or an arbitrary object. | |
28 """ | |
29 | |
30 def __init__(self, plugin, message): | |
31 self.plugin = plugin | |
32 super(Exception, self).__init__(message) | |
33 | |
34 | |
35 class DistFacade(object): | |
36 """Emulate a pkg_resources Distribution""" | |
37 | |
38 def __init__(self, dist): | |
39 self._dist = dist | |
40 | |
41 @property | |
42 def project_name(self): | |
43 return self.metadata["name"] | |
44 | |
45 def __getattr__(self, attr, default=None): | |
46 return getattr(self._dist, attr, default) | |
47 | |
48 def __dir__(self): | |
49 return sorted(dir(self._dist) + ["_dist", "project_name"]) | |
50 | |
51 | |
52 class PluginManager(object): | |
53 """ Core :py:class:`.PluginManager` class which manages registration | |
54 of plugin objects and 1:N hook calling. | |
55 | |
56 You can register new hooks by calling :py:meth:`add_hookspecs(module_or_class) | |
57 <.PluginManager.add_hookspecs>`. | |
58 You can register plugin objects (which contain hooks) by calling | |
59 :py:meth:`register(plugin) <.PluginManager.register>`. The :py:class:`.PluginManager` | |
60 is initialized with a prefix that is searched for in the names of the dict | |
61 of registered plugin objects. | |
62 | |
63 For debugging purposes you can call :py:meth:`.PluginManager.enable_tracing` | |
64 which will subsequently send debug information to the trace helper. | |
65 """ | |
66 | |
67 def __init__(self, project_name, implprefix=None): | |
68 """If ``implprefix`` is given implementation functions | |
69 will be recognized if their name matches the ``implprefix``. """ | |
70 self.project_name = project_name | |
71 self._name2plugin = {} | |
72 self._plugin2hookcallers = {} | |
73 self._plugin_distinfo = [] | |
74 self.trace = _tracing.TagTracer().get("pluginmanage") | |
75 self.hook = _HookRelay() | |
76 if implprefix is not None: | |
77 warnings.warn( | |
78 "Support for the `implprefix` arg is now deprecated and will " | |
79 "be removed in an upcoming release. Please use HookimplMarker.", | |
80 DeprecationWarning, | |
81 stacklevel=2, | |
82 ) | |
83 self._implprefix = implprefix | |
84 self._inner_hookexec = lambda hook, methods, kwargs: hook.multicall( | |
85 methods, | |
86 kwargs, | |
87 firstresult=hook.spec.opts.get("firstresult") if hook.spec else False, | |
88 ) | |
89 | |
90 def _hookexec(self, hook, methods, kwargs): | |
91 # called from all hookcaller instances. | |
92 # enable_tracing will set its own wrapping function at self._inner_hookexec | |
93 return self._inner_hookexec(hook, methods, kwargs) | |
94 | |
95 def register(self, plugin, name=None): | |
96 """ Register a plugin and return its canonical name or ``None`` if the name | |
97 is blocked from registering. Raise a :py:class:`ValueError` if the plugin | |
98 is already registered. """ | |
99 plugin_name = name or self.get_canonical_name(plugin) | |
100 | |
101 if plugin_name in self._name2plugin or plugin in self._plugin2hookcallers: | |
102 if self._name2plugin.get(plugin_name, -1) is None: | |
103 return # blocked plugin, return None to indicate no registration | |
104 raise ValueError( | |
105 "Plugin already registered: %s=%s\n%s" | |
106 % (plugin_name, plugin, self._name2plugin) | |
107 ) | |
108 | |
109 # XXX if an error happens we should make sure no state has been | |
110 # changed at point of return | |
111 self._name2plugin[plugin_name] = plugin | |
112 | |
113 # register matching hook implementations of the plugin | |
114 self._plugin2hookcallers[plugin] = hookcallers = [] | |
115 for name in dir(plugin): | |
116 hookimpl_opts = self.parse_hookimpl_opts(plugin, name) | |
117 if hookimpl_opts is not None: | |
118 normalize_hookimpl_opts(hookimpl_opts) | |
119 method = getattr(plugin, name) | |
120 hookimpl = HookImpl(plugin, plugin_name, method, hookimpl_opts) | |
121 hook = getattr(self.hook, name, None) | |
122 if hook is None: | |
123 hook = _HookCaller(name, self._hookexec) | |
124 setattr(self.hook, name, hook) | |
125 elif hook.has_spec(): | |
126 self._verify_hook(hook, hookimpl) | |
127 hook._maybe_apply_history(hookimpl) | |
128 hook._add_hookimpl(hookimpl) | |
129 hookcallers.append(hook) | |
130 return plugin_name | |
131 | |
132 def parse_hookimpl_opts(self, plugin, name): | |
133 method = getattr(plugin, name) | |
134 if not inspect.isroutine(method): | |
135 return | |
136 try: | |
137 res = getattr(method, self.project_name + "_impl", None) | |
138 except Exception: | |
139 res = {} | |
140 if res is not None and not isinstance(res, dict): | |
141 # false positive | |
142 res = None | |
143 # TODO: remove when we drop implprefix in 1.0 | |
144 elif res is None and self._implprefix and name.startswith(self._implprefix): | |
145 _warn_for_function( | |
146 DeprecationWarning( | |
147 "The `implprefix` system is deprecated please decorate " | |
148 "this function using an instance of HookimplMarker." | |
149 ), | |
150 method, | |
151 ) | |
152 res = {} | |
153 return res | |
154 | |
155 def unregister(self, plugin=None, name=None): | |
156 """ unregister a plugin object and all its contained hook implementations | |
157 from internal data structures. """ | |
158 if name is None: | |
159 assert plugin is not None, "one of name or plugin needs to be specified" | |
160 name = self.get_name(plugin) | |
161 | |
162 if plugin is None: | |
163 plugin = self.get_plugin(name) | |
164 | |
165 # if self._name2plugin[name] == None registration was blocked: ignore | |
166 if self._name2plugin.get(name): | |
167 del self._name2plugin[name] | |
168 | |
169 for hookcaller in self._plugin2hookcallers.pop(plugin, []): | |
170 hookcaller._remove_plugin(plugin) | |
171 | |
172 return plugin | |
173 | |
174 def set_blocked(self, name): | |
175 """ block registrations of the given name, unregister if already registered. """ | |
176 self.unregister(name=name) | |
177 self._name2plugin[name] = None | |
178 | |
179 def is_blocked(self, name): | |
180 """ return ``True`` if the given plugin name is blocked. """ | |
181 return name in self._name2plugin and self._name2plugin[name] is None | |
182 | |
183 def add_hookspecs(self, module_or_class): | |
184 """ add new hook specifications defined in the given ``module_or_class``. | |
185 Functions are recognized if they have been decorated accordingly. """ | |
186 names = [] | |
187 for name in dir(module_or_class): | |
188 spec_opts = self.parse_hookspec_opts(module_or_class, name) | |
189 if spec_opts is not None: | |
190 hc = getattr(self.hook, name, None) | |
191 if hc is None: | |
192 hc = _HookCaller(name, self._hookexec, module_or_class, spec_opts) | |
193 setattr(self.hook, name, hc) | |
194 else: | |
195 # plugins registered this hook without knowing the spec | |
196 hc.set_specification(module_or_class, spec_opts) | |
197 for hookfunction in hc.get_hookimpls(): | |
198 self._verify_hook(hc, hookfunction) | |
199 names.append(name) | |
200 | |
201 if not names: | |
202 raise ValueError( | |
203 "did not find any %r hooks in %r" % (self.project_name, module_or_class) | |
204 ) | |
205 | |
206 def parse_hookspec_opts(self, module_or_class, name): | |
207 method = getattr(module_or_class, name) | |
208 return getattr(method, self.project_name + "_spec", None) | |
209 | |
210 def get_plugins(self): | |
211 """ return the set of registered plugins. """ | |
212 return set(self._plugin2hookcallers) | |
213 | |
214 def is_registered(self, plugin): | |
215 """ Return ``True`` if the plugin is already registered. """ | |
216 return plugin in self._plugin2hookcallers | |
217 | |
218 def get_canonical_name(self, plugin): | |
219 """ Return canonical name for a plugin object. Note that a plugin | |
220 may be registered under a different name which was specified | |
221 by the caller of :py:meth:`register(plugin, name) <.PluginManager.register>`. | |
222 To obtain the name of an registered plugin use :py:meth:`get_name(plugin) | |
223 <.PluginManager.get_name>` instead.""" | |
224 return getattr(plugin, "__name__", None) or str(id(plugin)) | |
225 | |
226 def get_plugin(self, name): | |
227 """ Return a plugin or ``None`` for the given name. """ | |
228 return self._name2plugin.get(name) | |
229 | |
230 def has_plugin(self, name): | |
231 """ Return ``True`` if a plugin with the given name is registered. """ | |
232 return self.get_plugin(name) is not None | |
233 | |
234 def get_name(self, plugin): | |
235 """ Return name for registered plugin or ``None`` if not registered. """ | |
236 for name, val in self._name2plugin.items(): | |
237 if plugin == val: | |
238 return name | |
239 | |
240 def _verify_hook(self, hook, hookimpl): | |
241 if hook.is_historic() and hookimpl.hookwrapper: | |
242 raise PluginValidationError( | |
243 hookimpl.plugin, | |
244 "Plugin %r\nhook %r\nhistoric incompatible to hookwrapper" | |
245 % (hookimpl.plugin_name, hook.name), | |
246 ) | |
247 if hook.spec.warn_on_impl: | |
248 _warn_for_function(hook.spec.warn_on_impl, hookimpl.function) | |
249 # positional arg checking | |
250 notinspec = set(hookimpl.argnames) - set(hook.spec.argnames) | |
251 if notinspec: | |
252 raise PluginValidationError( | |
253 hookimpl.plugin, | |
254 "Plugin %r for hook %r\nhookimpl definition: %s\n" | |
255 "Argument(s) %s are declared in the hookimpl but " | |
256 "can not be found in the hookspec" | |
257 % ( | |
258 hookimpl.plugin_name, | |
259 hook.name, | |
260 _formatdef(hookimpl.function), | |
261 notinspec, | |
262 ), | |
263 ) | |
264 | |
265 def check_pending(self): | |
266 """ Verify that all hooks which have not been verified against | |
267 a hook specification are optional, otherwise raise :py:class:`.PluginValidationError`.""" | |
268 for name in self.hook.__dict__: | |
269 if name[0] != "_": | |
270 hook = getattr(self.hook, name) | |
271 if not hook.has_spec(): | |
272 for hookimpl in hook.get_hookimpls(): | |
273 if not hookimpl.optionalhook: | |
274 raise PluginValidationError( | |
275 hookimpl.plugin, | |
276 "unknown hook %r in plugin %r" | |
277 % (name, hookimpl.plugin), | |
278 ) | |
279 | |
280 def load_setuptools_entrypoints(self, group, name=None): | |
281 """ Load modules from querying the specified setuptools ``group``. | |
282 | |
283 :param str group: entry point group to load plugins | |
284 :param str name: if given, loads only plugins with the given ``name``. | |
285 :rtype: int | |
286 :return: return the number of loaded plugins by this call. | |
287 """ | |
288 count = 0 | |
289 for dist in importlib_metadata.distributions(): | |
290 for ep in dist.entry_points: | |
291 if ( | |
292 ep.group != group | |
293 or (name is not None and ep.name != name) | |
294 # already registered | |
295 or self.get_plugin(ep.name) | |
296 or self.is_blocked(ep.name) | |
297 ): | |
298 continue | |
299 plugin = ep.load() | |
300 self.register(plugin, name=ep.name) | |
301 self._plugin_distinfo.append((plugin, DistFacade(dist))) | |
302 count += 1 | |
303 return count | |
304 | |
305 def list_plugin_distinfo(self): | |
306 """ return list of distinfo/plugin tuples for all setuptools registered | |
307 plugins. """ | |
308 return list(self._plugin_distinfo) | |
309 | |
310 def list_name_plugin(self): | |
311 """ return list of name/plugin pairs. """ | |
312 return list(self._name2plugin.items()) | |
313 | |
314 def get_hookcallers(self, plugin): | |
315 """ get all hook callers for the specified plugin. """ | |
316 return self._plugin2hookcallers.get(plugin) | |
317 | |
318 def add_hookcall_monitoring(self, before, after): | |
319 """ add before/after tracing functions for all hooks | |
320 and return an undo function which, when called, | |
321 will remove the added tracers. | |
322 | |
323 ``before(hook_name, hook_impls, kwargs)`` will be called ahead | |
324 of all hook calls and receive a hookcaller instance, a list | |
325 of HookImpl instances and the keyword arguments for the hook call. | |
326 | |
327 ``after(outcome, hook_name, hook_impls, kwargs)`` receives the | |
328 same arguments as ``before`` but also a :py:class:`pluggy.callers._Result` object | |
329 which represents the result of the overall hook call. | |
330 """ | |
331 oldcall = self._inner_hookexec | |
332 | |
333 def traced_hookexec(hook, hook_impls, kwargs): | |
334 before(hook.name, hook_impls, kwargs) | |
335 outcome = _Result.from_call(lambda: oldcall(hook, hook_impls, kwargs)) | |
336 after(outcome, hook.name, hook_impls, kwargs) | |
337 return outcome.get_result() | |
338 | |
339 self._inner_hookexec = traced_hookexec | |
340 | |
341 def undo(): | |
342 self._inner_hookexec = oldcall | |
343 | |
344 return undo | |
345 | |
346 def enable_tracing(self): | |
347 """ enable tracing of hook calls and return an undo function. """ | |
348 hooktrace = self.trace.root.get("hook") | |
349 | |
350 def before(hook_name, methods, kwargs): | |
351 hooktrace.root.indent += 1 | |
352 hooktrace(hook_name, kwargs) | |
353 | |
354 def after(outcome, hook_name, methods, kwargs): | |
355 if outcome.excinfo is None: | |
356 hooktrace("finish", hook_name, "-->", outcome.get_result()) | |
357 hooktrace.root.indent -= 1 | |
358 | |
359 return self.add_hookcall_monitoring(before, after) | |
360 | |
361 def subset_hook_caller(self, name, remove_plugins): | |
362 """ Return a new :py:class:`.hooks._HookCaller` instance for the named method | |
363 which manages calls to all registered plugins except the | |
364 ones from remove_plugins. """ | |
365 orig = getattr(self.hook, name) | |
366 plugins_to_remove = [plug for plug in remove_plugins if hasattr(plug, name)] | |
367 if plugins_to_remove: | |
368 hc = _HookCaller( | |
369 orig.name, orig._hookexec, orig.spec.namespace, orig.spec.opts | |
370 ) | |
371 for hookimpl in orig.get_hookimpls(): | |
372 plugin = hookimpl.plugin | |
373 if plugin not in plugins_to_remove: | |
374 hc._add_hookimpl(hookimpl) | |
375 # we also keep track of this hook caller so it | |
376 # gets properly removed on plugin unregistration | |
377 self._plugin2hookcallers.setdefault(plugin, []).append(hc) | |
378 return hc | |
379 return orig | |
380 | |
381 | |
382 if hasattr(inspect, "signature"): | |
383 | |
384 def _formatdef(func): | |
385 return "%s%s" % (func.__name__, str(inspect.signature(func))) | |
386 | |
387 | |
388 else: | |
389 | |
390 def _formatdef(func): | |
391 return "%s%s" % ( | |
392 func.__name__, | |
393 inspect.formatargspec(*inspect.getargspec(func)), | |
394 ) |