pastescript離れを考える

From Evernote:

pastescript離れを考える

Pyramidも使ってるPasteScriptだけど、Pasteという結構大きめなライブラリに依存している。
そして、Pasteライブラリって大部分は既に使われていないんだよね。
ということで、WSGIに限らず細かなタスクをまとめるものとして、paverをもうちょっと使うことを考えた。
fabricでもいいかもしんない。

paverのタスクは、カレントディレクトリにあるpavement.py に定義します。
task デコレータで登録。

と、これが前回調べたときのおさらい。

今回はpaverからWSGIアプリケーションを実行させてみる。

WSGIアプリケーション実行

PasteScriptは内部でPasteDeployを使ってWSGIアプリをロードしている。
このとき、PasteDeployは指定された設定ファイルの内容を、app_factoryに渡す。

ということなので、WSGIアプリケーションをpkg_resourcesを使ってegg経由で呼び出す方法と、dotted_nameで呼び出す方法と二種類考える。

まずは、いきなり WSGIアプリケーションを取得する方法。
とりあえず、wsgiref.simple_server.demoapp を例にします。

文字列で渡されたパッケージ名からインポートするには、 __import__ 関数を使います。
また、インポート済のパッケージは、 sys.modules から取得できます。
オブジェクトは、getattr で取り出せばよいです。

from paver.easy import *

options(
    run_server={
        'port': '8080',
    },
)

@task
@cmdopts([
    ('appname=', 'a', 'wsgi application'),
    ('port=', 'p', 'port of httpd'),
])
def run_server(options, info):
    """ run wsgi application """
    dotted_name = options.appname
    port = int(options.port)
    mod, obj = dotted_name.split(":")
    __import__(mod)
    app = getattr(sys.modules[mod], obj)

    from wsgiref.simple_server import make_server
    httpd = make_server('', port, app)
    info('run wsgi application %s on port %d' % (dotted_name, port))
    httpd.serve_forever()

というところ。

しかし、これでは設定を切り替えたりできないので、 paste.app_factory エントリポイントを pkg_resources から取得して設定を渡したいところ。
appnameが、"egg:" で始まる場合は、pkg_resourcesで取得するようにします。
またコンフィグレーションファイルの内容をapp_factoryに渡してwsgiアプリを作成するようにします。
コンフィグレーションファイルのパーサーとして、configobjを利用することにします。

from paver.easy import *
import pkg_resources
import configobj

options(
    run_server={
        'port': '8080',
    },
)

@task
@cmdopts([
    ('appname=', 'a', 'wsgi application'),
    ('port=', 'p', 'port of httpd'),
    ('config=', 'c', 'configuration file'),
])
def run_server(options, info):
    """ run wsgi application """
    dotted_name = options.appname
    port = int(options.port)
    if dotted_name.startswith('egg:'):
        egg_name = dotted_name[len('egg:'):]
        distname, entryname = egg_name.split(':', 1)
        app_factory = pkg_resources.load_entry_point(distname, 'paste.app_factory', entryname)
        config = configobj.ConfigObj(options.config)
        app = app_factory(config.get('global', {}), **config[egg_name].dict())
    else:
        mod, obj = dotted_name.split(":")
        __import__(mod)
        app = getattr(sys.modules[mod], obj)

    from wsgiref.simple_server import make_server
    httpd = make_server('', port, app)
    info('run wsgi application %s on port %d' % (dotted_name, port))
    httpd.serve_forever()

PasteScript, PasteDeployの組み合わせだと、さらにWSGIミドルウェアの構成を変えられますが、そこまではさすがに手間なのでやらないことにします。