#!/usr/bin/env python3 from __future__ import annotations from datetime import datetime, timezone from typing import Any, Dict import os import traceback import uvicorn from fastapi import FastAPI, Request from fastapi.exceptions import RequestValidationError from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import JSONResponse from starlette.exceptions import HTTPException as StarletteHTTPException from app.api.v1.api import api_router from app.core.config import settings from app.core.exceptions import BaseAPIException app = FastAPI( title=settings.PROJECT_NAME, description=settings.PROJECT_DESCRIPTION, version=settings.VERSION, openapi_url="/openapi.json", docs_url="/docs", redoc_url="/redoc", ) # Add CORS middleware app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # Add request logging middleware if not in production if os.getenv("ENVIRONMENT", "development") != "production": from app.core.middleware import RequestLoggingMiddleware app.add_middleware(RequestLoggingMiddleware) # Exception handlers @app.exception_handler(BaseAPIException) async def base_api_exception_handler(request: Request, exc: BaseAPIException) -> JSONResponse: """ Handle custom API exceptions. """ return JSONResponse( status_code=exc.status_code, content={ "error": { "code": exc.status_code, "message": exc.detail, "type": exc.__class__.__name__, } }, headers=exc.headers, ) @app.exception_handler(RequestValidationError) async def validation_exception_handler(request: Request, exc: RequestValidationError) -> JSONResponse: """ Handle request validation errors. """ errors = exc.errors() error_details = [] for error in errors: loc = ".".join(str(x) for x in error["loc"]) error_details.append( { "field": loc, "message": error["msg"], "type": error["type"], } ) return JSONResponse( status_code=400, content={ "error": { "code": 400, "message": "Validation Error", "type": "ValidationError", "details": error_details, } }, ) @app.exception_handler(StarletteHTTPException) async def http_exception_handler(request: Request, exc: StarletteHTTPException) -> JSONResponse: """ Handle standard HTTP exceptions. """ return JSONResponse( status_code=exc.status_code, content={ "error": { "code": exc.status_code, "message": exc.detail, "type": "HTTPException", } }, headers=exc.headers, ) @app.exception_handler(Exception) async def general_exception_handler(request: Request, exc: Exception) -> JSONResponse: """ Handle all other exceptions. """ # In production, you might want to log the error instead of returning the traceback error_detail = str(exc) if os.getenv("ENVIRONMENT", "development") == "development": error_detail = "".join(traceback.format_exception(type(exc), exc, exc.__traceback__)) return JSONResponse( status_code=500, content={ "error": { "code": 500, "message": "Internal Server Error", "type": exc.__class__.__name__, "detail": error_detail, } }, ) # Include API router app.include_router(api_router, prefix=settings.API_V1_STR) @app.get("/") async def root() -> Dict[str, Any]: """ Root endpoint that returns basic service information. """ return { "title": settings.PROJECT_NAME, "description": settings.PROJECT_DESCRIPTION, "version": settings.VERSION, "docs_url": "/docs", "redoc_url": "/redoc", "openapi_url": "/openapi.json", "health_check": "/health", "api_base_url": settings.API_V1_STR, } @app.get("/health", tags=["health"]) async def health_check() -> Dict[str, Any]: """ Health check endpoint to verify the service is running correctly. """ try: # Check database connection from app.db.session import SessionLocal db = SessionLocal() db.execute("SELECT 1") db.close() db_status = "connected" except Exception as e: # Using a general exception handler with specific error handling db_status = f"error: {e!s}" return { "status": "healthy", "timestamp": datetime.now(tz=timezone.utc).isoformat(), "version": settings.VERSION, "database": db_status, "environment": os.getenv("ENVIRONMENT", "development"), } if __name__ == "__main__": # Using localhost for development, configurable for production host = os.getenv("HOST", "127.0.0.1") port = int(os.getenv("PORT", "8000")) uvicorn.run("main:app", host=host, port=port, reload=True)