FastAPI:(12)更大的应用(APIRouter)


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.pyapp.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 可以用来,例如,对一整组的_路径操作_要求身份认证。即使这些依赖项并没有分别添加到每个路径操作中。

prefixtagsresponses 以及 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/ping
  • main_router 被挂载到主应用的 /api 下,最终路径为 /api/tools/ping
  • 必须在 main_router 包含进应用 之前include_router(sub_router),否则 sub_router 不会生效

这个技巧非常适合做路由的多级结构分层,例如:

  • /api/tools/...
  • /api/admin/...
  • /api/user/...
    从而构建更清晰的项目结构。

文章作者: Hkini
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Hkini !
评论
  目录