DateTime par défaut de SQLAlchemy

175

Voici mon modèle déclaratif:

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)

Cependant, lorsque j'essaye d'importer ce module, j'obtiens cette erreur:

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'

Si j'utilise un type Integer, je peux définir une valeur par défaut. Que se passe-t-il?

Brandon O'Rourke
la source
1
Cela ne devrait pas être utilisé. utcnow est un horodatage naïf dans le fuseau horaire UTC, cependant, il y a de fortes chances que les horodatages naïfs soient interprétés dans le fuseau horaire local à la place.
Antti Haapala
1
Je sais que cette question a été posée il y a longtemps mais je pense que votre réponse devrait être remplacée par celle que @Jeff Widman a fournie, car l'autre utilisera le temps de "compilation" datetime lorsque la classe de table est définie par rapport à la création de l'enregistrement. Si vous êtes AFK, alors au moins ce commentaire fournira un avertissement pour que les autres vérifient attentivement les commentaires pour chaque question.
David

Réponses:

186

DateTimen'a pas de clé par défaut comme entrée. La clé par défaut doit être une entrée de la Columnfonction. Essaye ça:

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
la source
13
Ce n'est pas juste. L'horodatage au chargement du modèle sera utilisé pour tous les nouveaux enregistrements plutôt que l'heure à laquelle l'enregistrement est ajouté.
SkyLeach
8
@SkyLeach Ce code est correct, vous pouvez le tester ici: pastebin.com/VLyWktUn . datetime.datetime.utcnowsemble appelé comme rappel.
bux
1
J'ai utilisé cette réponse, mais l'horodatage au chargement du modèle est utilisé pour tous les nouveaux enregistrements.
scottydelta
105
@scottdelta: assurez-vous que vous n'utilisez pas default=datetime.datetime.utcnow(); vous voulez passer la utcnowfonction, pas le résultat de son évaluation au chargement du module.
dysfonctionnement du
Cela ne fonctionnait toujours pas pour moi. L'approche de jeff-widman a fonctionné pour moi:from sqlalchemy.sql import func; created_date = Column(DateTime(timezone=True), server_default=func.now()
tharndt le
396

Calculez les horodatages dans votre base de données, pas dans votre client

Par souci de cohérence, vous souhaiterez probablement que tout soit datetimescalculé par votre serveur de base de données, plutôt que par le serveur d'applications. Le calcul de l'horodatage dans l'application peut entraîner des problèmes car la latence du réseau est variable, les clients subissent une dérive d'horloge légèrement différente et différents langages de programmation calculent parfois l'heure légèrement différemment.

SQLAlchemy vous permet de le faire en passant func.now()ou func.current_timestamp()(ce sont des alias les uns des autres) qui indique à la base de données de calculer l'horodatage lui-même.

Utilisez SQLALchemy server_default

De plus, pour une valeur par défaut où vous dites déjà à la base de données de calculer la valeur, il est généralement préférable d'utiliser à la server_defaultplace de default. Cela indique à SQLAlchemy de transmettre la valeur par défaut dans le cadre de l' CREATE TABLEinstruction.

Par exemple, si vous écrivez un script ad hoc sur cette table, l'utilisation server_defaultsignifie que vous n'aurez pas à vous soucier de l'ajout manuel d'un appel d'horodatage à votre script - la base de données le définira automatiquement.

Comprendre SQLAlchemy onupdate/server_onupdate

SQLAlchemy prend également en charge onupdateafin que chaque fois que la ligne est mise à jour, il insère un nouvel horodatage. Encore une fois, mieux vaut dire à la base de données de calculer l'horodatage lui-même:

from sqlalchemy.sql import func

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

Il y a un server_onupdateparamètre, mais contrairement à server_defaultcela, il ne définit rien côté serveur. Il indique simplement à SQLalchemy que votre base de données changera la colonne lorsqu'une mise à jour se produit (peut-être avez-vous créé un déclencheur sur la colonne ), donc SQLAlchemy demandera la valeur de retour afin de pouvoir mettre à jour l'objet correspondant.

Un autre gotcha potentiel:

Vous serez peut-être surpris de constater que si vous apportez un ensemble de modifications dans une seule transaction, elles ont toutes le même horodatage. C'est parce que la norme SQL spécifie que CURRENT_TIMESTAMPrenvoie des valeurs basées sur le début de la transaction.

PostgreSQL fournit la non-SQL standard statement_timestamp()et clock_timestamp()qui ne le changement dans une transaction. Docs ici: https://www.postgresql.org/docs/current/static/functions-datetime.html#FUNCTIONS-DATETIME-CURRENT

Horodatage UTC

Si vous souhaitez utiliser les horodatages UTC, un stub d'implémentation pour func.utcnow()est fourni dans la documentation SQLAlchemy . Vous devez cependant fournir vous-même les fonctions spécifiques au pilote.

Jeff Widman
la source
1
J'ai vu qu'il y avait maintenant un moyen de dire à Postgres d'utiliser l'heure actuelle, pas seulement l'heure de début de la transaction ... c'est dans la documentation postgres
Jeff Widman
2
y a-t-il un func.utcnow () ou quelque chose comme ça?
yerassyl
1
@JeffWidman: Avons-nous une implémentation mysql pour utcnow? I dans la documentation uniquement mssql et postreg
JavaSa
1
Ceci, en effet, est une excellente réponse!
John Mutuma
3
server_default=func.now()a travaillé pour moi, mais onupdate=func.now()pas. Essayé onupdate=datetime.datetime.utcnow(sans crochets), cela n'a pas non plus aidé
Vikas Prasad
55

Vous pouvez également utiliser la fonction intégrée sqlalchemy par défaut DateTime

from sqlalchemy.sql import func

DT = Column(DateTime(timezone=True), default=func.now())
qwerty
la source
9
Vous pouvez également utiliser à la server_defaultplace de defaultce que la valeur sera gérée par la base de données elle-même.
rgtk
2
La fonction func.now () n'est pas exécutée lorsque le descripteur est défini, donc tous les modèles / enfants (s'il s'agit d'une classe de base) auront la même valeur DT. En passant une référence de fonction comme 'datetime.datetime.utcnow', elle est exécutée séparément pour chacune. Ceci est crucial pour les propriétés «créées» ou «mises à jour».
Metalstorm
10
@Metalstorm Non, c'est correct. sqlalchemy.sql.func est un cas spécial qui renvoie une instance de sqlalchemy.sql.functions.now, pas l'heure actuelle. docs.sqlalchemy.org/en/latest/core/…
Kris Hardy
Que fait timezone=True-on?
ChaimG
1
@self, De la documentation : "Si True, et pris en charge par le backend, produira 'TIMESTAMP WITH TIMEZONE'. Pour les backends qui ne prennent pas en charge les horodatages
tenant
7

Vous souhaiterez probablement utiliser onupdate=datetime.nowpour que les MISES À JOUR modifient également le last_updatedchamp.

SQLAlchemy a deux valeurs par défaut pour les fonctions exécutées en python.

  • default définit la valeur sur INSERT, une seule fois
  • onupdatedéfinit également la valeur du résultat appelable lors de UPDATE.
user3389572
la source
Je ne sais pas pourquoi, mais pour une raison quelconque, onupdatene fait rien pour moi.
Vikas Prasad
1
Je l'ai finalement fait fonctionner sur Postgres db en faisant ceci: created_at = db.Column(db.DateTime, server_default=UtcNow())et updated_at = db.Column(db.DateTime, server_default=UtcNow(), onupdate=UtcNow())UtcNowest une classe comme class UtcNow(expression.FunctionElement): type = DateTime()et nous avons@compiles(UtcNow, 'postgresql') def pg_utc_now(element, compiler, **kw): return "TIMEZONE('utc', CURRENT_TIMESTAMP)"
Vikas Prasad
6

Le defaultparamètre de mot-clé doit être donné à l'objet Column.

Exemple:

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

La valeur par défaut peut être un appelable, que j'ai défini ici comme suit.

from pytz import timezone
from datetime import datetime

UTC = timezone('UTC')

def time_now():
    return datetime.now(UTC)
Keith
la source
D'où time_nowvient-il?
kay - SE is evil
Je vous remercie! J'ai supposé qu'il y avait une fonction intégrée avec ce nom que j'avais en quelque sorte oublié.
kay - SE is evil
oudatetime.datetime.utcnow
serkef
1

Selon la documentation PostgreSQL, https://www.postgresql.org/docs/9.6/static/functions-datetime.html

now, CURRENT_TIMESTAMP, LOCALTIMESTAMP return the time of transaction.

Ceci est considéré comme une caractéristique: l'intention est de permettre à une seule transaction d'avoir une notion cohérente de l'heure "courante", de sorte que plusieurs modifications dans la même transaction portent le même horodatage.

Vous pouvez utiliser statement_timestamp ou clock_timestamp si vous ne voulez pas d'horodatage de transaction.

statement_timestamp ()

renvoie l'heure de début de l'instruction en cours (plus précisément, l'heure de réception du dernier message de commande du client). statement_timestamp

horloge_horodatage ()

renvoie l'heure actuelle réelle et, par conséquent, sa valeur change même dans une seule commande SQL.

abhi shukla
la source