All checks were successful
CI / lint-and-test (push) Successful in 17s
- Created a new `CHANGELOG.md` file to document all notable changes to the project, adhering to the Keep a Changelog format. - Updated `CONTRIBUTING.md` to include instructions for building and previewing documentation using MkDocs. - Added `mkdocs.yml` configuration for documentation generation, including navigation structure and theme settings. - Enhanced various documentation files, including API reference, architecture overview, configuration reference, and runbook, to provide comprehensive guidance for users and developers. - Included new sections in the README for changelog and documentation links, improving accessibility to project information.
68 lines
2.1 KiB
Python
68 lines
2.1 KiB
Python
"""SQLAlchemy engine and session factory.
|
|
|
|
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 use a different
|
|
URL in tests, set env (e.g. DATABASE_URL) before the first import of this module, or
|
|
clear _engine and _SessionLocal in test fixtures. Prefer session_scope() for all
|
|
callers so sessions are always closed and rolled back on error.
|
|
"""
|
|
|
|
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 that yields a session; rolls back on exception, closes on exit.
|
|
|
|
Args:
|
|
database_url: SQLAlchemy database URL.
|
|
|
|
Yields:
|
|
Session instance. Caller must not use it after exit.
|
|
"""
|
|
session = get_session(database_url)
|
|
try:
|
|
yield session
|
|
except Exception:
|
|
session.rollback()
|
|
raise
|
|
finally:
|
|
session.close()
|
|
|
|
|
|
def get_engine(database_url: str):
|
|
"""Return cached SQLAlchemy engine for the given URL (one per process)."""
|
|
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]:
|
|
"""Return cached session factory for the given URL (one per process)."""
|
|
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:
|
|
"""Create a new session from the factory for the given URL."""
|
|
return get_session_factory(database_url)()
|