Python新闻项目:Pydantic_Schema数据验证与序列化

一: 什么是Schema层

概念:

  • Schema层是项目中用于数据验证、序列化和反序列化的组件,通常使用 Pydantic 模型来实现。

核心作用:

  • 数据验证:确保客户端发送的数据符合要求(类型、长度、格式等)

  • 数据序列化(Serialization): 将 Python 对象(如 ORM 模型)转换为 JSON 格式(响应客户端)

  • 数据反序列化(Deserialization): 将客户端发送的 JSON 数据转换为 Python 对象

  • API文档生成: 自动生成 OpenAPI/Swagger 文档

  • 类型安全: 提供类型提示,让 IDE 智能提示

Schema层在项目中的位置:


客户端请求 (JSON)
    ↓
【Schema层】← 验证数据、转换为Python对象
    ↓
API层 (路由)
    ↓
Service层 (业务逻辑)
    ↓
CRUD层 (数据库操作)
    ↓
Models层 (ORM)
    ↓
数据库

返回响应时反向:
数据库 → Models → CRUD → Service → 【Schema层】→ 客户端 (JSON)
                                    ↑
                            序列化为JSON       
                            

二: 问题

  • Schema层的干啥的? 他的作用有哪些?
  • Schema层如何将客户请求Json类型转换为Python对象? Schema层如何将python对象转换为Json类型发送给客户端?
  • 数据验证模型和数据响应模型如何实现?
  • 单字段验证和多字段验证分别用哪两个模块?
  • 在实现数据验证模型中,加入config配置类的作用是什么?
  • 在实现数据响应模型中为什么没有Filed字段?在实现响应数据模型中为啥要加入config这个配置类?
  • config配置类的两个作用是什么?

带着以上的问题后,我们再开始写代码去实现

三: 核心代码

我们就以新闻项目中的Schema层为列子进行讲解
在这里插入图片描述

category模块


from pydantic import BaseModel, Field  # BaseModel用于模型转换,Field用于规则制定


class CategoryRequest(BaseModel):  # 定义一个名为 CategoryRequest 的类,继承自 BaseModel(Pydantic 的基类)
    name: str = Field(..., min_length=1, max_length=100) # 客户端必须传一个叫 name 的字段,必须是字符串,长度在 1-100 之间,不能为空
    sort_order: int = Field(..., ge=0) # 客户端必须传一个叫 sort_order 的字段,必须是整数,而且不能小于 0

class CategoryResponse(BaseModel): # 定义响应数据类型,用于格式化返回给客户端的数据,并配置了自动转换功能
    id: int
    name: str
    sort_order: int
    class Config:   # 配置类:允许从 SQLAlchemy ORM 对象自动转换为这个 Schema
        from_attributes = True
        
       
  • pydantic模型:数据验证库。包含BaseModel模块和Field模块。

  • BaseModel模块:pydantic的基类; 这个类就有了自动数据验证、类型转换和生成 JSON 格式文档的能力

  • Field模块: 字段验证器; 用来为 BaseModel 里的每一个字段制定详细规则。

  • 本代码分为两个部分,一个是数据验证模型,一个是响应数据模型

  • 响应数据模型中没有 Field(...) 验证规则,因为数据是从数据库来的,已经保证合法了

  • 配置类核心功能:允许从 SQLAlchemy ORM 对象自动转换为这个 Schema

favorite模块


from pydantic import BaseModel, Field


class FavoriteRequest(BaseModel): # 这个类定义了“收藏”操作时客户端必须传入的参数(新闻ID),并自动对传入数据进行验证。
    """
    收藏参数
    """
    news_id: int = Field(..., description="新闻ID")
    class Config: # 这是一个配置,允许这个类在后续操作中,能从数据库的对象(比如 FavoriteModel)自动提取数据
        from_attributes = True
        
  • class Config: from_attributes = True:这是一个配置,允许这个类在后续操作中能从数据库的对象(比如 FavoriteModel自动提取数据,但在这个请求类中,它更主要的作用是保持风格统一

history模块


from pydantic import BaseModel, Field


class FavoriteRequest(BaseModel): # FavoriteRequest 是用来验证"收藏操作"的请求数据,确保客户端必须传入一个有效的新闻ID
    """
    收藏参数
    """
    news_id: int = Field(..., description="新闻ID")
    class Config:  # config配置类;目的是保持风格统一,和后续可以直接从数据库对象中提取数据
        from_attributes = True
        

news模块


from pydantic import BaseModel, field_serializer # field_serializer是装饰器,用于字段的序列化
from typing import Optional, List  # Optional,List模块为类型注解
from datetime import datetime # 日期时间类型



# 新闻响应模型 - 用于返回单条新闻的详细信息
# 作用:格式化返回给客户端的新闻数据,包含所有字段
class NewsRespone(BaseModel):  

    id: int
    title: str
    description: Optional[str] = None
    author: str
    content: str
    image: Optional[str] = None
    category_id: int
    views: int = 0  # 浏览次数
    category_name: Optional[str] = None # 分类名称
    publish_time: Optional[datetime] = None  # 发布时间
    view_time: Optional[datetime] = None  # 浏览时间
 

    @field_serializer("publish_time") # @field_serializer是用于序列化的装饰器,里面的参数放入的是需要处理的字段
    def serialize_publish_time(self, v: Optional[datetime]) -> Optional[str]:
        return v.strftime("%Y-%m-%d %H:%M:%S") if v else None

    @field_serializer("view_time")
    def serialize_view_time(self, v: Optional[datetime]) -> Optional[str]:
        return v.strftime("%Y-%m-%d %H:%M:%S") if v else None


    class Config:
        from_attributes = True   


class NewsListResponse(BaseModel): # 新闻列表响应模型:作用是定义新闻列表响应格式
    total: int
    list: List[NewsRespone]    
    
      
  • field_serializer: 是 Python 的 Pydantic 库中一个非常实用的装饰器自定义单个字段的序列化行为
  • 三元运算符: return v.strftime(“%Y-%m-%d %H:%M:%S”) if v else None
# 完整的 if-else 写法
if v:
    # 如果 v 不是 None(有值)
    return v.strftime("%Y-%m-%d %H:%M:%S")
else:
    # 如果 v 是 None
    return None
  • config配置类的作用: 允许从 ORM 模型(SQLAlchemy)自动转换; 将数据库查询结果自动映射到当前 Schema

user模块


from typing import Optional

from pydantic import BaseModel, Field, model_validator # model_validator多字段验证模块


class UserRegisterRequest(BaseModel):  # 数据响应模型
    username:str= Field(..., min_length=3, max_length=20, description="用户名")
    password: str= Field(..., min_length=8, max_length=20, description="密码")
    password_confirm: str= Field(..., min_length=8, max_length=20, description="确认密码")

    @model_validator(mode="after") # after为模型创建之后
    def check_passwords_match(self):
        if self.password != self.password_confirm:
            raise ValueError("两次密码不一致")
        return self

    class Config:
        from_attributes = True


class UserLogin(BaseModel):
    username: str = Field(..., description="用户名")
    password: str = Field(..., description="密码")
    class Config:
        from_attributes = True

class UserResponse(BaseModel):
    id: int
    username: str
    nickname: Optional[str] = None
    avatar: Optional[str] = None
    gender: Optional[str] = None
    bio: Optional[str] = None
    phone: Optional[str] = None

    class Config:
        from_attributes = True

class UserRequest(BaseModel):
    username: str
    nickname: Optional[str] = None
    avatar: Optional[str] = None
    gender: Optional[str] = None
    bio: Optional[str] = None
    phone: Optional[str] = None
    
    @model_validator(mode="before")
    def empty_str_to_none(cls, data):
        if isinstance(data, dict):
            for key, value in data.items():
                if value == "":
                    data[key] = None
        return data
    class Config:
        from_attributes = True

class UserPasswordRequest(BaseModel):
    old_password: str = Field(..., min_length=8, max_length=20, description="密码")
    new_password: str = Field(..., min_length=8, max_length=20, description="新密码")
    password_confirm: str = Field(..., min_length=8, max_length=20, description="确认密码")

    @model_validator(mode="after")
    def check_passwords_match(self):
        if self.new_password != self.password_confirm:
            raise ValueError("两次密码不一致")
        return self

    class Config:
        from_attributes = True


class RefreshTokenRequest(BaseModel):
    refresh_token: str = Field(..., description="刷新令牌")

    class  Config:
        from_attributes = True
        
  • 简单来说,@field_validator 适合单字段的精确校验,而 @model_validator 则用于处理需要多个字段配合的复杂校验逻辑 。它在创建或更新一个模型对象时被调用,确保最终的数据在业务逻辑上是完整、有效且符合预期的。

  • 多字段序列化代码

   
  # 这段代码的作用是**验证用户两次输入的密码是否一致**,如果不一致则抛出错误,防止用户因输错密码导致注册失败。
  @model_validator(mode="after") # after为模型创建之后
    def check_passwords_match(self): 
        if self.password != self.password_confirm:
            raise ValueError("两次密码不一致")
        return self

   # 将字典中所有值为空字符串 "" 的字段,自动转换为 None。
   @model_validator(mode="before") # before为模型创建之前
    def empty_str_to_none(cls, data):
        if isinstance(data, dict):
            for key, value in data.items():
                if value == "":
                    data[key] = None
        return data
     
    
   # 验证用户输入的"新密码"和"确认密码"是否一致,不一致则报错。 一般用于用户修改密码的场景
    @model_validator(mode="after") 
    def check_passwords_match(self):
        if self.new_password != self.password_confirm:
            raise ValueError("两次密码不一致")
        return self     
      
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值