はじめに

本記事は、Python + Flask + Blueprintで構成するAPIサーバのエラーハンドリングの方法について共有します。

第一回の記事からの続きになりますので、必要な方は以下の記事をご参考ください。

Python+Flask+Blueprintを使ったAPIサーバ構築① 〜構成とサンプルプログラム編〜

環境

本記事では、以下の環境で動作確認を行いました。

  • Mac OSX Mojave(10.14.3)
  • Python 3.7.2
  • Click==7.0
  • Flask==1.0.2
  • Flask-Cors==3.0.7
  • itsdangerous==1.1.0
  • Jinja2==2.10
  • MarkupSafe==1.1.1
  • six==1.12.0
  • Werkzeug==0.15.1

前提

ここでは以下の2種類のエラーハンドリングについて記述します。

  • 予期せずエラーが上がるもの
  • 関数内で自らエラーを上げるもの(入力チェックなど)

関数内で自らエラーを上げるもの

Blueprintのエラーハンドラーを使うことこで、エラー時の挙動を定義することができます。
app/apiv1/の中にerrors.pyを以下のように定義します。

from app.exceptions import ValidationError
from . import api
from flask import render_template
from app.create_response import create_response

@api.app_errorhandler(500)
def validation_error(e):
    content = 'Internal Error Occurred'
    status_code = 500
    mimetype = 'application/json'
    response = create_response(content, status_code, mimetype)

    return response

こうすると、関数内で予期せずエラー(ステータスコード:500)が発生した場合に対処することができます。
あとは、これをapp/apiv1/__init__.pyでインポートするだけです。

from flask import Blueprint

api = Blueprint('apiv1', __name__)

# This modules need to be imported after the above api is defined, because the "api" variable is imported in each file
from . import testapp, errors

例えば、app/apiv1/testapp.pyを以下のようにあえてエラーが起こるように変更して実行すると、

from . import api
from flask import render_template, request, current_app
from app.exceptions import ValidationError
import base64

@api.route('/test', methods=['GET'])
def test():

    from app.create_response import create_response
    # content = render_template('ok.json')                                                                             
    content = render_template('okay.json') // あえて失敗させるため存在しないファイルを指定
    status_code = 200
    mimetype = 'application/json'
    response = create_response(content, status_code, mimetype)

    return response
$ curl http://127.0.0.1:5000/v1/test
Internal Error Occurred
$ curl http://127.0.0.1:5000/v1/test -o /dev/null -w '%{http_code}\n' -s
500

このようになります。想定外のエラーに対処ができていますね。
(エラー内容をerrors.pyに「Internal Error Occurred」とハードコーディングしていますが、エラー内容を別ファイルにしてrender_templateするほうが望ましいです。)

関数内で自らエラーを上げるもの

関数内で自らエラーを上げるものを定義するには、同じように、app/apiv1/errors.pyの中で以下のような定義を行います。

from app.exceptions import ValidationError
from . import api
from flask import render_template
from app.create_response import create_response

@api.app_errorhandler(500)
def validation_error(e):
    content = 'Internal Error Occured'
    status_code = 500
    mimetype = 'application/json'
    response = create_response(content, status_code, mimetype)

    return response

@api.app_errorhandler(400)
def bad_request(message):
    content = 'Bad Request'
    status_code = 400
    mimetype = 'application/json'
    response = create_response(content, status_code, mimetype)

    return response

これで、任意の関数でerrorをあげられます。例えば、app/apiv1/testapp.pyにabortモジュールを新たにimportして、以下のように400のステータスコードでabortをあげてみます。

from . import api
from flask import render_template, request, current_app, abort
from app.exceptions import ValidationError                                                                           
import base64

@api.route('/test', methods=['GET'])
def test():

    abort(400)

    from app.create_response import create_response
    content = render_template('ok.json')
    # content = render_template('okay.json')                                                                           
    status_code = 200
    mimetype = 'application/json'
    response = create_response(content, status_code, mimetype)

    return response

$ flask run
 * Serving Flask app "application.py"
 * Environment: production
   WARNING: Do not use the development server in a production environment.
   Use a production WSGI server instead.
 * Debug mode: off
None
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
127.0.0.1 - - [01/Apr/2019 09:45:31] "GET /v1/test HTTP/1.1" 400
$ curl http://127.0.0.1:5000/v1/test
Bad Request
$ curl http://127.0.0.1:5 -o /dev/null -w '%{http_code}\n' -s
400

想定通りの結果が得られました。
あとは、自身のエラー設計に従って、40x等を増やしていけば良いと思います。

おわり

今回は、Python + Flask + Blueprintで構成するAPIサーバのエラーハンドリングの方法について共有しました。

APIサーバを構築するときはエラーのレスポンスについても自分でハンドルして、json形式にしてリターンすると良いでしょう。
今回はレスポンスをjsonにせず、stringで返却しましたが、json形式で返却して、APIを呼んだクライアントに優しい作りにすることが望ましいです。

次回は、Python + Flask + Blueprintの構成で、どのようにユニットテストを行えばよいか共有します。