feat: initial FastAPI boilerplate setup
Set up Webber - multi-agent AI development system with: - Domain-based project structure (src/domains/, src/shared/) - BaseController pattern with lazy router instantiation - Pydantic Settings configuration with env file support - Logger decorator with temporal benchmarking and trace IDs - UserProvider singleton for request-scoped context - Custom exception hierarchy - Health endpoints (/, /health) Dependencies (CVE checked 2026-01-09): - FastAPI 0.128.0, Starlette 0.50.0, Uvicorn 0.40.0 - Pydantic 2.12.4, PydanticAI 1.40.0 - All packages at latest safe versions Placeholder domains for future implementation: - agents/ (explore, plan, task) - tools/ (file, shell, search) - auth/ (tatlock integration) Port: 8086 (per CONTAINERS.md allocation) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
+116
@@ -0,0 +1,116 @@
|
||||
"""
|
||||
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.shared.config import get_settings
|
||||
from src.shared.logging import setup_logging, get_logger
|
||||
from src.shared.exceptions import AppException
|
||||
from src.shared.context import user_provider
|
||||
from src.shared.auth import validate_api_key
|
||||
from src.domains.router import root_router
|
||||
|
||||
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("=" * 60)
|
||||
|
||||
# TODO: Initialize resources (LLM clients, etc.)
|
||||
|
||||
yield
|
||||
|
||||
# Cleanup
|
||||
logger.info("Shutting down")
|
||||
|
||||
|
||||
app = FastAPI(
|
||||
title=settings.app_name,
|
||||
version=settings.app_version,
|
||||
description="Multi-Agent AI Development System",
|
||||
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)
|
||||
Reference in New Issue
Block a user