【问题标题】:Why isn't SQLAlchemy default column value available before object is committed?为什么在提交对象之前 SQLAlchemy 默认列值不可用?
【发布时间】:2012-12-09 18:30:26
【问题描述】:

最近我发现 SQLAlchemy 的 Column 默认值不能像我预期的那样工作:

>>> Base = declarative_base()
>>> class TestModel(Base):
    ...     __tablename__ = 'tmodel'
...     id = sa.Column(sa.Integer, primary_key=True)
...     foo = sa.Column(sa.Integer, default=0)
...    
>>> tmodel_instance = TestModel()
>>> print tmodel_instance.foo
None
>>> session.add(tmodel_instance)
>>> print tmodel_instance.foo
None
>>> session.commit()
>>> print tmodel_instance.foo
0

我希望tmodel_instance.foo在对象实例化后立即等于0,但似乎只有在执行INSERT命令时才使用默认值,这让我很困惑。为什么人们更喜欢default 而不是server_default?以及如何实现我想要的?我应该在__init__ 中指定所有默认参数吗?这似乎是代码重复:要更改默认值,我必须更改两次并保持这些值相等——有什么方法可以避免这种情况吗?

【问题讨论】:

  • +1 同意。持久化一个对象的行为会改变它的属性是没有意义的。

标签: python sqlalchemy default-value


【解决方案1】:

出于以下四个原因之一,人们更喜欢默认而不是服务器默认:

  1. 您希望运行一个 Python 函数,而不是 SQL 函数,作为默认值(或者一个还需要一些每个 INSERT Python 状态的 SQL 表达式)。

  2. 默认是主键列的一部分。 ORM 无法在没有主键的情况下加载行,因此在使用 ORM 时,server_default 通常对于 PK 列没有用处。

  3. 数据库不支持您要运行的 SQL 表达式作为“服务器默认值”。

  4. 您正在处理无法/不想更改的架构。

在这种情况下,当您希望应用程序中的“foo”为“0”而与数据库操作无关时,可以选择:

  1. 使用__init__()。是蟒蛇!

  2. 使用事件。

这里是__init__()

class TestModel(Base):
   # ...

   def __init__(self):
       self.foo = 0

这里是事件(特别是init event):

from sqlalchemy import event

@event.listens_for(Foo, "init")
def init(target, args, kwargs):
    target.foo = 0

【讨论】:

  • 但是为什么 sqlAlchemy 模型是这样设计的:创建后没有默认值而不是默认值?这肯定会让习惯于其他 ORM 库的人感到困惑
  • “默认”通常是 SQL 表达式或完整的服务器端值。在 INSERT 发生之前无法知道它,或者更不理想的是,它是否在创建对象时笨拙地预先执行(这本身会降低值成为真正的“INSERT 默认值”)。因此,仅使核心级插入默认值的一个子集的行为完全不同是不一致的。
  • 虽然session.flush(model)session.refresh(model) 仍然会产生None 值,但很奇怪,因为刷新会话会生成主键...
【解决方案2】:

您可以使用来自sqlalchemy_utilsforce_instant_defaults 侦听器来更改此行为:

from sqlalchemy_utils import force_instant_defaults

force_instant_defaults()

class TestModel(Base):
    __tablename__ = 'tmodel'
    id = sa.Column(sa.Integer, primary_key=True)
    foo = sa.Column(sa.Integer, default=0)

model = TestModel()
assert model.foo == 0

【讨论】:

    【解决方案3】:

    您可以使用init 事件来填充默认值。这个事件监听器会这样做:

    from sqlalchemy import event
    from sqlalchemy.orm import mapper
    from sqlalchemy.inspection import inspect
    
    
    def instant_defaults_listener(target, args, kwargs):
        for key, column in inspect(target.__class__).columns.items():
            if column.default is not None:
                if callable(column.default.arg):
                    setattr(target, key, column.default.arg(target))
                else:
                    setattr(target, key, column.default.arg)
    
    
    event.listen(mapper, 'init', instant_defaults_listener)
    

    【讨论】:

    • 这工作了很长时间,但现在我得到一个错误:sqlalchemy Neither 'InstrumentedAttribute' object nor 'Comparator' object associated with * has an attribute '_supports_population' mapperKrassowski's answer 为我工作。
    猜你喜欢
    • 2012-11-27
    • 1970-01-01
    • 2020-03-05
    • 2012-04-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-07-07
    • 1970-01-01
    相关资源
    最近更新 更多