pluginbase が便利そう。

@mitsuhiko 先生の新作の pluginbase が便利そうというお話。


Python は他の Python なファイルをモジュールとして動的に読み込む場合は import があるから少し面倒。
例えば今作ってる Flask を使ったアプリケーションで、WSGI ミドルウェアを読み込む時には以下の様にしている。

def configure_middlewares(app):
    middlewares = app.config['MIDDLEWARES']
    if not isinstance(middlewares, tuple):
        middlewares = (middlewares,)
    for middleware in middlewares:
        target = middleware.split('.')
        module = '.'.join(target[0:-1])
        name = target[-1]
        klass = getattr(__import__(module, fromlist=[name]), name)
        app.wsgi_app = klass(app)

app.config['MIDDLEWARES'] には、WSGI ミドルウェア*1を格納しているパスを与えて、ファイル名からを使って、__import__() と getattr() を使っている。
klass(app) としている通り、クラスという制約を課して読み込んでいる*2


こういう風に、自分で頑張る必要がある。
現状これで困ってないけど、もっとシンプルにプラグインを読み出すフレームワークpluginbase


ディレクトリ構成はこんな感じ。

.
├── app.py
└── plugins
    ├── __init__.py
    ├── bar.py
    └── foo.py

app.py がエントリポイントで、plugins は以下のような感じのファイル。
foo.py

# -*- coding: utf-8 -*-
def say(s):

    return 'hello {0}'.format(s)

bar.py

# -*- coding: utf-8 -*-
class Bar(object):
    def __init__(self):
        print('init')
    
    def say(self, name):
        return 'say {0}'.format(name)

foo.py は関数定義、bar.py はクラスの定義をした。


app.py はこんな感じ。

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
from functools import partial
from pluginbase import PluginBase

here = os.path.dirname(os.path.abspath(__file__))
get_path = partial(os.path.join, here)


if __name__ == '__main__':
    plugin_base = PluginBase(package='plugins')
    plugin_source = plugin_base.make_plugin_source(searchpath=[
        get_path('plugins')
    ])

    my_plugin = plugin_source.load_plugin('foo')
    ret = my_plugin.say('hoo')
    print(ret)
    
    my_plugin_class = plugin_source.load_plugin('bar')
    ret = my_plugin_class.Bar().say('fooo')
    print(ret)

Plugin となるディレクトリのパスとディレクトリ名を設定して、ファイル名を呼び出すとモジュールがロードされる。
関数の場合はそのまま、関数オブジェクトが呼べる。
クラスの場合は、クラス名が呼べるので、インスタンスを生成すれば行ける。


実行結果

$ python app.py
hello hoo
init
say fooo


お手軽!!

*1:たとえば、HTTP の method のリライトを行う奴とか

*2:たまたま自分の例がそうなだけで、関数も行ける