简体   繁体   English

Flask-restful - 自定义错误处理

[英]Flask-restful - Custom error handling

I want to define custom error handling for a Flask-restful API.我想为 Flask-restful API 定义自定义错误处理。

The suggested approach in the documentation here is to do the following: 此处文档中建议的方法是执行以下操作:

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)

Now I find this format pretty attractive but I need to specify more parameters when some exception happens.现在我发现这种格式非常有吸引力,但是当发生某些异常时我需要指定更多参数。 For example, when encountering ResourceDoesNotExist , I want to specify what id does not exist.比如遇到ResourceDoesNotExist ,想指定什么id不存在。

Currently, I'm doing the following:目前,我正在做以下事情:

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

When called with an id that doesn't exist MyResource will return the following JSON:当使用不存在的 ID 调用时, MyResource将返回以下 JSON:

{'message': 'ResourceDoesNotExist: Resource MyModel 5 not found'}

This works fine but I'd like to use to Flask-restful error handling instead.这工作正常,但我想改为使用 Flask-restful 错误处理。

According to the docs根据文档

Flask-RESTful will call the handle_error() function on any 400 or 500 error that happens on a Flask-RESTful route, and leave other routes alone. Flask-RESTful 将在 Flask-RESTful 路由上发生的任何 400 或 500 错误上调用 handle_error() 函数,而不管其他路由。

You can leverage this to implement the required functionality.您可以利用它来实现所需的功能。 The only downside is having to create a custom Api.唯一的缺点是必须创建自定义 Api。

class CustomApi(flask_restful.Api):

    def handle_error(self, e):
        flask_restful.abort(e.code, str(e))

If you keep your defined exceptions, when an exception occurs, you'll get the same behaviour as如果你保留你定义的异常,当异常发生时,你会得到相同的行为

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

Instead of attaching errors dict to Api, I am overriding handle_error method of Api class to handle exceptions of my application.我没有将错误字典附加到 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')

Custom exceptions can be kept in separate file, like:自定义异常可以保存在单独的文件中,例如:

# 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"""

And in the views exceptions can be raised like:并且在视图中可以引发异常,例如:

# 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
        )

Like in view, exceptions can actually be raised from any file in the app which will be handled by the ExtendedAPI handle_error method.与视图一样,实际上可以从应用程序中的任何文件引发异常,这些文件将由 ExtendedAPI handle_error 方法处理。

I've used the Blueprint to work with the flask-restful, and I've found that the solution @billmccord and @cedmt provided on the issue not worked for this case, because the Blueprint don't have the handle_exception and handle_user_exception functions.我用的蓝图将工作与砂箱平安,我发现所提供的解决方案@billmccord和@cedmt 问题没有工作对于这种情况,因为蓝图没有handle_exceptionhandle_user_exception功能。

My workaround is that enhance the function handle_error of the Api , if the "error handler" of the "Exception" have been registered, just raise it, the "error handler" registered on app will deal with that Exception, or the Exception will be passed to the "flask-restful" controlled "custom error handler".我的解决方法是增强Api的函数handle_error ,如果“异常”的“错误处理程序”已被注册,只需提高它,应用程序上注册的“错误处理程序”将处理该异常,否则异常将是传递给“flask-restful”控制的“自定义错误处理程序”。

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)

BTW, seems the flask-restful had been deprecated...顺便说一句,似乎flask-restful已被弃用......

I faced the same issue too and after extending flask-restful.Api I realized that you really don't need to extend the flask-restful.Api我也遇到了同样的问题,在扩展 flask-restful.Api 之后我意识到你真的不需要扩展 flask-restful.Api

you can easily do this by inheriting from werkzeug.exceptions.HTTPException and solve this issue您可以通过继承 werkzeug.exceptions.HTTPException 轻松做到这一点并解决这个问题

app = Flask(__name__)
api = flask_restful.Api(app)

from werkzeug.exceptions import HTTPException

class APIException(HTTPException):
    def __init__(self, code, message):
        super().__init__()
        self.code = code
        self.description = 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().__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))

Well i had some issue in my application logic, because of catching exceptions again and changing response content, that's why it seemed broken. 好吧,我的应用程序逻辑中存在一些问题,因为再次捕获到异常并更改了响应内容,这就是为什么它看起来很糟糕的原因。 Now this works in my case. 现在这对我来说有效。

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  

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM