Flask Error Handling
Introduction
Error handling is a critical aspect of web application development. In any web application, things can go wrong - users might request non-existent pages, server-side processing might fail, or external services might be unavailable. Flask provides several mechanisms to handle these errors gracefully, improving user experience and making debugging easier.
In this guide, we'll learn how to:
- Handle common HTTP errors like 404 and 500
- Create custom error pages
- Work with application-wide error handlers
- Handle exceptions in Flask routes
- Implement logging for errors
By the end of this guide, you'll be able to implement robust error handling in your Flask applications.
HTTP Error Codes Basics
Before diving into Flask's error handling, let's understand the most common HTTP error codes you'll encounter:
- 400 Bad Request: The server cannot process the request due to client error
- 401 Unauthorized: Authentication is required
- 403 Forbidden: Server understood the request but refuses to authorize it
- 404 Not Found: Resource not found
- 500 Internal Server Error: Generic server error message
- 503 Service Unavailable: The server is temporarily unable to handle the request
Basic Error Handling in Flask
Default Error Behavior
By default, Flask returns a basic HTML page when an error occurs. For example, when a user requests a non-existent page, Flask returns a 404 error with minimal styling.
Let's look at a simple Flask application:
from flask import Flask
app = Flask(__name__)
@app.route('/')
def home():
return "Welcome to the homepage!"
if __name__ == '__main__':
app.run(debug=True)
If a user visits /non-existent-page
, Flask will return its default 404 page.
Custom Error Handlers
We can customize how errors are displayed by registering error handler functions using the app.errorhandler
decorator.
Here's how to create a custom 404 error page:
from flask import Flask, render_template
app = Flask(__name__)
@app.route('/')
def home():
return "Welcome to the homepage!"
@app.errorhandler(404)
def page_not_found(e):
# note that we set the 404 status explicitly
return render_template('404.html'), 404
if __name__ == '__main__':
app.run(debug=True)
For this to work, you need to create a 404.html
template in your templates
folder:
<!-- templates/404.html -->
<!DOCTYPE html>
<html>
<head>
<title>Page Not Found</title>
<style>
body {
font-family: Arial, sans-serif;
text-align: center;
padding-top: 50px;
}
h1 {
color: #d9534f;
}
.container {
max-width: 600px;
margin: 0 auto;
padding: 20px;
border: 1px solid #ddd;
border-radius: 5px;
}
</style>
</head>
<body>
<div class="container">
<h1>404 - Page Not Found</h1>
<p>The page you are looking for doesn't exist.</p>
<p><a href="/">Go back to homepage</a></p>
</div>
</body>
</html>
Handling Multiple Error Codes
You can register handlers for different HTTP error codes:
@app.errorhandler(404)
def page_not_found(e):
return render_template('404.html'), 404
@app.errorhandler(500)
def internal_server_error(e):
return render_template('500.html'), 500
Handling Exceptions
Try-Except in Route Functions
You can use Python's try-except blocks to catch exceptions within your route functions:
@app.route('/divide/<int:num1>/<int:num2>')
def divide(num1, num2):
try:
result = num1 / num2
return f"The result is: {result}"
except ZeroDivisionError:
return "Error: Cannot divide by zero!", 400
In this example:
- If the user visits
/divide/10/2
, they'll see "The result is: 5.0" - If the user visits
/divide/10/0
, they'll see "Error: Cannot divide by zero!"
Application-Wide Exception Handling
Flask allows you to register handlers for specific Python exceptions across your entire application:
@app.errorhandler(ZeroDivisionError)
def handle_zero_division_error(e):
return "Zero division error occurred", 400
@app.errorhandler(ValueError)
def handle_value_error(e):
return f"Invalid value: {str(e)}", 400
Now any uncaught ZeroDivisionError
or ValueError
in your application will be handled by these functions.
Advanced Error Handling
Custom Error Classes
You can create custom exception classes for your application:
class 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 handle_invalid_api_usage(e):
response = jsonify(e.to_dict())
response.status_code = e.status_code
return response
@app.route('/api/item/<int:item_id>')
def get_item(item_id):
if item_id <= 0:
raise InvalidAPIUsage('Item ID must be positive', status_code=400)
# Process the request...
return f"Item ID: {item_id}"
Logging Errors
It's important to log errors for debugging. Flask integrates with Python's logging module:
import logging
from flask import Flask
app = Flask(__name__)
# Configure logging
handler = logging.FileHandler('app.log')
handler.setLevel(logging.ERROR)
app.logger.addHandler(handler)
@app.errorhandler(500)
def internal_server_error(e):
app.logger.error(f'500 error: {str(e)}')
return render_template('500.html'), 500
@app.route('/error')
def trigger_error():
try:
# Simulate an error
1 / 0
except Exception as e:
app.logger.exception('An error occurred')
return f"An error occurred: {str(e)}", 500
With this setup, errors will be logged to app.log
for later review.
Real-World Example: API Error Handling
Let's create a more comprehensive example of error handling in a simple API:
from flask import Flask, jsonify, request
app = Flask(__name__)
# Database simulation
users = {
1: {"name": "Alice", "email": "[email protected]"},
2: {"name": "Bob", "email": "[email protected]"}
}
class APIError(Exception):
"""Base class for API errors"""
def __init__(self, message, status_code):
self.message = message
self.status_code = status_code
@app.errorhandler(APIError)
def handle_api_error(error):
response = jsonify({"error": error.message})
response.status_code = error.status_code
return response
@app.errorhandler(404)
def not_found(e):
return jsonify({"error": "Resource not found"}), 404
@app.errorhandler(400)
def bad_request(e):
return jsonify({"error": "Bad request"}), 400
@app.errorhandler(500)
def server_error(e):
app.logger.error(f"Server error: {str(e)}")
return jsonify({"error": "Internal server error"}), 500
@app.route('/api/users/<int:user_id>', methods=['GET'])
def get_user(user_id):
try:
if user_id not in users:
raise APIError("User not found", 404)
return jsonify(users[user_id])
except ValueError:
raise APIError("Invalid user ID", 400)
@app.route('/api/users', methods=['POST'])
def create_user():
data = request.get_json()
if not data:
raise APIError("No data provided", 400)
if 'name' not in data or 'email' not in data:
raise APIError("Missing required fields", 400)
# In a real app, you would insert into a database
new_id = max(users.keys()) + 1
users[new_id] = {"name": data['name'], "email": data['email']}
return jsonify({"id": new_id, "name": data['name'], "email": data['email']}), 201
if __name__ == '__main__':
app.run(debug=True)
In this example:
- We defined a custom
APIError
class for API-specific errors - We registered error handlers for our custom exception and common HTTP error codes
- We raised appropriate exceptions based on different error conditions
- We returned proper JSON responses with appropriate status codes
Blueprint Error Handling
If your app is structured using Flask blueprints, you can register error handlers for specific blueprints:
from flask import Flask, Blueprint, render_template
app = Flask(__name__)
admin_bp = Blueprint('admin', __name__, url_prefix='/admin')
@admin_bp.errorhandler(404)
def admin_not_found(e):
return render_template('admin/404.html'), 404
# Register the blueprint
app.register_blueprint(admin_bp)
# App-wide error handler
@app.errorhandler(404)
def page_not_found(e):
return render_template('404.html'), 404
In this example, 404 errors occurring within the admin blueprint will use the admin_not_found
handler, while other 404 errors will use the app-wide handler.
Best Practices for Error Handling
- Be specific: Create custom error pages for common errors like 404 and 500
- Don't expose sensitive information: In production, don't show stack traces or detailed error messages to users
- Log errors: Always log errors on the server for debugging
- Return appropriate status codes: Use the correct HTTP status codes for different error conditions
- Provide helpful messages: Give users guidance on what went wrong and what they can do next
- Consider the format: Return errors in the same format as the rest of your application (HTML for websites, JSON for APIs)
Summary
Error handling is a crucial part of building robust Flask applications. In this guide, we've learned:
- How to create custom error pages for different HTTP error codes
- Techniques for handling exceptions in route functions
- How to implement application-wide error handlers
- Ways to log errors for debugging
- How to structure error handling for APIs
- Blueprint-specific error handling
By implementing these patterns, you'll create applications that are more user-friendly and easier to debug when things go wrong.
Additional Resources
Exercises
- Create a Flask application with custom error pages for 404, 403, and 500 errors.
- Build a simple API that validates input and returns appropriate error responses.
- Implement logging for your Flask application that records errors to a file.
- Create a custom exception class and error handler for handling form validation errors.
- Modify the real-world API example to include authentication and proper error handling for unauthorized access.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)