Flask-restful - 自定义错误处理

13

我想为Flask-restful API定义自定义错误处理。

文档中建议的方法在这里,是按照以下步骤进行:

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的错误处理方式。


文档网址似乎已更改为 https://flask-restful.readthedocs.io/en/latest/extending.html#define-custom-error-messages - David Bridgeland
4个回答

9
根据文档,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))

1
如果你想在handle_error方法中返回一个JSON格式的错误,可以使用return jsonify({"message":"your error message"}), e.code - OzzyTheGiant

8

我不是将错误字典附加到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
        )

与视图中一样,应用程序中的任何文件实际上都可以引发异常,这些异常将由ExtendedAPI handle_error方法处理。


1
在Python核心异常的情况下,如果您想保留错误堆栈跟踪,请使用logging库中的logging.critical(err, exc_info=True)。 - Karolius
@Karolius,是的,在handle_error函数内部,您实际上需要记录该错误,而不是使用打印语句。 - AYUSH SENAPATI
1
迄今为止最佳答案。非常感谢@AYUSHSENAPATI!! - Jesse Reich
@AYUSHSENAPATI 你节省了我数小时的研究时间。非常感谢。谢谢。 - Shreyas Chorge

5
我使用Blueprint与flask-restful一起工作,但发现@billmccord和@cedmt在issue提供的解决方案对这种情况不起作用,因为Blueprint没有handle_exceptionhandle_user_exception函数。
我的解决方法是增强Apihandle_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)

顺便提一句,似乎Flask-RESTful已经被弃用了...


你认为为什么Flask-RESTful似乎已经被弃用了? - Chris2048
@Chris2048 或许我用词不当,但是这个代码库的更新频率非常低。在这里,有一个2014年版本1.0.0的问题,但长时间没有任何活动。 - Stark
1
@Stark 废弃肯定是用错了词,它可能不像以前那样得到维护,但真的必须这样吗?许多存储库不经常更新,但被广泛使用。 - jscul
1
@Stark 没错!如果一个仓库不断更新,这可能是好事也可能是坏事。坏的情况意味着该仓库存在许多需要解决的问题,但好的情况是维护者正在积极改进它。如果它没有被更新,那么要么意味着该仓库已经非常完美,要么就是维护者停止了对它的关注,这取决于软件包的情况。 - OzzyTheGiant

1

我也遇到了同样的问题,后来我扩展了flask-restful.Api之后发现你实际上不需要扩展flask-restful.Api

你可以通过继承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))

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接