Flask: Flask 1.0: global exception handler handles not found now?

Created on 26 Jun 2018  路  12Comments  路  Source: pallets/flask

Hi,

We're looking at upgrading from Flask 0.12 to 1.0, and have run into an issue with routes that don't exist.

We have a custom error handler that we install for Python's built-in Exception class so that we can have a useful error message should any error occur during request execution. With Flask 1.0, we are finding that URLs that don't exist are triggering a call to the custom error handler.

We would like to preserve the behaviour so that non-existent URLs continue to return a 404/Not Found response.

I've tried reading the docs and couldn't see a way, so might have missed something:
http://flask.pocoo.org/docs/1.0/errorhandling/#error-handlers

What is the recommended way of doing this? Or is this a bug?

I wasn't 100% sure, but this could be the same as https://github.com/pallets/flask/issues/2778?


Expected Behavior

With the Flask app running as below, the following command:

curl -XGET http://127.0.0.1:5001/does-not-exist

should return 404/Not Found.

#! python

"""
Simple Flask app with a global exception handler.
"""

from http import HTTPStatus

from flask import Flask


app = Flask(__name__)


@app.errorhandler(Exception)
def global_exception_handler(err):
    """ Global exception handler.
    """
    print("In global_exception_handler", err)
    return "From global_exception_handler\n", HTTPStatus.INTERNAL_SERVER_ERROR


@app.route("/error")
def error():
    """ Simple endpoint that raises an exception
    """
    # simulate a real error that can occur during request processing
    raise AttributeError("These aren't the attributes you are looking for.")


@app.route("/simple")
def simple():
    """ Simple endpoint
    """
    # sanity check
    return "Hello World!\n", HTTPStatus.OK


def main():
    """ The entry point
    """
    app.run(port=5001)


if __name__ == "__main__":
    main()

Actual Behavior

Our global exception handler is invoked and instead of 404/Not found we get what it returns (500/internal server error).

Command line output when running the app with Flask 1.0.2:

dan@dan-desktop:/tmp $ curl -XGET http://127.0.0.1:5001/error
From global_exception_handler
dan@dan-desktop:/tmp $ curl -XGET http://127.0.0.1:5001/does-not-exist
From global_exception_handler
dan@dan-desktop:/tmp $ curl -XGET http://127.0.0.1:5001/simple
Hello World!

Command line output when running the app with Flask 0.12.4:

dan@dan-desktop:/tmp $ curl -XGET http://127.0.0.1:5001/error
From global_exception_handler
dan@dan-desktop:/tmp $ curl -XGET http://127.0.0.1:5001/does-not-exist
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<title>404 Not Found</title>
<h1>Not Found</h1>
<p>The requested URL was not found on the server.  If you entered the URL manually please check your spelling and try again.</p>
dan@dan-desktop:/tmp $ curl -XGET http://127.0.0.1:5001/simple
Hello World!

Environment

  • Python version: Python 3.6.2
  • Flask version: 1.0.2
  • Werkzeug version: 0.14.1
docs

Most helpful comment

Also, it is perfectly valid that you want to have a custom handler for all exceptions so you can show a nicer error to users.

All 12 comments

I think registering an handler with Exception is too hack.Maybe you can create a base custom Exception for your global error handler:

class CustomGlobalException(Exception):
    pass

Maybe you can create a base custom Exception for your global error handler:

Unfortunately that won't help because exceptions from third-party libraries won't subclass this. For instance, in our Flask app we use SQLAlchemy, whose exceptions definitely don't subclass from this and will end up being uncaught.

Also, it is perfectly valid that you want to have a custom handler for all exceptions so you can show a nicer error to users.

I see. So it's an issue about how flask deal with url mapping failure. Is it a bug?

I understand the issue, and I'm not dismissing it.

You're basically doing the equivalent of except Exception, which is not a good design pattern in most cases. Flask fixed error handlers so this actually propagates correctly, and now people are noticing that they didn't write their handler robustly enough for this very generic exception.

I'm not sure what the answer here is. What do you expect Flask to do here while still respecting the MRO of other exceptions? A temporary fix is to return any HTTPException with a non-error code.

@app.errorhandler(Exception)
def handle_generic(e):
    if isinstance(e, HTTPException) and (500 <= e.code < 600):
        return e

    # handle actual error

This isn't what's causing a problem for us, but rather the fact that failures in routing look ups (in the Flask app's routing table) call the global exception handler in Flask 1.0 but didn't before.

I would have to add this:

if isinstance(e, werkzeug.exceptions.NotFound):
        return e

to the exception handler to obtain 404/Not Found status in the above curl examples, rather than @davidism's suggestion.

(Of course, our endpoints might also return 404/Not Found since they rely on databases etc.)

Adding this handler might do what you are looking for:

@app.errorhandler(HTTPException)
def handle_http_exception(e):
    return e

failures in routing look ups (in the Flask app's routing table) call the global exception handler in Flask 1.0 but didn't before

They always did, but it didn't propagate correctly.

Not sure what's different about your example from mine, NotFound is a subclass of HTTPException so yours is just a more specific case.

They always did, but it didn't propagate correctly.

Thanks! This is what I didn't understand properly. :man_facepalming:

What should Flask do here, if anything? Adding some special case so that except Exception isn't actually completely generic doesn't seem correct. Maybe we need to document this better so that users know they need to handle all unhandled exceptions?

Related to #2778.

Maybe we need to document this better so that users know they need to handle all unhandled exceptions?

This is probably the most useful thing to do, and (for people like me) emphasising that the handlers also deal with exceptions outside of view function code.

I just stumbled across this behaviour while trying to upgrade Flask for an app I didn't write.

I found a cleaner workaround by using

@app.errorhandler(http_client.INTERNAL_SERVER_ERROR)
def generic_error_handler(error):
  ...

This passes through any handler that already provided more specific error codes, such as BAD_REQUEST or NOT_FOUND.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

KeyC0de picture KeyC0de  路  4Comments

jab picture jab  路  4Comments

westonplatter picture westonplatter  路  3Comments

rochacbruno picture rochacbruno  路  3Comments

maangulo12 picture maangulo12  路  4Comments