FastAPI高级用法:如何同时上传文件与Pydantic列表字典数据

花韻仙語
发布: 2025-09-19 14:02:01
原创
427人浏览过

FastAPI高级用法:如何同时上传文件与Pydantic列表字典数据

本教程深入探讨了在FastAPI中同时上传文件和Pydantic复杂数据结构(如字典列表)的挑战与解决方案。文章首先剖析了传统方法中遇到的HTTP协议限制和Pydantic模型定义问题,随后详细介绍了两种核心策略:通过Form参数传输JSON字符串并手动解析,以及利用Pydantic的model_validator自动处理Body中的JSON字符串。通过实际代码示例,本教程旨在帮助开发者高效地构建支持混合数据上传的FastAPI接口。

1. 挑战:同时上传文件与复杂JSON数据

在fastapi应用开发中,我们经常需要处理多种类型的请求数据,例如文件上传(uploadfile)和结构化的json数据(pydantic basemodel)。当这些数据类型需要同时在一个请求中提交时,开发者可能会遇到一些挑战,特别是当json数据包含列表(list)或字典列表(list[basemodel])时。

常见的错误尝试包括:

  • 将Pydantic模型直接作为依赖项(Depends())与UploadFile一起使用,期望它能自动解析JSON体。
  • 在Pydantic模型中定义List类型的字段作为查询参数,但未显式使用Query()。
  • 尝试通过multipart/form-data同时发送JSON数据和文件。

这些尝试通常会导致422 Unprocessable Entity错误,其根本原因在于HTTP协议对请求体编码的限制以及FastAPI/Pydantic对不同数据源的解析机制。

核心问题点:

  1. HTTP协议限制: HTTP协议通常不允许在一个请求体中同时使用multipart/form-data(用于文件上传)和application/json(用于JSON数据)两种编码类型。当接口定义中包含File()参数时,FastAPI会将整个请求体视为multipart/form-data。
  2. List类型查询参数: 当Pydantic模型中包含List[str]或List[int]等列表类型的查询参数时,必须显式使用Field(Query(...))进行声明,否则FastAPI无法正确解析。
  3. List[dict]作为查询参数: List[BaseModel](即字典列表)无法作为查询参数传递。Pydantic模型中的复杂对象列表通常期望作为请求体的一部分。

2. Pydantic模型中列表参数的正确声明

在深入探讨文件与JSON混合上传之前,我们首先需要理解如何在Pydantic模型中正确声明列表类型的查询参数。如果你的Pydantic模型字段是List[str]或List[float]等,你需要使用Query()将其包装在Field()中。

示例代码 1:Pydantic模型中列表查询参数的正确用法

from fastapi import FastAPI, Query, Depends
from pydantic import BaseModel, Field
from typing import Optional, List

app = FastAPI()

class BaseQueryParams(BaseModel):
    width: Optional[float] = Field(None, description="宽度")
    height: Optional[float] = Field(None, description="高度")
    words: List[str] = Field(Query(..., description="单词列表")) # 必须使用 Query(...)

@app.get("/query-example")
async def get_with_list_query(params: BaseQueryParams = Depends()):
    """
    一个演示如何使用列表查询参数的端点。
    示例请求: /query-example?width=10.5&words=apple&words=banana
    """
    return params
登录后复制

说明:

  • words: List[str] = Field(Query(...)) 明确告诉FastAPI words 是一个列表类型的查询参数,可以接收多个同名参数值(例如 ?words=a&words=b)。
  • 如果缺少Query(...),FastAPI将无法正确解析List类型的查询参数。

3. 核心解决方案:同时上传文件与复杂JSON数据

由于HTTP协议的限制,我们不能直接将Pydantic模型(作为application/json)和文件(作为multipart/form-data)同时发送。解决方案的关键在于:将Pydantic模型的数据编码成一个字符串,并通过multipart/form-data的一部分(例如一个Form字段)发送,然后在服务器端进行解析。

以下介绍两种常用的实现方法。

3.1 方法一:通过Form参数传递JSON字符串并手动解析

这种方法将Pydantic模型的数据序列化为JSON字符串,然后作为Form参数的一部分提交。服务器端通过一个依赖函数手动解析这个JSON字符串。

示例代码 2:使用Form参数和依赖函数解析JSON数据

app.py

怪兽AI数字人
怪兽AI数字人

数字人短视频创作,数字人直播,实时驱动数字人

怪兽AI数字人 44
查看详情 怪兽AI数字人
from fastapi import FastAPI, status, Form, UploadFile, File, Depends, Query
from pydantic import BaseModel, Field, ValidationError
from fastapi.exceptions import HTTPException
from fastapi.encoders import jsonable_encoder
from typing import Optional, List
import json # 导入 json 模块

app = FastAPI()

# 定义查询参数模型
class BaseQueryParams(BaseModel):
    width: Optional[float] = Field(None, description="宽度")
    height: Optional[float] = Field(None, description="高度")
    words: List[str] = Field(Query(..., description="单词列表")) # 列表查询参数

# 定义复杂JSON数据模型中的子模型
class BaseBox(BaseModel):
    l: float = Field(..., description="左坐标")
    t: float = Field(..., description="上坐标")
    r: float = Field(..., description="右坐标")
    b: float = Field(..., description="下坐标")

# 定义复杂JSON数据模型
class BasePayload(BaseModel):
    boxes: List[BaseBox] = Field(..., description="边界框列表")
    comments: List[str] = Field(..., description="评论列表")
    code: int = Field(..., description="状态码")

# 定义一个依赖函数,用于解析 Form 参数中的 JSON 字符串
def parse_json_form_data(data: str = Form(...)):
    try:
        # 尝试将 Form 参数中的字符串解析为 BasePayload 模型
        return BasePayload.model_validate_json(data)
    except ValidationError as e:
        # 如果解析失败,抛出 422 错误
        raise HTTPException(
            detail=jsonable_encoder(e.errors()),
            status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
        )

@app.post("/submit_form_json")
def submit_with_form_json(
    query_params: BaseQueryParams = Depends(), # 查询参数
    payload: BasePayload = Depends(parse_json_form_data), # JSON数据通过Form解析
    files: List[UploadFile] = File(...), # 文件列表
):
    """
    通过 Form 参数传递 JSON 字符串,并同时上传文件。
    """
    return {
        "Query Params": query_params,
        "JSON Payload": payload,
        "Filenames": [file.filename for file in files],
    }
登录后复制

客户端请求示例 (使用 curl):

假设你有一个名为 test.png 的文件。

curl -X 'POST' \
  'http://localhost:8000/submit_form_json?width=10.5&height=20.0&words=apple&words=banana' \
  -H 'accept: application/json' \
  -H 'Content-Type: multipart/form-data' \
  -F 'files=@test.png;type=image/png' \
  -F 'data={"boxes": [{"l": 0,"t": 0,"r": 10,"b": 10}], "comments": ["first comment", "second comment"], "code": 123}'
登录后复制

说明:

  • BaseQueryParams 用于处理 URL 中的查询参数,其中的 words 字段正确使用了 Query(...)。
  • BasePayload 定义了我们期望的复杂JSON数据结构。
  • parse_json_form_data 是一个关键的依赖函数。它接收一个 Form 参数 data(客户端将JSON字符串作为此字段发送),然后使用 BasePayload.model_validate_json() 尝试将其解析为 BasePayload 对象。
  • 如果JSON字符串格式不正确,ValidationError 会被捕获并转换为 HTTPException,返回 422 状态码和详细错误信息。
  • files: List[UploadFile] = File(...) 用于接收一个或多个文件。

3.2 方法二:利用Pydantic的model_validator自动解析Body中的JSON字符串

这种方法通过Pydantic模型自身的model_validator来处理从请求体中接收到的JSON字符串。它将JSON字符串视为一个特殊的输入格式,并在模型实例化之前进行解析。这种方式通常更简洁,并且在Swagger UI (/docs) 中能更好地展示请求体结构。

示例代码 3:使用model_validator解析Body中的JSON字符串

app.py

from fastapi import FastAPI, Body, UploadFile, File, Depends, Query
from pydantic import BaseModel, Field, model_validator
from typing import Optional, List
import json

app = FastAPI()

# 定义查询参数模型
class BaseQueryParams(BaseModel):
    width: Optional[float] = Field(None, description="宽度")
    height: Optional[float] = Field(None, description="高度")
    words: List[str] = Field(Query(..., description="单词列表")) # 列表查询参数

# 定义复杂JSON数据模型中的子模型
class BaseBox(BaseModel):
    l: float = Field(..., description="左坐标")
    t: float = Field(..., description="上坐标")
    r: float = Field(..., description="右坐标")
    b: float = Field(..., description="下坐标")

# 定义复杂JSON数据模型,并添加 model_validator
class BasePayload(BaseModel):
    boxes: List[BaseBox] = Field(..., description="边界框列表")
    comments: List[str] = Field(..., description="评论列表")
    code: int = Field(..., description="状态码")

    @model_validator(mode="before")
    @classmethod
    def validate_to_json(cls, value):
        """
        在模型验证之前,如果输入是字符串,尝试将其解析为JSON。
        这允许客户端将JSON数据作为字符串发送。
        """
        if isinstance(value, str):
            try:
                return cls(**json.loads(value))
            except json.JSONDecodeError as e:
                # 如果JSON解析失败,Pydantic会捕获并抛出ValidationError
                # 这里可以添加更具体的错误处理,或让Pydantic默认处理
                raise ValueError("Invalid JSON string for BasePayload") from e
        return value

@app.post("/submit_body_json")
def submit_with_body_json(
    query_params: BaseQueryParams = Depends(), # 查询参数
    payload: BasePayload = Body(...), # JSON数据通过Body参数传递
    files: List[UploadFile] = File(...), # 文件列表
):
    """
    通过 Body 参数传递 JSON 字符串(由 model_validator 处理),并同时上传文件。
    """
    return {
        "Query Params": query_params,
        "JSON Payload": payload,
        "Filenames": [file.filename for file in files],
    }
登录后复制

客户端请求示例 (使用 curl):

假设你有一个名为 test.png 的文件。

curl -X 'POST' \
  'http://localhost:8000/submit_body_json?width=10.5&height=20.0&words=apple&words=banana' \
  -H 'accept: application/json' \
  -H 'Content-Type: multipart/form-data' \
  -F 'files=@test.png;type=image/png' \
  -F 'payload={"boxes": [{"l": 0,"t": 0,"r": 10,"b": 10}], "comments": ["first comment", "second comment"], "code": 123}'
登录后复制

说明:

  • BasePayload 模型中新增了一个 model_validator(mode="before") 方法。
  • validate_to_json 方法在Pydantic模型验证之前被调用。如果 value 是一个字符串(即客户端发送的JSON字符串),它会尝试使用 json.loads() 将其解析为字典,然后用这个字典来实例化 BasePayload。
  • payload: BasePayload = Body(...) 声明 payload 是请求体的一部分。FastAPI会将其作为 multipart/form-data 中的一个字段来处理,而 model_validator 则负责将其从字符串解析为Pydantic对象。
  • 这种方法在FastAPI的/docs接口中显示更友好,因为它能自动生成 BasePayload 的示例输入结构。

4. 注意事项与总结

  • 选择合适的方法: 方法二(使用model_validator)通常更推荐,因为它将JSON解析逻辑封装在Pydantic模型内部,使代码更简洁,且与FastAPI的文档生成集成度更高。
  • 客户端请求格式: 无论选择哪种方法,客户端都需要将Pydantic模型的数据序列化为JSON字符串,并作为multipart/form-data中的一个字段发送。
  • 查询参数: 对于URL中的列表类型查询参数,务必使用 Field(Query(...)) 进行声明。
  • 错误处理: 两种方法都包含了对JSON解析失败的错误处理,确保API在接收到无效数据时能返回清晰的错误信息。
  • 多文件上传: 示例中使用了 files: List[UploadFile] = File(...) 来支持多文件上传。如果只需要上传单个文件,可以将其改为 file: UploadFile = File(...)。

通过上述两种方法,开发者可以有效地解决在FastAPI中同时上传文件和复杂Pydantic模型数据(特别是包含字典列表)的挑战,构建出功能强大且健壮的API接口。

以上就是FastAPI高级用法:如何同时上传文件与Pydantic列表字典数据的详细内容,更多请关注php中文网其它相关文章!

最佳 Windows 性能的顶级免费优化软件
最佳 Windows 性能的顶级免费优化软件

每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。

下载
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新 English
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号