Pytest 源码解读 [1] - [pluggy] 插件框架介绍
前言
今天是祖国母亲的70华诞,早上在家里和媳妇一起看了令人激动的阅兵,真心为自己做位一个中国人而自豪。国庆的上海天气有点操蛋,受到台风的影响外面是大风大雨,不过因为媳妇怀孕的原因本来也没计划出去玩,趁着难得的假期,在家喝喝茶,吃吃东西,写写博客,把的之前拖欠的 Pytest 源码解读
给完成。
言归正传,上次说了 Pytest
的核心是基于Pluggy
这个 Python Plugin 框架,这次先介绍一下 Pluggy
的核心功能
一个简单的Demo
Pluggy
已经从之前的 Pytest
源码中独立出了一个单独的 Repo , 对于 Pytest
自身也是把它作为一个外部的依赖来使用,我们这里就用一个独立的 Python 项目来 Demo,先看代码
1 | from pluggy import HookspecMarker, HookimplMarker, PluginManager |
Output
1 | [3] |
解释
Pluggy
的核心就是三个类HookspecMarker
,HookimplMarker
,PluginManager
,核心的插件逻辑就是定义了一组 hook 的方法,然后 plugin 是hook 方法的具体实现- 整个 Project 需要用一个全局唯一的 Project Name ,这里是
pluggy_demo_1
HookSpec
是一个申明 hook method 的 class ,每一个 hook method 需要用spec
的装饰器来装饰HookImpl1
是一个 plugin 的实现,需要完整实现对应的hook方法,并且通过impl
装饰器来装饰- 核心代码的调用逻辑就是先创建一个
PluginManager
对象,注册 Spec 和对应的 plugin 对象,然后通过PluginManager
自带的 hook 变量来调用对应的hook方法,传入相关的参数即可。切记在调用 hook 的时候参数必须是通过关键字的方式来传递
hook 和 plugin 的关系
hook 和 plugin 的对应关系是 1:N
,如果说注册了多个实现了同一个 hook 的 plugin ,会返回多个结果,我们来看这个例子
1 | from pluggy import HookspecMarker, HookimplMarker, PluginManager |
Output
1 | [2,3] |
解释
在这里我们注册了两个 plugin ,
HookImpl1
和HookImpl2
,分别对应了加法和乘法的两个不同逻辑一次 hook 的调用返回了2个plugin 执行的结果,注意一下这里是先执行后注册的
HookImpl2
,再执行先注册的HookImpl1
, 下次具体分析pluggy
实现的时候会解释
plugin 调用顺序
HookimplMarker 装饰器参数
HookimplMarker
装饰器支持一些特定的参数
- tryfirst - 顾名思义就是这个 plugin 在
1:N
的执行链路中先执行 - trylast - 顾名思义后执行
- hookwrapper - 基于
yield
实现的一个wrapper,先执行 wrapper plugin 的一部分逻辑,然后执行其他 plugin,最后执行剩余的 wrapper plugin 逻辑
tryfirst
我们修改一下刚才那个demo,把HookImpl1
加上tryfirst
参数, 执行的顺序就变了
1 | class HookImpl1: |
Output
1 | [3,2] |
HookspecMarker 装饰器参数
hookwrapper
这里我们实现一个特殊 plugin ImplWrapper
,先看代码
1 | from pluggy import HookspecMarker, HookimplMarker, PluginManager |
Output
1 | before logic |
解释
ImplWrapper
是一个类似coroutine
的 生成器,它有两段逻辑,用outcome = yield
来分割- outcome 通过
yield
来获取,它是_Result
对象,包含了非wrapper 的 plugin 的执行结果,这里就是Impl1
和Impl2
,从实际的output来看,Get Result [5,3]
就是获取了返回值 - wrapper plugin 的返回值是会被 ignore 的,具体的原因下次分析源码的时候会给解释
HookspecMarker 装饰器参数
HookspckMarker
装饰器也支持一些参数,主要是
- firstresult - 获取第一个plugin 执行结果后就中断后续执行
- historic - 表示这个 hook 是需要保存call history 的,当有新的 plugin 注册的时候,需要回放历史
firstresult
调整一下 HookSpec
,添加 firstresult
参数,我们看一下执行结果
1 | class Spec: |
Output
1 | [2] |
总结
关于 plugin
的基本使用就先介绍到这里了,大家有有兴趣可以看这篇文章,介绍的很细致 https://buildmedia.readthedocs.org/media/pdf/pluggy/latest/pluggy.pdf