ようへいの日々精進XP

よかろうもん

Flask のエラーハンドラメモ

tl;dr

Flask で簡単なアプリケーションを書く機会を頂いたので試行錯誤しながら書いていますが、特にエラーハンドラが心地よかったのでメモしておきます。

参考

上記のサイトがとても参考になりました。感謝!

memo

エラーハンドラ

abort(xxx) でエラーを返す際に任意のエラーを返すことが出来るとのことで、例えば Web アプリケーション内のエラー処理で abort(xxx) と書いておけば、指定したエラー内容でクライアントに返却することが出来ます。

サンプル

以下のようなサンプルアプリケーションを書いてみました。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from flask import Flask
from flask import abort, jsonify, request
import json

app = Flask(__name__)

@app.route('/foo', methods=['POST'])
def post_foo():
    '''
     Description
      - リクエストボディ {"id": "foo"} であれば ok を返す
      - リクエストボディ {"id": "foo"} であれば id is different を返す
    '''
    if request.headers['Content-Type'] == 'application/json':
        id = request.json['id']
    else:
        id = json.loads(request.form.to_dict().keys()[0])['id']

    if id == 'foo':
        return jsonify({ 'message': 'ok'})
    else:
        abort(404, { 'id': id })

@app.errorhandler(404)
def error_handler(error):
    '''
     Description
      - abort(404) した時にレスポンスをレンダリングするハンドラ
    '''
    response = jsonify({ 'id': error.description['id'], 'message': 'id is different', 'result': error.code })
    return response, error.code

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

実行すると以下のような感じ。

$ curl -X POST -d '{"id": "foo"}' localhost:5000/foo
127.0.0.1 - - [01/Oct/2016 12:15:11] "POST /foo HTTP/1.1" 200 -
{
  "message": "ok"
}

$ curl -X POST -d '{"id": "fo0"}' localhost:5000/foo
127.0.0.1 - - [01/Oct/2016 12:15:25] "POST /foo HTTP/1.1" 404 -
{
  "id": "fo0",
  "message": "id is different",
  "result": 404
}

少し詳しく(1)

@app.errorhandler(404)
def error_handler(error):
    '''
     Description
      - abort(404) した時にレスポンスをレンダリングするハンドラ
    '''
    response = jsonify({ 'id': error.description['id'], 'message': 'id is different', 'result': 404 })
    return response, error.code

error_handler の引数 error にはどのようなメソッドが含まれているか確認してみます。

['__call__', '__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__getitem__', '__getslice__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setstate__', '__sizeof__', '__str__', '__subclasshook__', '__unicode__', '__weakref__', 'args', 'code', 'description', 'get_body', 'get_description', 'get_headers', 'get_response', 'message', 'name', 'response', 'wrap']

それぞれのメソッドを見てみます。

(snip)

@app.errorhandler(404)
def error_handler(error):
    '''
     Description
      - abort(404) した時にレスポンスをレンダリングするハンドラ
    '''

    print dir(error)
    print 'error.args = ' + str(error.args)
    print 'error.code = ' + str(error.code)
    print 'error.description = ' + str(error.description)
    print 'error.message = ' + str(error.message)
    print 'error.name = ' + str(error.name)
    print 'error.response = ' + str(error.response)
    print 'error.wrap = ' + str(error.wrap)

    response = jsonify({ 'id': error.description['id'], 'message': 'id is different', 'result': error.code })
    return response, error.code

(snip)

以下のような結果となりました。

['__call__', '__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__getitem__', '__getslice__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setstate__', '__sizeof__', '__str__', '__subclasshook__', '__unicode__', '__weakref__', 'args', 'code', 'description', 'get_body', 'get_description', 'get_headers', 'get_response', 'message', 'name', 'response', 'wrap']
error.args = ()
error.code = 404
error.description = {'id': u'fo0'}
error.message =
error.name = Not Found
error.response = None
error.wrap = <bound method type.wrap of <class 'werkzeug.exceptions.NotFound'>>

abort(xxx) を投げる時にエラーレスポンスに入れたい情報を引数として書いてあげれば error.description で取り出して、エラーに合わせたメッセージをレンダリングしてクライアント返却することができるので嬉しいです。

少し詳しく(2)

以下のように書けば、それぞれのエラーでも一つの関数でハンドリングすることが出来ます。

@app.errorhandler(400)
@app.errorhandler(404)
@app.errorhandler(500)
def error_handler(error):
    '''
     Description
      - abort(400) / abort(404) / abort(500) した時にレスポンスをレンダリングするハンドラ
    '''
    response = jsonify({ 'message': error.name, 'result': error.code })
    return response, error.code

これも嬉しいです。

以上

メモでした。