簡體   English   中英

Flask-restful - 自定義錯誤處理

[英]Flask-restful - Custom error handling

我想為 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 錯誤處理。

根據文檔

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

我沒有將錯誤字典附加到 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 方法處理。

我用的藍圖將工作與砂箱平安,我發現所提供的解決方案@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.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))

好吧,我的應用程序邏輯中存在一些問題,因為再次捕獲到異常並更改了響應內容,這就是為什么它看起來很糟糕的原因。 現在這對我來說有效。

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