Подтвердить что ты не робот

Почему значение объекта 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__? Кажется, это дублирование кода: для изменения значения по умолчанию мне нужно дважды его изменить и поддерживать равенство значений - есть ли способ избежать этого?

4b9b3361

Ответ 1

можно предпочесть по умолчанию по умолчанию для сервера по одной из четырех причин:

  • вы хотите запустить функцию Python, а не функцию SQL, для значения по умолчанию (или выражение SQL, которое также нуждается в каком-либо состоянии INSERT Python).

  • значение по умолчанию является частью столбца первичного ключа. ORM не может загрузить строку обратно без первичного ключа, поэтому server_default обычно не используется для столбца PK при использовании ORM.

  • выражение SQL, которое вы хотите запустить, не поддерживается базой данных как "по умолчанию сервера".

  • Вы имеете дело со схемой, которую вы не можете/не хотите изменять.

В этом случае, когда вы хотите, чтобы "foo" было "0" в приложении независимо от операций с базой данных, возможны следующие варианты:

  • используйте __init__(). Это python!

  • использовать события.

Здесь __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