Files
kadence/backend/app/db.py
T
2026-10-18 18:02:05 +03:00

63 lines
1.8 KiB
Python

"""Database engine and session plumbing.
Synchronous SQLAlchemy on purpose: the background scheduler, the Telegram
polling bot and the request handlers all touch the same tables, and one boring
threadsafe sessionmaker is far easier to reason about than three event loops.
FastAPI runs plain `def` endpoints in a threadpool, so nothing blocks.
"""
from __future__ import annotations
from collections.abc import Iterator
from contextlib import contextmanager
from sqlalchemy import create_engine
from sqlalchemy.orm import DeclarativeBase, Session, sessionmaker
from app.config import env_settings
class Base(DeclarativeBase):
pass
def _engine_kwargs(url: str) -> dict:
if url.startswith("sqlite"):
# One file, many threads (scheduler + bot + requests).
return {"connect_args": {"check_same_thread": False}}
return {"pool_pre_ping": True, "pool_size": 5, "max_overflow": 10}
engine = create_engine(env_settings().database_url, **_engine_kwargs(env_settings().database_url))
SessionLocal = sessionmaker(bind=engine, autoflush=False, expire_on_commit=False)
def init_db() -> None:
"""Create any missing tables. The schema is additive, so this is enough."""
from app import models # noqa: F401 (register the mappers)
Base.metadata.create_all(bind=engine)
@contextmanager
def session_scope() -> Iterator[Session]:
"""A transactional session for background jobs and scripts."""
session = SessionLocal()
try:
yield session
session.commit()
except Exception:
session.rollback()
raise
finally:
session.close()
def get_session() -> Iterator[Session]:
"""FastAPI dependency: one session per request, committed by the endpoint."""
session = SessionLocal()
try:
yield session
finally:
session.close()