Flaskの構成とかライブラリメモ

はじめに

Flaskでいくつかアプリを作るのでメンバーに共有するために、使うライブラリとその説明とかをメモ。 ソースコードGithubで公開しています。

github.com

Flask-SQLAlchemy

モデルの定義

from . import db
from werkzeug.security import generate_password_hash, check_password_hash

class User(db.Model):
    __tablename__ = 'users'
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(128), index=True)
    password_hash = db.Column(db.String(128))

    @property
    def password(self):
        raise AttributeError('password is not a readable attribute')

    @password.setter
    def password(self, password):
        self.password_hash = generate_password_hash(password)

    def verify_password(self, password):
        return check_password_hash(self.password_hash, password)

    def __repr__(self):
        return '<User %r>' % self.name

    @property
    def serialize(self):
        return {
            'id': self.id,
            'name': self.name,
        }
  • 状態を表すものにはpropertyデコレータをつける

    上の例だとserializeで返すものはUserの別表現みたいなもので、このオブジェクトの状態を表しているため、メソッド(手続き)として定義するのは直感的じゃないです。propertyデコレータをつけてオブジェクトが持つ値のようにみせます。

  • 値の種類が多いカラムには index=True をつける

    MySQLのインデックスは基本B-Treeインデックスというインデックスを使用しているため、簡単に言うとそのカラムの値の種類が多い(=カーディナリティが高いともいう)場合、インデックスを張っておくと効率的にデータを取得できます。主キーにはインデックスが張られているので不要。

  • __repr__ 属性を定義すると動作確認・デバッグの際に役立ちます

  • serializeはユーザの一覧をJSONで返したい時とかに利用する

from flask import jsonify
    
@app.route('/users')
def get_users():
    return jsonify(users=[user.serialize for user in User.query.all()])
  • 認証にはwerkzeugを使用している。下記のように使えます。
>>> from .models import User
>>> u = User()
>>> u.password='cat'
>>> u.password
Traceback (most recent call last):
      :
AttributeError: password is not a readable attribute
>>> u.password_hash
'pbkdf2:sha1:1000$pjsh7cVO$33e723c78dfe39b49ade0c7b5db8f034bdd6d31e'
>>> u.verify_password('cat')
True
>>> u.verify_password('cats')
False

クエリのプロファイリング

Flask-SQLAlchemyには get_debug_queries という遅いクエリがないか調べる機能があります。 configに SLOW_DB_QUERY_TIME を定義しておいて、下のようにすれば遅いクエリをロギングできます。

from flask.ext.sqlalchemy import get_debug_queries

@app.after_request
def after_request(response):
    for query in get_debug_queries():
        if query.duration >= app.config['SLOW_DB_QUERY_TIME']:
            app.logger.warning(
                'Slow query: %s\nParameters: %s\nDuration: %fs\nContext: %s\n'
                % (query.statement, query.parameters, query.duration,
                   query.context))
    return response


Flask-Script

djangoのmanage.pyみたいなものが作れます。ここでは下記のページに載っている werkzeug を使ったプロファイリングをFlask-Scriptから簡単に実行できるようにしてみましょう。

# manage.py
from flask.ext.script import Manager

from app import app, db

manager = Manager(app)
manager.add_command('db', MigrateCommand)

@manager.command
def profile(length=25):
    """Start the application under the code profiler."""
    from titl import ProfilerMiddleware
    app.wsgi_app = ProfilerMiddleware(app.wsgi_app, restrictions=[length])
    app.run()

if __name__ == '__main__':
    manager.run()

上のようにmanage.commandデコレータをつけると、python manage.py profile とすれば実行できます。 ちなみにrunservershell はデフォルトで定義されていて最初から使えます。

$ python manage.py runserver -p 8000
$ python manage.py shell


Flask-Migrate

Alembicというマイグレーションツールのラッパーです。Flask-Scriptを利用していて、先ほどのFlask-Scriptのファイルに下記のコードを追加することで、Djangoのように簡単にマイグレーションファイルが生成出来ます。

# manage.py
:
manager.add_command('db', MigrateCommand)
$ python manage.py db init     # 初期化 (./migrations/以下に雛形を生成)
$ python manage.py db migrate  # マイグレーションファイルの生成
$ python manage.py db upgrade  # マイグレート


Flask-Profiler (2015/11/04 追記)

github.com

試してないけど、ちょっと気になる。


Flask-DebugToolBar

Flask-DebugToolbar — Flask-DebugToolbar 0.10.0 documentation

Django Debug ToolbarのFlask版。 試してないけど、ちょっと気になる。


Flask-Admin

Flask-Admin — flask-admin 1.3.0 documentation

Django Adminみたいに使える。authenticationとかも簡単に追加できる。 多少カスタマイズをした方がいい。Django Adminよりカスタマイズは簡単そう。


デコレータ

ライブラリとかではないですが、PythonとかFlaskを勉強し始めた時に混乱したのでメモ。 下記のデコレータを利用するとユーザがログイン済みかどうかチェックしてログイン画面にredirectさせたり出来る。

# http://flask.pocoo.org/docs/patterns/viewdecorators/
from functools import wraps
from flask import g, request, redirect, url_for

def login_required(f):
    @wraps(f)
    def decorated_function(*args, **kwargs):
        if g.user is None:
            return redirect(url_for('login', next=request.url))
        return f(*args, **kwargs)
    return decorated_function

デコレータは一見ややこしいですが、下の記事の解説が分かりやすかったです。

ちなみに functools.wraps デコレータをつける理由については下の記事が分かりやすいと思います。


HATEOAS APIの実装例

これもライブラリではないですがHATEOAS(Hypermedia As The Engine Of Application State)という概念があります。 APIの返すデータの中に次に行う行動、取得するデータ等のURIをリンクとして含めることで、そのデータを見れば次にどのエンドポイントにアクセスすればよいかが分かるような設計です。Flaskだったら以下のようにすると良いと思います。JSONAPIを作るときには参考にして下さい。

from flask import Blueprint, jsonify, current_app, url_for
from ..models import User, Task

api = Blueprint('api', __name__)

@api.route('/')
def index():
    # 次にどのエンドポイントにアクセスすればよいか、URIを渡す
    return jsonify(
        users=url_for('.users'),
        tasks=url_for('.tasks'),
    )

@api.route('/users')
def users():
    """ユーザ一覧"""
    users = User.query.all()
    return jsonify(Users=[u.serialize for u in users])

@api.route('/task')
def tasks():
    """タスク一覧"""
    tasks = Task.query.all()
    return jsonify(Tasks=[t.serialize for t in tasks])
$ curl http://127.0.0.1:5000/api/v1/
{
  "users": "/api/v1/users"
  "tasks": "/api/v1/tasks"
}


Flask資料

Flaskについて調べてきた中で見つけた資料とかをメモ

Flask Web Development: Developing Web Applications with Python

Flask Web Development: Developing Web Applications with Python

ぼくはこの本で勉強しました。かなりおすすめです。この本の著者はブログにも別のチュートリアルを公開してるのでそっちを読んでもいいかもしれません。

blog.miguelgrinberg.com

他にはrunnableとかQiitaのFlaskタグ探すといいと思います。

参考にしたコード

おわりに

これからFlaskのアプリ作りながらもっといい方法とかあればこの記事も編集していきます。