You won’t need to handle the errors in a Flask application if you predict all the situations. But let’s face it, the user won’t use the application correctly, or you’ll forget to handle the case when a user purchase an item during the summer time change.
So, just in case of, let’s see how to handle errors in a Flask application and what are the best practices.
Why Bother Managing Errors?
Picture this: a user faces an error page and sees something like AttributeError: 'NoneType' object has no attribute 'something'. Not only do they have no idea what just happened, but they’re also wondering why they trusted your app in the first place. Worse, they might have just seen internal details of your application: a hacker’s dream.
Managing errors isn’t just about aesthetics; it’s about:
- Security: Don’t expose sensitive details about your app.
- Usability: Provide helpful, user-friendly error messages.
- Debugging: Organize errors for easier troubleshooting.
In short, manage your errors, or they’ll manage you.
Custom Exception
First things first: centralize your errors by creating a custom exception. This exception will act as a wrapper for all application-specific issues, keeping things neat and giving you a single point of control.
class AppException(Exception):
def __init__(self, message, http_code):
super(Exception, self).__init__(message)
self.message = message
self.http_code = http_code
Now you can use AppException to represent any error in your application with a custom message and an appropriate HTTP status code. This not only keeps your code consistent but also prevents you from scattering raise statements like confetti.
Blueprint
Blueprints aren’t just for organizing routes, they’re also a great way to manage errors.
Let’s set up a blueprint that catches all your custom errors and ensures they’re returned in a structured format.
error_bp = Blueprint("errors", __name__)
LOGGER = logging.getLogger(__name__)
@error_bp.app_errorhandler(AppException)
def handle_custom_exception(error):
LOGGER.warning(f"Error occurred for request {request.path} : {error.message}")
response = {"error": error.message}
return make_response(jsonify(response), error.http_code)
Register the blueprint in your app, and you’re good to go:
app.register_blueprint(errors)
Now, any AppException raised in your app will be handled by this blueprint.
Not Found Errors
Sometimes, users (or bots) will hit non-existent routes, triggering Flask’s dreaded 404 Not Found. First, I want to know who requests non-existent routes: an outdated system which still uses my application, or some guy which tries some random routes.
So, I log all the Not Found error and add a personal touch by logging the source IP.
@error_bp.app_errorhandler(NotFound)
def handle_not_found(error):
source_ip = request.remote_addr
message = f"Cannot find the following url requested by {source_ip}: {request.path}"
LOGGER.warning(message)
return make_response(jsonify({"error": message}), 404)
This logs who’s knocking on your door and why, making it easier to identify (and fix) recurring issues from specific sources.
SQLAlchemy Errors
Database errors are among the most common issues you’ll face. Instead of letting SQLAlchemy throw verbose and cryptic errors, let’s intercept them and log useful details, like the table and fields involved.
@error_bp.app_errorhandler(SQLAlchemyError)
def other(error):
LOGGER.exception(error)
platform = os.getenv("PLATFORM", "local")
if platform != "prod":
return make_response(jsonify({"error": str(error)}), 500)
return make_response(
jsonify({"error": "Unknown error. Please contact the support service."}),
500,
)
By logging the error details, you’ll have the context needed to debug issues without sharing those juicy SQL errors with end users.
But to hide most of the sensitive information, I first check the platform where is running the application. If it’s running in production, I return a static message like "Unknown error". Otherwise, if I’m in a development or a staging environment, I can return the full description of the error.
Remaining Errors
For everything else that slips through the cracks, set up a generic error handler. If a specific message is available, log it; otherwise, keep it vague (especially in production).
Example:
@error_bp.app_errorhandler(Exception)
def handle_generic_exception(error):
LOGGER.exception(f"Error while handling {request.method} {request.full_path} ({request.data}): {error}")
platform = os.getenv("PLATFORM", "local")
http_code = 500
if hasattr(error, "code"):
http_code = error.code
message = str(error)
if platform != "prod":
return make_response(jsonify({"error": message}), http_code)
return make_response(
jsonify({"error": "Unknown error. Please contact the support service."}),
http_code,
)
This catch-all handler ensures you never expose raw errors to users, even if something unexpected occurs.
Conclusion
Error management isn’t just about writing code; it’s about writing the right code for the environment. In production, less is more. Avoid logging sensitive details or displaying raw error messages, hackers don’t need your help.
In summary:
- Use custom exceptions to centralize error handling.
- Set up blueprints to handle errors cleanly.
- Log useful details for debugging, but only in development.
- Remember: no one loves a stack trace on their screen.



Leave a comment