98 lines
3.3 KiB
Python
98 lines
3.3 KiB
Python
from contextlib import asynccontextmanager
|
|
|
|
from fastapi import FastAPI
|
|
from fastapi.exceptions import RequestValidationError
|
|
from fastapi.middleware.cors import CORSMiddleware
|
|
from fastapi.responses import JSONResponse
|
|
from sqlalchemy import text
|
|
from sqlalchemy.exc import IntegrityError, OperationalError, ProgrammingError
|
|
from sqlalchemy.orm.exc import StaleDataError
|
|
|
|
from app.api.routes import auth, chat, documents, health, knowledge, retrieval, sources
|
|
from app.core.config import settings
|
|
from app.db.session import engine
|
|
from app.services.auth import dummy_hash
|
|
from app.services.retrieval import tokenizer
|
|
|
|
|
|
def create_app(db_engine=engine) -> FastAPI:
|
|
@asynccontextmanager
|
|
async def lifespan(app: FastAPI):
|
|
try:
|
|
with db_engine.connect() as connection:
|
|
revision = connection.scalar(text("SELECT version_num FROM alembic_version"))
|
|
if revision != "0004_auth_sessions":
|
|
raise RuntimeError(
|
|
"数据库版本不匹配,请先在 backend 目录执行 alembic upgrade head"
|
|
)
|
|
except ProgrammingError as exc:
|
|
raise RuntimeError(
|
|
"数据库尚未初始化,请先在 backend 目录执行 alembic upgrade head"
|
|
) from exc
|
|
tokenizer()
|
|
dummy_hash()
|
|
try:
|
|
yield
|
|
finally:
|
|
db_engine.dispose()
|
|
|
|
app = FastAPI(title=settings.app_name, version="0.4.0", lifespan=lifespan)
|
|
app.add_middleware(
|
|
CORSMiddleware,
|
|
allow_origins=settings.cors_origin_list,
|
|
allow_credentials=True,
|
|
allow_methods=["*"],
|
|
allow_headers=["*"],
|
|
)
|
|
for router in (
|
|
health.router,
|
|
auth.router,
|
|
chat.router,
|
|
knowledge.router,
|
|
sources.router,
|
|
documents.router,
|
|
retrieval.router,
|
|
):
|
|
app.include_router(router, prefix=settings.api_prefix)
|
|
|
|
@app.middleware("http")
|
|
async def no_cache_api(request, call_next):
|
|
response = await call_next(request)
|
|
if request.url.path.startswith(settings.api_prefix + "/"):
|
|
response.headers["Cache-Control"] = "no-store"
|
|
return response
|
|
|
|
@app.exception_handler(RequestValidationError)
|
|
async def validation_handler(request, exc):
|
|
# FastAPI's default includes raw input (including passwords) in error responses.
|
|
return JSONResponse(
|
|
status_code=422,
|
|
content={
|
|
"detail": [
|
|
{"loc": list(error["loc"]), "msg": error["msg"], "type": error["type"]}
|
|
for error in exc.errors()
|
|
]
|
|
},
|
|
)
|
|
|
|
@app.exception_handler(StaleDataError)
|
|
async def stale_data_handler(request, exc):
|
|
return JSONResponse(status_code=409, content={"detail": "记录已更新,请刷新后重试"})
|
|
|
|
@app.exception_handler(IntegrityError)
|
|
async def integrity_handler(request, exc):
|
|
return JSONResponse(status_code=409, content={"detail": "数据关联或约束冲突"})
|
|
|
|
@app.exception_handler(OperationalError)
|
|
async def database_unavailable_handler(request, exc):
|
|
return JSONResponse(status_code=503, content={"detail": "数据库暂不可用"})
|
|
|
|
@app.get("/")
|
|
def root():
|
|
return {"message": "Nalati Tourism QA API", "docs": "/docs"}
|
|
|
|
return app
|
|
|
|
|
|
app = create_app()
|