这里写目录标题
- 前言
- 一、阶段学习目标
- 二、Field 双端高级配置(数据库约束 + Pydantic校验)
- 2.1 Field 参数两大分类
- 2.2 default 与 default_factory 核心区别(高频踩坑点)
- 2.3 高级数据库约束:联合索引、自定义列类型
- 三、复用Pydantic专用校验类型(开箱即用)
- 3.1 邮箱、链接、UUID、IP
- 3.2 Enum 枚举状态约束(角色、订单状态)
- 四、标准分层DTO模型(企业开发规范,隔离数据库与接口)
- 4.1 四层分层设计思路
- 4.2 完整用户分层实战代码
- 4.3 DTO转换与脱敏示例
- 五、复用Pydantic自定义校验 @field_validator
- 5.1 用户名自动去空格、小写清洗
- 5.2 密码复杂度自定义校验(大写+小写+数字)
- 5.3 手机号正则校验
- 六、序列化精细化控制(敏感字段隐藏)
- 6.1 Field(exclude=True) 永久不序列化
- 6.2 model_dump 动态过滤参数
- 七、阶段综合完整可运行案例
- 八、阶段核心总结(半天必掌握)
- 九、新手高频避坑指南
前言
上一篇我们学习了SQLModel基础概念、Engine、Session与单表CRUD,掌握了最基础的表定义与数据库操作。但真实业务里,单纯基础字段完全无法满足需求:
- 字符串长度、数字区间、邮箱/URL格式需要强校验;
- 枚举状态、联合索引、UUID主键、自动时间戳需要高级字段配置;
- 新增、更新、接口返回需要分层模型,隔离敏感字段;
- 自定义密码、手机号等复杂业务校验逻辑。
SQLModel底层完全融合Pydantic v2,所有Pydantic校验能力可直接复用,不用两套模型重复写校验规则。
一、阶段学习目标
- 精通
Field双端配置:数据库列参数 + Pydantic校验参数; - 掌握默认值区分:
default静态值 /default_factory动态生成(时间、UUID、列表); - 学会高级数据库约束:联合索引、自定义字段类型、非空/唯一;
- 复用Pydantic专用类型:
EmailStr、HttpUrl、UUID、枚举Enum; - 标准分层DTO设计:Base基础模型 / Create创建模型 / Update更新模型 / Public返回模型;
- 在SQLModel中使用
@field_validator自定义业务校验; - 掌握序列化脱敏、隐藏敏感字段(密码不返回前端)。
二、Field 双端高级配置(数据库约束 + Pydantic校验)
2.1 Field 参数两大分类
SQLModel的Field一套参数同时作用两层:
- SQL层参数:控制数据库表结构
primary_key、index、unique、nullable、sa_column、sa_type - Pydantic校验参数:入参自动校验(和Pydantic完全通用)
min_length、max_length、gt、ge、lt、le、pattern、description
2.2 default 与 default_factory 核心区别(高频踩坑点)
default=xxx:静态常量,模块加载时只计算一次,禁止列表、字典、时间、UUID;default_factory=函数:每次实例化动态执行,可变类型、动态值必用。
示例:
fromsqlmodelimportSQLModel,FieldfromdatetimeimportdatetimeimportuuidclassUser(SQLModel,table=True):id:uuid.UUID=Field(default_factory=uuid.uuid4,primary_key,description="UUID主键")# 静态默认,固定布尔值is_active:bool=Field(default=True,nullable=False)# 动态生成创建时间,每次新建自动赋值当前UTC时间create_time:datetime=Field(default_factory=datetime.utcnow)# 错误写法:default=datetime.utcnow() 全局只会生成同一个时间2.3 高级数据库约束:联合索引、自定义列类型
单字段索引直接index=True,多字段联合索引使用__table_args__定义索引元组:
fromsqlmodelimportSQLModel,FieldfromsqlalchemyimportIndexclassUser(SQLModel,table=True):__tablename__="sys_user"# 联合索引:用户名+邮箱联合唯一__table_args__=(Index("idx_username_email","username","email",unique=True),)id:int|None=Field(default=None,primary_key=True)username:str=Field(max_length=32,nullable=False)email:str=Field(max_length=128,nullable=False)age:int|None=Field(default=None,ge=0,le=120)自定义数据库字段类型(长文本、大整数):
fromsqlalchemyimportText,BigIntegerclassArticle(SQLModel,table=True):id:int|None=Field(default=None,primary_key,sa_type=BigInteger)content:str=Field(sa_column=Text(),description="文章长文本内容")三、复用Pydantic专用校验类型(开箱即用)
SQLModel可直接导入使用Pydantic全部专用校验类型,无需手写正则,自动拦截非法格式。
3.1 邮箱、链接、UUID、IP
fromsqlmodelimportSQLModel,FieldfrompydanticimportEmailStr,HttpUrl,UUID4,IPvAnyAddressimportuuidclassUser(SQLModel,table=True):id:UUID4=Field(default_factory=uuid.uuid4,primary_key=True)# 自动校验合法邮箱email:EmailStr=Field(unique=True,index=True,max_length=128)# 头像合法URL校验avatar:HttpUrl|None=Field(default=None)# 登录IP自动校验IPv4/IPv6login_ip:IPvAnyAddress|None=Field(default=None)3.2 Enum 枚举状态约束(角色、订单状态)
使用str枚举,配合SQLEnum数据库原生枚举约束,同时Pydantic自动校验传值范围:
fromenumimportEnumfromsqlmodelimportSQLModel,FieldfromsqlalchemyimportSQLEnumclassUserRole(str,Enum):ADMIN="admin"USER="user"GUEST="guest"classUser(SQLModel,table=True):id:int|None=Field(default=None,primary_key=True)username:str=Field(min_length=3,max_length=16)# 数据库枚举 + Pydantic值范围校验role:UserRole=Field(default=UserRole.GUEST,sa_column=SQLEnum(UserRole,name="user_role_enum"))测试非法值会直接抛出ValidationError,无需手动if判断。
四、标准分层DTO模型(企业开发规范,隔离数据库与接口)
4.1 四层分层设计思路
XxxBase:基础公共模型,存放所有通用字段,统一校验规则;XxxCreate:新增接口入参模型,不含主键、自动生成字段;XxxUpdate:更新接口模型,所有字段全部可选,支持局部修改;XxxPublic:接口返回模型,剔除密码等敏感字段,仅对外暴露安全数据;Xxx(table=True):数据库实体,继承Base,增加主键、数据库专属字段。
4.2 完整用户分层实战代码
fromsqlmodelimportSQLModel,FieldfrompydanticimportEmailStrfromtypingimportOptionalimportuuid# 1. 基础公共模型(统一校验规则)classUserBase(SQLModel):username:str=Field(min_length=3,max_length=16,description="用户名3-16位")email:EmailStr=Field(max_length=128)age:Optional[int]=Field(default=None,ge=0,le=120)# 2. 创建入参模型:继承Base,新增密码字段classUserCreate(UserBase):password:str=Field(min_length=8,max_length=20,description="密码8-20位")# 3. 更新入参模型:全部字段可选,支持局部更新classUserUpdate(SQLModel):username:Optional[str]=Field(None,min_length=3,max_length=16)email:Optional[EmailStr]=Noneage:Optional[int]=Field(None,ge=0,le=120)# 4. 返回脱敏模型:隐藏密码,携带主键classUserPublic(UserBase):id:uuid.UUID# 5. 数据库实体:继承Base,映射数据表classUser(UserBase,SQLModel,table=True):id:uuid.UUID=Field(default_factory=uuid.uuid4,primary_key=True)password:str=Field(max_length=100,description="加密密码,不对外返回")create_time:str=Field(default_factory=lambda:str(uuid.uuid4()))4.3 DTO转换与脱敏示例
# 数据库实体转返回DTO,自动剔除password敏感字段db_user=User(username="test",email="test@qq.com",password="Abc123456")resp=UserPublic.model_validate(db_user)print(resp.model_dump())# 输出无password,安全返回前端五、复用Pydantic自定义校验 @field_validator
SQLModel完全兼容Pydantic v2校验装饰器,单字段清洗、复杂业务规则直接复用,数据库实体与DTO模型均可使用。
5.1 用户名自动去空格、小写清洗
fromsqlmodelimportSQLModel,Fieldfrompydanticimportfield_validatorclassUserCreate(SQLModel):username:str=Field(min_length=3,max_length=16)email:str@field_validator("username",mode="before")defclean_username(cls,v):ifisinstance(v,str):returnv.strip().lower()returnv5.2 密码复杂度自定义校验(大写+小写+数字)
importrefromsqlmodelimportSQLModel,Fieldfrompydanticimportfield_validator,ValidationErrorclassUserCreate(SQLModel):password:str=Field(min_length=8,max_length=20)@field_validator("password",mode="after")defcheck_pwd_rule(cls,v):ifnotre.search(r"[A-Z]",v):raiseValueError("密码必须包含大写字母")ifnotre.search(r"[a-z]",v):raiseValueError("密码必须包含小写字母")ifnotre.search(r"\d",v):raiseValueError("密码必须包含数字")returnv# 测试校验try:UserCreate(password="123456")exceptValidationErrorase:print(e.errors()[0]["msg"])5.3 手机号正则校验
fromsqlmodelimportSQLModel,Fieldfrompydanticimportfield_validatorimportreclassUserCreate(SQLModel):phone:str@field_validator("phone")defcheck_phone(cls,v):ifnotre.match(r"^1[3-9]\d{9}$",v):raiseValueError("请输入合法11位手机号")returnv六、序列化精细化控制(敏感字段隐藏)
6.1 Field(exclude=True) 永久不序列化
数据库密码字段,无论怎么转字典/JSON永远隐藏:
classUser(SQLModel,table=True):id:int|None=Field(default=None,primary_key=True)username:str# 序列化永久排除密码password:str=Field(exclude=True,max_length=100)user=User(username="admin",password="123Abc666")print(user.model_dump())# 输出不含password6.2 model_dump 动态过滤参数
user=User(...)# 过滤所有None空值user.model_dump(exclude_none=True)# 仅保留用户传入字段,过滤默认值user.model_dump(exclude_unset=True)# 临时手动排除指定字段user.model_dump(exclude={"password"})七、阶段综合完整可运行案例
整合高级字段、枚举、分层DTO、自定义校验、脱敏全部知识点:
fromsqlmodelimportSQLModel,Field,create_engine,SessionfrompydanticimportEmailStr,field_validator,ValidationErrorfromenumimportEnumfromsqlalchemyimportSQLEnumimportre# 1. 枚举定义classUserRole(str,Enum):ADMIN="admin"USER="user"# 2. 基础模型classUserBase(SQLModel):username:str=Field(min_length=3,max_length=16)email:EmailStr# 3. 创建模型(带密码校验)classUserCreate(UserBase):password:str=Field(min_length=8)role:UserRole=Field(default=UserRole.USER)@field_validator("password")defpwd_check(cls,v):ifnotre.search(r"[A-Za-z0-9]",v):raiseValueError("密码需包含字母数字")returnv# 4. 返回脱敏模型classUserPublic(UserBase):id:introle:UserRole# 5. 数据库实体classUser(UserBase,SQLModel,table=True):id:int|None=Field(default=None,primary_key=True)password:str=Field(exclude=True)role:UserRole=Field(sa_column=SQLEnum(UserRole))# 数据库初始化engine=create_engine("sqlite:///stage2.db",echo=False)SQLModel.metadata.create_all(bind=engine)# 新增测试if__name__=="__main__":try:create_data=UserCreate(username=" Admin666 ",email="admin@test.com",password="Abc123456",role="admin")withSession(engine)assession:db_user=User.model_validate(create_data)session.add(db_user)session.commit()session.refresh(db_user)# 脱敏返回resp=UserPublic.model_validate(db_user)print("接口返回数据:",resp.model_dump())exceptValidationErrorase:print("参数校验失败:",e.errors())八、阶段核心总结(半天必掌握)
Field同时承载数据库列配置与Pydantic数据校验,一套代码两用;- 动态默认值统一使用
default_factory,避免可变类型全局复用问题; - 联合索引通过
__table_args__定义,枚举搭配SQLEnum实现数据库层约束; - 直接复用Pydantic内置
EmailStr/HttpUrl/UUID等专用校验类型; - 四层分层DTO(Base/Create/Update/Public)是前后端项目标准规范,隔离敏感字段;
- SQLModel原生支持
@field_validator,可复用全部Pydantic自定义校验逻辑; Field(exclude=True)永久隐藏密码等敏感数据,接口序列化自动脱敏。
九、新手高频避坑指南
- ❌ 可变类型(list/datetime/uuid)使用
default,所有实例共享同一个值; - ❌ 数据库实体直接返回前端,泄露password、密钥等敏感字段;
- ❌ 更新模型复用Base基类,字段不带Optional,导致必须传全量参数;
- ❌ 不用枚举,直接用字符串存储角色/状态,缺少入参校验;
- ✅ 所有接口分层定义DTO,数据库实体仅用于数据库操作;
- ✅ 复杂格式校验(密码、手机号)统一使用
@field_validator,减少业务if判断。