SQLAlchemy по умолчанию DateTime

175

Это моя декларативная модель:

import datetime
from sqlalchemy import Column, Integer, DateTime
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

class Test(Base):
    __tablename__ = 'test'

    id = Column(Integer, primary_key=True)
    created_date = DateTime(default=datetime.datetime.utcnow)

Однако, когда я пытаюсь импортировать этот модуль, я получаю эту ошибку:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "orm/models2.py", line 37, in <module>
    class Test(Base):
  File "orm/models2.py", line 41, in Test
    created_date = sqlalchemy.DateTime(default=datetime.datetime.utcnow)
TypeError: __init__() got an unexpected keyword argument 'default'

Если я использую тип Integer, я могу установить значение по умолчанию. В чем дело?

Брэндон О'Рурк
источник
1
Это не должно быть использовано. utcnow является наивной временной меткой в ​​часовом поясе UTC, однако, скорее всего, вместо этого временные метки интерпретируются в местном часовом поясе.
Антти Хаапала
1
Я знаю, что этот вопрос задавался очень давно, но я думаю, что ваш ответ должен быть заменен на тот, который @Jeff Widman предоставил, так как другой будет использовать дату и время «компиляции», когда класс таблицы определяется по сравнению с тем, когда создается запись. Если вы AFK, то, по крайней мере, этот комментарий будет предупреждением для других, чтобы внимательно проверить комментарии к каждому вопросу.
Дэвид

Ответы:

186

DateTimeне имеет ключа по умолчанию в качестве ввода. Ключ по умолчанию должен быть вводом для Columnфункции. Попробуй это:

import datetime
from sqlalchemy import Column, Integer, DateTime
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

class Test(Base):
    __tablename__ = 'test'

    id = Column(Integer, primary_key=True)
    created_date = Column(DateTime, default=datetime.datetime.utcnow)
PearsonArtPhoto
источник
13
Это не правильно. Отметка времени при загрузке модели будет использоваться для всех новых записей, а не для добавления записи.
SkyLeach
8
@SkyLeach Этот код правильный, вы можете проверить его здесь: pastebin.com/VLyWktUn . datetime.datetime.utcnowкажется, называется обратным вызовом.
Bux
1
Я использовал этот ответ, но отметка времени при загрузке модели используется для всех новых записей.
Скоттиделта
105
@scottdelta: убедитесь, что вы не используете default=datetime.datetime.utcnow(); Вы хотите передать utcnowфункцию, а не результат ее оценки при загрузке модуля.
дисфункция
Все еще не работал для меня. Подход Джефф-Видман работал для меня:from sqlalchemy.sql import func; created_date = Column(DateTime(timezone=True), server_default=func.now()
Tharndt
396

Вычислять метки времени в вашей БД, а не в вашем клиенте

Для здравомыслия, вы, вероятно, хотите, чтобы все datetimesвычислялись вашим сервером БД, а не сервером приложений. Вычисление метки времени в приложении может привести к проблемам, поскольку задержка в сети является переменной, клиенты испытывают несколько различный сдвиг тактовой частоты, а разные языки программирования иногда вычисляют время немного по-разному.

SQLAlchemy позволяет вам сделать это, передав func.now()или func.current_timestamp()(они являются псевдонимами друг друга), который указывает БД рассчитать саму метку времени.

Используйте SQLALchemy's server_default

Кроме того, для значения по умолчанию, когда вы уже говорите БД для вычисления значения, обычно лучше использовать server_defaultвместо default. Это говорит SQLAlchemy передать значение по умолчанию как часть CREATE TABLEинструкции.

Например, если вы напишите специальный скрипт для этой таблицы, используя server_default означает, что вам не нужно беспокоиться о ручном добавлении вызова метки времени в ваш скрипт - база данных установит его автоматически.

Понимание SQLAlchemy's onupdate/server_onupdate

SQLAlchemy также поддерживает, onupdateтак что при каждом обновлении строки вставляется новая отметка времени. Опять же, лучше всего сказать БД рассчитать саму метку времени:

from sqlalchemy.sql import func

time_created = Column(DateTime(timezone=True), server_default=func.now())
time_updated = Column(DateTime(timezone=True), onupdate=func.now())

Есть server_onupdateпараметр, но, в отличие от него server_default, он ничего не устанавливает на стороне сервера. Он просто сообщает SQLalchemy, что ваша база данных изменит столбец при обновлении (возможно, вы создали триггер для столбца ), поэтому SQLAlchemy запросит возвращаемое значение, чтобы он мог обновить соответствующий объект.

Еще один потенциальный вопрос:

Вы можете быть удивлены, заметив, что если вы сделаете кучу изменений в одной транзакции, они все будут иметь одну и ту же метку времени. Это потому, что стандарт SQL определяет, чтоCURRENT_TIMESTAMP возвращает значения в зависимости от начала транзакции.

PostgreSQL предоставляет не-SQL стандарта statement_timestamp()и clock_timestamp()которые делают изменения в рамках транзакции. Документы здесь: https://www.postgresql.org/docs/current/static/functions-datetime.html#FUNCTIONS-DATETIME-CURRENT

Метка времени UTC

Если вы хотите использовать метки времени UTC, заглушка для реализации func.utcnow()предоставлена ​​в документации SQLAlchemy . Вы должны предоставить соответствующие специфичные для драйвера функции самостоятельно, хотя.

Джефф Видман
источник
1
Я видел, что теперь есть способ сказать Postgres использовать текущее время, а не только время начала транзакции ... это в документации postgres
Джефф Уидман
2
есть func.utcnow () или что-то в этом роде?
Yerassyl
1
@JeffWidman: у нас есть реализация mysql для utcnow? У меня в документации только mssql и postreg
JavaSa
1
Это действительно отличный ответ!
Джон Мутума
3
server_default=func.now()работал для меня, но onupdate=func.now()не сделал. Попробовал onupdate=datetime.datetime.utcnow(без скобок), что тоже не помогло
Викас Прасад
55

Вы также можете использовать встроенную функцию sqlalchemy по умолчанию DateTime

from sqlalchemy.sql import func

DT = Column(DateTime(timezone=True), default=func.now())
БУКВ
источник
9
Вы также можете использовать server_defaultвместо defaultэтого значение, которое будет обрабатываться самой базой данных.
rgtk
2
Не выполняется func.now (), когда дескриптор определен, поэтому все модели / потомки (если это базовый класс) будут иметь одинаковое значение DT. Передавая ссылку на функцию, такую ​​как datetime.datetime.utcnow, она выполняется отдельно для каждой. Это важно для «созданных» или «обновленных» свойств.
Metalstorm
10
@ Metalstorm Нет, это правильно. sqlalchemy.sql.func - это особый случай, который возвращает экземпляр sqlalchemy.sql.functions.now, а не текущее время. docs.sqlalchemy.org/en/latest/core/…
Крис Харди
Что делает timezone=True?
ChaimG
1
@self, Из документации : «Если True и поддерживается бэкендом, будет генерироваться« TIMESTAMP WITH TIMEZONE ». Для бэкэндов, которые не поддерживают временные метки с
учетом
7

Вы, вероятно, хотите использовать onupdate=datetime.nowтак, чтобы ОБНОВЛЕНИЯ также изменили last_updatedполе.

SQLAlchemy имеет два значения по умолчанию для функций, выполняемых на Python.

  • default устанавливает значение на INSERT, только один раз
  • onupdateтакже устанавливает значение для вызываемого результата в UPDATE.
user3389572
источник
Я не знаю почему, но по какой-то причине onupdate для меня ничего не делает.
Викас Прасад
1
Я, наконец, получил его работать на Postgres DB, выполнив это: created_at = db.Column(db.DateTime, server_default=UtcNow())и updated_at = db.Column(db.DateTime, server_default=UtcNow(), onupdate=UtcNow())где UtcNowкласс, как class UtcNow(expression.FunctionElement): type = DateTime()и у нас есть@compiles(UtcNow, 'postgresql') def pg_utc_now(element, compiler, **kw): return "TIMEZONE('utc', CURRENT_TIMESTAMP)"
Викас Прасад
6

Параметр defaultключевого слова должен быть передан объекту Column.

Пример:

Column(u'timestamp', TIMESTAMP(timezone=True), primary_key=False, nullable=False, default=time_now),

Значением по умолчанию может быть вызываемое, которое здесь я определил следующим образом.

from pytz import timezone
from datetime import datetime

UTC = timezone('UTC')

def time_now():
    return datetime.now(UTC)
Кит
источник
Откуда time_nowберутся?
Кей - SE злой
Спасибо! Я предположил, что есть встроенная функция с таким именем, которую я как-то упустил.
Кей - SE злой
илиdatetime.datetime.utcnow
serkef
1

Согласно документации PostgreSQL, https://www.postgresql.org/docs/9.6/static/functions-datetime.html.

now, CURRENT_TIMESTAMP, LOCALTIMESTAMP return the time of transaction.

Это считается особенностью: цель состоит в том, чтобы позволить одной транзакции иметь согласованное представление о «текущем» времени, так что несколько модификаций в одной транзакции имеют одинаковую отметку времени.

Вы можете использовать statement_timestamp или clock_timestamp , если вы не хотите транзакций метки времени.

statement_timestamp ()

возвращает время начала текущего оператора (точнее, время получения последнего командного сообщения от клиента). statement_timestamp

clock_timestamp ()

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

абхи шукла
источник