abort and error handlers

Flask · Essentials · 8 min

Users and API clients deserve clear, consistent errors instead of default pages or stack traces.

abort(status) raises an HTTPException that immediately ends the request with that status. Use it for guard clauses: abort(404) when a record doesn't exist, abort(403) when the user isn't allowed. You can pass a description: abort(400, description="missing email").

Without handlers, Flask sends Werkzeug's plain HTML error pages. Register your own with @app.errorhandler, keyed by status code or exception class:

@app.errorhandler(404)
def not_found(e):
    return render_template("404.html"), 404

Return the status code explicitly; the handler's response is otherwise a 200.

For a JSON API, one handler for HTTPException covers every HTTP error: read e.code, e.name and e.description and return them as JSON.

Unhandled exceptions in your code become 500 Internal Server Error. In debug mode you see the traceback; in production the user sees the 500 page (customize it with @app.errorhandler(500)) and the exception is logged to app.logger. You can also register handlers for your own exception classes, such as NotFoundError raised by a service layer, and translate them into HTTP responses in one place.

Handlers registered on the app apply everywhere. Blueprints can register their own, which take precedence for errors raised inside the blueprint's views, though 404 and 405 come from routing and are only handled by app-level handlers.

Example

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

app = Flask(__name__)
BOOKS = {1: "Dune"}

@app.get("/books/<int:bid>")
def book(bid):
    if bid not in BOOKS:
        abort(404, description=f"no book {bid}")
    return {"title": BOOKS[bid]}

@app.get("/boom")
def boom():
    return 1 / 0

@app.errorhandler(HTTPException)
def http_error(e):
    return {"error": e.name, "detail": e.description}, e.code
@app.errorhandler(ZeroDivisionError)
def math_error(e):
    return {"error": "calculation failed"}, 500
c = app.test_client()
print(c.get("/books/9").json)        # {'detail': 'no book 9', 'error': 'Not Found'}
print(c.get("/boom").status_code)    # 500

Remember

  • abort(code) ends a request immediately with an HTTP error
  • @app.errorhandler(code or exception class) customizes the response
  • Handlers must return the status code themselves

Watch out for

  • Forgetting the status in an error handler, so the custom 404 page is sent as 200.

Check yourself

  1. An error handler for 404 returns only render_template("404.html"). What status does the client get?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: flask.palletsprojects.com/en/stable/errorhandling/