ToB企服应用市场:ToB评测及商务社交产业平台
标题:
【pytest框架源码分析四】pluggy源码分析之hook实行
[打印本页]
作者:
石小疯
时间:
10 小时前
标题:
【pytest框架源码分析四】pluggy源码分析之hook实行
pluggy的主要实行方法在_callers.py中,这里简单介绍下。
def _multicall(
hook_name: str,
hook_impls: Sequence[HookImpl],
caller_kwargs: Mapping[str, object],
firstresult: bool,
) -> object | list[object]:
"""Execute a call into multiple python functions/methods and return the
result(s).
``caller_kwargs`` comes from HookCaller.__call__().
"""
__tracebackhide__ = True
results: list[object] = []
exception = None
only_new_style_wrappers = True
try: # run impl and wrapper setup functions in a loop
teardowns: list[Teardown] = []
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(
f"hook call must provide argument {argname!r}"
)
if hook_impl.hookwrapper:
only_new_style_wrappers = False
try:
# If this cast is not valid, a type error is raised below,
# which is the desired response.
res = hook_impl.function(*args)
wrapper_gen = cast(Generator[None, Result[object], None], res)
next(wrapper_gen) # first yield
teardowns.append((wrapper_gen, hook_impl))
except StopIteration:
_raise_wrapfail(wrapper_gen, "did not yield")
elif hook_impl.wrapper:
try:
# If this cast is not valid, a type error is raised below,
# which is the desired response.
res = hook_impl.function(*args)
function_gen = cast(Generator[None, object, object], res)
next(function_gen) # first yield
teardowns.append(function_gen)
except StopIteration:
_raise_wrapfail(function_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
except BaseException as exc:
exception = exc
finally:
# Fast path - only new-style wrappers, no Result.
if only_new_style_wrappers:
if firstresult: # first result hooks return a single value
result = results[0] if results else None
else:
result = results
# run all wrapper post-yield blocks
for teardown in reversed(teardowns):
try:
if exception is not None:
teardown.throw(exception) # type: ignore[union-attr]
else:
teardown.send(result) # type: ignore[union-attr]
# Following is unreachable for a well behaved hook wrapper.
# Try to force finalizers otherwise postponed till GC action.
# Note: close() may raise if generator handles GeneratorExit.
teardown.close() # type: ignore[union-attr]
except StopIteration as si:
result = si.value
exception = None
continue
except BaseException as e:
exception = e
continue
_raise_wrapfail(teardown, "has second yield") # type: ignore[arg-type]
if exception is not None:
raise exception.with_traceback(exception.__traceback__)
else:
return result
# Slow path - need to support old-style wrappers.
else:
if firstresult: # first result hooks return a single value
outcome: Result[object | list[object]] = Result(
results[0] if results else None, exception
)
else:
outcome = Result(results, exception)
# run all wrapper post-yield blocks
for teardown in reversed(teardowns):
if isinstance(teardown, tuple):
try:
teardown[0].send(outcome)
except StopIteration:
pass
except BaseException as e:
_warn_teardown_exception(hook_name, teardown[1], e)
raise
else:
_raise_wrapfail(teardown[0], "has second yield")
else:
try:
if outcome._exception is not None:
teardown.throw(outcome._exception)
else:
teardown.send(outcome._result)
# Following is unreachable for a well behaved hook wrapper.
# Try to force finalizers otherwise postponed till GC action.
# Note: close() may raise if generator handles GeneratorExit.
teardown.close()
except StopIteration as si:
outcome.force_result(si.value)
continue
except BaseException as e:
outcome.force_exception(e)
continue
_raise_wrapfail(teardown, "has second yield")
return outcome.get_result()
复制代码
其前面的调用过程可参考前面几篇,这里主要介绍下_multicall方法自己。
其入参如下:
hook_name:hook的name,为我们所写方法的名称
hook_impls:hook的impl列表,impl中包括插件,插件的方法及其设置,一个方法一个impl
caller_kwargs:调用hook方法时的入参,即我们编写impl方法自己的入参
firstresult:是否只必要首次结果,bool类型
下面是方法中的正式流程
1.首先界说了个teardown的list,这个是teardown的界说。这里teardown主要是为了天生器函数预备的,在我们调用插件时,如果希望有些函数中某些步骤在所有方法最后实行,则在函数中加入yield,并且wrapper设置成true。则该函数yield前的步骤先按顺序实行,yield之后的在所有函数实行完成后实行。
Teardown = Union[
Tuple[Generator[None, Result[object], None], HookImpl],
Generator[None, object, object],
]
复制代码
2.遍历hook_impls,留意这里reversed,将hook_impls反转了,这是为什么先添加的hook_impl后实行,后添加的先实行。
3.获取调用时的所有args,如果缺少参数,则报错。
4.接下来判断hookwrapper和wrapper参数是否为true,如果为true,这两个处置惩罚过程类似。都是先实行yield之前的内容,然后返回天生器函数放在teardowns里,留待所有函数实行完成再实行。如果不为true,则正常实行函数,并把返回值放到results中。如果只取第一次结果,则直接break跳出循环。
5.然后到finally,这里主要是处置惩罚天生器函数,即上方hookwrapper和wrapper参数为true的情况。
(1)如果是wrappers是true,先根据firstresult取result,然后再取teardowns中的内容。如果上面的实行有异常,则在这里抛出异常;如果没有异常,则继续实行send()方法,这一步大都会抛出异常,由于函数中只会有一个yield,如果这边不抛出异常,说明函数中有两个yeild,背面会提示报错"has second yield"。最后返回result(如果没有声明wrappers和hookwrapper参数,也是走该路径)–留意我们编写的方法中只能有一个yield。
(2)如果hookwrapper是true,也是先判断是否取firstresult,然后和上面的exception组成Result类型的outcome。接下来和上面类似,取teardowns中的内容,这里多个判断isinstance(teardown, tuple),主要和上面的处置惩罚有关,团体是一致的,有异常抛出异常,无异常则继续实行。最后返回result。
这边wrappers路径的处置惩罚过程要比hookwrapper简单。我们背面可以用wrappers参数来处置惩罚天生器函数。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/)
Powered by Discuz! X3.4