face-intel / api /main.py
Marwan
Restructure + add reverse face search (PimEyes-style)
f5eeb1c
Raw
History Blame Contribute Delete
3.22 kB
"""
FastAPI app factory.
Constructs the ServiceContainer (composition root), mounts middleware,
registers routes, and serves the UI.
"""
from __future__ import annotations
from contextlib import asynccontextmanager
from pathlib import Path
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
from loguru import logger
from api.container import build_container
from api.middleware import (
RequestContextMiddleware,
RateLimitMiddleware,
RequestSizeLimitMiddleware,
GlobalExceptionMiddleware,
)
from api.routes import (
health, stats, providers, cache, jobs, faces, search, export, analysis, osint,
)
from config.settings import Settings, UI_DIR
from utils.logging import setup_logging
def create_app(settings: Settings | None = None) -> FastAPI:
"""Application factory — composes the entire dependency graph."""
settings = settings or Settings()
setup_logging(settings)
@asynccontextmanager
async def lifespan(app: FastAPI):
logger.info(f"Starting {settings.app_name} v{settings.app_version}")
container = build_container(settings)
app.state.container = container
app.state.settings = settings
logger.info(f"Registered providers: {container.registry.list_names()}")
yield
logger.info("Shutting down")
container.database.close()
app = FastAPI(
title=settings.app_name,
version=settings.app_version,
description="Multi-provider face intelligence platform",
lifespan=lifespan,
)
# Middleware (order matters — outermost first)
app.add_middleware(
CORSMiddleware,
allow_origins=settings.cors_origins,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
app.add_middleware(GlobalExceptionMiddleware)
app.add_middleware(RequestSizeLimitMiddleware, max_bytes=settings.max_request_body_bytes)
app.add_middleware(RateLimitMiddleware, requests_per_minute=settings.rate_limit_per_minute)
app.add_middleware(RequestContextMiddleware)
# Routers
app.include_router(health.router, prefix="/health", tags=["health"])
app.include_router(stats.router, prefix="/stats", tags=["stats"])
app.include_router(providers.router, prefix="/providers", tags=["providers"])
app.include_router(cache.router, prefix="/cache", tags=["cache"])
app.include_router(jobs.router, prefix="/jobs", tags=["jobs"])
app.include_router(faces.router, prefix="/faces", tags=["faces"])
app.include_router(search.router, prefix="/search", tags=["search"])
app.include_router(analysis.router, prefix="/analysis", tags=["analysis"])
app.include_router(osint.router, prefix="/osint", tags=["osint"])
app.include_router(export.router, prefix="/export", tags=["export"])
# UI
if UI_DIR.exists() and (UI_DIR / "index.html").exists():
app.mount("/", StaticFiles(directory=str(UI_DIR), html=True), name="ui")
else:
@app.get("/")
async def root():
return {"app": settings.app_name, "version": settings.app_version,
"docs": "/docs"}
return app