Spaces:
Running
Running
File size: 4,853 Bytes
69be42f dc3879e 69be42f dc3879e 69be42f dc3879e 69be42f dc3879e 69be42f dc3879e 69be42f dc3879e 69be42f dc3879e 69be42f dc3879e 69be42f dc3879e 69be42f dc3879e 69be42f dc3879e 69be42f dc3879e 69be42f |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 |
"""FastAPI application entry point.
[Task]: T047
[From]: specs/001-user-auth/plan.md
"""
import logging
from contextlib import asynccontextmanager
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
from datetime import datetime
import time
from core.database import init_db, engine
from core.config import get_settings
from api.auth import router as auth_router
from api.tasks import router as tasks_router
from api.chat import router as chat_router
from core.logging import setup_logging, get_logger
settings = get_settings()
# Setup structured logging
setup_logging()
logger = get_logger(__name__)
@asynccontextmanager
async def lifespan(app: FastAPI):
"""Application lifespan manager.
Handles startup and shutdown events with graceful connection cleanup.
"""
# Startup
logger.info("Starting up application...")
init_db()
logger.info("Database initialized")
# Track background tasks for graceful shutdown
background_tasks = set()
yield
# Shutdown - Graceful shutdown handler
logger.info("Shutting down application...")
# Close database connections
try:
logger.info("Closing database connections...")
await engine.dispose()
logger.info("Database connections closed")
except Exception as e:
logger.error(f"Error closing database: {e}")
# Wait for background tasks to complete (with timeout)
if background_tasks:
logger.info(f"Waiting for {len(background_tasks)} background tasks to complete...")
try:
# Wait up to 10 seconds for tasks to complete
import asyncio
await asyncio.wait_for(asyncio.gather(*background_tasks, return_exceptions=True), timeout=10.0)
logger.info("All background tasks completed")
except asyncio.TimeoutError:
logger.warning("Background tasks did not complete in time, forcing shutdown...")
logger.info("Application shutdown complete")
# Create FastAPI application
app = FastAPI(
title="Todo List API",
description="REST API for managing tasks with JWT authentication",
version="1.0.0",
lifespan=lifespan,
)
# Add CORS middleware
app.add_middleware(
CORSMiddleware,
allow_origins=[settings.frontend_url],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Include routers
app.include_router(auth_router) # Authentication endpoints
app.include_router(tasks_router) # Task management endpoints
app.include_router(chat_router) # AI chat endpoints (Phase III)
@app.get("/")
async def root():
"""Root endpoint."""
return {
"message": "Todo List API",
"status": "running",
"version": "2.0.0",
"authentication": "JWT",
"features": {
"task_management": "REST API for CRUD operations",
"ai_chatbot": "Natural language task creation and listing"
}
}
@app.get("/health")
async def health_check():
"""Health check endpoint.
Verifies database connectivity and application status.
Returns 503 if database is unavailable.
[Task]: T048
[From]: specs/001-user-auth/plan.md
"""
from sqlmodel import select
from models.user import User
from sqlmodel import Session
# Try to get database session
try:
# Create a simple query to test database connection
with Session(engine) as session:
# Execute a simple query (doesn't matter if it returns data)
session.exec(select(User).limit(1))
return {"status": "healthy", "database": "connected", "timestamp": datetime.utcnow().isoformat()}
except Exception as e:
logger.error(f"Health check failed: {e}")
raise HTTPException(
status_code=503,
detail="Service unavailable - database connection failed"
)
@app.get("/metrics")
async def metrics():
"""Metrics endpoint for monitoring.
Returns basic application metrics for Kubernetes health probes.
"""
return {
"status": "running",
"timestamp": datetime.utcnow().isoformat(),
"uptime_seconds": time.time(),
"version": "1.0.0",
"database": "connected" # Simplified - in production would check actual DB status
}
# Global exception handler
@app.exception_handler(HTTPException)
async def http_exception_handler(request, exc):
"""Global HTTP exception handler.
Returns consistent error format for all HTTP exceptions.
[Task]: T046
[From]: specs/001-user-auth/research.md
"""
return JSONResponse(
status_code=exc.status_code,
content={
"error": {
"status_code": exc.status_code,
"detail": exc.detail
}
}
)
|