Mercurial > repos > shellac > sam_consensus_v3
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") |
