/home/by-natures/dev*

データ界隈で働くエンジニアとしての技術的なメモと、たまに普通の日記。

Flask の例外処理について考えてみる

Flask(フラスク)第2段です。

Flask の例外処理の方法について調べたところ、様々な例外処理の方法があることが分かりました。実装においては、好みのものや、アプリケーションにあったものを選択する必要がありますので、調べたことを簡単にご紹介します。

Flask を使って Basic 認証付きの REST API クラスを作る」も合わせてどうぞ。

[toc]

例外処理の方法

abort を使う

abort を使えば、特定のレスポンスコードを返答して処理を終えることができます。

abort に与えるレスポンスコードには、それぞれのエラーメッセージがモジュール内で定義されています。また、abort メソッドにはレスポンスコードしか与えられないので、エラーメッセージを状況に応じて定義したい場合には向かなそうです。

from flask import abort

@app.route('/some/path')
def my_method():
    abort(404)
    // abort 以降は実行されない

レスポンスコード or 例外クラスを個別に処理する

@app.errorhandler デコレータを使えば、特定のレスポンスコードや例外クラスの例外が発生した場合に、処理を受け取ることが可能です:

from flask import render_template

@app.errorhandler(404)
def page_not_found(error):
    return render_template('page_not_found.html'), 404

app.errorhandler には HTTPException を指定しておけば、ほとんどの例外はキャッチできるかと思うので、error オブジェクトの内容によって処理を記述すればよさそうです。

独自の例外クラスを定義する

HTTPException クラスを継承し、クラス変数に code, description を与えれば、処理内では raise するだけで例外処理が可能です:

class IllegalParameter(HTTPException):
    code = 400
    description = 'ILLIGAL PARAMETER'

class HelloWorld(restful.Resource):
    ...
    def get(self):
        raise IllegalParameter()
    ...

ただし、Flask-RESTful は description を見ず、モジュール内で定義されているレスポンスコードに紐づくエラーメッセージを使ってしまうため、独自のエラーメッセージを使いたい場合はエラーハンドリングをオーバーライドします。この方法であれば、同じ例外クラスでも description の内容が変わればエラーメッセージを変更できるので便利です。

class MyRestApi(flask_restful.Api):
    def handle_error(self, e):
        if isinstance(e, HTTPException):
            code = e.code
            data = {'status_code': e.code, 'message': e.description}
        else:
            return super(MyRestApi, self).handle_error(e)
        return self.make_response(data, code)

app = Flask(__name__)
api = MyRestApi(app)

Flask-RESTful の Custom Error Messages を利用する

Custom Error Messages(Flsk-RESTful 公式ページ) を利用すれば、例外クラス・レスポンスコード・エラーメッセージの組を自由に定義することができます(以下の例は公式ページから参照):

errors = {
    'UserAlreadyExistsError': {
        'message': "A user with that username already exists.",
        'status': 409,
    },
    'ResourceDoesNotExist': {
        'message': "A resource with that ID no longer exists.",
        'status': 410,
        'extra': "Any extra information you want.",
    },
}

app = Flask(__name__)
api = flask_restful.Api(app, errors=errors)

この方法は、PyPI でダウンロードできる Flask-RESTful v0.2.12 だと利用できないため、GitHub から直接インストールする必要があります。個人的には、errors の定義がやや自由すぎるのがちょっと使いづらそうだと感じました。

例外処理は独自の例外クラスを定義するのが良さそう

今回は業務のために調査していたのですが、独自の例外クラスを定義する方法を選びました。

同じステータスコードでもエラーメッセージを変更したかったことと、処理中にエラーを返したければ、独自定義した例外クラスを raise すればよいだけなので、かなりシンプルです。abort だと実際の処理中にステータスコードを気にする必要があったので、ステータスコードの管理が面倒なので使わないことにしました。