""" Aplicación FastAPI — entry point. Endpoints obligatorios del shape rest-api/python (ver coralware-shape.yaml): - /health → liveness + readiness (siempre 200 si el proceso está vivo) - /docs → Swagger UI (auth opcional) - /metrics → Prometheus Endpoints de dominio: agregar en app/routers/ y registrar en _register_routers(). """ import logging from fastapi import Depends, FastAPI, HTTPException, Request, status from fastapi.responses import JSONResponse, Response from prometheus_client import CONTENT_TYPE_LATEST, generate_latest from app.settings import settings logging.basicConfig( level=getattr(logging, settings.LOG_LEVEL, logging.INFO), format="%(asctime)s [%(levelname)s] [cid=%(correlation_id)s] %(message)s", datefmt="%Y-%m-%dT%H:%M:%SZ", ) class CorrelationIdFilter(logging.Filter): def filter(self, record: logging.LogRecord) -> bool: if not hasattr(record, "correlation_id"): record.correlation_id = "-" return True logging.getLogger().addFilter(CorrelationIdFilter()) log = logging.getLogger("__APP_NAME__") def _require_auth_dep() -> None: """ Dependency placeholder para /docs cuando OPENAPI_REQUIRE_AUTH=true. Implementación real: validar JWT contra KEYCLOAK_ISSUER (ADR-029 R-004). """ if not settings.OPENAPI_REQUIRE_AUTH: return raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Auth requerido — implementar validación JWT contra Keycloak", ) def create_app() -> FastAPI: app = FastAPI( title="__APP_NAME__", version="0.1.0", dependencies=( [Depends(_require_auth_dep)] if settings.OPENAPI_REQUIRE_AUTH else [] ), ) @app.middleware("http") async def correlation_id_middleware(request: Request, call_next): cid = request.headers.get(settings.CORRELATION_ID_HEADER) or _generate_cid() request.state.correlation_id = cid response = await call_next(request) response.headers[settings.CORRELATION_ID_HEADER] = cid return response @app.get("/health", tags=["platform"]) async def health() -> JSONResponse: return JSONResponse(status_code=200, content={"status": "ok"}) @app.get("/metrics", tags=["platform"]) async def metrics() -> Response: return Response(content=generate_latest(), media_type=CONTENT_TYPE_LATEST) _register_routers(app) return app def _generate_cid() -> str: """Genera correlation_id si el cliente no envió uno.""" import time import uuid return f"__APP_NAME__-{int(time.time() * 1000)}-{uuid.uuid4().hex[:8]}" def _register_routers(app: FastAPI) -> None: """ Registra los routers de dominio. Agrega aquí los include_router(...) conforme tu app crezca. """ pass app = create_app()