SQLAlchemy domyślny DateTime

174

Oto mój deklaratywny model:

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)

Jednak gdy próbuję zaimportować ten moduł, pojawia się ten błąd:

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'

Jeśli używam typu Integer, mogę ustawić wartość domyślną. Co się dzieje?

Brandon O'Rourke
źródło
1
Nie należy tego używać. utcnow to naiwny znacznik czasu w strefie czasowej UTC, jednak istnieje prawdopodobieństwo, że naiwne znaczniki czasu są zamiast tego interpretowane w lokalnej strefie czasowej.
Antti Haapala
1
Wiem, że to pytanie zostało zadane dawno temu, ale myślę, że twoja odpowiedź powinna zostać zmieniona na taką, którą dostarczył @Jeff Widman, ponieważ druga będzie używać daty i godziny „kompilacji”, kiedy klasa tabeli jest zdefiniowana, a kiedy tworzony jest rekord. Jeśli jesteś AFK, przynajmniej ten komentarz będzie ostrzeżeniem dla innych, aby dokładnie sprawdzili komentarze do każdego pytania.
David

Odpowiedzi:

186

DateTimenie ma domyślnego klucza jako wejścia. Domyślny klucz powinien być wejściem do Columnfunkcji. Spróbuj tego:

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
źródło
13
To nie jest w porządku. Sygnatura czasowa w momencie ładowania modelu będzie używana dla wszystkich nowych rekordów zamiast czasu dodania rekordu.
SkyLeach
8
@SkyLeach Ten kod jest poprawny, możesz go przetestować tutaj: pastebin.com/VLyWktUn . datetime.datetime.utcnowwydaje się być nazywany oddzwonieniem.
bux
1
Użyłem tej odpowiedzi, ale sygnatura czasowa przy ładowaniu modelu jest używana dla wszystkich nowych rekordów.
scottydelta
105
@scottdelta: Upewnij się, że nie używasz default=datetime.datetime.utcnow(); chcesz przekazać utcnowfunkcję, a nie wynik oceny jej podczas ładowania modułu.
dysfunkcja
Nadal nie działa dla mnie. Podejście jeff-widmana zadziałało dla mnie:from sqlalchemy.sql import func; created_date = Column(DateTime(timezone=True), server_default=func.now()
tharndt
396

Obliczaj sygnatury czasowe w swojej bazie danych, a nie w swoim kliencie

Ze względów rozsądnych prawdopodobnie chcesz, aby wszystko zostało datetimesobliczone przez serwer bazy danych, a nie przez serwer aplikacji. Obliczanie znacznika czasu w aplikacji może prowadzić do problemów, ponieważ opóźnienie sieci jest zmienne, klienci doświadczają nieco innego przesunięcia zegara, a różne języki programowania czasami obliczają czas nieco inaczej.

SQLAlchemy pozwala to zrobić, przekazując func.now()lub func.current_timestamp()(są one wzajemnymi aliasami), co mówi DB, aby obliczył sam znacznik czasu.

Użyj SQLALchemy's server_default

Dodatkowo, w przypadku domyślnego, w którym już mówisz DB, aby obliczył wartość, generalnie lepiej jest użyć server_defaultzamiast default. To mówi SQLAlchemy, aby przekazał wartość domyślną jako część CREATE TABLEinstrukcji.

Na przykład, jeśli napiszesz skrypt ad hoc na tej tabeli, użycie server_defaultoznacza, że ​​nie będziesz musiał martwić się o ręczne dodawanie wywołania znacznika czasu do skryptu - baza danych ustawi go automatycznie.

Zrozumienie SQLAlchemy onupdate/server_onupdate

SQLAlchemy obsługuje również, onupdatedzięki czemu za każdym razem, gdy wiersz jest aktualizowany, wstawia nowy znacznik czasu. Ponownie, najlepiej powiedzieć DB, aby obliczył sam znacznik czasu:

from sqlalchemy.sql import func

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

Jest server_onupdateparametr, ale w przeciwieństwie do server_defaulttego nie ustawia on niczego po stronie serwera. Po prostu mówi SQLalchemy, że twoja baza danych zmieni kolumnę, gdy nastąpi aktualizacja (być może utworzyłeś wyzwalacz w kolumnie ), więc SQLAlchemy zapyta o wartość zwracaną, aby mógł zaktualizować odpowiedni obiekt.

Jeszcze jedna potencjalna wpadka:

Możesz być zaskoczony, gdy zauważysz, że jeśli dokonasz wielu zmian w ramach jednej transakcji, wszystkie mają ten sam znacznik czasu. Dzieje się tak, ponieważ standard SQL określa, że CURRENT_TIMESTAMPzwraca wartości na podstawie początku transakcji.

PostgreSQL zapewnia non-SQL standard statement_timestamp()i clock_timestamp()które zrobić zmiany w obrębie transakcji. Dokumenty tutaj: https://www.postgresql.org/docs/current/static/functions-datetime.html#FUNCTIONS-DATETIME-CURRENT

Znacznik czasu UTC

Jeśli chcesz używać sygnatur czasowych UTC, func.utcnow()w dokumentacji SQLAlchemy znajduje się kod implementacji . Musisz jednak samodzielnie zapewnić odpowiednie funkcje specyficzne dla sterownika.

Jeff Widman
źródło
1
Widziałem, że jest teraz sposób, aby powiedzieć Postgresowi, aby używał aktualnego czasu, a nie tylko godziny rozpoczęcia transakcji ... jest to w dokumentach postgres
Jeff Widman
2
czy jest func.utcnow () czy coś takiego?
yerassyl
1
@JeffWidman: Czy mamy implementację mysql dla utcnow? W dokumentacji tylko mssql i postreg
JavaSa
1
To naprawdę świetna odpowiedź!
John Mutuma,
3
server_default=func.now()pracował dla mnie, ale onupdate=func.now()nie. Próbowałem onupdate=datetime.datetime.utcnow(bez nawiasów), to też nie pomogło
Vikas Prasad
55

Możesz także użyć domyślnej funkcji wbudowanej sqlalchemy DateTime

from sqlalchemy.sql import func

DT = Column(DateTime(timezone=True), default=func.now())
qwerty
źródło
9
Możesz także użyć server_defaultzamiast, defaultwięc wartość będzie obsługiwana przez samą bazę danych.
rgtk
2
Funkcja func.now () nie jest wykonywana, gdy deskryptor jest zdefiniowany, więc wszystkie modele / dzieci (jeśli jest to klasa bazowa) będą miały tę samą wartość DT. Przekazując odwołanie do funkcji, takie jak „datetime.datetime.utcnow”, jest ono wykonywane osobno dla każdego. Ma to kluczowe znaczenie dla „utworzonych” lub „zaktualizowanych” właściwości.
Metalstorm
10
@Metalstorm Nie, zgadza się. sqlalchemy.sql.func to szczególny przypadek, który zwraca instancję sqlalchemy.sql.functions.now, a nie aktualny czas. docs.sqlalchemy.org/en/latest/core/ ...
Kris Hardy
Co robi timezone=True?
ChaimG
1
@self, Z dokumentacji : „Jeśli prawda i jest obsługiwane przez zaplecze, wygeneruje„ TIMESTAMP WITH TIMEZONE ”. Dla backendów, które nie obsługują sygnatur czasowych uwzględniających strefę czasową, nie działa .
ChaimG
7

Prawdopodobnie będziesz chciał użyć onupdate=datetime.now, aby UPDATEs również zmieniały last_updatedpole.

SQLAlchemy ma dwie wartości domyślne dla funkcji wykonywanych w Pythonie.

  • default ustawia wartość na INSERT, tylko raz
  • onupdateustawia wartość na wywoływalny wynik również w UPDATE.
user3389572
źródło
Nie wiem dlaczego, ale z jakiegoś powodu onupdatenic dla mnie nie robi.
Vikas Prasad
1
W końcu udało mi się to działać na Postgres db, robiąc to: created_at = db.Column(db.DateTime, server_default=UtcNow())i updated_at = db.Column(db.DateTime, server_default=UtcNow(), onupdate=UtcNow())gdzie UtcNowjest klasa jak class UtcNow(expression.FunctionElement): type = DateTime()i mamy@compiles(UtcNow, 'postgresql') def pg_utc_now(element, compiler, **kw): return "TIMEZONE('utc', CURRENT_TIMESTAMP)"
Vikas Prasad
6

defaultParametr Hasło powinno być podane do obiektu kolumnie.

Przykład:

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

Wartość domyślna może być wywoływalna, co tutaj zdefiniowałem jak poniżej.

from pytz import timezone
from datetime import datetime

UTC = timezone('UTC')

def time_now():
    return datetime.now(UTC)
Keith
źródło
Skąd się time_nowbierze?
kay - SE is evil
Dziękuję Ci! Założyłem, że istnieje wbudowana funkcja o tej nazwie, którą jakoś przeoczyłem.
kay - SE is evil
lubdatetime.datetime.utcnow
serkef
1

Zgodnie z dokumentacją PostgreSQL, https://www.postgresql.org/docs/9.6/static/functions-datetime.html

now, CURRENT_TIMESTAMP, LOCALTIMESTAMP return the time of transaction.

Jest to uważane za cechę: chodzi o to, aby pojedyncza transakcja miała spójne pojęcie „bieżącego” czasu, tak aby wiele modyfikacji w ramach tej samej transakcji nosiło ten sam znacznik czasu.

Jeśli nie chcesz, aby sygnatura czasowa transakcji była używana , możesz użyć parametru Statement_timestamp lub clock_timestamp .

Statement_timestamp ()

zwraca czas rozpoczęcia bieżącej instrukcji (a dokładniej czas odebrania ostatniego komunikatu polecenia od klienta). Statement_timestamp

clock_timestamp ()

zwraca aktualny czas, dlatego jego wartość zmienia się nawet w ramach jednego polecenia SQL.

abhi shukla
źródło