"""Error handlers for Flask-More-Smorest.
This module provides error handler functions and a RequestHandlers class
for registering error handlers with Flask applications.
"""
import logging
from typing import TYPE_CHECKING
from flask import make_response
from sqlalchemy.exc import DatabaseError
from werkzeug.exceptions import HTTPException
from .exceptions import ApiException, DBError, ForbiddenError
from .exceptions import InternalServerError as ApiInternalServerError
if TYPE_CHECKING:
from flask import Flask, Response
logger = logging.getLogger(__name__)
[docs]
def server_error_handler(e: Exception) -> "Response":
"""Handle unhandled server errors.
Args:
e: The exception that was raised
Returns:
Flask Response with error details
"""
exc = ApiInternalServerError(message=f"Unhandled Exception: {e}")
logger.critical(
"Encountered Unhandled Exception!",
extra=exc.get_debug_context(),
)
return exc.make_error_response()
[docs]
def unauthorized_handler(
e: Exception,
errors: dict[str, str] | None = None,
level: str = "info",
warnings: list[str] | None = None,
) -> "Response":
"""Handle unauthorized access errors.
Args:
e: The exception that was raised
errors: Optional error details
level: Logging level to use
warnings: Optional warning messages
Returns:
Flask Response with error details
"""
exc = ForbiddenError(message=f"Unauthorized: {e}")
return exc.make_error_response()
[docs]
def handle_api_exception(e: ApiException) -> "Response":
"""Handle ApiException and its subclasses.
Args:
e: The API exception to handle
Returns:
Flask Response with error details
"""
return e.make_error_response()
[docs]
def handle_generic_exception(e: Exception) -> "Response":
"""Handle generic Python exceptions.
Args:
e: The exception to handle
Returns:
Flask Response with error details or original HTTP response
"""
# pass through HTTP errors
if isinstance(e, HTTPException):
return make_response(e.get_response())
api_exc = ApiInternalServerError(*e.args)
return api_exc.make_error_response()
[docs]
def handle_db_exception(e: DatabaseError) -> "Response":
"""Handle database exceptions.
Automatically rolls back the database session before generating
the error response.
Args:
e: The database error to handle
Returns:
Flask Response with error details
"""
# Rollback the session:
from ..sqla import db
# Check that db was initialized
if db.session is not None:
db.session.rollback()
api_exc = DBError(*e.args)
return api_exc.make_error_response()
[docs]
class RequestHandlers:
"""Handler class for registering error handlers with Flask.
This class provides a simple way to register all error handlers
with a Flask application.
Example:
>>> from flask import Flask
>>> from flask_more_smorest.error import RequestHandlers
>>>
>>> app = Flask(__name__)
>>> handlers = RequestHandlers(app)
"""
[docs]
def __init__(self, app: "Flask | None" = None) -> None:
"""Initialize request handlers.
Args:
app: Optional Flask application to register handlers with
"""
if app is not None:
self.init_app(app)
[docs]
def init_app(self, app: "Flask") -> None:
"""Register error handlers with Flask application.
Args:
app: Flask application to register handlers with
"""
app.register_error_handler(ApiException, handle_api_exception)
app.register_error_handler(DatabaseError, handle_db_exception)
app.errorhandler(403)(unauthorized_handler)
# TODO: debug 500 handlers
app.errorhandler(500)(server_error_handler)
app.register_error_handler(Exception, handle_generic_exception)