FastAPI 项目结构:打造模块化清晰的大型应用架构

FastAPI 项目结构:打造模块化清晰的大型应用架构


推荐阅读 📚🎓🧑‍🏫

[1] 一起学Python 专栏:深入探讨 Python 编程,涵盖基础与进阶内容,以及 NumPyPandasMatplotlibDockerLinux 等实用技术。


在构建大型应用时,保持清晰的目录结构是确保项目易于维护和扩展的关键。本文介绍了如何使用 FastAPI 组织应用代码,将不同功能模块(如路由、依赖、内部逻辑等)分开,形成模块化的项目架构。通过使用 APIRouter 组织路由,结合路径前缀、依赖注入和自定义响应,可以确保项目结构简洁明了,易于管理。此外,还提供了如何注册多个路由器、添加自定义前缀以及在不同版本之间暴露相同的接口的实践方法,帮助开发者构建高效、可扩展的 FastAPI 应用。

预备课Python Module 模块详解:模块导入与项目管理的最佳实践

以下示例中使用的 Python 版本为 Python 3.10.15,FastAPI 版本为 0.115.4

一 示例代码

from fastapi import Depends, FastAPI

from .dependencies import get_query_token, get_token_header
from .internal import admin
from .routers import items, users

app = FastAPI(dependencies=[Depends(get_query_token)])

app.include_router(users.router)
app.include_router(items.router)
app.include_router(
    admin.router,
    prefix="/admin",
    tags=["admin"],
    dependencies=[Depends(get_token_header)],
    responses={418: {"description": "I'm a teapot"}},
)


@app.get("/")
async def root():
    return {"message": "Hello Bigger Applications!"}

your-package/ 目录下,运行代码文件 main.py 来启动应用:

$ uvicorn app.main:app --reload

SwaggerUI 中可以查看在线文档:http://127.0.0.1:8000/docs

二 目录结构

your-package                       # 项目的根目录
├── app                            # 主应用目录,包含所有应用代码
│   ├── __init__.py                # 将该目录标识为 Python 包,初始化包时执行的代码(如果需要)
│   ├── main.py                    # 应用的入口文件,通常包含 FastAPI 应用实例(app)和路由配置
│   ├── dependencies.py            # 存放项目中常用的依赖,如数据库连接、认证、令牌处理等
│   └── routers                    # 存放所有路由(API 端点)的子目录
│   │   ├── __init__.py            # 将 routers 目录标识为 Python 包,初始化包时执行的代码
│   │   ├── items.py               # 处理与“物品”相关的 API 路由
│   │   └── users.py               # 处理与“用户”相关的 API 路由
│   └── internal                   # 存放应用内部逻辑的目录(例如管理后台)
│       ├── __init__.py            # 将 internal 目录标识为 Python 包,初始化包时执行的代码
│       └── admin.py               # 管理员相关的功能,可能包括权限管理、后台管理等
  • your-package:项目的根目录,包含所有应用文件。
  • app:主应用的代码目录,所有与业务相关的模块都会放在这里。
  • __init__.py:标识该目录为 Python 包,允许从该目录进行模块导入。在 Python 3.3+ 中虽然可以省略,但推荐在有多个子模块的目录中添加它,以保持包结构的清晰。
  • main.py:应用的启动文件,通常包含 FastAPIapp 实例,并导入并绑定路由。
  • dependencies.py:存放与应用相关的共享依赖项,如数据库连接、认证机制等,供各模块引用。
  • routers:存放各个路由模块的目录,通常每个文件负责不同的功能模块(如用户管理、物品管理等)。
  • internal:存放应用内部管理相关功能的模块,例如管理员控制台或后台管理接口。

APIRouter 路由器

假设 /app/routers/users.py 用于处理用户逻辑,可以使用 APIRouter 将与用户相关的路径操作与其他代码分开,保持 FastAPI 应用的结构清晰。使用 APIRouter 声明 路径操作,方式与 FastAPI 类似。

# 导入 FastAPI 的 APIRouter 用于定义路由
from fastapi import APIRouter

# 创建一个新的 APIRouter 实例,用于将路由逻辑组织到一个模块中,router 变量名可以自定义。
router = APIRouter()

# 定义一个 GET 请求的路由,访问 "/users/" 路径时返回固定的用户列表
@router.get("/users/", tags=["users"])
async def read_users():
    # 返回包含两个用户字典的列表
    return [{"username": "Rick"}, {"username": "Morty"}]

# 定义一个 GET 请求的路由,访问 "/users/me" 路径时返回当前用户的假数据
@router.get("/users/me", tags=["users"])
async def read_user_me():
    # 返回当前用户的用户名(假数据)
    return {"username": "fakecurrentuser"}

# 定义一个 GET 请求的路由,访问 "/users/{username}" 路径时返回指定用户名的用户信息
@router.get("/users/{username}", tags=["users"])
async def read_user(username: str):
    # 返回请求中提供的用户名
    return {"username": username}
1 小技巧

将共同部分(如路径前缀 /items、tags 为items、额外的响应和依赖项)统一添加到 APIRouter 中,避免重复定义。在 app/routers/items.py 中,对于 URI 路径操作 /items//items/{item_id},可以将公共部分 /items 这样统一配置:

from fastapi import APIRouter, Depends, HTTPException

from ..dependencies import get_token_header

router = APIRouter(
    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}
2 依赖项的执行顺序

每个路径操作的路径必须以 / 开头,但前缀不能以 / 结尾,因此前缀为 /items。可以为所有路径操作添加 tags 和额外的 responses。同时,dependencies 列表将应用于所有路径操作,并在每个请求中执行。依赖项的执行顺序为:先执行路由器的依赖项,再执行装饰器中的依赖项,最后执行路径操作的依赖项。

路由器 装饰器 路径操作 执行路由器的依赖项 执行装饰器中的依赖项 执行路径操作的依赖项 路由器 装饰器 路径操作
3 相对导入
# 从当前目录的 dependencies 模块导入 get_token_header
from .dependencies import get_token_header

# 从上一级目录的 dependencies 模块导入 get_token_header
from ..dependencies import get_token_header

# 从上两级目录的 dependencies 模块导入 get_token_header
from ...dependencies import get_token_header

4 自定义 tagsresponsesdependencies

可以为特定路径操作添加额外的tagsresponsesdependencies

@router.put(
    "/{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"}

这个路径操作将包含标签组合 ["items", "custom"],如下:

在这里插入图片描述

在文档中展示两个响应,一个用于 404,另一个用于 403,如下:

在这里插入图片描述

四 FastAPI 项目的 main.py

1 导入 FastAPI
from fastapi import Depends, FastAPI

from .dependencies import get_query_token, get_token_header

app = FastAPI(dependencies=[Depends(get_query_token)])
2 导入 APIRouter

相对导入

from .routers import items, users

也可以使用 绝对导入

from app.routers import items, users
3 避免名称冲突

如果像下面这样导入会产生冲突,如下:

from .routers.items import router
from .routers.users import router

导入子模块避免冲突,如下

from .routers import items, users
4 注册路由
# 将 'users' 模块中的路由注册到 FastAPI 应用中
app.include_router(users.router)

# 将 'items' 模块中的路由注册到 FastAPI 应用中
app.include_router(items.router)

app.include_router() 会为 APIRouter 中的每个路径操作自动创建相应的路径,因此所有路由像在同一应用中一样工作。注册路由仅在启动时花费几微秒,不会影响性能。

5 在注册路由时增加自定义参数

可以在不修改原始 APIRouter 的情况下添加定制化配置,且仅影响本应用中的 APIRouter

# 将 admin 路由器添加到 FastAPI 应用中,并配置自定义的参数
app.include_router(
    admin.router,  # 引入 admin 路由模块
    prefix="/admin",  # 为 admin 路由添加前缀 "/admin",所有路径都会以 "/admin" 开头
    tags=["admin"],  # 给 admin 路由添加标签 "admin",用于 OpenAPI 文档中分类
    dependencies=[Depends(get_token_header)],  # 为所有 admin 路由添加依赖项,所有请求都会先执行 get_token_header 函数
    responses={418: {"description": "I'm a teapot"}},  # 为 admin 路由添加自定义响应码 418,并设置响应描述为 "I'm a teapot"
)

FastAPI 中,APIRouter 只是一个组织路径操作的工具,它并不会被完全隔离,而是通过 include_router() 方法将其路径操作加入到主应用中。所以,APIRouter 中的路径操作最终还是会和主应用紧密结合,能够共享文档、依赖项等,而不是像独立模块一样完全自给自足。FastAPI 会重新创建这些路径操作,使其能够在应用程序中正确注册并显示在 OpenAPI 文档中。

五 使用不同前缀暴露同一路由器

在某些场景下,一个 API 可能需要通过多个前缀来公开,如 /api/v1/api/latest。下面是一个实现示例:

# 定义路由器
router = APIRouter()


@router.get("/version")
async def get_items():
    return {"version": ["version1", "version2", "version3"]}


# 通过不同的前缀包含相同的路由器
app.include_router(router, prefix="/api/v1")
app.include_router(router, prefix="/api/latest")

在这个示例中,/items 路径分别通过 /api/v1/items/api/latest/items 两个端点对外暴露。通过这种方式,可以为相同的 API 设置不同的版本或前缀。这种做法有助于版本化管理,确保旧版 API 用户不受新版本改动的影响,保证系统的向后兼容性。如下图所示:

在这里插入图片描述

六 在 APIRouter 中包含另一个 APIRouter

# 定义第一个路由器
router_v1 = APIRouter()


@router_v1.get("/items1")
async def get_items_v1():
    return {"items": ["item1", "item2", "item3"]}


# 定义第二个路由器
router_v2 = APIRouter()


@router_v2.get("/items2")
async def get_items_v2():
    return {"items": ["itemA", "itemB", "itemC"]}


# 将 router_v2 包含到 router_v1 中
router_v1.include_router(router_v2, prefix="/v2")

# 包含 router_v1 到 FastAPI 应用中
app.include_router(router_v1, prefix="/api/v1")

可以在一个 APIRouter 中包含另一个 APIRouter。在示例中,router_v1 包含了 router_v2 并为其设置了 /v2 前缀。访问 /api/v1/items 会返回版本 1 的数据,而访问 /api/v1/v2/items 会返回版本 2 的数据。为确保 router_v2 的路径操作被包含在 router_v1 中,需先通过 router_v1.include_router(router_v2) 将其包含进来,再将 router_v1 包含到 FastAPI 应用中。如下图所示:

在这里插入图片描述

七 完整代码示例

这是项目的 main.py ,全部示例见: GitHub your-package

from fastapi import Depends, FastAPI, APIRouter

from .dependencies import get_query_token, get_token_header
from .internal import admin
from .routers import items, users

app = FastAPI(dependencies=[Depends(get_query_token)])

app.include_router(users.router)
app.include_router(items.router)
app.include_router(
    admin.router,
    prefix="/admin",
    tags=["admin"],
    dependencies=[Depends(get_token_header)],
    responses={418: {"description": "I'm a teapot"}},
)


@app.get("/")
async def root():
    return {"message": "Hello Bigger Applications!"}


# 定义路由器
router = APIRouter()


@router.get("/version")
async def get_items():
    return {"version": ["version1", "version2", "version3"]}


# 通过不同的前缀包含相同的路由器
app.include_router(router, prefix="/api/v1")
app.include_router(router, prefix="/api/latest")

# 定义第一个路由器
router_v1 = APIRouter()


@router_v1.get("/items1")
async def get_items_v1():
    return {"items": ["item1", "item2", "item3"]}


# 定义第二个路由器
router_v2 = APIRouter()


@router_v2.get("/items2")
async def get_items_v2():
    return {"items": ["itemA", "itemB", "itemC"]}


# 将 router_v2 包含到 router_v1 中
router_v1.include_router(router_v2, prefix="/v2")

# 包含 router_v1 到 FastAPI 应用中
app.include_router(router_v1, prefix="/api/v1")

八 源码地址

详情见: GitHub your-package

FastAPI其他源码见:GitHub FastApiProj

九 参考

[1] FastAPI 官方文档

[2] Python Modules 官方文档

### FastAPI 项目架构与设计模式分析 FastAPI 是一个现代、快速(高性能)的 Web 框架,用于构建 API,基于 Python 类型提示。其架构设计遵循了清晰的组件布局和设计模式,使开发者能够高效地构建应用[^3]。 #### 1. 核心架构图 以下是 FastAPI 的典型架构示意图描述: - **路由管理**:FastAPI 使用路径操作装饰器(如 `@app.get()` 和 `@app.post()`)定义路由规则,这些规则将 HTTP 请求映射到相应的处理函数。 - **依赖注入系统**:通过内置的依赖注入机制,FastAPI 能够在请求处理过程中自动解析依赖项,减少代码重复。 - **数据模型**:使用 Pydantic 库定义数据模型,确保输入输出数据的类型安全性和验证。 - **中间件**:支持自定义中间件以处理跨切面功能,例如身份验证、日志记录等。 - **异常处理**:提供统一的异常处理机制,便于开发者定义全局错误响应格式。 以下为一个简化的架构图描述: ``` +-------------------+ | HTTP Request | +-------------------+ | v +-------------------+ | Middleware | <- 自定义中间件 +-------------------+ | v +-------------------+ | Router | <- 路由匹配 +-------------------+ | v +-------------------+ | Dependency Injector| <- 依赖注入 +-------------------+ | v +-------------------+ | Path Operation | <- 处理函数 +-------------------+ | v +-------------------+ | Data Validation | <- 数据验证 (Pydantic) +-------------------+ | v +-------------------+ | HTTP Response | +-------------------+ ``` #### 2. 设计模式 FastAPI架构中体现了多种设计模式,包括但不限于: - **工厂模式**:FastAPI 的依赖注入系统类似于工厂模式,它根据需求动态创建对象实例。 - **单例模式**:某些配置对象(如数据库连接池)可能在整个应用生命周期内保持单例状态。 - **责任链模式**:中间件的执行顺序遵循责任链模式,每个中间件依次处理请求或响应。 #### 3. 示例代码 以下是一个简单的 FastAPI 项目结构示例,展示其组件布局: ```python # main.py from fastapi import FastAPI, Depends from pydantic import BaseModel app = FastAPI() class Item(BaseModel): name: str description: str = None @app.post("/items/") async def create_item(item: Item): return {"item": item} # dependencies.py from typing import Annotated from fastapi import Depends def common_parameters(q: str = None): return {"q": q} @app.get("/items/") async def read_items(commons: dict = Depends(common_parameters)): return commons ``` 上述代码展示了 FastAPI 的核心组件,包括路由定义、数据模型以及依赖注入[^4]。 ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

敲代码不忘补水

感谢有你,让我的创作更有价值!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值