comparison env/lib/python3.9/site-packages/pluggy/hooks.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 """
2 Internal hook annotation, representation and calling machinery.
3 """
4 import inspect
5 import sys
6 import warnings
7 from .callers import _legacymulticall, _multicall
8
9
10 class HookspecMarker(object):
11 """ Decorator helper class for marking functions as hook specifications.
12
13 You can instantiate it with a project_name to get a decorator.
14 Calling :py:meth:`.PluginManager.add_hookspecs` later will discover all marked functions
15 if the :py:class:`.PluginManager` uses the same project_name.
16 """
17
18 def __init__(self, project_name):
19 self.project_name = project_name
20
21 def __call__(
22 self, function=None, firstresult=False, historic=False, warn_on_impl=None
23 ):
24 """ if passed a function, directly sets attributes on the function
25 which will make it discoverable to :py:meth:`.PluginManager.add_hookspecs`.
26 If passed no function, returns a decorator which can be applied to a function
27 later using the attributes supplied.
28
29 If ``firstresult`` is ``True`` the 1:N hook call (N being the number of registered
30 hook implementation functions) will stop at I<=N when the I'th function
31 returns a non-``None`` result.
32
33 If ``historic`` is ``True`` calls to a hook will be memorized and replayed
34 on later registered plugins.
35
36 """
37
38 def setattr_hookspec_opts(func):
39 if historic and firstresult:
40 raise ValueError("cannot have a historic firstresult hook")
41 setattr(
42 func,
43 self.project_name + "_spec",
44 dict(
45 firstresult=firstresult,
46 historic=historic,
47 warn_on_impl=warn_on_impl,
48 ),
49 )
50 return func
51
52 if function is not None:
53 return setattr_hookspec_opts(function)
54 else:
55 return setattr_hookspec_opts
56
57
58 class HookimplMarker(object):
59 """ Decorator helper class for marking functions as hook implementations.
60
61 You can instantiate with a ``project_name`` to get a decorator.
62 Calling :py:meth:`.PluginManager.register` later will discover all marked functions
63 if the :py:class:`.PluginManager` uses the same project_name.
64 """
65
66 def __init__(self, project_name):
67 self.project_name = project_name
68
69 def __call__(
70 self,
71 function=None,
72 hookwrapper=False,
73 optionalhook=False,
74 tryfirst=False,
75 trylast=False,
76 ):
77
78 """ if passed a function, directly sets attributes on the function
79 which will make it discoverable to :py:meth:`.PluginManager.register`.
80 If passed no function, returns a decorator which can be applied to a
81 function later using the attributes supplied.
82
83 If ``optionalhook`` is ``True`` a missing matching hook specification will not result
84 in an error (by default it is an error if no matching spec is found).
85
86 If ``tryfirst`` is ``True`` this hook implementation will run as early as possible
87 in the chain of N hook implementations for a specification.
88
89 If ``trylast`` is ``True`` this hook implementation will run as late as possible
90 in the chain of N hook implementations.
91
92 If ``hookwrapper`` is ``True`` the hook implementations needs to execute exactly
93 one ``yield``. The code before the ``yield`` is run early before any non-hookwrapper
94 function is run. The code after the ``yield`` is run after all non-hookwrapper
95 function have run. The ``yield`` receives a :py:class:`.callers._Result` object
96 representing the exception or result outcome of the inner calls (including other
97 hookwrapper calls).
98
99 """
100
101 def setattr_hookimpl_opts(func):
102 setattr(
103 func,
104 self.project_name + "_impl",
105 dict(
106 hookwrapper=hookwrapper,
107 optionalhook=optionalhook,
108 tryfirst=tryfirst,
109 trylast=trylast,
110 ),
111 )
112 return func
113
114 if function is None:
115 return setattr_hookimpl_opts
116 else:
117 return setattr_hookimpl_opts(function)
118
119
120 def normalize_hookimpl_opts(opts):
121 opts.setdefault("tryfirst", False)
122 opts.setdefault("trylast", False)
123 opts.setdefault("hookwrapper", False)
124 opts.setdefault("optionalhook", False)
125
126
127 if hasattr(inspect, "getfullargspec"):
128
129 def _getargspec(func):
130 return inspect.getfullargspec(func)
131
132
133 else:
134
135 def _getargspec(func):
136 return inspect.getargspec(func)
137
138
139 _PYPY3 = hasattr(sys, "pypy_version_info") and sys.version_info.major == 3
140
141
142 def varnames(func):
143 """Return tuple of positional and keywrord argument names for a function,
144 method, class or callable.
145
146 In case of a class, its ``__init__`` method is considered.
147 For methods the ``self`` parameter is not included.
148 """
149 cache = getattr(func, "__dict__", {})
150 try:
151 return cache["_varnames"]
152 except KeyError:
153 pass
154
155 if inspect.isclass(func):
156 try:
157 func = func.__init__
158 except AttributeError:
159 return (), ()
160 elif not inspect.isroutine(func): # callable object?
161 try:
162 func = getattr(func, "__call__", func)
163 except Exception:
164 return (), ()
165
166 try: # func MUST be a function or method here or we won't parse any args
167 spec = _getargspec(func)
168 except TypeError:
169 return (), ()
170
171 args, defaults = tuple(spec.args), spec.defaults
172 if defaults:
173 index = -len(defaults)
174 args, kwargs = args[:index], tuple(args[index:])
175 else:
176 kwargs = ()
177
178 # strip any implicit instance arg
179 # pypy3 uses "obj" instead of "self" for default dunder methods
180 implicit_names = ("self",) if not _PYPY3 else ("self", "obj")
181 if args:
182 if inspect.ismethod(func) or (
183 "." in getattr(func, "__qualname__", ()) and args[0] in implicit_names
184 ):
185 args = args[1:]
186
187 try:
188 cache["_varnames"] = args, kwargs
189 except TypeError:
190 pass
191 return args, kwargs
192
193
194 class _HookRelay(object):
195 """ hook holder object for performing 1:N hook calls where N is the number
196 of registered plugins.
197
198 """
199
200
201 class _HookCaller(object):
202 def __init__(self, name, hook_execute, specmodule_or_class=None, spec_opts=None):
203 self.name = name
204 self._wrappers = []
205 self._nonwrappers = []
206 self._hookexec = hook_execute
207 self.argnames = None
208 self.kwargnames = None
209 self.multicall = _multicall
210 self.spec = None
211 if specmodule_or_class is not None:
212 assert spec_opts is not None
213 self.set_specification(specmodule_or_class, spec_opts)
214
215 def has_spec(self):
216 return self.spec is not None
217
218 def set_specification(self, specmodule_or_class, spec_opts):
219 assert not self.has_spec()
220 self.spec = HookSpec(specmodule_or_class, self.name, spec_opts)
221 if spec_opts.get("historic"):
222 self._call_history = []
223
224 def is_historic(self):
225 return hasattr(self, "_call_history")
226
227 def _remove_plugin(self, plugin):
228 def remove(wrappers):
229 for i, method in enumerate(wrappers):
230 if method.plugin == plugin:
231 del wrappers[i]
232 return True
233
234 if remove(self._wrappers) is None:
235 if remove(self._nonwrappers) is None:
236 raise ValueError("plugin %r not found" % (plugin,))
237
238 def get_hookimpls(self):
239 # Order is important for _hookexec
240 return self._nonwrappers + self._wrappers
241
242 def _add_hookimpl(self, hookimpl):
243 """Add an implementation to the callback chain.
244 """
245 if hookimpl.hookwrapper:
246 methods = self._wrappers
247 else:
248 methods = self._nonwrappers
249
250 if hookimpl.trylast:
251 methods.insert(0, hookimpl)
252 elif hookimpl.tryfirst:
253 methods.append(hookimpl)
254 else:
255 # find last non-tryfirst method
256 i = len(methods) - 1
257 while i >= 0 and methods[i].tryfirst:
258 i -= 1
259 methods.insert(i + 1, hookimpl)
260
261 if "__multicall__" in hookimpl.argnames:
262 warnings.warn(
263 "Support for __multicall__ is now deprecated and will be"
264 "removed in an upcoming release.",
265 DeprecationWarning,
266 )
267 self.multicall = _legacymulticall
268
269 def __repr__(self):
270 return "<_HookCaller %r>" % (self.name,)
271
272 def __call__(self, *args, **kwargs):
273 if args:
274 raise TypeError("hook calling supports only keyword arguments")
275 assert not self.is_historic()
276 if self.spec and self.spec.argnames:
277 notincall = (
278 set(self.spec.argnames) - set(["__multicall__"]) - set(kwargs.keys())
279 )
280 if notincall:
281 warnings.warn(
282 "Argument(s) {} which are declared in the hookspec "
283 "can not be found in this hook call".format(tuple(notincall)),
284 stacklevel=2,
285 )
286 return self._hookexec(self, self.get_hookimpls(), kwargs)
287
288 def call_historic(self, result_callback=None, kwargs=None, proc=None):
289 """Call the hook with given ``kwargs`` for all registered plugins and
290 for all plugins which will be registered afterwards.
291
292 If ``result_callback`` is not ``None`` it will be called for for each
293 non-``None`` result obtained from a hook implementation.
294
295 .. note::
296 The ``proc`` argument is now deprecated.
297 """
298 if proc is not None:
299 warnings.warn(
300 "Support for `proc` argument is now deprecated and will be"
301 "removed in an upcoming release.",
302 DeprecationWarning,
303 )
304 result_callback = proc
305
306 self._call_history.append((kwargs or {}, result_callback))
307 # historizing hooks don't return results
308 res = self._hookexec(self, self.get_hookimpls(), kwargs)
309 if result_callback is None:
310 return
311 # XXX: remember firstresult isn't compat with historic
312 for x in res or []:
313 result_callback(x)
314
315 def call_extra(self, methods, kwargs):
316 """ Call the hook with some additional temporarily participating
317 methods using the specified ``kwargs`` as call parameters. """
318 old = list(self._nonwrappers), list(self._wrappers)
319 for method in methods:
320 opts = dict(hookwrapper=False, trylast=False, tryfirst=False)
321 hookimpl = HookImpl(None, "<temp>", method, opts)
322 self._add_hookimpl(hookimpl)
323 try:
324 return self(**kwargs)
325 finally:
326 self._nonwrappers, self._wrappers = old
327
328 def _maybe_apply_history(self, method):
329 """Apply call history to a new hookimpl if it is marked as historic.
330 """
331 if self.is_historic():
332 for kwargs, result_callback in self._call_history:
333 res = self._hookexec(self, [method], kwargs)
334 if res and result_callback is not None:
335 result_callback(res[0])
336
337
338 class HookImpl(object):
339 def __init__(self, plugin, plugin_name, function, hook_impl_opts):
340 self.function = function
341 self.argnames, self.kwargnames = varnames(self.function)
342 self.plugin = plugin
343 self.opts = hook_impl_opts
344 self.plugin_name = plugin_name
345 self.__dict__.update(hook_impl_opts)
346
347 def __repr__(self):
348 return "<HookImpl plugin_name=%r, plugin=%r>" % (self.plugin_name, self.plugin)
349
350
351 class HookSpec(object):
352 def __init__(self, namespace, name, opts):
353 self.namespace = namespace
354 self.function = function = getattr(namespace, name)
355 self.name = name
356 self.argnames, self.kwargnames = varnames(function)
357 self.opts = opts
358 self.argnames = ["__multicall__"] + list(self.argnames)
359 self.warn_on_impl = opts.get("warn_on_impl")