# services/inventory/app/main.py """ Inventory Service FastAPI Application """ import os from contextlib import asynccontextmanager from fastapi import FastAPI, Request from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import JSONResponse import structlog # Import core modules from app.core.config import settings from app.core.database import init_db, close_db from app.api import ingredients, stock, classification from shared.monitoring.health import router as health_router from shared.monitoring.metrics import setup_metrics_early # Auth decorators are used in endpoints, no global setup needed logger = structlog.get_logger() @asynccontextmanager async def lifespan(app: FastAPI): """Application lifespan management""" # Startup logger.info("Starting Inventory Service", version=settings.VERSION) try: # Initialize database await init_db() logger.info("Database initialized successfully") # Setup metrics is already done early - no need to do it here logger.info("Metrics setup completed") yield except Exception as e: logger.error("Startup failed", error=str(e)) raise finally: # Shutdown logger.info("Shutting down Inventory Service") try: await close_db() logger.info("Database connections closed") except Exception as e: logger.error("Shutdown error", error=str(e)) # Create FastAPI application app = FastAPI( title=settings.APP_NAME, description=settings.DESCRIPTION, version=settings.VERSION, openapi_url=f"{settings.API_V1_STR}/openapi.json", docs_url=f"{settings.API_V1_STR}/docs", redoc_url=f"{settings.API_V1_STR}/redoc", lifespan=lifespan ) # Setup metrics BEFORE any middleware and BEFORE lifespan metrics_collector = setup_metrics_early(app, "inventory-service") # CORS middleware app.add_middleware( CORSMiddleware, allow_origins=settings.CORS_ORIGINS, allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # Auth is handled via decorators in individual endpoints # Exception handlers @app.exception_handler(ValueError) async def value_error_handler(request: Request, exc: ValueError): """Handle validation errors""" return JSONResponse( status_code=400, content={ "error": "Validation Error", "detail": str(exc), "type": "value_error" } ) @app.exception_handler(Exception) async def general_exception_handler(request: Request, exc: Exception): """Handle general exceptions""" logger.error( "Unhandled exception", error=str(exc), path=request.url.path, method=request.method ) return JSONResponse( status_code=500, content={ "error": "Internal Server Error", "detail": "An unexpected error occurred", "type": "internal_error" } ) # Include routers app.include_router(health_router, prefix="/health", tags=["health"]) app.include_router(ingredients.router, prefix=settings.API_V1_STR) app.include_router(stock.router, prefix=settings.API_V1_STR) app.include_router(classification.router, prefix=settings.API_V1_STR) # Root endpoint @app.get("/") async def root(): """Root endpoint with service information""" return { "service": settings.SERVICE_NAME, "version": settings.VERSION, "description": settings.DESCRIPTION, "status": "running", "docs_url": f"{settings.API_V1_STR}/docs", "health_url": "/health" } # Service info endpoint @app.get(f"{settings.API_V1_STR}/info") async def service_info(): """Service information endpoint""" return { "service": settings.SERVICE_NAME, "version": settings.VERSION, "description": settings.DESCRIPTION, "api_version": "v1", "environment": settings.ENVIRONMENT, "features": [ "ingredient_management", "stock_tracking", "expiration_alerts", "low_stock_alerts", "batch_tracking", "fifo_consumption", "barcode_support" ] } if __name__ == "__main__": import uvicorn uvicorn.run( "app.main:app", host="0.0.0.0", port=8000, reload=os.getenv("RELOAD", "false").lower() == "true", log_level="info" )