FastAPI多表单请求报422错误:同时传dict与UploadFile失败
问题:FastAPI同时接收表单字典与上传文件时触发422错误
现象
- 仅保留
dict参数(移除UploadFile):接口正常接收参数 - 仅保留
UploadFile(移除dict和Body(...)):接口正常接收上传文件 - 同时使用
dict与UploadFile:多表单请求触发422 Unprocessable Entity错误
Swagger测试请求示例
------WebKitFormBoundaryewMFgxOJYb8xJ70T Content-Disposition: form-data; name="media" { "media_title": "string", "media_description": "string", "media_type": "string", "media_tags": "tag1", "firm_id": 0, "fund_id": 0, "user_id": 0, "file_path": "string", "version": 0 } ------WebKitFormBoundaryewMFgxOJYb8xJ70T Content-Disposition: form-data; name="media_file"; filename="file_example_MP4_640_3MG.mp4" Content-Type: video/mp4 ------WebKitFormBoundaryewMFgxOJYb8xJ70T--
现有代码
class Media(BaseModel): media_title: str media_description: str media_type: str media_tags: str firm_id: int fund_id: int = None user_id: int file_path: str version: int @media_router.post("/upload", response_model=Media) async def upload_media(db: db_dependency, media: dict = Body(...), media_file: UploadFile = File(...)): try: media["file_path"] = save_uploaded_file(media_file) output = await dblibrary.create(db=db, obj_in=Media(**media)) return output except Exception as e: raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
错误信息
{ "detail": [ { "type": "dict_type", "loc": [ "body", "media" ], "msg": "输入应为有效的字典", "input": "{ \"media_title\": \"string\", \"media_description\": \"string\", \"media_type\": \"string\", \"media_tags\": \"tag1\", \"firm_id\": 0, \"fund_id\": 0, \"user_id\": 0, \"file_path\": \"string\", \"version\": 0 }", "url": "https://errors.pydantic.dev/2.6/v/dict_type" } ] }
解决方案
问题本质:当接口同时存在File和Body参数时,FastAPI会将请求解析为multipart/form-data格式,但Body(...)定义的dict参数默认期望从请求体的JSON中获取,而非表单字段里的字符串格式JSON,导致解析失败。
方法1:表单接收JSON字符串后手动转字典
将media参数改为Form接收,用json.loads解析成字典后再传入模型:
import json from fastapi import Form @media_router.post("/upload", response_model=Media) async def upload_media( db: db_dependency, media: str = Form(...), media_file: UploadFile = File(...) ): try: media_dict = json.loads(media) media_dict["file_path"] = save_uploaded_file(media_file) output = await dblibrary.create(db=db, obj_in=Media(**media_dict)) return output except Exception as e: raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
方法2:用Pydantic模型直接验证JSON字符串
利用Media模型的model_validate_json方法,直接解析表单中的JSON字符串:
from fastapi import Form @media_router.post("/upload", response_model=Media) async def upload_media( db: db_dependency, media: str = Form(...), media_file: UploadFile = File(...) ): try: media_obj = Media.model_validate_json(media) media_obj.file_path = save_uploaded_file(media_file) output = await dblibrary.create(db=db, obj_in=media_obj) return output except Exception as e: raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
方法3:拆分模型字段为单独表单参数(可选)
如果不需要传递完整JSON对象,可将Media的每个字段设为单独的Form参数,结合UploadFile使用:
@media_router.post("/upload", response_model=Media) async def upload_media( db: db_dependency, media_title: str = Form(...), media_description: str = Form(...), media_type: str = Form(...), media_tags: str = Form(...), firm_id: int = Form(...), fund_id: int = Form(None), user_id: int = Form(...), version: int = Form(...), media_file: UploadFile = File(...) ): try: media_dict = { "media_title": media_title, "media_description": media_description, "media_type": media_type, "media_tags": media_tags, "firm_id": firm_id, "fund_id": fund_id, "user_id": user_id, "file_path": save_uploaded_file(media_file), "version": version } output = await dblibrary.create(db=db, obj_in=Media(**media_dict)) return output except Exception as e: raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
内容的提问来源于stack exchange,提问作者user3910623
相关产品推荐
相关产品推荐

