![](/style/images/good.png)
![](/style/images/bad.png)
Pytest 源码解读 [1] - [pluggy] 插件框架介绍
source link: http://markshao.github.io/2019/10/01/pluggy-guideline/#firstresult
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
今天是祖国母亲的70华诞,早上在家里和媳妇一起看了令人激动的阅兵,真心为自己做位一个中国人而自豪。国庆的上海天气有点操蛋,受到台风的影响外面是大风大雨,不过因为媳妇怀孕的原因本来也没计划出去玩,趁着难得的假期,在家喝喝茶,吃吃东西,写写博客,把的之前拖欠的 Pytest 源码解读
给完成。
言归正传,上次说了 Pytest
的核心是基于Pluggy
这个 Python Plugin 框架,这次先介绍一下 Pluggy
的核心功能
一个简单的Demo
Pluggy
已经从之前的 Pytest
源码中独立出了一个单独的 Repo , 对于 Pytest
自身也是把它作为一个外部的依赖来使用,我们这里就用一个独立的 Python 项目来 Demo,先看代码
from pluggy import HookspecMarker, HookimplMarker, PluginManager
spec = HookspecMarker("pluggy_demo_1")
impl = HookimplMarker("pluggy_demo_1")
class HookSpec:
@spec(historic=True)
def calculate(self, a, b):
pass
class HookImpl1:
@impl
def calculate(self, a, b):
return a + b
pm = PluginManager("pluggy_demo_1")
pm.add_hookspecs(HookSpec)
pm.register(HookImpl1())
pm.hook.calculate(a=1, b=2)
Output
[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 ,会返回多个结果,我们来看这个例子
from pluggy import HookspecMarker, HookimplMarker, PluginManager
spec = HookspecMarker("pluggy_demo_1")
impl = HookimplMarker("pluggy_demo_1")
class HookSpec:
@spec
def calculate(self, a, b):
pass
class HookImpl1:
@impl
def calculate(self, a, b):
return a + b
class HookImpl2:
@impl
def calculate(self, a, b):
return a * b
pm = PluginManager("pluggy_demo_1")
pm.add_hookspecs(HookSpec)
pm.register(HookImpl1())
pm.register(HookImpl2())
print(pm.hook.calculate(a=1, b=2))
Output
[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
参数, 执行的顺序就变了
class HookImpl1:
@impl(tryfirst=True)
def calculate(self, a, b):
return a + b
Output
[3,2]
HookspecMarker 装饰器参数
hookwrapper
这里我们实现一个特殊 plugin ImplWrapper
,先看代码
from pluggy import HookspecMarker, HookimplMarker, PluginManager
spec = HookspecMarker("plugin_demo_2")
impl = HookimplMarker("plugin_demo_2")
pm = PluginManager("plugin_demo_2")
class Spec:
@spec
def calculate(self, a, b):
pass
class Impl1:
@impl
def calculate(self, a, b):
return a + b
class Impl2:
@impl(tryfirst=True)
def calculate(self, a, b):
return a + b + 2
class ImplWrapper:
@impl(hookwrapper=True)
def calculate(self, a, b):
print("before logic")
outcome = yield
print("Get Result %s" % outcome.result)
return a * b * 10
pm.add_hookspecs(Spec)
pm.register(Impl1())
pm.register(Impl2())
pm.register(ImplWrapper())
print(pm.hook.calculate(a=1, b=2))
Output
before logic
Get Result [5, 3]
[5, 3]
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
参数,我们看一下执行结果
class Spec:
@spec(firstresult)
def calculate(self, a, b):
pass
class HookImpl1:
@impl(tryfirst=True)
def calculate(self, a, b):
return a + b
Output
[2]
关于 plugin
的基本使用就先介绍到这里了,大家有有兴趣可以看这篇文章,介绍的很细致 https://buildmedia.readthedocs.org/media/pdf/pluggy/latest/pluggy.pdf
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK