FastAPI 入门与 ASGI
本系列是一本「轻量用法手册」:每章只认识 FastAPI 生态里的一个零件,讲清楚它是什么、怎么装、怎么跑、文档去哪看,不搭建贯穿全课程的大工程。示例数据统一来自虚构的内容平台「墨集 inkhub」——栏目、文章、作者…
本系列是一本「轻量用法手册」:每章只认识 FastAPI 生态里的一个零件,讲清楚它是什么、怎么装、怎么跑、文档去哪看,不搭建贯穿全课程的大工程。示例数据统一来自虚构的内容平台「墨集 inkhub」——栏目、文章、作者,仅作数据演示。
读完本章你会:拿到一张框架选型对比表、听懂 WSGI/ASGI 这对名词、装好 Uvicorn、跑起来第一个服务、打开 /docs 玩一圈、给 Pydantic 留个第一印象。
1. 先摊开地图:Django、Flask、FastAPI 怎么挑
是什么
三者都是用 Python 写 Web 服务的框架,但性格完全不同。用装修打个比方:
- Django(2005 年生):整装交付的精装大平层。家具、家电、保安(ORM、后台管理、用户认证)全给你配齐,拎包入住;代价是想砸一面墙,得先弄清整栋楼的水电走向。
- Flask(2010 年生):一间毛坯房加基础水电。框架只给最核心的一小坨,瓷砖、灯具自己挑,自由度最高,腿也跑得最勤。
- FastAPI(2018 年生):专为「外卖档口」设计的小店。动线全围绕出餐(API)优化——类型标注一写,参数校验、接口文档全都自动跟上。
一张表横向看:
| 对比维度 | Django | Flask | FastAPI |
|---|---|---|---|
| 出场年份 | 2005 | 2010 | 2018 |
| 性格 | 大包大揽,什么都要自己管 | 极简内核,按需外挂 | 为 API 而生,类型驱动 |
| 速度体感(Hello World) | 约 5k req/s | 约 9k req/s | 约 30k req/s |
| 上手难度 | 陡峭:概念多、体量大 | 平缓:核心直观 | 中等:得先习惯类型标注与异步 |
| 顺手的场景 | 大型站点、CMS、自带后台的项目 | 小工具、快速原型、高度定制 | 高性能 API、微服务、给 AI 模型开接口 |
| 代表用户 | Instagram、Pinterest | Netflix、Airbnb | Uber、Microsoft、Netflix(部分业务) |
怎么选:一句话——要后台管理选 Django,要轻巧灵活选 Flask,要做 API 选 FastAPI。本课程从这章起全程 FastAPI。
怎么看文档
- Django 官网:https://www.djangoproject.com/
- Flask 官网:https://flask.palletsprojects.com/
- FastAPI 官网(有中文版):https://fastapi.tiangolo.com/
2. WSGI 与 ASGI:前台和后厨的通话规范
是什么
先解决一个困惑:你在浏览器敲下 http://127.0.0.1:8000/columns,这串请求本质是原始字节流;而你的 Python 函数拿到的是规整的参数、返回的是 dict。中间的「翻译 + 体力活」由谁干?
答案是分工:
- Web 服务器:干体力活——守端口、收字节、拆报文、送响应。好比餐厅的前台。
- Web 应用(框架):干脑力活——对路由、校验数据、跑业务、拼结果。好比餐厅的后厨。
前台和后厨怎么对话?得先约好一套「通话规范」。Python 社区先后定过两版:
| 规范 | 定位 | 工作模式 | 一个比喻 |
|---|---|---|---|
| WSGI | 早期标准 | 同步 + 多线程 | 传统小馆:一个服务员从头盯到尾一桌,接待量靠多雇人(线程)堆出来 |
| ASGI | 现代标准 | 异步 + 事件循环 | 网红店:一个机灵服务员满场穿梭,几十桌同时照看,谁举手先应谁 |
一句话记住:WSGI 是「一桌一服务员」的世界,ASGI 是「一人看全场」的世界。高并发、WebSocket、长连接这些新需求,都得靠 ASGI 才好使。
2.1 ASGI 服务器(前台)具体在忙什么
- 守着端口,接收客户端连接,读写原始字节流
- 把
b'GET /columns/1 HTTP/1.1\r\n...'这样的字节拆成结构化的 method、path、headers - 处理 HTTP/1.1、HTTP/2、WebSocket 的协议细节(分块传输、Keep-Alive……)
- 启动并维护事件循环,管理进程与线程
2.2 ASGI 应用(后厨)具体在忙什么
- 路由匹配、数据校验、执行业务逻辑、组装响应
- 常见 ASGI 应用框架:FastAPI(底层是 Starlette)、Django(新版本同样支持)、Starlette 本身
2.3 双方如何对接:接头暗号是 app
规范要求:ASGI 应用必须由 ASGI 服务器启动,且对外暴露一个可调用对象 app,签名固定:
app(scope, receive, send)
# scope: dict,本次连接的「订单小票」——type(http / websocket / lifespan)、path、method、headers 等
# receive: 异步函数,await receive() 拿到下一段「原料」(请求体 / 客户端消息)
# send: 异步函数,await send(message) 把做好的「菜」递出传菜口(响应头 / 响应体)
平时写 FastAPI 你几乎碰不到这三个参数——框架已经替你包好了。但排查部署问题、读框架源码时,认得这层接头暗号会轻松很多。
2.4 一单请求的完整旅程
- Uvicorn 在 8000 端口守门
- 客人进门:请求字节流到达
- 前台拆包:把字节流解析成 scope 小票,备好 receive / send 两个对讲频道
- 前台喊单:调用
app(scope, receive, send) - FastAPI 拿小票对路由,找到对应的灶台(你写的处理函数)
- 灶台按需取原料(
await receive())、做菜、递菜(await send()) - 前台把菜打包成完整的 HTTP 响应,字节流写回 socket,客人收货
2.5 常见 ASGI 服务器速查
| 服务器 | 一句话画像 | 协议覆盖 | 什么时候会想到它 |
|---|---|---|---|
| Uvicorn | uvloop + httptools 双引擎 | HTTP/1、WebSocket(HTTP/2 实验性) | FastAPI 圈默认款,本课程全程用它 |
| Hypercorn | 协议覆盖最全的全能型 | HTTP/1、HTTP/2、HTTP/3、WebSocket | 要上 HTTP/2 / HTTP/3 时 |
| Daphne | Django Channels 官方配套 | HTTP/1、HTTP/2、WebSocket | Django 异步项目 |
| Granian | Rust 实现,性能狂热派 | HTTP/1、HTTP/2、WebSocket | 追求极限吞吐的生产环境 |
| Gunicorn | 老牌 WSGI 服务器,可挂 ASGI worker | HTTP/1 | 想要「多进程 + ASGI」混搭部署时 |
| NGINX Unit | 多语言通吃的应用服务器 | HTTP/1、HTTP/2 | 一个入口管理多语言应用时 |
怎么看文档:ASGI 规范见 https://asgi.readthedocs.io/ ,属于兴趣加餐,不要求一次读完。
3. Uvicorn:给应用装上发动机
是什么
Uvicorn 是目前最流行的 ASGI 服务器。打个比方:FastAPI 是车身,Uvicorn 是发动机——车身再漂亮,没发动机点不着火。第 2 节里的 app,最终就是被 Uvicorn 拉起来跑的。
怎么装
uv add fastapi "uvicorn[standard]"
两个细节:
uvicorn[standard]的[standard]叫 extras(可选依赖组),勾选后会额外装上 uvloop、httptools 等加速件,性能明显更好,日常直接带上- 两侧引号别省:zsh 等终端会把方括号当特殊字符解析,加引号最稳妥
怎么跑(三种姿势,按需取用)
姿势一:脚本内嵌启动(本章 demo 的做法,最省心):
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="127.0.0.1", port=8000)
姿势二:命令行启动(「模块路径:应用变量」定位):
uv run uvicorn demo.demo_app:app --reload
姿势三:官方脚手架命令(自带热重载):
uv run fastapi dev demo/demo_app.py
生产环境再加码:--host 0.0.0.0 对外开放,--workers 4 多进程吃满多核。注意 --reload 与 --workers 互斥,前者只留给开发期。
常用参数速查:
| 参数 | 作用 | 建议值 |
|---|---|---|
--host |
监听地址 | 本机调试 127.0.0.1;对外 0.0.0.0 |
--port |
监听端口 | 8000 |
--reload |
代码一改自动重启 | 仅开发期 |
--workers |
工作进程数 | 生产环境 2 ~ CPU 核数 |
怎么看文档:https://www.uvicorn.org/ ,全部配置项在「Settings」一节。
4. 第一个应用:墨集开门营业
怎么装:命令同上一节;若本章目录还不是 uv 工程,先 uv init 再 uv add。
怎么跑
uv run demo/demo_app.py
终端出现 Uvicorn running on http://127.0.0.1:8000 就是点着火了。
代码全景(节选自 demo/demo_app.py,完整文件可直接运行):
from fastapi import FastAPI
from pydantic import BaseModel, Field
app = FastAPI(title="墨集 inkhub", version="0.1.0")
class ArticleCreate(BaseModel):
"""创建文章的请求体"""
title: str = Field(min_length=1, max_length=30, examples=["夜航船里的旧灯"])
column_id: int = Field(ge=1, description="文章所属的栏目编号")
tags: list[str] = Field(default_factory=list, examples=[["散文", "夜记"]])
@app.get("/columns/{column_id}", summary="栏目详情")
async def get_column(column_id: int) -> dict[str, object]:
... # 按编号查找栏目并返回
@app.post("/articles", summary="创建文章")
async def create_article(article: ArticleCreate) -> dict[str, object]:
... # article 已通过校验,此处用内存列表代替真实落库
逐行拆解
app = FastAPI():造出一个符合 ASGI 规范的可调用对象,变量名习惯叫app——正好对上第 2 节的接头暗号@app.get("/columns/{column_id}"):挂招牌,HTTP 方法 + 路径一一对应;{column_id}是路径里的占位坑column_id: int:一行类型标注,就是 FastAPI 的「魔法开关」——框架自动把路径字符串转成 int,转不动(比如传abc)直接回 422- 请求体:参数标成 Pydantic 模型
article: ArticleCreate,框架自动解析 JSON 并校验 - 返回值:dict / list 原样返回,框架自动打包成 JSON 响应
怎么看效果:浏览器挨个访问
| 地址 | 看点 |
|---|---|
| http://127.0.0.1:8000/ | 首页欢迎语 |
| http://127.0.0.1:8000/columns | 栏目列表 |
| http://127.0.0.1:8000/columns/1 | 栏目详情;把 1 换成 abc 体验 422 |
| http://127.0.0.1:8000/articles?limit=1&keyword=伞 | 查询参数:limit 截断条数、keyword 按标题模糊匹配 |
| http://127.0.0.1:8000/docs | 交互式文档(下一节主角) |
| http://127.0.0.1:8000/redoc | 只读文档 |
| http://127.0.0.1:8000/openapi.json | 机器可读的接口总账 |
怎么看文档:https://fastapi.tiangolo.com/zh/ ,中文版教程质量很高,跟着翻一遍胜过十篇二手博客。
5. /docs、/redoc、/openapi.json:自动印好的说明书
是什么
三个地址,三份「自动生成的产品说明书」:
- OpenAPI 规范:用一份标准 JSON 描述整套 API 的行业规范。FastAPI 启动时扫描全部路由和类型标注,自动生成 openapi.json 并挂在
/openapi.json——这就是接口的「机器可读总账」 - Swagger UI(
/docs):能试吃的柜台。点「Try it out」填参数、发请求、看真实响应,开发调试期的主力 - ReDoc(
/redoc):精装印刷的菜谱。三栏排版、滚动到哪渲染到哪,上百个接口的大文档也不卡;适合对外发布接口文档
| 对比点 | Swagger UI(/docs) | ReDoc(/redoc) |
|---|---|---|
| 定位 | 交互调试 | 阅读查阅 |
| Try it out | 支持,页面上直接发请求 | 不支持,纯只读 |
| 界面 | 功能全,配置项多 | 简洁清爽,长文档加载快 |
| 适用阶段 | 开发自测 | 对外发布 |
怎么让文档更好看:路由装饰器上直接加文案:
@app.get(
"/columns",
summary="栏目列表",
description="返回墨集当前全部栏目,含名称与一句话简介",
response_description="栏目数组",
)
async def list_columns() -> list[dict[str, object]]:
...
怎么关掉它们:文档本质也是路由,把地址设成 None 就摘牌:
app = FastAPI(docs_url=None, redoc_url=None, openapi_url=None)
生产环境常见写法是按环境变量动态决定:
import os
is_production = os.getenv("INHUB_ENV") == "prod"
app = FastAPI(
docs_url=None if is_production else "/docs",
redoc_url=None if is_production else "/redoc",
openapi_url=None if is_production else "/openapi.json",
)
怎么看文档:路由级配置项大全见 https://fastapi.tiangolo.com/zh/tutorial/path-operation-configuration/ ;OpenAPI 规范本身见 https://www.openapis.org/ 。
6. Pydantic 初见:给数据过安检
是什么
Pydantic 是一个「数据建模 + 校验 + 转换」三合一的库,FastAPI 的地基之一。把它想象成海关安检通道:
- 字符串数字
"101"进来,整数101出去(转换) - 缺斤少两、超长越界的字段,当场拦下并开出明细单(校验 + ValidationError)
- 出关之后,代码里拿到的一定是干净、类型正确的数据(建模)
怎么装:不用单独装——uv add fastapi 时它作为底层依赖已经进包了。
怎么跑:uv run demo/demo_pydantic.py,纯脚本、不起服务,直接在终端看结果:
from datetime import datetime
from pydantic import BaseModel, Field, ValidationError
class Author(BaseModel):
author_id: int = Field(ge=1, description="作者编号,从 1 开始")
pen_name: str = Field(min_length=1, max_length=12, examples=["沈墨"])
class Article(BaseModel):
article_id: int = Field(default=0, ge=0)
title: str = Field(min_length=1, max_length=30)
published_at: datetime | None = Field(default=None)
author: Author
tags: list[str] = Field(default_factory=list)
raw = {
"article_id": "101", # 字符串数字
"title": "夜航船里的旧灯",
"published_at": "2026-08-27 09:30", # 字符串时间
"author": {"author_id": "7", "pen_name": "沈墨"},
"tags": ["散文", "夜记"],
}
article = Article(**raw) # 安检通过:类型全部转换到位
print(article.article_id, article.published_at)
print(article.model_dump()) # 导出为普通字典
故意塞一份违规数据,看它怎么拦:
bad = {
"article_id": -1, # 违反 ge=0
"title": "", # 违反 min_length=1
"author": {"author_id": 0, "pen_name": "沈" * 20}, # 违反 ge=1 与 max_length=12
}
try:
Article(**bad)
except ValidationError as exc:
for error in exc.errors():
print(error["loc"], "->", error["msg"]) # 哪个字段、什么原因,逐条列出
Field:给字段做精装修。裸类型标注只能管「是或不是」,Field 能管「到什么程度」:
class Column(BaseModel):
column_id: int | None = Field(
default=None, # 默认值
title="栏目ID", # 文档里的字段标题
description="栏目的唯一编号,新建时留空由系统分配",
ge=1, # 必须 ≥ 1
examples=[1, 2], # 文档里展示的示例值
)
name: str = Field(
..., # 三个点 = 必填
title="栏目名称",
min_length=2,
max_length=12,
examples=["深夜书房"],
)
它和 FastAPI 的三点关系(后面章节会反复用到):
- 请求体声明成 Pydantic 模型后,校验全自动——字段越线直接回 422,错误明细就是
errors()那个格式,一行 if 都不用你写 - 响应同样可以用模型约束输出结构(第 2 章展开)
- 模型的字段、约束、示例全部进 openapi.json,/docs 里的表单就是照着模型渲染出来的
怎么看文档:https://docs.pydantic.dev/latest/ ,Field 的全部约束参数在「API → Fields」页。
7. 收摊小结
- 选型口诀:要后台管理找 Django,要轻巧灵活找 Flask,要做 API 用 FastAPI
- WSGI 是同步多线程规范,ASGI 是异步规范;服务器干体力活、应用干脑力活,接头暗号是
app(scope, receive, send) - Uvicorn 是发动机:
uv add fastapi "uvicorn[standard]"一条命令备齐全家 - 类型标注是 FastAPI 的魔法开关:参数转换、请求校验、自动文档,全都从这一行标注开始
- /docs 用来试吃,/redoc 用来阅读,/openapi.json 是机器可读的接口总账
- Pydantic 是数据安检机:转换脏数据、拦下违规数据,
errors()会把原因逐条列清楚
8. 自测清单
- 用自己的话向同事讲清 WSGI 和 ASGI 的分工差异
- Uvicorn 和 FastAPI 各扮演什么角色?写好的
app为什么自己跑不起来? /docs与/redoc分别适合什么阶段使用?openapi.json 是谁、在什么时机生成的?- 浏览器访问
/columns/abc时发生了什么?谁发现的问题、谁返回的 422? - Pydantic 在 FastAPI 里承担哪几件事?ValidationError 的
errors()返回什么结构? - 动手:把 demo 服务的端口改成 9000,用「脚本内嵌」和「命令行」两种姿势分别启动验证
下一章:02 · 路由与请求响应