"""SQLAlchemy engine and session factory. Note: Engine and session factory are cached globally per process. Only one DATABASE_URL is effectively used for the process lifetime. Using a different URL later (e.g. in tests with in-memory SQLite) would still use the first engine. To support multiple URLs, cache by database_url (e.g. a dict keyed by URL). """ from contextlib import contextmanager from typing import Generator from sqlalchemy import create_engine from sqlalchemy.orm import Session, sessionmaker _engine = None _SessionLocal = None @contextmanager def session_scope(database_url: str) -> Generator[Session, None, None]: """Context manager: yields a session and closes it on exit.""" session = get_session(database_url) try: yield session finally: session.close() def get_engine(database_url: str): global _engine if _engine is None: _engine = create_engine( database_url, connect_args={"check_same_thread": False} if "sqlite" in database_url else {}, echo=False, ) return _engine def get_session_factory(database_url: str) -> sessionmaker[Session]: global _SessionLocal if _SessionLocal is None: engine = get_engine(database_url) _SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) return _SessionLocal def get_session(database_url: str) -> Session: return get_session_factory(database_url)()