Flask-restfulAPIのカスタムエラー処理を定義したいと思います。
ドキュメントで推奨されているアプローチ ここ は、次のことを行うことです。
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)
この形式は非常に魅力的ですが、何らかの例外が発生した場合は、さらにパラメーターを指定する必要があります。たとえば、ResourceDoesNotExist
に遭遇したときに、存在しないid
を指定したいと思います。
現在、私は次のことを行っています。
app = Flask(__name__)
api = flask_restful.Api(app)
class APIException(Exception):
def __init__(self, code, message):
self._code = code
self._message = message
@property
def code(self):
return self._code
@property
def message(self):
return self._message
def __str__(self):
return self.__class__.__name__ + ': ' + self.message
class ResourceDoesNotExist(APIException):
"""Custom exception when resource is not found."""
def __init__(self, model_name, id):
message = 'Resource {} {} not found'.format(model_name.title(), id)
super(ResourceNotFound, self).__init__(404, message)
class MyResource(Resource):
def get(self, id):
try:
model = MyModel.get(id)
if not model:
raise ResourceNotFound(MyModel.__name__, id)
except APIException as e:
abort(e.code, str(e))
存在しないIDで呼び出されると、MyResource
は次のJSONを返します。
{'message': 'ResourceDoesNotExist: Resource MyModel 5 not found'}
これは問題なく動作しますが、代わりにFlask-restfulエラー処理に使用したいと思います。
Flask-RESTfulは、Flask-RESTfulルートで発生する400または500エラーに対してhandle_error()関数を呼び出し、他のルートはそのままにします。
これを利用して、必要な機能を実装できます。唯一の欠点は、カスタムApiを作成する必要があることです。
class CustomApi(flask_restful.Api):
def handle_error(self, e):
flask_restful.abort(e.code, str(e))
定義した例外を保持すると、例外が発生したときに、と同じ動作が得られます。
class MyResource(Resource):
def get(self, id):
try:
model = MyModel.get(id)
if not model:
raise ResourceNotFound(MyModel.__name__, id)
except APIException as e:
abort(e.code, str(e))
ブループリントを使用してフラスコを操作しました-安らかな、そして issue で提供されたソリューション@billmccordと@cedmtは、ブループリントが機能しないため、この場合は機能しないことがわかりましたhandle_exception
およびhandle_user_exception
関数があります。
私の回避策は、Api
の関数handle_error
を拡張することです。「例外」の「エラーハンドラー」が登録されている場合は、それを上げるだけで、アプリに登録されている「エラーハンドラー」が処理されます。その例外がある場合、または例外は「フラスコレストフル」制御の「カスタムエラーハンドラー」に渡されます。
class ImprovedApi(Api):
def handle_error(self, e):
for val in current_app.error_handler_spec.values():
for handler in val.values():
registered_error_handlers = list(filter(lambda x: isinstance(e, x), handler.keys()))
if len(registered_error_handlers) > 0:
raise e
return super().handle_error(e)
api_entry = ImprovedApi(api_entry_bp)
ところで、フラスコの安らぎは廃止されたようです...
エラーdictをApiに添付する代わりに、アプリケーションの例外を処理するためにApiクラスのhandle_errorメソッドをオーバーライドしています。
# File: app.py
# ------------
from flask import Blueprint, jsonify
from flask_restful import Api
from werkzeug.http import HTTP_STATUS_CODES
from werkzeug.exceptions import HTTPException
from view import SomeView
class ExtendedAPI(Api):
"""This class overrides 'handle_error' method of 'Api' class ,
to extend global exception handing functionality of 'flask-restful'.
"""
def handle_error(self, err):
"""It helps preventing writing unnecessary
try/except block though out the application
"""
print(err) # log every exception raised in the application
# Handle HTTPExceptions
if isinstance(err, HTTPException):
return jsonify({
'message': getattr(
err, 'description', HTTP_STATUS_CODES.get(err.code, '')
)
}), err.code
# If msg attribute is not set,
# consider it as Python core exception and
# hide sensitive error info from end user
if not getattr(err, 'message', None):
return jsonify({
'message': 'Server has encountered some error'
}), 500
# Handle application specific custom exceptions
return jsonify(**err.kwargs), err.http_status_code
api_bp = Blueprint('api', __name__)
api = ExtendedAPI(api_bp)
# Routes
api.add_resource(SomeView, '/some_list')
カスタム例外は、次のように別のファイルに保存できます。
# File: errors.py
# ---------------
class Error(Exception):
"""Base class for other exceptions"""
def __init__(self, http_status_code:int, *args, **kwargs):
# If the key `msg` is provided, provide the msg string
# to Exception class in order to display
# the msg while raising the exception
self.http_status_code = http_status_code
self.kwargs = kwargs
msg = kwargs.get('msg', kwargs.get('message'))
if msg:
args = (msg,)
super().__init__(args)
self.args = list(args)
for key in kwargs.keys():
setattr(self, key, kwargs[key])
class ValidationError(Error):
"""Should be raised in case of custom validations"""
また、ビューでは、次のように例外を発生させることができます。
# File: view.py
# -------------
from flask_restful import Resource
from errors import ValidationError as VE
class SomeView(Resource):
def get(self):
raise VE(
400, # Http Status code
msg='some error message', code=SomeCode
)
ビューと同様に、例外は、ExtendedAPIhandle_errorメソッドによって処理されるアプリ内の任意のファイルから実際に発生する可能性があります。
例外を再度キャッチして応答コンテンツを変更したため、アプリケーションロジックに問題が発生したため、壊れているように見えました。今、これは私の場合は機能します。
from flask import jsonify
class ApiError(Exception):
def __init__(self, message, payload=None, status_code=400):
Exception.__init__(self)
self.message = message
self.status_code = status_code
self.payload = payload or ()
# loggin etc.
def get_response(self):
ret = dict(self.payload)
ret['message'] = self.message
return jsonify(ret), self.status_code
def create_app():
app = Flask('foo')
# initialising libs. setting up config
api_bp = Blueprint('foo', __name__)
api = Api(api_bp)
@app.errorhandler(ApiError)
def handle_api_error(error):
return error.get_response()
app.register_blueprint(api_bp, url_prefix='/api')
# add resources
return app