Flask-Restless を試してみる

API サーバとして Flask を使う場合にコードが簡略できそうなので、Flask-Restless を試してみた。
同様なプロダクトとして、Flask-Restful があるけど、Flask-Restless を選んでみたのは view を書かなくてよさそうだったから。
# 逆に言えば「おまかせ」になってしまう可能性もある


前提としてアップロードした写真を表示する Web アプリケーションで、以下のルーティングを想定する。

http://example.com/pictures 写真一覧を html で返す
http://example.com/pictures/:id :id の写真情報を html で返す
http://example.com/api/pictures 写真一覧を JSON で返す
http://example.com/api/pictures/:id :id の写真情報を JSON で返す

app.py

# -*- coding: utf-8 -*-
from flask import Flask, request, render_template
from pictapp.db import mysession
from pictapp.extensions import restless
from pictapp.views import views
from pictapp.views.api import register_api


def create_app():
    app = Flask(__name__)

    restless.init_app(app, session=mysession)

    for view in views:
        app.register_blueprint(view)

    apis = register_api()
    [app.register_blueprint(api) for api in apis]

    return app


if __name__ == '__main__':
    create_app().run(debug=True)

extensions.py

from flask.ext.restless import APIManager
    
__all__ = ['restless']

restless = APIManager()

views/__init__.py

from pictapp.views.index import app

views = (app,)

views/api.py

from pictapp.extensions import restless
from pictapp.models import Picture


def register_api():
    return (restless.create_api_blueprint(Picture, methods=['GET']),)

db.py

from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import scoped_session, session maker

engine = create_engine('sqlite:////tmp/testdb.sqlite', convert_unicode=True)
Session = sessionmaker(autocommit=False, autoflush=False, bind=engine)
mysession = scoped_session(Session)

Base = declarative_base()
Base.metadata.bind = engine

modes.py

from sqlalchemy import Column, Integer, String
from pictapp.db import Base


class Picture(Base):
    __tablename__ = 'pictures'
    id = Column(Integer, primary_key=True)
    name = Column(String(254), nullable=False)
    title = Column(String(254), nullable=False)
    description = Column(String(254), nullable=False)

    def __init__(self, name, title, description):
        self.name = name
        self.title = title
        self.description = description

    def __repr__(self):
        return "<Picture({0}, {1}, {2})>".format(
            self.id, self.name, self.title, self.description
        )

だいぶ簡略したけど雰囲気はこんな感じ。
サンプルコードとかは app.py とかに直接 restless の create_api_blueprint() とかを書いてあるけど、モデルの追加とか普通にあるし、イチイチ Applictaion factory である app.py とかの変更をしたくない。
そのため views/api.py という所に view として実装してみた。
モデルを api に追加したくなったら、views/api.py の return しているタプルに追加すれば自動的に反映するようにしてみた。
あとサンプルは Flask-SQLAlchemy を使うコードがあるけど、素の SQLAlchemy も使える(ドキュメントに書いてある)。


これでブラウザで http://examples/api/pictures*1 にアクセスすると SQLite3 に格納されたデータが全て応答する。

{
  "num_results": 5,
  "objects": [
    {
      "description": "test",
      "id": 1,
      "name": "717f303c.jpg",
      "title": "sample"
    },
    {
      "description": "test",
      "id": 2,
      "name": "62744a28.jpg",
      "title": "sample"
    },
    {
      "description": "bbbb",
      "id": 3,
      "name": "896844eb.jpg",
      "title": "aaa"
    },
    {
      "description": "bbbb",
      "id": 4,
      "name": "62744a28_1.jpg",
      "title": "aaa"
    },
    {
      "description": "bbb",
      "id": 5,
      "name": "4a8b6d8f.jpg",
      "title": "aaa"
    }
  ],
  "page": 1,
  "total_pages": 1
}

意外と簡単になった。
特に API の場合 view を一切書かなくても良さそう。
POST や PUT のような作成/更新なものも preprocessor や postprocessor を使って定義してやればある程度自由度が高いものが出来そう。
Customizing the ReSTful interface — Flask-Restless 1.0.0b2.dev documentation

*1:最初 /api/picture かと思ったけど、特に指定しなければ、モデルで定義した __tablename__ を見るよう