创建一个列"不可变的"在SQLAlchemy中

时间:2016-01-22 18:41:27

标签: python sqlalchemy flask-sqlalchemy

我想创建一个专栏" immutable"或"不可更新的"在SQLAlchemy中。

现在我正在使用一个事件监听器,如果该列已更新,则会引发异常:

@event.listens_for(Person.email, 'set')
def receive_set_person_email(target, value, old_value, initiator):
    if old_value != symbol('NEVER_SET') and old_value != symbol('NO_VALUE'):
        raise AttributeError("Cannot change a Person's email")

但是我想知道这样的事情是否已经内置,或者我是否可以找到更漂亮,更通用的解决方案。

2 个答案:

答案 0 :(得分:5)

您可以使email成为无法使用ORM写入的内部列。

class Person(Base):
    __tablename__ = 'person'

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

    @hybrid_property
    def email(self):
        return self._email

无法写信:

>>> p = Person(email="foo@bar.com")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<string>", line 4, in __init__
  File "/Library/Python/2.7/site-packages/sqlalchemy/orm/state.py", line 306, in _initialize_instance
    manager.dispatch.init_failure(self, args, kwargs)
  File "/Library/Python/2.7/site-packages/sqlalchemy/util/langhelpers.py", line 60, in __exit__
    compat.reraise(exc_type, exc_value, exc_tb)
  File "/Library/Python/2.7/site-packages/sqlalchemy/orm/state.py", line 303, in _initialize_instance
    return manager.original_init(*mixed[1:], **kwargs)
  File "/Library/Python/2.7/site-packages/sqlalchemy/ext/declarative/base.py", line 649, in _declarative_constructor
    setattr(self, k, kwargs[k])
AttributeError: can't set attribute

答案 1 :(得分:5)

这是我的解决方案:

from sqlalchemy import event
from sqlalchemy.util.langhelpers import symbol


class NonUpdateableColumnError(AttributeError):
    def __init__(self, cls, column, old_value, new_value, message=None):
        self.cls = cls
        self.column = column
        self.old_value = old_value
        self.new_value = new_value

        if message is None:
            self.message = 'Cannot update column {} on model {} from {} to {}: column is non-updateable.'.format(
                column, cls, old_value, new_value)


def make_nonupdateable(col):
    @event.listens_for(col, 'set')
    def unupdateable_column_set_listener(target, value, old_value, initiator):
        if old_value != symbol('NEVER_SET') and old_value != symbol('NO_VALUE') and old_value != value:
            raise NonUpdateableColumnError(col.class_.__name__, col.name, old_value, value)


class Person(Base):
    email = Column(String)

make_nonupdateable(Person.email)