FastAPI:(12)更大的应用(APIRouter)
概述
文章内容概括
graph TD
A[FastAPI大型应用] --> B[项目结构]
A --> C[APIRouter]
B --> D[模块化]
B --> E[分层设计]
D --> F[功能模块划分]
E --> G[路由/模型/服务分层]
C --> H[路由模块化]
C --> I[路由嵌套]
H --> J[独立功能封装]
I --> K[多级路由结构]
J --> L[用户模块]
J --> M[商品模块]
K --> N[主路由包含子路由]
项目文件结构
将项目的各个功能模块(如路由、模型、服务逻辑、配置等)组织成分层、分模块的目录结构。这种结构设计的核心目的是提升项目的可读性、可维护性、可扩展性,以及便于多人协作开发。清晰合理的结构是实现大型系统工程化开发的基础。
重要特征:
- 分层清晰:根据职责将代码分为路由层、业务逻辑层、数据模型层、配置层等。
- 模块独立:每个模块(如用户模块、订单模块)有独立的子目录,逻辑和依赖低耦合。
- 统一入口:主程序文件(如
main.py或app.py)作为统一入口负责加载配置、挂载路由。 - 可拓展性强:结构支持按需扩展,新增模块或逻辑无需破坏原有结构。
- 易于协作:多人开发时职责清晰,文件冲突概率低。
用户管理分层结构(正例)
项目文件结构
采用典型 MVC 风格,结构如下:
project/
├── main.py
├── routers/
│ └── user.py
├── models/
│ └── user.py
├── services/
│ └── user_service.py
├── config/
│ └── settings.py
每层只负责一个方面的职责,如路由层仅处理 HTTP 请求和响应,service 处理业务逻辑,models 定义 Pydantic 和数据库模型。
特征对比:
- ✅ 分层清晰:不同层职责分明,互不混杂。
- ✅ 模块独立:user 模块相关逻辑集中,不依赖其他模块。
- ✅ 统一入口:
main.py加载 config 并挂载 routers。 - ✅ 可拓展性强:可轻松添加如
product,order模块。 - ✅ 易于协作:前后端、业务逻辑、数据库各自有独立开发空间。
金融微服务结构(正例)
项目文件结构
按领域模块拆分服务,并在每个服务中采用标准结构,结构如下:
risk-service/
├── app/
│ ├── main.py
│ ├── routers/
│ ├── models/
│ ├── services/
│ └── utils/
├── tests/
├── requirements.txt
用于部署为单独服务,并支持 CI/CD 流程和自动测试。
特征对比:
- ✅ 分层清晰:模块职责划分严格。
- ✅ 模块独立:每个微服务模块自包含,可独立运行。
- ✅ 统一入口:每个服务有独立
main.py。 - ✅ 可拓展性强:支持快速复制模板创建新服务。
- ✅ 易于协作:团队可以按微服务分组进行开发。
官网例子(正例)
项目文件结构
app目录包含了所有内容。并且它有一个空文件app/__init__.py,因此它是一个「Python 包」(「Python 模块」的集合):app。- 它包含一个
app/main.py文件。由于它位于一个 Python 包(一个包含__init__.py文件的目录)中,因此它是该包的一个「模块」:app.main。 - 还有一个
app/dependencies.py文件,就像app/main.py一样,它是一个「模块」:app.dependencies。 - 有一个子目录
app/routers/包含另一个__init__.py文件,因此它是一个「Python 子包」:app.routers。 - 文件
app/routers/items.py位于app/routers/包中,因此它是一个子模块:app.routers.items。 - 同样适用于
app/routers/users.py,它是另一个子模块:app.routers.users。 - 还有一个子目录
app/internal/包含另一个__init__.py文件,因此它是又一个「Python 子包」:app.internal。 app/internal/admin.py是另一个子模块:app.internal.admin。
.
├── app # 「app」是一个 Python 包
│ ├── __init__.py # 这个文件使「app」成为一个 Python 包
│ ├── main.py # 「main」模块,例如 import app.main
│ ├── dependencies.py # 「dependencies」模块,例如 import app.dependencies
│ └── routers # 「routers」是一个「Python 子包」
│ │ ├── __init__.py # 使「routers」成为一个「Python 子包」
│ │ ├── items.py # 「items」子模块,例如 import app.routers.items
│ │ └── users.py # 「users」子模块,例如 import app.routers.users
│ └── internal # 「internal」是一个「Python 子包」
│ ├── __init__.py # 使「internal」成为一个「Python 子包」
│ └── admin.py # 「admin」子模块,例如 import app.internal.admin
上面有几个
__init__.py文件:每个目录或子目录中都有一个。这就是能将代码从一个文件导入到另一个文件的原因。
APIRouter
APIRouter 是 FastAPI 提供的一种路由模块化工具,用于将不同功能模块的接口(API 路由)组织成多个独立的路由集合。它支持将各路由逻辑拆分、组合,并统一注册到主应用中,促进项目的结构化、可维护性和团队协作开发。
重要特征:
- 模块化:通过将不同功能的路由组织在不同的 APIRouter 中,提高代码的可读性和可维护性。
- 可重用性:每个 APIRouter 可以被重复导入到不同项目或子应用中。
- 集中式管理:支持在主应用中集中注册多个 APIRouter,便于统一挂载路径、添加中间件、标签、依赖等。
- 增强协作:团队成员可以分别在不同的 router 文件中开发功能,避免冲突。
用户管理模模块封装(正例)
APIRouter
例子描述:一个大型系统中,用户管理模块(如注册、登录、信息查询)通过 user_router 进行封装。所有关于用户的接口都集中在 routers/user.py 中,主应用只需在启动时引入该 router 并挂载到 /users 路径下。
特征对比:
- ✅ 模块化:
user_router独立文件封装所有用户相关 API。 - ✅ 可重用性:该 router 可以被用在多个项目中。
- ✅ 集中式管理:所有
/users/*路径统一管理、便于权限控制。 - ✅ 增强协作:团队中某成员可专注开发 user router,不干扰其他部分
# routers/user.py
from fastapi import APIRouter
user_router = APIRouter(prefix="/users", tags=["users"])
@user_router.get("/profile")
def get_user_profile():
return {"msg": "User profile"}
@user_router.post("/login")
def login():
return {"msg": "User logged in"}
AI模型接口封装(正例)
APIRouter
例子描述:一个 Web 服务包含多个模型预测接口(如情感分析、图像识别等),开发者将每种模型分别放在不同的 router 中,例如 text_model_router, image_model_router,并以 /models/text 和 /models/image 挂载。
特征对比:
- ✅ 模块化:每类模型服务单独封装成 router。
- ✅ 可重用性:某 router(如图像识别模型)可部署为单独微服务。
- ✅ 集中式管理:统一前缀如
/models下管理所有模型接口。 - ✅ 增强协作:多模型由不同工程师独立负责,无路由冲突。
# routers/model.py
from fastapi import APIRouter
model_router = APIRouter(prefix="/models", tags=["models"])
@model_router.post("/text")
def predict_text_model():
return {"prediction": "positive"}
@model_router.post("/image")
def predict_image_model():
return {"prediction": "cat"}
单文件内编码所有接(反例)
APIRouter
例子描述:开发者将所有 API 接口(用户、商品、订单等)都写在 main.py 文件内,没有使用 APIRouter,也没有模块化结构,所有路径均直接由 @app.get/post 定义。
特征对比:
- ❌ 模块化缺失:所有接口混杂在同一个文件中,难以阅读和维护。
- ❌ 可重用性差:路径和逻辑耦合严重,难以迁移重用。
- ❌ 缺乏集中式管理:路径分布杂乱无章,无法统一挂载策略或中间件。
- ❌ 协作困难:多人协作易冲突,难以独立开发与测试模块。
官网例子(正例)
APIRouter
所有「路径操作」都有相同的:
- 路径
prefix:/items。 tags:(仅有一个items标签)。- 额外的
responses。 dependencies:它们都需要创建的X-Token依赖项。
因此,可以将其添加到APIRouter中,而不是将其添加到每个路径操作中。
每个_路径操作_的路径都必须以
/开头,例如:@router.get("/{item_id}") async def read_item(item_id: str):。前缀不能以/作为结尾。
还可以添加一个 tags 列表和额外的 responses 列表,这些参数将应用于此路由器中包含的所有「路径操作」。
在
APIRouter中具有dependencies可以用来,例如,对一整组的_路径操作_要求身份认证。即使这些依赖项并没有分别添加到每个路径操作中。
prefix、tags、responses以及dependencies参数只是(和其他很多情况一样)FastAPI 的一个用于帮助避免代码重复的功能。
from fastapi import APIRouter, Depends, HTTPException
from ..dependencies import get_token_header # 导入依赖项
router = APIRouter( # 添加自定义的tags,response,dependencies
prefix="/items",
tags=["items"],
dependencies=[Depends(get_token_header)],
responses={404: {"description": "Not found"}},
)
fake_items_db = {"plumbus": {"name": "Plumbus"}, "gun": {"name": "Portal Gun"}}
@router.get("/")
async def read_items():
return fake_items_db
@router.get("/{item_id}")
async def read_item(item_id: str):
if item_id not in fake_items_db:
raise HTTPException(status_code=404, detail="Item not found")
return {"name": fake_items_db[item_id]["name"], "item_id": item_id}
@router.put( # 添加自定义的tags,response
"/{item_id}",
tags=["custom"],
responses={403: {"description": "Operation forbidden"}},
)
async def update_item(item_id: str):
if item_id != "plumbus":
raise HTTPException(
status_code=403, detail="You can only update the item: plumbus"
)
return {"item_id": item_id, "name": "The great Plumbus"}
FastAPI主体:
像平常一样导入并创建一个 FastAPI 类。甚至可以声明全局依赖项,它会和每个 APIRouter 的依赖项组合在一起:
from fastapi import Depends, FastAPI # 导入FastAPI类
from .dependencies import get_query_token, get_token_header
from .internal import admin
from .routers import items, users # 导入APIRouter
app = FastAPI(dependencies=[Depends(get_query_token)]) # 声明全局依赖项
# 包含 `users` 和 `items` 的 `APIRouter`
app.include_router(users.router)
app.include_router(items.router)
app.include_router( # 包含一个有自定义 `prefix`、`tags`、`responses` 和 `dependencies` 的 `APIRouter`
admin.router,
prefix="/admin",
tags=["admin"],
dependencies=[Depends(get_token_header)],
responses={418: {"description": "I'm a teapot"}},
)
# ### 包含一个「路径操作」可以直接将「路径操作」添加到 `FastAPI` 应用中。这样做只是为了表明FastAPI可以做到。
@app.get("/")
async def root():
return {"message": "Hello Bigger Applications!"}
技术细节:
APIRouter没有被「挂载」,它们与应用程序的其余部分没有隔离。这是因为想要在 「OpenAPI 模式」和「用户界面」中包含它们的「路径操作」。由于不能仅仅隔离它们并独立于其余部分来「挂载」它们,因此「路径操作」是被「克隆的」(重新创建),而不是直接包含。
多次使用不同的前缀(正例)
APIRouter
多次使用不同的 prefix 包含同一个路由器¶也可以在_同一_路由器上使用不同的前缀来多次使用 .include_router()。
在有些场景这可能有用,例如以不同的前缀公开同一个的 API,比方说 /api/v1 和 /api/latest。这是一个可能并不真正需要的高级用法,但万一有需要了就能够用上。
# routers/items.py
from fastapi import APIRouter
router = APIRouter()
@router.get("/items")
def get_items():
return {"items": ["apple", "banana", "cherry"]}
# main.py
from fastapi import FastAPI
from routers import items
app = FastAPI()
# 将相同的 router 以不同的前缀挂载
app.include_router(items.router, prefix="/api/v1", tags=["v1"])
app.include_router(items.router, prefix="/api/latest", tags=["latest"])
- 可以用来支持 版本兼容性:v1 是固定版本,而 latest 始终指向当前主版本。
- 方便做 API 过渡或迁移阶段 的多版本并存。
- 有利于维护 内部(/api/v1)和外部(/api/latest)文档统一性。
APIRouter彼此包含(正例)
APIRouter
在另一个 APIRouter 中包含一个 APIRouter¶
与在 FastAPI 应用程序中包含 APIRouter 的方式相同,也可以在另一个 APIRouter 中包含 APIRouter,通过:
router.include_router(other_router)
请确保在将 router 包含到 FastAPI 应用程序之前进行此操作,以便 other_router 中的路径操作也能被包含进来。
假设有两个功能模块:
sub_router:定义具体路径(如/ping)main_router:将sub_router包含进来,并设置前缀- 然后将
main_router注册到主应用中
项目结构示意:
project/
├── main.py
├── routers/
│ ├── sub_router.py
│ └── main_router.py
# routers/sub_router.py
from fastapi import APIRouter
sub_router = APIRouter()
@sub_router.get("/ping")
def ping():
return {"message": "pong"}
#routers/main_router.py
from fastapi import APIRouter
from .sub_router import sub_router
main_router = APIRouter()
main_router.include_router(sub_router, prefix="/tools") # 注意:包含进来前设置子路径
# main.py
from fastapi import FastAPI
from routers.main_router import main_router
app = FastAPI()
app.include_router(main_router, prefix="/api")
| 请求路径 | 响应 |
|---|---|
GET /api/tools/ping |
{"message": "pong"} |
✅ 重点说明
sub_router被包含进main_router,路径为/tools/pingmain_router被挂载到主应用的/api下,最终路径为/api/tools/ping- 必须在
main_router包含进应用 之前 先include_router(sub_router),否则sub_router不会生效
这个技巧非常适合做路由的多级结构分层,例如:
/api/tools/.../api/admin/.../api/user/...
从而构建更清晰的项目结构。