6

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.

David White
  • 1,763
  • 2
  • 16
  • 27
  • Possible duplicate of https://stackoverflow.com/questions/41149409/flask-restful-custom-error-handling? – djnz May 08 '19 at 22:40
  • 1
    @dylanj.nz, nope, that's a discussion of the Flask-RESTful solution, the one I'm trying to avoid in favor of a pure Flask error handler.. – David White May 09 '19 at 01:30
  • The key piece of information I took from both that question and the docs 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()`. – djnz May 09 '19 at 02:03
  • Hmmm. I had interpreted the [docs](https://flask-restful.readthedocs.io/en/0.3.6/extending.html) to mean that Flask-RESTful would intercept 400 and 500 errors, but not (for example) the 505 error I tried to raise in my exception above. But maybe it's intercepting more than I think - guess I'll have to experiment. – David White May 09 '19 at 03:55
  • Well, phooey. Looks like Flask-RESTful is indeed intercepting **all** 5xx errors, even if no custom handlers are defined. Bummer. @dylanj.nz, if you want to put your comment into an answer, I'll accept it. – David White May 09 '19 at 04:34
  • Pity! but not too painful implementing your own API class and overriding, I hope. – djnz May 09 '19 at 21:07

2 Answers2

7

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

djnz
  • 2,021
  • 1
  • 13
  • 13
  • Some more info here how you might implement https://stackoverflow.com/questions/41149409/flask-restful-custom-error-handling – jcroll Jul 19 '21 at 13:19
4

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')
David White
  • 1,763
  • 2
  • 16
  • 27