-
Flask 1.1.2
-
Flask-RESTful 0.3.8
-
例外クラスを自作したい人向け
-
追記
- 2020年5月1日:
Flask
,Flask-RESTful
の最新版で動作確認済み
- 2020年5月1日:
概要
Flaskは自由度の高いフレームワークなので、例外処理の実装方法も色々と用意されている。さっとググっただけでも3パターンほど記事が見つかる。
-
abort()
関数 -
Flask#errorhandler
デコレーター -
Flask#register_error_handler()
メソッド - (Flask-RESTful限定)
Api#handle_error()
メソッドのオーバーライド
この記事では「自作クラスで例外を送出したい」という場合に、自分なりに色々試してみて辿り着いたベストプラクティスをまとめた。
基本方針
Api#handle_error()
メソッドをオーバーライドする方針で進める。
以降に示すサンプルコードは次のようなプロジェクト構成を前提としている。
<src>
| main.py
+---app/
| __init__.py
+---dto/
| __init__.py
| exception.py
+---api/
__init__.py
sample.py
例外クラスの実装
効果的に例外処理を行うためには
- どんな例外が発生したのか
- どうすれば解決できるのか(もしくは解決できないのか)
をRESTクライアントへ伝える必要がある。そのために例外クラスに独自のフィールドを設けて、構造化したオブジェクトにしたい、というニーズは確実にあるだろう。
-
ポイント
-
werkzeug.HTTPException
クラスを継承する - HTTPステータスコードは
code
フィールドに持たせる
-
from typing import List, Union
from werkzeug.exception import HTTPException
class MyException(HTTPException):
fault_id: str # custom field
message_id: str # custom field
arguments: List[str] # custom field
def __init__(self, fault_id: str, message_id: str, arguments: Union[None, List[str]] = None):
self.fault_id = fault_id
self.message_id = message_id
if arguments is None:
self.arguments = []
else:
self.arguments = arguments
class MyBadRequestException(MyException):
code = 400 # override field
class MyNotFoundException(MyException):
code = 404 # override field
class MyUnprocessableEntityException(MyException):
code = 422 # override field
class MyInternalServerErrorException(MyException):
code = 500 # override field
上記のサンプルではアプリケーションで扱う全ての例外の基底クラスとなるMyException
クラスを定義し、HTTPステータスごとに各例外クラスを定義している。
基底クラスMyException
にはアプリケーション固有のフィールドを持たせているが、ここなんでも良いのでアプリケーションに合わせて各自で設定する。
Api#handle_error()
メソッドのオーバーライド
Flask-RESTfulが提供するApi
クラスを継承する。
-
ポイント
-
handle_error()
メソッドでは「自作例外処理クラスの場合とそれ以外の場合を分けて処理する -
make_response()
メソッドを利用し、HTTPレスポンスのペイロードを組み立て、HTTPステータスコードを設定する
-
from flask_restful import Api
from ..dto.exception import MyException
class MyApi(Api):
def handle_error(self, e: MyException):
if isinstance(e, MyException):
return self.make_response({
'faultId': e.fault_id,
'messageId': e.message_id,
'arguments': e.arguments,
}, e.code)
else:
return super().handle_error(e)
上記のサンプルコードでは自作クラスのフィールド値はそのままに、フィールド名をスネークケースからキャメルケースへ変換した上で、HTTPレスポンスボディとして返している。
例外の送出
ResourceクラスやServiceクラスなど、ビジネスロジックの中で自作例外クラスを送出する。
-
ポイント
- エンドポイントを定義する際には
Api
クラスの代わりに、Api
クラスを継承したクラスを使用する - 自作の例外クラスを
raise
することで例外を送出する
- エンドポイントを定義する際には
from flask import Blueprint
from flask_restful import Resource
from . import MyApi
from ..dto.exception import MyNotFoundException
bp = Blueprint('sample', __name__)
api = MyApi(bp)
@api.resource('/api/sample/<int:sample_id>')
class SampleResource(Resource):
def get(self, sample_id: int) -> str:
# do something
raise MyNotFoundException('F0001', 'M0001')
def put(self, sample_id: int) -> bool:
# do something
raise MyUnprocessableEntityException('F0002', 'M0002', ['foo', 'bar'])
以上。