scagbackbone
scagbackbone

Reputation: 791

how to catch all exceptions raised in flask_restful app

I do have simple restful app with Flask-Restful

from flask import Flask
from flask_restful import Api

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

api.add_resource(ContactList, "/contacts")


if __name__ == '__main__':
    from object_SQLAlchemy import db
    db.init_app(app)
    app.run(port=5000)


class Contact(Resource):
    parser = reqparse.RequestParser()
    parser.add_argument(
        'contact_no',
        type=str,
        required=True,
        help="This field cannot be left blank"
    )

    @throttling.Throttle("10/m", strategy=2)
    def get(self, name):
        contact = Contacts.findbyname(name)
        if contact:
            return contact.json()
        return {"message": "Contact does not exist."}, 404

'get' method is decorated with my implementation of throttling (https://github.com/scgbckbone/RESTAPI/blob/master/resources/utils/throttling.py). What is important is that the throttling decorator raises exceptions on some occasions - most importantly when limit is reached. I would like to be able to catch that exception and return some reasonable json message.

But none of following works:

from ..app_alchemy import api, app


@api.errorhandler(Exception)
def handle_error(e):
    return {"error": str(e)}


@app.errorhandler(500)
def handle_error_app(e):
    return {"error": str(e.args[0])}


@app.handle_exception(Exception)
def handle_it_app(e):
    return {"error": str(e.args[0])}


@api.handle_exception(Exception)
def handle_it(e):
   return {"error": str(e.args[0])}

I'm still getting back default message

{"message": "Internal Server Error"}

Do I use errorhandlers correctly, or is the issue related to the use of decorator? I truly have no idea.

Upvotes: 2

Views: 4502

Answers (3)

simon
simon

Reputation: 31

I override the flask_restulf.Api and override the handle_error() and error_router() inside. The APIException is a subclass of the HttpException, and the UnknownException is a subclass of the APIException. There is no logger in the code, since sentry does not work properly with the flask_restful in my code, the same problem was presented in https://github.com/flask-restful/flask-restful/issues/90.

class Api_modified(Api):

def error_router(self, original_handler, e):
    """This function decides whether the error occured in a flask-restful
    endpoint or not. If it happened in a flask-restful endpoint, our
    handler will be dispatched. If it happened in an unrelated view, a UnknownException
    will be returned.
    In the event that the error occurred in a flask-restful endpoint but
    the local handler can't resolve the situation, the router will return a
    UnknownException.

    :param original_handler: the original Flask error handler for the app
    :type original_handler: function
    :param e: the exception raised while handling the request
    :type e: Exception

    """
    if self._has_fr_route():
        try:
            return self.handle_error(e)
        except Exception:
            pass
    else:
        if isinstance(e,APIException):
            return e.get_response()
        else:
            return UnknownException("未知原因错误,具体原因请查询日志").get_response()

def handle_error(self, e):
    """Error handler for the API transforms a raised exception into a Flask
    response, with the appropriate HTTP and body.The status code is always 200, and
    the message and result in the body varies.

    :param e: the raised Exception object
    :type e: Exception

    """
    got_request_exception.send(current_app._get_current_object(), exception=e)

    if not isinstance(e, HTTPException) and current_app.propagate_exceptions:
        exc_type, exc_value, tb = sys.exc_info()
        if exc_value is e:
            raise UnknownException("未知原因错误,具体原因请查询日志")
        else:
            raise UnknownException("未知原因错误,具体原因请查询日志")

    headers = Headers()
    if isinstance(e, HTTPException):
        if e.response is not None:
            # If HTTPException is initialized with a response, then return e.get_response().
            # This prevents specified error response from being overridden.
            # eg. HTTPException(response=Response("Hello World"))
            resp = e.get_response()
            return resp

        code = e.code
        default_data = {
            'message': getattr(e, 'description', http_status_message(code))
        }
        headers = e.get_response().headers
    else:
        resp = UnknownException("未知原因错误,具体原因请查询日志").get_response()
        return resp

    # Werkzeug exceptions generate a content-length header which is added
    # to the response in addition to the actual content-length header
    # https://github.com/flask-restful/flask-restful/issues/534
    remove_headers = ('Content-Length',)

    for header in remove_headers:
        headers.pop(header, None)

    data = getattr(e, 'data', default_data)

    if code and code >= 500:
        exc_info = sys.exc_info()
        if exc_info[1] is None:
            exc_info = None
        current_app.log_exception(exc_info)

    error_cls_name = type(e).__name__
    if error_cls_name in self.errors:
        custom_data = self.errors.get(error_cls_name, {})
        code = custom_data.get('status', 500)
        data.update(custom_data)

    if code == 406 and self.default_mediatype is None:
        # if we are handling NotAcceptable (406), make sure that
        # make_response uses a representation we support as the
        # default mediatype (so that make_response doesn't throw
        # another NotAcceptable error).
        supported_mediatypes = list(self.representations.keys())
        fallback_mediatype = supported_mediatypes[0] if supported_mediatypes else "text/plain"
        resp = self.make_response(
            data,
            code,
            headers,
            fallback_mediatype = fallback_mediatype
        )
    else:
        resp = self.make_response(data, code, headers)

    if code == 401:
        resp = self.unauthorized(resp)
    return resp

Upvotes: 0

Sergey Shubin
Sergey Shubin

Reputation: 3257

There is a Flask-Restful built-in tool for handling exceptions, you can pass a dictionary of exception classes and response fields to Api constructor:

api = Api(app, errors={
    'Exception': {
        'status': 400,
        'message': 'bad_request',
        'some_description': 'Something wrong with request'
    }
})

Status is 500 by default, all other field are just turned to JSON and sent in response.

There is a major downside: you cannot use exception text as error message. There is an open issue for it.

Upvotes: 3

Sidharth Shah
Sidharth Shah

Reputation: 1609

Sentry is a great tool for catching exceptions across different platforms and frameworks {Including Python, Django and Flask}. This example give pointers on you can integrate it with your Flask application.

I've used it in production, the feature I liked the most is that it captures context of the error, including Operating System, Browser Version etc along with other information.

Upvotes: 1

Related Questions