P0 fixes: - CORS: replace wildcard methods/headers with configurable lists - deps.py: remove unsafe global _warned_unset variable P1 fixes: - http_client: read default timeout from Settings - bzzoiro: replace sync urllib with async httpx - bzzoiro: normalize validation failures use warning level only - db pool: read pool config from Settings (default 5+10) - backtest: add asyncio.Semaphore(8) for concurrent execution - predict/context_builder: add backtest parameter for cutoff buffer P2 improvements: - injuries: enforce int conversion for player_id/fixture_id - injuries: use system temp dir for cache - utils.py: extract shared actual_1x2/is_correct_1x2 - validation: downgrade 1x2 mismatch log to debug - docker-compose: use env vars for all credentials - .env.example: add POSTGRES_USER/PASSWORD/PORT, API_PORT
74 lines
2.1 KiB
Python
74 lines
2.1 KiB
Python
"""FastAPI 应用工厂。"""
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
from collections.abc import AsyncIterator
|
|
from contextlib import asynccontextmanager
|
|
|
|
from fastapi import FastAPI
|
|
from fastapi.middleware.cors import CORSMiddleware
|
|
|
|
from src.core.config import settings
|
|
|
|
|
|
@asynccontextmanager
|
|
async def lifespan(app: FastAPI) -> AsyncIterator[None]:
|
|
from src.db.base import init_db
|
|
from src.core.http_client import close_client
|
|
await init_db() # 验证连接,不建表
|
|
yield
|
|
await close_client()
|
|
|
|
|
|
def create_app() -> FastAPI:
|
|
app = FastAPI(
|
|
title="Profeto API",
|
|
description="足球数据 + LLM 预测服务",
|
|
version="0.1.0",
|
|
lifespan=lifespan,
|
|
)
|
|
|
|
origins = [o.strip() for o in settings.CORS_ORIGINS.split(",") if o.strip()]
|
|
methods = [m.strip() for m in settings.CORS_METHODS.split(",") if m.strip()]
|
|
headers = [h.strip() for h in settings.CORS_HEADERS.split(",") if h.strip()]
|
|
app.add_middleware(
|
|
CORSMiddleware,
|
|
allow_origins=origins,
|
|
allow_credentials=True,
|
|
allow_methods=methods,
|
|
allow_headers=headers,
|
|
)
|
|
|
|
from src.api.routes.matches import router as matches_router
|
|
from src.api.routes.predict import router as predict_router
|
|
from src.api.routes.ingest import router as ingest_router
|
|
from src.api.routes.eval import router as eval_router
|
|
from src.api.routes.backtest import router as backtest_router
|
|
|
|
app.include_router(matches_router)
|
|
app.include_router(predict_router)
|
|
app.include_router(ingest_router)
|
|
app.include_router(eval_router)
|
|
app.include_router(backtest_router)
|
|
|
|
@app.get("/health")
|
|
async def health():
|
|
return {"status": "healthy", "service": "profeto"}
|
|
|
|
|
|
@app.get("/health/ready")
|
|
async def health_ready():
|
|
"""就绪检查: 验证数据库连接。"""
|
|
from src.db.base import engine
|
|
try:
|
|
async with engine.begin() as conn:
|
|
await conn.run_sync(lambda conn: None)
|
|
return {"status": "ready"}
|
|
except Exception:
|
|
return {"status": "not_ready"}
|
|
|
|
return app
|
|
|
|
|
|
app = create_app()
|