Python+Flask+Blueprintを使ったAPIサーバ構築 ② 〜エラーハンドリング編〜
はじめに
本記事は、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の構成で、どのようにユニットテストを行えばよいか共有します。