import inspect
import warnings
from .helpers import fqn
from .markers import HookimplMarker, HookspecMarker
[docs]class HookCallError(Exception):
""" Hook was called wrongly.
"""
class HookSpec(object):
def __init__(self, namespace, name, flag_set):
self.namespace = namespace
self.name = name
self.function = getattr(namespace, name)
self.is_first_notnone = self.is_first_only = self.is_replay = \
self.is_required = self.is_sync = False
self.__dict__.update(HookspecMarker.set2dict(flag_set))
self.__init_args()
def __init_args(self):
signature = inspect.signature(self.function)
parameters = list(signature.parameters.values())
if inspect.isclass(self.namespace) and inspect.isfunction(self.function):
parameters = parameters[1:]
if any(parameter.kind != inspect.Parameter.POSITIONAL_OR_KEYWORD # not in self._ALLOWED_PARAMETER_KINDS
for parameter in parameters):
raise ValueError(
"%s.%s%s: Only positional arguments allowed "
"for hook specifications." %
(fqn(self.namespace), self.name, signature)
)
self.req_args = {
p.name for p in parameters
if p.default is inspect.Parameter.empty # p.kind == inspect.Parameter.POSITIONAL_ONLY
}
self.opt_args = {
p.name: p.default for p in parameters
if p.default is not inspect.Parameter.empty # p.kind == inspect.Parameter.POSITIONAL_OR_KEYWORD
}
def __str__(self):
return "%s.%s%s" % (
fqn(self.namespace), self.name, inspect.signature(self.function)
)
[docs]class HookValidationError(Exception):
""" Plugin failed validation.
"""
class HookImpl(object):
_ALLOWED_PARAMETER_KINDS = {
inspect.Parameter.POSITIONAL_ONLY,
inspect.Parameter.POSITIONAL_OR_KEYWORD
}
def __init__(self, plugin, name, flag_set):
self.plugin = plugin
self.name = name
self.function = getattr(plugin, name)
self.is_try_first = self.is_try_last = self.is_dont_await = self.is_before = False
self.__dict__.update(HookimplMarker.set2dict(flag_set))
# noinspection PyUnresolvedReferences
self.is_async = (inspect.iscoroutinefunction(self.function) and
not self.is_dont_await)
self.__init_args()
def __init_args(self):
signature = inspect.signature(self.function)
parameters = list(signature.parameters.values())
if any(parameter.kind != inspect.Parameter.POSITIONAL_OR_KEYWORD # not in self._ALLOWED_PARAMETER_KINDS
for parameter in parameters):
raise ValueError(
"%s.%s%s: Only positional arguments allowed "
"for hook specifications." %
(fqn(self.plugin), self.name, signature)
)
self.req_args = {
p.name for p in parameters
if p.default is inspect.Parameter.empty # p.kind == inspect.Parameter.POSITIONAL_ONLY
}
self.opt_args = {
p.name: p.default for p in parameters
if p.default is not inspect.Parameter.empty # p.kind == inspect.Parameter.POSITIONAL_OR_KEYWORD
}
def filtered_args(self, kwargs):
return {
name: value for (name, value) in kwargs.items()
if name in self.req_args or name in self.opt_args
}
def validate_against(self, spec: HookSpec):
# positional arg checking
notinspec = self.req_args - spec.req_args - \
set(spec.opt_args.keys())
if notinspec:
raise HookValidationError(
"%s.%s: required argument(s) %s in the hook function "
"are not in the hook specification." %
(fqn(self.plugin), self.name, notinspec)
)
for arg_name, default_value in self.opt_args.items():
if arg_name in spec.opt_args and \
default_value != spec.opt_args[arg_name]:
warnings.warn(
"Hook function %s.%s: optional argument '%s' has other "
"default value than the corresponding hook specification." %
(fqn(self.plugin), self.name, arg_name)
)
# noinspection PyUnresolvedReferences
if self.is_async and spec.is_sync:
raise HookValidationError(
"%s.%s: asynchronous hook function not allowod by hook spec." %
(fqn(self.plugin), self.name)
)
def __str__(self):
return "%s.%s%s" % (
fqn(self.plugin), self.name, inspect.signature(self.function)
)