1070 字
约 3 分钟
7
S10-B04 · FastAPI 入门:类型即文档

适用读者:要写第一个 Python API,且希望"少写校验代码"的人。
前置:S10-B03。
实测环境:FastAPI 0.162.0 / uvicorn 0.54.0 / Python 3.12.3(2026-11-05 真实启动 + curl)。
覆盖旧文:A-195(FastAPI 入门教程,292 点)。
覆盖扩展:E-S10-005(Pydantic 校验与 422)、E-S10-006(OpenAPI 由类型推导)。
上一篇:S10-B03 · 虚拟环境全生命周期:创建/激活/删除/导出
下一篇:S10-B05 · Flask 路由与错误页。


1. 一句话结论

FastAPI 的核心不是"快",是"类型标注即契约":你写 name: str,框架替你做校验、生成文档、生成 OpenAPI。

你写的 框架自动得到
函数签名类型 请求校验(422 + 明细)
Pydantic 模型 请求体 schema
返回类型 响应 schema
全部路径 /openapi.json 与 /docs

2. 最小可运行应用(实测代码)

from fastapi import FastAPI, HTTPException, Query
from pydantic import BaseModel, Field

app = FastAPI(title="S10 Demo", version="1.0.0")

class Item(BaseModel):
    name: str = Field(min_length=1, max_length=20)
    price: float = Field(gt=0)
    tags: list[str] = []

DB: dict[int, Item] = {}
_next = 1

@app.get("/items/{item_id}")
def get_item(item_id: int, verbose: bool = Query(False)):
    if item_id not in DB:
        raise HTTPException(status_code=404, detail="item not found")
    return {"id": item_id, "verbose": verbose, **DB[item_id].model_dump()}

@app.post("/items", status_code=201)
def create_item(item: Item):
    global _next
    iid = _next; _next += 1; DB[iid] = item
    return {"id": iid, **item.model_dump()}

@app.get("/health")
def health():
    return {"ok": True}

启动:

$ uvicorn app:app --port 19060 --log-level warning

3. 实测:校验到底有多细(E-S10-005)

3.1 正常路径

$ curl -s http://127.0.0.1:19060/health
{"ok":true}

$ curl -s -X POST http://127.0.0.1:19060/items -H 'Content-Type: application/json' \
       -d '{"name":"apple","price":9.9,"tags":["fruit"]}'
{"id":1,"name":"apple","price":9.9,"tags":["fruit"]}

3.2 非法入参:422 + 逐字段明细

$ curl -s -X POST .../items -d '{"name":"","price":-1}'
HTTP 422
{"detail":[
  {"type":"string_too_short","loc":["body","name"],
   "msg":"String should have at least 1 character","input":"","ctx":{"min_length":1}},
  {"type":"greater_than","loc":["body","price"],
   "msg":"Input should be greater than 0","input":-1,"ctx":{"gt":0.0}}
]}

三个字段就能定位问题:

字段 含义 用法
loc 出错位置(["body","name"]) 直接告诉前端改哪个字段
msg 人能读的原因 展示给调用方
type 机器可读的错误码 前端做统一映射

注意:detail 是数组——一次请求可能同时触发多个字段错误(实测就是两条)。

3.3 类型错误与 404

$ curl -o /dev/null -w "%{http_code}" .../items/abc
422                    # 路径参数类型不符 → 校验层拦截
$ curl -o /dev/null -w "%{http_code}" .../items/999
404                    # 通过校验,业务层抛 HTTPException

关键区分:422 是"请求不合法",404 是"资源不存在"。这个分离让前端能区分"我错了"还是"东西没了"。


4. 文档是自动生成的(E-S10-006)

$ curl -s http://127.0.0.1:19060/openapi.json | python -c "import json,sys; d=json.load(sys.stdin); print('title:', d['info']['title']); print('paths:', list(d['paths'])); print('Item props:', list(d['components']['schemas']['Item']['properties']))"
title: S10 Demo
paths: ['/items/{item_id}', '/items', '/health']
Item props: ['name', 'price', 'tags']

这说明:/openapi.json 里的路径与 schema 完全来自你的函数签名与 Pydantic 模型——你没有写一行文档。

端点 内容
/docs Swagger UI(可直接试调)
/redoc ReDoc(更适合阅读)
/openapi.json 机器可读契约(可喂给 S02-B10 的工具化链路)

与 S02-B10 的接口:这个 openapi.json 就是"API → 可执行工具"的输入——描述质量决定工具质量。


5. 五个易踩点

坑 说明
路径参数没写类型 默认变成 str,"abc" 也能进业务
Query/Body 不声明 参数被当成 query 还是 body 取决于默认规则,易混
同步函数跑阻塞操作 会占满线程池(J02 讲异步)
HTTPException 返回结构被改 自定义 exception_handler 会改变 detail 结构
状态码靠默认 创建类接口应显式 status_code=201

6. 自检

  1. FastAPI 的校验来自哪里?为什么不用手写 if not name:?
  2. 422 的 loc/msg/type 分别有什么用?
  3. 422 与 404 的区别是什么?
  4. /openapi.json 的内容由什么决定?
  5. 路径参数不写类型会有什么后果?

7. 下一篇

S10-B05 · Flask 路由与错误页

下一篇对照另一个主流框架:Flask 的路由注册与自定义 404/500,并用实测说明它对"类型不匹配"的处理与 FastAPI 完全不同。


本篇属于系列《S10 · Python 语言与环境》。旧文来源:A-195;扩展点 E-S10-005/006。FastAPI 0.162.0 全部响应为 2026-11-05 实测。

S10-B04 · FastAPI 入门:类型即文档
https://www.clxhxhhr.top/posts/5319/
作者
clxstart
发布于
2024-12-31
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。