pytest系列——pluggy插件源码解读(二)PluginManager类实例化

PluginManager类简介

首先还是把pluggy的小例子放在这:

importpluggy# HookspecMarker 和 HookimplMarker 实质上是一个装饰器带参数的装饰器类,作用是给函数增加额外的属性设置hookspec = pluggy.HookspecMarker("myproject")hookimpl = pluggy.HookimplMarker("myproject")# 定义自己的Spec,这里可以理解为定义接口类classMySpec:# hookspec 是一个装饰类中的方法的装饰器,为此方法增额外的属性设置,这里myhook可以理解为定义了一个接口    @hookspecdefmyhook(self, arg1, arg2):pass# 定义了一个插件classPlugin_1:# 插件中实现了上面定义的接口,同样这个实现接口的方法用 hookimpl装饰器装饰,功能是返回两个参数的和    @hookimpldefmyhook(self, arg1, arg2):print("inside Plugin_1.myhook()")returnarg1 + arg2# 定义第二个插件classPlugin_2:# 插件中实现了上面定义的接口,同样这个实现接口的方法用 hookimpl装饰器装饰,功能是返回两个参数的差    @hookimpldefmyhook(self, arg1, arg2):print("inside Plugin_2.myhook()")returnarg1 - 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)

从上面的使用代码可以看出,通过HookspecMarker类和HookimplMarker实例化了两个对象,通过源码解读(1)知道,这两个实例实质上是两个装饰器,装饰器的作用是给函数设置一个名称为”{project_name}_spec”和”{project_name}_impl”属性

紧接着就是定义的子集的Spec类了,这个类实质上类似于面向对象编程中的定义接口,相当于在这个类中可以定义好许多接口(方法),即只要将自定义的Spec类中的方法加上@hookspec即相当于成为了插件的接口

然后即可以开始定义插件类了,插件类当然需要去对接口类中定义的方法去做接口的实现,即实现接口在不同的插件中的具体功能实现,即在插件中对方法加上@hookimpl装饰即相当于就约定好了是接口的实现(理解上可以这么理解,具体实现后面都会讲到)

紧接着就是到了PluginManager类的实例化了,这个类是pluggy模块中最最核心的一个类,它相当于pluggy的中枢大脑,pluggy的所有动作指令都是从这个类中发出的,这个类的是在manager.py文件中定义的

下面就是PluginManager类的初始化函数的源码(这个类还有很多功能代码,这里先只讲初始化,所以先只放这一小段源码)

classPluginManager:""" Core :py:class:`.PluginManager` class which manages registration

    of plugin objects and 1:N hook calling.

    You can register new hooks by calling :py:meth:`add_hookspecs(module_or_class)

    <.PluginManager.add_hookspecs>`.

    You can register plugin objects (which contain hooks) by calling

    :py:meth:`register(plugin) <.PluginManager.register>`.  The :py:class:`.PluginManager`

    is initialized with a prefix that is searched for in the names of the dict

    of registered plugin objects.

    For debugging purposes you can call :py:meth:`.PluginManager.enable_tracing`

    which will subsequently send debug information to the trace helper.

    """def__init__(self, project_name):        self.project_name = project_name        self._name2plugin = {}        self._plugin2hookcallers = {}        self._plugin_distinfo = []        self.trace = _tracing.TagTracer().get("pluginmanage")        self.hook = _HookRelay()        self._inner_hookexec = _multicall

PluginManager 类实例化的时候主要是初始化了几个变量:

project_name 可以理解为项目名称

_name2plugin 是一个字典,主要用于存放插件名称和插件对象的映射关系

_plugin2hookcallers 是也是一个字典,主要用于存放插件对象和插件对象对应的调用钩子函数的映射关系

_plugin_distinfo 是一个列表,用来存放通过setuptools注册的插件的信息

hook 是 _HookRelay类的实例,这个类的代码在hooks.py文件中,这个是一个空类,目的是用于存放hook函数的,所以主要用在后面注册插件的时候给这个空类的实例加设置属性的

_HookRelay 类的源代码如下:

class_HookRelay:""" hook holder object for performing 1:N hook calls where N is the number

    of registered plugins.

    """

_inner_hookexec 是一个函数,即是_multicall,这个函数的代码在callers.py文件中,这函数是整个pluggy插件模块最最核心的一个函数,所有的被注册的插件中的接口的执行顺序以及结果返回等等逻辑都在这个函数中

_multicall 的代码如下,这里先暂时不分析这个函数,现在只要知道_inner_hookexec属性其实就是_multicall这个函数即可

def_multicall(hook_name, hook_impls, caller_kwargs, firstresult):"""Execute a call into multiple python functions/methods and return the

    result(s).

    ``caller_kwargs`` comes from _HookCaller.__call__().

    """__tracebackhide__ =Trueresults = []    excinfo =Nonetry:# run impl and wrapper setup functions in a loopteardowns = []try:forhook_implinreversed(hook_impls):try:                    args = [caller_kwargs[argname]forargnameinhook_impl.argnames]exceptKeyError:forargnameinhook_impl.argnames:ifargnamenotincaller_kwargs:raiseHookCallError("hook call must provide argument %r"% (argname,)                            )ifhook_impl.hookwrapper:try:                        gen = hook_impl.function(*args)next(gen)# first yieldteardowns.append(gen)exceptStopIteration:                        _raise_wrapfail(gen,"did not yield")else:                    res = hook_impl.function(*args)ifresisnotNone:                        results.append(res)iffirstresult:# halt further impl callsbreakexceptBaseException:            excinfo = sys.exc_info()finally:iffirstresult:# first result hooks return a single valueoutcome = _Result(results[0]ifresultselseNone, excinfo)else:            outcome = _Result(results, excinfo)# run all wrapper post-yield blocksforgeninreversed(teardowns):try:                gen.send(outcome)                _raise_wrapfail(gen,"has second yield")exceptStopIteration:passreturnoutcome.get_result()

至此,PluginManager类的实例化流程就完成了

【功能测试到测试开发的全套教程+各种模板以及工具安装包获取:点击下方推荐的视频即可获取】

----------------------------------------------------------------------------------------------

自动化测试相关教程推荐:

2023最新自动化测试自学教程新手小白26天入门最详细教程,目前已有300多人通过学习这套教程入职大厂!!

_哔哩哔哩_bilibili2023最新合集Python自动化测试开发框架【全栈/实战/教程】合集精华,学完年薪40W+_哔哩哔哩_bilibili

测试开发相关教程推荐

2023全网最牛,字节测试开发大佬现场教学,从零开始教你成为年薪百万的测试开发工程师_哔哩哔哩_bilibili

postman/jmeter/fiddler测试工具类教程推荐

讲的最详细JMeter接口测试/接口自动化测试项目实战合集教程,学jmeter接口测试一套教程就够了!!

_哔哩哔哩_bilibili2023自学fiddler抓包,请一定要看完【如何1天学会fiddler抓包】的全网最详细视频教程!!

_哔哩哔哩_bilibili2023全网封神,B站讲的最详细的Postman接口测试实战教学,小白都能学会_哔哩哔哩_bilibili----------------------------------------------------------------------------------------------------------------

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容