PythonでWebアプリ開発入門のススメ Flask/MongoDB


TwitBlogin! http://twitblogin.com/ とか作ったことだし、そこそこ開発環境整って、今なら思いつく限りのサービスはさっくり実装できそうだったのでPython初心者向けに書いておく。
少しでもPythonユーザが増えれば幸い。

対象は Python の基本的な構文程度はわかるけど、具体的に何から手をつけていいかわからない人

目次

細かいライブラリの使い方とかPython本体の言語仕様とかは適当にぐぐってください。
それぞれのセキュリティ等はここでは書いてないので、必要に迫られたらやってくださいね。

Apache/WSGI/MongoDBの環境構築

OS さくらVPS Ubuntu10.04
サーバー Apache+WSGI
データベース MongoDB

WSGIとは、プログラミング言語Pythonのための、Webアプリケーション(もしくは、フレームワーク)とWebサーバ間の簡潔かつ統一されたインタフェースを定義する by wikipedia らしいのでmod-pythonやめてこっちを使いましょう。Python界ではWSGIデファクトです。Googleも使ってるし。

今ならGAEも熱いですが、自由度の点ではroot持ってる鯖の方がはるかに上です。Googleが敷いたレールの上で生きることに飽きたらVPS使いましょう。
データベースにはNoSQLのMongoDBを使います。PythonでMongoDBを使うと大きな恩恵に預かることができます。詳しくはpymongoの項で。
とりあえず最低限やっとくこと(Ubuntuの場合)は、

sudo apt-get install apache2 # たぶん入ってる
sudo sudo apt-get install libapache2-mod-wsgi
sudo a2enmod wsgi #wsgi有効化
sudo easy_install pip # python用の高機能パッケージマネージャです。以下紹介するライブラリは全て pip install ~ で
sudo vi /etc/apache2/sites-available/default # エディタならでもなんでも

/etc/apache2/sites-available/defaultを編集

#virtualhostの中に追記
<Directory /py/ >
     AddHandler wsgi-script .py
</Directory>

ライブラリ管理にpipは欠かせないです。

これで /var/www/py/natotoka.py http://yourdomain.com/py/nantoka.pywsgiアプリケーションとして起動します
実際に公開する際はnantoka.pyは virtualhost直下に WSGIScriptAlias /myapp /var/www/py/myapp.py など


そんでApache再起動

sudo apache2ctl -k restart

デプロイしたWSGIアプリケーションは、本体を直接書き換えたときはそのまま適用されるんですが、依存ライブラリを変更した際はApacheの再起動をしてやらないと変更が適用されません

MongoDB

いくつか導入方法ありますが以下から適当に
mongodb install(インストールメモ) - ubuntu python - 清田コーヒーブログ  kiyotaman@twitter
Ubuntu に MongoDB をインストールする :: marugoshi.org

boost噛んでるのビルドに時間かかります。寝る前にやるのがいいでしょう

オールインワンより自分が理解できるパーツを増やす

とりあえずの方針。
自分が最初触ったのはDjangoだったんですが、とにかく使いにくかった!APIがなにしてるのか分かりづらいしやりたいことやるのに何をいじっていいか……
他にフレームワークとしてPylonsなどもありますが、同じく、仕様の理解で何日、場合によっては何月もかかります。
これはRoRPHPMVCフレームワークでも同じで、できることがフレームワークに依存してしまい、そこから抜けだそうとするとフレームワーク本体のコードへの理解が求められます。


よって、巨大なフレームワークは入門として不適で、フレームワークの構成を想像できるようになるまではコード量が少ない、小さいライブラリを組み合わせるのがいいと自分は考えます。
とはいえ、サーバーをゼロからスクラッチするのも馬鹿馬鹿しいので(それが無駄だともいいませんが)、自分が考える機能実装にどの程度の抽象度が必要か、使いながら見極めましょう。
共通パーツやルーチンが理解できるようになるとそこからフレームワークがみえてきますし、Django等を使うのはそれからでもいいでしょう
というかDjangoから入門すると心折れます <= 自分

ライブラリは?

国内じゃサンプルでRubyに負けることも多いですが海外だと強いので、和訳されてるのは少ないものの、ほしいライブラリは大抵あります
ほとんどは Python Package Index : PyPI http://pypi.python.org/pypi にあるので確認してみましょう。ここにないor最新版はどっかの(作者の)githubにあるはずです。
updated 一覧を眺めてニヤニヤ出来ますね。


で、以下自分が使ったライブラリの解説。

Flask

Welcome | Flask (A Python Microframework)
[用途] URIマッパー/セッション管理 [依存] Werkzeug
フレームワーク批判をしていきなり申し訳ないんですが、Webアプリケーションフレームワークです。が、機能がコンパクトにまとまっており、コードの可読性も高いです。
RubySinatraに影響を受けており、MicroFrameworkを標榜しています。あまりお節介なことはしておらず素直です。

公式のサンプルみてもらうのが早いですね

from flask import Flask
app = Flask(__name__)

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

if __name__ == "__main__":
    app.run()

debugの為に$python app.py でlocalhost:5000で起動。VPSなどのリモートホストならapp.run(host="0.0.0.0") とすれば外から見えます。もちろん、そのまま公開しちゃいけませんが
WSGIアプリケーションなので flask本体の変数名を appではなくapplication として、WSGIScriptAliasで指定すればそのままデプロイできます

Werkzeug

Welcome | Werkzeug (The Python WSGI Utility Library)
[用途]WSGIデバッガ
WSGIのラッパーライブラリ兼デバッガです。Flaskに限らずWSGIアプリケーションならwerkzeugで包んでしまうことができて、バグが起きた箇所を遡って教えてくれる優れものです。
Flaskはセキュリティ上の理由からApacheを通したアクセスではデバッガを出さないので、前述したように $python app.pyで直接起動してください。
app.debug = True としてapp.run()すると ファイルの変更を察知して自動で再起動してくれてすごい便利。(syntax error の場合は異常終了するけどな!)

Jinja2

[用途]HTMLテンプレートビルダー
Welcome | Jinja2 (The Python Template Engine)
Flaskで使いやすいように組み込まれていますが、単体でも使える高機能なテンプレートビルダーです。
Python界のテンプレートビルダーのデファクトはこれかmakoですね。makoの方が泥臭いことができますが、Jinja2の方がAPIがすっきりしてます。

公式のサンプル

{% extends "layout.html" %}
{% block body %}
  <ul>
  {% for user in users %}
    <li><a href="{{ user.url }}">{{ user.username }}</a></li>
  {% endfor %}
  </ul>
{% endblock %}

シンタックスはほぼDjangoのそれと同等ですが、継承が使えるのが大きいです。(Djangoも最新版なら使えるんでしたっけ)

flaskから使うときは以下のように

from flask import render_template
...
@app.route("/")
def hello():
    return render_template("index.html", title="MyWeb", items=["a", "b", "c"])

のような形でtemplateに変数を渡してhtmlをビルドできます
ディレクトリ構成は

app.py
templates/
    - index.html
    - layout.html
static/
    - style.css

のようにしておく必要があります

pymongo

Pythonの辞書型のシンタックスjavascriptのそれ(JSON)とほぼ同等です。
辞書型そのままで扱えるpymongoは、PythonとMongoDBが組み合わさり最強にみえる!
mongodbの構成は、すっごく簡単に言うと

db - collection - {key1:value1, key2, value2, "_id":ObjcetId("~")} のJSONカラム(ObjectIdは自動生成される)

です。具体的には myapp1 - userlist - { "username":"mizchi", "password":"kogaidan" } のカラム といった感じになります。
これをPyMongoで表現すると

from pymongo import Connection
conn = Connection("localhost")
ul = conn["myapp1"]["userlist"]

ul.insert({"username":"mizchi", "password", "kogaidan"})
ul.insert({"username":"arakawatomonori", "password", "nonoriri"})

for i in ul.find().sort("username"):
    print i["username"], i["password"]
"""
arakawatomonori nonoriri
mizchi kogaidan
"""

といった使い方ができます。ね、簡単でしょ?

具体的な使い方は以下で
PyMongo 1.9+ Documentation ― PyMongo v1.9+ documentation
Symfoware MongoDBをPythonで操作する(PyMongo使用)

pyquery

pyquery: a jquery-like library for python ― pyquery v0.6.1 documentation
[用途] HTMLパーサ [依存] lxml
jQueryのようにCSSセレクタでHTMLをパースできます。クローラを書くときにお世話になります
API仕様までjQueryに習ってるので、あんまりPythonicな使い方が出来ないんですが、それを差し置いても便利なライブラリです。
utf-8の日本語渡すとバグるので、 unicodeで渡す必要があります。pythonで誰しも最初ハマるのですが、unicodePythonUnicode実装でありutf-8とは別物です。
unicodeで統一して書いたつもりでも、ファイルIOとかネットワーク経由で渡されたファイルのエンコーディングは相手に依存してるので、適宜書き換える必要がありますね


サンプル書くの飽きてきたのでこちらで
5分でWebスクレイピングをする - YAMAGUCHI::weblog

nose

[用途]TDD
テスト駆動開発のお供。
testを含むクラス/メソッドをテストしてくれます

def get(arg):
    d = {"a":1, "b":2, "c":3}
    return d[arg]

def test1_get():
    print get("c") #=> ok

def test2_get():
    print get("d") #=> error

自分はよく $ nosetests -s --pdb hoge.py のような使い方をします
-s でコマンドライン出力付き --pdbをつけるとエラー箇所でpdb(デバッガ)が起動します
webアプリは werkzeugに任せて、テキストの入出力処理などはnoseでtestすることが多いです

以上!

flaskとmongodbの紹介みたいになってしまいましたが
Pythonのパッケージは日本語でまとまってないので、初心者が探しにくいだろうと思って書きました。
まだまだ便利なライブラリはあるんですが、とりあえず1つのサンプルとして見てもらえれば


そんな感じで出来たのが TwitBlogin! http://twitblogin.com/ で、是非使ってみてください

以下サンプル

ユーザーを登録するだけ
ひと通り Model -View - Controlを持つので最低限のサンプルにはなるかなぁ…と

app.py
#!/usr/bin/env python
# -*- encoding:utf8 -*-
from flask import Flask, redirect, render_template, request, url_for
application = Flask(__name__)

from pymongo import Connection
conn = Connection("localhost")
ul = conn["myapp"]["userlist"]

@application.route("/add_user", methods=['POST'])
def add_user():
    name = request.form['name']
    ul.insert({"name":name})
    return redirect(url_for("index"))

@application.route("/")
def index():
    users = [i["name"] for i in ul.find()]
    return render_template("index.html", items =users)

if __name__ == "__main__":
    application.run(host="0.0.0.0", debug=True)
templates/index.html
<html><body>
<form method="POST" action="{{ url_for("add_user")}}">
<div>name:<input type="text" name="name"></div>
<input type="submit" value="send">
</form>

{% if items %}
<ul>
{% for i in items %}
<li> {{ i }} </li>
{% endfor%}
</ul>
{% endif %}

</body></html>