Posted on

Python Bottleのソースを読む プラグイン編

Pythonの軽量WebフレームワークBottleのソースを読む プラグイン編
Bottleを触ってみると通常のWebフレームワークには用意されているであろう機能がなかったりします。これはマイクロフレームワークであるが故であり、すべてがそろってない状態がむしろ正しい状態と言えます。Bottleではそういったものを補うためにプラグインが用意されていてある程度の機能はそちらでまかなうことができます。また、Plugin Development Guide を参考にしてプラグインを自作することも可能です。

Class Hierarchy

plugin用クラスはなくインターフェースが定義されているのでそれにしたがって実装します。

プラグインの使い方

公式ドキュメントに簡単な使い方が乗っているのでこちらを参考にすれば簡単に導入することができます。
以下がサンプルコードで、簡単な流れとしては
install()で任意のプラグインをインストールする
リクエスト時にプラグイン実行
となります。サンプルコードでは、プラグインでkwargsにdbをセットされている為ルートのアクションでdb変数が利用できるようになってます。

from bottle import route, install, template
from bottle_sqlite import SQLitePlugin
install(SQLitePlugin(dbfile='/tmp/test.db'))
@route('/show/<post_id:int>')
def show(db, post_id):
    c = db.execute('SELECT title, content FROM posts WHERE id = ?', (post_id,))
    row = c.fetchone()
    return template('show_post', title=row['title'], text=row['content'])
@route('/contact')
def contact_page():
    ''' This callback does not need a db connection. Because the 'db'
        keyword argument is missing, the sqlite plugin ignores this callback
        completely. '''
    return template('contact')

プラグインの作り方

プラグインの作り方も公式ドキュメントにもっともシンプルな形のサンプルがあります。これは実行速度をレスポンスヘッダーにつけて返す処理ですが、サーバ起動時にstopwatchをインストールし、リクエストが来た際にデコレータを実行(表現あってるか分からない)することでリクエストの処理時間は計測できるようになっています。

from bottle import response, install, route
import time
def stopwatch(callback):
    def wrapper(*args, **kwargs):
        start = time.time()
        body = callback(*args, **kwargs)
        end = time.time()
        response.headers['X-Exec-Time'] = str(end - start)
        return body
    return wrapper
install(stopwatch)
@route('/')
def index():
    return 'INDEX'

プラグインのinstall()処理をチェック

プラグインはインターフェースが定義されているのでそれに従って書く必要がありますが、setup()で事前準備をして、apply()で実際にプラグインの処理を実行するといった流れになります。実際にはstopwatch()のように関数だけを実装して渡すことも可能です。

def install(self, plugin):
    """ Add a plugin to the list of plugins and prepare it for being
        applied to all routes of this application. A plugin may be a simple
        decorator or an object that implements the :class:`Plugin` API.
    """
    if hasattr(plugin, 'setup'): plugin.setup(self)
    if not callable(plugin) and not hasattr(plugin, 'apply'):
        raise TypeError("Plugins must be callable or implement .apply()")
    self.plugins.append(plugin)
    self.reset()
    return plugin

プラグイン組み合わせて使うことでスムーズにBottleを使ったWeb開発が進められるようになります。ここまでで主要機能は一通りみることができたので、次回はテストをチェックしてみます。