2014年7月14日月曜日

bottleのテンプレートで共通ヘッダ、フッタ

前回はbottleのテンプレート機能をご紹介しました。今回はその延長です。

各ページで共通のヘッダ、フッタを使いたいニーズってありますよね。過去、自分はphpでヘッダ、フッタ出力用の関数を作って、各HTMLからそれを呼ぶ、というように実装していました。

それが正しいのかは分かりませんが、一つの例として、今回も基本は同じやり方で、実装してみたいと思います。ぶっちゃけ、php部分を、bottleテンプレートのコード埋め込み機能に変えるだけですね。


準備


ファイルフォルダ構成は以下の通りです。

kitabat@clientX% pwd
/home/kitabat/dev001/work
kitabat@clientX% tree .
.
├── headfoot.py
├── hello.py
├── static
│   └── css
│       └── default.css
└── views
    └── top.tpl

3 directories, 4 files


各ファイルの内容は以下の通りです。

hello.py


メイン処理です。今までと変更点はありません。(余計なコードは除きました)

from bottle import route, run, static_file, view

@route('/<hoge>')
@view('top')
def hello(hoge="root"):
    return dict(message = "hello, " + hoge)

@route('/file/<filename:path>')
def static(filename):
    return static_file(filename, root="/home/kitabat/dev001/work/static")

run(host='0.0.0.0', port=8080, debug=True, reloader=True)

headfoot.py


ヘッダ、フッタ出力用の関数を定義したファイルです。
header()関数で<head>タグの内容を共通化します。ページタイトルは引数で受け取ります。また、この中でcssファイルを指定しています。
footer()関数は、引数を与えると、それを表示するようにしています。

それとポイントとしては、ヒアドキュメントを使っています。pythonでは、' か " を3つ重ねる事で、ヒアドキュメントになります。

def header(title='title'):
        return '''
<head><meta http-equiv="Content-Style-Type" content="text/css">
<link rel="stylesheet" href="/file/css/default.css" type="text/css" />
<title>%s</title></head>
''' % title

def footer(msg=''):
        return '''
<hr><div align="right"><font class="s2">%s</font></div>
''' % msg


static/default.css


共通ヘッダ、フッタを使うならスタイルシートと組み合わせたいですよね。という事で、用意しておきました。背景色を変えるだけですが…。

BODY {
  background-color: green
}

top.tpl


テンプレート定義です。2〜4行目に埋め込んだコードのとおり、このファイルでは、上で定義したheadfoot.py内の関数を呼び出し、結果を格納した変数を、HTMLの表示したい部分に書いています。
注意点として、変数指定の際に {{!f}} というように、頭に!を付けてください。これをしないと、タグがそのまま表示されてしまいます。


<html>
% from headfoot import header, footer
% h = header()
% f = footer("bye,bye!")
{{!h}}
<body>
{{message}}
{{!f}}
</body>
</html>


最後に、アクセス結果を。こんな感じです。




はい、如何でしたか?
これでテンプレートファイルが複数存在する場合でも、中でheader()、footer()関数を呼ぶようにしておけば良いですね。


2014年7月8日火曜日

bottleのテンプレート機能を使う

人間、見た目が9割と言いますが…。

今回は見た目、てことでbottleのテンプレート機能を使ってみたいと思います。
ざっくり言うと、変数やコードを埋め込んだ、画面のひな形です。

早速使ってみる


まずは使ってみましょう。テンプレートファイルはbottleを実行したディレクトリ以下のviewsに置き、拡張子は*.tplとします。(static等は気にしないで下さい。前回利用したものです。)

(dev001)kitabat@clientX% pwd
/home/kitabat/dev001
(dev001)kitabat@clientX% tree work
work
├── hello.py
├── static
│   ├── css
│   │   └── default.css
│   └── img
│       └── city.jpg
└── views
    └── top.tpl

4 directories, 4 files

今回はtop.tplとしました。内容は以下の通りです。

<html>
<head><title>hoge</title></head>
<body>
message:{{message}}
</body>
</html>

4行目の{{message}}部分が変数です。ここに色々な値を与える事が出来ます。
ではいつものhello.pyに、こんなコードを書いてみましょう。

hello.py
from bottle import route, run, static_file, view
import datetime

@route('/')
@route('/<hoge>')
@view('top')
def hello(hoge="root"):
    m = "today is %s" % str( datetime.date.today().isoformat() )
    return dict(message = m)

@route('/file/<filename:path>')
def static(filename):
    return static_file(filename, root="/home/kitabat/dev001/work/static")

run(host='0.0.0.0', port=8080, debug=True, reloader=True)

前回からの変更点のみ、記載します。
まず1行目、bottleのview機能を利用する定義を追記しています。
次のimport datetimeは日時関連の処理をする為の標準ライブラリです。
ポイントは6行目。これもデコレータですね。先ほど作成したテンプレートファイルの、拡張子を除いた名前を与えます。これで、以下のhello()関数ではtop.tplテンプレートを使う、という意味になります。
8行目は今日の日付を取得しているだけです。
9行目、ここもポイントです。こんな感じで辞書型の変数をreturnする事で、テンプレートに値が渡ります。

先ほど、テンプレートファイルに書いた変数を思い出してください。{{message}}でしたね。{{ }}にはreturnで返した辞書型変数のキーを書きます。そこがバリューに置換えられます。
このようにして、今回は1つでしたが、複数のキーとバリューをテンプレートに渡す事が出来るという訳です。

アクセス結果は以下の通り。


テンプレートにコードを埋め込む


ぶっちゃけ、今回くらいの処理であれば、わざわざ値を渡さなくても、テンプレート内にpythonコードを埋め込む事で、実現出来てしまいます。
では、コードを埋め込む方法をご紹介します。

<%
        import datetime
        m = "today is %s" % str( datetime.date.today().isoformat() )
%>

<html>
<head><title>hoge</title></head>
<body>
message : {{m}}
</body>
</html>


はい、この通り、<% と %> で囲った部分にコードを書けばOKです。
あるいはコードが1行だけなのであれば、行の頭に % を付けても良いです。
テンプレート内で定義した変数であっても、HTML内で参照する場合は {{ }} で囲みます。
ではサイトにアクセスしてみて下さい。テンプレートを変更しただけですが、先程と同じ結果が得られると思います。


もう少し複雑なコード


テンプレートにコードを埋め込む際の注意点ですが、インデントは無視されます(可読性のためにインデントするのは自由です)。
ではif文など、区切りはどう表現するかというと、end というキーワードを明示的に書きます。
例えばこんな、、、。

<%
        import datetime

        hour = datetime.datetime.now().hour
        if hour > 18 or hour < 5:
                m = "good evening."
        elif hour >= 5 and hour < 10:
                m = "good morning."
        else:
                m = "hello."
        end     
%>

<html>
<head><title>hoge</title></head>
<body>
message : {{m}}
</body>
</html>

if文だけでなく、for文や関数定義等、pythonでインデントして区切りを表す部分には、終わりに必ずendを書きます。

これもhello.py側の変更は不要です(無駄な処理は残ってますが)。アクセスしてみて下さい。適切な挨拶が、なされましたか?



はい、早く寝ましょうね。では今回はこの辺で。

2014年7月4日金曜日

bottle環境で静的ファイルを扱う

bottleでは、静的ファイルへのルーティングをする為に、コードを書いてあげないといけません。静的ファイルというのは、例えばスタイルシートや画像ファイルなんかですね。

前提ディレクトリ構造


今回は以下ディレクトリ構造を想定します。

(dev001)kitabat@clientX% tree work
work
├── hello.py
└── static
    ├── css
    │   └── default.css
    └── img
        └── city.jpg

3 directories, 3 files

hello.pyを実行するディレクトリ(/home/kitabat/dev001/work)が、'/' でアクセスされた際のディレクトリになります。
この状態で、http://192.168.56.103:8080/static/img/city.jpg にアクセスしても、、、エラーになるのです。もちろん、前述の通り、静的ファイルへのルーティングが無いからですね。



静的ファイルへのルーティングコード追加


こんな感じです。
from bottle import route, run, static_file

@route('/')
@route('/<hoge>')
def hello(hoge="root"):
    return "Hello World!<br> path is " + hoge

@route('/file/<filename:path>')
def static(filename):
    return static_file(filename, root="/home/kitabat/dev001/work/static")

run(host='0.0.0.0', port=8080, debug=True, reloader=True)

まず1行目。static_fileというbottleの機能を使うので読み込みます。3〜6行目は今までに書いた部分。8〜10行目が今回追記したルーティング用コードです。
8行目の file/<filename:path> について。< >は前回説明した通り、アクセスされたURLを以後の関数内で使うための書き方です。:pathはfilenameに、パスを含んで良い事を意味します。単純に<filename>だけだった場合、/file/img/city.jpg へのアクセスは上手く行かず404になります。filenameに格納される部分が "img/city.jpg" と、パスを含むからですね。
10行目でstatic_file()関数を使います。第1引数が静的ファイル名、第2引数が、root=サーバのローカルパスです。

では、http://192.168.56.103:8080/file/img/city.jpg にアクセスしてみると、、、



やった〜!

注意すべき点として、アクセスするURLと、サーバ側のローカルパスは一致させる必要が無いので、混乱しないように、くらいですかね。今回はあえて、アクセスURL側を "file"、実際のディレクトリ側を "staic" としておりました。


ではまた。

2014年7月1日火曜日

軽量pythonフレームワーク「bottle」の導入

開発するにあたり、やはりフレームワークは有った方が良いですよね。
openstackなんかはDjangoを使っているようですが、なにぶん初心者なので、、、挫折しないシンプルなものは無いかと探していたところ、ありました!bottle!

なんと驚愕の1ファイル、146kbyte!
でもこれで、立派なフレームワークです。

では恒例のhello worldまでを見ていきましょう。

bottleインストール

公式webはこちら
wgetでbottle.pyを持ってきてカレントディレクトリに置くのでも良いのですが、せっかくpipを入れた事ですし、pipで入れましょう。

(dev001)kitabat@clientX% pip install bottle
(dev001)kitabat@clientX% pip list
bottle (0.12.7)
pip (1.5.6)
setuptools (3.6)
はい、終わりです。これで from bottle import すればbottleが使えます。
pip listを打つとbottleが入っている事を確認出来ます。今回は 0.12.7 というバージョンが入りました。


hello, world!!


まず、virtualenvで作ったディレクトリのルートにはbin、libなどがあるので、workというディレクトリを作ってそこで作業を進めます。

(dev001)kitabat@clientX% pwd
/home/kitabat/dev001
(dev001)kitabat@clientX% mkdir work
(dev001)kitabat@clientX% cd work
(dev001)kitabat@clientX% vim hello.py 

では公式webにもある例文を見てみます。以下をhello.pyとして作成しましょう。

from bottle import route, run

@route('/hello')
def hello():
    return "Hello, World!!"

run(host='0.0.0.0', port=8080, debug=True)


1行目はbottleの利用宣言です。bottleという機能群から、routeとrunという機能を使いますよ、の意味です。
説明順が前後しますが、7行目がmain処理です。bottleは自身がwebサーバとして動作し、この例だと http://<サーバIP>:8080/ でwebアクセスを待ち受けます(0.0.0.0とする事でサーバの全NICでListenします)。
debug=Trueで、ターミナルにデバッグメッセージが出ます。
ここまではbottleを利用する場合は毎回ほぼ同じです。

アプリケーションの動作を決定するのは3〜5行目です。
/helloにアクセスがあれば(正確には、http://<サーバIP>:8080/hello にアクセスがあれば)、hello()以下が呼び出されます。
def xxx()はpythonの関数定義、@xxx()はデコレータと呼ばれる仕組みです。この辺りは後ほど書ければ書きます。

では実行してみましょう!
っとその前にこのサーバのIPを確認しておきます。192.168.56.103です(virtual boxなのがバレバレですね)。
(dev001)kitabat@clientX% python hello.py 
(dev001)kitabat@clientX% ip addr show | grep inet
    inet 127.0.0.1/8 scope host lo
    inet 192.168.56.103/24 brd 192.168.56.255 scope global eth0

おもむろにpython hello.pyとタイプします。
(dev001)kitabat@clientX% python hello.py 
Bottle v0.12.7 server starting up (using WSGIRefServer())...
Listening on http://0.0.0.0:8080/
Hit Ctrl-C to quit.


はい、ターミナル、何も返ってこなくなりましたね。これでOKです。
先ほど確認したip:8080/helloにブラウザでアクセスしてみましょう!

おお〜!皆さん、出ましたか?
ちなみにターミナルを見ると、アクセスログが表示されております。
(dev001)kitabat@clientX% python hello.py 
Bottle v0.12.7 server starting up (using WSGIRefServer())...
Listening on http://0.0.0.0:8080/
Hit Ctrl-C to quit.

192.168.56.1 - - [26/Jun/2014 17:56:56] "GET /hello HTTP/1.1" 200 12


もうちょっと使ってみる


例えば '/' (http://192.168.56.103:8080/)にアクセスすると、当然、定義が無いので怒られます。


以下のコードを追記すると、問題なくアクセス出来るようになります。

from bottle import route, run

@route('/')
@route('/hello')
def hello():
    return "Hello World!"

run(host='0.0.0.0', port=8080, debug=True, reloader=True)

3行目に @route('/') を加えています。こんな感じでデコレータは関数に対して幾つも記載可能です。それと、直接は関係無いのですがrun()に渡す引数として reloader=True を加えています。これはbottleが、コードの編集を認識し再読み込みしてくれるオプションです。付けない場合は、コードの反映にbottleの停止起動が必要になります。



はい、無事アクセス出来ました。

ちなみに以下のようにする事でURLのパスを取得出来ます。

from bottle import route, run

@route('/')
@route('/<hoge>')
def hello(hoge="root"):
    return "Hello World!<br> path is " + hoge

run(host='0.0.0.0', port=8080, debug=True, reloader=True)

4行目で、アクセスされたurlをhogeという変数で受け取り、それをhello()関数の引数として使っています。5行目で、hogeのデフォルト値を"root"にしていますが、これは '/' へのアクセス時に使われます。
6行目で、hoge変数も表示するよう設定しています。

/helloへアクセスした場合



行けますね。じゃあ次。

ルート('/')にアクセスした場合


問題無しです!デフォルト値の"root" が適用されている事が確認出来ます。


補足:注意点

reloader=Trueが悪いのか分からないのですが、コードを編集し、bottleがリロードされてから暫く、アクセス出来るはずのURLに、何故かアクセス出来なくなる事があります。その時は根気よくブラウザのリロードをすれば良いのですが、酷い場合は1分程、アクセス出来ません。



ひとたびアクセス出来れば、以後はスムーズなのですが。。。
この問題の困るところは、修正したコードに何か誤りが有ったから表示出来なくなったのだ、と勘違いする事です。
この思考に落ち入ると、せっかく書いたコードを過去に戻していき、しかし以前上手く行った筈のコードですらアクセス出来なくて、更に以前に戻し…、という非常に悲惨な循環にハマります。
pythonの構文でエラーが有るならば必ずターミナルに出力されますので、それが出ていないのにアクセス出来なかったら、この問題を疑って、ブラウザリロードを何度か、試みて下さい。

------

はい、という事で、如何でしたでしょうか。結構簡単に色々面白い事が出来そうですよね!
次回以降、アプリケーションを作る過程で、これはどうやるんだろう?に直面した事を、順にご紹介していきます。