# 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, health_check as db_health_check from app.api import ingredients, stock, classification from app.services.inventory_alert_service import InventoryAlertService 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") # Initialize alert service alert_service = InventoryAlertService(settings) await alert_service.start() logger.info("Inventory alert service started") # Store alert service in app state app.state.alert_service = alert_service # 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: # Stop alert service if hasattr(app.state, 'alert_service'): await app.state.alert_service.stop() logger.info("Alert service stopped") 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(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) # Include enhanced routers from app.api.dashboard import router as dashboard_router from app.api.food_safety import router as food_safety_router app.include_router(dashboard_router, prefix=settings.API_V1_STR) app.include_router(food_safety_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" } @app.get("/health") async def health_check(): """Comprehensive health check endpoint""" try: return { "status": "healthy", "service": settings.SERVICE_NAME, "version": settings.VERSION, "timestamp": structlog.get_logger().info("Health check requested") } except Exception as e: logger.error("Health check failed", error=str(e)) return { "status": "unhealthy", "service": settings.SERVICE_NAME, "version": settings.VERSION, "database": "unknown", "alert_service": {"status": "unknown"}, "error": str(e), "timestamp": structlog.get_logger().info("Health check failed") } # 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", "food_safety_compliance", "temperature_monitoring", "dashboard_analytics", "business_model_detection", "real_time_alerts", "regulatory_reporting" ] } 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" )