1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147
|
"""Hook annotation decorators"""
from typing import Callable, Optional
from .implementation import HookImplementation, HookSpecification
class HookSpecificationMarker:
"""Decorator helper class for marking functions as hook specifications.
You can instantiate it with a project_name to get a decorator. Calling
:py:meth:`.PluginManager.add_hookspecs` later will discover all marked
functions if the :py:class:`.PluginManager` uses the same project_name.
"""
def __init__(self, project_name):
self.project_name = project_name
def __call__(
self,
function: Optional[Callable] = None,
firstresult: bool = False,
historic: bool = False,
warn_on_impl=None,
):
"""if passed a function, directly sets attributes on the function
which will make it discoverable to
:py:meth:`.PluginManager.add_hookspecs`. If passed no function, returns
a decorator which can be applied to a function later using the
attributes supplied.
If ``firstresult`` is ``True`` the 1:N hook call (N being the number of
registered hook implementation functions) will stop at I<=N when the
I'th function returns a non-``None`` result.
If ``historic`` is ``True`` calls to a hook will be memorized and
replayed on later registered plugins.
"""
def setattr_hookspec_opts(func):
if historic and firstresult:
raise ValueError("cannot have a historic firstresult hook")
setattr(
func,
HookSpecification.format_tag(self.project_name),
dict(
firstresult=firstresult,
historic=historic,
warn_on_impl=warn_on_impl,
),
)
return func
if function is not None:
return setattr_hookspec_opts(function)
else:
return setattr_hookspec_opts
class HookImplementationMarker:
"""Decorator helper class for marking functions as hook implementations.
You can instantiate with a ``project_name`` to get a decorator. Calling
:meth:`.PluginManager.register` later will discover all marked functions if
the :class:`.PluginManager` uses the same project_name.
Parameters
----------
project_name : str
A namespace for plugin implementations. Implementations decorated with
this class will be discovered by ``PluginManager.register`` if and only
if ``project_name`` matches the ``project_name`` of the
``PluginManager``.
"""
def __init__(self, project_name: str):
self.project_name = project_name
def __call__(
self,
function: Optional[Callable] = None,
*,
hookwrapper: bool = False,
optionalhook: bool = False,
tryfirst: bool = False,
trylast: bool = False,
specname: str = '',
) -> Callable:
"""Call the marker instance.
If passed a function, directly sets attributes on the function which
will make it discoverable to :meth:`.PluginManager.register`. If passed
no function, returns a decorator which can be applied to a function
later using the attributes supplied.
Parameters
----------
function : callable, optional
A function to decorate as a hook implementation, If ``function`` is
None, this method returns a function that can be used to decorate
other functions.
hookwrapper : bool, optional
Whether this hook implementation behaves as a hookwrapper.
by default False
optionalhook : bool, optional
If ``True``, a missing matching hook specification will not result
in an error (by default it is an error if no matching spec is
found), by default False.
tryfirst : bool, optional
If ``True`` this hook implementation will run as early as possible
in the chain of N hook implementations for a specification, by
default False
trylast : bool, optional
If ``True`` this hook implementation will run as late as possible
in the chain of N hook implementations, by default False
specname : str, optional
If provided, ``specname`` will be used instead of the function name
when matching this hook implementation to a hook specification
during registration, by default the implementation function name
must match the name of the corresponding hook specification.
Returns
-------
Callable
If ``function`` is not ``None``, will decorate the function with
attributes, and return the function. If ``function`` is None, will
return a decorator that can be used to decorate functions.
"""
def set_hook_implementation_attributes(func):
setattr(
func,
HookImplementation.format_tag(self.project_name),
dict(
hookwrapper=hookwrapper,
optionalhook=optionalhook,
tryfirst=tryfirst,
trylast=trylast,
specname=specname,
),
)
return func
if function is None:
return set_hook_implementation_attributes
else:
return set_hook_implementation_attributes(function)
|