Recently I figured out that SQLAlchemy\'s Column default doesn\'t work as I expect it to:
>>> Base = declarative_base()
>>> class TestModel
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
one would prefer default over server default for one of four reasons:
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).
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.
the SQL expression you want to run isn't supported by the database as a "server default".
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:
use __init__()
. It's python!
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
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)