一: 什么是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

856

被折叠的 条评论
为什么被折叠?



