One request exercises every layer: JWT auth, quota reservation, a versioned prompt through the LLM gateway, a cost row, quota commit or refund, and the unified envelope. The trivial ping task proves the queue routing and the tk_task transitions before any real long-running task is written. Routes are mounted at the /api/v1 base path of 架构文档 §9.1; /v1/* is rewritten to it at the ASGI layer so both spellings work from one route table.
79 lines
2.5 KiB
Python
Executable File
79 lines
2.5 KiB
Python
Executable File
"""FastAPI application factory (架构文档 §3.1 L3 API 层).
|
|
|
|
The API layer only orchestrates: auth, validation, idempotency, quota,
|
|
unified response, OpenAPI. Business rules live in ``packages/<domain>``.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from collections.abc import AsyncIterator
|
|
from contextlib import asynccontextmanager
|
|
|
|
from fastapi import FastAPI
|
|
|
|
from apps.api.deps import build_llm_gateway
|
|
from apps.api.routers import agent, auth, health, quota, tasks
|
|
from packages.shared.config import Settings, get_settings
|
|
from packages.shared.db import dispose_engine
|
|
from packages.shared.handlers import install_exception_handlers
|
|
from packages.shared.logging import configure_logging, get_logger
|
|
from packages.shared.middleware import RequestContextMiddleware, install_version_alias
|
|
from packages.shared.observability import setup_tracing
|
|
from packages.shared.redis_client import close_redis
|
|
|
|
logger = get_logger(__name__)
|
|
|
|
|
|
@asynccontextmanager
|
|
async def lifespan(app: FastAPI) -> AsyncIterator[None]:
|
|
settings: Settings = get_settings()
|
|
app.state.llm_gateway = build_llm_gateway(settings)
|
|
logger.info(
|
|
"api_started",
|
|
env=settings.app_env,
|
|
api_prefix=settings.api_prefix,
|
|
llm_primary=settings.llm_primary_provider,
|
|
llm_model=settings.deepseek_model,
|
|
)
|
|
try:
|
|
yield
|
|
finally:
|
|
await app.state.llm_gateway.aclose()
|
|
await close_redis()
|
|
await dispose_engine()
|
|
logger.info("api_stopped")
|
|
|
|
|
|
def create_app(settings: Settings | None = None) -> FastAPI:
|
|
cfg = settings or get_settings()
|
|
configure_logging(
|
|
level=cfg.log_level, json_output=cfg.log_json, extra_redact_keys=cfg.extra_redact_keys
|
|
)
|
|
|
|
app = FastAPI(
|
|
title="美搭 backend",
|
|
version="0.1.0",
|
|
description=(
|
|
"AI 穿搭美学助手服务端。统一响应见架构文档 §9.2;"
|
|
"所有业务接口在 " + cfg.api_prefix + " 之下(/v1 为等价别名)。"
|
|
),
|
|
openapi_url=f"{cfg.api_prefix}/openapi.json",
|
|
docs_url=f"{cfg.api_prefix}/docs" if cfg.is_local else None,
|
|
redoc_url=None,
|
|
lifespan=lifespan,
|
|
)
|
|
|
|
install_version_alias(app, cfg.api_prefix)
|
|
app.add_middleware(RequestContextMiddleware)
|
|
install_exception_handlers(app)
|
|
setup_tracing(app, cfg)
|
|
|
|
app.include_router(health.router)
|
|
for router in (auth.router, agent.router, quota.router, tasks.router):
|
|
app.include_router(router, prefix=cfg.api_prefix)
|
|
|
|
return app
|
|
|
|
|
|
app = create_app()
|