FastAPI最佳的实践及编码约定资料

1. 项目结构

fastapi-project
├── alembic/
├── src
│   ├── auth
│   │   ├── router.py
│   │   ├── schemas.py  # pydantic 模型
│   │   ├── models.py  # 数据库模型
│   │   ├── dependencies.py
│   │   ├── config.py  # 局部配置
│   │   ├── constants.py
│   │   ├── exceptions.py
│   │   ├── service.py
│   │   └── utils.py
│   ├── aws
│   │   ├── client.py  # 外部服务通信的客户端模型
│   │   ├── schemas.py
│   │   ├── config.py
│   │   ├── constants.py
│   │   ├── exceptions.py
│   │   └── utils.py
│   └── posts
│   │   ├── router.py
│   │   ├── schemas.py
│   │   ├── models.py
│   │   ├── dependencies.py
│   │   ├── constants.py
│   │   ├── exceptions.py
│   │   ├── service.py
│   │   └── utils.py
│   ├── config.py  # 全局配置
│   ├── models.py  # 全局模型
│   ├── exceptions.py  # 全局异常
│   ├── pagination.py  # 全局模块,例如分页
│   ├── database.py  # 数据库连接相关
│   └── main.py
├── tests/
│   ├── auth
│   ├── aws
│   └── posts
├── templates/
│   └── index.html
├── requirements
│   ├── base.txt
│   ├── dev.txt
│   └── prod.txt
├── .env
├── .gitignore
├── logging.ini
└── alembic.ini
  1. 将所有域目录存储在src文件夹 内
    • src/- 应用程序的最高级别,包含常见模型、配置和常量等。
    • src/main.py- 项目的根目录,用于初始化 FastAPI 应用程序
  2. 每个包都有自己的路由器、模式、模型等。
    • router.py- 是每个模块的核心,具有所有端点
    • schemas.py- 适用于 pydantic 模型
    • models.py- 对于数据库模型
    • service.py- 模块特定的业务逻辑
    • dependencies.py- 路由器依赖关系
    • constants.py- 模块特定常量和错误代码
    • config.py- 例如环境变量
    • utils.py- 非业务逻辑功能,例如响应规范化、数据丰富等。
    • exceptions.py- 模块特定的异常,例如PostNotFound,InvalidUserData
  3. 当包需要来自其他包的服务或依赖项或常量时 - 使用显式模块名称导入它们
from src.auth import constants as auth_constants
from src.notifications import service as notification_service
from src.posts.constants import ErrorCode as PostsErrorCode  # in case we have Standard ErrorCode in constants module of each package

2. Pydantic数据验证

Pydantic 具有一组丰富的功能来验证和转换数据。

除了具有默认值的必填和非必填字段等常规功能之外,Pydantic 还内置了全面的数据处理工具,例如正则表达式、有限允许选项的枚举、长度验证、电​​子邮件验证等。

from enum import Enum
from pydantic import AnyUrl, BaseModel, EmailStr, Field, constr

class MusicBand(str, Enum):
   AEROSMITH = "AEROSMITH"
   QUEEN = "QUEEN"
   ACDC = "AC/DC"


class UserBase(BaseModel):
    first_name: str = Field(min_length=1, max_length=128)
    username: constr(regex="^[A-Za-z0-9-_]+$", to_lower=True, strip_whitespace=True)
    email: EmailStr
    age: int = Field(ge=18, default=None)  # 必须大于或等于 18
    favorite_band: MusicBand = None  # 只允许输入“AEROSMITH”、“QUEEN”、“AC/DC”值
    website: AnyUrl = None

3. 数据验证与数据库的依赖关系

Pydantic 只能验证客户端输入的值。使用依赖项根据数据库约束(例如电子邮件已存在、用户未找到等)验证数据

# dependencies.py
async def valid_post_id(post_id: UUID4) -> Mapping:
    post = await service.get_by_id(post_id)
    if not post:
        raise PostNotFound()

    return post


# router.py
@router.get("/posts/{post_id}", response_model=PostResponse)
async def get_post_by_id(post: Mapping = Depends(valid_post_id)):
    return post


@router.put("/posts/{post_id}", response_model=PostResponse)
async def update_post(
    update_data: PostUpdate,  
    post: Mapping = Depends(valid_post_id), 
):
    updated_post: Mapping = await service.update(id=post["id"], data=update_data)
    return updated_post


@router.get("/posts/{post_id}/reviews", response_model=list[ReviewsResponse])
async def get_post_reviews(post: Mapping = Depends(valid_post_id)):
    post_reviews: list[Mapping] = await reviews_service.get_by_post_id(post["id"])
    return post_reviews

如果我们不将数据验证置于依赖项中,则必须为每个端点添加 post_id 验证,并为每个端点编写相同的测试

4. 链式依赖

依赖关系可以使用其他依赖关系,避免类似逻辑的代码重复

# dependencies.py
from fastapi.security import OAuth2PasswordBearer
from jose import JWTError, jwt

async def valid_post_id(post_id: UUID4) -> Mapping:
    post = await service.get_by_id(post_id)
    if not post:
        raise PostNotFound()

    return post


async def parse_jwt_data(
    token: str = Depends(OAuth2PasswordBearer(tokenUrl="/auth/token"))
) -> dict:
    try:
        payload = jwt.decode(token, "JWT_SECRET", algorithms=["HS256"])
    except JWTError:
        raise InvalidCredentials()

    return {
   "user_id": payload["id"]}


async def valid_owned_post(
    post: Mapping = Depends(valid_post_id), 
    token_data: dict = Depends(parse_jwt_data),
) -> Mapping:
    if post["creator_id"] != token_data["user_id"]:
        raise UserNotOwner()

    return post

# router.py
@router.get("/users/{user_id}/posts/{post_id}", response_model=PostResponse)
async def get_user_post(post: Mapping = Depends(valid_owned_post)):
    return post

5. 解耦和重用依赖关系,依赖项调用被缓存

依赖项可以多次重用,并且不会重新计算 - FastAPI 默认情况下会在请求范围内缓存依赖项的结果,即如果我们有一个调用 service 的依赖项get_post_by_id,则每次调用此依赖项时我们都不会访问数据库 -仅第一个函数调用。

知道了这一点,我们可以轻松地将依赖关系解耦到多个较小的函数,这些函数在较小的域上运行,并且更容易在其他路由中重用。例如,在下面的代码中我们使用了parse_jwt_data三次:

  1. valid_owned_post
  2. valid_active_creator
  3. get_user_post

parse_jwt_data仅在第一次调用时被调用一次。

# dependencies.py
from fastapi import BackgroundTasks
from fastapi.security import OAuth2Pa
  • 30
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

FFFPAG

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值