You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

问题原因

  1. Peewee的字段处理逻辑:未显式赋值的null=False字段不会被包含在INSERT语句中,仅提交已赋值的字段。
  2. MySQL的隐式默认值机制:在TRADITIONAL模式下,对于未在INSERT中指定的非空字段,MySQL会自动填充隐式默认值:
    • 字符串类型(CharField/FixedCharField)填充为空字符串''
    • 日期类型(DateTimeField)填充为0000-00-00 00:00:00
    • 布尔类型(BooleanField)填充为0
      这些值不属于NULL,因此不会触发NOT NULL约束报错。
  3. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 16:40:43