flaskr

公式の Flask のチュートリアルにある flaskr というミニブログを写経していきます。

公式のチュートリアルでは SQLite を直接使っていましたが、 今回は SQLAlchemy という O/R マッパーを使っていきます。

SQLAlchemy は Python の O/R マッパーの中では最も強力なものですが、 簡単な使い方もできるようになっています。

Step 0: 準備

インストール

Flask-SQLAlchemy という Flask 拡張をインストールします。

$ pip install Flask-SQLAlchemy

ディレクトリ構成

Hello, World プログラムでは1ファイルで完結していましたが、今回は少しファイルを分けて、 本格的な構成にしていきます。

まず、適当なディレクトリの中に次のようにファイルとディレクトリを作っていきます:

run.py
flaskr/
    __init__.py
    config.py
    views.py
    models.py
    templates/
    static/

run.py

デバッグモードでアプリケーションを起動するだけのスクリプトです。 (ダウンロード)

#!/usr/bin/env python
from flaskr import app
app.run(debug=True)

flaskr/__init__.py

Python では __init__.py というファイルを含むディレクトリがパッケージになります。 パッケージとは、子モジュールを持てるモジュールです。 この場合 flaskr/__init__.pyflaskr というパッケージになり、 flaskr/config.pyflaskr.config モジュールになります。

flaskr/__init__.pyapp オブジェクトの生成とプラグインのセットアップをし、 最後に views を読み込みます。 (ダウンロード)

from flask import Flask
from flask.ext.sqlalchemy import SQLAlchemy

app = Flask(__name__)
app.config.from_object('flaskr.config')
db = SQLAlchemy(app)

import flaskr.views

flaskr/config.py

flaskr/config.py には設定を書いていきます。

SQLALCHEMY_DATABASE_URI は SQLAlchemy 用の設定で、 “ドライバ名://ホスト名/db名?オプション=値” という URI 形式で利用するデータベースを指定します。 今回は sqlite を使うので、ドライバ名は sqlite, ホスト名は空、db名はファイル名になります。

SECRET_KEY は、セッション情報を暗号化するための鍵です。 Flask はデフォルトではセッション情報を全て Cookie に保存するので、改ざん対策のために暗号化しています。 (ダウンロード)

SQLALCHEMY_DATABASE_URI = 'sqlite:///flaskr.db'
SECRET_KEY="secret key"

その他

flaskr/views.py と flaskr/models.py はこれから実装していくので、 いまは空にしておいてください。 views にはアクションを、 models には モデルを書いていきます。

templates にはテンプレートファイルを、 static には CSS などの静的ファイルを格納していきます。

Step 1: model

スキーマを定義するだけのモデルクラスを作ります。

flaskr/models.py (ダウンロード):

# coding: utf-8
from flaskr import db
from sqlalchemy import *


class Entry(db.Model):
    u"ブログエントリ"
    id = Column(Integer, primary_key=True)
    title = Column(Text)
    text = Column(Text)

    def __repr__(self):
        return "<Entry id={} title={!r}>".format(self.id, self.title)


def init():
    db.create_all()

実際にデータベースとテーブルを作ります:

python -c "import flaskr.models; flaskr.models.init()"

インタラクティブシェルでモデルを触ってみましょう. SQLAlchemy は Unit of Work というスタイルの O/R マッパーで、取得したエンティティは自動的に「セッション」に紐づけられます。 エンティティを操作したあとに db.session.commit() することで、変更がDBに反映されます。 新規にエンティティを作成する場合は、 db.session.add(entity) でセッションに紐づけます。

>>> from flaskr.models import Entry
>>> from flaskr import db
>>> entry = Entry(title="title1", text="text1")
>>> db.session.add(entry)
>>> db.session.commit()
>>> for i in range(2, 5):
...     entry = Entry(title='title' + str(i), text='text' + str(i))
...     db.session.add(entry)
...
>>> db.session.commit()
>>> entries = Entry.query.all()
>>> entries
[<Entry id=1 title='title1'>, <Entry id=2 title='title2'>, <Entry id=3 title='title3'>, <Entry id=4 title='title4'>]
>>> entries[0].id
1
>>> entries[0].text
u'text1'
>>> Entry.query.get(3)
<Entry id=3 title='title3'>
>>> Entry.query.filter_by(title='title2').one()
<Entry id=2 title='title2'>
>>> entry = _
>>> entry.text
u'text2'
>>> entry.text = "fizz buzz"
>>> db.session.commit()
>>> Entry.query.filter_by(title='title2').one().text
u'fizz buzz'

Step 2: view

Blog エントリの一覧と投稿ができるように、 flaskr/views.py を実装していきます。

flaskr/views.py (ダウンロード):

import flask
from flaskr import app, db
from flaskr.models import Entry


@app.route('/')
def show_entries():
    entries = Entry.query.order_by("id desc").limit(10).all()
    return flask.render_template('show_entries.html',
                                 entries=entries)


@app.route('/add', methods=['POST'])
def add_entry():
    entry = Entry(
            title=flask.request.form['title'],
            text =flask.request.form['text'],
            )
    db.session.add(entry)
    db.session.commit()
    flask.flash('New entry was successfully posted')
    return flask.redirect(flask.url_for('show_entries'))

Step 3: テンプレートとCSSを用意する

flaskr/templates/show_entries.html (ダウンロード):

<!doctype html>
<html>
<head>
    <title>Flaskr</title>
    <link rel=stylesheet type=text/css href="{{ url_for('static', filename='style.css') }}">
</head>
<body>
    <div class=page>
      <h1>Flaskr</h1>
      {% for message in get_flashed_messages() %}
        <div class=flash>{{ message }}</div>
      {% endfor %}
      <form action="{{ url_for('add_entry') }}" method=post class=add-entry>
        <dl>
          <dt>Title:
          <dd><input type=text size=30 name=title>
          <dt>Text:
          <dd><textarea name=text rows=5 cols=40></textarea>
          <dd><input type=submit value=Share>
        </dl>
      </form>
      <ul class=entries>
      {% for entry in entries %}
        <li><h2>{{ entry.title }}</h2>{{ entry.text }}
      {% else %}
        <li><em>Unbelievable.  No entries here so far</em>
      {% endfor %}
      </ul>
    </div>
</body>
</html>

CSS も用意しましょう.

flaskr/static/style.css (ダウンロード):

body            { font-family: sans-serif; background: #eee; }
a, h1, h2       { color: #377BA8; }
h1, h2          { font-family: 'Georgia', serif; margin: 0; }
h1              { border-bottom: 2px solid #eee; }
h2              { font-size: 1.2em; }

.page           { margin: 2em auto; width: 35em; border: 5px solid #ccc;
                  padding: 0.8em; background: white; }
.entries        { list-style: none; margin: 0; padding: 0; }
.entries li     { margin: 0.8em 1.2em; }
.entries li h2  { margin-left: -1em; }
.add-entry      { font-size: 0.9em; border-bottom: 1px solid #ccc; }
.add-entry dl   { font-weight: bold; }
.metanav        { text-align: right; font-size: 0.8em; padding: 0.3em;
                  margin-bottom: 1em; background: #fafafa; }
.flash          { background: #CEE5F5; padding: 0.5em;
                  border: 1px solid #AACBE2; }
.error          { background: #F0D6D6; padding: 0.5em; }

これでひとまず完成です。

$ python run.py

で実行して、ブラウザで動いていることを確認してみましょう。

Step 4: テスト

インタラクティブシェル

まずはインタラクティブシェルからリクエストを実行してみましょう.

>>> import flaskr
>>> client = flaskr.app.test_client()
>>> response = client.post('/add', data={'title': 'test title 1', 'text': 'test text 1'}, follow_redirects=True)
>>> response.status_code
200
>>> response.status
'200 OK'
>>> print(response.data)
<!doctype html>
<html>
<head>
    <title>Flaskr</title>
    <link rel=stylesheet type=text/css href="/static/style.css">
</head>
<body>
    <div class=page>
      <h1>Flaskr</h1>

        <div class=flash>New entry was successfully posted</div>

      <form action="/add" method=post class=add-entry>
        <dl>
          <dt>Title:
          <dd><input type=text size=30 name=title>
          <dt>Text:
          <dd><textarea name=text rows=5 cols=40></textarea>
          <dd><input type=submit value=Share>
        </dl>
      </form>
      <ul class=entries>

        <li><h2>test title 1</h2>test text 1

      </ul>
    </div>
</body>
</html>

py.test のインストール

Python の標準ライブラリにも unittest モジュールがありますが、より簡単にテストを書ける pytest を使いましょう。

$ pip install pytest

テストを書く

py.test は、 *_testtest_* という名前のモジュールを検索して、その中の test_* という名前の関数や Test* といった名前のクラスを検索し実行します。

py.test を使う場合、 assertEqual などの assert メソッドを利用しなくても、 python の assert 文を使うだけで十分です。 たくさんのメソッドを覚える必要が無いので楽ちんです。

先ほどインタラクティブシェルで試したログを見ながらテストを書いていきます。

flaskr/tests/test_actions.py (ダウンロード):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
import flaskr
from flaskr import app
from flaskr.models import Entry

def setup_module():
    flaskr.db.drop_all()
    flaskr.models.init()

def test_post_entry():
    client = app.test_client()
    response = client.post('/add',
                           data={'title': 'test title 1', 'text': 'test text 1'},
                           follow_redirects=True)
    assert response.status_code == 200
    assert "test title 1" in response.data
    assert "test text 1" in response.data
    with app.test_request_context():
        assert Entry.query.count() == 1
        entry = Entry.query.get(1)
        assert entry.title == 'test title 1'
        assert entry.text == 'test text 1'

実行する

py.test を実行すると、テストファイルを見つけた場所でテストを実行するので、 import flaskr が失敗してしまいます。 プロジェクトのトップディレクトリを検索パスに追加するために PYTHONPATH を使いましょう。

$ PYTHONPATH=. py.test
================================== test session starts ===================================
platform darwin -- Python 2.7.3 -- pytest-2.3.4
collected 1 items

flaskr/tests/test_actions.py .

================================ 1 passed in 0.20 seconds ================================

Note

PYTHONPATH 以外の方法

setup.py というファイルを書いて、一般的なインストールできるパッケージの形にすると、 python setup.py develop で Python の検索パスに現在のディレクトリを追加されます。

また、 py.test –genscript=runtests.py でテストを実行するスクリプトを生成し、 その先頭で次のように検索パスを追加しても良いでしょう。

import sys, os
sys.path.insert(0, os.path.dirname(__file__))

IDD

モデルやアクションを書く前にテストを書くTDDをするためには、まずモデルやレスポンスオブジェクトのAPIを覚えないといけません。

初心者のうちは、インタラクティブシェルで触ってみて、コードを書いて、インタラクティブシェルで動作を確認して、 その結果をテストにするというインタラクティブシェル駆動開発を行えば、APIを覚えながら開発ができます。