SQLAlchemy default DateTime
Question:
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?
Answers:
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)
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)
You can also use sqlalchemy builtin function for default DateTime
from sqlalchemy.sql import func
DT = Column(DateTime(timezone=True), default=func.now())
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.
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.
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.
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)
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.
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.
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.
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 ...
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?
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)
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)
You can also use sqlalchemy builtin function for default DateTime
from sqlalchemy.sql import func
DT = Column(DateTime(timezone=True), default=func.now())
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.
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.
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.
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)
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.
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.
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.
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 ...