FastAPI上传文件与字典类型Form参数兼容及文档优化问题
FastAPI同时上传文件与Form字典字段的最优解决方案
问题场景
需要通过Form Data同时上传文件和存入数据库的凭证信息(要求为str类型键、Any类型值的字典),最初编写的接口如下:
@router.post('/upload', response_model=SuccessResponse) async def upload(file: UploadFile = File(...), credential: Dict[str, Any] = Form(...)): return response.success(SuccessResponse(data=credential))
使用curl调用时:
curl --location '127.0.0.1:8000/api/v1/file/upload' \ --form 'file=@"/Users/hanie/Desktop/example.jpg"' \ --form 'credential[user_id]="2"' \ --form 'credential[page_id]="1"'
始终收到凭证字段缺失的错误:
{"detail": [ { "type": "missing", "loc": [ "body", "credential" ], "msg": "Field required", "input": null } ]}
尝试方案的缺陷
通过自定义依赖函数解析Form数据的方式可以正常运行,但会导致OpenAPI文档无法正确识别credential字段:
async def parse_dict_form(request: Request) -> Dict[str, Any]: form_data = await request.form() result = {} prefix_with_brackets = "credential[" for key, value in form_data.items(): if key.startswith(prefix_with_brackets) and key.endswith(']'): actual_key = key[len(prefix_with_brackets):-1] result[actual_key] = value return result @router.post('/upload', response_model=SuccessResponse) async def upload( file: UploadFile = File(...), credential: Dict[str, Any] = Depends(parse_dict_form) ): return response.success(SuccessResponse(data=credential))
最优解决方法
方法1:使用JSON格式的Form字段(推荐)
这种方式既保证功能正常,又能让OpenAPI文档正确识别字段结构,是最简洁的方案。
接口代码(用Pydantic模型,文档更友好)
from pydantic import BaseModel from fastapi import UploadFile, File, Form # 定义凭证的Pydantic模型,明确字段结构 class Credential(BaseModel): user_id: str page_id: str # 可根据需求添加其他字段 @router.post('/upload', response_model=SuccessResponse) async def upload( file: UploadFile = File(...), # 直接用Pydantic模型接收Form中的JSON字符串,自动解析为字典 credential: Credential = Form(...) ): return response.success(SuccessResponse(data=credential.dict()))
对应的curl调用
传递JSON格式的credential字段:
curl --location '127.0.0.1:8000/api/v1/file/upload' \ --form 'file=@"/Users/hanie/Desktop/example.jpg"' \ --form 'credential="{\"user_id\":\"2\",\"page_id\":\"1\"}"'
如果不想定义Pydantic模型,也可以直接用Dict[str, Any]:
from typing import Dict, Any from fastapi import UploadFile, File, Form @router.post('/upload', response_model=SuccessResponse) async def upload( file: UploadFile = File(...), credential: Dict[str, Any] = Form(...) ): return response.success(SuccessResponse(data=credential))
curl调用方式和上面一致,传递JSON字符串即可。
方法2:保留表单数组格式并修复OpenAPI文档
如果必须使用credential[user_id]这种表单字段格式,可以在自定义依赖中添加OpenAPI schema注释,让文档正确显示字段:
from fastapi import Depends, Request, UploadFile, File from typing import Dict, Any async def parse_dict_form(request: Request) -> Dict[str, Any]: form_data = await request.form() result = {} prefix_with_brackets = "credential[" for key, value in form_data.items(): if key.startswith(prefix_with_brackets) and key.endswith(']'): actual_key = key[len(prefix_with_brackets):-1] result[actual_key] = value return result # 手动添加OpenAPI schema,让文档显示正确的表单字段 parse_dict_form.__openapi_operation__ = { "requestBody": { "content": { "multipart/form-data": { "schema": { "type": "object", "properties": { "file": {"type": "string", "format": "binary"}, "credential[user_id]": {"type": "string"}, "credential[page_id]": {"type": "string"} }, "required": ["file", "credential[user_id]", "credential[page_id]"] } } } } } @router.post('/upload', response_model=SuccessResponse) async def upload( file: UploadFile = File(...), credential: Dict[str, Any] = Depends(parse_dict_form) ): return response.success(SuccessResponse(data=credential))
这种方法需要手动维护OpenAPI schema,适合必须保留原有表单格式的场景。
内容的提问来源于stack exchange,提问作者Hanie Asemi
相关产品推荐
相关产品推荐

