Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is my Flask error handler not being called?

I am trying to create a custom error handler in Flask 1.0.2 and Flask-RESTful 0.3.7, using the guidelines on the "Implementing API Exceptions" page. (Flask-RESTful has its own way of creating custom error messages, but since it doesn't seem to have a way to accept a customized error message at the time of the exception, I am trying to use the vanilla Flask method instead.)

from flask import Flask, jsonify
from flask_restful import Resource, Api

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

#########################################

class MyGenericException(Exception):
    status_code = 500
    def __init__(self, message, status_code=None, payload=None):
        Exception.__init__(self)
        self.message = message
        if status_code is not None:
            self.status_code = status_code
        self.payload = payload
    def to_dict(self):
        rv = dict(self.payload or ())
        rv['message'] = self.message
        return rv

@app.errorhandler(MyGenericException)
def handle_generic_error(error):
    response = jsonify(error.to_dict())
    response.status_code = error.status_code
    return response

#########################################

class TestMe(Resource):
    def get(self):
        raise MyGenericException('A generic error', status_code=501)
api.add_resource(TestMe, '/testme', endpoint='TestMe')

#########################################

if __name__ == '__main__':
    app.run(debug=False)

Calling http://127.0.0.1:5000/testme only returns a generic "500 Internal Server Error" message, however, not a 501 error with my custom error text. It appears that MyGenericException is being raised properly, but Flask seems to ignore it.

[2019-05-08 17:09:18,409] ERROR in app: Exception on /testme [GET]
Traceback (most recent call last):
  File "C:\Users\testuser\Envs\testenv\lib\site-packages\flask\app.py", line 1813, in full_dispatch_request
    rv = self.dispatch_request()
  File "C:\Users\testuser\Envs\testenv\lib\site-packages\flask\app.py", line 1799, in dispatch_request
    return self.view_functions[rule.endpoint](**req.view_args)
  File "C:\Users\testuser\Envs\testenv\lib\site-packages\flask_restful\__init__.py", line 458, in wrapper
    resp = resource(*args, **kwargs)
  File "C:\Users\testuser\Envs\testenv\lib\site-packages\flask\views.py", line 88, in view
    return self.dispatch_request(*args, **kwargs)
  File "C:\Users\testuser\Envs\testenv\lib\site-packages\flask_restful\__init__.py", line 573, in dispatch_request
    resp = meth(*args, **kwargs)
  File "C:/Users/testuser/Documents/PyCharm Projects/TestApp/testapp.py", line 32, in get
    raise MyGenericException('A generic error', status_code=505)
MyGenericException

The @app.errorhandler decorator seems to be properly set for the custom MyGenericException Exception. Why is it not being handled by Flask?

Thanks to anyone who can help.

like image 255
David White Avatar asked May 08 '19 21:05

David White


2 Answers

Referring to both this question, and the docs, the key piece of information I took from both is if your route is a Flask-RESTful one, which yours is, it will be handled by handle_error(), and the only way to prevent or customise this is to implement your own API class, and override handle_error().

like image 113
djnz Avatar answered Sep 21 '22 16:09

djnz


Following up on @dylanj.nz's answer, this vanilla-Flask error handling method, and this example of overriding Flask-RESTful's API , here's the method I settled on. It allows Flask-RESTful to handle HTTPException types of exceptions, but passes everything else through to the default (Flask) handler, where custom error messages can be specified (an entire JSON object of key/value entries, if you want) at the time of the exception.

from flask_restful import Resource, Api as _Api, HTTPException
app = Flask(__name__)

# This new Exception will accept a message, a status code, and a
# payload of other values to be displayed as a JSON object
class FlaskGenericException(Exception):
    status_code = 500   # default unless overridden
    def __init__(self, message, status_code=None, payload=None):
        Exception.__init__(self)
        self.message = message
        if status_code is not None:
            self.status_code = status_code
        self.payload = payload
    def to_dict(self):
        rv = dict(self.payload or ())
        rv['message'] = self.message
        return rv

@app.errorhandler(FlaskGenericException)
def handle_flask_generic_error(error):
    response = jsonify(error.to_dict())
    response.status_code = error.status_code
    return response

# This overridden Flask-RESTful API class will keep Flask-RESTful
# from handling errors other than HTTPException ones.
class Api(_Api):
    def error_router(self, original_handler, e):
        # Override original error_router to only handle HTTPExceptions.
        if self._has_fr_route() and isinstance(e, HTTPException):
            try:
                # Use Flask-RESTful's error handling method
                return self.handle_error(e) 
            except Exception:
                # Fall through to original handler (i.e. Flask)
                pass
        return original_handler(e)

api = Api(app)

class TestMe(Resource):
    def get(self):
        try:
            ldapc = ldap.connection
        except:
            # message = first parameter.  Other parameters come in as "payload"
            raise FlaskGenericException('A generic error', status_code=505, payload={'user': 'John Doe', 'company': 'Foobar Corp.'})

api.add_resource(TestMe, '/testme', endpoint='TestMe')
like image 44
David White Avatar answered Sep 21 '22 16:09

David White