Some checks failed
CI/CD Pipeline / Build Docker Images (svc-rpa) (push) Has been cancelled
CI/CD Pipeline / Code Quality & Linting (push) Has been cancelled
CI/CD Pipeline / Security Scanning (svc-kg) (push) Has been cancelled
CI/CD Pipeline / Policy Validation (push) Has been cancelled
CI/CD Pipeline / Test Suite (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (svc-coverage) (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (svc-extract) (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (svc-firm-connectors) (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (svc-forms) (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (svc-hmrc) (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (svc-ingestion) (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (svc-kg) (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (svc-normalize-map) (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (svc-ocr) (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (svc-rag-indexer) (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (svc-rag-retriever) (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (svc-reason) (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (ui-review) (push) Has been cancelled
CI/CD Pipeline / Security Scanning (svc-coverage) (push) Has been cancelled
CI/CD Pipeline / Security Scanning (svc-extract) (push) Has been cancelled
CI/CD Pipeline / Security Scanning (svc-rag-retriever) (push) Has been cancelled
CI/CD Pipeline / Security Scanning (ui-review) (push) Has been cancelled
CI/CD Pipeline / Generate SBOM (push) Has been cancelled
CI/CD Pipeline / Deploy to Staging (push) Has been cancelled
CI/CD Pipeline / Deploy to Production (push) Has been cancelled
CI/CD Pipeline / Notifications (push) Has been cancelled
132 lines
4.3 KiB
Python
132 lines
4.3 KiB
Python
"""Factory for creating FastAPI applications with consistent setup."""
|
|
|
|
# FILE: libs/app_factory.py
|
|
|
|
from collections.abc import AsyncIterator, Awaitable, Callable
|
|
from contextlib import asynccontextmanager
|
|
from typing import Any
|
|
|
|
from fastapi import FastAPI, HTTPException, Request
|
|
from fastapi.responses import JSONResponse
|
|
|
|
from libs.config import BaseAppSettings, get_default_settings
|
|
from libs.observability import setup_observability
|
|
from libs.schemas import ErrorResponse
|
|
from libs.security import get_current_user, get_tenant_id
|
|
from libs.security.middleware import TrustedProxyMiddleware
|
|
|
|
|
|
def create_trusted_proxy_middleware(
|
|
internal_cidrs: list[str], disable_auth: bool = False
|
|
) -> TrustedProxyMiddleware:
|
|
"""Create a TrustedProxyMiddleware instance with the given internal CIDRs."""
|
|
|
|
# This is a factory function that will be called by FastAPI's add_middleware
|
|
# We return a partial function that creates the middleware
|
|
def middleware_factory(app: Any) -> TrustedProxyMiddleware:
|
|
return TrustedProxyMiddleware(app, internal_cidrs, disable_auth)
|
|
|
|
return middleware_factory # type: ignore
|
|
|
|
|
|
def create_app( # pylint: disable=too-many-arguments,too-many-positional-arguments
|
|
service_name: str,
|
|
title: str,
|
|
description: str,
|
|
version: str = "1.0.0",
|
|
settings_class: type[BaseAppSettings] = BaseAppSettings,
|
|
custom_settings: dict[str, Any] | None = None,
|
|
startup_hooks: list[Callable[[], Awaitable[None]]] | None = None,
|
|
shutdown_hooks: list[Callable[[], Awaitable[None]]] | None = None,
|
|
) -> tuple[FastAPI, BaseAppSettings]:
|
|
"""Create a FastAPI application with standard configuration"""
|
|
|
|
# Create settings
|
|
settings_kwargs = {"service_name": service_name}
|
|
if custom_settings:
|
|
settings_kwargs.update(custom_settings)
|
|
|
|
settings = get_default_settings(**settings_kwargs)
|
|
if settings_class != BaseAppSettings:
|
|
# Use custom settings class
|
|
settings = settings_class(**settings_kwargs) # type: ignore
|
|
|
|
# Create lifespan context manager
|
|
@asynccontextmanager
|
|
async def lifespan(
|
|
app: FastAPI,
|
|
) -> AsyncIterator[None]: # pylint: disable=unused-argument
|
|
# Startup
|
|
setup_observability(settings)
|
|
if startup_hooks:
|
|
for hook in startup_hooks:
|
|
await hook()
|
|
yield
|
|
# Shutdown
|
|
if shutdown_hooks:
|
|
for hook in shutdown_hooks:
|
|
await hook()
|
|
|
|
# Create FastAPI app
|
|
app = FastAPI(
|
|
title=title, description=description, version=version, lifespan=lifespan
|
|
)
|
|
|
|
# Add middleware
|
|
app.add_middleware(
|
|
TrustedProxyMiddleware,
|
|
internal_cidrs=settings.internal_cidrs,
|
|
disable_auth=getattr(settings, "disable_auth", False),
|
|
)
|
|
|
|
# Add exception handlers
|
|
@app.exception_handler(HTTPException)
|
|
async def http_exception_handler(
|
|
request: Request, exc: HTTPException
|
|
) -> JSONResponse:
|
|
"""Handle HTTP exceptions with RFC7807 format"""
|
|
return JSONResponse(
|
|
status_code=exc.status_code,
|
|
content=ErrorResponse(
|
|
type=f"https://httpstatuses.com/{exc.status_code}",
|
|
title=exc.detail,
|
|
status=exc.status_code,
|
|
detail=exc.detail,
|
|
instance=str(request.url),
|
|
trace_id=getattr(request.state, "trace_id", None),
|
|
).model_dump(),
|
|
)
|
|
|
|
# Add health endpoints
|
|
@app.get("/healthz")
|
|
async def health_check() -> dict[str, str]:
|
|
"""Health check endpoint"""
|
|
return {
|
|
"status": "healthy",
|
|
"service": settings.service_name,
|
|
"version": version,
|
|
}
|
|
|
|
@app.get("/readyz")
|
|
async def readiness_check() -> dict[str, str]:
|
|
"""Readiness check endpoint"""
|
|
return {"status": "ready", "service": settings.service_name, "version": version}
|
|
|
|
@app.get("/livez")
|
|
async def liveness_check() -> dict[str, str]:
|
|
"""Liveness check endpoint"""
|
|
return {"status": "alive", "service": settings.service_name, "version": version}
|
|
|
|
return app, settings
|
|
|
|
|
|
# Dependency factories
|
|
def get_user_dependency() -> Any:
|
|
"""Get user dependency function"""
|
|
return get_current_user()
|
|
|
|
|
|
def get_tenant_dependency() -> Any:
|
|
"""Get tenant dependency function"""
|
|
return get_tenant_id()
|