TanggalTime default SQLAlchemy

174

Ini adalah model deklaratif saya:

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)

Namun, ketika saya mencoba mengimpor modul ini, saya mendapatkan kesalahan ini:

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'

Jika saya menggunakan tipe Integer, saya bisa menetapkan nilai default. Apa yang sedang terjadi?

Brandon O'Rourke
sumber
1
Ini seharusnya tidak digunakan. utcnow adalah stempel waktu naif di zona waktu UTC, namun, kemungkinan stempel waktu naif ditafsirkan dalam zona waktu lokal sebagai gantinya.
Antti Haapala
1
Saya tahu pertanyaan ini sudah lama ditanyakan, tetapi saya pikir jawaban Anda harus diubah dengan yang @Jeff Widman berikan sebagai yang lain akan menggunakan waktu kompilasi "waktu" ketika kelas tabel didefinisikan versus ketika catatan dibuat. Jika Anda AFK maka setidaknya komentar ini akan memberikan peringatan bagi orang lain untuk memeriksa komentar untuk setiap pertanyaan.
David

Jawaban:

186

DateTimetidak memiliki kunci default sebagai input. Kunci default harus menjadi input ke Columnfungsi. Coba ini:

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
sumber
13
Ini tidak benar. Stempel waktu pada pemuatan model akan digunakan untuk semua catatan baru alih-alih saat catatan ditambahkan.
SkyLeach
8
@SkyLeach Kode ini benar, Anda dapat mengujinya di sini: pastebin.com/VLyWktUn . datetime.datetime.utcnowtampaknya disebut sebagai panggilan balik.
bux
1
Saya menggunakan jawaban ini tetapi stempel waktu di model memuat sedang digunakan untuk semua catatan baru.
scottydelta
105
@scottdelta: Pastikan Anda tidak menggunakan default=datetime.datetime.utcnow(); Anda ingin melewatkan utcnowfungsi, bukan hasil mengevaluasinya pada beban modul.
Disfungsi
Masih tidak bekerja untuk saya. Pendekatan jeff-widman bekerja untuk saya:from sqlalchemy.sql import func; created_date = Column(DateTime(timezone=True), server_default=func.now()
tharndt
396

Hitung cap waktu dalam DB Anda, bukan klien Anda

Untuk kewarasan, Anda mungkin ingin semua datetimesdihitung oleh server DB Anda, bukan dari server aplikasi. Menghitung stempel waktu dalam aplikasi dapat menyebabkan masalah karena latensi jaringan bervariasi, klien mengalami pergeseran jam yang sedikit berbeda, dan bahasa pemrograman yang berbeda terkadang menghitung waktu sedikit berbeda.

SQLAlchemy memungkinkan Anda untuk melakukan ini dengan melewati func.now()atau func.current_timestamp()(mereka adalah alias satu sama lain) yang memberitahu DB untuk menghitung timestamp itu sendiri.

Gunakan SQLALchemy server_default

Selain itu, untuk default di mana Anda sudah memberi tahu DB untuk menghitung nilainya, umumnya lebih baik digunakan server_defaultdaripada default. Ini memberitahu SQLAlchemy untuk memberikan nilai default sebagai bagian dari CREATE TABLEpernyataan.

Misalnya, jika Anda menulis skrip ad hoc pada tabel ini, menggunakan server_defaultcara yang berarti Anda tidak perlu khawatir menambahkan panggilan timestamp secara manual ke skrip Anda - database akan mengaturnya secara otomatis.

Memahami SQLAlchemy onupdate/server_onupdate

SQLAlchemy juga mendukung onupdatesehingga kapan pun baris diperbarui, akan menyisipkan stempel waktu baru. Sekali lagi, sebaiknya beri tahu DB untuk menghitung timestamp itu sendiri:

from sqlalchemy.sql import func

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

Ada server_onupdateparameter, tetapi tidak seperti server_defaultitu, itu tidak benar-benar mengatur apa pun sisi server. Ini hanya memberitahu SQLalchemy bahwa database Anda akan mengubah kolom ketika pembaruan terjadi (mungkin Anda membuat pemicu pada kolom ), jadi SQLAlchemy akan meminta nilai kembali sehingga dapat memperbarui objek yang sesuai.

Satu potensi gotcha lainnya:

Anda mungkin terkejut melihat bahwa jika Anda membuat banyak perubahan dalam satu transaksi, mereka semua memiliki stempel waktu yang sama. Itu karena standar SQL menentukan bahwa CURRENT_TIMESTAMPmengembalikan nilai berdasarkan pada awal transaksi.

PostgreSQL menyediakan non-SQL-standar statement_timestamp()dan clock_timestamp()yang melakukan perubahan dalam suatu transaksi. Documents di sini: https://www.postgresql.org/docs/current/static/functions-datetime.html#FUNCTIONS-DATETIME-CURRENT

Stempel waktu UTC

Jika Anda ingin menggunakan cap waktu UTC, potongan implementasi untuk func.utcnow()disediakan dalam dokumentasi SQLAlchemy . Anda perlu menyediakan fungsi spesifik driver yang sesuai sendiri.

Jeff Widman
sumber
1
Saya memang melihat sekarang ada cara untuk memberitahu Postgres untuk menggunakan waktu saat ini, bukan hanya waktu mulai transaksi ... itu ada dalam dokumen postgres
Jeff Widman
2
apakah ada func.utcnow () atau sesuatu seperti itu?
yerassyl
1
@JeffWidman: Apakah kita memiliki implementasi mysql untuk utcnow? Saya dalam dokumentasi hanya mssql dan postreg
JavaSa
1
Ini memang jawaban yang bagus!
John Mutuma
3
server_default=func.now()bekerja untuk saya, tetapi onupdate=func.now()tidak. Mencoba onupdate=datetime.datetime.utcnow(tanpa tanda kurung), itu juga tidak membantu
Vikas Prasad
55

Anda juga dapat menggunakan fungsi built-in sqlalchemy untuk default DateTime

from sqlalchemy.sql import func

DT = Column(DateTime(timezone=True), default=func.now())
qwerty
sumber
9
Anda juga dapat menggunakan server_defaultalih-alih defaultnilai akan ditangani oleh basis data itu sendiri.
rgtk
2
Tidak func.now () dijalankan ketika deskriptor didefinisikan, sehingga semua model / anak-anak (jika ini adalah kelas dasar) akan memiliki nilai DT yang sama. Dengan meneruskan referensi fungsi seperti 'datetime.datetime.utcnow' itu dijalankan secara terpisah untuk masing-masing. Ini sangat penting untuk properti 'dibuat' atau 'diperbarui'.
Metalstorm
10
@ Metalstorm Tidak, ini benar. sqlalchemy.sql.func adalah kasus khusus yang mengembalikan sqlalchemy.sql.functions.sekarang, bukan waktu saat ini. docs.sqlalchemy.org/en/latest/core/…
Kris Hardy
Apa yang timezone=Trueharus dilakukan
ChaimG
1
@diri, Dari dokumentasi : "Jika Benar, dan didukung oleh backend, akan menghasilkan 'TIMESTAMP WITH TIMEZONE'. Untuk backend yang tidak mendukung cap waktu sadar zona waktu, tidak berpengaruh .
ChaimG
7

Anda mungkin ingin menggunakan onupdate=datetime.nowsehingga UPDATE juga mengubah last_updatedbidang.

SQLAlchemy memiliki dua default untuk fungsi yang dieksekusi python.

  • default menetapkan nilai pada INSERT, hanya sekali
  • onupdatemenetapkan nilai ke hasil yang bisa dipanggil pada UPDATE juga.
pengguna3389572
sumber
Saya tidak tahu mengapa, tetapi untuk beberapa alasan onupdatetidak melakukan apa pun untuk saya.
Vikas Prasad
1
Saya akhirnya berhasil mengerjakan Postgres db dengan melakukan ini: created_at = db.Column(db.DateTime, server_default=UtcNow())dan di updated_at = db.Column(db.DateTime, server_default=UtcNow(), onupdate=UtcNow())mana UtcNowkelasnya seperti yang class UtcNow(expression.FunctionElement): type = DateTime()kita miliki@compiles(UtcNow, 'postgresql') def pg_utc_now(element, compiler, **kw): return "TIMEZONE('utc', CURRENT_TIMESTAMP)"
Vikas Prasad
6

The defaultparameter kunci harus diberikan ke objek Kolom.

Contoh:

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

Nilai standarnya bisa berupa callable, yang di sini saya definisikan seperti berikut.

from pytz import timezone
from datetime import datetime

UTC = timezone('UTC')

def time_now():
    return datetime.now(UTC)
Keith
sumber
Dari mana datangnya time_now?
kay - SE jahat
Terima kasih! Saya berasumsi bahwa ada fungsi bawaan dengan nama itu yang entah bagaimana saya abaikan.
kay - SE adalah kejahatan
ataudatetime.datetime.utcnow
serkef
1

Sesuai dokumentasi PostgreSQL, https://www.postgresql.org/docs/9.6/static/functions-datetime.html

now, CURRENT_TIMESTAMP, LOCALTIMESTAMP return the time of transaction.

Ini dianggap sebagai fitur: tujuannya adalah untuk memungkinkan satu transaksi memiliki gagasan yang konsisten tentang waktu "saat ini", sehingga beberapa modifikasi dalam transaksi yang sama memiliki cap waktu yang sama.

Anda mungkin ingin menggunakan statement_timestamp atau clock_timestamp jika Anda tidak ingin timestamp transaksi.

statement_timestamp ()

mengembalikan waktu mulai dari pernyataan saat ini (lebih khusus, waktu penerimaan pesan perintah terbaru dari klien). statement_timestamp

clock_timestamp ()

mengembalikan waktu aktual saat ini, dan karenanya nilainya berubah bahkan dalam satu perintah SQL.

abhi shukla
sumber