先日、AppEngine 向けには自分用のツールだけを開発すると宣言しました。
自分用のツールなら Kay みたいなヘビー級のフレームワークを使うまでもないです。なので、Flask を使っています。
ただ、Flask はビューとテンプレートの機能ぐらいしか提供していないので、足りない機能は他のライブラリで補う必要があります。AppEngine なら、モデルは db モジュール、フォームは WTForms を使えばいいですね。
でも、それだけではまだ不便。そこで AppEngine ユーティリティを集めた Flask 拡張を作ってみました。
"""
flask_appengine
------------------
Adds basic App Engine support for Flask.
:copyright: (c) 2011 by tnakaura.
:license: BSD, see LICENSE for more details.
"""
from functools import wraps
from google.appengine.api import users
from werkzeug.exceptions import (
NotFound, Unauthorized, Forbidden,
)
from flask import (
request, redirect,
)
class AppEngine(object):
def __init__(self, app=None):
if app is not None:
self.app = app
self.init_app(self.app)
else:
self.app = None
def init_app(self, app):
self.app = app
self.app.context_processor(self._inject_users)
def _inject_users(self):
return dict(
create_login_url=self.create_login_url,
create_logout_url=self.create_logout_url,
get_current_user=users.get_current_user,
is_current_user_admin=users.is_current_user_admin)
def create_login_url(self, url=None):
"""Computes the login URL for redirection.
"""
if url is None:
url = request.url
return users.create_login_url(url)
def create_logout_url(self, url=None):
"""Computes the logout URL for this request and specified destination URL,
for both federated login App and Google Accounts App.
"""
if url is None:
url = request.url
return users.create_logout_url(url)
def login_required(func):
"""A decorator to require that a user be logged in to access a view.
"""
@wraps(func)
def _login_required(*args, **kwargs):
user = users.get_current_user()
if user is None:
if request.is_xhr:
raise Unauthorized()
else:
return redirect(users.create_login_url(request.url))
return func(*args, **kwargs)
return _login_required
def admin_required(func):
"""A decorator to require that a user be admin to access a view.
"""
@wraps(func)
def _admin_required(*args, **kwargs):
if not users.is_current_user_admin():
user = users.get_current_user()
if user:
raise Forbidden()
elif request.is_xhr:
raise Unauthorized()
else:
return redirect(users.create_login_url(request.url))
return func(*args, **kwargs)
return _admin_required
def get_or_404(cls, key):
model = cls.get(key)
if not model:
raise NotFound()
return model
def get_by_key_name_or_404(cls, key_name):
model = cls.get_by_key_name(key_name)
if not model:
raise NotFound()
return model
def get_by_id_or_404(cls, id):
model = cls.get_by_id(id)
if not model:
raise NotFound()
return model
login_required デコレータや、get_or_404 関数のような、Flask での AppEngine 開発がちょっとだけ便利になるユーティリティを集めています。ちりも積もればなんとやら、です。
使い方はこんな感じ。
from flask import Flask
from flask_appengine import (
AppEngine, login_required,
)
app = Flask(__name__)
gae = AppEngine(app)
@app.route("/")
@login_required
def index():
return "Hello, world!"
あと、AppEngine クラスは内部で ContextProcessers を登録するので
<html>
<head>
<title>FlaskAppEngineSample</title>
</head>
<body>
{% if get_current_user() %}
<a href="{{ create_logout_url() }}">logout</a>
{% else %}
<a href="{{ create_login_url() }}">login</a>
{% endif %}
</body>
</html>
という風に、テンプレート内で Users API が使えます。これは結構便利かも。
AppEngine の全ての機能をサポートしようとしたら半端ない量になるので、バランスを考えながら機能を追加していく予定。必要最小限の機能を提供するのが Flask っぽいですから。とりあえず、Flask のセッションでデータストアを使うようにはしたいです。あと appstats のサポートも。