Coverage for src/semware/main.py: 90%
41 statements
« prev ^ index » next coverage.py v7.10.6, created at 2025-09-09 02:16 -0700
« prev ^ index » next coverage.py v7.10.6, created at 2025-09-09 02:16 -0700
1"""Main FastAPI application for SemWare."""
3from datetime import datetime
5from fastapi import FastAPI, Request, status
6from fastapi.exceptions import RequestValidationError
7from fastapi.responses import JSONResponse
8from loguru import logger
10from .config import settings
11from .models.requests import HealthResponse
12from .models.schemas import ErrorResponse
13from .utils.logging import setup_logging
16def create_app() -> FastAPI:
17 """Create and configure the FastAPI application."""
19 # Setup logging
20 setup_logging(
21 level=settings.log_level,
22 log_file=settings.log_file,
23 )
25 # Create data directory if it doesn't exist
26 settings.db_path.mkdir(parents=True, exist_ok=True)
27 logger.info(f"Data directory: {settings.db_path}")
29 # Create FastAPI app
30 app = FastAPI(
31 title=settings.app_name,
32 version=settings.app_version,
33 description="Semantic search API server using vector databases and ML embeddings",
34 debug=settings.debug,
35 docs_url="/docs" if settings.debug else None,
36 redoc_url="/redoc" if settings.debug else None,
37 )
39 # Add middleware and exception handlers
40 setup_exception_handlers(app)
42 # Add routes
43 setup_routes(app)
45 logger.info(f"FastAPI app created: {settings.app_name} v{settings.app_version}")
46 return app
49def setup_exception_handlers(app: FastAPI) -> None:
50 """Setup custom exception handlers."""
52 @app.exception_handler(RequestValidationError)
53 async def validation_exception_handler(
54 request: Request, exc: RequestValidationError
55 ):
56 """Handle request validation errors."""
57 logger.warning(f"Validation error for {request.url}: {exc}")
58 return JSONResponse(
59 status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
60 content=ErrorResponse(
61 error="Validation Error", detail=str(exc), error_code="VALIDATION_ERROR"
62 ).model_dump(),
63 )
65 @app.exception_handler(Exception)
66 async def general_exception_handler(request: Request, exc: Exception):
67 """Handle general exceptions."""
68 logger.exception(f"Unhandled exception for {request.url}: {exc}")
69 return JSONResponse(
70 status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
71 content=ErrorResponse(
72 error="Internal Server Error",
73 detail="An unexpected error occurred",
74 error_code="INTERNAL_ERROR",
75 ).model_dump(),
76 )
79def setup_routes(app: FastAPI) -> None:
80 """Setup API routes."""
82 @app.get("/health", response_model=HealthResponse, tags=["Health"])
83 async def health_check():
84 """Health check endpoint."""
85 return HealthResponse(
86 status="healthy",
87 app_name=settings.app_name,
88 version=settings.app_version,
89 timestamp=datetime.now().isoformat(),
90 )
92 @app.get("/", tags=["Root"])
93 async def root():
94 """Root endpoint."""
95 return {
96 "message": f"Welcome to {settings.app_name} v{settings.app_version}",
97 "docs_url": (
98 "/docs" if settings.debug else "Documentation disabled in production"
99 ),
100 }
102 # Import and include routers
103 from .api.data import router as data_router
104 from .api.search import router as search_router
105 from .api.tables import router as tables_router
107 app.include_router(tables_router, prefix="/tables", tags=["Tables"])
108 app.include_router(data_router, prefix="/tables", tags=["Data"])
109 app.include_router(search_router, prefix="/tables", tags=["Search"])
112# Create the app instance
113app = create_app()
116if __name__ == "__main__":
117 import uvicorn
119 logger.info(f"Starting SemWare server on {settings.host}:{settings.port}")
120 uvicorn.run(
121 "semware.main:app",
122 host=settings.host,
123 port=settings.port,
124 workers=settings.workers,
125 reload=settings.debug,
126 log_level=settings.log_level.lower(),
127 )