pytest pluggy库源码解析

代码案例

import pluggy
​
# HookspecMarker 和 HookimplMarker 实质上是一个装饰器带参数的装饰器类,作用是给函数增加额外的属性设置
hookspec = pluggy.HookspecMarker("myproject")
hookimpl = pluggy.HookimplMarker("myproject")
​
'''
HookspeckMarker:
    传入firstresult=True时,获取第一个plugin执行结果后就停止继续执行 @hookspec(firstresult=True)
    historic - 表示这个 hook 是需要保存call history 的,当有新的 plugin 注册的时候,需要回放历史
hookimpl:
    当传入tryfirst=True时,表示这个类的hook函数会优先执行,其他的仍然按照后进先出的顺序执行
    当传入trylast=True,表示当前插件的hook函数会尽可能晚的执行,其他的仍然按照后进先出的顺序执行
    当传入hookwrapper=True时,需要在这个plugin中实现一个yield,plugin先执行yield之前的代码,
        然后去执行其他的pluggin,然后再回来执行yield之后的代码,同时通过yield可以获取到其他插件执行的结果
​
'''
​
# 定义自己的Spec,这里可以理解为定义接口类
class MySpec:
    # hookspec 是一个装饰类中的方法的装饰器,为此方法增额外的属性设置,这里myhook可以理解为定义了一个接口
    # 会给当前方法添加属性  键为 {self.project_name + "_spec"} 值是装饰器传入的参数
    @hookspec
    def myhook(self, arg1, arg2):
        pass
​
​
# 定义了一个插件
class Plugin_1:
    # 插件中实现了上面定义的接口,同样这个实现接口的方法用 hookimpl装饰器装饰,功能是返回两个参数的和
    @hookimpl
    def myhook(self, arg1, arg2):
        print("inside Plugin_1.myhook()")
        return arg1 + arg2
​
​
# 定义第二个插件
class Plugin_2:
    # 插件中实现了上面定义的接口,同样这个实现接口的方法用 hookimpl装饰器装饰,功能是返回两个参数的差
    @hookimpl(hookwrapper=True)
    def myhook(self, arg1, arg2):
        out = yield
        print("inside Plugin_2.myhook()")
        return arg1 - arg2
​
​
# 实例化一个插件管理的对象,注意这里的名称要与文件开头定义装饰器的时候的名称一致
pm = pluggy.PluginManager("myproject")
# 将自定义的接口类加到钩子定义中去
pm.add_hookspecs(MySpec)
# 注册定义的两个插件
pm.register(Plugin_1())
pm.register(Plugin_2())
# 通过插件管理对象的钩子调用方法,这时候两个插件中的这个方法都会执行,而且遵循后注册先执行即LIFO的原则,两个插件的结果讲义列表的形式返回
results = pm.hook.myhook(arg1=1, arg2=2)
print(results)

实例化:

  1. 初始化一些参数,如_name2plugin:存放后续注册plugin

添加到钩子定义中(add_hookspecs):

  1. 将定义的类已参数的方式传递进去(module_or_class)

  2. 遍历类中全部的方法

  3. 判断: getattr(method, self.project_name + "_spec", None),判断类方法中是否有当前定义myproject+spec的属性,

    • 如果有则返回装饰器所得到的参数,没有则返回None,其实就是判断有没有被@hookspec装饰,因为装饰了会设置上myproject+spec这个属性以及相对应的值

  4. 如果有被装饰: 判断一下self.hook中是否以及存在了这个spec

  5. 如果不存在: 创建一个HookCaller(spec名字,hookexec(本质就是一个执行hook的方法),传递进来的spec对象,第三步获得的参数,也就是通过装饰器set到方法中的一些参数)对象

    • init: 判断spec对象是否为空,如果不为空:

      • 先判断参数是否存在,如果存在,创建一个HookSpec给self.spec,传递参数为 当前spec对象,当前spec名字,参数,self.function就是对应的那个被装饰的方法,最后判断一下这个spec需不需要保存历史,如果需要,初始化一个列表

  6. 将上面初始化的对象,通过setattr的方式存在到self.hook中,名字就是被装饰方法的名字,值是刚刚创建的对象

  7. 最后会在names的列表中把这个添加的方法的名字添加进去,判断一个names是否为空,如果为空,则抛出异常

  8. 添加add_hookspecs的步骤全部完成

注册插件(register): 传递实现插件的实体类对象

  1. 判断是否传递插件名字,如果没传,就获取对象的name属性,如果还没有就直接用id()生产一个随机字符串做当前对象在插件中的名字

  2. 判断名字是否存在,或者是否已被注册: self.name2plugin和self.plugin2hookcaller,前者是用 plugin_name 做key,后者是用 plugin object 做key,判断是否已经注册过重复的plugin

  3. self.name2plugin[plugin_name(插件名字)] = plugin(传递的实体类对象) self.plugin2hookcallers[plugin(传递的实体类对象)] = hookcallers = [],其实就是初始化一下self._plugin2hookcallers[plugin],因为列表的引用传递,所有直接修改hookcallers也可以作用在self中

  4. 遍历实体类对象的方法列表,判断是否被impl装饰:

    • a.先获取到方法对象

    • b.判断对象是否是内置函数、函数、方法或者方法描述符,如果不是直接返回

    • c.获取该方法的属性 hook对象创建时传递的名字(myproject) + "_impl" ,没有则返回None

    • d.判断获取到的值是不是None并且不是一个字典,则将获取到的res赋值为None

    • e. 最后返回res,其实就是 hookimpl 装饰器,如果你不给值就给一堆默认值

  5. 先判断参数列表是否为空: 如果不为空,进行设置默认值(其实正常是不会出现没有值的情况),然后从实体类对象中获取到该方法的对象

    • 在创建一个新的对象(HookImpl):init(self,传递进来的实体类对象,hook名字(第一步获取或者id生成),method对象,第四步返回的参数字典),并且将参数列表跟新到self.dict

  6. 判断self.hook中是否以及注册了当前插件(就是add_hookspecs注册的spec中是否有当前方法)

    • 如果没有注册,会直接注册一个,这样注册specmodule_or_class参数会为空,意为着不会有额外的一些参数 eg:tryfirst

  7. hook.has_spec() 判断注册spec的spec属性不为空

    • self.verify_hook(hook(spec对象), hookimpl(插件对象)) a.先判断当前对象中是否有(call_history属性),历史 和是否 需要使用yield b. 判断hookimpl和hook.spec的参数列表是否相等,如果不相等报错

  8. hook.maybe_apply_history(hookimpl) a.判断是否有call_history这个属性

  9. hook._add_hookimpl(hookimpl): a.判断是否为hookwrapper为True,添加到不同的wrappers中 b.判断是否有trylast tryfirst属性,将hookimpl存放到对应位置 c.将hook添加到hookcallers中

  10. 遍历结束后,返回plugin_name(第一步产生)

运行插件 pm.hook.myhook(arg1=1, arg2=2):本质就是调用对象的call方法

  1. 先判断是否有顺序参数,如果有直接报错

  2. 在判断是否有_call_history这个属性

  3. 判断实际传入参数,是否和插件需要参数一样

  4. self._hookexec(self(hook对象), self.get_hookimpls()(全部的已经注册的插件), kwargs(传入的参数))

    • self._inner_hookexec(hook(hook对象), methods(插件), kwargs(参数))

      # 实际调用,也就是hook.multicall的方法
      self._inner_hookexec = lambda hook, methods, kwargs: hook.multicall(
                  methods,
                  kwargs,
                  firstresult=hook.spec.opts.get("firstresult") if hook.spec else False,
              )

    • _multicall(hook_impls(插件), caller_kwargs(参数), firstresult=False(@hookspec传入,默认False))

      1. 先将hook_impls变成一个可迭代对象(reversed(hook_impls))

      2. 先把顺序参数的参数列表,拿到(列表推导式)

      3. 判断需不需要将其他插件执行的结果传递进去

        • 需要

          • 先从hook_impl中拿出对应的方法并且传递参数,执行关键字yield前面部分

          • 然后next()

          • 最后将这个方法添加到teardowns列表中去

        • 不需要

          • 先从hook_impl中拿出对于的方法并且传递参数

          • 判断执行后的返回值是不是为空,不为空则添加到results列表中

          • 最后判断是否有firstresult属性,如果有直接结束循环

      4. 最后执行(finally中代码)

        • 如果firstresult为true,那么直接返回第一个插件返回的结果即可

        • 执行teardowns列表中的需要最后执行的插件

        • 通过迭代器的send方法,将上几个插件的结果传递进去

      5. 返回result对象 :会判断是否有报错 如果没有直接返回结果列表,如果有报错会抛出异常

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值