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/