from collections.abc import Generator from sqlalchemy import create_engine, inspect, text from sqlalchemy.orm import DeclarativeBase, Session, sessionmaker from app.core.config import settings class Base(DeclarativeBase): pass def build_engine(database_url: str): connect_args: dict[str, object] = {} if database_url.startswith("sqlite"): connect_args["check_same_thread"] = False return create_engine(database_url, future=True, connect_args=connect_args) engine = build_engine(settings.database_url) SessionLocal = sessionmaker( bind=engine, autoflush=False, autocommit=False, expire_on_commit=False, class_=Session, ) def get_db() -> Generator[Session, None, None]: db = SessionLocal() try: yield db finally: db.close() def init_db() -> None: import app.models # noqa: F401 # Keep a lightweight runtime bootstrap for empty local databases so the app # can still start before Alembic is wired into every environment. Base.metadata.create_all(bind=engine) _upgrade_legacy_schema() def _upgrade_legacy_schema() -> None: inspector = inspect(engine) table_names = set(inspector.get_table_names()) if "users" not in table_names: return existing_columns = {column["name"] for column in inspector.get_columns("users")} if "token_version" in existing_columns: pass else: # Keep this compatibility path narrowly scoped for older local databases # that predate the Alembic baseline migration. with engine.begin() as connection: connection.execute( text( "ALTER TABLE users " "ADD COLUMN token_version INTEGER NOT NULL DEFAULT 0" ) ) if "telegram_links" not in table_names: Base.metadata.tables["telegram_links"].create(bind=engine)