为啥在提交对象之前 SQLAlchemy 默认列值不可用?
Posted
技术标签:
【中文标题】为啥在提交对象之前 SQLAlchemy 默认列值不可用?【英文标题】: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 同意。持久化一个对象的行为会改变它的属性是没有意义的。 【参考方案1】:出于以下四个原因之一,人们更喜欢默认而不是服务器默认:
您希望运行一个 Python 函数,而不是 SQL 函数,作为默认值(或者一个还需要一些每个 INSERT Python 状态的 SQL 表达式)。
默认是主键列的一部分。 ORM 无法在没有主键的情况下加载行,因此在使用 ORM 时,server_default 通常对于 PK 列没有用处。
数据库不支持您要运行的 SQL 表达式作为“服务器默认值”。
您正在处理无法/不想更改的架构。
在这种情况下,当您希望应用程序中的“foo”为“0”而与数据库操作无关时,可以选择:
使用__init__()
。是蟒蛇!
使用事件。
这里是__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_utils
的force_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' mapper
。 Krassowski's answer 为我工作。以上是关于为啥在提交对象之前 SQLAlchemy 默认列值不可用?的主要内容,如果未能解决你的问题,请参考以下文章
在 SQLAlchemy 中,如何在提交之前预览 SQL 语句以进行调试?