SQLAlchemy default DateTime
PythonDateSqlalchemyPython Problem Overview
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?
Python Solutions
Solution 1 - Python
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.
server_default
Use SQLALchemy's 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.
onupdate
/server_onupdate
Understanding SQLAlchemy's 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.
Solution 2 - Python
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)
Solution 3 - Python
You can also use sqlalchemy builtin function for default DateTime
from sqlalchemy.sql import func
DT = Column(DateTime(timezone=True), default=func.now())
Solution 4 - Python
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 onceonupdate
sets the value to the callable result on UPDATE as well.
Solution 5 - Python
Using the default
parameter with datetime.now
:
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)
Solution 6 - Python
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)
Solution 7 - Python
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.
Solution 8 - Python
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 text
from 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.
Solution 9 - Python
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.
Solution 10 - Python
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.