【自用】Fastapi
路由操作依赖项是一种“前置/后置处理器”机制,通过抽离通用逻辑,使代码更简洁、可维护。在 FastAPI 等框架中,它们被广泛用于处理身份验证、权限控制等横切关注点,是构建健壮 API 的重要工具。
pydantic
Pydantic 是一个基于 Python 类型提示(type hints)的数据验证和设置管理库,主要用于数据解析、验证和序列化。它由 Samuel Colvin 开发,目前广泛应用于 FastAPI、Starlette 等现代 Python 框架中,也常用于数据处理、配置管理等场景。
核心功能
-
数据验证
通过类型提示自动验证输入数据的类型和结构,确保数据符合预期格式。支持基础类型(int、str、bool 等)、复杂类型(List、Dict、Union 等)以及自定义类型。 -
数据解析
自动将输入数据(如 JSON、字典、表单数据)转换为 Python 对象,并处理类型转换(例如将字符串 “123” 转换为整数 123)。 -
模型定义
通过继承pydantic.BaseModel创建数据模型,清晰定义数据结构和验证规则,支持默认值、可选字段、嵌套模型等。 -
错误处理
验证失败时生成详细的错误信息,包含字段路径和具体错误原因,便于调试。 -
序列化
支持将模型实例转换为字典、JSON 等格式,方便数据传输和存储。
基本用法示例
首先需要安装 Pydantic:
pip install pydantic
以下是一个简单示例,定义一个用户模型并验证数据:
from pydantic import BaseModel, EmailStr, Field
from typing import Optional, List
# 定义数据模型
class User(BaseModel):
id: int # 必选字段,整数类型
name: str = Field(..., min_length=2, max_length=50) # 必选字符串,长度限制
email: EmailStr # 内置邮箱格式验证
age: Optional[int] = Field(None, ge=0, le=120) # 可选整数,范围限制
hobbies: List[str] = [] # 默认为空列表
# 验证合法数据
valid_data = {
"id": 1,
"name": "Alice",
"email": "alice@example.com",
"age": 30,
"hobbies": ["reading", "hiking"]
}
user = User(**valid_data)
print(user.dict()) # 转换为字典
# 输出: {'id': 1, 'name': 'Alice', 'email': 'alice@example.com', 'age': 30, 'hobbies': ['reading', 'hiking']}
# 验证非法数据(会抛出 ValidationError)
invalid_data = {
"id": "not_a_number", # 类型错误
"name": "A", # 长度不足
"email": "invalid-email", # 邮箱格式错误
"age": 150 # 超出范围
}
try:
User(** invalid_data)
except Exception as e:
print(e)
高级特性
- 嵌套模型
支持在模型中嵌套其他模型,用于验证复杂数据结构:class Address(BaseModel): city: str street: str class UserWithAddress(User): address: Address # 嵌套模型 data = { "id": 2, "name": "Bob", "email": "bob@example.com", "address": {"city": "Beijing", "street": "Main St"} } user = UserWithAddress(**data)
2.** 配置选项 **通过模型的 Config 类自定义验证行为,例如:
class User(BaseModel):
name: str
class Config:
extra = "forbid" # 禁止未知字段
allow_mutation = False # 模型实例不可修改
3.** 数据转换 **支持自定义验证器(@validator)和根验证器(@root_validator)处理复杂逻辑:
from pydantic import validator
class User(BaseModel):
username: str
password: str
@validator('password')
def password_must_contain_number(cls, v):
if not any(c.isdigit() for c in v):
raise ValueError('password must contain at least one number')
return v
4.** 与其他库集成 **- 与 FastAPI 深度集成,自动生成 API 文档和请求验证
- 支持 ORM 模型(如 SQLAlchemy)的转换
- 兼容 Python 标准库(如
dataclasses)
版本差异
Pydantic 有两个主要版本:
-** v1 :稳定版本,支持 Python 3.6+,文档参见 v1 docs
- v2 **:全新重写,性能提升显著,基于 Rust 扩展,支持 Python 3.7+,文档参见 v2 docs
v2 引入了一些不兼容的变化(如 BaseModel 的部分方法调整),但提供了更好的性能和更多特性。
应用场景
- API 请求/响应验证(如 FastAPI)
- 配置文件解析和验证
- 数据序列化/反序列化(替代
json模块) - 表单验证和数据清洗
- 测试数据生成
Pydantic 凭借其简洁的 API 和强大的功能,已成为现代 Python 开发中处理数据验证的首选工具之一。
解释user = User(** valid_data)
user = User(** valid_data) 是 Python 中一种常见的对象实例化方式,结合了 Pydantic 模型的特性,具体含义如下:
拆解说明
-
User(...):调用User类(继承自 Pydantic 的BaseModel)的构造方法,创建一个User模型实例。 -
**valid_data:这是 Python 的字典解包语法,作用是将字典valid_data中的键值对“拆包”后,作为关键字参数传递给User类的构造方法。例如,若
valid_data是:valid_data = {"id": 1, "name": "Alice", "email": "alice@example.com"}则
User(** valid_data)等价于:User(id=1, name="Alice", email="alice@example.com")
在 Pydantic 中的意义
对于 Pydantic 模型而言,这个操作的核心作用是:
- 将字典数据传入模型,触发 Pydantic 的**自动数据验证 **(检查类型、约束条件等)。
- 验证通过后,生成一个模型实例(
user),可以通过.语法访问字段(如user.name、user.email)。 - 同时,Pydantic 会自动完成类型转换(例如将字符串 “123” 转为整数 123,前提是符合类型规则)。
为什么这样用?
在实际开发中,我们经常从外部获取数据(如 API 请求、JSON 文件、数据库查询),这些数据通常以字典形式存在。使用 ** 解包语法可以直接将字典数据传入 Pydantic 模型,快速完成数据验证和对象化,避免手动逐个传递参数,简化代码。
例如,从 API 接收的 JSON 数据解析为字典后,可直接通过这种方式传入模型进行验证:
import json
# 假设从 API 接收的 JSON 字符串
json_data = '{"id": 2, "name": "Bob", "email": "bob@example.com"}'
data_dict = json.loads(json_data) # 转换为字典
# 直接用字典创建模型实例(自动验证)
user = User(** data_dict)
总结:User(**valid_data) 是将字典数据传入 Pydantic 模型的便捷方式,既完成了对象实例化,又触发了 Pydantic 的核心功能——数据验证和类型转换。
路由操作依赖项(Route Operation Dependencies)
在 Web 开发(尤其是 FastAPI 等现代框架中),** 路由操作依赖项(Route Operation Dependencies)**是一种可复用的组件,用于在处理路由请求前(或后)执行特定逻辑,例如身份验证、权限检查、资源准备等。
它们的核心作用是:抽离通用逻辑,确保在路由处理函数执行前完成某些必要操作,并可将结果传递给路由函数。
预处理(Before)依赖项: 在路由操作函数执行前运行,用于预处理输入数据,验证请求等。
后处理(After)依赖项: 在路由操作函数执行后运行,用于执行一些后处理逻辑,如日志记录、清理等。
主要特点
1.自动执行:当客户端请求某个路由时,框架会先自动执行其依赖项,再调用路由处理函数。
2.可复用:同一依赖项可被多个路由共享(如身份验证逻辑),避免代码重复。
3.传递参数:依赖项的返回值可作为参数传入路由函数,供业务逻辑使用。
4.层级嵌套:依赖项本身可以依赖其他依赖项,形成依赖链。
典型使用场景
- 身份验证(验证请求中的 Token)
- 权限检查(判断用户是否有权限访问资源)
- 数据库连接初始化
- 请求参数预处理
- 日志记录或性能监控
- 资源清理(如关闭连接,在请求处理后执行)
FastAPI 中的示例
在 FastAPI 中,依赖项是其核心特性之一,定义和使用非常简洁:
from fastapi import FastAPI, Depends, HTTPException
app = FastAPI()
# 1. 定义一个依赖项:验证 Token
def verify_token(token: str):
if token != "secret":
raise HTTPException(status_code=401, detail="无效的 Token")
return "valid_user" # 返回验证后的用户信息
# 2. 定义路由,通过 Depends() 引用依赖项
@app.get("/protected")
def protected_route(user: str = Depends(verify_token)):
# 依赖项 verify_token 执行成功后,其返回值会传给 user 参数
return {"message": f"欢迎访问受保护资源,用户:{user}"}
上述代码中:
- 客户端访问
/protected时,FastAPI 会先调用verify_token依赖项。 - 如果 Token 无效,依赖项直接抛出异常,阻止路由函数执行。
- 如果 Token 有效,依赖项返回的
"valid_user"会作为user参数传入protected_route。
更复杂的依赖项
依赖项可以是函数、类,甚至异步函数,也支持传递参数:
# 带参数的依赖项:检查用户角色
def check_role(user: str, required_role: str):
# 假设从数据库查询用户角色(简化示例)
user_roles = {"valid_user": "admin", "guest": "viewer"}
if user_roles.get(user) != required_role:
raise HTTPException(status_code=403, detail="权限不足")
return True
# 路由使用带参数的依赖项
@app.get("/admin")
def admin_route(
user: str = Depends(verify_token),
_: bool = Depends(lambda: check_role(user, "admin")) # 嵌套依赖
):
return {"message": "欢迎访问管理员页面"}
总结
路由操作依赖项是一种“前置/后置处理器”机制,通过抽离通用逻辑,使代码更简洁、可维护。在 FastAPI 等框架中,它们被广泛用于处理身份验证、权限控制等横切关注点,是构建健壮 API 的重要工具。
异步依赖项
在实际 FastAPI 后端开发中,异步依赖项(Async Dependencies) 的使用非常普遍,尤其是在高并发场景或需要与异步服务交互时,其价值尤为突出。
为什么异步依赖项被广泛使用?
FastAPI 基于 Starlette 和 Pydantic 构建,天生支持异步编程(async/await)。当依赖项中涉及I/O 密集型操作时(如数据库查询、网络请求、缓存操作等),异步依赖项能显著提升性能:
- 非阻塞等待:异步操作在等待 I/O 响应时(如等待数据库返回结果),不会阻塞事件循环,允许服务器同时处理其他请求,提高资源利用率。
- 与异步生态兼容:现代 Python 后端常用的工具(如异步数据库驱动
asyncpg、aiomysql,异步 HTTP 客户端httpx.AsyncClient等)都需要在异步环境中使用,异步依赖项是自然的选择。
典型使用场景
-
异步数据库操作
当使用异步数据库驱动(如asyncpg操作 PostgreSQL)时,依赖项需要用异步函数编写以配合await语法:from fastapi import Depends, FastAPI import asyncpg app = FastAPI() # 异步依赖项:创建数据库连接 async def get_db_connection(): # 定义了一个异步函数 get_db_connection,它用于创建和管理数据库连接。 conn = await asyncpg.connect("postgresql://user:pass@localhost/db") # 使用 asyncpg.connect 异步连接到 PostgreSQL 数据库。连接字符串 "postgresql://user:pass@localhost/db" 包含了数据库的用户名、密码、主机地址和数据库名称 try: yield conn # 提供连接给路由函数 (yield 关键字用于将连接对象 conn 提供给依赖项的调用者(这里是路由函数)。yield 之后的代码会在依赖项使用完毕后执行) finally: await conn.close() # 后处理:关闭连接;在依赖项使用完毕后关闭数据库连接 # 异步路由使用异步依赖项 @app.get("/items") async def get_items(conn = Depends(get_db_connection)): items = await conn.fetch("SELECT * FROM items") # 异步查询 return [dict(item) for item in items] -
调用外部异步 API
若依赖项需要请求外部服务(如第三方 API),使用异步 HTTP 客户端(如httpx.AsyncClient)时,依赖项需定义为异步:from fastapi import Depends, FastAPI import httpx app = FastAPI() # 异步依赖项:调用外部 API 获取数据 async def fetch_external_data(): async with httpx.AsyncClient() as client: response = await client.get("https://api.example.com/data") return response.json() @app.get("/external") async def get_external_data(data = Depends(fetch_external_data)): return data -
异步缓存操作
与 Redis 等缓存服务交互时,使用异步客户端(如aioredis)需配合异步依赖项:import aioredis # 一个异步的 Redis 客户端库,用于与 Redis 数据库进行异步交互。 from fastapi import Depends, FastAPI app = FastAPI() # 异步依赖项:获取 Redis 连接 async def get_redis(): redis = await aioredis.from_url("redis://localhost") # 使用 aioredis.from_url 异步连接到 Redis 服务器 try: yield redis # yield 关键字用于将 Redis 连接对象 redis 提供给依赖项的调用者(这里是路由函数)。yield 之后的代码会在依赖项使用完毕后执行。 finally: await redis.close() # 在依赖项使用完毕后关闭 Redis 连接 @app.get("/cache/{key}") # {key} 是一个路径参数,表示缓存的键 async def get_cache(key: str, redis = Depends(get_redis)): value = await redis.get(key) # 异步获取缓存;使用 redis.get 异步获取指定键的缓存值。redis.get 是一个异步方法,返回缓存中对应键的值 return {"key": key, "value": value} # 将获取到的缓存值包装成一个字典并返回给客户端
什么时候不适合用异步依赖项?
- CPU 密集型操作:如复杂计算、数据处理等,异步无法提升性能(甚至可能因事件循环切换导致效率下降),此时应使用同步依赖项配合
run_in_threadpool执行。 - 依赖同步库:若依赖项必须调用同步阻塞库(如
requests而非httpx.AsyncClient),强行用异步函数包裹反而会阻塞事件循环,需谨慎处理。
总结
在 FastAPI 开发中,异步依赖项的使用频率与项目的异步化程度直接相关:
- 对于以 I/O 操作为主的后端服务(如 API 网关、数据查询服务),异步依赖项是主流选择,能充分发挥 FastAPI 的性能优势。
- 即使是混合使用同步和异步代码的项目,涉及异步服务交互的部分也通常会用异步依赖项。
因此,掌握异步依赖项的设计和使用,是 FastAPI 开发中的重要技能。
更多推荐


所有评论(0)