首页
/ FastAPI 扩展数据类型指南:UUID、datetime、timedelta、bytes 与 Decimal 在 API 参数中的使用

FastAPI 扩展数据类型指南:UUID、datetime、timedelta、bytes 与 Decimal 在 API 参数中的使用

2026-09-06 12:08:02作者:霍妲思

本篇技术指南聚焦 FastAPI 教程中"扩展数据类型"(Extra Data Types)一节:在 intfloatstrbool 等基础类型之外,如何在路径参数与请求体中使用 UUIDdatetime.datetimedatetime.timedeltafrozensetbytesDecimal 等更复杂的数据类型,并完整保留编辑器支持、请求数据转换、响应数据转换、数据校验以及自动 OpenAPI 文档生成这五大核心能力。读完后,你将掌握这些类型在请求/响应中的序列化形式、生成的 JSON Schema 格式,以及结合 fastapi/encoders.py 源码与官方测试用例验证行为的方法。

为什么扩展数据类型依然"零成本"可用

在 FastAPI 中,只要你在端点函数签名上写出正确的 Python 类型注解,框架就会借助 Pydantic 自动完成以下工作:

  • 优秀的编辑器支持:类型注解本身就是标准 Python 类型,IDE 可以提供补全与检查;
  • 请求数据转换:入站请求中的 JSON 数据会被自动解析为对应的 Python 对象;
  • 响应数据转换:函数返回值中的 Python 对象会被自动序列化为 JSON 可表示的形式;
  • 数据校验:类型不合法时自动返回 422 校验错误;
  • 自动标注与文档:OpenAPI Schema 中会为每个类型生成正确的 typeformat 字段。

这与基础类型的行为完全一致——区别只在于不同复杂类型各自采用什么序列化形式。

常用扩展数据类型及其序列化形式

以下是文档中列出的主要扩展类型及其在请求和响应中的表现:

Python 类型 请求/响应中的表现形式 说明
UUID str 标准"通用唯一识别码"(Universally Unique Identifier),在各类数据库与系统中常用作 ID
datetime.datetime str,ISO 8601 格式 例如 2008-09-15T15:53:00+05:00
datetime.date str,ISO 8601 格式 例如 2008-09-15
datetime.time str,ISO 8601 格式 例如 14:23:55.003
datetime.timedelta float,表示总秒数 Pydantic 也支持通过自定义序列化器表示为 ISO 8601 时间差编码
frozenset set 处理相同 请求侧读取列表、去重后转为 set;响应侧将 set 转为 list;生成的 Schema 通过 JSON Schema 的 uniqueItems 表明集合值唯一
bytes str 生成的 Schema 会标注为带 binary 格式的 str
Decimal float 相同 标准 Python decimal.Decimal

此外,所有 Pydantic 支持的合法数据类型在 FastAPI 中均可直接使用——FastAPI 的校验与序列化能力直接建立在 Pydantic 之上,因此 Pydantic 类型系统就是 FastAPI 的类型系统上限。

完整示例:一个使用多种扩展类型的路径操作

下面是仓库 docs_src/extra_data_types/tutorial001_an_py310.py 的完整示例:路径参数使用 UUID,请求体参数分别使用 datetimetimedelta 和可选的 time

from datetime import datetime, time, timedelta
from typing import Annotated
from uuid import UUID

from fastapi import Body, FastAPI

app = FastAPI()


@app.put("/items/{item_id}")
async def read_items(
    item_id: UUID,
    start_datetime: Annotated[datetime, Body()],
    end_datetime: Annotated[datetime, Body()],
    process_after: Annotated[timedelta, Body()],
    repeat_at: Annotated[time | None, Body()] = None,
):
    start_process = start_datetime + process_after
    duration = end_datetime - start_process
    return {
        "item_id": item_id,
        "start_datetime": start_datetime,
        "end_datetime": end_datetime,
        "process_after": process_after,
        "repeat_at": repeat_at,
        "start_process": start_process,
        "duration": duration,
    }

(仓库中同时提供了不使用 Annotated 的等价写法 tutorial001_py310.py,其中通过 start_datetime: datetime = Body() 这类默认值形式声明请求体参数,两者行为一致。)

关键点:函数内部拿到的是"自然类型"

示例中最值得注意的两行是:

start_process = start_datetime + process_after
duration = end_datetime - start_process

函数内部拿到的参数已经是自然的 Python 类型——start_datetime 是真正的 datetime.datetime 对象,process_after 是真正的 timedelta 对象,因此可以直接执行标准的日期算术运算(datetime + timedelta 得到新的 datetime,两个 datetime 相减得到 duration)。开发者不需要手写任何解析或序列化代码,类型转换全部由框架在边界处完成。

源码级佐证:FastAPI 如何序列化这些类型

从源码结构看,响应侧的类型序列化由 fastapi/encoders.py 中的 ENCODERS_BY_TYPE 映射表驱动,该映射明确定义了文档中各类型的输出形式:

ENCODERS_BY_TYPE: dict[type[Any], Callable[[Any], Any]] = {
    bytes: lambda o: o.decode(),
    datetime.date: isoformat,
    datetime.datetime: isoformat,
    datetime.time: isoformat,
    datetime.timedelta: lambda td: td.total_seconds(),
    Decimal: decimal_encoder,
    frozenset: list,
    # ...
}

(见 fastapi/encoders.py#L84-L96

对照文档中的描述,可以逐条印证:

  • datetime.date / datetime.datetime / datetime.time 都走 isoformat() 函数,输出 ISO 8601 字符串;
  • datetime.timedelta 通过 total_seconds() 输出为总秒数(float),这正是文档中"represented as a float of total seconds"的实现依据;
  • Decimaldecimal_encoder,无指数时输出 int、否则输出 float,与文档"handled the same as a float"一致;
  • frozenset 直接转为 list 输出;bytes 通过 decode() 转为字符串。

也就是说,文档中列出的"类型 → 表现形式"表格并非约定,而是这份编码器映射的直接结果。

请求侧与 OpenAPI Schema 中的格式

请求侧的解析由 Pydantic 完成:字符串形式的 ISO 8601 日期时间、总秒数形式的 timedelta 等都会被校验并转换为对应 Python 类型;OpenAPI Schema 的 format 字段则由 Pydantic 的类型元信息生成。

仓库测试 tests/test_tutorial/test_extra_data_types/test_tutorial001.py 为上述行为提供了可执行的事实依据:

  1. 端到端请求/响应测试:客户端发送 JSON {"start_datetime": "2018-12-22T14:00:00+00:00", "end_datetime": "2018-12-24T15:00:00+00:00", "repeat_at": "15:30:00", "process_after": 300},其中 process_after: 300 正是"timedelta 以总秒数表示"的体现;期望响应中 start_process2018-12-22T14:05:00+00:00(即 start_datetime + 300 秒),duration176100(秒),验证了函数内日期运算与响应序列化的完整闭环;
  2. OpenAPI Schema 快照测试:断言生成的 Schema 中路径参数 item_id{"type": "string", "format": "uuid"}start_datetime / end_datetime{"type": "string", "format": "date-time"}repeat_atanyOf: [string/time, null](体现 time | None 的可选语义),process_after{"type": "string", "format": "duration"}——即文档所述"自动标注"能力在这些类型上的具体落地。

实践建议与适用前提

  • ID 优先用 UUID:数据库主键、外部资源标识用 UUID 类型注解,既能获得 format: uuid 的 Schema 标注,又能在客户端传入非法字符串时自动返回 422 校验错误;
  • 时间字段用 datetime 家族:请求中一律接受 ISO 8601 字符串,响应中自动输出 ISO 8601 字符串,避免手写字符串解析;注意 datetime 带时区信息时应保持 ISO 8601 时区偏移(如 +05:00);
  • 时长/延迟参数用 timedelta:默认以总秒数 float 传输。若业务上希望用 ISO 8601 时间差编码(如 PT5M)传输,可在 Pydantic 层通过自定义序列化器实现(见文档提示的 Pydantic 序列化文档),FastAPI 本身默认采用总秒数;
  • 金额等精确计算用 Decimal:避免 float 的浮点误差,序列化和校验行为与 float 一致;
  • 集合去重用 frozenset:请求列表自动去重,Schema 自动标注 uniqueItems
  • 所有类型必须通过标准类型注解声明,FastAPI 才会同时提供校验、转换与文档生成;这与仓库教程中其他章节(路径参数、查询参数、请求体)的规则一致。

小结

FastAPI 的"扩展数据类型"能力本质上是 Pydantic 类型系统的透传:你在签名中写 UUIDdatetimetimedeltafrozensetbytesDecimal,框架就在请求边界完成解析与校验、在响应边界通过 fastapi/encoders.py 的编码器映射(或 Pydantic v2 的序列化)完成转换,同时在 OpenAPI 文档中给出带正确 format 的 Schema。结合 示例代码对应测试,你可以直接复制运行并验证每一个类型转换行为。

登录后查看全文
热门项目推荐
相关项目推荐