第 2 章:环境搭建与第一个应用
环境是最容易被糊弄、也最容易在深夜把人卡住的一步。本章用两种方式把 FastAPI 装起来,跑通热重载开发服务器,并在 Swagger UI 里发出第一个真实请求。
学习目标
- 说清 FastAPI 0.141 的 Python 版本要求,选对解释器版本
- 掌握
uv(推荐)与venv + pip两种安装路径 - 理解
fastapi dev、fastapi run、uvicorn三者的定位差异 - 能跑通一个带路径参数、查询参数与 Pydantic 请求体的小应用,并在
/docs里发请求
2.1 Python 版本要求
FastAPI 官方 pyproject.toml 声明 requires-python = ">=3.10",分类器覆盖 3.10 到 3.14:
| 版本 | 状态 | 说明 |
|---|---|---|
| 3.9 及以下 | ❌ 不支持 | 0.141 已放弃,装不上或运行报错 |
| 3.10 / 3.11 | ✅ 可用 | 最低门槛,支持 str | None 新式注解 |
| 3.12 / 3.13 | ⭐ 推荐 | 本教程所有示例基于 3.12 验证,生态最均衡 |
| 3.14 | ✅ 可用 | 新解释器,第三方库预编译 wheel 的兼容性需自行确认 |
💡 本教程统一使用 Python 3.12。3.10 是底线而非推荐值——异步数据库驱动、密码哈希库的 wheel 通常滞后于新版本发布。
python --version # Python 3.12.82.2 方式一(推荐):用 uv 管理环境
uv 是 Astral 出品的包与环境管理器,Rust 实现,同时替代 pip、venv、pip-tools 的部分职责,装包与依赖解析快一到两个数量级。
curl -LsSf https://astral.sh/uv/install.sh | sh # macOS / Linux
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex" # Windows重开一个终端后,创建项目、安装依赖并启动:
uv init fastapi-demo
cd fastapi-demo
uv add "fastapi[standard]"
uv run fastapi dev main.pyuv run 会先确保虚拟环境与依赖是最新的,再执行命令,不需要手动 activate。uv init 生成的骨架中,pyproject.toml 是依赖声明的单一事实来源,uv.lock 锁定全部依赖的精确版本(保证团队与 CI 环境一致),.python-version 用 uv python pin 3.12 修改,.venv/ 在首次执行 uv run 时自动创建。
2.2.1 fastapi[standard] 装了什么
standard 是官方推荐的附加依赖集合,一次装齐开发与常见功能所需的组件:
| 包 | 用途 | 对应章节 |
|---|---|---|
fastapi-cli | 提供 fastapi 命令(dev / run) | 本章 |
uvicorn[standard] | ASGI 服务器,附带 uvloop、httptools、watchfiles | 本章、第 17 章 |
httpx | 测试客户端与异步 HTTP 客户端 | 第 13、16 章 |
jinja2 | 服务端模板渲染 | 第 14 章 |
python-multipart | 解析表单与文件上传 | 第 7、14 章 |
email-validator | 支撑 Pydantic 的 EmailStr 类型 | 第 4 章 |
pydantic-settings | 从环境变量读取配置 | 第 17 章 |
⚠️ 只写
pip install fastapi是不够的:既没有fastapi命令,也没有 Uvicorn,应用根本起不来。
2.3 方式二:venv + pip
不想引入新工具时,标准库的 venv 完全够用。
cd fastapi-demo
python -m venv .venv按操作系统激活虚拟环境,成功后提示符前会出现 (.venv):
# Windows PowerShell
.venv\Scripts\Activate.ps1
# Windows CMD
.venv\Scripts\activate.bat
# macOS / Linux
source .venv/bin/activatepython -m pip install --upgrade pip
pip install "fastapi[standard]"⚠️ 虚拟环境不会自动保持激活。每新开一个终端都要重新激活——这是「明明装了包却
ModuleNotFoundError」的头号原因。
2.4 dev、run 与 uvicorn
| 命令 | 定位 | 热重载 | 默认监听 |
|---|---|---|---|
fastapi dev main.py | 本地开发 | 默认开启 | 127.0.0.1:8000 |
fastapi run main.py | 生产运行 | 默认关闭 | 0.0.0.0:8000 |
uvicorn main:app --reload | 直接驱动 ASGI 应用 | 需显式加 --reload | 127.0.0.1:8000 |
三点容易忽略的差异:默认监听地址不同——dev 绑 127.0.0.1 只有本机能访问,这是刻意的安全默认,run 绑 0.0.0.0(容器里必须如此),想让同事连你的机器要显式 --host 0.0.0.0;热重载有代价——--reload 靠监听文件变化重启工作进程,内存占用翻倍、重启瞬间请求可能被中断,生产一律不开;main:app 的含义是「main.py 模块中的 app 变量」,开了 --reload 时必须传导入字符串而不是应用对象,服务器才能在子进程里重新导入。
fastapi dev main.py --host 0.0.0.0 --port 8001 # 换监听地址与端口
fastapi run main.py --workers 4 # 生产:多进程
uvicorn main:app --reload --port 8001 # 等价写法2.5 项目结构约定
本章从单文件起步,把注意力放在语法上。当路由、模型、数据库逐渐堆积,单文件会迅速失控——第 11 章会重构为下面这个分层结构,后续所有章节的示例都会落到这套目录里:
fastapi-demo/ # 第 11 章的目标结构
├── app/
│ ├── main.py # 创建 FastAPI 实例、挂载路由与中间件
│ ├── core/ # 配置、安全
│ ├── db/ # engine、session
│ ├── models/ # SQLAlchemy 模型
│ ├── schemas/ # Pydantic 模型
│ ├── repositories/ # 数据访问
│ ├── services/ # 业务逻辑
│ └── api/v1/ # 路由(APIRouter)
├── tests/
├── pyproject.toml
└── uv.lock2.6 完整可运行示例
用下面的内容覆盖 main.py:
# main.py
from fastapi import FastAPI
from pydantic import BaseModel, Field
app = FastAPI(title="FastAPI 教程 Demo", version="0.1.0")
class Item(BaseModel):
name: str = Field(min_length=1, max_length=50)
price: float = Field(gt=0, description="单价,必须大于 0")
tags: list[str] = Field(default_factory=list)
@app.get("/items/{item_id}")
async def read_item(item_id: int, q: str | None = None) -> dict[str, object]:
return {"item_id": item_id, "q": q}
@app.post("/items", status_code=201)
async def create_item(item: Item) -> Item:
return item这个文件覆盖了四种参数来源与响应类型:
| 声明 | 来源 | 行为 |
|---|---|---|
路径中的 {item_id} | 路径参数 | 字符串自动转 int,失败返回 422 |
q: str | None = None | 查询参数 | 有默认值 → 可选 |
item: Item | 请求体 | 由 Pydantic 解析 JSON 并逐字段校验 |
-> dict[str, str] / -> Item | 响应模型 | 决定文档里的响应结构与序列化格式 |
启动并验证:
uv run fastapi dev main.pycurl "http://127.0.0.1:8000/items/7?q=keyboard"
# {"item_id":7,"q":"keyboard"}
curl -X POST http://127.0.0.1:8000/items -H "Content-Type: application/json" -d '{"name":"","price":-1}'
# 422,detail 中同时列出 name 与 price 的失败原因最后一条是关键体验:一次请求把所有字段错误都告诉你,而不是遇到第一个错误就返回。
2.7 在 /docs 里动手试一遍
开发环境的完整链路:
- 打开
http://127.0.0.1:8000/docs,展开POST /items,点右上角 Try it out - 在 Request body 里把
price改成-5,点 Execute,观察 Curl(操作被翻译成可复制的命令)、Request URL(实际地址)与 Server response(状态码 422 及错误明细) - 把
price改回正数再执行一次,观察状态码变成 201
💡 调试阶段优先用
/docs而不是 Postman:它永远和代码同步,改完模型刷新页面就能看到最新约束,省掉手工维护请求集合的成本。
2.8 错误示范 vs 正确示范
❌ 不推荐:把依赖装进系统 Python,并在 main.py 里硬编码启动逻辑。
# main.py —— 反面写法:硬编码启动逻辑
import uvicorn
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
async def root():
return {"ok": True}
if __name__ == "__main__":
uvicorn.run(app, host="127.0.0.1", port=8000)✅ 推荐:main.py 只定义应用对象,启动交给 CLI。
# main.py
from fastapi import FastAPI
app = FastAPI() # 应用对象是唯一出口,供 fastapi CLI / uvicorn 导入
@app.get("/")
async def root():
return {"ok": True}uv add "fastapi[standard]"
uv run fastapi dev main.py # 热重载 + 交互文档,改完即生效2.9 常用开发命令速查
| 场景 | 命令 |
|---|---|
| 创建项目 / 添加依赖 | uv init fastapi-demo、uv add "fastapi[standard]" |
| 开发启动(热重载) | uv run fastapi dev main.py |
| 指定端口 | fastapi dev main.py --port 8001 |
| 监听所有网卡 | fastapi dev main.py --host 0.0.0.0 |
| 生产启动(多进程) | fastapi run main.py --workers 4 |
| 直接驱动 uvicorn | uvicorn main:app --reload |
| 查看 FastAPI 版本 | python -c "import fastapi; print(fastapi.__version__)" |
| 查看 uv 版本 / 已装依赖 | uv --version、uv pip list |
| 固定 Python 版本 | uv python pin 3.12 |
常见坑与排查
| 现象 | 原因 | 解决 |
|---|---|---|
fastapi: command not found(Windows 提示「不是内部或外部命令」) | 没装 fastapi[standard],缺 fastapi-cli;或虚拟环境未激活 | 装 fastapi[standard];用 uv run fastapi dev main.py 免激活;venv 方案必须先激活再装 |
ModuleNotFoundError: No module named 'fastapi' | 包装进了另一个解释器(系统 Python 或旧 venv) | where python(macOS/Linux 用 which python)确认解释器在 .venv 内,再重装 |
[Errno 10048] error while attempting to bind on address ('127.0.0.1', 8000) | 8000 端口被占用,常见于上次的 dev 服务器没关干净 | fastapi dev main.py --port 8001;或 netstat -ano | findstr :8000 找到 PID 后结束进程 |
ERROR: Could not import module "main" | 工作目录不对,或启动命令里的模块名与实际文件不符 | 在 main.py 所在目录执行;文件名变了要同步改命令,如 fastapi dev app/main.py |
| 改了代码刷新浏览器没变化 | 未开启热重载,或用的是 uvicorn 但没加 --reload | 改用 fastapi dev(默认开启);uvicorn 需显式加 --reload;必要时手动重启 |
| PowerShell 报「无法加载文件 Activate.ps1,因为在此系统上禁止运行脚本」 | Windows 默认执行策略 Restricted 禁止执行脚本 | 执行 Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser,重开终端再激活 |
本章小结
| 要点 | 说明 |
|---|---|
| 版本基线 | FastAPI 0.141 要求 Python ≥ 3.10,本教程统一用 3.12 |
| 推荐安装 | uv init → uv add "fastapi[standard]" → uv run fastapi dev main.py |
| 传统安装 | python -m venv .venv → 激活 → pip install "fastapi[standard]" |
standard 的价值 | 一次装齐 CLI、Uvicorn、httpx、jinja2、python-multipart、email-validator、pydantic-settings |
| dev 与 run | dev 默认热重载、绑 127.0.0.1;run 默认不重载、绑 0.0.0.0,uvicorn main:app 等价于导入 main 模块里的 app 对象 |
| 项目结构 | 本章单文件起步,第 11 章重构为 app/ 分层结构 |
| 调试入口 | /docs 的 Try it out 可直接发请求,并查看 curl、URL 与响应 |
练习题
- 用
uv从零创建fastapi-demo,装上fastapi[standard],启动开发服务器,访问/docs与/openapi.json,记录info.title与info.version的值。 - 用
venv + pip重做练习 1,然后故意不激活虚拟环境执行fastapi dev main.py,记录报错并解释为什么uv run不会遇到这个问题。 - 把
price: float = Field(gt=0)改成price: float,再向POST /items发送{"name":"键盘","price":-1},对比两次响应,说明Field约束在校验与文档两个层面的作用。 - 把开发服务器端口改成
8001并允许局域网访问,从同一网络下的手机浏览器打开/docs,说明为什么fastapi dev默认只绑127.0.0.1。
下一章预告
环境跑通了,接下来进入 FastAPI 最核心的日常:用装饰器声明路由,用类型注解声明路径参数与查询参数,并用
APIRouter把路由拆到多个文件里。