【问题标题】:Flask-restful - Custom error handlingFlask-restful - 自定义错误处理
【发布时间】:2017-04-30 04:26:34
【问题描述】:

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

文档here 中建议的方法是执行以下操作:

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

【问题讨论】:

标签: python flask flask-restful


【解决方案1】:

根据the docs

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

【讨论】:

  • 如果要在handle_error方法中返回json语法错误,请使用return jsonify({"message":"your error message"}), e.code
【解决方案2】:

我没有将错误字典附加到 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 方法处理。

【讨论】:

  • 在 Python 核心异常的情况下 - 如果您想保留错误堆栈跟踪,只需使用 logging lib 中的 logging.critical(err, exc_info=True)
  • @Karolius 是的,在 handle_error 函数中,代替 print 语句,您实际上需要记录该错误。
  • 迄今为止最好的答案。非常感谢@AYUSHSENAPATI!
【解决方案3】:

我已经使用蓝图来处理flask-restful,我发现issue 上提供的解决方案@billmccord 和@cedmt 不适用于这种情况,因为蓝图没有handle_exceptionhandle_user_exception 函数。

我的解决方法是增强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)

顺便说一句,flask-restful 似乎已被弃用...

【讨论】:

  • 为什么你认为 flask-restful 已经被弃用了?
  • @Chris2048 可能是我用错了,repository的更新频率很低,还有here,这是2014年开的1.0.0版本的issue,但是没有长期活动。
  • @Stark deprecated 肯定是一个错误的词,它可能不会像以前那样维护,但它真的必须如此吗?大量的存储库不经常更新,但被广泛使用。
  • @Stark 完全正确!如果一个 repo 不断更新,它可能是好事也可能是坏事,坏的意思是 repo 有很多问题需要解决,但好的是维护者正在积极改进它。如果它没有被更新,这要么意味着回购接近完美,要么维护者停止处理它,这取决于包
猜你喜欢
  • 1970-01-01
  • 2015-03-10
  • 2019-09-22
  • 2021-10-27
  • 2014-10-26
  • 2014-03-05
  • 2013-02-12
  • 2011-01-29
  • 2010-12-07
相关资源
最近更新 更多