Skip to content

第 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 通常滞后于新版本发布。

bash
python --version        # Python 3.12.8

2.2 方式一(推荐):用 uv 管理环境 ​

uv 是 Astral 出品的包与环境管理器,Rust 实现,同时替代 pip、venv、pip-tools 的部分职责,装包与依赖解析快一到两个数量级。

bash
curl -LsSf https://astral.sh/uv/install.sh | sh            # macOS / Linux
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"   # Windows

重开一个终端后,创建项目、安装依赖并启动:

bash
uv init fastapi-demo
cd fastapi-demo
uv add "fastapi[standard]"
uv run fastapi dev main.py

uv 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 完全够用。

bash
cd fastapi-demo
python -m venv .venv

按操作系统激活虚拟环境,成功后提示符前会出现 (.venv):

bash
# Windows PowerShell
.venv\Scripts\Activate.ps1

# Windows CMD
.venv\Scripts\activate.bat

# macOS / Linux
source .venv/bin/activate
bash
python -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 应用需显式加 --reload127.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 时必须传导入字符串而不是应用对象,服务器才能在子进程里重新导入。

bash
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 章会重构为下面这个分层结构,后续所有章节的示例都会落到这套目录里:

text
fastapi-demo/                 # 第 11 章的目标结构
├── app/
│   ├── main.py               # 创建 FastAPI 实例、挂载路由与中间件
│   ├── core/                 # 配置、安全
│   ├── db/                   # engine、session
│   ├── models/               # SQLAlchemy 模型
│   ├── schemas/              # Pydantic 模型
│   ├── repositories/         # 数据访问
│   ├── services/             # 业务逻辑
│   └── api/v1/               # 路由(APIRouter)
├── tests/
├── pyproject.toml
└── uv.lock

2.6 完整可运行示例 ​

用下面的内容覆盖 main.py:

python
# 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响应模型决定文档里的响应结构与序列化格式

启动并验证:

bash
uv run fastapi dev main.py
bash
curl "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 里动手试一遍 ​

开发环境的完整链路:

  1. 打开 http://127.0.0.1:8000/docs,展开 POST /items,点右上角 Try it out
  2. 在 Request body 里把 price 改成 -5,点 Execute,观察 Curl(操作被翻译成可复制的命令)、Request URL(实际地址)与 Server response(状态码 422 及错误明细)
  3. 把 price 改回正数再执行一次,观察状态码变成 201

💡 调试阶段优先用 /docs 而不是 Postman:它永远和代码同步,改完模型刷新页面就能看到最新约束,省掉手工维护请求集合的成本。


2.8 错误示范 vs 正确示范 ​

❌ 不推荐:把依赖装进系统 Python,并在 main.py 里硬编码启动逻辑。

python
# 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。

python
# main.py
from fastapi import FastAPI

app = FastAPI()          # 应用对象是唯一出口,供 fastapi CLI / uvicorn 导入


@app.get("/")
async def root():
    return {"ok": True}
bash
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
直接驱动 uvicornuvicorn 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 与 rundev 默认热重载、绑 127.0.0.1;run 默认不重载、绑 0.0.0.0,uvicorn main:app 等价于导入 main 模块里的 app 对象
项目结构本章单文件起步,第 11 章重构为 app/ 分层结构
调试入口/docs 的 Try it out 可直接发请求,并查看 curl、URL 与响应

练习题 ​

  1. 用 uv 从零创建 fastapi-demo,装上 fastapi[standard],启动开发服务器,访问 /docs 与 /openapi.json,记录 info.title 与 info.version 的值。
  2. 用 venv + pip 重做练习 1,然后故意不激活虚拟环境执行 fastapi dev main.py,记录报错并解释为什么 uv run 不会遇到这个问题。
  3. 把 price: float = Field(gt=0) 改成 price: float,再向 POST /items 发送 {"name":"键盘","price":-1},对比两次响应,说明 Field 约束在校验与文档两个层面的作用。
  4. 把开发服务器端口改成 8001 并允许局域网访问,从同一网络下的手机浏览器打开 /docs,说明为什么 fastapi dev 默认只绑 127.0.0.1。

下一章预告 ​

环境跑通了,接下来进入 FastAPI 最核心的日常:用装饰器声明路由,用类型注解声明路径参数与查询参数,并用 APIRouter 把路由拆到多个文件里。

👉 第 3 章:路由与路径/查询参数

📖本文阅读--次|📊全站访问--次|👥访客--人