Le schéma ne déclarait aucun code d'erreur : ni 401, ni 403, ni 404, ni 409,
ni 429. Swagger affirmait que /auth/login ne pouvait répondre que 200 ou 422,
alors que 31-contrat-authentification.md décrit ces codes comme le contrat que
le frontend doit traiter.
Le 422 publié était pire qu'absent : le schéma exposait HTTPValidationError,
le modèle par défaut de FastAPI avec sa clé `loc`, quand
validation_error_handler renvoie {"detail": [{"champ", "type"}]}. Un client
codé sur la documentation lisait une clé qui n'arrive jamais.
Les métadonnées arrivent avec : description, résumé et une description par
tag. `servers`, `license_info` et `contact` restent absents, ils poseraient
des décisions qui ne sont pas prises.
Le cookie de rafraîchissement devient visible par un APIKeyCookie en
auto_error=False, purement documentaire : lit_le_cookie() reste seul maître du
401 de /auth/refresh.
Au passage, health.py posait son tag deux fois, une fois sur son APIRouter et
une fois à l'include_router.
46 lines
1.6 KiB
Python
46 lines
1.6 KiB
Python
from fastapi import APIRouter, HTTPException, status
|
|
from sqlalchemy import text
|
|
from sqlalchemy.exc import SQLAlchemyError
|
|
|
|
from app.api.deps import SessionDep, SettingsDep
|
|
from app.api.openapi import REPONSE_INDISPONIBLE
|
|
from app.core.logging import get_logger
|
|
from app.schemas.health import LivenessStatus, ReadinessStatus
|
|
|
|
logger = get_logger(__name__)
|
|
router = APIRouter()
|
|
|
|
TIMESCALEDB_VERSION = text("SELECT extversion FROM pg_extension WHERE extname = 'timescaledb'")
|
|
|
|
|
|
@router.get("/live", summary="Sonde de vivacité")
|
|
async def liveness(settings: SettingsDep) -> LivenessStatus:
|
|
return LivenessStatus(
|
|
status="ok",
|
|
service=settings.name,
|
|
version=settings.version,
|
|
environment=settings.env,
|
|
)
|
|
|
|
|
|
@router.get("/ready", summary="Sonde de disponibilité", responses=REPONSE_INDISPONIBLE)
|
|
async def readiness(session: SessionDep) -> ReadinessStatus:
|
|
try:
|
|
version: str | None = await session.scalar(TIMESCALEDB_VERSION)
|
|
except SQLAlchemyError, OSError:
|
|
logger.exception("Base de données injoignable")
|
|
raise HTTPException(
|
|
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
|
detail="Base de données injoignable",
|
|
) from None
|
|
|
|
if version is None:
|
|
logger.error("Extension TimescaleDB absente de la base")
|
|
raise HTTPException(
|
|
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
|
detail="Extension TimescaleDB absente",
|
|
)
|
|
|
|
logger.debug("Extension TimescaleDB en version %s", version)
|
|
return ReadinessStatus(status="ready", database="reachable", timescaledb="loaded")
|