PluginManager.hook
调用plugin实现。 def __call__(self, *args, **kwargs):
if args:
raise TypeError("hook calling supports only keyword arguments")
assert not self.is_historic()
if self.spec and self.spec.argnames:
notincall = (
set(self.spec.argnames) - set(["__multicall__"]) - set(kwargs.keys())
)
if notincall:
warnings.warn(
"Argument(s) {} which are declared in the hookspec "
"can not be found in this hook call".format(tuple(notincall)),
stacklevel=2,
)
return self._hookexec(self, self.get_hookimpls(), kwargs)
__call__
的代码可以看到核心逻辑是最后一行的self._hookexec
,我们可以发现这是_HookCaller的一个属性self._hookexec
往回找到_HookCaller的构造函数 def __init__(self, name, hook_execute, specmodule_or_class=None, spec_opts=None):
self.name = name
self._wrappers = []
self._nonwrappers = []
self._hookexec = hook_execute
_HookCaller
对象时传入的的方法,我们再往回找,看看hook_execute
是从哪里传进来的hook_execute
是从PluginManager类的register方法中实例化_HookCaller时传递的 if hook is None:
hook = _HookCaller(name, self._hookexec)
def _hookexec(self, hook, methods, kwargs):
# called from all hookcaller instances.
# enable_tracing will set its own wrapping function at self._inner_hookexec
return self._inner_hookexec(hook, methods, kwargs)
hook_execute
其实是hook.multicall方法,也就是multicall函数的封装 self._inner_hookexec = lambda hook, methods, kwargs: hook.multicall(
methods,
kwargs,
firstresult=hook.spec.opts.get("firstresult") if hook.spec else False,
)
_HookCaller
类的self.multicall
class _HookCaller(object):
def __init__(self, name, hook_execute, specmodule_or_class=None, spec_opts=None):
self.name = name
self._wrappers = []
self._nonwrappers = []
self._hookexec = hook_execute
self.argnames = None
self.kwargnames = None
self.multicall = _multicall
_HookCaller
类的_multicall
方法,分段看一下 def _multicall(hook_impls, caller_kwargs, firstresult=False):
"""Execute a call into multiple python functions/methods and return the
result(s).
``caller_kwargs`` comes from _HookCaller.__call__().
"""
__tracebackhide__ = True
results = []
excinfo = None
try: # run impl and wrapper setup functions in a loop
teardowns = []
try:
for hook_impl in reversed(hook_impls):
try:
args = [caller_kwargs[argname] for argname in hook_impl.argnames]
except KeyError:
for argname in hook_impl.argnames:
if argname not in caller_kwargs:
raise HookCallError(
"hook call must provide argument %r" % (argname,)
)
if hook_impl.hookwrapper:
try:
gen = hook_impl.function(*args)
next(gen) # first yield
teardowns.append(gen)
except StopIteration:
_raise_wrapfail(gen, "did not yield")
else:
res = hook_impl.function(*args)
if res is not None:
results.append(res)
if firstresult: # halt further impl calls
break
gen = hook_impl.function(*args)
执行plugin function中yield前的部分,然后停下next(gen)
迭代到plugin function中yield后面的部分 finally:
if firstresult: # first result hooks return a single value
outcome = _Result(results[0] if results else None, excinfo)
else:
outcome = _Result(results, excinfo)
# run all wrapper post-yield blocks
for gen in reversed(teardowns):
try:
gen.send(outcome)
_raise_wrapfail(gen, "has second yield")
except StopIteration:
pass
return outcome.get_result()
_raise_wrapfail(gen, "has second yield")
->RuntimeError
_raise_wrapfail(gen, "has second yield")
会抛出错误了。hook_execute
->self._hookexec
->pm.hook.xxx(**kwargs)
class _Result(object):
def __init__(self, result, excinfo):
self._result = result
self._excinfo = excinfo
def get_result(self):
"""Get the result(s) for this hook call.
If the hook was marked as a ``firstresult`` only a single value
will be returned otherwise a list of results.
"""
__tracebackhide__ = True
if self._excinfo is None:
return self._result
else:
ex = self._excinfo
if _py3:
raise ex[1].with_traceback(ex[2])
_reraise(*ex) # noqa
hook call
过程是有否异常。hook call
的执行结果。BaseException
的回溯结果GitHub:https://github.com/potatoImp/pytestCodeParsing