有没有办法透明地对SQLAlchemy对象执行验证?

时间:2010-03-05 23:58:23

标签: python validation model dns sqlalchemy

是否有一种方法可以在设置属性之后但在提交会话之前对对象执行验证?

例如,我有一个具有Device属性的域模型mac。我想确保mac属性在数据库中添加或更新之前包含有效且已清理的mac值。

看起来Pythonic方法是将大多数东西作为属性(包括SQLAlchemy)。如果我用PHP或Java编写了这个代码,我可能会选择创建getter / setter方法来保护数据并让我灵活地在域模型本身中处理它。

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方法是什么?

(虽然我知道验证并且应该在其他地方处理(例如,web框架),但我想弄清楚如何处理这些特定领域的验证规则,因为它们必然经常出现。)

更新

我知道在正常情况下我可以使用property来执行此操作。关键部分是我在这些类中使用SQLAlchemy。我不清楚SQLAlchemy究竟是如何表现它的魔力但我怀疑自己创建和覆盖这些属性可能导致不稳定和/或不可预测的结果。

3 个答案:

答案 0 :(得分:14)

您可以使用@validates()装饰器在SQLAlchemy类中添加数据验证。

来自文档 - Simple Validators

  

属性验证器可以引发异常,停止变更属性值的过程,或者可以将给定值更改为不同的值。

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

答案 1 :(得分:8)

是。这可以使用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引用,因为此处的每个实例都不会实际更新到数据库。

答案 2 :(得分:2)

“看起来像Pythonic方法就是做大多数事情作为属性”

它有所不同,但那很接近。

“如果我用PHP或Java编写过代码,我可能会选择创建getter / setter方法......”

好。那就是Pythonic就够了。你的getter和setter函数绑定在一个属性中;那很不错。

问题是什么?

您是否在询问如何拼写property

但是,“透明验证” - 如果我正确阅读了您的示例代码 - 可能并不是真的那么好。

您的模型和验证应该保持独立。对单个模型进行多次验证是很常见的。对于某些用户,字段是可选的,固定的或不使用的;这导致了多次验证。

遵循Django设计模式,使用Form进行验证,与模型分开,您会更高兴。