Skip to content

请求体与校验

GET 请求通过 URL 传参数,适合查询。提交数据——创建文章、更新内容、登录——要把数据放在请求体里。FastAPI 用 Pydantic 模型定义请求体的结构:哪些字段、什么类型、哪些必填、有什么约束。

一、Pydantic 模型

Pydantic 是一个数据校验库。定义一个模型类,声明字段和类型,Pydantic 自动校验传入数据:

python
from pydantic import BaseModel


class ArticleCreate(BaseModel):
    title: str          # 必填,字符串
    content: str        # 必填,字符串
    status: str = "draft"  # 可选,默认 draft

ArticleCreate 就是一个继承了 BaseModel 的类。字段后面跟类型标注,带默认值的是可选字段。这个模型描述了"创建一篇文章时,请求体应该长什么样"

二、接收 POST 请求体

FastAPI 里,函数参数标注成 Pydantic 模型类型,就自动从请求体解析和校验:

python
from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()


class ArticleCreate(BaseModel):
    title: str
    content: str
    status: str = "draft"


@app.post("/articles")
def create_article(article: ArticleCreate):
    return {"title": article.title, "content": article.content, "status": article.status}

article: ArticleCreate 这个参数,FastAPI 看到类型是 Pydantic 模型,就从请求体的 JSON 里取数据,按 ArticleCreate 的字段校验。

测试:

bash
curl -X POST http://localhost:8000/articles \
  -H "Content-Type: application/json" \
  -d '{"title": "新文章", "content": "正文"}'

少传 status 不报错——有默认值。少传 title 会报 422:

json
{
  "detail": [
    {
      "type": "missing",
      "loc": ["body", "title"],
      "msg": "Field required"
    }
  ]
}

422 是 FastAPI 的校验失败状态码,错误信息里写清楚了哪个字段出了什么问题。这个错误格式是自动的,不用手写校验逻辑。

三、字段约束

Pydantic 用 Field 给字段加约束——最小长度、最大长度、正则匹配等:

python
from pydantic import BaseModel, Field


class ArticleCreate(BaseModel):
    title: str = Field(min_length=1, max_length=100, description="文章标题")
    content: str = Field(min_length=1, description="正文内容")
    status: str = Field(default="draft", pattern="^(draft|published)$")

title 为空字符串,或者 status 传了 "archived"(不匹配正则),都返回 422 错误。

常用的约束:

约束作用于示例
min_length / max_length字符串标题 1-100 字符
gt / ge / lt / le数字端口 > 0,分页 limit ≤ 100
pattern字符串正则状态只能是 draft 或 published
default所有类型不传时的默认值

这些约束直接体现在自动文档里——访问 /docs 时,每个字段的约束一目了然。

四、响应模型

默认情况下接口返回什么,FastAPI 就原样转成 JSON。但有时候想控制返回哪些字段——比如数据库实体里有密码字段,返回给前端时不该包含。

response_model 声明接口返回什么结构,FastAPI 按这个模型过滤多余字段:

python
class ArticleResponse(BaseModel):
    id: int
    title: str
    status: str
    # 不含 content——列表接口不需要返回正文


@app.get("/articles", response_model=list[ArticleResponse])
def list_articles():
    return [
        {"id": 1, "title": "Python 入门", "content": "很长的正文...", "status": "published"},
        {"id": 2, "title": "Go 并发", "content": "也很长...", "status": "draft"},
    ]

虽然函数返回的数据里有 content,但 response_model 声明了只返回 idtitlestatus,实际响应里 content 被过滤掉了。列表接口不返回大字段(正文)、详情接口才返回完整内容——这种区分用 response_model 控制

五、嵌套模型

复杂的数据结构用嵌套模型表达。一篇文章带作者信息:

python
from pydantic import BaseModel


class Author(BaseModel):
    name: str
    email: str


class ArticleDetail(BaseModel):
    id: int
    title: str
    status: str
    author: Author      # 嵌套模型
    tags: list[str]     # 字符串列表

对应的 JSON:

json
{
  "id": 1,
  "title": "Python 入门",
  "status": "published",
  "author": {
    "name": "张三",
    "email": "zhangsan@example.com"
  },
  "tags": ["Python", "入门"]
}

Pydantic 自动处理嵌套校验——author 里的 name 少了或类型不对,一样返回 422。

六、更新操作——部分字段可选

创建时所有字段必填,更新时只改部分字段。用两种模型区分:

python
from pydantic import BaseModel
from typing import Optional


class ArticleCreate(BaseModel):
    title: str
    content: str
    status: str = "draft"


class ArticleUpdate(BaseModel):
    title: Optional[str] = None       # 全部可选,不传就是不改
    content: Optional[str] = None
    status: Optional[str] = None


@app.patch("/articles/{article_id}")
def update_article(article_id: int, article: ArticleUpdate):
    # 只更新传了的字段
    stored = {"id": article_id, "title": "原标题", "content": "原内容", "status": "draft"}

    update_data = article.model_dump(exclude_unset=True)  # 只拿传了的字段
    stored.update(update_data)

    return stored

Optional[str] = None 让字段变成可选——不传时是 Nonemodel_dump(exclude_unset=True) 只返回客户端实际传了的字段,没传的不包含——这样 PATCH 更新就只改传入的字段,不动其他字段