【问题标题】:Is there a way to transparently perform validation on SQLAlchemy objects?有没有办法透明地对 SQLAlchemy 对象执行验证?
【发布时间】:2011-01-24 08:20:48
【问题描述】:

有没有办法在设置属性之后(或之后)但在提交会话之前对对象执行验证?

例如,我有一个域模型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 方法是什么?

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

更新

我知道在正常情况下我可以使用property 来执行此操作。关键部分是我在这些类中使用 SQLAlchemy。我不完全了解 SQLAlchemy 是如何发挥其魔力的,但我怀疑我自己创建和覆盖这些属性可能会导致不稳定和/或不可预测的结果。

【问题讨论】:

    标签: python validation model dns sqlalchemy


    【解决方案1】:

    是的。这可以使用 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】:

      您可以使用 @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
      

      【讨论】:

      • 问一个简单的问题:如果我想要简单的“存在”验证,我是否应该将nullable=false 添加到列声明中,然后尝试捕获session.commit() 错误?还是我应该尝试类似的验证器?谢谢。
      • @art-solopov 简单验证器使用起来非常简单,装饰器参数采用元组,因此如果您有其他需要相同验证的字段,只需将其添加到 args 元组中。跨度>
      【解决方案3】:

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

      它各不相同,但很接近。

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

      很好。这已经够Pythonic了。您的 getter 和 setter 函数绑定在一个属性中;挺好的。

      问题是什么?

      你问property怎么拼写?

      但是,“透明验证”(如果我正确阅读了您的示例代码)可能并不是一个好主意。

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

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

      【讨论】:

      • 我不确定 SQLAlchemy 使用哪种魔法来绑定到 Model 类属性。我不认为在我的班级定义我自己的mac = property() 是安全的。我想我的问题是这样做安全吗?如果是这样,有什么陷阱吗?如果没有,还有哪些其他选择?
      • 没有魔法。这不是问题。另一种方法是我们通常做的:我们在模型类外部进行验证,使其更加简单。在模型外部定义你的验证,然后你就不必担心这些了。
      • 我不知道你多久会为同一个模型获得不同的验证,这通常会让 DBA 发疯(ORM 模型的约束应该由实际数据模型的约束驱动,或者你'重新破坏了 ORM 模式的目的)。如果您有单独的用例,则使您的模型具有多态性。紧耦合在这里是更好的答案,特别是因为您有自省(在 2017 年:inspect 模块)从架构元数据本身动态引用验证)。
      猜你喜欢
      • 1970-01-01
      • 2014-07-02
      • 2020-11-07
      • 1970-01-01
      • 2021-12-20
      • 2011-01-28
      • 1970-01-01
      • 1970-01-01
      • 2010-11-26
      相关资源
      最近更新 更多