import uuid from datetime import UTC, datetime from typing import Any from sqlalchemy import DateTime, MetaData, TypeDecorator from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column NAMING_CONVENTION = { "ix": "ix_%(column_0_label)s", "uq": "uq_%(table_name)s_%(column_0_name)s", "ck": "ck_%(table_name)s_%(constraint_name)s", "fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s", "pk": "pk_%(table_name)s", } class Base(DeclarativeBase): metadata = MetaData(naming_convention=NAMING_CONVENTION) class UTCDateTime(TypeDecorator): """Datetimes stored as naive UTC in the DB and exposed tz-aware (UTC) in Python.""" impl = DateTime cache_ok = True def process_bind_param(self, value: Any, dialect: Any) -> Any: if value is not None: if value.tzinfo is None: value = value.replace(tzinfo=UTC) value = value.astimezone(UTC).replace(tzinfo=None) return value def process_result_value(self, value: Any, dialect: Any) -> Any: if value is not None: value = value.replace(tzinfo=UTC) return value def utcnow() -> datetime: return datetime.now(UTC) class PkMixin: id: Mapped[uuid.UUID] = mapped_column(primary_key=True, default=uuid.uuid4) class TimestampMixin: created_at: Mapped[datetime] = mapped_column(UTCDateTime(), default=utcnow, nullable=False) updated_at: Mapped[datetime] = mapped_column( UTCDateTime(), default=utcnow, onupdate=utcnow, nullable=False )