Files
duty-teller/duty_teller/db/session.py
Nikolay Tatarinov 86f6d66865
All checks were successful
CI / lint-and-test (push) Successful in 17s
chore: add changelog and documentation updates
- 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.
2026-02-20 15:32:10 +03:00

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)()