FastAPI使用APIRouter返回列表报Cannot instantiate typing.Union错误问询
问题诱因
出现该错误的核心原因是FastAPI的APIRouter相较于全局@app装饰器,对返回值类型的推导和校验逻辑更严格,触发报错的具体原因如下:
- 接口存在两种完全不兼容的返回类型:404场景返回
JSONResponse对象,正常场景返回List[AuthModel]列表,APIRouter会自动将返回值推导为Union[JSONResponse, List[AuthModel]],而Pydantic无法直接实例化Union类型,触发报错。 AuthModel中使用了自定义的PyObjectId类型,未配置允许Pydantic解析自定义类型的相关参数,类型校验时无法识别该自定义类型。- 未显式声明接口的
response_model与函数返回值类型,APIRouter自动推导类型时容错性更低,无法正确解析包含自定义类型、可选类型的复合返回结构。
解决方案
按照以下步骤修改即可通过router正常返回数据:
- 统一返回类型,替换自定义JSONResponse为HTTPException
使用FastAPI内置的HTTPException抛出错误响应,无需手动返回JSONResponse,避免接口返回多种不兼容类型。同时注意不要覆盖路径参数domain的变量值,避免逻辑异常。 - 为AuthModel添加自定义类型支持配置
在AuthModel中添加配置项,允许Pydantic解析自定义的PyObjectId类型:
如果使用Pydantic V1:
class AuthModel(BaseModel): id: PyObjectId = Field(default_factory=PyObjectId, alias="_id") userid: UUID = Field(default_factory=uuid4) firstname: Optional[str] lastname: Optional[str] domain: UUID = Field(default_factory=uuid4) email: EmailStr phone: Optional[str] plain_secret: Optional[str] status: Optional[str] class Config: arbitrary_types_allowed = True allow_population_by_field_name = True
如果使用Pydantic V2:
class AuthModel(BaseModel): id: PyObjectId = Field(default_factory=PyObjectId, alias="_id") userid: UUID = Field(default_factory=uuid4) firstname: Optional[str] lastname: Optional[str] domain: UUID = Field(default_factory=uuid4) email: EmailStr phone: Optional[str] plain_secret: Optional[str] status: Optional[str] model_config = { "arbitrary_types_allowed": True, "populate_by_name": True }
- 显式声明接口响应模型与返回值类型
给router路由装饰器添加response_model参数,同时给视图函数添加返回值类型注解,明确告诉FastAPI返回值的结构,关闭自动类型推导:
from typing import List from fastapi import APIRouter, HTTPException, status router = APIRouter() # 替换@app.get为@router.get,显式声明response_model @router.get("/api/accounts/userslist/{domain}", response_description="List all users", response_model=List[AuthModel]) async def account_list( domain: str, credentials: HTTPAuthorizationCredentials = Security(security)) -> List[AuthModel]: token = credentials.credentials if auth_handler.decode_token(token): # 修改变量名避免覆盖路径参数domain domain_doc = domains_db.find_one({'domain': domain}) if not domain_doc: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="There is no user matching the requested domain." ) accounts = users_db.find({'domain': domain_doc['domain_id']}) return [AuthModel(**account) for account in accounts] # 务必在FastAPI实例初始化位置注册路由 # app.include_router(router)
内容的提问来源于stack exchange,提问作者ali ardakani
相关产品推荐
相关产品推荐

