feat: Migrate to Pydantic's @field_validator and implement lifespan handler in FastAPI
This commit is contained in:
65
main.py
65
main.py
@@ -1,4 +1,5 @@
|
||||
import logging
|
||||
from contextlib import asynccontextmanager
|
||||
from typing import Awaitable, Callable
|
||||
|
||||
from fastapi import FastAPI, Request, Response
|
||||
@@ -22,42 +23,14 @@ from monitoring import router as monitoring_router
|
||||
from services.bootstrap import bootstrap_admin, bootstrap_pricing_settings
|
||||
from scripts.init_db import init_db as init_db_script
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
app.add_middleware(AuthSessionMiddleware)
|
||||
app.add_middleware(MetricsMiddleware)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@app.middleware("http")
|
||||
async def json_validation(
|
||||
request: Request, call_next: Callable[[Request], Awaitable[Response]]
|
||||
) -> Response:
|
||||
return await validate_json(request, call_next)
|
||||
|
||||
|
||||
@app.get("/health", summary="Container health probe")
|
||||
async def health() -> dict[str, str]:
|
||||
return {"status": "ok"}
|
||||
|
||||
|
||||
@app.get("/favicon.ico", include_in_schema=False)
|
||||
async def favicon() -> Response:
|
||||
static_directory = "static"
|
||||
favicon_img = "favicon.ico"
|
||||
return FileResponse(f"{static_directory}/{favicon_img}")
|
||||
|
||||
|
||||
@app.on_event("startup")
|
||||
# TODO: use lifespan events for startup/shutdown tasks
|
||||
async def ensure_admin_bootstrap() -> None:
|
||||
async def _bootstrap_startup() -> None:
|
||||
settings = get_settings()
|
||||
admin_settings = settings.admin_bootstrap_settings()
|
||||
pricing_metadata = settings.pricing_metadata()
|
||||
try:
|
||||
# Ensure DB schema/types/seeds required for bootstrapping exist.
|
||||
# The initializer is idempotent and safe to run on every startup.
|
||||
try:
|
||||
init_db_script()
|
||||
except Exception:
|
||||
@@ -74,9 +47,6 @@ async def ensure_admin_bootstrap() -> None:
|
||||
admin_result.password_rotated,
|
||||
admin_result.roles_granted,
|
||||
)
|
||||
# Avoid accessing ORM-managed attributes that may be detached outside
|
||||
# of the UnitOfWork/session scope. Attempt a safe extraction and
|
||||
# fall back to minimal logging if attributes are unavailable.
|
||||
try:
|
||||
seed = pricing_result.seed
|
||||
slug = getattr(seed.settings, "slug", None) if seed and getattr(
|
||||
@@ -102,6 +72,37 @@ async def ensure_admin_bootstrap() -> None:
|
||||
"Failed to bootstrap administrator or pricing settings")
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def app_lifespan(_: FastAPI):
|
||||
await _bootstrap_startup()
|
||||
yield
|
||||
|
||||
|
||||
app = FastAPI(lifespan=app_lifespan)
|
||||
|
||||
app.add_middleware(AuthSessionMiddleware)
|
||||
app.add_middleware(MetricsMiddleware)
|
||||
|
||||
|
||||
@app.middleware("http")
|
||||
async def json_validation(
|
||||
request: Request, call_next: Callable[[Request], Awaitable[Response]]
|
||||
) -> Response:
|
||||
return await validate_json(request, call_next)
|
||||
|
||||
|
||||
@app.get("/health", summary="Container health probe")
|
||||
async def health() -> dict[str, str]:
|
||||
return {"status": "ok"}
|
||||
|
||||
|
||||
@app.get("/favicon.ico", include_in_schema=False)
|
||||
async def favicon() -> Response:
|
||||
static_directory = "static"
|
||||
favicon_img = "favicon.ico"
|
||||
return FileResponse(f"{static_directory}/{favicon_img}")
|
||||
|
||||
|
||||
app.include_router(dashboard_router)
|
||||
app.include_router(calculations_router)
|
||||
app.include_router(auth_router)
|
||||
|
||||
Reference in New Issue
Block a user