Why isn't SQLAlchemy default column value available before object is committed?

旧城冷巷雨未停 提交于 2019-11-26 19:58:00

问题


Recently I figured out that SQLAlchemy's Column default doesn't work as I expect it to:

>>> 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

I want tmodel_instance.foo to equal 0 right after object instantiation, but it seems that the default value is only used when executing INSERT command, and it really confuses me. Why would one prefer the default over server_default? And how do I achieve what I want? Am I supposed to specify all default arguments in __init__? That seems to be code duplication: to change the default value I have to change it twice and maintain those values equality -- is there some way to avoid that?


回答1:


You can use force_instant_defaults listener from sqlalchemy_utils to change this behavior:

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



回答2:


one would prefer default over server default for one of four reasons:

  1. you'd like to run a Python function, not a SQL function, for the default (or a SQL expression that needs some per-INSERT Python state also).

  2. the default is part of a primary key column. the ORM can't load a row back without the primary key, so server_default is generally not useful for a PK column when using the ORM.

  3. the SQL expression you want to run isn't supported by the database as a "server default".

  4. You're dealing with a schema you can't/don't want to change.

In this case, when you'd like "foo" to be "0" in your application independent of database operations, the choices are:

  1. use __init__(). It's python!

  2. use events.

Here's __init__():

class TestModel(Base):
   # ...

   def __init__(self):
       self.foo = 0

Here's events (specifically the init event):

from sqlalchemy import event

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



回答3:


You can use the init event to fill defaults. This event listener will do it:

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)


来源:https://stackoverflow.com/questions/14002631/why-isnt-sqlalchemy-default-column-value-available-before-object-is-committed

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!