You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

FastAPI启动阶段覆盖Pydantic模型后接口文档未更新的问题

问题描述

在FastAPI应用中,需要创建一个作为输入参数的动态Pydantic模型,当启动阶段检查的某个条件为True时,覆盖该模型。目前的实现是在lifespan事件中重新赋值Model,但接口文档(/docs)仍然显示默认模型的参数限制,所有使用该模型的端点也没有同步更新。

最小复现代码如下:

from contextlib import asynccontextmanager
from typing import Annotated, Type

from fastapi import FastAPI, Depends, Query
from pydantic import BaseModel, create_model

def dynamic_model(lim=10) -> Type[BaseModel]:
    return create_model("DynamicModel", lim=(Annotated[int, Query(ge=1, le=lim)], 1))

Model = dynamic_model()

@asynccontextmanager
async def lifespan(_app: FastAPI):
    global Model
    something_is_true = True
    if something_is_true:
        Model = dynamic_model(lim=100)  # 尝试覆盖模型
        
    yield


app = FastAPI(lifespan=lifespan)


@app.get("/")
async def root(model: Annotated[Model, Depends()]):
    # 使用快捷方式通过查询参数调用端点
    return model

if __name__ == "__main__":
    import uvicorn
    uvicorn.run("main:app", reload=True)

当前问题:示例代码未能成功覆盖模型,接口文档仍显示默认模型的参数限制。


解决方案

方案一:启动前初始化模型(推荐)

既然条件仅在启动阶段检查,直接在创建FastAPI实例前完成模型的初始化,这样FastAPI生成OpenAPI Schema时就会使用正确的模型,无需后续修改。

修改后的代码:

from typing import Annotated, Type

from fastapi import FastAPI, Depends, Query
from pydantic import BaseModel, create_model

def dynamic_model(lim=10) -> Type[BaseModel]:
    return create_model("DynamicModel", lim=(Annotated[int, Query(ge=1, le=lim)], 1))

# 启动前检查条件并初始化模型
something_is_true = True
Model = dynamic_model(lim=100) if something_is_true else dynamic_model()

app = FastAPI()

@app.get("/")
async def root(model: Annotated[Model, Depends()]):
    return model

if __name__ == "__main__":
    import uvicorn
    uvicorn.run("main:app", reload=True)

方案二:动态依赖+手动更新Schema(适用于条件必须在lifespan中判断的场景)

如果条件必须在lifespan内检查(比如依赖启动时加载的资源或配置),可以通过依赖函数动态返回模型,并手动更新OpenAPI Schema:

修改后的代码:

from contextlib import asynccontextmanager
from typing import Annotated, Type

from fastapi import FastAPI, Depends, Query
from pydantic import BaseModel, create_model

def dynamic_model(lim=10) -> Type[BaseModel]:
    return create_model("DynamicModel", lim=(Annotated[int, Query(ge=1, le=lim)], 1))

# 存储当前使用的模型
current_model = dynamic_model()

def get_dynamic_model() -> Type[BaseModel]:
    return current_model

@asynccontextmanager
async def lifespan(app: FastAPI):
    global current_model
    something_is_true = True
    if something_is_true:
        current_model = dynamic_model(lim=100)
        # 更新所有使用该模型的端点参数定义
        for route in app.routes:
            if hasattr(route, "dependant"):
                for param in route.dependant.params:
                    # 找到绑定了动态模型依赖的参数
                    if param.dependency == get_dynamic_model:
                        param.type_ = current_model
                        # 更新字段的校验规则
                        param.field_info = current_model.__fields__["lim"].field_info
        # 重新生成OpenAPI Schema
        app.openapi()
    yield

app = FastAPI(lifespan=lifespan)

@app.get("/")
async def root(model: Annotated[BaseModel, Depends(get_dynamic_model)]):
    return model

if __name__ == "__main__":
    import uvicorn
    uvicorn.run("main:app", reload=True)

原方案失效原因

FastAPI在启动时会根据端点定义生成OpenAPI Schema,此时已经将初始的Model类型绑定到端点和Schema中。lifespan事件是在应用启动后才执行的,修改全局变量Model不会改变已经生成的Schema和端点绑定的类型,因此接口文档和参数校验都不会同步更新。

内容的提问来源于stack exchange,提问作者brnk

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 00:22:31