""" Mi Aplicación Backend API con FastAPI + Keycloak + SQLAlchemy """ import logging import subprocess from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from fastapi.staticfiles import StaticFiles from pathlib import Path from contextlib import asynccontextmanager # Core Modules (Secondary) import core.celery_app # Initialize Celery App from api.v1.router import router as api_v1_router from core.config import settings from core.storage_s3 import ensure_s3_bucket from core.paths import layout_path from core.error_handlers import register_exception_handlers from core.middleware import ( LicenseValidationMiddleware, RequestLoggingMiddleware, TenantMiddleware, ) # Configurar logging logging.basicConfig( level=logging.INFO if not settings.DEBUG else logging.DEBUG, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", ) # Crear aplicación FastAPI app = FastAPI( title="Mi Aplicación API", version=settings.APP_VERSION, description="Aplicación web multi-tenant con autenticación Workspace", docs_url="/api/docs" if settings.DEBUG else None, redoc_url="/api/redoc" if settings.DEBUG else None, openapi_url="/api/openapi.json" if settings.DEBUG else None, ) logger = logging.getLogger(__name__) # Registrar manejadores de excepciones register_exception_handlers(app) def run_migrations(): subprocess.run(["alembic", "upgrade", "head"], check=True) # Inicializar la base de datos async def on_startup(): """Evento de inicio de la aplicación""" logger.info("Iniciando la aplicación...") #init_db() run_migrations() if settings.use_s3_object_storage: ensure_s3_bucket() logger.info("Base de datos inicializada correctamente.") # Agregar middlewares personalizados app.add_middleware(LicenseValidationMiddleware) app.add_middleware(TenantMiddleware) # RequestLoggingMiddleware va después de Tenant/License (add_middleware() invierte # el orden de ejecución: el último en registrarse corre primero) para que su cronómetro # envuelva TODO, incluyendo las llamadas al Hub — si no, "Duration:" en los logs # nunca reflejó el costo real de verify-license/auth-me, dando una falsa sensación # de que todo respondía en 2-5ms. if settings.DEBUG: app.add_middleware(RequestLoggingMiddleware) # CORS debe ser el último en añadirse para que sea el más externo # y cubra todas las respuestas, incluyendo las de los middlewares internos app.add_middleware( CORSMiddleware, allow_origins=settings.cors_origins_list, allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) @asynccontextmanager async def lifespan(app: FastAPI): # Centraliza startup para evitar on_event() (deprecated en FastAPI) await on_startup() yield app.router.lifespan_context = lifespan # Crear directorio de uploads si no existe y montar archivos estáticos uploads_dir = Path("uploads").resolve() uploads_dir.mkdir(parents=True, exist_ok=True) app.mount("/api/uploads", StaticFiles(directory=str(uploads_dir)), name="uploads") # Directorios para importación CSV (layouts: temp y errors) Path(layout_path("imports", "temp")).mkdir(parents=True, exist_ok=True) Path(layout_path("imports", "errors")).mkdir(parents=True, exist_ok=True) # Registrar routers app.include_router(api_v1_router, prefix="/api/v1") @app.get("/api/") async def root(): """Root endpoint""" return { "name": "Mi Aplicación API", "version": settings.APP_VERSION, "status": "running", "docs": "/api/docs" if settings.DEBUG else "disabled in production", } @app.get("/api/health") async def health_check(): """Health check endpoint""" return {"status": "healthy", "environment": settings.ENVIRONMENT} @app.get("/api/version") async def get_version(): """ Endpoint de versión de la aplicación Retorna la versión de la aplicación que fue incrustada en la imagen Docker durante el proceso de CI/CD. La versión se genera automáticamente según la rama: - development: YY.MM.1. - main: YY.MM.0. Returns: dict: Información de versión y entorno """ return { "service": settings.APP_NAME, "version": settings.APP_VERSION, "environment": settings.ENVIRONMENT, "debug": settings.DEBUG, }