Почему значение объекта SQLAlchemy по умолчанию не доступно перед объектом?
Недавно я понял, что по умолчанию 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, а не функцию SQL, для значения по умолчанию (или выражение SQL, которое также нуждается в каком-либо состоянии INSERT Python).
-
значение по умолчанию является частью столбца первичного ключа. ORM не может загрузить строку обратно без первичного ключа, поэтому server_default обычно не используется для столбца PK при использовании ORM.
-
выражение SQL, которое вы хотите запустить, не поддерживается базой данных как "по умолчанию сервера".
-
Вы имеете дело со схемой, которую вы не можете/не хотите изменять.
В этом случае, когда вы хотите, чтобы "foo" было "0" в приложении независимо от операций с базой данных, возможны следующие варианты:
Здесь __init__()
:
class TestModel(Base):
# ...
def __init__(self):
self.foo = 0
Здесь события (в частности событие инициализации):
from sqlalchemy import event
@event.listens_for(Foo, "init")
def init(target, args, kwargs):
target.foo = 0
Ответ 2
Вы можете использовать событие 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)
Ответ 3
Вы можете использовать force_instant_defaults
прослушиватель из sqlalchemy_utils
, чтобы изменить это поведение:
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