続・玩式草子 ―戯れせんとや生まれけん―

第7回ウィスキー・ボトル・ブルース(1番)

突然ですが、皆さんは"Bottle"というPython用のWebフレームワークを知っていますか?

"Webフレームワーク"というと、大規模なWebサービスを構築するためのツールで、自分には関係ない、と思われる方も多いかも知れません。しかしながら、趣味でWebサイトを作っている場合でも、写真類は日付やキーワードを元に動的に呼び出したり、家計簿管理用にバックエンドでデータベースを使いたくなることはよくあるでしょう。

Bottleはそのような際に便利なWebフレームワークで、Pythonのモジュールとして書かれた1つのファイルをimportするだけで、PythonスクリプトがWebアプリになる便利なツールです。

筆者自身、まだ半年くらいしか使っていない初心者なものの、2,3のWebアプリを書いてみてその便利さに感心したので、同じような興味を持っている人向けに、今回からしばらく、この"Bottle"というWebフレームワークを紹介してみようと思います。

Bottleの実行例

"Bottle"はpip等を使ってインストールすることもできるものの、bottle.pyという1つのファイルだけで構成されているので、使いたいディレクトリに配置するだけで使えます。

たとえば、"Bottle-test"というディレクトリでテスト用のコードを書く場合、そのディレクトリにbottle.pyというファイルをダウンロードするだけでインストールは完了です。

$ mkdir Bottle-test ; cd Bottle-test
$ wget https://bottlepy.org/bottle.py
$ ls -l
合計 172,032
-rw-r--r-- 1 kojima users 170,346  2月 22日  21:00 bottle.py

次にこのディレクトリに次のようなPythonスクリプトを用意します。ファイル名は"hello.py"としておきましょう。

 1     #!/usr/bin/python
 2     # -*- coding: utf-8 -*-
 3     
 4     from bottle import route, run
 5     
 6     @route("/")
 7     def hello():
 8         return("Hello World")
 9     
10     run(host='localhost', port=8080, reloader=True, debug=True)

このスクリプトを起動すると、importされたbottleの機能で、8080番のポートを見張るWebサーバとして動作します。

$ python hello.py
Bottle v0.13-dev server starting up (using WSGIRefServer())...
Listening on http://localhost:8080/
Hit Ctrl-C to quit.

この状態でfirefox等のブラウザから "http://localhost:8080/" を開くと、"Hello World"というメッセージが表示されます。

図1 Hello Worldの例
図1 Hello Worldの例

この画面だけではショボく見えるかも知れませんが、httpdサーバを立ちあげなくても、HTMLなページを書かなくても、bottle.pyをimportするだけでWebサービスを提供できるというのは凄いことだと思いませんか?

HTMLで簡単なWebアプリを作ろうとすると、入力用のページ⁠、出力用のページ⁠、編集用のページ等々、必要な機能ごとのHTMLファイルを用意して、どのようにデータを受け渡すかを考慮しつつ、各ページをリンクでつないでやる必要がありました。

一方、Bottleを使えば、それらの機能は全てPythonの関数として書けるので、1つのスクリプトだけでWebアプリを完結することができます。

筆者自身、⁠Webフレームワーク」というとRailsやStrutsを連想し、⁠大規模なWebアプリを作るためのもので、高機能ではあるけれど設定や準備が大変そう⁠⁠、という先入観を持っていたので、Bottleのこの単純さには目から鱗が落ちる思いでした。

ルーティング機能

新しいURLを追加するために、前節のhello.pyに3行ほど書き加えてみます。追加したのは10から13行目です。

 1     #!/usr/bin/python
 2     # -*- coding: utf-8 -*-
 3     
 4     from bottle import route, run
 5     
 6     @route("/")
 7     def hello():
 8         return("Hello World")
 9
10     @route("/hello/<name>")
11     def greet(name):
12         return("Hello {}".format(name))
13
14     run(host='localhost', port=8080, reloader=True, debug=True)

ちなみに14行目のrunコマンドがBottleを起動する命令で、引数に"reloader=True"を指定しておけば、スクリプトを修正すると自動的に再読み込みしてくれるので便利です。また、"debug=True"はデバッグメッセージを出力するための指定です。

この状態で "http://localhost:8080/hello/kojima" を開くと、"Hello kojima"というメッセージが出力されます。

図2 URLに引数を取る例
図2 URLに引数を取る例

これらの例が示すように、Bottleでは@route(...)という指定でURLとPythonの関数を結びつけ、"http://localhost:8080/"では7行目の"def hello():"が、"http://localhost:8080/hello/kojima" では11行目の"def greet(name):" が実行されます。

このようにURLと関数を結びつける指定をルーティング(routing)と呼んでいます。ルーティングには完全なURLを指定することも、10行目の"/hello/<name>"のように可変部分を含めることもできます。この"<...>"で囲んだ部分には"/hello/kojima"や"/hello/Tanaka"のような任意の文字列が対応し、ここに指定した文字列(name)が11行目の"def greet(name)"の引数としてPythonスクリプトに取り込まれます。

テンプレート機能

今まで紹介してきたように、Bottleでは、"@route(..)"で修飾した各関数が"return(...)"で返す内容がそのURLの"コンテンツ"としてブラウザ側に送られます。

すなわち、return()で返す文字列が、HTMLファイルになるように書けばいいわけですが、長い文字列をいちいち生成するのは面倒です。

そのためBottleには、あらかじめ作成しておいたファイルを読み込んで、指定された部分のみを更新して出力するテンプレート機能が用意されています。

テンプレート用のファイルは".tpl"という拡張子を使うことになっているので、次のようなファイルを"hello.tpl"という名前で同じディレクトリに用意しておきます。

 1     <!DOCTYPE html>
 2     <html lang="ja">
 3     <head>
 4     <title>Hello {{name}}</title>
 5     </head>
 6     <body>
 7     
 8     <h1> {{mes}} {{name}} さん</h1>
 9     
10     </p>
11     </body>
12     </html>

二重の中括弧"{{...}}"はBottle固有の記法で、この部分が実行時に展開される変数であることを示します。

このテンプレートを使うためにhello.pyの方も改修します。まず、template機能を有効にするために、Bottleの各機能をimportしている4行目に"template"を追加します。そして、17行目のreturn()の中に、⁠⁠nameとmesを引数にして、hello.tplというテンプレートを呼び出す」という指示を追加しました。

加えて、名前を表示するだけだとあまりに単純すぎるので、時間帯によってメッセージを変えてみようと、Pythonのdatetimeモジュールを使って現在時刻を調べ、時間に応じたメッセージを用意することにしました。

 1     #!/usr/bin/python
 2     # -*- coding: utf-8 -*-
 3     
 4     from bottle import route, run, template
 5     from datetime import datetime
 6     
 7     @route("/hello/<name>")
 8     def greet(name):
 9         now_dt = datetime.now()
10         if now_dt.hour < 12:
11             mes = "おはようございます"
12         elif now_dt.hour < 18:
13             mes = "こんにちは"
14         else:
15             mes = "こんばんわ"
16     
17         return(template('hello', name=name, mes=mes))
18     
19     run(host='localhost', port=8080, reloader=True, debug=True)

<name>の指定にはマルチバイト文字を使うことも可能なので、"http://localhost:8080/hello/こじま" を開いてやると、ちゃんと日本語で返事を返します。

図3 テンプレート機能の例
図3 テンプレート機能の例

念のためにソースを開くと、テンプレートファイルの{{name}}と{{mes}}の部分が、引数として指定した文字列にきちんと変換されていました。

Bottleではテンプレートファイルの中でもPythonの機能が使え、行頭に"%"を付けた行は、Pythonのスクリプトとして実行されます。そこで、hello.tplを以下のように改造してみます。

7行目でdatetimeモジュールをインポート、8行目でそれを使ってnowに現在時刻を入れておき、{{name}}や{{mes}}と同じように、日付や時刻表示したい所に{{now.year}}や{{now.hour}}として用意しておきます。

 1     <!DOCTYPE html>
 2     <html lang="ja">
 3     <head>
 4     <title>Hello {{name}}</title>
 5     </head>
 6     <body>
 7     % from datetime import datetime
 8     % now = datetime.now()
 9     
10     <h1> {{mes}} {{name}} さん</h1>
11     
12     今日は {{now.year}}/{{now.month}}/{{now.day}} です。
13     <p>
14     現在時刻は {{now.hour}}:{{now.minute}}:{{now.second}} です。
15     
16     </p>
17     </body>
18     </html>

このように変更した上で、"http://localhost:8080/hello/こじま" をリロードしてみると、ちゃんと現在時刻も表示されるようになりました。

図4 テンプレートファイル中でPythonの機能を使った例
図4 テンプレートファイル中でPythonの機能を使った例

以上、Bottleの基本機能を簡単に紹介してみましたが、これだけでも結構いろんなことができそうな気がしませんか?

次回以降、Bottleの機能を紹介しつつ、簡単なWebアプリを作ってみようと考えていますので、乞御期待。

おすすめ記事

記事・ニュース一覧