""" FastAPI Marine Species Identification API Main application entry point for the marine species identification API. """ import asyncio from contextlib import asynccontextmanager from fastapi import FastAPI, HTTPException, Request from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import JSONResponse, HTMLResponse from fastapi.staticfiles import StaticFiles from fastapi.templating import Jinja2Templates import uvicorn from app.core.config import settings from app.core.logging import setup_logging, get_logger from app.api.v1.api import api_router from app.services.model_service import model_service # Setup logging setup_logging() logger = get_logger(__name__) @asynccontextmanager async def lifespan(app: FastAPI): """ Application lifespan manager. Handles startup and shutdown events. """ # Startup logger.info("Starting Marine Species Identification API...") try: # Ensure model is available and loaded await model_service.ensure_model_available() logger.info("Model loaded successfully") except Exception as e: logger.error(f"Failed to load model during startup: {str(e)}") # Don't fail startup - let health checks handle this logger.info("API startup completed") yield # Shutdown logger.info("Shutting down Marine Species Identification API...") # Create FastAPI application app = FastAPI( title=settings.PROJECT_NAME, version=settings.VERSION, description=settings.DESCRIPTION, openapi_url=f"{settings.API_V1_STR}/openapi.json", docs_url="/docs", redoc_url="/redoc", lifespan=lifespan ) # Mount static files and templates (for dashboard) app.mount("/static", StaticFiles(directory="static"), name="static") templates = Jinja2Templates(directory="templates") # Add CORS middleware app.add_middleware( CORSMiddleware, allow_origins=["*"], # Configure appropriately for production allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # 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: {str(exc)}", exc_info=True) return JSONResponse( status_code=500, content={ "error": "Internal Server Error", "message": "An unexpected error occurred", "details": str(exc) if settings.DEBUG else None } ) # Include API router app.include_router(api_router, prefix=settings.API_V1_STR) # Dashboard endpoint (new web interface) @app.get("/", response_class=HTMLResponse, tags=["Dashboard"]) async def dashboard(request: Request): """ Main dashboard for species identification. Provides a web interface for testing the API. """ return templates.TemplateResponse("dashboard.html", { "request": request, "active_tab": "dashboard" }) # API root endpoint (moved to /api for clarity) @app.get("/api", tags=["Root"]) async def api_root(): """ API root endpoint providing basic API information. """ return { "message": "Seamo Species API", "version": settings.VERSION, "docs": "/docs", "health": f"{settings.API_V1_STR}/health", "api_info": f"{settings.API_V1_STR}/info", "dashboard": "/" } # Health check endpoint at root level (for load balancers) @app.get("/health", tags=["Health"]) async def root_health(): """Simple health check at root level.""" return {"status": "ok"} if __name__ == "__main__": # Run the application uvicorn.run( "app.main:app", host=settings.HOST, port=settings.PORT, reload=False, # Set to True for development log_level="info" )