Pytest 源码解读 [1] - [pluggy] 插件框架介绍

Pluggy
  • (https://github.com/pytest-dev/pluggy)
  • Pytest 的核心实际的基于 Pluggy 这个 plugin framework 的,实际上 pytest 本身就是由一个一个插件组成的
  • 本来 pluggy 的代码是在 pytest 的 repo 里,后来迁移了出来,作为一个独立的项目。Pluggy 作为一个独立的plugin framework 来看也是很优雅的存在

 

一个简单的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的核心就是三个类 HookspecMarkerHookimplMarker,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]

 

原文链接:https://markshao.github.io/2019/10/01/pluggy-guideline/
posted @ 2024-01-26 20:01  超级宝宝11  阅读(126)  评论(0编辑  收藏  举报