Files
blog.pyaqa.ru/app/presentation/web/error_handlers.py
Sergey Vanyushkin 41b6698c55
Some checks failed
ci/woodpecker/pr/lint Pipeline was successful
ci/woodpecker/pr/test Pipeline failed
ci/woodpecker/pr/type Pipeline failed
fix: add nl2br filter and fix TemplateResponse arguments
- Add nl2br Jinja2 filter to convert newlines to <br> tags
- Fix TemplateResponse argument order (request first) in error handlers
- Fix type annotations for mypy
- All 97 tests passing
2026-05-02 16:48:44 +03:00

195 lines
5.3 KiB
Python

"""Error handlers and middleware for web UI.
This module provides custom error pages and flash message middleware
for the web interface.
"""
from typing import Any
from fastapi import HTTPException, Request
from fastapi.responses import HTMLResponse, RedirectResponse
from fastapi.templating import Jinja2Templates
from app.presentation.web.flash import FlashManager, get_flash_messages
templates = Jinja2Templates(directory="app/presentation/templates")
async def setup_flash_manager(request: Request) -> None:
"""Setup flash manager on request state.
Args:
request: FastAPI request object.
"""
request.state.flash_manager = FlashManager(request)
async def add_flash_to_response(request: Request, response: HTMLResponse) -> None:
"""Add flash cookie to response if needed.
Args:
request: FastAPI request object.
response: FastAPI response object.
"""
if hasattr(request.state, "flash_manager"):
request.state.flash_manager.set_cookie(response)
def get_template_context(request: Request) -> dict[str, Any]:
"""Get base template context with flash messages.
Args:
request: FastAPI request object.
Returns:
Template context dictionary.
"""
from app.presentation.web.deps import can_create_post, get_user_role
user = getattr(request.state, "user", None)
user_role = get_user_role(user)
return {
"request": request,
"user": user,
"user_role": user_role.value if user_role else None,
"can_create": can_create_post(user),
"flash_messages": get_flash_messages(request),
}
async def http_exception_handler(request: Request, exc: HTTPException) -> HTMLResponse:
"""Handle HTTP exceptions with custom error pages.
Args:
request: FastAPI request object.
exc: HTTPException instance.
Returns:
HTMLResponse with error page.
"""
# Handle redirects (307, 308)
if exc.status_code in (307, 308):
location = exc.headers.get("Location") if exc.headers else None
if location:
return RedirectResponse(url=location, status_code=exc.status_code) # type: ignore[return-value]
error_pages = {
403: ("Access Denied", "You don't have permission to access this page."),
404: ("Page Not Found", "The page you're looking for doesn't exist."),
500: ("Server Error", "Something went wrong on our end. Please try again later."),
}
error_title, error_message = error_pages.get(
exc.status_code, ("Error", exc.detail or "An unexpected error occurred.")
)
context = get_template_context(request)
context.update(
{
"error_code": exc.status_code,
"error_title": error_title,
"error_message": error_message,
}
)
return templates.TemplateResponse(
request,
"pages/error.html",
context,
status_code=exc.status_code,
)
async def not_found_handler(request: Request, exc: HTTPException) -> HTMLResponse:
"""Handle 404 Not Found errors.
Args:
request: FastAPI request object.
exc: HTTPException instance.
Returns:
HTMLResponse with 404 page.
"""
context = get_template_context(request)
context.update(
{
"error_code": 404,
"error_title": "Page Not Found",
"error_message": "The page you're looking for doesn't exist or has been moved.",
}
)
return templates.TemplateResponse(
request,
"pages/error.html",
context,
status_code=404,
)
async def forbidden_handler(request: Request, exc: HTTPException) -> HTMLResponse:
"""Handle 403 Forbidden errors.
Args:
request: FastAPI request object.
exc: HTTPException instance.
Returns:
HTMLResponse with 403 page.
"""
context = get_template_context(request)
context.update(
{
"error_code": 403,
"error_title": "Access Denied",
"error_message": "You don't have permission to access this resource. Please sign in or contact an administrator.",
}
)
return templates.TemplateResponse(
request,
"pages/error.html",
context,
status_code=403,
)
async def server_error_handler(request: Request, exc: Exception) -> HTMLResponse:
"""Handle 500 Internal Server Error.
Args:
request: FastAPI request object.
exc: Exception instance.
Returns:
HTMLResponse with 500 page.
"""
context = get_template_context(request)
context.update(
{
"error_code": 500,
"error_title": "Server Error",
"error_message": "Something went wrong on our end. Please try again later or contact support if the problem persists.",
}
)
return templates.TemplateResponse(
request,
"pages/error.html",
context,
status_code=500,
)
def register_error_handlers(app: Any) -> None:
"""Register error handlers with FastAPI app.
Args:
app: FastAPI application instance.
"""
app.add_exception_handler(404, not_found_handler)
app.add_exception_handler(403, forbidden_handler)
app.add_exception_handler(500, server_error_handler)
app.add_exception_handler(HTTPException, http_exception_handler)