SQLAlchemy: created_at and updated_at columns

Updated: January 6, 2024 By: Guest Contributor Post a comment

Overview

Tracking record inception and modification times is a common requirement for web applications. This tutorial will guide you through the process of using SQLAlchemy to automatically create and update timestamp columns in your database models.

Introduction to Timestamp Columns

Most modern applications have the need to track when a record was created and when it was last updated. This practice not only helps in maintaining data integrity but also provides valuable insights into user behavior and content lifecycle. In ORM (Object-Relational Mapping) frameworks like SQLAlchemy, implementing these timestamp columns can be done efficiently with the use of default column values and database triggers.

Basic Concept

To start with creating created_at and updated_at columns, you might define a model as follows:

from sqlalchemy import create_engine, Column, Integer, DateTime
from sqlalchemy.orm import declarative_base
from sqlalchemy.sql import func

Base = declarative_base()

class ExampleModel(Base):
    __tablename__ = 'example_table'

    id = Column(Integer, primary_key=True)
    created_at = Column(DateTime, default=func.now())
    updated_at = Column(DateTime, default=func.now(), onupdate=func.now())

# Setup the database engine
engine = create_engine('sqlite:///mydatabase.db')
Base.metadata.create_all(engine)

In this basic example, the created_at column will receive the current timestamp when a new record is inserted. The updated_at column has both a default value and an onupdate value, both calling the now() function to record the timestamp when a record is created or modified.

Using Context-Sensitive Default Functions

While the default func.now() works well with direct inserts and updates, it might not suffice for all cases. If you use bulk operations, for instance, updated_at won’t be triggered. In such scenarios, using SQLAlchemy event listeners to manually assign values before committing transactions ensures all records get correct timestamps.

from sqlalchemy import event

@event.listens_for(ExampleModel, 'before_insert')
def receive_before_insert(mapper, connection, target):
    target.created_at = datetime.datetime.now()

@event.listens_for(ExampleModel, 'before_update')
def receive_before_update(mapper, connection, target):
    target.updated_at = datetime.datetime.now()

Advanced Usage

For even more control over your timestamp columns, you can create a base class that defines these properties, ensuring that all your models that require these timestamps inherit from it. An abstract base model with timestamp columns can be defined and used throughout your application:

class TimestampMixin(object):
    created_at = Column(DateTime, default=func.now())
    updated_at = Column(DateTime, default=func.now(), onupdate=func.now())

    @declared_attr
    def __tablename__(cls):
        return cls.__name__.lower()

class User(TimestampMixin, Base):
    id = Column(Integer, primary_key=True)
    name = Column(String)

# All User instances will now have created_at and updated_at columns inherited from the TimestampMixin.

This approach helps maintain consistency and reduces repetition in your model definitions.

Timezone Awareness

Timezone handling is an often-overlooked aspect of timestamp management. When you’re dealing with global applications, it’s important to store timestamps in a timezone-aware format. SQLAlchemy can handle timezone-aware datetime columns by setting the timezone attribute to True:

created_at = Column(DateTime(timezone=True), default=func.now())
updated_at = Column(DateTime(timezone=True), default=func.now(), onupdate=func.now())

Storing timezone-aware timestamps can help in accurately tracking user activities across different geographical locations.

Conclusion

SQLAlchemy provides a flexible and robust way of managing timestamp columns, such as created_at and updated_at. Through the use of default column values, event listeners, and a mix of abstracted model mixins, developers can handle timestamp data with consistency and precision. Always consider the timezone implications of your application’s scope and test thoroughly to ensure reliable timestamp management.