web-dev-qa-db-ja.com

Flask-restful-カスタムエラー処理

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エラー処理に使用したいと思います。

7
JahMyst

によるとドキュメント

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))
6
Anfernee

ブループリントを使用してフラスコを操作しました-安らかな、そして 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)

ところで、フラスコの安らぎは廃止されたようです...

3
Stark

エラー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メソッドによって処理されるアプリ内の任意のファイルから実際に発生する可能性があります。

0
AYUSH SENAPATI

例外を再度キャッチして応答コンテンツを変更したため、アプリケーションロジックに問題が発生したため、壊れているように見えました。今、これは私の場合は機能します。

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  
0
Karolius