FastAPI
1. 环境与项目初始化
1.1 依赖安装(Windows + Python 3.12)
# 基础安装(推荐镜像)
pip install fastapi==0.138.0 fastapi-cli==0.0.20 fastapi-core==0.138.0 "uvicorn[standard]" -i https://pypi.tuna.tsinghua.edu.cn/simple
# 表单/文件上传必需
pip install python-multipart==0.0.9
# 数据库(同步方案)
pip install PyMySQL==1.1.0 SQLAlchemy==2.0.28 cryptography==42.0.5
# 异步数据库方案(可选,见后)
pip install aiomysql==0.2.0 sqlalchemy[asyncio] # 或直接用 asyncpg + SQLAlchemy异步
1.2 启动命令
uvicorn main:app --reload --host 127.0.0.1 --port 8000
# 或
fastapi dev main.py
1.3 PyCharm 运行配置(可选)
- 添加 Python 运行配置,Module name:
uvicorn,Parameters:main:app --reload --port 8000
2. FastAPI 应用基础
2.1 最小应用
from fastapi import FastAPI
app = FastAPI(title="My API", version="1.0.0")
@app.get("/")
def root():
return {"message": "Hello World"}
2.2 自动文档访问
- Swagger UI:
http://127.0.0.1:8000/docs - ReDoc:
http://127.0.0.1:8000/redoc
3. 路由与参数
3.1 HTTP 方法装饰器
@app.get("/items") # 读取
@app.post("/items") # 创建
@app.put("/items/{id}") # 完整更新
@app.patch("/items/{id}") # 部分更新
@app.delete("/items/{id}") # 删除
3.2 路径参数(Path Parameters)
@app.get("/books/{book_id}")
def get_book(book_id: int): # 自动转换类型,非数字返回422
return {"id": book_id}
高级校验(使用 Path):
from fastapi import Path
@app.get("/books/{book_id}")
def get_book(
book_id: int = Path(..., gt=0, le=10000, title="图书ID", example=1)
):
return {"id": book_id}
...表示必填;gt/ge/lt/le用于数值范围。
路由顺序:静态路径必须写在动态路径之前(否则 /users/me 会被 /users/{user_id} 匹配)。
3.3 查询参数(Query Parameters)
未在路径中声明的参数自动成为查询参数。
@app.get("/items")
def list_items(page: int = 1, limit: int = 10, q: str | None = None):
return {"page": page, "limit": limit, "q": q}
高级校验(使用 Query):
from fastapi import Query
@app.get("/search")
def search(
keyword: str | None = Query(None, min_length=1, max_length=20),
min_price: float = Query(0, ge=0)
):
...
4. 请求体(Request Body)
4.1 使用 Pydantic 模型
from pydantic import BaseModel
class BookCreate(BaseModel):
title: str
author: str
price: float = Field(..., gt=0)
is_published: bool = True
@app.post("/books")
def create_book(book: BookCreate):
return book.model_dump() # v2 转 dict
4.2 字段校验(Field)
from pydantic import Field
class Book(BaseModel):
title: str = Field(..., min_length=2, max_length=50)
price: float = Field(..., gt=0, le=1000)
tags: list[str] = Field(default=[], max_length=5)
4.3 请求体 + 路径 + 查询参数混用
FastAPI 自动根据类型区分: - 路径中占位符 → 路径参数 - 简单类型(未在路径中)→ 查询参数 - BaseModel 子类 → 请求体
@app.put("/books/{book_id}")
def update(book_id: int, book: BookCreate, notify: bool = False):
...
4.4 单一简单值请求体(使用 Body)
from fastapi import Body
@app.post("/importance")
def set_importance(importance: int = Body(..., embed=True, ge=1, le=5)):
...
embed=True 要求 JSON 必须为 {"importance": 5}。
4.5 表单数据(Form)
from fastapi import Form
@app.post("/login")
def login(username: str = Form(...), password: str = Form(...)):
...
必须安装 python-multipart。
4.6 文件上传(UploadFile)
from fastapi import File, UploadFile
import shutil
@app.post("/upload")
def upload(file: UploadFile = File(...)):
# 校验文件类型
if file.content_type not in ["image/jpeg", "image/png"]:
raise HTTPException(400, "只允许jpg/png")
# 保存
with open(f"uploads/{file.filename}", "wb") as buffer:
shutil.copyfileobj(file.file, buffer)
return {"filename": file.filename}
UploadFile vs bytes:UploadFile 使用缓冲流,节省内存,推荐。
多文件上传:
@app.post("/upload-multiple")
def upload_multiple(files: list[UploadFile] = File(...)):
return [f.filename for f in files]
5. 响应模型与状态码
5.1 响应模型(response_model)
用于过滤敏感字段,只返回指定字段。
class UserPublic(BaseModel):
id: int
username: str
class UserDB(UserPublic):
hashed_password: str
@app.get("/users/{id}", response_model=UserPublic)
def get_user(id: int):
user = UserDB(id=1, username="alice", hashed_password="xxx")
return user # 自动过滤掉 hashed_password
5.2 状态码
from fastapi import status
@app.post("/items", status_code=status.HTTP_201_CREATED)
def create():
return {"id": 1}
5.3 非 JSON 响应
- HTMLResponse
from fastapi.responses import HTMLResponse
@app.get("/page", response_class=HTMLResponse)
def page():
return "<h1>Hello</h1>"
- PlainTextResponse:返回纯文本
- RedirectResponse:重定向
- FileResponse:返回文件下载
- StreamingResponse:流式响应(如模拟打字机)
from fastapi.responses import StreamingResponse
def generate():
yield "chunk1"
yield "chunk2"
@app.get("/stream")
def stream():
return StreamingResponse(generate(), media_type="text/plain")
5.4 自定义响应头
@app.get("/headers")
def headers(response: Response):
response.headers["X-Custom"] = "value"
return {"ok": True}
5.5 读取请求头
from fastapi import Header
@app.get("/read-header")
def read(user_agent: str | None = Header(None), x_token: str | None = Header(None, alias="X-Auth-Token")):
...
6. 异常处理
6.1 抛出 HTTPException
from fastapi import HTTPException, status
@app.get("/items/{id}")
def get(id: int):
if id not in db:
raise HTTPException(status_code=404, detail="Item not found", headers={"X-Error": "missing"})
return db[id]
6.2 自定义异常与全局处理器
class BusinessException(Exception):
def __init__(self, code: int, msg: str):
self.code = code
self.msg = msg
@app.exception_handler(BusinessException)
def handle_biz(req, exc):
return JSONResponse(status_code=400, content={"code": exc.code, "msg": exc.msg})
@app.get("/biz")
def biz(score: int):
if score < 60:
raise BusinessException(1001, "不及格")
return {"ok": True}
7. 异步编程基础
7.1 同步 vs 异步
- 同步(
def):会阻塞调用线程,但 FastAPI 会自动将普通def放入线程池,不阻塞事件循环。 - 异步(
async def):需配合await非阻塞 I/O(如httpx.AsyncClient,asyncpg,aiofiles)。
import asyncio
@app.get("/sync")
def sync_task():
time.sleep(2) # 阻塞,但被线程池隔离
return {"ok": True}
@app.get("/async")
async def async_task():
await asyncio.sleep(2) # 非阻塞,释放事件循环
return {"ok": True}
黄金法则:使用 await 异步库时用 async def,否则用普通 def。
8. 依赖注入(Depends)
8.1 基本依赖
from fastapi import Depends, Header
def verify_token(x_token: str = Header(...)):
if x_token != "secret":
raise HTTPException(401)
return x_token
@app.get("/admin")
def admin(token: str = Depends(verify_token)):
return {"token": token}
8.2 依赖嵌套
def pagination(page: int = 1, size: int = 10):
return {"skip": (page-1)*size, "limit": size}
def search_context(q: str | None = None, pag: dict = Depends(pagination)):
return {"q": q, **pag}
@app.get("/search")
def search(ctx: dict = Depends(search_context)):
return ctx
8.3 使用 yield 管理资源(数据库会话)
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
@app.get("/items")
def read_items(db = Depends(get_db)):
return db.query(Item).all()
9. 数据库集成(MySQL + SQLAlchemy)
9.1 同步方案(教案原版)
目录结构
project/
├── database.py # 引擎、会话、Base
├── models.py # ORM 模型
├── schemas.py # Pydantic 模型
└── main.py # 路由
database.py
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
SQLALCHEMY_DATABASE_URL = "mysql+pymysql://root:123456@127.0.0.1:3306/school_db?charset=utf8mb4"
engine = create_engine(SQLALCHEMY_DATABASE_URL, echo=True, pool_pre_ping=True)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
models.py
from sqlalchemy import Column, Integer, String, Float, Boolean, DateTime
from sqlalchemy.sql import func
from database import Base
class BookModel(Base):
__tablename__ = "books"
id = Column(Integer, primary_key=True, index=True, autoincrement=True)
title = Column(String(100), nullable=False, index=True)
author = Column(String(50), nullable=False)
price = Column(Float, nullable=False)
is_borrowed = Column(Boolean, default=False)
created_at = Column(DateTime(timezone=True), server_default=func.now())
schemas.py
from pydantic import BaseModel, Field
from datetime import datetime
class BookCreate(BaseModel):
title: str = Field(..., min_length=1, max_length=100)
author: str = Field(..., min_length=1, max_length=50)
price: float = Field(..., gt=0)
class BookUpdate(BaseModel):
title: str | None = None
author: str | None = None
price: float | None = Field(None, gt=0)
is_borrowed: bool | None = None
class BookResponse(BookCreate):
id: int
is_borrowed: bool
created_at: datetime
class Config:
from_attributes = True # 支持 ORM 对象转 dict
main.py(CRUD 示例)
from fastapi import FastAPI, Depends, HTTPException, status
from sqlalchemy.orm import Session
from database import get_db, engine, Base
import models, schemas
Base.metadata.create_all(bind=engine) # 建表
app = FastAPI()
@app.post("/books", response_model=schemas.BookResponse, status_code=201)
def create(book: schemas.BookCreate, db: Session = Depends(get_db)):
db_book = models.BookModel(**book.model_dump())
db.add(db_book)
db.commit()
db.refresh(db_book)
return db_book
@app.get("/books", response_model=list[schemas.BookResponse])
def list_books(skip: int = 0, limit: int = 20, db: Session = Depends(get_db)):
return db.query(models.BookModel).offset(skip).limit(limit).all()
@app.get("/books/{id}", response_model=schemas.BookResponse)
def get(id: int, db: Session = Depends(get_db)):
book = db.query(models.BookModel).filter(models.BookModel.id == id).first()
if not book:
raise HTTPException(404, "Not found")
return book
@app.put("/books/{id}", response_model=schemas.BookResponse)
def update(id: int, book_in: schemas.BookUpdate, db: Session = Depends(get_db)):
book = db.query(models.BookModel).filter(models.BookModel.id == id).first()
if not book:
raise HTTPException(404, "Not found")
for key, value in book_in.model_dump(exclude_unset=True).items():
setattr(book, key, value)
db.commit()
db.refresh(book)
return book
@app.delete("/books/{id}", status_code=204)
def delete(id: int, db: Session = Depends(get_db)):
book = db.query(models.BookModel).filter(models.BookModel.id == id).first()
if not book:
raise HTTPException(404, "Not found")
db.delete(book)
db.commit()
return None
9.2 异步数据库方案(高并发通用)
使用 aiomysql + SQLAlchemy 异步支持,配合 async def 实现非阻塞数据库操作。
安装
pip install aiomysql sqlalchemy[asyncio]
database.py(异步引擎)
from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker, AsyncSession
from sqlalchemy.orm import declarative_base
# 注意 URL 前缀:mysql+aiomysql://...
ASYNC_DATABASE_URL = "mysql+aiomysql://root:123456@127.0.0.1:3306/school_db?charset=utf8mb4"
async_engine = create_async_engine(ASYNC_DATABASE_URL, echo=True, pool_pre_ping=True)
AsyncSessionLocal = async_sessionmaker(async_engine, expire_on_commit=False)
Base = declarative_base()
async def get_db() -> AsyncSession:
async with AsyncSessionLocal() as session:
yield session
异步 CRUD 示例(main.py 中)
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
@app.post("/books", response_model=schemas.BookResponse, status_code=201)
async def create(book: schemas.BookCreate, db: AsyncSession = Depends(get_db)):
db_book = models.BookModel(**book.model_dump())
db.add(db_book)
await db.commit()
await db.refresh(db_book)
return db_book
@app.get("/books/{id}", response_model=schemas.BookResponse)
async def get(id: int, db: AsyncSession = Depends(get_db)):
result = await db.execute(select(models.BookModel).where(models.BookModel.id == id))
book = result.scalar_one_or_none()
if not book:
raise HTTPException(404, "Not found")
return book
注意:异步 CRUD 中所有数据库操作(
commit,refresh,execute)都要加await。
优点:不阻塞事件循环,高并发下性能更好。
10. 中间件
10.1 自定义中间件(计时)
import time
from fastapi import Request
@app.middleware("http")
async def add_process_time(request: Request, call_next):
start = time.time()
response = await call_next(request)
response.headers["X-Process-Time"] = f"{time.time()-start:.4f}"
return response
10.2 CORS 跨域中间件
from fastapi.middleware.cors import CORSMiddleware
app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:3000", "*"], # 生产环境限定具体域名
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
11. 常用功能速查
| 功能 | 代码片段 |
|---|---|
| 读取环境变量 | import os; DB_URL = os.getenv("DATABASE_URL") |
| 配置文件 | 使用 pydantic_settings 或 python-dotenv |
| 日志 | import logging; logging.basicConfig(level=logging.INFO) |
| 分页通用依赖 | 见第8.2节 |
| 文件下载 | FileResponse(path, filename="xxx") |
| 重定向 | RedirectResponse(url="/docs") |
| 返回图片流 | StreamingResponse(open("img.png","rb"), media_type="image/png") |
12. 常见问题与坑点
| 问题 | 解决方案 |
|---|---|
| 表单/文件上传 500 错误 | 安装 python-multipart |
数据库连接报错 cryptography |
安装 cryptography |
| 路由顺序错误 | 静态路径写在动态路径之前 |
| 响应模型过滤失效 | 检查 response_model 是否指定,且字段名一致 |
| Pydantic v2 兼容 ORM | 设置 Config.from_attributes = True(原 orm_mode) |
| 异步函数中使用同步库阻塞 | 使用 def 或 loop.run_in_executor 避免阻塞 |
| 8000 端口占用 | netstat -ano \| findstr 8000 杀死进程或换端口 |
13. 异步增强
- 同步方案:代码简单,适合快速开发,依赖线程池,并发尚可。
- 异步方案:使用
async def+aiomysql+ SQLAlchemy 异步,高并发下性能更优,更符合 FastAPI 异步基因。
迁移步骤:
1. 替换数据库驱动为 aiomysql
2. 使用 create_async_engine 和 AsyncSession
3. 所有 ORM 操作加 await
4. 路由函数改为 async def