bank-fraud / app /db /session.py
root
init
942b115
Raw
History Blame Contribute Delete
3.11 kB
"""SQLAlchemy engine, session factory, and an explicit schema-creation helper.
The engine is built from `settings.DATABASE_URL` (`app.config`, already
fail-loud-validated at import time) -- no credentials are hardcoded or
logged anywhere in this module. Connection-failure messages name the
database host/port/name being targeted but intentionally never include
`DB_PASSWORD` or the raw `DATABASE_URL`.
`create_all()` is NOT called at import time or anywhere else in this module
automatically. Table creation is a deliberate, explicit step gated on human
confirmation of `migrations/schema.sql` (DB-02) -- importing this module
must never mutate the database.
"""
from __future__ import annotations
from collections.abc import Generator
from contextlib import contextmanager
from sqlalchemy import create_engine
from sqlalchemy.engine import Engine
from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy.orm import Session, sessionmaker
from app.config import settings
from app.db.models import Base
engine: Engine = create_engine(settings.DATABASE_URL, pool_pre_ping=True, future=True)
SessionLocal = sessionmaker(bind=engine, autoflush=False, autocommit=False, future=True)
def get_session() -> Generator[Session, None, None]:
"""FastAPI-dependency-style session generator.
Usage: `db: Session = Depends(get_session)` in a router. Closes the
session after the request regardless of outcome.
"""
session = SessionLocal()
try:
yield session
finally:
session.close()
@contextmanager
def session_scope() -> Generator[Session, None, None]:
"""Context-manager session for scripts (training/, one-off maintenance)
that run outside FastAPI's dependency-injection system. Commits on
success, rolls back on any exception, always closes.
"""
session = SessionLocal()
try:
yield session
session.commit()
except Exception:
session.rollback()
raise
finally:
session.close()
def create_all() -> None:
"""Create every table defined on `Base.metadata` against the configured
MySQL database.
This is the explicit, human-approved migration step for phase 01-02
(DB-02): only invoke this after `migrations/schema.sql` has been
reviewed and confirmed by the user. Never called automatically by
importing this module.
Raises `RuntimeError` with a clear, credential-free message if the
database is unreachable or rejects the DDL -- the password is never
included, only the host/port/database name being targeted.
"""
try:
Base.metadata.create_all(bind=engine)
except SQLAlchemyError as exc:
raise RuntimeError(
"Could not create the database schema: the configured MySQL "
f"database (host={settings.DB_HOST}, port={settings.DB_PORT}, "
f"db={settings.DB_NAME}) is unreachable or rejected the "
"operation. Check DB_HOST/DB_PORT/DB_NAME/DB_USER/DB_PASSWORD "
"in .env -- credential values are intentionally omitted from "
"this message."
) from exc