taisablog

taisa's engineer blog

Python

Python Bottleのソースを読む 起動編

投稿日:December 1, 2016 更新日:


Pythonの軽量WebフレームワークBottleのソースを読む 起動編

Bottleとは

Bottle is a fast, simple and lightweight WSGI micro web-framework for Python. It is distributed as a single file module and has no dependencies other than the Python Standard Library.

Pythonの軽量Webフレームワークで、特徴はシンプルで早く、Pythonの標準ライブラリにも依存していないWebフレームワークであることとフレームワーク本体が1ファイルで構成されていることである

Class Hierarchy

20160204171644
20160204171658

Doxygenを使って出力した図


Bottleは1ファイルながら中でそれぞれのクラス、主にServer、Templateが継承関係にあるのがわかる(コード量は4000行位)。ServerやTemplateクラスはたくさんあるが実際はその中のどれかを選択して利用する形となる

起動

Bottleの起動はrun()を呼び出す方法とコマンドラインインターフェースを使う方法が用意されている

run()を使う方法

以下のように記載し起動することでサーバが立ち上がる

from bottle import run, route
@route('/')
def index():
    return 'Hello World'
run(host='localhost', port=8000, debug=True)

コマンドラインインターフェースを使う場合

以下のコマンドで起動可能

# コントローラを指定
python -m bottle 'package.controller'
# 説明は省略するが明示的にアプリを指定することも可能
python -m bottle 'package.controller:app'

controller.py

# runは不要
from bottle import ,route
@route('/')
def index():
    return 'Hello World'

起動処理を確認する

コマンドラインインターフェースを使う場合

mainが2箇所あるが、これはサーバアダプダに必要なライブラリを必要としているからで、1つ目のmainでまずサーバアダプタに必要なライブラリを読み込み2つ目のmainでサーバが起動される仕組みになっている

run()でサーバを起動する

run()では、渡された引数の値をそれぞれ読み込んだあと最後にServerAdapterのrun()を呼び出している。Bottleでは多くのサーバをServerAdapterを継承することでサポートしており、指定されたサーバを起動するようになっている。指定しない場合はwsgirefがデフォルトで呼ばれる。また、appパラメータは特別指定しなければ、内部で自動的にdefaultが使われるので特別指定する必要はない。

サーバをロードするまで処理を追ってみる

まず、Bottleでサポートしているサーバは以下のように宣言してある。

server_names = {
    'cgi': CGIServer,
    'flup': FlupFCGIServer,
    'wsgiref': WSGIRefServer,
    'waitress': WaitressServer,
    'cherrypy': CherryPyServer,
    'paste': PasteServer,
    'fapws3': FapwsServer,
    'tornado': TornadoServer,
    'gae': AppEngineServer,
    'twisted': TwistedServer,
    'diesel': DieselServer,
    'meinheld': MeinheldServer,
    'gunicorn': GunicornServer,
    'eventlet': EventletServer,
    'gevent': GeventServer,
    'geventSocketIO': GeventSocketIOServer,
    'rocket': RocketServer,
    'bjoern': BjoernServer,
    'aiohttp': AiohttpServer,
    'auto': AutoServer,
}

サーバを指定するには、run()実行時にserverとして必要な値を文字列で指定して渡す。以下に記載の通りデフォルトでは’wsgiref’が指定されている。

def run(app=None,
        server='wsgiref',
        host='127.0.0.1',
        port=8080,
        interval=1,
        reloader=False,
        quiet=False,
        plugins=None,
        debug=None,
        config=None, **kargs):

このあたりでサーバを取得し設定しておりServerAdapterを継承していない値が渡された場合はサポート外としてExceptionを投げる

if server in server_names:
    server = server_names.get(server)
if isinstance(server, basestring):
    server = load(server)
if isinstance(server, type):
    server = server(host=host, port=port, **kargs)
if not isinstance(server, ServerAdapter):
    raise ValueError("Unknown or unsupported server: %r" % server)

WSGIRefServerなどは標準エラーを出力する設定になっている

server.quiet = server.quiet or quiet
if not server.quiet:
    _stderr("Bottle v%s server starting up (using %s)...\n" %
            (__version__, repr(server)))
    _stderr("Listening on http://%s:%d/\n" %
            (server.host, server.port))
    _stderr("Hit Ctrl-C to quit.\n\n")

reloaderをTrueにした場合はFileCheckerThread(ファイルの変更をチェックして変更があったら自動でリロードする)を立ち上げて起動する

if reloader:
    lockfile = os.environ.get('BOTTLE_LOCKFILE')
    bgcheck = FileCheckerThread(lockfile, interval)
    with bgcheck:
        server.run(app)
    if bgcheck.status == 'reload':
        sys.exit(3)
else:
    server.run(app)

簡単に起動までの流れをおってみたが、細かく見てみると他にも起動オプションがあるので必要に応じて設定するとよい
ざっと見てみたところBottleは1ファイルで構成されているので中身を確認しやすくPython初級者の私にとってはとっつきやすいフレームワークに思う。起動自体も1ファイルだけでできるので開発も簡単に始められる。ただその分逆にどのようにアプリケーションとしてファイルを構成していけばいいかが分かりにくいというデメリットもあるのではと感じた。最終的には自分なりに構成していけばいいのだがその辺はまた追って確認していこうと思う。

-Python
-,

執筆者:

関連記事

Python Bottleのソースを読む テンプレート編

前回に続きPythonの軽量WebフレームワークBottleのソースを読む テンプレート編 Class Hierarchy ここの部分 Bottleのテンプレートについて Bottleのテンプレートは、Simple、Cheetah、Jinja2、Makoの4種類があり、BaseTemplateを継承しているPythonのテンプレートの種類(参考)http://www.cmscom.jp/blog/af0ga8 テンプレートの使い方 テンプレートの拡張子には以下が利用できる extensions = [‘tpl’, ‘html’, ‘thtml’, ‘stpl’] 例えばjinja2を使う場合は以下のように呼び出すことができる(jinja2をpip installする必要あり) @route(‘/’) def jinja2(): name = ‘text’ return jinja2_template(‘jinja2’, name=name) view {{ name }} テンプレート呼び出しの処理をみてみる 各テンプレートは以下のように設定されている。 functools.partialにfunc templateが渡されている mako_template = functools.partial(template, template_adapter=MakoTemplate) cheetah_template = functools.partial(template, template_adapter=CheetahTemplate) jinja2_template = functools.partial(template, template_adapter=Jinja2Template) jinja2_templateが呼ばれた時の処理 *argsにはjinja2が、**kwargsにはtemplate_adapter=Jinja2Templateが渡される def template(*args, **kwargs): 引数からjinja2テンプレート名を取得 tpl = args[0] if args else None Jinja2Templateを取得 adapter = kwargs.pop(‘template_adapter’, SimpleTemplate) テンプレートパスをlookup(パスは./views/か./) lookup = kwargs.pop(‘template_lookup’, TEMPLATE_PATH) Jinja2Templateをインスタンス化 TEMPLATES[tplid] = adapter(name=tpl, lookup=lookup, **settings) BaseTemplateのコンストラクタの最後のprepareでJinja2Templateのprepareが呼ばれる self.prepare(**self.settings) ここではじめてjinja2がimportされる from jinja2 import Environment, FunctionLoader テンプレートのrenderメソッドにてレンダリングしviewを返す return TEMPLATES[tplid].render(kwargs) BottleのテンプレートはBaseTemplateを継承しprepareとrenderを実装することで使える仕組みになっている def prepare(self, **options): “”” Run preparations (parsing, caching, …). It should be possible …

Python Bottleのソースを読む ユニットテスト編

Python Bottleのソースを読む テスト編前回までで一通りメインの機能は確認できました。ではいよいよコード追加してプルリクを投げましょうというところですが、テストは?ということでユニットテストとカバレッジ状況をチェックしてみます。また自作アプリに対してのテスト方法もチェックしてみます。 Bottleのユニットテストとカバレッジ状況 GitHub上のBottleをクローンすると直下にtestディレクトリがあります。ここにずらーっとテストがありますが、よくよく見てみるとtestall.pyという全実行のエントリポイントが用意されているのでそれを実行します。 # noseを入れてない場合はインストール pip install nose # こんな感じに実行 nosetests –with-coverage –cover-html testall.py でました。bottle.pyだけを見てみると76%しっかりテストが書かれてるのがわかります。 bottle.py 2277 485 849 79 76% そしてhtmlをチェックコードのカバレッジ状況が一目瞭然。あとは足りないとこテストしたり自分で処理追加してテストしたり。これでいろいろ安心。 BottleでつくったWebアプリのテスト方法 せっかくなのでWebアプリのテスト方法も見てみます。これは公式ドキュメントにしっかり書かれているのでそこをみればだいたい分かると思います。noseインストール pip install nose テスト対象コード import bottle @bottle.route(‘/’) def index(): return ‘Hi!’ if __name__ == ‘__main__’: bottle.run() テストコード単純にindex()を実行して結果をassertする import mywebapp def test_webapp_index(): assert mywebapp.index() == ‘Hi!’ テスト実行 nosetests test_app.py Ran 1 test in 0.014s OK 上記は直接関数をテストする方法ですがwebtestを使ってwebベースでアクセスしてテストする方法もあります。ただこの方法をやってみたところ明示的 app = Bottle()を宣言しないとうまく実行できませんでした。ここら辺はもう少しwebtestの使い方がわかればなんとかなるかもしれません。webtestインストール pip install webtest テスト対象コード from bottle import Bottle # アプリを明示的に作成 app = Bottle() @app.route(‘/’) def index(): return ‘Hi!’ if __name__ == ‘__main__’: run(app=app) テストコード公式ドキュメントではログイン・ログアウトのテストが書かれてましたがここでは省略 from webtest import TestApp import mywebapp def test_index(): app = TestApp(mywebapp.app) assert …

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’] = …

FlaskをBottleと比較した雑感

以前Bottleのソースをチェックしてみた流れでFlaskも見てみた。結論から言うとBottleと大して変わらん。もちろんFlaskのほうがコード量が多く多少リッチではあるもののざっくり機能ベースで言うと大して変わらんのです。そもそも両方ともマイクロWebフレームワークが売りなので当たり前といえば当たり前ですが、歴史的にもBottleが2009年リリースでFlaskが2010年4月1日(エイプリルフールのネタとして)リリースと、名前もBottleに対抗してFlask(フラスコ)という名前をつけたということなので、もともとがBottleっぽいフレームワークを遊びで作ってみたという感じなんだと思います。それっぽいことはwikiに書かれています。 [Bottle Wiki](https://en.wikipedia.org/wiki/Bottle_(web_framework)Flask Wiki クラス構成に関しては以下にある通りで、Bottleが1ファイル構成でFlaskは機能にファイルが分かれてる構成という違いはあるものの、機能自体にほとんど違いはありません。ただFlaskはBlueprintという大規模アプリ対応の機能があります。なお、使い方については公式ドキュメントとサンプルコードも複数用意されているのでアプリの構成や使い方が確認しやすいです。

Python Bottleのソースを読む リクエスト・レスポンス編

Pythonの軽量WebフレームワークBottleのソースを読む リクエスト・レスポンス編   Class Hierarchy ここの部分 リクエスト受付からレスポンスまで 前回、サーバの立ち上げ時にルーティングが読み込まれるところまでを確認したので今回はリクエスト受付からレスポンスを返すまでを見てみる。 まずリクエストが来るとwsgiref/handlers.pyのrunが呼ばれ、Bottleの__call__が呼び出される wsgiref/handlers.py self.setup_environ() # リクエストを処理しレスポンスを取得する self.result = application(self.environ, self.start_response) # レスポンスを返す self.finish_response() bottle.py def __call__(self, environ, start_response): “”” Each instance of :class:’Bottle’ is a WSGI application. “”” return self.wsgi(environ, start_response) リクエストを処理する際に、LocalRequestのbindを呼び出しBaseRequestのコンストラクタにてenvironとLocalRequestをセットする。これでBaseRequestのラッパーからパラメータを取得可能になる。レスポンスも同様にLocalResponseのbindを呼び出す。 try: out = None environ[‘bottle.app’] = self request.bind(environ) response.bind() try: self.trigger_hook(‘before_request’) except HTTPResponse: return _e() # ここでリクエストの主処理を実行する out = _inner_handle() return out; finally: if isinstance(out, HTTPResponse): out.apply(response) self.trigger_hook(‘after_request’) BaseRequestのコンストラクタ self.environ = {} if environ is None else environ self.environ[‘bottle.request’] = self リクエスト情報は以下のように取得可能になる def index(): request.forms.get(‘test’) return ‘TOP’ LocalRequestは以下の通りマルチスレッドに対応している #: A thread-safe instance of :class:`LocalRequest`. If accessed from within a #: request …