import logging from contextlib import asynccontextmanager from fastapi import FastAPI, Request, HTTPException, Depends from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import JSONResponse import asyncpg import redis.asyncio as redis import time from .config import get_settings from .dependencies import get_db_pool, get_redis_client, get_cache, verify_bearer_token from .routes import vehicles, vin from .models.responses import HealthResponse from .services.cache_service import CacheService # Configure logging logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' ) logger = logging.getLogger(__name__) settings = get_settings() @asynccontextmanager async def lifespan(app: FastAPI): """Application lifespan manager""" # Startup logger.info("Starting MVP Platform Vehicles API...") # Initialize database pool try: app.state.db_pool = await asyncpg.create_pool( host=settings.POSTGRES_HOST, port=settings.POSTGRES_PORT, user=settings.POSTGRES_USER, password=settings.POSTGRES_PASSWORD, database=settings.POSTGRES_DATABASE, min_size=settings.DATABASE_MIN_CONNECTIONS, max_size=settings.DATABASE_MAX_CONNECTIONS, command_timeout=30 ) logger.info("Database pool initialized") except Exception as e: logger.error(f"Failed to initialize database pool: {e}") raise # Initialize Redis client try: app.state.redis_client = redis.Redis( host=settings.REDIS_HOST, port=settings.REDIS_PORT, db=settings.REDIS_DB, decode_responses=False, socket_connect_timeout=5, socket_timeout=5 ) # Test connection await app.state.redis_client.ping() logger.info("Redis client initialized") except Exception as e: logger.warning(f"Failed to initialize Redis client: {e}") app.state.redis_client = None # Initialize cache service app.state.cache_service = CacheService( app.state.redis_client, enabled=bool(app.state.redis_client), default_ttl=settings.CACHE_TTL ) yield # Shutdown logger.info("Shutting down MVP Platform Vehicles API...") if hasattr(app.state, 'db_pool') and app.state.db_pool: await app.state.db_pool.close() logger.info("Database pool closed") if hasattr(app.state, 'redis_client') and app.state.redis_client: await app.state.redis_client.aclose() logger.info("Redis client closed") # Create FastAPI app app = FastAPI( title="MVP Platform Vehicles API", description="Hierarchical Vehicle API with VIN decoding for MotoVaultPro platform services", version="1.0.0", lifespan=lifespan, docs_url="/docs" if settings.DEBUG else None, redoc_url="/redoc" if settings.DEBUG else None ) # Add CORS middleware app.add_middleware( CORSMiddleware, allow_origins=settings.CORS_ORIGINS, allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # Request timing middleware @app.middleware("http") async def add_process_time_header(request: Request, call_next): start_time = time.time() response = await call_next(request) process_time = time.time() - start_time response.headers["X-Process-Time"] = str(process_time) return response # Global exception handler @app.exception_handler(Exception) async def global_exception_handler(request: Request, exc: Exception): logger.error(f"Unhandled exception in {request.method} {request.url.path}: {exc}") return JSONResponse( status_code=500, content={"detail": "Internal server error"} ) # Include routers app.include_router(vehicles.router, prefix="/api/v1", dependencies=[Depends(verify_bearer_token)]) app.include_router(vin.router, prefix="/api/v1", dependencies=[Depends(verify_bearer_token)]) # Health check endpoint @app.api_route("/health", methods=["GET", "HEAD"], response_model=HealthResponse) async def health_check(request: Request): """Health check endpoint""" db_status = "ok" cache_status = "ok" # Check database try: db_pool = request.app.state.db_pool async with db_pool.acquire() as conn: await conn.fetchval("SELECT 1") except Exception as e: logger.error(f"Database health check failed: {e}") db_status = "error" # Check cache try: cache = request.app.state.cache_service if cache and cache.enabled: await cache.redis.ping() else: cache_status = "disabled" except Exception as e: logger.error(f"Cache health check failed: {e}") cache_status = "error" overall_status = "ok" if db_status == "ok" else "degraded" return HealthResponse( status=overall_status, database=db_status, cache=cache_status, version="1.0.0" ) # Root endpoint @app.get("/") async def root(): """Root endpoint with API information""" return { "name": "MVP Platform Vehicles API", "version": "1.0.0", "description": "Hierarchical Vehicle API with VIN decoding", "docs_url": "/docs" if settings.DEBUG else "Contact administrator for documentation", "endpoints": { "health": "/health", "makes": "/api/v1/vehicles/makes?year=2024", "models": "/api/v1/vehicles/models?year=2024&make_id=1", "trims": "/api/v1/vehicles/trims?year=2024&make_id=1&model_id=1", "engines": "/api/v1/vehicles/engines?year=2024&make_id=1&model_id=1", "transmissions": "/api/v1/vehicles/transmissions?year=2024&make_id=1&model_id=1", "vin_decode": "/api/v1/vehicles/vindecode" } } # Cache stats endpoint @app.get("/api/v1/cache/stats") async def cache_stats(request: Request, token: str = Depends(verify_bearer_token)): """Get cache statistics""" try: cache = request.app.state.cache_service stats = await cache.get_stats() return stats except Exception as e: logger.error(f"Failed to get cache stats: {e}") raise HTTPException(status_code=500, detail="Failed to retrieve cache statistics") if __name__ == "__main__": import uvicorn uvicorn.run( "api.main:app", host="0.0.0.0", port=8000, reload=settings.DEBUG, log_level="info" )