mirror of
https://github.com/mwisnowski/mtg_python_deckbuilder.git
synced 2026-03-18 11:16:30 +01:00
refactor: modular route organization (Phase 1-2 complete)
- Split monolithic build route handler into focused modules - Extract validation, multi-copy, include/exclude, themes, and partner routes - Add response utilities and telemetry decorators - Create route pattern documentation - Fix multi-copy detection bug (tag key mismatch) - Improve code maintainability and testability Roadmap 9 M1 Phase 1-2
This commit is contained in:
parent
97da117ccb
commit
e81b47bccf
20 changed files with 2852 additions and 1552 deletions
158
code/web/utils/responses.py
Normal file
158
code/web/utils/responses.py
Normal file
|
|
@ -0,0 +1,158 @@
|
|||
"""Response builder utilities for standardized HTTP responses.
|
||||
|
||||
Provides helper functions for creating consistent response objects across all routes.
|
||||
"""
|
||||
from typing import Any, Dict, Optional
|
||||
from fastapi import Request
|
||||
from fastapi.responses import HTMLResponse, JSONResponse
|
||||
from fastapi.templating import Jinja2Templates
|
||||
|
||||
|
||||
def build_error_response(
|
||||
request: Request,
|
||||
status_code: int,
|
||||
error_type: str,
|
||||
message: str,
|
||||
detail: Optional[str] = None,
|
||||
fields: Optional[Dict[str, list[str]]] = None
|
||||
) -> JSONResponse:
|
||||
"""Build a standardized error response.
|
||||
|
||||
Args:
|
||||
request: FastAPI request object
|
||||
status_code: HTTP status code
|
||||
error_type: Type of error (e.g., "ValidationError", "NotFoundError")
|
||||
message: User-friendly error message
|
||||
detail: Additional error detail
|
||||
fields: Field-level validation errors
|
||||
|
||||
Returns:
|
||||
JSONResponse with standardized error structure
|
||||
"""
|
||||
import time
|
||||
|
||||
request_id = getattr(request.state, "request_id", "unknown")
|
||||
error_data = {
|
||||
"status": status_code,
|
||||
"error": error_type,
|
||||
"message": message,
|
||||
"path": str(request.url.path),
|
||||
"request_id": request_id,
|
||||
"timestamp": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
|
||||
}
|
||||
|
||||
if detail:
|
||||
error_data["detail"] = detail
|
||||
if fields:
|
||||
error_data["fields"] = fields
|
||||
|
||||
return JSONResponse(content=error_data, status_code=status_code)
|
||||
|
||||
|
||||
def build_success_response(
|
||||
data: Any,
|
||||
status_code: int = 200,
|
||||
headers: Optional[Dict[str, str]] = None
|
||||
) -> JSONResponse:
|
||||
"""Build a standardized success response.
|
||||
|
||||
Args:
|
||||
data: Response data to return
|
||||
status_code: HTTP status code (default 200)
|
||||
headers: Optional additional headers
|
||||
|
||||
Returns:
|
||||
JSONResponse with data
|
||||
"""
|
||||
response = JSONResponse(content=data, status_code=status_code)
|
||||
if headers:
|
||||
for key, value in headers.items():
|
||||
response.headers[key] = value
|
||||
return response
|
||||
|
||||
|
||||
def build_template_response(
|
||||
request: Request,
|
||||
templates: Jinja2Templates,
|
||||
template_name: str,
|
||||
context: Dict[str, Any],
|
||||
status_code: int = 200
|
||||
) -> HTMLResponse:
|
||||
"""Build a standardized template response.
|
||||
|
||||
Args:
|
||||
request: FastAPI request object
|
||||
templates: Jinja2Templates instance
|
||||
template_name: Name of template to render
|
||||
context: Template context dictionary
|
||||
status_code: HTTP status code (default 200)
|
||||
|
||||
Returns:
|
||||
HTMLResponse with rendered template
|
||||
"""
|
||||
# Ensure request is in context
|
||||
if "request" not in context:
|
||||
context["request"] = request
|
||||
|
||||
return templates.TemplateResponse(
|
||||
request,
|
||||
template_name,
|
||||
context,
|
||||
status_code=status_code
|
||||
)
|
||||
|
||||
|
||||
def build_htmx_response(
|
||||
content: str,
|
||||
trigger: Optional[Dict[str, Any]] = None,
|
||||
retarget: Optional[str] = None,
|
||||
reswap: Optional[str] = None
|
||||
) -> HTMLResponse:
|
||||
"""Build an HTMX partial response with appropriate headers.
|
||||
|
||||
Args:
|
||||
content: HTML content to return
|
||||
trigger: HTMX trigger events to fire
|
||||
retarget: Optional HX-Retarget header
|
||||
reswap: Optional HX-Reswap header
|
||||
|
||||
Returns:
|
||||
HTMLResponse with HTMX headers
|
||||
"""
|
||||
import json
|
||||
|
||||
response = HTMLResponse(content=content)
|
||||
|
||||
if trigger:
|
||||
response.headers["HX-Trigger"] = json.dumps(trigger)
|
||||
if retarget:
|
||||
response.headers["HX-Retarget"] = retarget
|
||||
if reswap:
|
||||
response.headers["HX-Reswap"] = reswap
|
||||
|
||||
return response
|
||||
|
||||
|
||||
def merge_hx_trigger(response: HTMLResponse, events: Dict[str, Any]) -> None:
|
||||
"""Merge additional HTMX trigger events into an existing response.
|
||||
|
||||
Args:
|
||||
response: Existing HTMLResponse
|
||||
events: Additional trigger events to merge
|
||||
"""
|
||||
import json
|
||||
|
||||
if not events:
|
||||
return
|
||||
|
||||
existing = response.headers.get("HX-Trigger")
|
||||
if existing:
|
||||
try:
|
||||
existing_events = json.loads(existing)
|
||||
existing_events.update(events)
|
||||
response.headers["HX-Trigger"] = json.dumps(existing_events)
|
||||
except (json.JSONDecodeError, AttributeError):
|
||||
# If existing is a simple string, convert to dict
|
||||
response.headers["HX-Trigger"] = json.dumps(events)
|
||||
else:
|
||||
response.headers["HX-Trigger"] = json.dumps(events)
|
||||
Loading…
Add table
Add a link
Reference in a new issue