【pytest框架源码分析四】pluggy源码分析之hook执行

news/2025/3/4 16:16:05/

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 theresult(s).``caller_kwargs`` comes from HookCaller.__call__()."""__tracebackhide__ = Trueresults: list[object] = []exception = Noneonly_new_style_wrappers = Truetry:  # run impl and wrapper setup functions in a loopteardowns: 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 = Falsetry:# 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 yieldteardowns.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 yieldteardowns.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 callsbreakexcept BaseException as exc:exception = excfinally:# Fast path - only new-style wrappers, no Result.if only_new_style_wrappers:if firstresult:  # first result hooks return a single valueresult = results[0] if results else Noneelse:result = results# run all wrapper post-yield blocksfor 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.valueexception = Nonecontinueexcept BaseException as e:exception = econtinue_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 valueoutcome: Result[object | list[object]] = Result(results[0] if results else None, exception)else:outcome = Result(results, exception)# run all wrapper post-yield blocksfor teardown in reversed(teardowns):if isinstance(teardown, tuple):try:teardown[0].send(outcome)except StopIteration:passexcept BaseException as e:_warn_teardown_exception(hook_name, teardown[1], e)raiseelse:_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)continueexcept 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参数来处理生成器函数。


http://www.ppmy.cn/news/1576580.html

相关文章

Unity 内置渲染管线各个Shader的用途和性能分析,以及如何修改Shader(build in shader 源码下载)

文章目录 所有Shader分析路径:Standard路径:Nature/路径:UI/路径:Particles/Particles/Standard SurfaceParticles/Standard Unlit 路径:Unlit/Unlit/TextureUnlit/ColorUnlit/TransparentUnlit/Transparent CutoutUnl…

android12 屏幕亮度控制修改为线性变化

由于高版本的亮度调节不是线性变化了,有客户反馈在Android11或者12上使用代码获取亮度不对,比如我们在设置中查看屏幕亮度是80%,读出来的亮度值是100,客户认为亮度值是39%。 获取屏幕亮度adb shell settings get system screen_brightness 或者 adb shell cat /sys/class…

STM32G431RBT6——(2)浅析Cortex-M4内核

本篇博客是一个对Cortex-M4内核了解性的简介,不会涉及到深奥的理论,请大家放心食用。 我们所学习的STM32G431RBT6单片机是基于ARM的Cotex-M4内核,因此我们有必要对此内核做一个大概了解。其实M4内核和M3内核有很大的相似之处,很多…

广州4399游戏25届春招游戏策划管培生内推

【热招岗位】 游戏策划管培生、产品培训生、游戏文案策划、游戏数值策划、游戏系统策划、游戏产品运营、游戏战斗策划、游戏关卡策划 【其他岗位】产品类(产品培训生、产品运营等)、技术类(开发、测试、算法、运维等)、运营市场类…

【计算机网络基础】-------计算机网络概念

1.什么是计算机网络 定义: 图解: 2.最简单的计算机网络 其中: 结点可以是计算机、集线器、交换机、路由器等链路可以是有线链路、无线链路 2.1集线器 2.2交换机 3.互连网(internet)与 路由器 路由器 与 家用路由…

日语学习-日语知识点小记-构建基础-JLPT-N4N5阶段(15):についてどう思いますか 关于 があります有 と言います

日语学习-日语知识点小记-构建基础-JLPT-N4&N5阶段(15):についてどう思いますか 关于& があります有 & と言います 1、前言(1)情况说明(2)工程师的信仰2、知识点(1)「~」についてどう思いますか(2)「~」で「~」があります。(3)「~」と言います…

本地部署 Cursor 编辑器的完整教程

Cursor 是一款基于人工智能的代码编辑器,专为开发者设计,支持智能代码补全、错误检测、代码重构等功能。虽然 Cursor 提供了云端的服务,但如果你想在本地部署 Cursor,以便更好地控制数据隐私或进行定制化开发,本教程将为你提供详细的步骤。 1. 准备工作 在开始之前,请确…

manylinux_2_17_x86_64是什么东西?如何升级到manylinux_2_28_x86_64?

一、什么是manylinux_2_17_x86_64? manylinux_2_17_x86_64是Python生态中用于标识Linux平台二进制兼容性的标准标签,其核心目标是确保编译生成的Python扩展包(如.whl文件)能够在多种Linux发行版上运行。它的命名规则与底层系统库…