Phase 3 (Part 1): API Infrastructure - FastAPI, Database, Redis, gRPC Client

Completed Tasks (T027-T032):
-  FastAPI application with structured logging, CORS, global error handlers
-  Pydantic Settings for environment configuration
-  SQLAlchemy async engine with session management
-  Alembic migration environment setup
-  Redis async client with connection pooling
-  gRPC SDK Bridge client (placeholder - awaiting protobuf generation)

Next: JWT utilities, middleware, database models

🤖 Generated with Claude Code
This commit is contained in:
Geutebruck API Developer
2025-12-09 08:49:08 +01:00
parent 48fafae9d2
commit 12c4e1ca9c
6 changed files with 724 additions and 0 deletions

136
src/api/main.py Normal file
View File

@@ -0,0 +1,136 @@
"""
Geutebruck Cross-Switching API
FastAPI application entry point
"""
from fastapi import FastAPI, Request, status
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
from fastapi.exceptions import RequestValidationError
import structlog
import sys
from pathlib import Path
# Add src/api to Python path for imports
sys.path.insert(0, str(Path(__file__).parent))
from config import settings
# Configure structured logging
structlog.configure(
processors=[
structlog.processors.TimeStamper(fmt="iso"),
structlog.stdlib.add_log_level,
structlog.processors.JSONRenderer() if settings.LOG_FORMAT == "json" else structlog.dev.ConsoleRenderer()
],
wrapper_class=structlog.stdlib.BoundLogger,
context_class=dict,
logger_factory=structlog.stdlib.LoggerFactory(),
)
logger = structlog.get_logger()
# Create FastAPI app
app = FastAPI(
title=settings.API_TITLE,
version=settings.API_VERSION,
description="REST API for Geutebruck GeViScope/GeViSoft Cross-Switching Control",
docs_url="/docs",
redoc_url="/redoc",
openapi_url="/openapi.json"
)
# CORS middleware
app.add_middleware(
CORSMiddleware,
allow_origins=settings.CORS_ORIGINS,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Global exception handlers
@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request: Request, exc: RequestValidationError):
"""Handle validation errors"""
logger.warning("validation_error", errors=exc.errors(), body=exc.body)
return JSONResponse(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
content={
"error": "Validation Error",
"detail": exc.errors(),
},
)
@app.exception_handler(Exception)
async def global_exception_handler(request: Request, exc: Exception):
"""Handle unexpected errors"""
logger.error("unexpected_error", exc_info=exc)
return JSONResponse(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
content={
"error": "Internal Server Error",
"message": "An unexpected error occurred" if settings.ENVIRONMENT == "production" else str(exc),
},
)
# Startup event
@app.on_event("startup")
async def startup_event():
"""Initialize services on startup"""
logger.info("startup",
api_title=settings.API_TITLE,
version=settings.API_VERSION,
environment=settings.ENVIRONMENT)
# TODO: Initialize database connection pool
# TODO: Initialize Redis connection
# TODO: Initialize gRPC SDK Bridge client
logger.info("startup_complete")
# Shutdown event
@app.on_event("shutdown")
async def shutdown_event():
"""Cleanup on shutdown"""
logger.info("shutdown")
# TODO: Close database connections
# TODO: Close Redis connections
# TODO: Close gRPC connections
# Health check endpoint
@app.get("/health", tags=["system"])
async def health_check():
"""Health check endpoint"""
return {
"status": "healthy",
"version": settings.API_VERSION,
"environment": settings.ENVIRONMENT
}
# Root endpoint
@app.get("/", tags=["system"])
async def root():
"""API root endpoint"""
return {
"name": settings.API_TITLE,
"version": settings.API_VERSION,
"docs": "/docs",
"health": "/health"
}
# Register routers (TODO: will add as we implement phases)
# from routers import auth, cameras, monitors, crossswitch
# app.include_router(auth.router, prefix="/api/v1/auth", tags=["authentication"])
# app.include_router(cameras.router, prefix="/api/v1/cameras", tags=["cameras"])
# app.include_router(monitors.router, prefix="/api/v1/monitors", tags=["monitors"])
# app.include_router(crossswitch.router, prefix="/api/v1", tags=["crossswitch"])
if __name__ == "__main__":
import uvicorn
uvicorn.run(
"main:app",
host=settings.API_HOST,
port=settings.API_PORT,
reload=settings.ENVIRONMENT == "development"
)