Initial Commit
This commit is contained in:
202
mvp-platform-services/vehicles/api/main.py
Normal file
202
mvp-platform-services/vehicles/api/main.py
Normal file
@@ -0,0 +1,202 @@
|
||||
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"
|
||||
)
|
||||
Reference in New Issue
Block a user