""" FastAPI main application for chocolate tempering machine control system. Provides REST API endpoints for system control and monitoring. """ import logging from contextlib import asynccontextmanager from typing import Dict, Any from fastapi import FastAPI, Request, HTTPException from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.trustedhost import TrustedHostMiddleware from fastapi.responses import JSONResponse import uvicorn from ...shared.config import settings from ...shared.database import init_database, create_tables, close_database from ..hardware.hardware_manager import hardware_manager from ..recipe.recipe_controller import recipe_controller from ..safety.safety_monitor import safety_monitor from ..safety.error_handler import error_handler from .routers import recipes, process, hardware, users, system, health logger = logging.getLogger(__name__) @asynccontextmanager async def lifespan(app: FastAPI): """Application lifespan manager for startup and shutdown tasks.""" # Startup logger.info("Starting chocolate tempering machine control system") try: # Initialize database init_database() await create_tables() logger.info("Database initialized") # Initialize hardware manager if await hardware_manager.initialize(): logger.info("Hardware manager initialized") else: logger.error("Hardware manager initialization failed") # Initialize safety monitor if await safety_monitor.initialize(): logger.info("Safety monitor initialized") else: logger.error("Safety monitor initialization failed") logger.info("System startup completed successfully") except Exception as e: logger.critical(f"System startup failed: {e}") raise yield # Shutdown logger.info("Shutting down chocolate tempering machine control system") try: # Stop any running recipes await recipe_controller.emergency_stop(reason="System shutdown") # Shutdown safety monitor await safety_monitor.shutdown() # Shutdown hardware manager await hardware_manager.shutdown() # Close database connections await close_database() logger.info("System shutdown completed") except Exception as e: logger.error(f"Error during shutdown: {e}") # Create FastAPI application app = FastAPI( title=settings.web.api_title, version=settings.web.api_version, description="Industrial chocolate tempering machine control system API", docs_url="/docs", redoc_url="/redoc", openapi_url="/openapi.json", lifespan=lifespan, ) # Add middleware app.add_middleware( CORSMiddleware, allow_origins=settings.web.cors_origins, allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) app.add_middleware( TrustedHostMiddleware, allowed_hosts=["*"] # Configure appropriately for production ) # Global exception handler @app.exception_handler(Exception) async def global_exception_handler(request: Request, exc: Exception): """Global exception handler for unhandled errors.""" logger.error(f"Unhandled exception in {request.url}: {exc}") # Handle error through error handling system await error_handler.handle_error(exc, "web_api", { "url": str(request.url), "method": request.method, "client": request.client.host if request.client else "unknown" }) return JSONResponse( status_code=500, content={ "error": "Internal server error", "message": "An unexpected error occurred", "request_id": getattr(request.state, "request_id", "unknown") } ) # HTTP exception handler @app.exception_handler(HTTPException) async def http_exception_handler(request: Request, exc: HTTPException): """Handler for HTTP exceptions.""" return JSONResponse( status_code=exc.status_code, content={ "error": exc.detail, "status_code": exc.status_code } ) # Request ID middleware @app.middleware("http") async def add_request_id(request: Request, call_next): """Add request ID for tracing.""" import uuid request_id = str(uuid.uuid4()) request.state.request_id = request_id response = await call_next(request) response.headers["X-Request-ID"] = request_id return response # Include routers app.include_router( health.router, prefix="/health", tags=["health"] ) app.include_router( recipes.router, prefix="/api/v1/recipes", tags=["recipes"] ) app.include_router( process.router, prefix="/api/v1/process", tags=["process"] ) app.include_router( hardware.router, prefix="/api/v1/hardware", tags=["hardware"] ) app.include_router( users.router, prefix="/api/v1/users", tags=["users"] ) app.include_router( system.router, prefix="/api/v1/system", tags=["system"] ) # Root endpoint @app.get("/", response_model=Dict[str, Any]) async def root(): """Root endpoint with system information.""" return { "name": settings.app_name, "version": settings.app_version, "environment": settings.environment, "api_version": "v1", "docs_url": "/docs", "health_url": "/health" } # API info endpoint @app.get("/api/v1/info", response_model=Dict[str, Any]) async def api_info(): """API information endpoint.""" return { "api_title": settings.web.api_title, "api_version": settings.web.api_version, "environment": settings.environment, "features": { "recipe_management": True, "process_control": True, "hardware_monitoring": True, "safety_monitoring": True, "user_management": True, "real_time_monitoring": True, }, "endpoints": { "recipes": "/api/v1/recipes", "process": "/api/v1/process", "hardware": "/api/v1/hardware", "users": "/api/v1/users", "system": "/api/v1/system", "health": "/health", } } def create_app() -> FastAPI: """Factory function to create FastAPI app.""" return app def run_server(): """Run the development server.""" uvicorn.run( "tempering_machine.services.web.main:app", host=settings.web.host, port=settings.web.port, reload=settings.web.reload, workers=settings.web.workers if not settings.web.reload else 1, access_log=settings.web.access_log, log_level=settings.log_level.lower(), ) if __name__ == "__main__": run_server()