stevedore是用来实现动态加载代码的开源模块。它是在OpenStack中用来加载插件的公共模块。能够独立于OpenStack而安装使用:https://pypi.python.org/pypi/stevedore/html
stevedore使用setuptools的entry points来定义并加载插件。entry point引用的是定义在模块中的对象,好比类、函数、实例等,只要在import模块时可以被建立的对象均可以。python
一:插件的名字和命名空间app
通常来说,entry point的名字是公开的,用户可见的,常常出如今配置文件中。而命名空间,也就是entry point组名倒是一种实现细节,通常是面向开发者而非最终用户的。能够用Python的包名做为entry point命名空间,以保证惟一性,但这不是必须的。函数
entry points的主要特征就是,它能够是独立注册的,也就是说插件的开发和安装能够彻底独立于使用它的应用,只要开发者和使用者在命名空间和API上达成一致便可。this
命名空间被用来搜索entry points。entry points的名字在给定的发布包中必须是惟一的,但在一个命名空间中能够不惟一。也就是说,同一个发布包内不容许出现同名的entry point,可是若是是两个独立的发布包,却可使用彻底相同的entrypoint组名和entry point名来注册插件。spa
二:插件的使用方式插件
在stevedore中,有三种使用插件的方式:Drivers、Hooks、Extensions命令行
1:Drivers 设计
一个名字对应一个entry point。使用时根据插件的命名空间和名字,定位到单独的插件:code
2:Hooks,一个名字对应多个entry point。容许同一个命名空间中的插件具备相同的名字,根据给定的命名空间和名字,加载该名字对应的多个插件。
3:Extensions,多个名字,多个entry point。给定命名空间,加载该命名空间中全部的插件,固然也容许同一个命名空间中的插件具备相同的名字。
三:定义并注册插件
在通过了大量的试验和总结教训以后,发现定义API最简单的方式是遵循下面的步骤:
a:使用abc模块,建立一个抽象基类来定义插件API的行为;虽然开发者无需继承一个基类,可是这种方式自有它的好处;
b:经过继承基类并实现必要的方法来建立插件
c:为每一个API定义一个命名空间。能够将应用或者库的名字,以及API的名字结合起来,这种方式通俗易懂,如 “cliff.formatters”或“ceilometer.pollsters.compute”。
本节例子中建立的插件,用来对数据进行格式化输出,每一个格式化方法接受一个字典做为输入,而后按照必定的规则产生要输出的字符串。格式化类能够有一个最大输出宽度的参数。
1:首先定义一个基类,其中的API须要由插件来实现
# example/pluginbase.py import abc import six @six.add_metaclass(abc.ABCMeta) class FormatterBase(object): """Base class for example plugin used in the tutorial.""" def __init__(self, max_width=60): self.max_width = max_width @abc.abstractmethod def format(self, data): """Format the data and return unicode text. :param data: A dictionary with string keys and simple types as values. :type data: dict(str:?) :returns: Iterable producing the formatted text. """
2:定义插件1
开始定义具体的插件类,这些类须要实现format方法。下面是一个简单的插件,它产生的输出都在一行上。
# example/simple.py import pluginbase class Simple(pluginbase.FormatterBase): """A very basic formatter. """ def format(self, data): """Format the data and return unicode text. :param data: A dictionary with string keys and simple types as values. :type data: dict(str:?) """ for name, value in sorted(data.items()): line = '{name} = {value}\n'.format( name=name, value=value, ) yield line
3:注册插件1
本例中,使用” stevedoretest.formatter”做entry points的命名空间,也就是entry points组名,源码树以下:
setup.py example/ __init__.py pluginbase.py simple.py
该发布包的setup.py内容以下:
from setuptools import setup, find_packages setup( name='stevedoretest1', version='1.0', packages=find_packages(), entry_points={ 'stevedoretest.formatter': [ 'simple = example.simple:Simple', 'plain = example.simple:Simple', ], }, )
每一个entry point都以” name = module:importable ”的形式进行注册,name就是插件的名字,module就是python模块,importable就是模块中可引用的对象。
这里注册了两个插件,simple和plain,这两个插件所引用的Python对象是同样的,都是example.simple:Simple类,所以plain只是simple的别名而已。
定义好setup.py以后,运行python setup.py install便可安装该发布包。安装成功后,在该发布的egg目录中存在文件entry_points.txt,其内容以下:
[stevedoretest.formatter] plain = example.simple:Simple simple = example.simple:Simple
运行时,pkg_resources在全部已安装包的entry_points.txt中寻找插件,所以不要手动编辑该文件。
4:定义插件2
使用entry points建立插件的好处之一就是,能够为一个应用独立的开发不一样的插件。所以能够在另一个发布包中定义第二个插件:
#example2/fields.py import textwrap from example import pluginbase class FieldList(pluginbase.FormatterBase): """Format values as a reStructuredText field list. For example:: : name1 : value : name2 : value : name3 : a long value will be wrapped with a hanging indent """ def format(self, data): """Format the data and return unicode text. :param data: A dictionary with string keys and simple types as values. :type data: dict(str:?) """ for name, value in sorted(data.items()): full_text = ': {name} : {value}'.format( name=name, value=value, ) wrapped_text = textwrap.fill( full_text, initial_indent='', subsequent_indent=' ', width=self.max_width, ) yield wrapped_text + '\n'
5:注册插件2
插件2的源码树以下:
setup.py example2/ __init__.py fields.py
在setup.py中,一样要使用”stevedoretest.formatter”做为entry points组名,该发布包的setup.py内容以下:
from setuptools import setup, find_packages setup( name='stevedoretest2', version='1.0', packages=find_packages(), entry_points={ 'stevedoretest.formatter': [ 'fields = example2.fields:FieldList' ], }, )
这里注册了插件fields,它引用的是example2.fields:FieldList类。定义好setup.py以后,运行python setup.py install便可安装该发布包。在该发布的entry_points.txt文件内容以下:
[stevedoretest.formatter] fields = example2.fields:FieldList
四:加载插件
1:Drivers加载
最多见的使用插件的方式是做为单独的驱动来使用,这种场景中,能够有多种插件,但只须要加载和调用其中的一个,这种状况下,可使用stevedore的DriverManager 类。下面就是一个使用该类的例子:
from __future__ import print_function import argparse from stevedore import driver if __name__ == '__main__': parser = argparse.ArgumentParser() parser.add_argument( 'format', nargs='?', default='simple', help='the output format', ) parser.add_argument( '--width', default=60, type=int, help='maximum output width for text', ) parsed_args = parser.parse_args() data = { 'a': 'A', 'b': 'B', 'long': 'word ' * 80, } mgr = driver.DriverManager( namespace='stevedoretest.formatter', name=parsed_args.format, invoke_on_load=True, invoke_args=(parsed_args.width,), ) for chunk in mgr.driver.format(data): print(chunk, end='')
其中的parser主要用来解析命令行参数的,该脚本接受三个参数,一个是format,也就是要使用的插件名字,这里默认是simple;另外一个参数是--width,是插件方法可能会用到的参数,这里默认是60,该脚本还能够经过--help参数打印帮助信息:
# python load_as_driver.py --help usage: load_as_driver.py [-h] [--width WIDTH] [format] positional arguments: format the output format optional arguments: -h, --help show this help message and exit --width WIDTH maximum output width for text
在该脚本中,driver.DriverManager以插件的命名空间以及插件名来寻找插件,也就是entry points组名和entry points自己的名字。也就是但愿经过组名和entry point自己的名字来惟必定位一个插件,可是由于相同的entry points组中能够有同名的entry point,因此,对于DriverManager来讲,若是经过entry points组名和entry points自己的名字找到了多个注册的插件,则会报错。好比本例中,若是在”stevedoretest.formatter”中,有多个发布模块注册了名为”simiple”的entry point,则执行该脚本时就会报错:
RuntimeError: Multiple 'stevedoretest.formatter' drivers found: example.simple:Simple,example2.fields:FieldList
因invoke_on_load为True,因此在加载该插件的时候就会调用它,这里插件引用的是一个类,因此加载插件的时候,就会实例化该类。invoke_args会以位置参数的形式,传递给该类的初始化方法,也就是用来设置输出的宽度。
当建立了一个manager以后,就已经建立好了某个具体插件类的实例。该实例的引用被关联到了manager的driver属性上,所以能够经过driver调用该实例的方法了:
for chunk in mgr.driver.format(data): print(chunk, end='')
运行该脚本,传入不一样的参数,能够获得不一样的输出格式:
# python load_as_driver.py a = A b = B long = word word ... word
# python load_as_driver.py plain a = A b = B long = word word ... word
# python load_as_driver.py fields : a : A : b : B : long : word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word
# python load_as_driver.py fields --width 30 : a : A : b : B : long : word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word
2:Extensions加载
另外一种使用插件的方式是一次性的加载多个扩展,能够有多个manager类支持这种使用模式,包括ExtensionManager,NamedExtensionManager和 EnabledExtensionManager。好比下面的代码:
import argparse from stevedore import extension if __name__ == '__main__': parser = argparse.ArgumentParser() parser.add_argument( '--width', default=60, type=int, help='maximum output width for text', ) parsed_args = parser.parse_args() data = { 'a': 'A', 'b': 'B', 'long': 'word ' * 80, } mgr = extension.ExtensionManager( namespace='stevedoretest.formatter', invoke_on_load=True, invoke_args=(parsed_args.width,), ) def format_data(ext, data): return (ext.name, ext.obj.format(data)) results = mgr.map(format_data, data) for name, result in results: print 'Formatter: %s'%name for chunk in result: print chunk
ExtensionManager和DriverManager略有不一样,它不须要提早知道要加载哪一个插件,它会加载全部找到的插件。
要想调用插件,须要使用map方法,须要传给map一个函数,这里就是format_data函数,针对每一个扩展都会调用该函数。format_data函数有两个参数,分别是Extension实例和map的第二个参数data:
def format_data(ext, data): return (ext.name, ext.obj.format(data)) results = mgr.map(format_data, data)
format_data的Extension参数,是stevedore中封装插件的一个类,该类的成员有:表示插件名字的name;表示由pkg_resources返回的EntryPoint实例的entry_point,表示插件自己的plugin,也就是entry_point.load()的返回值;若是invoke_on_load为True,则还有一个成员obj表示调用plugin(*args, **kwds)后返回的结果。
map函数返回一个序列,其中的每一个元素就是回调函数的返回值,也就是format_data的返回值。函数format_data返回一个元组,该元组包含扩展的名字,以及调用插件的format方法后的返回值。
插件加载的顺序是未定义的,该顺序取决于找到包的顺序,以及包中元数据文件的读取顺序,若是关心插件加载的顺序的话,可使用NamedExtensionManager.类。下面是调用该脚本的例子:
# python load_as_extension.py --width=30 Formatter: simple a = A b = B long = word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word Formatter: plain a = A b = B long = word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word Formatter: fields : a : A : b : B : long : word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word
之因此要在map中使用回调函数,而不是直接调用插件,是由于这样作能够保持应用代码和插件之间的隔离性,这种隔离性有利于应用代码和插件API的设计。
若是map直接调用插件,则每一个插件必须是可调用的,这样命名空间实际上就只能用于插件的一个方法上了。使用回调函数,则插件的API就无需在应用中匹配特定的用例。
3:Hook式加载
最后一种使用插件的方式,至关于Drivers加载和Extensions加载的结合。它容许在给定的entry points组名下有同名的entry point,这样,在给定entry points组名和entry point名的状况下,hook式加载会加载全部找到的插件。
好比这里的例子,在插件2中,注册一个一样名为”simple”的插件,修改其setup.py内如以下:
from setuptools import setup, find_packages setup( name='stevedoretest2', version='1.0', packages=find_packages(), entry_points={ 'stevedoretest.formatter': [ 'fields = example2.fields:FieldList', 'simple= example2.fields:FieldList' ], }, )
这里的”simple”插件也仅仅是”fields”插件的别名而已,它与”fields”引用的对象是同样的。从新安装插件2以后,定义使用Hook加载插件的脚本以下:
import argparse from stevedore import hook if __name__ == '__main__': parser = argparse.ArgumentParser() parser.add_argument( 'format', nargs='?', default='simple', help='the output format', ) parser.add_argument( '--width', default=60, type=int, help='maximum output width for text', ) parsed_args = parser.parse_args() data = { 'a': 'A', 'b': 'B', 'long': 'word ' * 80, } mgr = hook.HookManager( namespace='stevedoretest.formatter', name = parsed_args.format, invoke_on_load=True, invoke_args=(parsed_args.width,), ) def format_data(ext, data): return (ext.name, ext.obj.format(data)) results = mgr.map(format_data, data) for name, result in results: print 'Formatter: %s'%name for chunk in result: print chunk
这里使用hook.HookManager加载插件,参数与构建DriverManager 时是同样的,都是须要给定插件的namespace和name。又由于hook.HookManager继承自NamedExtensionManager,而NamedExtensionManager又继承自ExtensionManager。因此这里使用插件的方式与上例同样。下面是调用该脚本的例子:
# python load_as_hook.py Formatter: simple a = A b = B long = word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word Formatter: simple : a : A : b : B : long : word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word
# python load_as_hook.py fields Formatter: fields : a : A : b : B : long : word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word
更多内容,参阅原文http://docs.openstack.org/developer/stevedore/