"""Deklarative Basisklasse und gemeinsame Spaltentypen.""" from datetime import UTC, datetime from decimal import Decimal from sqlalchemy import DateTime, MetaData, Numeric from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column # Feste Namenskonventionen, damit Alembic stabile Constraint-Namen erzeugt. 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", } # Alle Geldbeträge einheitlich als NUMERIC(12,2) -> Decimal. Money = Numeric(12, 2, asdecimal=True) def utcnow() -> datetime: """Zeitstempel in UTC – die Anzeige rechnet nach Europe/Berlin um.""" return datetime.now(UTC) class Base(DeclarativeBase): metadata = MetaData(naming_convention=NAMING_CONVENTION) type_annotation_map = { # noqa: RUF012 Decimal: Money, } class TimestampMixin: """created_at/updated_at für Tabellen mit Änderungsverfolgung.""" created_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), default=utcnow, nullable=False ) updated_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), default=utcnow, onupdate=utcnow, nullable=False ) class CreatedAtMixin: """Nur created_at für rein additive Tabellen.""" created_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), default=utcnow, nullable=False )