Pluggy源码解读----register注册插件源码解析

【原文链接】Pluggy源码解读----register注册插件源码解析

首先看应用中注册的代码如下一行,即调用register函数,传入实现接口的插件类。

pm.register(Plugin_1())

register方法定义如下,这里首先是获取插件的名称,通过register函数的定义可以看出,在注册插件的时候,可以通过name形参指定插件的名称。如果没有指定则通过get_canonical_name方法获取。

    def register(self, plugin, name=None):
        """Register a plugin and return its canonical name or ``None`` if the name
        is blocked from registering.  Raise a :py:class:`ValueError` if the plugin
        is already registered."""
        plugin_name = name or self.get_canonical_name(plugin)

        if plugin_name in self._name2plugin or plugin in self._plugin2hookcallers:
            if self._name2plugin.get(plugin_name, -1) is None:
                return  # blocked plugin, return None to indicate no registration
            raise ValueError(
                "Plugin already registered: %s=%s\n%s"
                % (plugin_name, plugin, self._name2plugin)
            )

        # XXX if an error happens we should make sure no state has been
        # changed at point of return
        self._name2plugin[plugin_name] = plugin

        # register matching hook implementations of the plugin
        self._plugin2hookcallers[plugin] = hookcallers = []
        for name in dir(plugin):
            hookimpl_opts = self.parse_hookimpl_opts(plugin, name)
            if hookimpl_opts is not None:
                normalize_hookimpl_opts(hookimpl_opts)
                method = getattr(plugin, name)
                hookimpl = HookImpl(plugin, plugin_name, method, hookimpl_opts)
                name = hookimpl_opts.get("specname") or name
                hook = getattr(self.hook, name, None)
                if hook is None:
                    hook = _HookCaller(name, self._hookexec)
                    setattr(self.hook, name, hook)
                elif hook.has_spec():
                    self._verify_hook(hook, hookimpl)
                    hook._maybe_apply_history(hookimpl)
                hook._add_hookimpl(hookimpl)
                hookcallers.append(hook)
        return plugin_name

get_canonical_name方法的定义如下,可以看出这里首先获取插件类的__name__属性值,如果没有此属性,则返回插件类的id作为插件的名称。

    def get_canonical_name(self, plugin):
        """Return canonical name for a plugin object. Note that a plugin
        may be registered under a different name which was specified
        by the caller of :py:meth:`register(plugin, name) <.PluginManager.register>`.
        To obtain the name of an registered plugin use :py:meth:`get_name(plugin)
        <.PluginManager.get_name>` instead."""
        return getattr(plugin, "__name__", None) or str(id(plugin))

回到register方法,接下来就是做了一个判断,如果插件名称已经存在_name2plugin属性中或者插件类对象已经存在_plugin2hookcallers属性中了,说明插件已经注册过了,则报错提醒。如果没有注册,如下一行代码可以看到_name2plugin属性果然是存放插件名称和插件类对象的一个属性。

self._name2plugin[plugin_name] = plugin

而接下来一行代码则是说明plugin2hookcallers属性时存放插件和hookcallers映射关系的属性,因为一个插件完全可以存在多个hookcallers,因此这里初始化为一个空的列表。

self._plugin2hookcallers[plugin] = hookcallers = []

紧接下来的for循环部分则和add_hookspecs中的解析接口类属性的代码是类似的,即对HookimplMarker类的所有方法和属性进行循环遍历,然后针对每一个方法或属性首先解析hookimpl_opts的值,是通过parse_hookimpl_opts方法获得的,而parse_hookimpl_opts的定义代码如下,即同样还是看此方法或属性是否被hookimpl装饰器修饰,如果没有被修饰说明不是要找的方法,则直接返回进入下一个循环,如果是则再获取myproject_impl属性,同样通过前面对HookimplMarker类的初始化代码的分析得知,myproject_impl属性的值也是一个字典,而且此字典包含hookwrapper、optionalhook、tryfirst、trylast、specname五个key,即这里res里存的就是由这五个key组成的字典。

    def parse_hookimpl_opts(self, plugin, name):
        method = getattr(plugin, name)
        if not inspect.isroutine(method):
            return
        try:
            res = getattr(method, self.project_name + "_impl", None)
        except Exception:
            res = {}
        if res is not None and not isinstance(res, dict):
            # false positive
            res = None
        return res

再次回到register方法,当hookimpl_opts不为空时,即此时的方法是被hookimpl装饰的亦即是我们所要寻找的myhook实现方法。然后这里又对hookimpl_opts进行了一次规范化处理,即normalize_hookimpl_opts函数,实现代码如下:

def normalize_hookimpl_opts(opts):
    opts.setdefault("tryfirst", False)
    opts.setdefault("trylast", False)
    opts.setdefault("hookwrapper", False)
    opts.setdefault("optionalhook", False)
    opts.setdefault("specname", None)

接下来method即为myhook方法对象,hookimpl为HookImpl类的一个实例对象,HookImpl类的代码如下,可以看出,这里HookImpl类和HookSpec类是类似的,即主要用于存放插件类的属性的,比如这里就存放了插件类中的方法、方法参数、插件、插件名称以及修饰的参数等,这里的__repr__方法是python语言中的常用的一个魔法函数,用于打印对象时在控制台显示的内容。

class HookImpl:
    def __init__(self, plugin, plugin_name, function, hook_impl_opts):
        self.function = function
        self.argnames, self.kwargnames = varnames(self.function)
        self.plugin = plugin
        self.opts = hook_impl_opts
        self.plugin_name = plugin_name
        self.__dict__.update(hook_impl_opts)

    def __repr__(self):
        return f"<HookImpl plugin_name={self.plugin_name!r}, plugin={self.plugin!r}>"

回到register方法,接下来name的取值首先看hookimpl_opts,如果hookimpl_opts字典中有specname则取其值,否则则直接去方法名,这里即myhook,然后获取hook,即hook从当前类的self.hook对象中获取myhook的值,由于在分析add_hookspecs的时候,曾经分析到,self.hook中增加了名为myhook的属性,myhook属性的值为add_hookspecs方法中的hc即_HookCaller类对象,因此这里hook即可以获取到add_hookspecs中的hc。然后通过_add_hookimpl方法继续对hook增加属性配置。

hook._add_hookimpl(hookimpl)

如下,_add_hookimpl方法实现如下,这里可以看到,如果hookimpl的trylast为True,则会在方法列表中将当前的hookimpl插入到第一个,这就是为什么在插件定义中如果使用了trylast=True事,插件会最后一个执行,因为这些方法是先进后出的队列,同样如果设置了tryfirst=True,则会将当前hookimpl使用append插入到最后,这样最后一个就是第一个出队列执行了。此外这里还有hookimpl的hookwrapper是否为True,若为True,则表示此方法中有yield关键字,则此时将hookimpl放入_wrappers属性中,而若为False,则直接将hookimpl放入_nonwrappers属性中。

    def _add_hookimpl(self, hookimpl):
        """Add an implementation to the callback chain."""
        if hookimpl.hookwrapper:
            methods = self._wrappers
        else:
            methods = self._nonwrappers

        if hookimpl.trylast:
            methods.insert(0, hookimpl)
        elif hookimpl.tryfirst:
            methods.append(hookimpl)
        else:
            # find last non-tryfirst method
            i = len(methods) - 1
            while i >= 0 and methods[i].tryfirst:
                i -= 1
            methods.insert(i + 1, hookimpl)

最后将hook放入_plugin2hookcallers属性的plugin类对象作为key的值中。
至此,register方法就解析完成了。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

redrose2100

您的鼓励是我最大的创作动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值