Global error handler for any exception
Asked Answered
V

7

58

Is there a way to add a global catch-all error handler in which I can change the response to a generic JSON response?

I can't use the got_request_exception signal, as it is not allowed to modify the response (http://flask.pocoo.org/docs/0.10/signals/).

In contrast all signal handlers are executed in undefined order and do not modify any data.

I would prefer to not wrap the app.handle_exception function as that feels like internal API. I guess I'm after something like:

 @app.errorhandler()
 def handle_global_error(e):
     return "Global error"

Note the errorhandler does not take any parameters, meaning it would catch all exceptions/status codes which does not have a specific error handler attached to them. I know I can use errorhandler(500) or errorhandler(Exception) to catch exceptions, but if I do abort(409) for example, it will still return a HTML response.

Vellavelleity answered 29/3, 2015 at 16:58 Comment(0)
P
74

You can use @app.errorhandler(Exception):

Demo (the HTTPException check ensures that the status code is preserved):

from flask import Flask, abort, jsonify
from werkzeug.exceptions import HTTPException

app = Flask('test')

@app.errorhandler(Exception)
def handle_error(e):
    code = 500
    if isinstance(e, HTTPException):
        code = e.code
    return jsonify(error=str(e)), code

@app.route('/')
def index():
    abort(409)

app.run(port=1234)

Output:

$ http get http://127.0.0.1:1234/
HTTP/1.0 409 CONFLICT
Content-Length: 31
Content-Type: application/json
Date: Sun, 29 Mar 2015 17:06:54 GMT
Server: Werkzeug/0.10.1 Python/3.4.3

{
    "error": "409: Conflict"
}

$ http get http://127.0.0.1:1234/notfound
HTTP/1.0 404 NOT FOUND
Content-Length: 32
Content-Type: application/json
Date: Sun, 29 Mar 2015 17:06:58 GMT
Server: Werkzeug/0.10.1 Python/3.4.3

{
    "error": "404: Not Found"
}

If you also want to override the default HTML exceptions from Flask (so that they also return JSON), add the following before app.run:

from werkzeug.exceptions import default_exceptions
for ex in default_exceptions:
    app.register_error_handler(ex, handle_error)

For older Flask versions (<=0.10.1, i.e. any non-git/master version at the moment), add the following code to your application to register the HTTP errors explicitly:

from werkzeug import HTTP_STATUS_CODES
for code in HTTP_STATUS_CODES:
    app.register_error_handler(code, handle_error)
Population answered 29/3, 2015 at 17:5 Comment(13)
This is so strange. I'm copypasting your code into a Python shell with Flask 0.10.1, and I still get HTML errors when requesting those endpoints using curl. What version of Flask are you using?Vellavelleity
Ah, apparently something changed between 0.10.1 and the version from git I was testing it with.Population
github.com/mitsuhiko/flask/blob/0.10.1/flask/app.py#L1086 - apparently it only uses the exception's code in older versions.Population
Isn't that identical to master though? github.com/mitsuhiko/flask/blob/master/flask/app.py#L1139Vellavelleity
github.com/mitsuhiko/flask/blob/0.10.1/flask/app.py#L1352 github.com/mitsuhiko/flask/blob/master/flask/app.py#L1429Population
Thanks, marking this is as answered as it will work in the next release of Flask. Maybe you should edit your answer saying it doesn't work in the current release so people finding this question won't be confused?Vellavelleity
Thank you! This was the only answer that worked for me. Can you please make the part at the bottom (i.e, iterating the default codes) more obvious in the answer? errorhandler is well documented, attaching it to all default HttpException subclasses is not.Borborygmus
This is not working in 0.11.1. It does not catch 405 errors. I have to explicitly write @app.errorhandler(405)Snob
Indeed Exception doesn't work anymore. This is such a step backwards. Why have they changed it?Inherence
@Inherence I think what's happening is that Flask comes with default error handlers for the most common HTTP errors; so if your application raises one of those HTTP errors the 'most specific' error handler is chosen (instead of Exception which is very generic). I was able to work around this by replacing all of the default error handlers with a JSON one (see edit)Pharr
The documentation for Flask now suggests an alternate approach: flask.palletsprojects.com/en/1.1.x/errorhandling/…Mizzen
Let say I have sqlalche.me error then error.code will let say return f405 that this code will not work in python 3 because status code only takes Decimal arumentsPontine
To find out which route triggered the exception you can do from flask import request and then get vale from request.pathSpanner
F
14

This is Flask 0.12 compatible, and a very good solution to the problem (it allows one to render errors in JSON or any other format)

from functools import wraps
from flask import Flask, redirect, jsonify
app = Flask(__name__)

def get_http_exception_handler(app):
    """Overrides the default http exception handler to return JSON."""
    handle_http_exception = app.handle_http_exception
    @wraps(handle_http_exception)
    def ret_val(exception):
        exc = handle_http_exception(exception)    
        return jsonify({'code':exc.code, 'message':exc.description}), exc.code
    return ret_val

# Override the HTTP exception handler.
app.handle_http_exception = get_http_exception_handler(app)

https://github.com/pallets/flask/issues/671#issuecomment-12746738

Fardel answered 20/5, 2017 at 8:32 Comment(0)
M
9

Far from elegant, but the following works for tying all subclasses of HTTPException to a single error handler:

from flask import jsonify
from werkzeug.exceptions import HTTPException

def handle_error(error):
    code = 500
    if isinstance(error, HTTPException):
        code = error.code
    return jsonify(error='error', code=code)

for cls in HTTPException.__subclasses__():
    app.register_error_handler(cls, handle_error)
Mischiefmaker answered 14/1, 2017 at 21:55 Comment(2)
This code works perfectly, but there is one downside, there can be the case that an http status 200 is returned with a response json of code 500 and error "X".Bani
I corrected this by doing changing the return line into json.dumps({'success': False, 'error': str(error)}), 500Bani
T
3

It is possible to register error handlers for very generic base classes such as HTTPException or even Exception. However, be aware that these will catch more than you might expect.

For example, an error handler for HTTPException might be useful for turning the default HTML errors pages into JSON. However, this handler will trigger for things you don’t cause directly, such as 404 and 405 errors during routing. Be sure to craft your handler carefully so you don’t lose information about the HTTP error.

from flask import Flask, abort, jsonify, json
from werkzeug.exceptions import HTTPException


app = Flask('test')
app.config['JSON_SORT_KEYS'] = False


@app.errorhandler(HTTPException)
def handle_exception(e):
    """Return JSON instead of HTML for HTTP errors."""
    # start with the correct headers and status code from the error
    response = e.get_response()
    # replace the body with JSON
    response.data = json.dumps({
        "error": {
            "code": e.code,
            "name": e.name,
            "description": e.description,
        }
    })
    print(response.data) 
    response.content_type = "application/json"
    return response


@app.route('/')
def index():
    abort(409)


@app.route('/aloha')
def aloha():
    abort(400, "I'm not in the mood to talk!")


app.run(port=1234)

output:

enter image description here

enter image description here

enter image description here

An error handler for Exception might seem useful for changing how all errors, even unhandled ones, are presented to the user. However, this is similar to doing except Exception: in Python, it will capture all otherwise unhandled errors, including all HTTP status codes.

In most cases it will be safer to register handlers for more specific exceptions. Since HTTPException instances are valid WSGI responses, you could also pass them through directly.

from werkzeug.exceptions import HTTPException

@app.errorhandler(Exception)
def handle_exception(e):
    # pass through HTTP errors
    if isinstance(e, HTTPException):
        return e

    # now you're handling non-HTTP exceptions only
    return render_template("500_generic.html", e=e), 500

Error handlers still respect the exception class hierarchy. If you register handlers for both HTTPException and Exception, the Exception handler will not handle HTTPException subclasses because it the HTTPException handler is more specific.

Typewriting answered 7/12, 2021 at 12:6 Comment(0)
D
2

A cleaner way to implement this in Flask >=0.12 would be to explicitly register the handler for every Werkzeug exception:

from flask import jsonify
from werkzeug.exceptions import HTTPException, default_exceptions

app = Flask('test')

def handle_error(error):
    code = 500
    if isinstance(error, HTTPException):
        code = error.code
    return jsonify(error='error', code=code)

for exc in default_exceptions:
    app.register_error_handler(exc, handle_error)
Deontology answered 6/8, 2018 at 9:49 Comment(0)
F
0

If the Exceptions doesn't work, you may try app.register_error_handler (or use app.errorhandler in a non-decorator way)

Source: https://github.com/pallets/flask/issues/1837

Faradism answered 30/3, 2018 at 7:26 Comment(0)
T
0

Based on Plain (non-HTML) error pages in REST api

I wanted to return json without changing any of my code at all, so I just added the following on the top of my code

@app.errorhandler(500)
def error_500(exception):
    return jsonify({"error": str(exception)}), 500, {'Content-Type': 'application/json'}

@app.errorhandler(400)
def error_400(exception):
    return jsonify({"error": str(exception)}), 400, {'Content-Type': 'application/json'}
Truthfunction answered 31/3, 2019 at 0:46 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.