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)
判断是否传递插件名字,如果没传,就获取对象的name属性,如果还没有就直接用 id() 生产一个随机字符串做当前对象在插件中的名字
判断名字是否存在,或者是否已被注册: self._name2plugin 和 self._plugin2hookcaller,前者是用 plugin_name 做 key,后者是用 plugin object 做 key,判断是否已经注册过重复的 plugin
self._name2plugin[plugin_name(插件名字)] = plugin(传递的实体类对象)
self._plugin2hookcallers[plugin(传递的实体类对象)] = hookcallers = [],其实就是初始化一下 self._plugin2hookcallers[plugin],因为列表的引用传递,所有直接修改 hookcallers 也可以作用在 self 中
遍历实体类对象的方法列表,判断是否被 impl 装饰:
先判断参数列表是否为空: 如果不为空,进行设置默认值 (其实正常是不会出现没有值的情况),然后从实体类对象中获取到该方法的对象
判断 self.hook 中是否以及注册了当前插件 (就是 add_hookspecs 注册的 spec 中是否有当前方法)
hook.has_spec() 判断注册 spec 的 spec 属性不为空
hook._maybe_apply_history(hookimpl)
a.判断是否有_call_history 这个属性
hook._add_hookimpl(hookimpl):
a.判断是否为 hookwrapper 为 True,添加到不同的 wrappers 中
b.判断是否有 trylast tryfirst 属性,将 hookimpl 存放到对应位置
c.将 hook 添加到 hookcallers 中
遍历结束后,返回 plugin_name(第一步产生)
先判断是否有顺序参数,如果有直接报错
在判断是否有_call_history 这个属性
判断实际传入参数,是否和插件需要参数一样
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 对象 :会判断是否有报错 如果没有直接返回结果列表,如果有报错会抛出异常