Files
webber/webber-api/src/main.py
T
jpmschweitzerandClaude Opus 4.5 2523db4da7
Build and Push API / release (push) Successful in 3s
Build and Push API / build (push) Failing after 34s
feat: add conversation persistence and context management layer
- SQLAlchemy async database layer (SQLite dev, PostgreSQL prod)
- Conversation and Message models with UUID primary keys
- Token counting utilities using litellm
- Context summarization at 80% token threshold
- REST API endpoints for multi-turn conversations
- 19 conversation tests, 6 token tests (176 total passing)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-11 22:42:38 +01:00

122 lines
3.2 KiB
Python

"""
Webber - Multi-Agent AI Development System
FastAPI application entry point.
NO routes here - all routes delegated to domain routers.
"""
from contextlib import asynccontextmanager
from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
from src.domains.router import root_router
from src.shared.auth import validate_api_key
from src.shared.config import get_settings
from src.shared.context import user_provider
from src.shared.exceptions import AppException
from src.shared.logging import get_logger, setup_logging
settings = get_settings()
setup_logging(settings.log_level)
logger = get_logger(__name__)
@asynccontextmanager
async def lifespan(app: FastAPI):
"""Application startup and shutdown."""
logger.info("=" * 60)
logger.info(f"Starting {settings.app_name} v{settings.app_version}")
logger.info(f"Debug: {settings.debug}")
logger.info(f"Port: {settings.port}")
logger.info(f"Ollama: {settings.ollama_url}")
logger.info(f"Agent model: {settings.ollama_agent_model}")
logger.info(f"Database: {settings.database_url}")
logger.info("=" * 60)
yield
# Cleanup
from src.db import get_database
try:
database = get_database()
await database.close()
except Exception:
pass
logger.info("Shutting down")
app = FastAPI(
title=settings.app_name,
version=settings.app_version,
description=settings.app_description,
docs_url="/docs",
redoc_url=None,
openapi_url="/openapi.json",
lifespan=lifespan,
debug=settings.debug,
)
# === Middleware ===
app.add_middleware(
CORSMiddleware,
allow_origins=settings.cors_origins,
allow_credentials=settings.cors_credentials,
allow_methods=settings.cors_methods,
allow_headers=settings.cors_headers,
)
@app.middleware("http")
async def auth_middleware(request: Request, call_next):
"""
Extract and validate API key, set user context.
Allows unauthenticated requests - individual routes decide if auth is required.
"""
api_key = request.headers.get("X-API-Key")
if api_key:
user = await validate_api_key(api_key)
if user:
user_provider.set_user(user)
try:
response = await call_next(request)
return response
finally:
user_provider.clear_user()
# === Exception Handlers ===
@app.exception_handler(AppException)
async def app_exception_handler(request: Request, exc: AppException):
"""Handle application exceptions."""
logger.warning(f"AppException: {exc.error_code} - {exc.message}")
return JSONResponse(
status_code=exc.status_code,
content=exc.to_dict(),
)
@app.exception_handler(Exception)
async def global_exception_handler(request: Request, exc: Exception):
"""Catch-all exception handler."""
logger.error(f"Unhandled exception: {exc}", exc_info=True)
return JSONResponse(
status_code=500,
content={
"error": "InternalServerError",
"message": "Internal server error",
"details": {"type": type(exc).__name__},
}
)
# === Routes ===
app.include_router(root_router)