FastAPI路由(router)
在“FastAPI系列02:FastAPI程序结构与生命周期”一节中,我们以宏观的视野分析了FastAPI框架的结构,了解了FastAPI路由系统在FastAPI框架中的重要地位,本节将对FastAPI路由进行展开分析,进一步了解FastAPI的路由规则、路由管理器以及路由的注册和执行过程。
1、路由的注册过程
路由是 Web 应用中的 URL 映射规则。FastAPI 使用装饰器的方式将这些URL 映射规则与指定的路由函数绑定起来,从而完成特定的 HTTP 请求与具体Python 函数响应的连接。
FastAPI 提供了以下几种方式向路由管理器注册路由:
1)使用 FastAPI 实例注册路由
from fastapi import FastAPI
app = FastAPI()
@app.get("/items/{item_id}")
async def read_item(item_id: int, q: str = None):
return {"item_id": item_id, "q": q}
说明:
• 使用 @app.get(“/items/{item_id}”) 注册路由
• item_id 是路径参数
• q 是查询参数,默认为 None
2)使用 APIRouter 模块化路由
APIRouter 提供了更好的路由管理方式,便于拆分和复用。
from fastapi import FastAPI, APIRouter
app = FastAPI()
router = APIRouter()
@router.get("/users/{user_id}")
async def read_user(user_id: int):
return {"user_id": user_id}
# 将路由挂载到主应用
app.include_router(router, prefix="/api", tags=["Users"])
说明:
• 使用 APIRouter() 创建路由对象
• 使用 include_router() 将路由挂载到主应用
• prefix=“/api” 为路由统一加前缀 /api
3)路由注册装饰器方法
FastAPI 提供了简洁的装饰器方法来定义路由。常用的路由方法包括:
• @app.get()
:处理 GET 请求
• @app.post()
:处理 POST 请求
• @app.put()
:处理 PUT 请求
• @app.delete()
:处理 DELETE 请求
• @app.patch()
:处理 PATCH 请求
• @app.options()
:处理 OPTIONS 请求
• @app.head()
:处理 HEAD 请求
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
async def read_root():
return {"message": "Hello, FastAPI"}
@app.post("/items/")
async def create_item(name: str):
return {"name": name}
2、路由管理器
在FastAPI中,所有的注册路由都会统一保存到app.routes中,app.routes保存了所有的路由注册信息,包括通过API Router所注册的路由。
使用 app.routes 查看所有路由
from fastapi import FastAPI, APIRouter
app = FastAPI()
# 由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(name: str):
return {"name": name}
# 由APIRouter 注册的路由
router = APIRouter()
@router.get("/users/")
async def get_users():
return [{"name": "Alice"}, {"name": "Bob"}]
app.include_router(router)
# 打印所有路由
for route in app.routes:
print(f"Path: {route.path}, Methods: {route.methods}")
如果需要更多的路由信息,比如处理函数的名称、依赖项等,可以通过以下方式获取:
for route in app.routes:
print(f"Path: {route.path}")
print(f"Methods: {route.methods}")
print(f"Name: {route.name}")
print(f"Endpoint: {route.endpoint}")
print("-" * 30)
说明:
• route.path: 路径
• route.methods: 支持的 HTTP 方法
• route.name: 路由的名称
• route.endpoint: 对应的 Python 处理函数
3、路由规则
1)路由规则的结构
在 FastAPI 中,一个路由规则主要由以下组件构成:
组件 | 说明 | 示例 |
---|---|---|
路径 (Path) | URL 中的访问路径 | /items/{item_id} |
方法 (Method) | HTTP 请求方法 | 如 GET、POST 等 GET、POST |
操作处理函数 | 处理该路径和方法的函数 | async def read_item() |
路径参数 | 动态参数,用于捕获路径中的变量 | {item_id} |
查询参数 | 使用 ?key=value 形式的参数 | ?q=search |
请求体参数 | 用于接收 JSON 等格式的数据 | { “name”: “item1”, “price”: 10.0 } |
依赖项 (Dependency) | 依赖注入,用于共享逻辑或中间处理 | Depends(get_db) |
响应模型 | 定义 API 响应的数据结构 | ResponseModel |
状态码 | 定义成功或失败时的 HTTP 状态码 | 200 OK、404 Not Found |
2)路由规则的配置
使用路由方法注册路由时可以配置相关的参数,下面是@app.get()
方法的参数配置说明,其他方法也类似:
@app.get(
# 【基本参数】
"/items/{item_id}", # path: 定义路径,支持路径参数 {item_id}
status_code=200, # status_code: 指定成功时的 HTTP 状态码
# 【response 参数】
response_model=Item, # response_model: 指定响应数据模型,用于数据校验和文档生成
response_class=None, # response_class: 自定义响应类型,例如 JSONResponse 或 HTMLResponse
responses={404: {"description": "Item not found"}}, # responses: 自定义特定状态码的响应信息
# 【依赖参数】
dependencies=[Depends(get_current_user)], # dependencies: 定义依赖项,常用于认证、权限管理、日志记录等
# 【OpenAPI 参数】
tags=["items"], # tags: 为路由分组打标签,方便在 API 文档中展示
summary="获取指定 ID 的商品信息", # summary: 简要描述接口功能
description="根据商品 ID 获取商品信息,支持可选查询参数进行过滤", # description: 详细描述接口功能,支持 Markdown 格式
deprecated=False, # deprecated: 标记该接口是否已废弃
operation_id="read_item_by_id", # operation_id: 自定义操作 ID,用于在 OpenAPI 文档中唯一标识此接口
include_in_schema=True # include_in_schema: 是否在 OpenAPI 文档中显示该路由,False 隐藏
)
基本参数
参数名 | 作用 | 示例 |
---|---|---|
path | 定义 API 路径,支持路径参数 | /items/{item_id} |
status_code | 设置成功时的 HTTP | 状态码 200 |
依赖参数
参数名 | 作用 | 示例 |
---|---|---|
dependencies | 定义依赖项,常用于认证、权限管理、日志记录等 | [Depends(get_current_user)] |
Response 参数
参数名 | 作用 | 示例 |
---|---|---|
response_model | 指定响应的数据模型,确保输出符合预期 | Item |
response_class | 自定义响应类, | 例如 JSONResponse、HTMLResponse JSONResponse |
responses | 自定义状态码的响应信息和描述 | {404: {“description”: “Item not found”}} |
OpenAPI 参数
参数名 | 作用 | 示例 |
---|---|---|
tags | 分配标签,用于在 API 文档中分组显示 | [‘items’] |
summary | 简要描述 API 的功能 | “获取指定 ID 的商品信息” |
description | 详细描述 API 的功能,支持 Markdown | “根据商品 ID 获取商品信息” |
deprecated | 标记 API 是否已废弃,True 表示已废弃 | False |
operation_id | 自定义操作 ID,用于 OpenAPI 中标识接口 | “read_item_by_id” |
include_in_schema | 控制 API 是否在 OpenAPI 文档中显示 | True |
3)路径参数、查询参数、请求体参数
在 FastAPI 中,路由处理函数可以接收 路径参数、查询参数 和 请求体参数,它们各自有不同的作用和使用场景。
路径参数(Path Parameters)
• 路径参数是在 URL 的路径中定义的参数,用于标识特定的资源,路径参数可以清晰地表示资源之间的层级关系。
• 使用 {} 包裹路径参数,并在函数中声明对应的参数名。
• 参数类型需要在函数签名中明确标注。
from fastapi import FastAPI
app = FastAPI()
@app.get("/items/{item_id}")
async def read_item(item_id: int):
return {"item_id": item_id}
说明
• /items/{item_id}:定义了 item_id 作为路径参数。
• item_id: int:在函数中定义并指定数据类型,FastAPI会自动校验。
• 响应:返回 item_id 的值。
注意事项
• 路径参数是必需的。
• 数据类型必须匹配,否则会返回 422 Unprocessable Entity。
• 路径参数数据类型可以是基本类型,也可以是枚举类型。
查询参数(Query Parameters)
• 查询参数是在 URL 中 ?key=value 形式的参数,通常用于对资源进行筛选、排序、分页等操作,适合在需要对资源进行动态查询时使用。
• 在函数中直接声明参数即可,FastAPI 会自动识别它们为查询参数。
from typing import Optional
from fastapi import FastAPI
app = FastAPI()
@app.get("/items/")
async def read_items(q: Optional[str] = None, limit: int = 10):
return {"query": q, "limit": limit}
说明
• /items/?q=book&limit=5:传入查询参数 q 和 limit。
• Optional[str] = None:q 是可选参数,默认为 None。
• limit: int = 10:limit 参数有默认值 10。
• 响应:返回传入的查询参数。
注意事项
• 查询参数是可选的,但可以设置默认值。
• 支持多参数使用 & 连接:/items/?q=book&limit=5。
请求体参数(Request Body)
• 请求体参数通过在 HTTP 请求的 Body 中发送数据,用于传递大量的数据或复杂的数据结构,适合在创建、更新资源时使用。
• FastAPI 使用 Pydantic 模型来定义请求体,并自动进行数据验证。
from pydantic import BaseModel
from fastapi import FastAPI
app = FastAPI()
# 定义请求体模型
class Item(BaseModel):
name: str
description: str
price: float
tax: Optional[float] = None
@app.post("/items/")
async def create_item(item: Item):
return {"item_name": item.name, "item_price": item.price}
说明
• Item:使用 Pydantic 定义请求体数据模型。
• item: Item:FastAPI 自动将请求体数据映射到 Item 实例。
• 响应:返回处理后的数据。
注意事项
• 请求体数据必须是合法的 JSON 格式。
• FastAPI 会根据 BaseModel 自动校验数据类型和必填字段。
• 请求体一般用于数据创建、更新等场景。
4、路由函数
在 FastAPI中,路由函数 是用来处理客户端请求并返回响应的函数。它是一个在特定的 URL 路径和 HTTP 方法(如 GET、POST、PUT、DELETE 等)上执行的函数。
1)路由函数参数
在 FastAPI中,路由函数一般以路径参数、查询参数、请求体参数为函数参数。在前面的例子中我们已经详细解析了路径参数、查询参数、请求体参数的使用,在此不再赘述。
1)同步、异步
在 FastAPI 中,路由函数可以是同步函数或异步函数,这两种类型的路由函数在使用和性能表现上有所不同,下面为你详细介绍:
同步路由函数
同步函数 是常规的阻塞式函数,执行完成后才会返回结果。适用于 CPU 密集型任务或不涉及 I/O 操作的场景
from fastapi import FastAPI
app = FastAPI()
@app.get("/sync")
def sync_function():
# 模拟CPU密集型任务
result = sum(i for i in range(1000000))
return {"result": result}
说明
• def sync_function():使用普通的同步函数。
• 适用场景:数学计算、数据处理等。
• 阻塞行为:函数执行期间,无法处理其他请求。
异步路由函数
异步函数 使用 async def 定义,通常搭配 await 调用异步 I/O 操作。适用于 I/O 密集型任务,例如数据库查询、网络请求等。FastAPI 使用 Python 的 asyncio 库实现异步操作。
import asyncio
from fastapi import FastAPI
app = FastAPI()
@app.get("/async")
async def async_function():
# 模拟异步I/O操作
await asyncio.sleep(2)
return {"message": "Completed after 2 seconds"}
说明
• async def async_function()
:定义异步函数。
• await asyncio.sleep(2)
:模拟一个耗时 2 秒的异步操作。
• 适用场景:I/O 密集型任务如数据库操作、调用外部 API 等,高并发场景。
• 非阻塞行为:在等待期间可以处理其他请求。
5、路由的执行过程
1)FastAPI路由的执行过程
当一个请求发送到 FastAPI 时,执行过程如下:
Step 1:路由匹配
- FastAPI 使用基于 ASGI 的路由树结构,遍历所有注册的路由。
- 根据请求的 Path 和 Method 进行匹配。
- 如果匹配成功,进入下一步;否则返回 404 Not Found。
Step 2:路径参数解析
• FastAPI 会从 URL 中提取路径参数,并进行类型转换。
• 如果参数不符合定义的类型(如 int),返回 422 Unprocessable Entity。
Step 3:依赖注入
• FastAPI 执行 Depends() 中定义的依赖项。
• 如果依赖项执行失败,会返回 HTTPException 的错误信息。
Step 4:请求体和查询参数解析
• FastAPI 会根据函数签名中的参数,从请求体或查询参数中提取数据。
• 自动进行数据校验和类型转换。
Step 5:执行路由函数
• FastAPI 调用目标函数,将解析后的参数传入执行。
• 如果函数是异步的 (async),FastAPI 使用 asyncio 进行调度。
Step 6:生成响应
• FastAPI 会根据函数的返回值和响应模型生成 HTTP 响应。
• 默认返回 200 OK,除非另有指定。
2)路由规则的匹配优先级
先定义的路由优先匹配
在 FastAPI 中,先定义的路由优先匹配,如果有多个路由能匹配某个请求路径,FastAPI 会选择第一个符合条件的路由。
from fastapi import FastAPI
app = FastAPI()
@app.get("/items/{item_id}")
async def read_item(item_id: int):
return {"message": "This is a generic item"}
@app.get("/items/special")
async def read_special_item():
return {"message": "This is a special item"}
结果:
• GET /items/special:返回 {“message”: “This is a generic item”}
• 即使有精确匹配的路由,FastAPI 已经在上一个路由中匹配到了 /items/{item_id}。
静态路由优先于动态路由
如果 FastAPI 能找到完全匹配的静态路径,就不会再考虑其他路由。
from fastapi import FastAPI
app = FastAPI()
@app.get("/users/me")
async def read_current_user():
return {"user": "current user"}
@app.get("/users/{user_id}")
async def read_user(user_id: int):
return {"user_id": user_id}
结果:
• GET /users/me:返回 {“user”: “current user”}
• GET /users/123:返回 {“user_id”: 123}
• 即使 /users/{user_id} 也能匹配 /users/me**,但静态路由优先。**
更具体的参数类型优先
如果两个路由路径都包含变量,FastAPI 会根据参数类型的精确程度决定优先级,例如 int 优先于 str。
from fastapi import FastAPI
app = FastAPI()
@app.get("/items/{item_id}")
async def read_item_int(item_id: int):
return {"item_id": item_id, "type": "int"}
@app.get("/items/{item_id}")
async def read_item_str(item_id: str):
return {"item_id": item_id, "type": "str"}
结果:
• GET /items/123:返回 {“item_id”: 123, “type”: “int”}
• GET /items/abc:返回 {“item_id”: “abc”, “type”: “str”}
2)路由规则的覆盖规则
在 FastAPI 中,如果出现 重复的路由规则(即路径和方法相同),会产生以下几种情况:
1.路径和方法完全相同的重复路由,会覆盖:FastAPI 会直接覆盖之前的路由,最后注册的路由会生效。
2.路径和方法相同但参数不同,不会覆盖:FastAPI 会根据参数的类型和名称区分路由,通常不会报错。
3.路径相同但HTTP方法不同,不会覆盖:FastAPI 允许同一路径使用不同的 HTTP 方法,例如同一个路径同时支持 GET 和 POST 请求。请求时方法完全匹配的路由会被优先选择。
from fastapi import FastAPI
app = FastAPI()
@app.get("/items/{item_id}")
async def read_item(item_id: int):
return {"item_id": item_id, "method": "GET"}
@app.post("/items/{item_id}")
async def create_item(item_id: int):
return {"item_id": item_id, "method": "POST"}
结果:
• GET /items/123:返回 {“item_id”: 123, “method”: “GET”}
• POST /items/123:返回 {“item_id”: 123, “method”: “POST”}