300

This is my declarative 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)

However, when I try to import this module, I get this error:

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'

If I use an Integer type, I can set a default value. What's going on?

Nathaniel Jones
  • 939
  • 1
  • 14
  • 25
Brandon O'Rourke
  • 24,165
  • 16
  • 57
  • 58
  • 1
    This shouldn't be used. utcnow is a naive timestamp in UTC timezone, however, chances are that naive timestamps are interpreted in local timezone instead. – Antti Haapala -- Слава Україні Oct 06 '16 at 07:33
  • 3
    I know this question was asked a long time ago but I think your answer should be changed to the one that @Jeff Widman provided as the other will use the "compile" time datetime when the table class is defined versus when the record is created. If you are AFK then at least this comment will provide a warning for others to carefully check the comments for each question. – David Feb 21 '19 at 21:12

11 Answers11

671

Calculate timestamps within your DB, not your client

For sanity, you probably want to have all datetimes calculated by your DB server, rather than the application server. Calculating the timestamp in the application can lead to problems because network latency is variable, clients experience slightly different clock drift, and different programming languages occasionally calculate time slightly differently.

SQLAlchemy allows you to do this by passing func.now() or func.current_timestamp() (they are aliases of each other) which tells the DB to calculate the timestamp itself.

Use SQLALchemy's server_default

Additionally, for a default where you're already telling the DB to calculate the value, it's generally better to use server_default instead of default. This tells SQLAlchemy to pass the default value as part of the CREATE TABLE statement.

For example, if you write an ad hoc script against this table, using server_default means you won't need to worry about manually adding a timestamp call to your script--the database will set it automatically.

Understanding SQLAlchemy's onupdate/server_onupdate

SQLAlchemy also supports onupdate so that anytime the row is updated it inserts a new timestamp. Again, best to tell the DB to calculate the timestamp itself:

from sqlalchemy.sql import func

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

There is a server_onupdate parameter, but unlike server_default, it doesn't actually set anything serverside. It just tells SQLalchemy that your database will change the column when an update happens (perhaps you created a trigger on the column ), so SQLAlchemy will ask for the return value so it can update the corresponding object.

One other potential gotcha:

You might be surprised to notice that if you make a bunch of changes within a single transaction, they all have the same timestamp. That's because the SQL standard specifies that CURRENT_TIMESTAMP returns values based on the start of the transaction.

PostgreSQL provides the non-SQL-standard statement_timestamp() and clock_timestamp() which do change within a transaction. Docs here: https://www.postgresql.org/docs/current/static/functions-datetime.html#FUNCTIONS-DATETIME-CURRENT

UTC timestamp

If you want to use UTC timestamps, a stub of implementation for func.utcnow() is provided in SQLAlchemy documentation. You need to provide appropriate driver-specific functions on your own though.

Community
  • 1
  • 1
Jeff Widman
  • 22,014
  • 12
  • 72
  • 88
  • I totally agree with you. However, my setup does not work. ``` class TimestampMixin(object): created_at = Column('created_at', DateTime(timezone=True), default=func.now()) updated_at = Column('updated_at', DateTime(timezone=True), default=func.now(), onupdate=func.now()) ``` I issued a create and an update statement 1 second apart. The two values for created_at and updated_at are always the same. – Khanh Hua Jan 08 '16 at 02:16
  • IIRC, in PostgreSQL, if you issue both of them within the same DB transaction the timestamps will both be from the transaction start time. Not sure about other DB's. You may also not have flushed and/or committed the SQLAlchemy session in between the create/update statement. If not either of those, then it will be far easier to debug if you open a new question. Feel free to include a link here and I'll take a look. – Jeff Widman Jan 08 '16 at 04:52
  • 1
    I did see there's now a way to tell Postgres to use the current time, not just the transaction start time... it's in the postgres docs – Jeff Widman May 05 '16 at 22:03
  • 4
    is there a func.utcnow() or something like that? – yerassyl Apr 19 '17 at 04:47
  • 1
    @JeffWidman: Do we have a mysql implementation for utcnow? I in documentation only mssql and postreg – JavaSa Nov 29 '17 at 15:13
  • @JavaSa I don't know off top of my head. If you open a new question and link to it here then I can try to look it up. – Jeff Widman Nov 29 '17 at 22:10
  • @JeffWidman: You are welcome, https://stackoverflow.com/questions/47554658/sqlalchemy-utcnow-implementation?noredirect=1#comment82069780_47554658 – JavaSa Nov 30 '17 at 10:51
  • Got `sqlalchemy.exc.OperationalError: (_mysql_exceptions.OperationalError) (1067, "Invalid default value for 'created'")` – Mithril May 03 '18 at 08:57
  • how to set default datetime to a fixed datetime like 1970-1-1? – Zen Jun 14 '18 at 01:49
  • 5
    `server_default=func.now()` worked for me, but `onupdate=func.now()` didn't. Tried `onupdate=datetime.datetime.utcnow` (without brackets), that also didnt help – Vikas Prasad Aug 16 '18 at 15:03
  • tried `server_onupdate=func.now()` too. That didnt work either. – Vikas Prasad Aug 16 '18 at 15:16
  • I finally got it working on Postgres db by doing this: `created_at = db.Column(db.DateTime, server_default=UtcNow())` and `updated_at = db.Column(db.DateTime, server_default=UtcNow(), onupdate=UtcNow())` where `UtcNow` is a class as `class UtcNow(expression.FunctionElement): type = DateTime()` and we have `@compiles(UtcNow, 'postgresql') def pg_utc_now(element, compiler, **kw): return "TIMEZONE('utc', CURRENT_TIMESTAMP)"` – Vikas Prasad Aug 17 '18 at 08:44
  • @VikasPrasad - I don't think the onupdate will generate the column definition you expect; meaning, it won't update the timestamp on record 'update'. You need a DB function + update trigger to do this. – James Addison Sep 23 '19 at 17:54
  • great answer - thing to note, at least for me - the insert ts only started working after i re-created the table. instead of re-creating the table - you could also modify your table in mysql, and add an expression (last column) saying CURRENT_TIMESTAMP – FlyingZebra1 Oct 05 '19 at 22:01
  • @JeffWidman (or anyone else who knows), assuming you set up the before update trigger properly, does it matter what you put in `server_onupdate` then if it's just a signal to SQLAlchemy to pull the new value from the DB? – gtmtg Nov 27 '19 at 20:24
  • Works great but I just want to point out that for me, the update timestamp does not get filled in upon creation. So, if you want all recently edited records, you would have to look at both time_created and time_updated. This was using sqlite and flask-sqlalchemy. – rossdavidh Feb 18 '20 at 20:19
  • `server_default=func.now()` did not work with sqlalchemy + sqlite3 . datetime solution worked – muon Jun 17 '21 at 19:36
  • @JeffWidman I found an open source implementation for UTC that seems to work for many DB drivers. Its name is SQLAlchemy-Utc. See my answer here https://stackoverflow.com/a/68588586/5193682 – user5193682 Jul 30 '21 at 09:28
  • Regarding "If you want to use UTC timestamps," - aren't all timestamps UTC? Or does @JeffWidman mean if you want to retrieve (as well as store) UTC timestamps? – Raph117 Mar 23 '22 at 09:58
276

DateTime doesn't have a default key as an input. The default key should be an input to the Column function. Try this:

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
  • 38,970
  • 17
  • 111
  • 142
  • 24
    This isn't right. The timestamp at model load will be used for all new records rather than the time the record is added. – SkyLeach Feb 09 '16 at 16:18
  • 13
    @SkyLeach This code is correct, you can test it here: http://pastebin.com/VLyWktUn . ``datetime.datetime.utcnow`` seems called as callback. – bux Mar 04 '16 at 14:55
  • 4
    I used this answer but the timestamp at model load are being used for all new records. – scottydelta Jan 26 '17 at 02:20
  • 162
    @scottdelta: Make sure you aren't using `default=datetime.datetime.utcnow()`; you want to pass the `utcnow` function, not the result of evaluating it at module load. – dysfunction Feb 22 '17 at 15:54
  • 9
    Still did not work for me. The approach of jeff-widman worked for me: `from sqlalchemy.sql import func; created_date = Column(DateTime(timezone=True), server_default=func.now()` – tharndt Jan 21 '20 at 10:18
  • 6
    I now understand the difference between the two methods discussed in this thread: `server_default` will result in a default value stored at the SQL table definition level (you can see it if you do `\d ` in `psql` for instance), and which would be taken into account while using a raw `insert` query, outside the ORM. On the other hand, `default` will only be working at the ORM level: if you create a model object and save it, it will contain the default value (because the ORM has explicitly set it), while a raw `insert` query would result in a record without the default value. – cjauvin Mar 14 '20 at 17:24
  • I forgot to mention it in my previous comment, and I feel it's important somehow (I cannot edit my comment anymore unfortunately): what I describe is true in the context of a Postgres database; I'm not certain it applies to other databases though. – cjauvin Mar 14 '20 at 18:05
  • this code also fails to append the timezone. Since Python datetime does not include the tz, these timestamps can't be read from a javascript frontend, for instance. – Christian Davis Jun 21 '21 at 19:53
  • @ChristianDavis if you need the timezone use a lambda instead of directly referencing the function. eg: `Column(Datetime, lambda: default=datetime.datetime.now(datetime.timezone.utc))` – Philip Couling Oct 26 '21 at 14:15
88

You can also use sqlalchemy builtin function for default DateTime

from sqlalchemy.sql import func

DT = Column(DateTime(timezone=True), default=func.now())
taras
  • 6,566
  • 10
  • 39
  • 50
qwerty
  • 1,451
  • 13
  • 10
  • 15
    You can also use `server_default` instead of `default` so value will by handled by database itself. – rgtk Jun 06 '15 at 14:55
  • 3
    Isn't func.now() executed when the descriptor is defined, so all models/children (if this is a base class) will have the same DT value. By passing in a function reference like 'datetime.datetime.utcnow' it is executed separately for each. This is crucial for 'created' or 'updated' properties. – Metalstorm Feb 26 '16 at 01:27
  • 14
    @Metalstorm No, this is correct. sqlalchemy.sql.func is a special case that returns a sqlalchemy.sql.functions.now instance, not the current time. http://docs.sqlalchemy.org/en/latest/core/defaults.html#sql-expressions – Kris Hardy Mar 15 '16 at 22:25
  • 4
    What does `timezone=True` do? – ChaimG Jun 23 '16 at 01:41
  • 2
    @self, From [the documentation](http://docs.sqlalchemy.org/en/latest/core/type_basics.html): _"If True, and supported by the backend, will produce ‘TIMESTAMP WITH TIMEZONE’. For backends that don’t support timezone aware timestamps, has no effect._ . – ChaimG Jun 23 '16 at 01:45
15

Using the default parameter with datetime.now:

from sqlalchemy import Column, Integer, DateTime
from datetime import datetime
class Test(Base):
     __tablename__ = 'test'
     id = Column(Integer, primary_key=True)
     created_at = Column(DateTime, default=datetime.now)
     updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now)                                                            
Fernando Nogueira
  • 1,302
  • 1
  • 14
  • 22
Rahul Mishra
  • 224
  • 2
  • 4
14

You likely want to use onupdate=datetime.now so that UPDATEs also change the last_updated field.

SQLAlchemy has two defaults for python executed functions.

  • default sets the value on INSERT, only once
  • onupdate sets the value to the callable result on UPDATE as well.
SuperShoot
  • 9,880
  • 2
  • 38
  • 55
user3389572
  • 381
  • 3
  • 5
  • 2
    I dont know why, but for some reason `onupdate` doesn't do anything for me. – Vikas Prasad Aug 17 '18 at 07:24
  • 7
    I finally got it working on Postgres db by doing this: `created_at = db.Column(db.DateTime, server_default=UtcNow())` and `updated_at = db.Column(db.DateTime, server_default=UtcNow(), onupdate=UtcNow())` where `UtcNow` is a class as `class UtcNow(expression.FunctionElement): type = DateTime()` and we have `@compiles(UtcNow, 'postgresql') def pg_utc_now(element, compiler, **kw): return "TIMEZONE('utc', CURRENT_TIMESTAMP)"` – Vikas Prasad Aug 17 '18 at 08:43
  • I believe `sqlalchmey.sql.func.now()` is preferred, though won't be too different. It's slightly better to use DB time instead of server time if the system is distributed & many different users may make changes around the same time – Charles L. Apr 07 '21 at 23:03
  • is there a way to disable the onupdate call for python-executed functions? https://docs.sqlalchemy.org/en/14/core/defaults.html – fersarr Nov 26 '21 at 10:31
  • I did this same thing and I got an error due to not passing any args into UtcNow in the DB model. – Robert Riley Feb 15 '22 at 14:38
6

The default keyword parameter should be given to the Column object.

Example:

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

The default value can be a callable, which here I defined like the following.

from pytz import timezone
from datetime import datetime

UTC = timezone('UTC')

def time_now():
    return datetime.now(UTC)
Keith
  • 42,110
  • 11
  • 57
  • 76
2

For mariadb thats worked for me:

from sqlalchemy import Column, Integer, String, DateTime, TIMESTAMP, text
from sqlalchemy.sql import func
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

class Test(Base):
  __tablename__ = "test"

  id              = Column(Integer, primary_key=True, autoincrement=True)
  name            = Column(String(255), nullable=False)
  email           = Column(String(255), nullable=False)
  created_at      = Column(TIMESTAMP, nullable=False, server_default=func.now())
  updated_at      = Column(DateTime, server_default=text("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP"))

In the sqlalchemy documentation for mariadb, it is recommended to import the textfrom sqlalchemy itself and set the server_default with the text, inserting the custom command.

updated_at=Column(DateTime, server_default=text("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP"))

To understand func.now you can read the sql alchemy documentation.

Hope I helped in some way.

1

As per PostgreSQL documentation:

now, CURRENT_TIMESTAMP, LOCALTIMESTAMP return the time of transaction

This is considered a feature: the intent is to allow a single transaction to have a consistent notion of the "current" time, so that multiple modifications within the same transaction bear the same time stamp.

You might want to use statement_timestamp or clock_timestamp if you don't want transaction timestamp.

statement_timestamp()

returns the start time of the current statement (more specifically, the time of receipt of the latest command message from the client). statement_timestamp

clock_timestamp()

returns the actual current time, and therefore its value changes even within a single SQL command.

Maicon Mauricio
  • 2,052
  • 1
  • 13
  • 29
abhi shukla
  • 1,119
  • 1
  • 10
  • 19
0

Jeff Widman said on his answer that you need to create your own implementation of UTC timestamps for func.utcnow()

As I didnt want to implement it myself, I have searched for and found a python package which already does the job and is maintained by many people.

The package name is spoqa/sqlalchemy-ut.

A summary of what the package does is: Long story short, UtcDateTime does:

take only aware datetime.datetime, return only aware datetime.datetime, never take or return naive datetime.datetime, ensure timestamps in database always to be encoded in UTC, and work as you’d expect.

user5193682
  • 260
  • 1
  • 11
0

Note that for server_default=func.now() and func.now() to work :

Local_modified = Column(DateTime, server_default=func.now(), onupdate=func.now())

you need to set DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP in your table DDL.

For example

create table test
(
    id int auto_increment
        primary key,
    source varchar(50) null,
    Local_modified datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
)
collate=utf8mb4_bin;

Otherwise, server_default=func.now(), onupdate=func.now() makes no effects.

Rick
  • 7,007
  • 2
  • 49
  • 79
  • 'ON UPDATE' is a mysql specific syntax; https://dev.mysql.com/doc/refman/8.0/en/timestamp-initialization.html – spazm Apr 14 '23 at 18:58
-1

You can use TIMESTAMP with sqlalchemy.

from sqlalchemy import TIMESTAMP, Table, MetaData, Column, ...

... ellipsis ...  
def function_name(self) -> Table:  
    return Table(  
        "table_name",  
        self._metadata,  
        ...,
        Column("date_time", TIMESTAMP),  
    )  
... ellipsis ...  
romulus
  • 1
  • 1