Python FastAPI后端开发规范

Python3.8

Python 是一种高级、解释型、通用的编程语言,以其简洁易读的语法而闻名,适用于广泛的应用,包括Web开发、数据分析、人工智能和自动化脚本

结构介绍

所有内容放在app目录下

  • utils: 通用资源或变量
  • api: 所有的接口
  • crud: 数据的增删改查具体操作
  • db: 数据库初始化以及数据表结构和关系
  • models: 定义数据库模型,如绑定数据库的表结构等
  • schemas: 为数据校验定义请求与响应数据
    • request: 基础请求数据模型(Pydantic模型),定义API接口的原始请求参数
    • response: 基础响应数据模型(Pydantic模型),定义API接口的原始响应数据
    • full_request: 完整请求包装模型,包含用户信息、业务线、参数等元数据,用于统一请求格式
    • full_response: 完整响应包装模型,包含状态码、消息、数据等统一响应格式
  • tests: 自测代码
  • main.py: 主入口

模型文件位置规范

数据库模型 (SQLModel)
  • 位置: app/models/ 目录
  • 用途: 定义数据库表结构,使用SQLModel
  • 示例: app/models/user_model.py
# app/models/user_model.py
from sqlmodel import SQLModel, Field
from datetime import datetime

class User(SQLModel, table=True):
    user_id: str = Field(primary_key=True, description="用户ID")
    user_name: str = Field(..., description="用户名")
    # ... 其他字段
Pydantic模型 (请求/响应)
  • 请求模型位置: app/schemas/request/ 目录
  • 响应模型位置: app/schemas/response/ 目录
  • 完整包装模型位置: app/schemas/full_request/app/schemas/full_response/ 目录
  • 用途: 定义API接口的请求和响应数据结构
# app/schemas/request/user_request.py - 请求模型
class UserCreateRequest(BaseModel):
    user_name: str = Field(..., min_length=1, max_length=50)
    user_id: str = Field(..., min_length=1, max_length=20)
    # ... 其他字段

# app/schemas/response/user_response.py - 响应模型
class UserResponse(BaseModel):
    user_id: str
    user_name: str
    # ... 其他字段

# app/schemas/full_request/user_full_request.py - 完整请求包装
class UserFullCreateRequest(BaseModel):
    user: dict[str, str] | None = None
    business: str | None = None
    param: str | None = None
    data: dict[str, str] | None = None

开发规范

1. 命名规范

1.1 类命名
  • 使用 PascalCase(首字母大写)
  • 类名应该是名词,具有描述性
# ✅ 正确
class UserService:
class DatabaseConnection:
class TestCalculator:

# ❌ 错误
class userService:
class database_connection:
1.2 常量命名
  • 使用 全大写字母,单词间用下划线分隔
# ✅ 正确
MAX_RETRY_COUNT = 3
DEFAULT_TIMEOUT = 30
API_BASE_URL = "https://api.example.com"

# ❌ 错误
maxRetryCount = 3
default_timeout = 30
1.3 变量和函数命名
  • 使用 snake_case(全小写,单词间用下划线分隔)
  • 变量名应该是名词,函数名应该是动词
# ✅ 正确
user_name = "张三"
def get_user_by_id(user_id: str):
def calculate_test_result():
def validate_input_data():

# ❌ 错误
userName = "张三"
def getUserById(userId: str):
def CalculateTestResult():
1.4 模块和包命名
  • 使用 snake_case
  • 避免使用下划线开头(除非是私有模块)
# ✅ 正确
user_services.py
test_calculator.py
database_client.py

# ❌ 错误
userServices.py
TestCalculator.py
database-client.py

2. 类型注解规范

2.1 Python 3.10+ 类型注解语法
  • 优先使用Python 3.10+的内置类型注解语法
  • 复杂类型仍使用 typing 模块
# ✅ Python 3.10+ 推荐写法
def get_user_by_id(user_id: str) -> User | None:
    pass

def get_users_by_role(role: str, limit: int | None = None) -> list[User]:
    pass

def process_user_data(user_data: dict[str, str]) -> User:
    pass

def handle_multiple_types(data: str | bytes | dict[str, str]) -> str:
    pass

# ✅ 复杂类型仍使用typing模块
from typing import TypeVar, Callable, Union

T = TypeVar('T')
def generic_function(data: list[T], processor: Callable[[T], str]) -> list[str]:
    pass

# ❌ 旧版本写法(不推荐)
from typing import Optional, List, Dict
def old_style_function(user_id: str) -> Optional[User]:
    pass
def old_style_list_function() -> List[User]:
    pass
2.2 类型注解最佳实践
  • 使用 str | None 替代 Optional[str]Union[str, None]
  • 使用 list[str] 替代 List[str]
  • 使用 dict[str, int] 替代 Dict[str, int]
  • 使用 tuple[int, str] 替代 Tuple[int, str]
  • 使用 set[str] 替代 Set[str]
  • 复杂泛型仍使用 typing 模块
2.3 Pydantic模型类型注解
2.3.1 BaseModel 和 Field 说明
  • BaseModel: Pydantic的基础模型类,提供数据验证、序列化、文档生成等功能
  • Field: 用于定义字段的详细配置,包括验证规则、默认值、描述等
2.3.2 Field 参数说明
  • ...: 表示必填字段,不能为空
  • default: 设置默认值
  • default_factory: 设置默认值工厂函数,用于复杂类型(如list、dict)
  • min_length/max_length: 字符串长度限制
  • min/max: 数值范围限制
  • description: 字段描述,用于API文档
  • alias: 字段别名,用于JSON序列化
  • exclude: 是否在序列化时排除该字段
# app/schemas/request/user_request.py - 请求模型
from pydantic import BaseModel, Field

class UserCreateRequest(BaseModel):
    user_name: str = Field(
        ...,  # 必填字段
        min_length=1, 
        max_length=50,
        description="用户名,长度1-50个字符"
    )
    user_id: str = Field(
        ..., 
        min_length=1, 
        max_length=20,
        description="用户ID,长度1-20个字符"
    )
    role: str = Field(..., description="用户角色")
    business: str = Field(..., description="业务线")
    staff_id: str | None = Field(None, description="员工ID,可选")  # 使用 | None 替代 Optional
    tags: list[str] = Field(default_factory=list, description="用户标签列表")  # 使用 list[str]
    metadata: dict[str, str] = Field(default_factory=dict, description="元数据")  # 使用 dict[str, str]

# app/schemas/response/user_response.py - 响应模型
from datetime import datetime

class UserResponse(BaseModel):
    user_id: str = Field(..., description="用户ID")
    user_name: str = Field(..., description="用户名")
    role: str = Field(..., description="用户角色")
    business: str = Field(..., description="业务线")
    staff_id: str | None = Field(None, description="员工ID")
    created_at: datetime | None = Field(None, description="创建时间")
2.4 数据库模型类型注解
2.4.1 SQLModel 和 Field 说明
  • SQLModel: 结合了SQLAlchemy和Pydantic的模型类,既支持数据库操作又支持数据验证
  • Field: 用于定义数据库字段的详细配置,包括主键、索引、约束等
2.4.2 SQLModel Field 参数说明
  • primary_key=True: 设置主键
  • index=True: 创建数据库索引
  • unique=True: 设置唯一约束
  • nullable=False: 设置非空约束
  • default: 设置默认值
  • default_factory: 设置默认值工厂函数
  • description: 字段描述
  • table=True: 表示该模型对应数据库表
# app/models/user_model.py - 数据库模型
from sqlmodel import SQLModel, Field
from datetime import datetime

class User(SQLModel, table=True):
    user_id: str = Field(primary_key=True, description="用户ID,主键")
    user_name: str = Field(..., index=True, description="用户名,创建索引")
    role: str = Field(..., description="用户角色")
    business: str = Field(..., description="业务线")
    staff_id: str | None = Field(default=None, unique=True, description="员工ID,唯一")  # 使用 | None
    created_at: datetime = Field(default_factory=datetime.now, description="创建时间")
    updated_at: datetime = Field(default_factory=datetime.now, description="更新时间")
    permissions: list[str] = Field(default_factory=list, description="权限列表")  # 使用 list[str]

3. 代码注释规范

3.1 文件头部注释

每个Python文件必须在开头包含utf-8注释,其他可自定义,最好有模块描述:

# -*- coding: UTF-8 -*-
# @File  : 文件名.py
# @Author: 作者姓名 工号
# @Desc : 文件功能描述
# @Date  : 创建日期 YYYY/MM/DD
3.2 函数注释

所有函数必须包含详细的文档字符串,说明功能、参数和返回值:

def get_user_by_id(user_id: str, session: Session) -> User | None:
    """
    根据用户ID获取用户信息
  
    Args:
        user_id (str): 用户ID
        session (Session): 数据库会话对象
    
    Returns:
        User | None: 用户对象,如果不存在则返回None
    
    Raises:
        ValueError: 当user_id为空时抛出
        DatabaseError: 数据库连接错误时抛出
    """
    if not user_id:
        raise ValueError("用户ID不能为空")
  
    return session.query(User).filter(User.user_id == user_id).first()

def get_users_by_filters(
    role: str | None = None,
    business: str | None = None,
    limit: int = 100
) -> list[User]:
    """
    根据过滤条件获取用户列表
  
    Args:
        role (str | None): 用户角色过滤条件
        business (str | None): 业务线过滤条件
        limit (int): 返回结果数量限制,默认100
    
    Returns:
        list[User]: 用户列表
    
    Raises:
        ValueError: 当limit小于0时抛出
    """
    if limit < 0:
        raise ValueError("limit不能为负数")
  
    # 实现逻辑
    pass
3.3 类注释

所有类必须包含类级别的文档字符串:

class UserService:
    """
    用户服务类,提供用户相关的业务逻辑操作
  
    主要功能:
    - 用户创建、更新、删除
    - 用户信息查询
    - 用户权限验证
    """
  
    def __init__(self, session: Session):
        """
        初始化用户服务
  
        Args:
            session (Session): 数据库会话对象
        """
        self.session = session

4. FastAPI 接口开发规范

4.1 路由定义
  • 使用有意义的URL路径
  • 使用适当的HTTP方法
  • 添加标签分组(tags)用于API文档分类
  • 注意: 一般路由可以设置prefix前缀,比如v1这种版本信息,用于统一的路由管理,所以prefix最好不在各个模块中定义,最好能够统一管理
4.1.1 标签分组说明
  • 作用: tags用于在Swagger/OpenAPI文档中对接口进行分组
  • 好处:
    • 提高API文档的可读性和组织性
    • 便于前端开发人员快速找到相关接口
    • 便于接口管理和维护
  • 命名规范: 使用复数形式,如 ["users"], ["tests"], ["calculations"]
4.1.2 路由定义示例
# app/api/v1/user_routers.py
from fastapi import APIRouter

# 注意:不在这里定义prefix,prefix在app/api/routers.py中统一管理
user_router = APIRouter(tags=["users"])

@user_router.post("/create_user",
                  response_model=SuccessResponse[UserResponse],
                  responses={
                      422: {"model": FailResponse, "description": "参数错误"},
                      500: {"model": FailResponse, "description": "服务器内部错误"}
                  })
async def create_user(request: UserFullCreateRequest):
    """
    创建新用户
  
    Args:
        request (UserFullCreateRequest): 用户创建请求
      
    Returns:
        SuccessResponse[UserResponse]: 创建成功的用户信息
    """
    pass

@user_router.get("/get_user_by_id",
                 response_model=SuccessResponse[UserResponse],
                 tags=["users"])  # 可以在这里再次指定tags,但通常不需要
async def get_user_by_id(user_id: str):
    """
    根据ID获取用户信息
    """
    pass
4.1.3 统一路由管理
# app/api/routers.py - 统一管理所有路由的prefix
from fastapi import APIRouter
from app.api.v1 import user_routers, test_api, acc_routes

# 定义统一的API版本和前缀
API_VERSION = "/v1"
PREFIX = "/api"

# 在这里统一定义prefix
api_router = APIRouter()

# 用户相关接口
api_router.include_router(
    user_routers.user_router,
    prefix=f"{PREFIX}{API_VERSION}/users",
    tags=["users"]
)

# 测试相关接口
api_router.include_router(
    test_api.test_router,
    prefix=f"{PREFIX}{API_VERSION}/tests",
    tags=["tests"]
)

# 计算相关接口
api_router.include_router(
    acc_routes.acc_router,
    prefix=f"{PREFIX}{API_VERSION}/calculations",
    tags=["calculations"]
)
4.2 数据校验规范
  • 所有请求和响应必须使用Pydantic模型
  • 使用类型注解确保类型安全
  • 添加适当的验证器
4.2.1 validator 验证器说明
  • @validator: 用于自定义字段验证逻辑
  • 参数: 字段名,可以是单个字段或多个字段
  • 返回值: 验证后的值,可以修改输入值
  • 异常: 验证失败时抛出 ValueError
4.2.2 class Config 配置说明
  • extra: 控制额外字段的处理方式
    • "ignore": 忽略多余的字段
    • "allow": 允许多余的字段
    • "forbid": 禁止多余的字段
  • schema_extra: 为API文档提供示例数据
  • validate_assignment: 是否在赋值时进行验证
  • json_encoders: 自定义JSON编码器
# app/schemas/request/user_request.py
from pydantic import BaseModel, Field, validator

class UserCreateRequest(BaseModel):
    user_name: str = Field(..., min_length=1, max_length=50, description="用户名")
    user_id: str = Field(..., min_length=1, max_length=20, description="用户ID")
    role: str = Field(..., description="用户角色")
    business: str = Field(..., description="业务线")
    staff_id: str | None = Field(None, description="员工ID")
  
    @validator('user_name')
    def validate_user_name(cls, v):
        """验证用户名,去除首尾空格"""
        if not v.strip():
            raise ValueError('用户名不能为空')
        return v.strip()
    
    @validator('user_id')
    def validate_user_id(cls, v):
        """验证用户ID格式"""
        if not v.isalnum():
            raise ValueError('用户ID只能包含字母和数字')
        return v.lower()
    
    @validator('role')
    def validate_role(cls, v):
        """验证用户角色"""
        allowed_roles = ['admin', 'developer', 'tester', 'manager']
        if v not in allowed_roles:
            raise ValueError(f'角色必须是以下之一: {", ".join(allowed_roles)}')
        return v
  
    class Config:
        extra = "ignore"  # 忽略多余的字段
        validate_assignment = True  # 赋值时进行验证
        schema_extra = {
            "example": {
                "user_name": "张三",
                "user_id": "zhangsan001",
                "role": "developer",
                "business": "AI",
                "staff_id": "EMP001"
            }
        }
4.3 错误处理
  • 使用统一的响应格式
  • 提供详细的错误信息
  • 记录错误日志
from app.schemas.response.full_response import SuccessResponse, FailResponse
from app.utils.log.logger import Logger

logger = Logger.getInstance()

@user_router.post("/create_user")
async def create_user(request: UserFullCreateRequest):
    try:
        # 业务逻辑
        user = user_service.create_user(request)
        return SuccessResponse(data=UserResponse(**user.dict()), message="用户创建成功!")
    except ValueError as e:
        logger.warning(f"参数验证失败: {str(e)}")
        return FailResponse(message=f"参数错误: {str(e)}")
    except Exception as e:
        error_msg = traceback.format_exc()
        logger.error(f"创建用户失败: \n{error_msg}")
        return FailResponse(message=f"创建用户失败: {str(e)}")

5. 数据库操作规范

5.1 模型定义
  • 使用SQLModel进行模型定义
  • 添加适当的字段约束
  • 使用类型注解
# app/models/user_model.py
from sqlmodel import SQLModel, Field
from datetime import datetime

class User(SQLModel, table=True):
    user_id: str = Field(primary_key=True, description="用户ID")
    user_name: str = Field(..., description="用户名")
    role: str = Field(..., description="用户角色")
    department: str = Field(..., description="部门")
    staff_id: str | None = Field(default=None, description="工号")
    created_at: datetime = Field(default_factory=datetime.now, description="创建时间")
    updated_at: datetime = Field(default_factory=datetime.now, description="更新时间")
5.2 CRUD操作
  • 使用依赖注入获取数据库会话
  • 添加适当的异常处理
  • 使用事务确保数据一致性
# app/api/v1/user_routers.py - 依赖注入
def get_user_service(session: Session = Depends(lambda: next(get_session()))):
    return UserService(session=session)

# app/services/user_services.py - 业务逻辑
class UserService:
    def __init__(self, session: Session):
        self.session = session
  
    def create_user(self, user_data: UserCreateRequest) -> User:
        """
        创建用户
    
        Args:
            user_data (UserCreateRequest): 用户数据
        
        Returns:
            User: 创建的用户对象
        """
        try:
            user = User(**user_data.dict())
            self.session.add(user)
            self.session.commit()
            self.session.refresh(user)
            return user
        except Exception as e:
            self.session.rollback()
            raise e

6. 日志规范

6.1 日志级别使用
  • DEBUG: 调试信息,开发时使用
  • INFO: 一般信息,如请求开始、结束
  • WARNING: 警告信息,如参数验证失败
  • ERROR: 错误信息,如异常捕获
  • CRITICAL: 严重错误,如系统崩溃
6.2 日志格式
logger.info(f"开始处理用户请求: user_id={user_id}")
logger.warning(f"用户输入参数异常: {param_name}={param_value}")
logger.error(f"数据库操作失败: {str(e)}", exc_info=True)

7. 测试规范

7.1 单元测试
  • 每个函数都应该有对应的单元测试
  • 测试覆盖率不低于80%
  • 使用描述性的测试方法名
import pytest
from app.services.user_services import UserService

class TestUserService:
    def test_create_user_success(self):
        """测试成功创建用户"""
        # 测试代码
  
    def test_create_user_with_invalid_data(self):
        """测试使用无效数据创建用户"""
        # 测试代码

8. 性能规范

8.1 数据库查询优化
8.1.1 SQLModel vs 自定义MySQL客户端
  • SQLModel: 适用于ORM操作,提供类型安全和自动验证
    • 适合:简单的CRUD操作、关系查询、数据验证
    • 示例:用户管理、基础数据操作
  • 自定义MySQL客户端: 适用于复杂查询和性能优化
    • 适合:复杂SQL、批量操作、性能敏感的场景
    • 示例:报表查询、数据分析、批量导入
8.1.2 适当的索引
  • 主键索引: 自动创建,用于唯一标识
  • 唯一索引: 确保字段值唯一性
  • 普通索引: 加速查询,特别是WHERE条件
  • 复合索引: 多字段组合索引,注意字段顺序
  • 覆盖索引: 包含查询所需的所有字段
# 示例:创建适当的索引
class User(SQLModel, table=True):
    user_id: str = Field(primary_key=True)  # 主键索引
    user_name: str = Field(..., index=True)  # 普通索引
    email: str = Field(..., unique=True)  # 唯一索引
    business: str = Field(..., index=True)  # 普通索引
    created_at: datetime = Field(default_factory=datetime.now, index=True)  # 时间索引
8.1.3 N+1查询问题
  • 问题: 查询主表后,再逐个查询关联表,导致大量数据库查询
  • 解决方案: 使用JOIN或批量查询
# ❌ N+1查询问题
users = session.query(User).all()
for user in users:
    user_details = session.query(UserDetail).filter(UserDetail.user_id == user.user_id).first()

# ✅ 使用JOIN解决
users_with_details = session.query(User, UserDetail).join(UserDetail).all()
8.1.4 分页查询
  • 作用: 避免一次性加载大量数据,提高性能
  • 实现: 使用LIMIT和OFFSET
def get_users_paginated(page: int = 1, page_size: int = 20) -> tuple[list[User], int]:
    """
    分页查询用户
    
    Args:
        page: 页码,从1开始
        page_size: 每页数量
        
    Returns:
        (用户列表, 总数量)
    """
    offset = (page - 1) * page_size
    
    # 查询数据
    users = session.query(User).offset(offset).limit(page_size).all()
    
    # 查询总数
    total = session.query(User).count()
    
    return users, total
8.2 缓存使用
  • 合理使用Redis缓存
  • 设置适当的缓存过期时间
  • 避免缓存穿透和雪崩
8.2.1 缓存穿透
  • 问题: 查询不存在的数据,导致每次请求都访问数据库
  • 解决方案:
    • 缓存空值(设置较短的过期时间)
    • 使用布隆过滤器
    • 参数验证
def get_user_by_id(user_id: str) -> User | None:
    """获取用户信息,使用缓存防止穿透"""
    cache_key = f"user:{user_id}"
    
    # 先从缓存获取
    cached_user = redis_client.get(cache_key)
    if cached_user is not None:
        if cached_user == "null":  # 缓存空值
            return None
        return User.parse_raw(cached_user)
    
    # 查询数据库
    user = session.query(User).filter(User.user_id == user_id).first()
    
    if user is None:
        # 缓存空值,防止穿透
        redis_client.setex(cache_key, 300, "null")  # 5分钟过期
        return None
    
    # 缓存用户数据
    redis_client.setex(cache_key, 3600, user.json())  # 1小时过期
    return user
8.2.2 缓存雪崩
  • 问题: 大量缓存同时过期,导致数据库压力激增
  • 解决方案:
    • 设置随机过期时间
    • 使用缓存预热
    • 实现缓存更新机制
import random

def get_user_by_id_with_avalanche_protection(user_id: str) -> User | None:
    """获取用户信息,防止缓存雪崩"""
    cache_key = f"user:{user_id}"
    
    # 先从缓存获取
    cached_user = redis_client.get(cache_key)
    if cached_user is not None:
        if cached_user == "null":
            return None
        return User.parse_raw(cached_user)
    
    # 查询数据库
    user = session.query(User).filter(User.user_id == user_id).first()
    
    if user is None:
        # 随机过期时间,防止雪崩
        expire_time = 300 + random.randint(0, 60)  # 5-6分钟
        redis_client.setex(cache_key, expire_time, "null")
        return None
    
    # 随机过期时间
    expire_time = 3600 + random.randint(0, 300)  # 1小时-1小时5分钟
    redis_client.setex(cache_key, expire_time, user.json())
    return user

您可能感兴趣的与本文相关的镜像

Python3.8

Python3.8

Conda
Python

Python 是一种高级、解释型、通用的编程语言,以其简洁易读的语法而闻名,适用于广泛的应用,包括Web开发、数据分析、人工智能和自动化脚本

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值