FastAPI表单绑定模型报错“str type expected”的解决方法咨询
解决FastAPI中Form参数绑定Pydantic模型的422错误问题
问题场景
开发Banner更新接口时,需同时上传两张图片(背景图、前景图)并提交Banner基础信息(title、sub_title、path_url、desc)。使用Form和UploadFile实现时,将BannerInputSchema作为Form参数传入会触发422验证错误,但单独传入每个属性可正常运行。
现有代码
Schema模型
from pydantic import BaseModel class BannerInputSchema(BaseModel): """Banner Create Model""" title: str sub_title: str | None = None path_url: str | None = None desc: str | None = None class Config: orm_mode = True
路由代码
from fastapi import APIRouter, Form, File, UploadFile from your_module import ResponseSchema, BannerService router = APIRouter() @router.put('/update_banner/{id}', response_model=ResponseSchema, response_model_exclude_none=True) async def update_banner( banner_id: str = Form(), banner_info: BannerInputSchema = Form(), b_img: UploadFile = File(default=None, description='upload background img'), f_img: UploadFile = File(default=None, description='upload front img'), ): result = await BannerService.update_banner( banner_id=banner_id, banner_info=banner_info, b_img=b_img, f_img=f_img, ) return ResponseSchema(detail='Success Banner Data', result=result)
触发的错误
{ "detail": [ { "loc": [ "body", "banner_info", "title" ], "msg": "str type expected", "type": "type_error.str" } ] }
错误原因
FastAPI默认无法将扁平的表单键值对自动映射到嵌套的Pydantic模型中。直接声明banner_info: BannerInputSchema = Form()时,FastAPI会期望表单中存在名为banner_info的字段,且其值是符合模型结构的JSON字符串,但实际前端提交的是title、sub_title等独立字段,导致解析失败。
修复方案
方案1:给Pydantic模型添加表单解析方法
通过给模型添加as_form类方法,将独立的表单字段组合成模型实例,再通过Depends注入到路由中。
修改后的Schema模型:
from pydantic import BaseModel from fastapi import Form, Depends class BannerInputSchema(BaseModel): """Banner Create Model""" title: str sub_title: str | None = None path_url: str | None = None desc: str | None = None class Config: orm_mode = True @classmethod def as_form( cls, title: str = Form(...), sub_title: str | None = Form(None), path_url: str | None = Form(None), desc: str | None = Form(None) ) -> "BannerInputSchema": return cls( title=title, sub_title=sub_title, path_url=path_url, desc=desc )
修改后的路由代码:
@router.put('/update_banner/{id}', response_model=ResponseSchema, response_model_exclude_none=True) async def update_banner( banner_id: str = Form(), banner_info: BannerInputSchema = Depends(BannerInputSchema.as_form), b_img: UploadFile = File(default=None, description='upload background img'), f_img: UploadFile = File(default=None, description='upload front img'), ): result = await BannerService.update_banner( banner_id=banner_id, banner_info=banner_info, b_img=b_img, f_img=f_img, ) return ResponseSchema(detail='Success Banner Data', result=result)
方案2:手动组合表单字段为模型实例
若不想修改模型,可直接在路由中声明所有表单字段,手动实例化BannerInputSchema:
@router.put('/update_banner/{id}', response_model=ResponseSchema, response_model_exclude_none=True) async def update_banner( banner_id: str = Form(), title: str = Form(), sub_title: str | None = Form(None), path_url: str | None = Form(None), desc: str | None = Form(None), b_img: UploadFile = File(default=None, description='upload background img'), f_img: UploadFile = File(default=None, description='upload front img'), ): banner_info = BannerInputSchema( title=title, sub_title=sub_title, path_url=path_url, desc=desc ) result = await BannerService.update_banner( banner_id=banner_id, banner_info=banner_info, b_img=b_img, f_img=f_img, ) return ResponseSchema(detail='Success Banner Data', result=result)
说明
两种方案均可解决问题:方案1符合代码复用原则,适合多接口复用同一模型的场景;方案2更直接,适合快速修复单个接口的场景。
内容的提问来源于stack exchange,提问作者Michael Mecil
相关产品推荐
相关产品推荐

