"""SQLAlchemy-Basis, Engine und Sitzungen.""" from collections.abc import Iterator from contextlib import contextmanager from datetime import UTC, datetime from sqlalchemy import DateTime, MetaData, create_engine from sqlalchemy.engine import Engine from sqlalchemy.orm import DeclarativeBase, Session, sessionmaker # Einheitliche Namen für Constraints, damit Alembic-Migrationen stabil bleiben NAMING = { "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) type_annotation_map = {datetime: DateTime(timezone=True)} def now() -> datetime: return datetime.now(UTC) class Database: def __init__(self, url: str): self.engine: Engine = create_engine(url, pool_pre_ping=True) self.sessions = sessionmaker(self.engine, expire_on_commit=False) @contextmanager def session(self) -> Iterator[Session]: """Eine Transaktion: commit am Ende, rollback bei Fehler.""" with self.sessions() as session, session.begin(): yield session def dispose(self) -> None: self.engine.dispose()