fastapi
FastAPI 框架快速学习
概述
FastAPI 是一个用于构建 API 的现代、高性能 Python Web 框架,基于 Starlette(ASGI Web 服务器)和 Pydantic(数据验证与序列化)。
核心特性:
- ✅ 极高性能:得益于 Starlette 和 Pydantic,性能与 NodeJS 和 Go 相当。
- ✅ 高效开发:自动交互式文档 + 类型提示支持,提升开发速度 200–300%。
- ✅ 更少错误:类型系统减少约 40% 的人为错误。
- ✅ 生产就绪:自动生成 OpenAPI 文档,支持认证、中间件、依赖注入等完整生态。
- ✅ 异步原生:全面支持
async/await,轻松处理高并发场景。
一、快速开始
安装与基础使用
pip install fastapi uvicorn
from fastapi import FastAPI
app = FastAPI(docs_url=None, # 禁用 Swagger UI(/docs)
redoc_url=None, # 禁用 ReDoc(/redoc)
)
@app.get("/")
async def root():
return {"message": "Hello World"}
# 直接运行
if __name__ == "__main__":
import uvicorn
uvicorn.run("main:app", host="127.0.0.1", port=8000, reload=True)
运行后访问:
http://127.0.0.1:8000—— API 端点http://127.0.0.1:8000/docs—— 自动交互式 Swagger UI 文档http://127.0.0.1:8000/redoc—— ReDoc 风格文档
💡 提示:
uvicorn.run()中的reload=True仅用于开发环境,生产环境应关闭。
二、路径操作
支持多种 HTTP 方法
from fastapi import FastAPI
app = FastAPI()
@app.get("/items/{item_id}")
async def read_item(item_id: int):
return {"item_id": item_id}
@app.post("/items/")
async def create_item(item: dict):
return {"item": item}
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: dict):
return {"item_id": item_id, "updated_item": item}
@app.delete("/items/{item_id}")
async def delete_item(item_id: int):
return {"message": f"Item {item_id} deleted"}
路由分组与组织
# users.py —— 生产环境中写到单独 app 下的 urls 或 routers 中
from fastapi import APIRouter
user_router = APIRouter(prefix="/users", tags=["Users"])
@user_router.get("/")
async def get_users():
return [{"id": 1, "name": "John"}]
@user_router.get("/{user_id}")
async def get_user(user_id: int):
return {"id": user_id, "name": "John"}
# main.py
from fastapi import FastAPI
app = FastAPI()
app.include_router(user_router)
路由视图的2种写法
推荐用这种和django的写法类似

写法2,路由装饰器在视图函数上
这种将路由和视图写在一起

三、请求处理
路径参数与查询参数
from typing import Optional
@app.get("/users/{user_id}")
async def read_user(
user_id: int, # 路径参数
skip: int = 0, # 查询参数,有默认值
limit: Optional[int] = 10, # 可选查询参数
search: Optional[str] = None # 可选搜索参数
):
return {"user_id": user_id, "skip": skip, "limit": limit, "search": search}
请求体与 Pydantic 模型
from pydantic import BaseModel, EmailStr
from typing import List, Optional
class Item(BaseModel):
name: str
description: Optional[str] = None
price: float
tags: List[str] = []
class User(BaseModel):
email: EmailStr
password: str
@app.post("/items/")
async def create_item(item: Item):
return item
@app.post("/users/")
async def create_user(user: User):
# 密码应该哈希处理,这里仅为示例
return {"email": user.email, "message": "User created"}
Pydantic 字段校验
| 装饰器 | 作用 | 是否参与输入 | 是否参与输出 | 典型用途 |
|---|---|---|---|---|
| @field_validator | 字段验证/预处理 | ✅ 是 | ❌ 否 | 类型转换、范围检查 |
| @model_validator(mode='before') | 模型预处理 | ✅ 是 | ❌ 否 | 数据清洗、补全 |
| @model_validator(mode='after') | 模型后验证 | ✅ 是 | ❌ 否 | 跨字段校验 |
| @computed_field | 计算属性(只读) | ❌ 否 | ✅ 是 | 输出衍生字段(如 area, full_name) |
| @field_serializer | 自定义字段输出 | ❌ 否 | ✅ 是 | 格式化时间、隐藏字段 |
| @model_serializer | 自定义整个模型输出 | ❌ 否 | ✅ 是 | 包装成 {status, data} 结构 |
from pydantic import BaseModel, field_validator, model_validator
from pydantic import computed_field, field_serializer, model_serializer
class UserCreate(BaseModel):
password: str
confirm_password: str
@field_validator("confirm_password",mode='before')
@classmethod
def passwords_match(cls, v, info):
if "password" in info.data and v != info.data["password"]:
raise ValueError("两次密码不一致")
return v
@model_validator(mode='after') # after是模型后验证
def passwords_match(self):
if self.password != self.confirm_password:
raise ValueError("两次密码不一致")
return self
class Log(BaseModel):
timestamp: datetime
# 控制某个字段如何被 .model_dump() 或 model_dump_json() 输出。
@field_serializer('timestamp')
def serialize_ts(self, value: datetime):
return value.strftime('%Y-%m-%d %H:%M:%S')
class Rectangle(BaseModel):
width: float
height: float
#通常是基于其他字段计算而来。
@computed_field
@property
def area(self) -> float:
return self.width * self.height
@computed_field
@property
def is_square(self) -> bool:
return self.width == self.height
from pydantic import model_serializer
class UserOut(BaseModel):
username: str
roles: list[str]
@model_serializer
def serialize(self):
return {
"status": 200,
"detail": self.model_dump()
}
# 调用 user_out.model_dump() 会直接返回:
{
"status": 200,
"detail": {
"username": "alex",
"roles": ["admin"]
}
}
字段定义模式(模式总结)
以下是 Python + Pydantic + 框架开发中常见的 字段定义模式,每种都有固定语法结构。
✅ 模式 1:【普通字段】基本 Pydantic 字段定义
class User(BaseModel):
name: str
age: int = 30
- ✅ 语法结构:
field_name: type = default_value # 可选 - 🔧 用途:定义数据模型的字段
- 📌 特点:支持类型推断、校验、序列化
✅ 模式 2:【带描述的字段】使用 Field(...)
class CarAccountInput(BaseModel):
a: str = Field(..., description="账号名称", min_length=2)
b: str = Field(default="", description="车信息")
- ✅ 语法结构:
field_name: type = Field(default, description="...", **metadata) - 🔧 用途:添加元数据(用于生成 OpenAPI/Swagger/Tool Schema)
- 📌 特点:
...表示必填;default表示可选
✅ 模式 3:【类变量】框架级配置属性(如 args_schema, name)
class MyTool(BaseTool):
name: str = "my_tool"
description: str = "这是一个工具"
args_schema: Type[BaseModel] = CarAccountInput
- ✅ 语法结构:
attr_name: type_annotation = value - 🔧 用途:不是实例字段,而是类级别的配置项
- 📌 特点:简单的理解args_schema这个变量应该是一个类,而不是字段,简化写法args_schema = CarAccountInput,这个简化写法会忽略校验,比如不会校验是否是BaseModel的子类,不严谨
✅ 模式 4:【带 reducer 的状态字段】LangGraph 特有写法
from langgraph.graph.message import add_messages
class State:
messages: Annotated[Sequence[BaseMessage], add_messages]
- ✅ 语法结构:
field_name: Annotated[type, reducer_func] - 🔧 用途:告诉 LangGraph 如何合并状态更新
- 📌 特点:
Annotated是 Python 标准库typing.Annotated- 第一个参数是类型,第二个是元数据(这里是 reducer 函数)
✅ 模式 5:【泛型字段】复杂结构定义
from typing import List, Optional
class BatchInput(BaseModel):
users: List[User]
note: Optional[str] = None
- ✅ 语法结构:
field: List[SomeModel] field: Optional[str] field: Dict[str, int] - 🔧 用途:定义嵌套结构
- 📌 特点:Pydantic 自动递归校验
✅ 模式 6:【动态类变量】用于运行时注入(高级)
class ApiTool(BaseTool):
base_url: str = "https://api.example.com"
def __init__(self, base_url: str, **kwargs):
super().__init__(**kwargs)
self.base_url = base_url # 动态设置
- ✅ 语法结构:
class_attr: type = default # 或通过 __init__ 动态赋值 - 🔧 用途:创建可配置的工具实例
- 📌 特点:适合多租户、不同环境调用
总结:常见语法模式一览表
| 模式 | 典型写法 | 用途 | 是否 Pydantic 原生? |
|---|---|---|---|
| 1. 普通字段 | name: str |
数据建模 | ✅ 是 |
| 2. 带描述字段 | name: str = Field(..., description="...") |
API 文档生成 | ✅ 是 |
| 3. 类变量配置 | name: str = "tool"args_schema: Type[BaseModel] = Model |
工具元信息 | ❌ 否(LangChain 约定) |
| 4. Annotated reducer | messages: Annotated[List[Msg], add_messages] |
状态合并 | ❌ 否(LangGraph 约定) |
| 5. 泛型结构 | users: List[User] |
嵌套数据 | ✅ 是 |
| 6. 动态属性 | self.base_url = url in __init__ |
可配置工具 | ✅ 半原生 |
Pydantic 字段校验自定义输出orm输出格式
我们再输出的字段中需要自定义输出的内容,受限在basemodel子类中定义 from_attributes = True
class UserOut(BaseModel):
status_code: int = 400
id: int
username: str
email: str
created_at: datetime
@field_serializer("created_at")
def serialize_datetime(self, value: datetime | None) -> str | None:
if value is None:
return None
return value.strftime("%Y-%m-%d %H:%M:%S")
class Config:
from_attributes = True # Tortoise 兼容 ORM Mode v1版本使用orm_mode=True
class status(BaseModel):
status_code: int = 400
reason: str = ""
response: Union[UserOut, str] = ""
额外知识
Pydantic 在创建模型时会自动读取class Config,虽然 class Config 仍然支持,但 Pydantic v2 更推荐使用 model_config 字典
某些配置也比较实用
model_config = ConfigDict(
from_attributes=True, # 替代 orm_mode
str_strip_whitespace=True,
extra='forbid'
)
比如 str_strip_whitespace 自动去除字符串字段前后的空白字符(空格、制表符、换行等)
extra 是控制模型如何处理 “额外字段”(即定义中没有的字段)一共有allow,ignore,forbid三种模式 默认是ignore 忽略处理,不抛异常也不获取该字段
然后获取到了orm对象
# 这里ret是orm的查询对象
ret = await User.filter(id=query_id).get()
final_ret = status(status_code=200, response=UserOut.model_validate(ret))
输出结果如下
{
"status_code": 200,
"reason": "",
"response": {
"status_code": 400,
"id": 1,
"username": "Young",
"email": "Young@sina.com",
"created_at": "2025-09-13 22:27:13"
}
}
表单数据处理
需要提前安装:pip install python-multipart
from fastapi import Form
@app.post("/login/")
async def login(username: str = Form(...), password: str = Form(...)):
return {"username": username}
文件上传
from fastapi import UploadFile, File
from typing import List
@app.post("/upload/")
async def upload_file(file: UploadFile = File(...)):
return {"filename": file.filename}
@app.post("/upload-multiple/")
async def upload_multiple_files(files: List[UploadFile] = File(...)):
return {"filenames": [file.filename for file in files]}
⚠️ 重要说明:
File(...)会将文件直接读入内存,不适合大文件上传。- 推荐使用
UploadFile对象进行流式写入,避免内存溢出。
@app.post("/upload-stream/")
async def upload_stream(file: UploadFile = File(...)):
with open(f"uploads/{file.filename}", "wb") as f:
while chunk := await file.read(8192): # 每次读取 8KB
f.write(chunk)
return {"filename": file.filename, "status": "uploaded"}
四、响应处理
响应模型与过滤
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float = 10.5
tags: list[str] = []
items = {
"foo": {"name": "Foo", "price": 50.2},
"bar": {"name": "Bar", "description": "The bartenders", "price": 62, "tax": 20.2},
"baz": {"name": "Baz", "description": None, "price": 50.2, "tax": 10.5, "tags": []},
}
@app.get("/items/{item_id}", response_model=Item, response_model_exclude_unset=True)
async def read_item(item_id: str):
return items[item_id]
✅
response_model_exclude_unset=True:仅返回实际设置的字段,忽略默认值。
五、数据库集成(Tortoise ORM + Aerich)
🔧 本节重点:使用 Tortoise ORM 实现异步数据库操作,并通过 Aerich 管理数据库迁移。
5.1 安装依赖
# 核心 ORM
pip install tortoise-orm
# 异步数据库驱动(任选其一)
pip install aiosqlite # SQLite(推荐开发)
pip install asyncpg # PostgreSQL
pip install asyncmy # MySQL(性能优于 aiomysql)
# 数据库迁移工具
pip install aerich
5.2 Tortoise ORM 配置
from tortoise.contrib.fastapi import register_tortoise
TORTOISE_ORM = {
"connections": {
"default": "sqlite://db.sqlite3"
# 或者 PostgreSQL: "postgres://user:pass@localhost:5432/mydb"
# 或者 Mysql :"mysql://myuser:mypass@db.host:3306/somedb"
},
"apps": {
"models": {
"models": ["models", "aerich.models"], # 如果要迁移必须包含aerich.models
"default_connection": "default",
}
},
}
✅ 关键点说明:
aerich.models:Aerich 的迁移历史表模型,必须包含在配置中,否则迁移会失败。models: 列出所有模型模块路径,如["myapp.models", "aerich.models"]。
注册到 FastAPI:
register_tortoise(
app,
config=TORTOISE_ORM,
generate_schemas=False, # 关闭自动建表(由 Aerich 控制)
add_exception_handlers=True,
)
5.3 初始化 ORM 与迁移系统(Aerich)
初始化 Aerich(首次使用)
# 初始化 Aerich,创建 migrations 目录和配置
aerich init -t config.TORTOISE_ORM
📌 命令解释:
aerich init:初始化迁移系统。-t config.TORTOISE_ORM:指定 Tortoise 配置变量路径(模块名.变量名)。
初始化数据库连接(代码中)
from tortoise import Tortoise
async def init_db():
await Tortoise.init(config=TORTOISE_ORM)
await Tortoise.generate_schemas() # 创建表(首次)
5.4 迁移命令流程
- 生成迁移脚本(检测模型变更)
aerich migrate --name create_user_table
✅ 会在
migrations/versions/下生成类似0_20250912100000_create_user_table.py的文件。
- 应用迁移(同步到数据库)
aerich upgrade
- 查看迁移状态
aerich history
aerich heads
- 回滚迁移
aerich downgrade
5.5 模型定义与级联规则
from tortoise.models import Model
from tortoise import fields
from tortoise.fields.relational import ReverseRelation
class User(Model):
id = fields.IntField(pk=True)
username = fields.CharField(max_length=50, unique=True)
email = fields.CharField(max_length=100, null=True)
created_at = fields.DatetimeField(auto_now_add=True)
# 这不是实际的字段,而是 类型提示(type hint),用于告诉 IDE 或类型检查器(如 mypy):User 实例可以通过 .blogs 访问其关联的 Blog 对象集合。
# 是一个“虚拟”的属性,由 Blog.author 的 related_name 动态生成。可以省略,但是不建议省略,便于编译时/开发时的类型提示,帮助代码提示和类型检查。
blogs: ReverseRelation["Blog"]
comments: ReverseRelation["Comment"]
class Blog(Model):
id = fields.IntField(pk=True)
title = fields.CharField(max_length=200)
content = fields.TextField(null=True)
created_at = fields.DatetimeField(auto_now_add=True)
# 外键 + 级联规则 Blog.author 这是 正向关系(forward relation),表示 Blog 模型有一个外键字段 author,指向 User 模型。
# related_name="blogs":为反向关系指定名称,即从 User 查找其所有博客时,使用 .blogs 属性。
author: fields.ForeignKeyRelation[User] = fields.ForeignKeyField(
"models.User",
related_name="blogs",
on_delete=fields.CASCADE # 删除用户时,级联删除其博客
)
comments: ReverseRelation["Comment"]
tags: fields.ManyToManyRelation["Tag"]
class Comment(Model):
id = fields.IntField(pk=True)
content = fields.TextField()
created_at = fields.DatetimeField(auto_now_add=True)
user: fields.ForeignKeyRelation[User] = fields.ForeignKeyField(
"models.User", related_name="comments", on_delete=fields.SET_NULL, null=True
)
blog: fields.ForeignKeyRelation[Blog] = fields.ForeignKeyField(
"models.Blog", related_name="comments", on_delete=fields.CASCADE
)
✅ 级联规则说明:
CASCADE:主表删除,从表记录也删除。SET_NULL:主表删除,从表外键设为NULL(需null=True)。RESTRICT:阻止删除。SET_DEFAULT:设为默认值。
5.6 ORM 查询:select_related 与 prefetch_related
select_related 通常用于 外键 / 一对一 / 一对多(从多查一)
prefetch_related 可用于 所有关系,尤其是多对多
场景:避免 N+1 查询问题
# ❌ 错误:N+1 查询
users = await User.all()
for user in users:
print(user.username)
for blog in user.blogs: # 每次循环都查一次数据库
print(blog.title)
# ✅ 正确:使用 prefetch_related
users = await User.all().prefetch_related("blogs")
for user in users:
print(user.username)
for blog in user.blogs:
print(blog.title)
多级关联预加载
# 预加载用户 → 博客 → 评论
users = await User.all().prefetch_related("blogs__comments")
for user in users:
for blog in user.blogs:
for comment in blog.comments:
print(f"{user.username} 的博客 {blog.title} 有评论:{comment.content}")
多对多预加载
# 查询带标签的博客
blogs = await Blog.all().prefetch_related("tags")
for blog in blogs:
print(f"Blog: {blog.title}")
for tag in blog.tags:
print(f" Tag: {tag.name}")
select_related vs prefetch_related
| 方法 | 用途 | 适用场景 |
|---|---|---|
select_related |
JOIN 查询,返回单个对象 | 外键一对一/多对一 |
prefetch_related |
分两次查询,合并结果 | 多对多、反向外键 |
# select_related 示例
blog = await Blog.get(id=1).select_related("author")
print(blog.author.username)
5.7 完整 CRUD 示例
见原始文档中“增删改查完整示例”部分,已涵盖 create, filter, update, delete, annotate, aggregate 等操作。
六、中间件(Middleware)
6.1 中间件执行顺序
中间件按注册顺序正向执行请求,反向执行响应:
Request → Middleware 1 → Middleware 2 → View → Middleware 2 → Middleware 1 → Response
6.2 自定义中间件示例
from fastapi import Request, FastAPI
from fastapi.responses import JSONResponse
import time
import logging
@app.middleware("http")
async def log_requests(request: Request, call_next):
start_time = time.time()
# 请求前处理
logging.info(f"Request: {request.method} {request.url}")
try:
# 调用下一个中间件或视图
response = await call_next(request)
# 响应后处理
process_time = time.time() - start_time
response.headers["X-Process-Time"] = str(process_time)
logging.info(f"Response status: {response.status_code}")
return response
except Exception as e:
# 捕获视图函数异常,统一返回
logging.error(f"Error: {e}")
return JSONResponse(
status_code=500,
content={"status": "error", "reason": str(e)}
)
6.3 中间件能力演示
| 场景 | 实现方式 |
|---|---|
| 拦截请求 | 在 call_next 前 return response |
| 修改响应头 | 修改 response.headers |
| 捕获异常 | 用 try-except 包裹 call_next |
| 添加响应内容 | 修改 response.body 或返回新响应 |
# 示例:拦截未授权请求
@app.middleware("http")
async def auth_check(request: Request, call_next):
if request.url.path.startswith("/admin"):
token = request.headers.get("Authorization")
if not token:
return JSONResponse(
status_code=401,
content={"status": "error", "reason": "Missing token"}
)
return await call_next(request)
6.4 CORS 中间件
from fastapi.middleware.cors import CORSMiddleware
app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:3000"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
七、高级特性
(原内容已完善,略作调整)
7.1 依赖注入
在编程中,“依赖”指的是一个函数或类需要另一个组件(如数据库连接、身份验证逻辑)才能正常工作。依赖注入(Dependency Injection, DI) 是一种设计模式,它允许我们将这些“依赖”从外部传递给函数,而不是在函数内部创建或硬编码它们。
FastAPI 的依赖注入系统非常强大,可以:
- 复用代码逻辑(如认证、分页)
- 自动解析依赖关系
- 提高代码可测试性和可维护性
- 实现权限控制、数据库连接管理等
### 代码示例与详细注释
```python
from fastapi import Depends, FastAPI, HTTPException, status
from typing import Optional
app = FastAPI()
# ========================
# 示例 1:复用公共查询参数
# ========================
def common_parameters(
q: Optional[str] = None, # 搜索关键词
skip: int = 0, # 分页:跳过前 skip 条
limit: int = 10 # 分页:最多返回 limit 条
):
"""
这是一个通用依赖函数,用于提取所有 API 中常见的查询参数。
优势:
- 避免在每个路由中重复声明相同的参数
- 统一处理分页、搜索逻辑
- 易于修改和维护
返回:
一个字典,包含所有提取出的参数
"""
return {"q": q, "skip": skip, "limit": limit}
@app.get("/items/")
async def read_items(commons: dict = Depends(common_parameters)):
"""
获取物品列表。
参数:
commons: 由 common_parameters() 返回的字典
工作流程:
1. 用户请求:GET /items/?q=book&skip=2&limit=5
2. FastAPI 自动调用 common_parameters(q="book", skip=2, limit=5)
3. 将返回值赋给 commons
4. 执行本函数
返回示例:
{
"message": "获取物品列表",
"data": [],
"params": {"q": "book", "skip": 2, "limit": 5}
}
"""
return {
"message": "获取物品列表",
"data": [], # 假设这是从数据库查出的数据
"params": commons
}
@app.get("/users/")
async def read_users(commons: dict = Depends(common_parameters)):
"""
获取用户列表。
复用相同的依赖函数,避免代码重复。
即使是不同业务的接口,只要参数相同,就可以共享依赖。
"""
return {
"message": "获取用户列表",
"data": [],
"params": commons
}
# ========================
# 示例 2:身份验证依赖
# ========================
def verify_token(token: str):
"""
模拟一个身份验证函数。
参数:
token (str): 客户端传入的令牌
逻辑:
- 如果 token 不是 "secret-token",则抛出 401 错误
- 否则返回 token(表示验证通过)
应用场景:
- 所有需要登录才能访问的接口
- 可替换为 JWT、OAuth2 等真实认证逻辑
"""
if token != "secret-token":
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="无效的令牌,请检查后重试",
headers={"WWW-Authenticate": "Bearer"},
)
return token # 可选:返回 token 供后续使用
@app.get("/secure-data/")
async def get_secure_data(user_token: str = Depends(verify_token)):
"""
只有通过身份验证的用户才能访问此接口。
当用户访问 /secure-data/?token=xxx 时:
1. FastAPI 提取 token 参数
2. 调用 verify_token(token=xxx)
3. 如果验证失败,直接返回 401
4. 如果成功,继续执行函数逻辑
优势:
- 所有需要认证的接口只需添加 `Depends(verify_token)`
- 认证逻辑集中管理,便于修改
"""
return {
"message": "安全数据已获取",
"user_token": user_token,
"detail": "你已通过身份验证"
}
7.2 后台任务
什么是后台任务?
在 Web 开发中,有些操作耗时较长(如发送邮件、生成报告、记录日志)。如果在请求处理过程中同步执行,会导致用户等待时间变长。
后台任务(Background Tasks) 允许你在响应发送给客户端之后,继续在后台执行某些操作。FastAPI 使用 BackgroundTasks 类来实现这一功能。
from fastapi import BackgroundTasks, FastAPI
from typing import Optional
app = FastAPI()
# ========================
# 定义后台任务函数
# ========================
def log_email_sent(email: str, message: str):
"""
模拟发送邮件后的日志记录。
参数:
email (str): 收件人邮箱
message (str): 邮件内容
说明:
这个函数不会阻塞主请求流程。
它会在主响应发送后由 FastAPI 自动调用。
应用场景:
- 发送确认邮件
- 记录用户行为日志
- 清理临时文件
- 同步数据到第三方服务
"""
print(f"【后台任务】已向 {email} 发送邮件,内容:{message}")
def generate_report(user_id: int):
"""
模拟为用户生成报告。
参数:
user_id (int): 用户唯一标识
说明:
生成报告可能需要几秒甚至几分钟。
使用后台任务可避免用户长时间等待。
"""
print(f"【后台任务】正在为用户 {user_id} 生成报告...")
# ========================
# 在路由中使用后台任务
# ========================
@app.post("/send-email/")
async def send_email(
email: str,
message: str,
background_tasks: BackgroundTasks
):
"""
发送邮件接口。
参数:
email (str): 收件人邮箱
message (str): 邮件内容
background_tasks (BackgroundTasks): FastAPI 提供的后台任务管理器
工作流程:
1. 接收请求,立即返回成功响应
2. 将 log_email_sent 函数添加到后台任务队列
3. 响应发送给客户端
4. 后台异步执行 log_email_sent 函数
用户体验:
用户无需等待日志写入完成即可看到“邮件发送成功”。
"""
# 模拟邮件发送(实际中可能是调用 SMTP 或第三方 API)
print(f"正在发送邮件到 {email}...")
# 将任务添加到后台任务队列
background_tasks.add_task(log_email_sent, email, message)
# 立即返回响应,不等待后台任务完成
return {"message": "邮件发送请求已接收", "status": "success"}
@app.get("/profile/{user_id}")
async def get_user_profile(user_id: int, background_tasks: BackgroundTasks):
"""
获取用户资料接口。
当用户访问个人资料时,后台自动为其生成最新报告。
参数:
user_id (int): 用户 ID
background_tasks (BackgroundTasks): 后台任务管理器
优势:
- 用户快速看到当前资料
- 报告在后台生成,下次访问时即可获取
"""
# 模拟从数据库获取用户资料
user_data = {"id": user_id, "name": "张三", "email": "zhangsan@example.com"}
# 添加后台任务:生成报告
background_tasks.add_task(generate_report, user_id)
return {
"message": "用户资料获取成功",
"data": user_data,
"note": "报告正在后台生成,稍后可查看"
}
7.3 静态文件与 Jinja2 模板
7.3.1 静态文件服务
FastAPI 可以使用 StaticFiles 类来提供静态文件服务(如 CSS、JavaScript、图片等)。
安装依赖:
pip install aiofiles
目录结构:
my_project/
├── main.py
├── static/
│ ├── css/
│ │ └── style.css
│ ├── js/
│ │ └── script.js
│ └── images/
│ └── logo.png
└── templates/
└── index.html
注册静态文件路由:
from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles
app = FastAPI()
# 挂载静态文件目录
app.mount("/static", StaticFiles(directory="static"), name="static")
现在可以通过以下 URL 访问静态资源:
http://127.0.0.1:8000/static/css/style.csshttp://127.0.0.1:8000/static/js/script.jshttp://127.0.0.1:8000/static/images/logo.png
7.3.2 Jinja2 模板引擎
Jinja2 是一个现代、设计者友好的 Python 模板引擎,广泛用于 Web 开发中生成 HTML、XML 或其他标记语言。
安装依赖:
pip install jinja2
创建模板文件:
在 templates/ 目录下创建 index.html:
<!-- templates/index.html -->
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>{{ title }}</title>
<link rel="stylesheet" href="/static/css/style.css">
</head>
<body>
<h1>{{ title }}</h1>
<p>欢迎,{{ user.name }}!</p>
<h2>物品列表:</h2>
<ul>
{% for item in items %}
<li>{{ item.name }} - 价格:{{ item.price }} 元</li>
{% endfor %}
</ul>
{% if is_admin %}
<p><strong>您是管理员。</strong></p>
{% else %}
<p>您是普通用户。</p>
{% endif %}
<script src="/static/js/script.js"></script>
</body>
</html>
配置模板引擎:
from fastapi import FastAPI, Request
from fastapi.templating import Jinja2Templates
from fastapi.responses import HTMLResponse
app = FastAPI()
# 初始化模板引擎
templates = Jinja2Templates(directory="templates")
# 示例数据
items = [
{"name": "笔记本电脑", "price": 5999},
{"name": "鼠标", "price": 99},
{"name": "键盘", "price": 299}
]
@app.get("/", response_class=HTMLResponse)
async def read_root(request: Request):
return templates.TemplateResponse(
request=request,
name="index.html",
context={
"title": "首页",
"user": {"name": "Alice"},
"items": items,
"is_admin": False
}
)
Jinja2 核心语法:
| 语法 | 说明 | 示例 |
|---|---|---|
{{ }} |
变量输出 | {{ name }} |
{% %} |
控制结构 | {% if condition %}...{% endif %} |
{# #} |
注释 | {# 这是注释 #} |
{% extends "base.html" %} |
模板继承 | 继承基础模板 |
{% block content %}{% endblock %} |
定义可替换块 | 在子模板中覆盖内容 |
模板继承示例:
<!-- templates/base.html -->
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>{% block title %}默认标题{% endblock %}</title>
<link rel="stylesheet" href="/static/css/style.css">
</head>
<body>
<header>
<h1>我的网站</h1>
</header>
<main>
{% block content %}{% endblock %}
</main>
<footer>
<p>© 2025 版权所有</p>
</footer>
</body>
</html>
<!-- templates/home.html -->
{% extends "base.html" %}
{% block title %}首页{% endblock %}
{% block content %}
<h2>欢迎光临</h2>
<p>这里是主页内容。</p>
{% endblock %}
过滤器(Filters):
Jinja2 提供了丰富的内置过滤器:
{{ name|upper }} <!-- 转大写 -->
{{ text|length }} <!-- 字符串长度 -->
{{ number|round(2) }} <!-- 保留两位小数 -->
{{ items|join(', ') }} <!-- 列表转字符串 -->
{{ content|safe }} <!-- 标记为安全 HTML(不转义) -->
八、异常捕获
可以考虑用第三方的apiexception
pip install apiexception
官网地址https://akutayural.github.io/APIException/
from fastapi import FastAPI, Request, HTTPException, status
from fastapi.exceptions import RequestValidationError
from fastapi.responses import JSONResponse
from pydantic import BaseModel
import logging
app = FastAPI()
# 配置日志
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# -------------------------------
# 🔁 自定义异常类(可选)
# -------------------------------
class BizException(Exception):
"""业务逻辑异常"""
def __init__(self, message: str, code: int = 400):
self.message = message
self.code = code
class NotFoundException(Exception):
"""资源未找到"""
def __init__(self, resource: str = "资源"):
self.message = f"{resource}不存在"
self.code = 404
# -------------------------------
# 🛡️ 全局异常处理器
# -------------------------------
@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request: Request, exc: RequestValidationError):
"""
处理请求体校验失败(Pydantic 验证错误)
"""
errors = []
for error in exc.errors():
errors.append({
"loc": error["loc"],
"type": error["type"],
"msg": error["msg"],
"input": str(error.get("input", ""))[:100] # 截断避免太大
})
logger.warning(f"Validation error on {request.url}: {errors}")
return JSONResponse(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
content={
"success": False,
"message": "请求数据格式错误,请检查输入字段。",
"code": 422,
"details": errors
}
)
@app.exception_handler(BizException)
async def biz_exception_handler(request: Request, exc: BizException):
"""
处理业务异常
"""
logger.info(f"BizException: {exc.message}")
return JSONResponse(
status_code=200 if exc.code == 200 else 400,
content={
"success": False,
"message": exc.message,
"code": exc.code
}
)
@app.exception_handler(NotFoundException)
async def not_found_exception_handler(request: Request, exc: NotFoundException):
"""
处理资源未找到
"""
return JSONResponse(
status_code=404,
content={
"success": False,
"message": exc.message,
"code": 404
}
)
@app.exception_handler(HTTPException)
async def http_exception_handler(request: Request, exc: HTTPException):
"""
捕捉 FastAPI 内部的 HTTPException(如 raise HTTPException(403))
"""
return JSONResponse(
status_code=exc.status_code,
content={
"success": False,
"message": exc.detail,
"code": exc.status_code
}
)
@app.exception_handler(Exception)
async def unhandled_exception_handler(request: Request, exc: Exception):
"""
⚠️ 兜底异常处理器:捕捉所有未处理的异常(如代码 bug、数据库错误等)
"""
logger.error(f"Unhandled exception at {request.url}", exc_info=True)
return JSONResponse(
status_code=500,
content={
"success": False,
"message": "服务器内部错误,请联系管理员。",
"code": 500,
"details": "Internal Server Error" if not app.debug else str(exc)
}
)
@app.post("/analyze")
async def analyze(data: dict):
if not data.get("alarm_type"):
raise BizException("告警类型不能为空", code=400)
return {"success": True, "data": "ok"}
九、生命周期
FastAPI 通过 ASGI 规范提供的 lifespan 机制,支持在应用启动和关闭时执行自定义逻辑。这是管理应用资源生命周期的核心方式,适用于数据库连接、模型加载、缓存初始化等场景。
1. lifespan 简介
- ✅
lifespan是 FastAPI 中唯一的生命周期管理机制。 - 它替代了早期版本中的
@app.on_event("startup")和@app.on_event("shutdown")。 - 基于 ASGI 的
lifespan协议,使用 异步上下文管理器 实现。 - 支持两个阶段:
- Startup:应用启动后、接收请求前。
- Shutdown:应用关闭前、所有请求处理完毕后。
⚠️ 注意:
lifespan不处理单个请求的生命周期(那是中间件的职责)。
2. 支持的生命周期阶段
| 阶段 | 是否支持 | 说明 |
|---|---|---|
| Startup | ✅ | 应用启动时执行初始化逻辑 |
| Shutdown | ✅ | 应用关闭时执行清理逻辑 |
| Request 开始 | ❌ | 使用中间件实现 |
| Request 结束 | ❌ | 使用中间件实现 |
| 异常发生 | ❌ | 使用异常处理器实现 |
3. 代码示例
方法一:使用 @asynccontextmanager(推荐)
from fastapi import FastAPI
from contextlib import asynccontextmanager
import logging
# 配置日志
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
@asynccontextmanager
async def lifespan(app: FastAPI):
# ✅ 启动阶段
logger.info("🚀 应用正在启动...")
print("✅ 数据库连接已建立")
print("✅ ML 模型已加载")
yield # 应用运行中
# ✅ 关闭阶段
logger.info("🛑 应用正在关闭...")
print("🧹 数据库连接已关闭")
print("🧹 清理临时文件")
# 创建应用并传入 lifespan
app = FastAPI(lifespan=lifespan)
@app.get("/")
def read_root():
return {"message": "Hello World"}
方法二:使用函数 + yield(简洁版)
from fastapi import FastAPI
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
async def lifespan(app: FastAPI):
logger.info("🚀 应用启动:初始化资源")
# 初始化逻辑(如连接数据库)
yield
logger.info("🛑 应用关闭:释放资源")
# 清理逻辑(如断开连接)
app = FastAPI(lifespan=lifespan)
方法三:异步加载大模型示例
from fastapi import FastAPI
from contextlib import asynccontextmanager
import asyncio
# 模拟异步加载模型
async def load_model():
print("⏳ 正在加载大模型...")
await asyncio.sleep(2)
print("✅ 大模型加载完成")
return {"model": "qwen3-4b", "status": "loaded"}
model = None
@asynccontextmanager
async def lifespan(app: FastAPI):
global model
model = await load_model() # 启动时加载
yield
print("🧹 正在卸载模型...")
model = None # 关闭时清理
print("✅ 模型已卸载")
app = FastAPI(lifespan=lifespan)
@app.get("/predict")
async def predict():
return {"result": f"预测结果(使用模型:{model['model']})"}
4. 已废弃的方式(请勿使用)
# ❌ 已废弃!不要使用
@app.on_event("startup")
async def startup():
pass
@app.on_event("shutdown")
async def shutdown():
pass
FastAPI v0.99+ 起,官方推荐使用
lifespan替代on_event。
5. 与中间件的对比
| 功能 | lifespan |
中间件 |
|---|---|---|
| 应用启动/关闭 | ✅ | ❌ |
| 每个请求前后 | ❌ | ✅ |
| 异常处理 | ❌ | ✅ |
| 性能监控 | ❌ | ✅ |
| 认证/日志 | ❌ | ✅ |
✅
lifespan与中间件互补,分别处理应用级和请求级逻辑。
6. 实战建议
| 场景 | 推荐方式 |
|---|---|
| 数据库连接 | lifespan |
| 加载 ML 模型 | lifespan |
| 初始化缓存(Redis) | lifespan |
| 请求日志记录 | 中间件 |
| 身份认证 | 中间件或依赖项 |
| 错误监控 | 异常处理器 + 中间件 |
7. 总结
- ✅ FastAPI 只支持一个
lifespan函数。 - ✅ 支持
startup和shutdown两个阶段。 - ✅ 使用
@asynccontextmanager是最清晰、最推荐的方式。 - ✅ 避免使用已废弃的
on_event。 - ✅ 结合中间件,可构建完整生命周期管理方案。
通过合理使用 lifespan,你可以确保应用在启动时正确初始化资源,在关闭时优雅释放,提升系统的稳定性和可维护性。
十、任务调度
除了celery外,还有个新的模块叫TaskIQ
属于轻量级的,适配fastapi而生的任务调度模块
https://taskiq-python.github.io/
还有国产框架funboost
https://funboost.readthedocs.io/zh-cn/latest/articles/c4.html
最佳实践
-
项目结构:
my_project/ ├── main.py ├── routers/ │ ├── users.py │ └── items.py ├── models/ │ └── __init__.py ├── schemas/ │ └── user.py └── config.py -
配置管理:使用 Pydantic
BaseSettings。 -
安全:密码哈希、JWT 认证、CORS 控制。
-
测试:使用
TestClient编写单元测试。
参考资源
📌 注意:本文档基于 Pydantic V2 语法。V1 用户请注意:
model.dict()→model.model_dump()validator→field_validatorOptional[T]仍可用,但推荐使用T | None

浙公网安备 33010602011771号