FastAPI 高级数据类型的完全指南:UUID、datetime、bytes、Decimal 等
本文是 FastAPI 教程系列中的进阶篇,讲解如何在 path operation(路径操作)的参数、请求体与响应中使用比
int、str等更丰富的 Python 数据类型。你将掌握UUID、datetime.datetime/date/time/timedelta、frozenset、bytes、Decimal等在请求解析、数据校验、JSON 序列化与 OpenAPI 文档自动生成中的具体行为,并看到经过测试验证的真实运行效果。读完即可在自己的接口中放心地使用这些类型处理带时区的日期、数据库主键 ID、二进制数据与高精度数值。
为什么需要"额外数据类型"
到目前为止,绝大多数示例都只用到几种最基础的 Python 类型:
int:整数float:浮点数str:字符串bool:布尔值
但在真实业务里,这些类型远远不够。例如:
- 数据库中的主键往往是
UUID(通用唯一标识符); - 任务的创建时间、截止时间需要精确到带时区的
datetime; - 某些字段只关心日期或只关心一天中的时刻;
- 性能统计需要表示"持续了多长时间"的
timedelta; - 上传内容的 SHA 校验值或原始字节需要用
bytes; - 金额计算需要精度可控的
Decimal。
好消息是:FastAPI 声明这些类型并不需要额外配置。只要你在类型注解中直接写出这些 Python 类型,就能免费获得与基础类型完全一致的四项能力:
- 优秀的编辑器支持(自动补全与静态类型检查);
- 入站请求数据的自动转换(字符串 → 真正的 Python 对象);
- 出站响应数据的自动转换(Python 对象 → JSON 兼容格式);
- 自动的数据校验与 OpenAPI 注解、交互式文档生成。
这套机制由 FastAPI 声明参数类型的 Pydantic 数据模型与 JSON Schema 生成 能力,配合 JSON 兼容编码器 共同完成。
支持的数据类型一览与 JSON 表示规则
下面逐一说明这些"额外数据类型"在网络传输层(JSON 请求/响应)的表示方式。理解这一点是使用它们的关键——JSON 本身并没有日期、二进制、UUID 这些概念,所以框架必须在字符串/数字与 Python 对象之间建立明确的双向映射。
UUID
- 标准的"通用唯一标识符",许多数据库和系统用它作为 ID。
- 在请求和响应中均表示为字符串(如
"ff97dd87-a4a5-4a12-b412-cde99f33e00e")。 - 校验时会检查字符串是否真的是合法的 UUID 格式,非法值会触发 422 校验错误。
datetime.datetime / datetime.date / datetime.time
三者都对应 Python 标准库 datetime 中同名类型:
datetime.datetime:在请求和响应中表示为 ISO 8601 格式字符串,例如2008-09-15T15:53:00+05:00(可带时区偏移)。datetime.date:仅表示日期,表示为 ISO 8601 日期字符串,例如2008-09-15。datetime.time:仅表示一天中的时刻,表示为 ISO 8601 时间字符串,例如14:23:55.003(可含毫秒)。
在 OpenAPI 生成的 JSON Schema 中,它们分别对应 type: string 与 format: date-time / format: date / format: time(时间与日期时间的对照可在 测试用例 的 OpenAPI 快照中看到)。
datetime.timedelta
- 表示一段持续时间。
- 在请求和响应中表示为表示总秒数的
float。例如测试数据中process_after: 300就代表 300 秒(5 分钟)。 - Pydantic 还支持将其序列化为"ISO 8601 时间差编码"(如
PT5M),这一点可查看 Pydantic 官方文档的序列化(custom serializers)部分。在 OpenAPI Schema 中其format显示为duration(见测试快照)。
frozenset
- 与
set完全同等对待:- 在请求中,读取一个数组(list),去重后转换为
set(frozenset同理); - 在响应中,
set/frozenset会转换回数组(list); - 生成的 JSON Schema 会使用
uniqueItems: true声明集合中的值唯一。
- 在请求中,读取一个数组(list),去重后转换为
注意 FastAPI 约定返回 set 时统一转换为 list,这正是 JSON 可序列化性所要求的——JSON 本身没有"集合"这一数据结构,只有数组。
bytes
- 标准 Python
bytes。 - 在请求和响应中按字符串处理(如二进制内容经过
decode得到文本)。 - 生成的 JSON Schema 声明为
type: string且format: binary。 - 若想处理真正的文件上传(multipart),应配合 request files 使用
UploadFile/File。
Decimal
- Python 标准
decimal.Decimal。 - 在请求和响应中与
float同等处理(按十进制精确解析,响应时再编码为 JSON 数值)。 - 适合金额等对精度敏感的场景。
以上类型并非全部。Pydantic 还支持 IP 地址、URL、颜色、枚举等更多数据类型,完整列表见 Pydantic 官方数据类型文档。
源码视角:这些转换究竟发生在哪
理解"请求字符串如何变成 Python 对象、返回的 Python 对象如何变成 JSON",可以看 FastAPI 的 JSON 兼容编码器实现 fastapi/encoders.py。其中定义了类型到编码函数的映射表 ENCODERS_BY_TYPE:
bytes→lambda o: o.decode()(字节串解码为普通字符串);datetime.date、datetime.datetime、datetime.time→isoformat(即o.isoformat(),产出 ISO 8601 文本);datetime.timedelta→lambda td: td.total_seconds()(换算成总秒数,这正解释了"请求/响应中 timedelta 是 float 秒数");Decimal→decimal_encoder(指数为非负整数时转为int,否则转为float,避免精度在 JSON 中丢失);frozenset、set→list(集合转数组);UUID→str。
jsonable_encoder 会在 FastAPI 发送任何响应前运行(内部也用于 Pydantic 模型 model_dump(mode="json") 之后),保证所有返回值都是 JSON 可序列化的。也就是说:你在函数体里做的任何类型运算(如日期相加、求差),最终返回时都会经过这张映射表得到 JSON 兼容结果。
完整示例:一个同时使用多种额外类型的接口
带类型注解的推荐写法(使用 Annotated)
文档主推的现代写法在 docs_src/extra_data_types/tutorial001_an_py310.py:
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,
}
代码要点:
item_id: UUID放在路径中。FastAPI 会先校验路径段是合法 UUID,再传入函数,所以函数体里拿到的是真正的UUID对象。start_datetime、end_datetime、process_after与可选的repeat_at都通过Body()声明为请求体字段。带时区的 ISO 8601 字符串进入函数前已被解析成真实的datetime/time/timedelta对象。repeat_at用time | None表示可为空,且带默认值None,因此请求体中可以省略它(OpenAPI 中对应的 Schema 是anyOf: [{"type": "string", "format": "time"}, {"type": "null"}],见测试快照)。- 函数体内的参数都是"原生 Python 类型",可以直接做常规运算:
start_datetime + process_after(把开始时刻加上处理时长得到真正开始处理的时刻),以及end_datetime - start_process(求时间差,得到一个timedelta,最终以总秒数形式出现在响应中)。
不使用 Annotated 的等价写法
仓库还提供了旧式写法 docs_src/extra_data_types/tutorial001_py310.py,二者行为完全一致:
from datetime import datetime, time, timedelta
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: datetime = Body(),
end_datetime: datetime = Body(),
process_after: timedelta = Body(),
repeat_at: time | None = Body(default=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 把 Body() 与类型绑定,后者用默认值传入 Body();实际效果相同。仓库测试 test_tutorial001.py 用参数化(pytest.param("tutorial001_py310") 与 pytest.param("tutorial001_an_py310"))把两个文件都跑了一遍,验证行为一致(示例使用 Python 3.10 及以上语法,测试以 needs_py310 标记约束运行环境)。
实际请求与响应示例
参照测试用例中的请求数据构造一次调用(接口为 PUT /items/{item_id}):
请求(路径与 JSON 请求体):
PUT /items/ff97dd87-a4a5-4a12-b412-cde99f33e00e
{
"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
}
观察几个细节:
item_id是带连字符的 UUID 字符串;- 两个日期时间带时区偏移
+00:00; repeat_at只给了时刻;process_after直接给了数值秒数 300(对应 5 分钟)。
响应(测试断言 response.json() 完全等于以下内容):
{
"item_id": "ff97dd87-a4a5-4a12-b412-cde99f33e00e",
"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,
"start_process": "2018-12-22T14:05:00+00:00",
"duration": 176100
}
据此可以直观地验证两条"计算"逻辑:
start_process(真正开始处理的时间)=start_datetime加 5 分钟 →14:05;duration=end_datetime减start_process。两天零一小时再减去 5 分钟后,时差为 48 小时 55 分钟 = 176100 秒,以 float 总秒数(这里是整数)输出,这正是timedelta的 JSON 编码规则。
自动生成的 OpenAPI 文档行为
启动应用后,访问自动文档(FastAPI 的 /docs,Swagger UI),路径参数与请求体的字段类型声明均来自类型注解。测试对 /openapi.json 做了完整快照断言,其中关键片段为:
- 路径参数
item_id:{"type": "string", "format": "uuid"}; - 请求体字段
start_datetime/end_datetime:{"type": "string", "format": "date-time"}; - 请求体字段
repeat_at:{"anyOf": [{"type": "string", "format": "time"}, {"type": "null"}]}(因可为空且非必填,因此不在required数组中); - 请求体字段
process_after:{"type": "string", "format": "duration"}; required数组为["start_datetime", "end_datetime", "process_after"](repeat_at因有默认值而可选)。
也就是说:交互式文档不仅提示字段名,还会自动标注每个字段的格式(uuid / date-time / duration / binary),并提供对应的输入校验,客户端开发与前后端联调因此可以直接复用这份契约。
补充:请求体中声明"非模型"字段
本例没有定义 Pydantic BaseModel,而是直接在函数签名里用 Body() 声明多个请求体字段。FastAPI 会为这种情况自动生成一个隐式的请求体模型(OpenAPI 中表现为名为 Body_read_items_items__item_id__put 的组件 Schema,可从测试快照中确认)。当你只需要一组松散字段、不希望单独建模型时,这是一种轻量写法;字段更多或结构复杂时,则更适合定义 请求体模型 以复用结构。
小结
- FastAPI 的额外数据类型支持"零配置、全自动":解析、校验、序列化、OpenAPI 文档由类型注解 + 底层 Pydantic / JSON 编码器共同完成。
- 网络层只认字符串与数字:
UUID与日期时间走 ISO 文本,timedelta走总秒数,set/frozenset走数组,bytes走binary格式字符串。 - 响应编码规则集中在 fastapi/encoders.py 的
ENCODERS_BY_TYPE映射表中,函数体内可放心使用原生类型做日期加减等运算。 - 仓库的示例代码位于 docs_src/extra_data_types/,配套的端到端测试位于 tests/test_tutorial/test_extra_data_types/test_tutorial001.py,包含运行时行为与 OpenAPI Schema 的完整断言,可作为理解与验证这些规则的第一手依据。
后续若想把这些类型放进 Pydantic 模型字段(响应模型、请求体模型),规则完全一致,可进一步阅读 请求体字段与模型、响应模型 及 JSON 兼容编码器。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0627
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00