"""Helpers for bringing older local SQLite databases under Alembic control.""" import os import sqlite3 from dotenv import load_dotenv DEFAULT_DATABASE_URL = "sqlite+aiosqlite:///./data/discovery.db" CURRENT_REVISION = "001" REQUIRED_TABLES = {"discovery_conversation", "discovery_profile"} def _sqlite_path(database_url: str) -> str | None: if not database_url.startswith("sqlite") or ":///" not in database_url: return None return database_url.split(":///", 1)[1] def stamp_existing_sqlite_schema( database_url: str, revision: str = CURRENT_REVISION ) -> bool: """Stamp a pre-Alembic SQLite DB when it already has the app tables. Early local/dev runs could create tables through SQLAlchemy create_all() before Alembic was applied. In that case, `alembic upgrade head` tries to create tables that already exist. This marks that compatible schema as revision 001 so normal migrations can continue. """ path = _sqlite_path(database_url) if not path or not os.path.exists(path): return False with sqlite3.connect(path) as conn: rows = conn.execute( "SELECT name FROM sqlite_master WHERE type = 'table'" ).fetchall() tables = {row[0] for row in rows} if not REQUIRED_TABLES.issubset(tables): return False if "alembic_version" in tables: versions = conn.execute( "SELECT version_num FROM alembic_version" ).fetchall() if versions: return False else: conn.execute( "CREATE TABLE alembic_version " "(version_num VARCHAR(32) NOT NULL)" ) conn.execute( "INSERT INTO alembic_version (version_num) VALUES (?)", (revision,), ) conn.commit() return True def main() -> None: load_dotenv() database_url = os.getenv("DATABASE_URL", DEFAULT_DATABASE_URL) stamped = stamp_existing_sqlite_schema(database_url) if stamped: print(f"Stamped existing SQLite schema as Alembic revision {CURRENT_REVISION}") if __name__ == "__main__": main()