如何在FastAPI中结合Pydantic BaseModel上传文件与字典列表?
FastAPI接口接收文件与复杂参数时返回422错误
我编写了如下FastAPI代码,意图同时接收上传文件和包含数组、对象的结构化参数,但无论是通过交互式API文档测试,还是使用curl命令调用,都返回422 Unprocessable Entity错误。
代码示例
from fastapi import File, UploadFile, Request, FastAPI, Depends from typing import List from fastapi.responses import HTMLResponse from pydantic import BaseModel, Field from typing import Optional app = FastAPI() class BaseBox(BaseModel): l: float=Field(...) t: float=Field(...) r: float=Field(...) b: float=Field(...) class BaseInput(BaseModel): boxes: List[BaseBox] = Field(...) words: List[str] = Field(...) width: Optional[float] = Field(...) height: Optional[float] = Field(...) @app.post("/submit") def submit( base_input: BaseInput = Depends(), file: UploadFile = File(...), ): return { "JSON Payload": base_input, "Filename": file.filename, } @app.get("/") def main(request: Request): return {"status":"alive"}
尝试的curl命令
curl -X 'POST' \ 'http://localhost:8007/submit?width=10&height=10' \ -H 'accept: application/json' \ -H 'Content-Type: multipart/form-data' \ -F 'file=@test.png;type=image/png' \ -F 'boxes={ \"l\": 0, \"t\": 0, \"r\": 0, \"b\": 0 }' \ -F 'words=test,test2,tes3,test'
收到的错误
"POST /submit?width=10&height=10 HTTP/1.1" 422 Unprocessable Entity
解决方案
问题核心:当请求为multipart/form-data类型时,Depends()默认会尝试从JSON请求体解析BaseInput,但此时请求内容是表单格式,无法正确识别复杂的嵌套数组和对象结构,导致参数解析失败。以下两种方法可以解决:
方法一:通过表单字段传递JSON字符串并手动解析
修改接口代码,将结构化参数通过单个表单字段传递JSON字符串,再解析为BaseInput对象:
from fastapi import File, UploadFile, Request, FastAPI, Form from typing import List from pydantic import BaseModel, Field from typing import Optional import json app = FastAPI() class BaseBox(BaseModel): l: float=Field(...) t: float=Field(...) r: float=Field(...) b: float=Field(...) class BaseInput(BaseModel): boxes: List[BaseBox] = Field(...) words: List[str] = Field(...) width: Optional[float] = None height: Optional[float] = None @app.post("/submit") def submit( base_input_json: str = Form(...), file: UploadFile = File(...), width: Optional[float] = None, height: Optional[float] = None ): # 解析JSON字符串为BaseInput实例 base_input = BaseInput(**json.loads(base_input_json)) # 覆盖查询参数中的宽高值(如果存在) if width is not None: base_input.width = width if height is not None: base_input.height = height return { "JSON Payload": base_input.dict(), "Filename": file.filename, }
对应的curl调用命令:
curl -X 'POST' \ 'http://localhost:8007/submit?width=10&height=10' \ -H 'accept: application/json' \ -H 'Content-Type: multipart/form-data' \ -F 'file=@test.png;type=image/png' \ -F 'base_input_json={ "boxes": [{"l": 0, "t": 0, "r": 0, "b": 0}], "words": ["test", "test2", "tes3", "test"] }'
方法二:使用Body(embed=True)解析表单中的复杂模型(FastAPI 0.95+)
如果使用FastAPI 0.95及以上版本,可以直接用Body(embed=True)让框架自动从表单字段中解析嵌套模型:
from fastapi import File, UploadFile, Request, FastAPI, Body from typing import List from pydantic import BaseModel, Field from typing import Optional app = FastAPI() class BaseBox(BaseModel): l: float=Field(...) t: float=Field(...) r: float=Field(...) b: float=Field(...) class BaseInput(BaseModel): boxes: List[BaseBox] = Field(...) words: List[str] = Field(...) width: Optional[float] = None height: Optional[float] = None @app.post("/submit") def submit( base_input: BaseInput = Body(..., embed=True), file: UploadFile = File(...) ): return { "JSON Payload": base_input.dict(), "Filename": file.filename, }
可以用两种方式调用:
- 拆分表单字段传递参数:
curl -X 'POST' \ 'http://localhost:8007/submit' \ -H 'accept: application/json' \ -H 'Content-Type: multipart/form-data' \ -F 'file=@test.png;type=image/png' \ -F 'boxes[0][l]=0' \ -F 'boxes[0][t]=0' \ -F 'boxes[0][r]=0' \ -F 'boxes[0][b]=0' \ -F 'words=test' \ -F 'words=test2' \ -F 'words=tes3' \ -F 'words=test' \ -F 'width=10' \ -F 'height=10'
- 单个表单字段传递完整JSON:
curl -X 'POST' \ 'http://localhost:8007/submit' \ -H 'accept: application/json' \ -H 'Content-Type: multipart/form-data' \ -F 'file=@test.png;type=image/png' \ -F 'base_input={ "boxes": [{"l": 0, "t": 0, "r": 0, "b": 0}], "words": ["test", "test2", "tes3", "test"], "width": 10, "height": 10 }'
内容的提问来源于stack exchange,提问作者Thanh Long Phan
相关产品推荐
相关产品推荐

