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的写法类似
image
写法2,路由装饰器在视图函数上
这种将路由和视图写在一起
image

三、请求处理

路径参数与查询参数

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 迁移命令流程

  1. 生成迁移脚本(检测模型变更)
aerich migrate --name create_user_table

✅ 会在 migrations/versions/ 下生成类似 0_20250912100000_create_user_table.py 的文件。

  1. 应用迁移(同步到数据库)
aerich upgrade
  1. 查看迁移状态
aerich history
aerich heads
  1. 回滚迁移
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:设为默认值。

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 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_nextreturn 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.css
  • http://127.0.0.1:8000/static/js/script.js
  • http://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>&copy; 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 函数。
  • ✅ 支持 startupshutdown 两个阶段。
  • ✅ 使用 @asynccontextmanager 是最清晰、最推荐的方式。
  • ✅ 避免使用已废弃的 on_event
  • ✅ 结合中间件,可构建完整生命周期管理方案。

通过合理使用 lifespan,你可以确保应用在启动时正确初始化资源,在关闭时优雅释放,提升系统的稳定性和可维护性。

十、任务调度

除了celery外,还有个新的模块叫TaskIQ
属于轻量级的,适配fastapi而生的任务调度模块
https://taskiq-python.github.io/

还有国产框架funboost
https://funboost.readthedocs.io/zh-cn/latest/articles/c4.html

最佳实践

  1. 项目结构

    my_project/
    ├── main.py
    ├── routers/
    │   ├── users.py
    │   └── items.py
    ├── models/
    │   └── __init__.py
    ├── schemas/
    │   └── user.py
    └── config.py
    
  2. 配置管理:使用 Pydantic BaseSettings

  3. 安全:密码哈希、JWT 认证、CORS 控制。

  4. 测试:使用 TestClient 编写单元测试。


参考资源

📌 注意:本文档基于 Pydantic V2 语法。V1 用户请注意:

  • model.dict()model.model_dump()
  • validatorfield_validator
  • Optional[T] 仍可用,但推荐使用 T | None
posted @ 2025-09-11 10:17  零哭谷  阅读(27)  评论(0)    收藏  举报