Handling Application Errors — Flask Documentation (2.2.x) (2024)

Applications fail, servers fail. Sooner or later you will see an exceptionin production. Even if your code is 100% correct, you will still seeexceptions from time to time. Why? Because everything else involved willfail. Here are some situations where perfectly fine code can lead to servererrors:

  • the client terminated the request early and the application was stillreading from the incoming data

  • the database server was overloaded and could not handle the query

  • a filesystem is full

  • a harddrive crashed

  • a backend server overloaded

  • a programming error in a library you are using

  • network connection of the server to another system failed

And that’s just a small sample of issues you could be facing. So how do wedeal with that sort of problem? By default if your application runs inproduction mode, and an exception is raised Flask will display a very simplepage for you and log the exception to the logger.

But there is more you can do, and we will cover some better setups to dealwith errors including custom exceptions and 3rd party tools.

Error Logging Tools

Sending error mails, even if just for critical ones, can becomeoverwhelming if enough users are hitting the error and log files aretypically never looked at. This is why we recommend using Sentry for dealing with application errors. It’savailable as a source-available project on GitHub and is also available as a hosted version which you can try for free. Sentryaggregates duplicate errors, captures the full stack trace and localvariables for debugging, and sends you mails based on new errors orfrequency thresholds.

To use Sentry you need to install the sentry-sdk client with extraflask dependencies.

$ pip install sentry-sdk[flask]

And then add this to your Flask app:

import sentry_sdkfrom sentry_sdk.integrations.flask import FlaskIntegrationsentry_sdk.init('YOUR_DSN_HERE', integrations=[FlaskIntegration()])

The YOUR_DSN_HERE value needs to be replaced with the DSN value youget from your Sentry installation.

After installation, failures leading to an Internal Server Errorare automatically reported to Sentry and from there you canreceive error notifications.

See also:

Error Handlers

When an error occurs in Flask, an appropriate HTTP status code will bereturned. 400-499 indicate errors with the client’s request data, orabout the data requested. 500-599 indicate errors with the server orapplication itself.

You might want to show custom error pages to the user when an error occurs.This can be done by registering error handlers.

An error handler is a function that returns a response when a type of error israised, similar to how a view is a function that returns a response when arequest URL is matched. It is passed the instance of the error being handled,which is most likely a HTTPException.

The status code of the response will not be set to the handler’s code. Makesure to provide the appropriate HTTP status code when returning a response froma handler.

Registering

Register handlers by decorating a function witherrorhandler(). Or useregister_error_handler() to register the function later.Remember to set the error code when returning the response.

@app.errorhandler(werkzeug.exceptions.BadRequest)def handle_bad_request(e): return 'bad request!', 400# or, without the decoratorapp.register_error_handler(400, handle_bad_request)

werkzeug.exceptions.HTTPException subclasses likeBadRequest and their HTTP codes are interchangeablewhen registering handlers. (BadRequest.code == 400)

Non-standard HTTP codes cannot be registered by code because they are not knownby Werkzeug. Instead, define a subclass ofHTTPException with the appropriate code andregister and raise that exception class.

class InsufficientStorage(werkzeug.exceptions.HTTPException): code = 507 description = 'Not enough storage space.'app.register_error_handler(InsufficientStorage, handle_507)raise InsufficientStorage()

Handlers can be registered for any exception class, not justHTTPException subclasses or HTTP statuscodes. Handlers can be registered for a specific class, or for all subclassesof a parent class.

Handling

When building a Flask application you will run into exceptions. If some partof your code breaks while handling a request (and you have no error handlersregistered), a “500 Internal Server Error”(InternalServerError) will be returned by default.Similarly, “404 Not Found”(NotFound) error will occur if a request is sent to an unregistered route.If a route receives an unallowed request method, a “405 Method Not Allowed”(MethodNotAllowed) will be raised. These are allsubclasses of HTTPException and are provided bydefault in Flask.

Flask gives you the ability to raise any HTTP exception registered byWerkzeug. However, the default HTTP exceptions return simple exceptionpages. You might want to show custom error pages to the user when an error occurs.This can be done by registering error handlers.

When Flask catches an exception while handling a request, it is first looked up by code.If no handler is registered for the code, Flask looks up the error by its class hierarchy; the most specific handler is chosen.If no handler is registered, HTTPException subclasses show ageneric message about their code, while other exceptions are converted to ageneric “500 Internal Server Error”.

For example, if an instance of ConnectionRefusedError is raised,and a handler is registered for ConnectionError andConnectionRefusedError, the more specific ConnectionRefusedErrorhandler is called with the exception instance to generate the response.

Handlers registered on the blueprint take precedence over those registeredglobally on the application, assuming a blueprint is handling the request thatraises the exception. However, the blueprint cannot handle 404 routing errorsbecause the 404 occurs at the routing level before the blueprint can bedetermined.

Generic Exception Handlers

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

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

from flask import jsonfrom werkzeug.exceptions import HTTPException@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({ "code": e.code, "name": e.name, "description": e.description, }) response.content_type = "application/json" return response

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

In most cases it will be safer to register handlers for morespecific exceptions. Since HTTPException instances are valid WSGIresponses, 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 youregister handlers for both HTTPException and Exception, theException handler will not handle HTTPException subclassesbecause it the HTTPException handler is more specific.

Unhandled Exceptions

When there is no error handler registered for an exception, a 500Internal Server Error will be returned instead. Seeflask.Flask.handle_exception() for information about thisbehavior.

If there is an error handler registered for InternalServerError,this will be invoked. As of Flask 1.1.0, this error handler will alwaysbe passed an instance of InternalServerError, not the originalunhandled error.

The original error is available as e.original_exception.

An error handler for “500 Internal Server Error” will be passed uncaughtexceptions in addition to explicit 500 errors. In debug mode, a handlerfor “500 Internal Server Error” will not be used. Instead, theinteractive debugger will be shown.

Custom Error Pages

Sometimes when building a Flask application, you might want to raise aHTTPException to signal to the user thatsomething is wrong with the request. Fortunately, Flask comes with a handyabort() function that aborts a request with a HTTP error fromwerkzeug as desired. It will also provide a plain black and white error pagefor you with a basic description, but nothing fancy.

Depending on the error code it is less or more likely for the user toactually see such an error.

Consider the code below, we might have a user profile route, and if the userfails to pass a username we can raise a “400 Bad Request”. If the user passes ausername and we can’t find it, we raise a “404 Not Found”.

from flask import abort, render_template, request# a username needs to be supplied in the query args# a successful request would be like /profile?username=jack@app.route("/profile")def user_profile(): username = request.arg.get("username") # if a username isn't supplied in the request, return a 400 bad request if username is None: abort(400) user = get_user(username=username) # if a user can't be found by their username, return 404 not found if user is None: abort(404) return render_template("profile.html", user=user)

Here is another example implementation for a “404 Page Not Found” exception:

from flask import render_template@app.errorhandler(404)def page_not_found(e): # note that we set the 404 status explicitly return render_template('404.html'), 404

When using Application Factories:

from flask import Flask, render_templatedef page_not_found(e): return render_template('404.html'), 404def create_app(config_filename): app = Flask(__name__) app.register_error_handler(404, page_not_found) return app

An example template might be this:

{% extends "layout.html" %}{% block title %}Page Not Found{% endblock %}{% block body %} <h1>Page Not Found</h1> <p>What you were looking for is just not there. <p><a href="{{ url_for('index') }}">go somewhere nice</a>{% endblock %}

Further Examples

The above examples wouldn’t actually be an improvement on the defaultexception pages. We can create a custom 500.html template like this:

{% extends "layout.html" %}{% block title %}Internal Server Error{% endblock %}{% block body %} <h1>Internal Server Error</h1> <p>Oops... we seem to have made a mistake, sorry!</p> <p><a href="{{ url_for('index') }}">Go somewhere nice instead</a>{% endblock %}

It can be implemented by rendering the template on “500 Internal Server Error”:

from flask import render_template@app.errorhandler(500)def internal_server_error(e): # note that we set the 500 status explicitly return render_template('500.html'), 500

When using Application Factories:

from flask import Flask, render_templatedef internal_server_error(e): return render_template('500.html'), 500def create_app(): app = Flask(__name__) app.register_error_handler(500, internal_server_error) return app

When using Modular Applications with Blueprints:

from flask import Blueprintblog = Blueprint('blog', __name__)# as a decorator@blog.errorhandler(500)def internal_server_error(e): return render_template('500.html'), 500# or with register_error_handlerblog.register_error_handler(500, internal_server_error)

Blueprint Error Handlers

In Modular Applications with Blueprints, most error handlers will work as expected.However, there is a caveat concerning handlers for 404 and 405exceptions. These error handlers are only invoked from an appropriateraise statement or a call to abort in another of the blueprint’sview functions; they are not invoked by, e.g., an invalid URL access.

This is because the blueprint does not “own” a certain URL space, sothe application instance has no way of knowing which blueprint errorhandler it should run if given an invalid URL. If you would like toexecute different handling strategies for these errors based on URLprefixes, they may be defined at the application level using therequest proxy object.

from flask import jsonify, render_template# at the application level# not the blueprint level@app.errorhandler(404)def page_not_found(e): # if a request is in our blog URL space if request.path.startswith('/blog/'): # we return a custom blog 404 page return render_template("blog/404.html"), 404 else: # otherwise we return our generic site-wide 404 page return render_template("404.html"), 404@app.errorhandler(405)def method_not_allowed(e): # if a request has the wrong method to our API if request.path.startswith('/api/'): # we return a json saying so return jsonify(message="Method Not Allowed"), 405 else: # otherwise we return a generic site-wide 405 page return render_template("405.html"), 405

Returning API Errors as JSON

When building APIs in Flask, some developers realise that the built-inexceptions are not expressive enough for APIs and that the content type oftext/html they are emitting is not very useful for API consumers.

Using the same techniques as above and jsonify() we can return JSONresponses to API errors. abort() is calledwith a description parameter. The error handler willuse that as the JSON error message, and set the status code to 404.

from flask import abort, jsonify@app.errorhandler(404)def resource_not_found(e): return jsonify(error=str(e)), 404@app.route("/cheese")def get_one_cheese(): resource = get_resource() if resource is None: abort(404, description="Resource not found") return jsonify(resource)

We can also create custom exception classes. For instance, we canintroduce a new custom exception for an API that can take a proper human readable message,a status code for the error and some optional payload to give more contextfor the error.

This is a simple example:

from flask import jsonify, requestclass InvalidAPIUsage(Exception): status_code = 400 def __init__(self, message, status_code=None, payload=None): super().__init__() 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(InvalidAPIUsage)def invalid_api_usage(e): return jsonify(e.to_dict()), e.status_code# an API app route for getting user information# a correct request might be /api/user?user_id=420@app.route("/api/user")def user_api(user_id): user_id = request.arg.get("user_id") if not user_id: raise InvalidAPIUsage("No user id provided!") user = get_user(user_id=user_id) if not user: raise InvalidAPIUsage("No such user!", status_code=404) return jsonify(user.to_dict())

A view can now raise that exception with an error message. Additionallysome extra payload can be provided as a dictionary through the payloadparameter.

Logging

See Logging for information about how to log exceptions, such asby emailing them to admins.

Debugging

See Debugging Application Errors for information about how to debug errors indevelopment and production.

Handling Application Errors — Flask Documentation (2.2.x) (2024)
Top Articles
Latest Posts
Article information

Author: Terrell Hackett

Last Updated:

Views: 5830

Rating: 4.1 / 5 (52 voted)

Reviews: 83% of readers found this page helpful

Author information

Name: Terrell Hackett

Birthday: 1992-03-17

Address: Suite 453 459 Gibson Squares, East Adriane, AK 71925-5692

Phone: +21811810803470

Job: Chief Representative

Hobby: Board games, Rock climbing, Ghost hunting, Origami, Kabaddi, Mushroom hunting, Gaming

Introduction: My name is Terrell Hackett, I am a gleaming, brainy, courageous, helpful, healthy, cooperative, graceful person who loves writing and wants to share my knowledge and understanding with you.