sqlalchemy:如何阻止特定列的更新

Mat*_*chs 5 sqlalchemy

我有一个声明性映射:

class User(base):
    username = Column(Unicode(30), unique=True)
Run Code Online (Sandbox Code Playgroud)

我怎么能告诉 sqlalchemy 这个属性不能被修改?我想出的解决方法有点笨拙:

from werkzeug.utils import cached_property
# regular @property works, too

class User(base):
    _username = Column('username', Unicode(30), unique=True)
    @cached_property
    def username(self):
        return self._username
    def __init__(self, username, **kw):
        super(User,self).__init__(**kw)
        self._username=username
Run Code Online (Sandbox Code Playgroud)

在数据库列权限级别上执行此操作将不起作用,因为并非所有数据库都支持该操作。

Ale*_*eue 8

您可以使用validatesSQLAlchemy 功能。

from sqlalchemy.orm import validates
...

class User(base):
  ...
  
  @validates('username')
  def validates_username(self, key, value):
    if self.username:  # Field already exists
      raise ValueError('Username cannot be modified.')

    return value
Run Code Online (Sandbox Code Playgroud)

参考:https://docs.sqlalchemy.org/en/13/orm/mapped_attributes.html#simple-validators


Ale*_*xei 3

我可以建议以下方法来保护列免受修改:

首先是在设置任何属性时使用钩子

如果以上Base声明性表中的所有列都将被挂钩,因此您需要以某种方式存储有关列是否可以修改的信息。例如,您可以继承 sqlalchemy.Column 类以向其添加一些属性,然后检查挂钩中的属性。

class Column(sqlalchemy.Column):

    def __init__(self, *args, **kwargs):
        self.readonly = kwargs.pop("readonly", False)
        super(Column, self).__init__(*args, **kwargs)

# noinspection PyUnusedLocal
@event.listens_for(Base, 'attribute_instrument')
def configure_listener(class_, key, inst):
    """This event is called whenever an attribute on a class is instrumented"""

    if not hasattr(inst.property, 'columns'):
        return

    # noinspection PyUnusedLocal
    @event.listens_for(inst, "set", retval=True)
    def set_column_value(instance, value, oldvalue, initiator):
        """This event is called whenever a "set" occurs on that instrumented attribute"""
        logging.info("%s: %s -> %s" % (inst.property.columns[0], oldvalue, value))
        column = inst.property.columns[0]

        # CHECK HERE ON CAN COLUMN BE MODIFIED IF NO RAISE ERROR
        if not column.readonly:
            raise RuntimeError("Column %s can't be changed!" % column.name)

        return value
Run Code Online (Sandbox Code Playgroud)

要挂钩具体属性,您可以执行以下操作(不需要将属性添加到列):

# standard decorator style
@event.listens_for(SomeClass.some_attribute, 'set')
def receive_set(target, value, oldvalue, initiator):
    "listen for the 'set' event"
    # ... (event handling logic) ...
Run Code Online (Sandbox Code Playgroud)

是有关 SQLAlchemy 事件的指南。

我建议的第二种方法是使用标准 Python 属性或 SQLAlchemy hybrid_property,正如您在问题中所示的那样,但使​​用这种方法会导致代码增长。

PS 我认为紧凑的方式是将属性添加到列并挂钩所有设置事件。