FastAPI 教程详解:用 PydanticField为请求体模型声明字段校验与元数据(Body - Fields)
【免费下载链接】fastapiFastAPI framework, high performance, easy to learn, fast to code, ready for production项目地址: https://gitcode.com/GitHub_Trending/fa/fastapi
在 FastAPI 中,你可以用Query、Path、Body为路径操作函数(path operation function)的参数声明附加的校验规则与元数据;而本篇所介绍的Field,则是把同样的能力"下放"到Pydantic 模型内部的属性(model attribute)上——让你在请求体的数据结构定义处,就近声明每个字段的约束、标题、描述等 JSON Schema 元数据。读完本篇,你将掌握Field的正确导入方式、在模型属性上的完整用法、它与Query/Path/Body共享的底层继承关系,以及它生成的 OpenAPI/JSON Schema 究竟长什么样。
本篇以仓库内韩语文档 docs/ko/docs/tutorial/body-fields.md 为骨架,并结合仓库中的 示例代码、单元测试 与 FastAPI 参数类源码 展开。
一、先导概念:什么时候需要在模型属性上"写校验"
回顾之前几篇教程你会看到两种位置的声明方式:
- 在路径操作函数参数上:通过
Query、Path、Body声明校验与元数据,相关主题见 查询参数与字符串校验、路径参数与数值校验; - 在Pydantic 模型(请求体)的属性上:使用 Pydantic 的
Field。
当你用BaseModel定义一个请求体模型,比如Item,字段name、description、price、tax本身已经带有类型约束(如str、float),但若还需要"价格必须大于 0""描述最长 300 字符""这个字段在文档里显示一个特定标题"这类声明式约束,就要借助Field写在模型内部——这正是本篇的主题。
二、导入Field:来源是pydantic,不是fastapi
Field是Pydantic提供的函数,因此导入时必须直接来自pydantic:
from fastapi import Body, FastAPI from pydantic import BaseModel, Field⚠️警告(重要)
Field不像Query、Path、Body那样从fastapi导入,而是直接从pydantic导入。FastAPI 的这类辅助函数(Query、Path、Body等)本质上是在 FastAPI 层对请求参数的封装,而Field是纯 Pydantic 概念,它服务于模型类的字段声明,不依赖 FastAPI 的请求上下文。这一点在原文档(docs/ko/docs/tutorial/body-fields.md)中被特别强调,混用导入源是新手最容易踩的坑。
仓库内的示例代码 tutorial001_an_py310.py 第 4 行正是这一导入方式的直接体现。
三、在模型属性上声明校验与元数据:完整可运行示例
导入后,就可以在模型类的属性上直接使用Field。仓库中的完整示例(Annotated 风格版本)如下:
from typing import Annotated from fastapi import Body, FastAPI from pydantic import BaseModel, Field app = FastAPI() class Item(BaseModel): name: str description: str | None = Field( default=None, title="The description of the item", max_length=300 ) price: float = Field(gt=0, description="The price must be greater than zero") tax: float | None = None @app.put("/items/{item_id}") async def update_item(item_id: int, item: Annotated[Item, Body(embed=True)]): results = {"item_id": item_id, "item": item} return results对应源码见 docs_src/body_fields/tutorial001_an_py310.py。仓库还提供了不使用Annotated、采用默认值语法的等价版本 docs_src/body_fields/tutorial001_py310.py,二者的差别仅在于请求体参数的声明风格:
@app.put("/items/{item_id}") async def update_item(item_id: int, item: Item = Body(embed=True)): results = {"item_id": item_id, "item": item} return results两个文件都是py310后缀,表明它们使用了 Python 3.10+ 的X | None联合类型语法;仓库 pyproject.toml 声明requires-python = ">=3.10",对应的测试也都通过needs_py310标记来限定 Python 版本(见 tests/test_tutorial/test_body_fields/test_tutorial001.py)。
这段代码里Field展示了三种用途:
| 属性 | 声明写法 | 含义 |
|---|---|---|
description | Field(default=None, title=..., max_length=300) | 默认值为None(可选字段);文档标题显示为"The description of the item";长度上限 300 |
price | Field(gt=0, description=...) | 必须大于 0;并附带一段用于交互文档的描述文本 |
name、tax | 普通类型注解(无Field) | 仅类型约束,无附加元数据 |
其中gt=0对应"大于(greater than)"约束,FastAPI 会据此生成数值校验;max_length=300则约束字符串长度。测试 test_tutorial001.py 验证了当请求中price为负数-3.0时接口返回422,错误类型为greater_than,msg为"Input should be greater than 0"——说明gt校验在运行时真实生效。
四、运行与验证:字段元数据如何落到 JSON Schema / OpenAPI
将上面应用保存后用uvicorn运行(FastAPI 自带fastapi dev/uvicorn工作流),请求PUT /items/5:
{"item": {"name": "Foo", "price": 3.0}}返回中会由 FastAPI 自动补齐默认字段:
{ "item_id": 5, "item": {"name": "Foo", "price": 3.0, "description": null, "tax": null} }这一点由测试 test_items_5 精确断言。更关键的是观察生成的 OpenAPI schema(GET /openapi.json),测试 test_openapi_schema 给出了快照级的预期结果。其中Item模型对应的components.schemas.Item会呈现:
"Item": { "title": "Item", "required": ["name", "price"], "type": "object", "properties": { "name": {"title": "Name", "type": "string"}, "description": { "title": "The description of the item", "anyOf": [{"maxLength": 300, "type": "string"}, {"type": "null"}] }, "price": { "title": "Price", "exclusiveMinimum": 0.0, "type": "number", "description": "The price must be greater than zero" }, "tax": {"title": "Tax", "anyOf": [{"type": "number"}, {"type": "null"}]} } }从这份输出可以清晰地读出Field参数与 JSON Schema 的映射关系:
default=None+str | None联合类型 →anyOf中包含{"type": "null"};title="The description of the item"→ 属性title直接替换默认标题;max_length=300→ 出现在字符串 schema 的maxLength键;gt=0→ 对应数值 schema 的exclusiveMinimum: 0.0;description=...→ 原样出现在属性description上。
也就是说,Field中声明的一切都会被纳入JSON Schema,并最终进入OpenAPI schema,进而在/docs交互式文档与生成客户端中可见。这正是原文档强调"用Field声明校验和元数据"的核心价值。
五、深入原理:Field与Query/Path/Body的底层血缘关系
原文档用一段"技术细节(Technical Details)"说明了它们之间的类层次关系,我们可以结合仓库源码来印证:
fastapi中你看到的Query、Path、Body,从fastapi导入时实际上是函数,调用后返回特定类的对象;- 这些类(
Query、Path等)是公共类Param的子类,而Param本身又是 PydanticFieldInfo的子类; - Pydantic 的
Field返回的同样是FieldInfo的实例; Body直接返回FieldInfo子类的对象,后续教程中还会有其他Body子类的存在。
打开仓库源码 fastapi/params.py 可以逐条核对:
- 第 10 行
from pydantic.fields import FieldInfo; - 第 26 行
class Param(FieldInfo):—— 公共参数基类直接继承 Pydantic 的FieldInfo; - 第 137 行
class Path(Param):、第 221 行class Query(Param):——Path、Query继承自Param; - 第 469 行
class Body(FieldInfo):——Body直接继承FieldInfo。
而 fastapi/param_functions.py 则定义了Query()、Path()、Body()等对外暴露的函数,用于创建上述类的实例。
正是因为有FieldInfo这条共同的"血缘",Field才能与Query、Path、Body共享同一套参数集合(标题、默认值、数值范围、长度约束、描述等)。用原文档里的话说:模型中的每个属性——类型 + 默认值 +Field——与路径操作函数参数——用Field替换掉Path/Query/Body——在结构上是同构的。这也是为什么 Pydantic 模型可以直接作为 FastAPI 的请求体/响应模型使用:两者共享同一套元数据语义。
💡技巧:把
Field与Query、Body放在一起对比记忆会事半功倍——它们在声明结构、参数名与生成的 Schema 行为上高度一致,只是作用对象不同:Field面向模型属性,其余三者面向路径操作函数的参数。
六、传递额外信息:自定义键会进入 OpenAPI,但要谨慎
在Field、Query、Body等中,你还可以传入任意"额外信息"(extra information),例如为后续学习"示例(examples)"章节时使用的键。这些内容会被包含进生成的 JSON Schema。
仓库中关于如何在请求体中声明示例的进阶主题位于 Schema 额外示例(schema-extra-example);而本篇示例中直接通过description为字段附加说明文本,就是"额外元数据进入 schema"的最简形式。
⚠️警告
传递给
Field的额外键也会出现在你应用的最终 OpenAPI schema 中。由于这些键未必属于 OpenAPI 规范本身,一些严格的 OpenAPI 工具(例如 OpenAPI 官方校验器 swagger validator)可能无法兼容你生成的 schema。因此,自定义额外键要节制使用,并清楚其只在部分工具链内可见。
七、小结(Recap)
- 使用 Pydantic 的
Field,可以在模型属性层面声明附加校验与元数据,与Query/Path/Body在路径操作函数参数层面提供的能力一一对应; Field必须从pydantic导入(而非fastapi);- 除了常规约束(如
gt、max_length、default、title、description),还可以通过额外关键字参数向生成的 JSON Schema / OpenAPI 传递自定义元数据; - 模型的完整示例代码与测试分别位于 docs_src/body_fields/ 与 tests/test_tutorial/test_body_fields/test_tutorial001.py,可作为对照学习的可运行范本。
掌握Field后,建议继续阅读仓库中的相关主题以形成完整拼图:嵌套模型的字段声明见 Body - Nested Models,请求体与参数混用见 Body - Multiple Params。若需对照其他语言版本,本教程的英文原文位于 docs/en/docs/tutorial/body-fields.md。
【免费下载链接】fastapiFastAPI framework, high performance, easy to learn, fast to code, ready for production项目地址: https://gitcode.com/GitHub_Trending/fa/fastapi
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考