Peewee覆盖默认SQL模式:非空字段未赋值插入无报错问题
问题:MySQL严格模式下Peewee未触发非空字段约束报错
在全局配置TRADITIONAL严格SQL模式的MySQL环境中,使用Peewee ORM实例化模型时省略null=False(默认配置)且default=None的字段,调用save()/create()/insert().execute()时未触发预期的非空约束报错,与直接执行等价SQL的行为不一致。
环境配置与测试场景
1. 配置MySQL全局严格模式
mysql> SET GLOBAL sql_mode="TRADITIONAL"; Query OK, 0 rows affected (0.00 sec)
2. Peewee测试代码
from rich import inspect from peewee import Model, MySQLDatabase from peewee import CharField, FixedCharField, BooleanField, DateTimeField debug_db = MySQLDatabase( database='debug_db', user='DEBUG', host='localhost', password='secret' ) class Person(Model): first_name = CharField(32) last_name = CharField(32, null=False) email = FixedCharField(255) signup_time = DateTimeField() approved = BooleanField() class Meta: database = debug_db debug_db.connect() debug_db.create_tables([Person]) # 仅赋值first_name,省略其他非空字段 john_doe = Person(first_name="John") inspect(john_doe) # 输出显示未赋值字段均为None:last_name=None、email=None等 john_doe.save() # 数据库插入结果:未赋值字段被MySQL填充为隐式默认值 # mysql> select * from person; # +----+------------+-----------+-------+---------------------+----------+ # | id | first_name | last_name | email | signup_time | approved | # +----+------------+-----------+-------+---------------------+----------+ # | 1 | John | | | 0000-00-00 00:00:00 | 0 | # +----+------------+-----------+-------+---------------------+----------+ # Peewee生成的SQL:仅包含显式赋值的字段 # ('INSERT INTO `person` (`first_name`) VALUES (%s)', ['John'])
3. 直接执行MySQL INSERT语句的行为
mysql> INSERT INTO person (first_name) VALUES ("John"); ERROR 1364 (HY000): Field 'last_name' doesn't have a default value
4. 手动设置字段为None的行为
john_doe = Person( first_name="John", last_name=None ) john_doe.save() # 触发报错:peewee.IntegrityError: (1048, "Column 'last_name' cannot be null") # 生成的SQL包含last_name字段:('INSERT INTO `person` (`first_name`, `last_name`) VALUES (%s, %s)', ['John', None])
5. SQLite环境下的对比
使用SQLite时,Peewee会正常触发非空约束报错:
peewee.IntegrityError: NOT NULL constraint failed: person.last_name
问题原因
- Peewee的字段处理逻辑:未显式赋值的
null=False字段不会被包含在INSERT语句中,仅提交已赋值的字段。 - MySQL的隐式默认值机制:在TRADITIONAL模式下,对于未在INSERT中指定的非空字段,MySQL会自动填充隐式默认值:
- 字符串类型(CharField/FixedCharField)填充为空字符串
'' - 日期类型(DateTimeField)填充为
0000-00-00 00:00:00 - 布尔类型(BooleanField)填充为
0
这些值不属于NULL,因此不会触发NOT NULL约束报错。
- 字符串类型(CharField/FixedCharField)填充为空字符串
- SQLite的差异:SQLite对未指定的非空字段会视为NULL,直接触发NOT NULL约束,因此Peewee会捕获到报错。
解决方法
方法1:开启Peewee严格模式+显式设置字段默认值
在初始化MySQL数据库时开启strict=True,同时给所有null=False的字段设置显式默认值,Peewee会在插入前校验未赋值的非空字段:
from datetime import datetime debug_db = MySQLDatabase( database='debug_db', user='DEBUG', host='localhost', password='secret', strict=True # 开启Peewee严格模式 ) class Person(Model): first_name = CharField(32, default='') last_name = CharField(32, null=False, default='') email = FixedCharField(255, default='') signup_time = DateTimeField(default=datetime.now) approved = BooleanField(default=False) class Meta: database = debug_db
方法2:重写Model的save方法,手动校验字段
自定义BaseModel,在保存前校验所有null=False且未赋值的字段:
class BaseModel(Model): def save(self, *args, **kwargs): # 遍历所有字段,校验非空约束 for field in self._meta.fields.values(): if not field.null and getattr(self, field.name) is None: raise ValueError(f"字段 {field.name} 不能为空") return super().save(*args, **kwargs) class Person(BaseModel): first_name = CharField(32) last_name = CharField(32, null=False) email = FixedCharField(255) signup_time = DateTimeField() approved = BooleanField() class Meta: database = debug_db
方法3:使用Peewee的validate方法实现自定义校验
利用Peewee的Model.validate()钩子,实现非空字段校验:
class Person(Model): first_name = CharField(32) last_name = CharField(32, null=False) email = FixedCharField(255) signup_time = DateTimeField() approved = BooleanField() def validate(self): super().validate() # 校验非空字段 required_fields = [f for f in self._meta.fields.values() if not f.null] for field in required_fields: if getattr(self, field.name) is None: raise ValueError(f"字段 {field.name} 必须赋值") class Meta: database = debug_db # 使用时需手动调用validate john_doe = Person(first_name="John") john_doe.validate() # 触发报错 john_doe.save()
内容的提问来源于stack exchange,提问作者Magnus Lind Oxlund
相关产品推荐
相关产品推荐

