有没有办法在SQLAlchemy对象上透明地进行验证?

18 投票
3 回答
8784 浏览
提问于 2025-04-15 20:04

有没有办法在对象的属性被设置后(或者在设置的同时),但在会话提交之前,对这些属性进行验证呢?

举个例子,我有一个叫做 Device 的模型,它有一个 mac 属性。我想确保这个 mac 属性在被添加到数据库或更新之前,包含一个有效且经过处理的 mac 值。

看起来在 Python 中,很多事情都是通过属性来处理的(包括 SQLAlchemy)。如果我用 PHP 或 Java 编写这段代码,我可能会选择创建获取器和设置器方法来保护数据,并让我在模型内部灵活处理这些数据。

public function mac() { return $this->mac; }
public function setMac($mac) {
    return $this->mac = $this->sanitizeAndValidateMac($mac);
}
public function sanitizeAndValidateMac($mac) {
    if ( ! preg_match(self::$VALID_MAC_REGEX) ) {
        throw new InvalidMacException($mac);
    }
    return strtolower($mac);
}

那么,使用 SQLAlchemy 处理这种情况的 Pythonic 方法是什么呢?

(虽然我知道验证应该在其他地方处理(比如在网页框架中),但我想弄清楚如何处理一些特定于领域的验证规则,因为这些规则很可能会经常出现。)

更新

我知道在正常情况下可以使用 property 来做到这一点。关键是我在使用 SQLAlchemy 和这些类。我不太明白 SQLAlchemy 是如何运作的,但我怀疑自己创建和覆盖这些属性可能会导致不稳定或不可预测的结果。

3 个回答

2

看起来在Python中,处理大多数事情时,使用属性(properties)是比较常见的做法。

这情况有点不同,但差不多就是这样。

“如果我用PHP或Java来写这个,我可能会选择创建获取器(getter)和设置器(setter)方法……”

很好,这样做在Python中也算是可以接受的。你的获取器和设置器功能已经和属性结合在一起了,这样挺不错的。

那你的问题是什么呢?

你是在问怎么拼写property这个词吗?

不过,“透明验证”——如果我理解你的示例代码没错——可能并不是个好主意。

你的模型和验证应该分开处理。通常一个模型会有多种验证方式。对某些用户来说,有些字段是可选的、固定的或者根本不使用;这就导致了需要多种验证。

遵循Django的设计模式,使用表单(Form)来进行验证,而不是和模型混在一起,这样你会更开心。

9

是的。我们可以通过使用一个叫做MapperExtension的工具来很好地实现这个功能。

# uses sqlalchemy hooks to data model class specific validators before update and insert
class ValidationExtension( sqlalchemy.orm.interfaces.MapperExtension ):
    def before_update(self, mapper, connection, instance):
        """not every instance here is actually updated to the db, see http://www.sqlalchemy.org/docs/reference/orm/interfaces.html?highlight=mapperextension#sqlalchemy.orm.interfaces.MapperExtension.before_update"""
        instance.validate()
        return sqlalchemy.orm.interfaces.MapperExtension.before_update(self, mapper, connection, instance)
    def before_insert(self, mapper, connection, instance):
        instance.validate()
        return sqlalchemy.orm.interfaces.MapperExtension.before_insert(self, mapper, connection, instance)


sqlalchemy.orm.mapper( model, table, extension = ValidationExtension(), **mapper_args )

你可能想看看before_update的相关说明,因为这里并不是每个实例都会被更新到数据库中。

16

你可以在你的SQLAlchemy类里面添加数据验证,方法是使用@validates()这个装饰器。

根据文档 - 简单验证器

一个属性验证器可以抛出一个异常,这样就会停止修改这个属性的值,或者可以把给定的值转换成其他的东西。

from sqlalchemy.orm import validates

class EmailAddress(Base):
    __tablename__ = 'address'

    id = Column(Integer, primary_key=True)
    email = Column(String)

    @validates('email')
    def validate_email(self, key, address):
        # you can use assertions, such as
        # assert '@' in address
        # or raise an exception:
        if '@' not in address:
            raise ValueError('Email address must contain an @ sign.')
        return address

撰写回答