A class used in a recent project showing the use of TypeDecorator from sqlalchemy. TypeDecorator facilitates translation behaviour between Python types and database values. In this case, it ensures that all datetime fields using this type are stored and retrieved in UTC, providing consistent timezone handling across the application.
More info from SQLAlchemy docs
class UTCDateTime(TypeDecorator):
"""Custom SQLAlchemy type that automatically converts datetimes to UTC.
This type decorator ensures all datetime values are stored in UTC timezone
in the database, providing consistent timezone handling across the application.
Attributes:
impl: The underlying DateTime type implementation.
cache_ok: Whether this type is safe to cache.
"""
impl = DateTime
cache_ok = True
@property
def python_type(self):
"""Return the Python type object expected for this type."""
return dt.datetime
def process_bind_param(self, value, _dialect):
"""Process the binding parameter and convert to UTC timezone if not None.
Args:
value (datetime): The datetime value to be processed.
_dialect: The SQL dialect (unused).
Returns:
datetime | None: The UTC timezone converted datetime if value is not None.
"""
if value is not None:
return value.astimezone(dt.UTC)
return value
def process_result_value(self, value, _dialect):
"""Process result values - ensures timezone awareness."""
if value is not None and value.tzinfo is None:
return value.replace(tzinfo=dt.UTC)
return value
Usage:
class DBBase(DeclarativeBase):
"""Base class for all database models.
Provides common attributes for tracking record creation and modification times.
All model classes should inherit from this base class to maintain consistent
timestamp tracking.
Attributes:
created: Timestamp when the record was created, stored in UTC.
modified: Timestamp when the record was last modified, stored in UTC.
"""
__abstract__ = True
created: Mapped[dt.datetime] = mapped_column(
UTCDateTime, insert_default=func.now(), nullable=False
)
modified: Mapped[dt.datetime | None] = mapped_column(
UTCDateTime, nullable=True
)