- Repo-Struktur für Backend, Frontend, Dokumentation und Gitea-Workflows - FastAPI-Skeleton mit pydantic-settings und RFC-7807-artigem Fehlerformat - Vollständiges Datenmodell (15 Tabellen) als SQLAlchemy-2.0-Modelle - Alembic gegen die Async-Engine inklusive Erstmigration - Idempotenter Seed für den deutschen Kategoriebaum und Standardregeln - Endpunkte /api/health und /api/version - pytest-Infrastruktur mit transaktionsisolierten Fixtures Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014e7t8UpmoVNMtWivY5LiSH
52 lines
1.5 KiB
Python
52 lines
1.5 KiB
Python
"""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
|
||
)
|