Spaces:
Sleeping
Sleeping
Aissugur Kurganbekov
Claude Opus 4.8
Add hybrid retrieval (TF-IDF + embeddings), OCR, A/B eval, and answer trust/UX
f65a6b3 Download backend/app/database.py from financeflow/ai: direct link, hf CLI and curl.
- Browser
- Download file 2.65 kB
-
https://huggingface.co/spaces/financeflow/ai/resolve/main/backend/app/database.py
- Command line
-
hf download hf://spaces/financeflow/ai/backend/app/database.py
-
curl -L -o database.py https://huggingface.co/spaces/financeflow/ai/resolve/main/backend/app/database.py
2.65 kB
| """SQLAlchemy engine, session, and Base used across the app.""" | |
| from sqlalchemy import create_engine, event | |
| from sqlalchemy.engine import Engine | |
| from sqlalchemy.orm import declarative_base, sessionmaker | |
| from .config import DATABASE_URL | |
| # check_same_thread=False is required for SQLite used by FastAPI's thread pool. | |
| engine = create_engine( | |
| DATABASE_URL, | |
| connect_args={"check_same_thread": False}, | |
| ) | |
| def _enable_sqlite_foreign_keys(dbapi_connection, _connection_record): | |
| """SQLite ignores ON DELETE CASCADE unless foreign keys are enabled per | |
| connection. Without this, the FK constraints on chunks/qa_history would be | |
| dead weight and rely solely on ORM-level cascades.""" | |
| cursor = dbapi_connection.cursor() | |
| cursor.execute("PRAGMA foreign_keys=ON") | |
| cursor.close() | |
| SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) | |
| Base = declarative_base() | |
| def ensure_schema() -> None: | |
| """Tiny, idempotent migration for an MVP that uses create_all (no Alembic). | |
| create_all() creates missing TABLES but never adds a new COLUMN to a table | |
| that already exists. So when a model gains a column (e.g. chunks.embedding for | |
| hybrid retrieval), an older on-disk SQLite file would 'no such column' on the | |
| next query. This adds any such columns in place. Safe to call repeatedly and | |
| before the tables exist (it simply skips what isn't there yet), and never | |
| raises — a migration hiccup must not brick startup. | |
| """ | |
| from sqlalchemy import inspect, text | |
| # (table, column, sqlite_type) added after the initial schema. | |
| additions = [("chunks", "embedding", "BLOB")] | |
| try: | |
| insp = inspect(engine) | |
| tables = set(insp.get_table_names()) | |
| except Exception: | |
| return | |
| for table, column, col_type in additions: | |
| if table not in tables: | |
| continue # fresh DB: create_all() will make it with the column | |
| try: | |
| cols = {c["name"] for c in insp.get_columns(table)} | |
| if column not in cols: | |
| with engine.begin() as conn: | |
| conn.execute(text(f"ALTER TABLE {table} ADD COLUMN {column} {col_type}")) | |
| except Exception: | |
| pass # leave the DB as-is rather than crash on import | |
| # Run on import so every entrypoint (app, eval CLIs, scripts) gets a current | |
| # schema before its first query, including processes that never call create_all(). | |
| ensure_schema() | |
| def get_db(): | |
| """FastAPI dependency that yields a DB session and always closes it.""" | |
| db = SessionLocal() | |
| try: | |
| yield db | |
| finally: | |
| db.close() | |