FastAPI 系列

  1. FastAPI 系列:从第一个接口到生产部署

  2. FastAPI 为什么适合做 JSON API(当前篇)

  3. 第一个接口怎么跑起来

  4. 路径、查询和请求体为什么要分开声明

  5. Pydantic 模型怎么当契约

  6. 路由怎么拆

  7. 依赖注入到底省了什么

  8. 数据库会话怎么进接口

  9. 状态码和异常为什么不要裸 raise

  10. 鉴权怎么接到 Depends 上

  11. 中间件、CORS 和后台任务分别解决什么

  12. 测试怎么写才不连真实库

  13. 生产怎么部署

很多人第一次被 FastAPI 吸引,是因为打开 /docs 就能试接口。自动文档不是魔法,它是类型提示、Pydantic 和 OpenAPI 三条链路的结果。把这三层搞混,就会既写不好校验,也看不懂为什么生产必须用 ASGI 服务器。

请求经过 Uvicorn、FastAPI、Starlette 与 Pydantic

它站在哪一层

FastAPI 自己几乎不处理 TCP。典型调用栈是:

客户端
  → Uvicorn(ASGI 服务器)
    → FastAPI(路由、依赖、OpenAPI)
      → Starlette(ASGI 工具、中间件、WebSocket)
        → Pydantic(请求/响应数据校验)

ASGI 是异步服务网关接口,对应 WSGI 的下一代。Flask 默认走 WSGI;FastAPI 默认走 ASGI。所以生产入口是 Uvicorn 这类 ASGI 服务器,而不是 Waitress / Gunicorn 的同步 worker 单独扛请求(Gunicorn 现在更多是进程管理器角色,官方镜像路线也已改为直接用 Uvicorn workers)。

类型提示为什么是一等公民

下面这个函数不是「顺便写了类型」,类型就是接口契约:

from fastapi import FastAPI

app = FastAPI()


@app.get("/items/{item_id}")
async def read_item(item_id: int, q: str | None = None):
    return {"item_id": item_id, "q": q}

item_id: int 会让 FastAPI 把路径转换成整数,失败则返回 422。q: str | None = None 表示可选查询参数。这些信息同时用于:运行时校验、编辑器补全、OpenAPI schema。

Pydantic v2 负责把 JSON 变成模型实例。你声明的不是注释,是校验器。

OpenAPI 从哪来

应用启动后,FastAPI 扫描所有路径操作,生成 OpenAPI JSON,默认挂在:

/openapi.json
/docs      → Swagger UI
/redoc     → ReDoc

文档不是手写 Markdown 渲染出来的。你改了模型字段,文档跟着改。这也是为什么响应要用模型过滤:文档里写「不返回 password」,运行时也必须真的不返回。

async 到底快在哪

事件循环适合「等网络、等数据库」的时间。等待期间可以去处理别的请求,所以同样的线程能撑住更多 I/O 并发。

它不适合:

  • async def 里调用同步 requests.get 或同步 ORM;

  • 长时间 CPU 计算还不丢进线程池。

官方建议:I/O 用异步库;必须调用阻塞代码时,用普通 def 路径操作,FastAPI 会把它放到线程池,避免堵住事件循环。

常见误区

FastAPI 等于 Uvicorn

FastAPI 是应用框架,Uvicorn 是服务器。本地 fastapi dev 只是帮你把两者连起来。

类型提示只给 IDE 看

在 FastAPI 里,类型提示会进入校验和 OpenAPI。写错类型,线上就会按错误契约跑。

ASGI 会自动让整个调用链异步

ASGI 只定义服务器与应用的对话方式。你的依赖、数据库驱动、HTTP 客户端都必须自己选异步实现。

小结

记住三句话:服务器是 ASGI;契约是类型;文档是 OpenAPI 的投影。下一篇把第一个应用跑起来,让这三层变成可访问的 /docs

参考资料