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

目录

  • 1. 项目结构
  • 2. Pydantic数据验证
  • 3. 数据验证与数据库的依赖关系
  • 4. 链式依赖
  • 5. 解耦和重用依赖关系,依赖项调用被缓存
  • 6. Restful API 规则
  • 7. 如果你只有阻塞 I/O 操作,不要让你的路由异步
  • 8. 从0开始创建基础模型
  • 9. 编写代码文档
  • 10. 使用 Pydantic 的 BaseSettings 进行配置
  • 11. SQLAlchemy:设置数据库键命名约定
  • 12. Alembic的数据库迁移
  • 13. 设置数据库命名约定
  • 14. 从0开始设置异步测试客户端
  • 15. BackgroundTasks处理I/O
  • 16. 以块的形式保存文件
  • 17. 小心动态 pydantic 字段 (Pydantic v1)
    • 解决方法
  • 18. SQL第一,Pydantic第二
  • 19. 如果用户可以发送公开可用的url,需要验证host
  • 20. 如果 schema 直接面向客户端,则在自定义 pydantic 验证器中引发 ValueError
  • 21. FastAPI将Pydantic对象转换为dict,然后转换为Pydantic对象,然后转换为JSON
  • 22. 如果必须使用sync SDK,需要在线程池中运行它
  • 23. 使用linters(black, ruff)

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 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 

你可能感兴趣的:(FastApi,fastapi,数据库,网络,python,django,idea)