Source code for pytams.sqlmanager

"""A base class for pyTAMS databases."""

from __future__ import annotations
import logging
from contextlib import contextmanager
from pathlib import Path
from typing import TYPE_CHECKING
from sqlalchemy import MetaData
from sqlalchemy import create_engine
from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy.orm import Session
from sqlalchemy.orm import sessionmaker

if TYPE_CHECKING:
    from collections.abc import Generator

_logger = logging.getLogger(__name__)


[docs] class BaseSQLManager: """A base class to handle SQLAlchemy engine and session boilerplate.""" def __init__(self, file_name: str, base_metadata: MetaData, in_memory: bool = False, ro_mode: bool = False) -> None: """Initialize the file. Args: file_name : The file name base_metadata: the table metadata to initialize in_memory: a bool to trigger in-memory creation ro_mode: a bool to trigger read-only access to the database """ self._file_name = "" if in_memory else file_name # URI mode requires absolute path file_path = Path(file_name).absolute().as_posix() if in_memory: self._engine = create_engine("sqlite:///:memory:", echo=False) else: uri = f"sqlite:///file:{file_path}?mode=ro&uri=true" if ro_mode else f"sqlite:///{file_path}" self._engine = create_engine(uri, echo=False) self._Session = sessionmaker(bind=self._engine, expire_on_commit=False) self._init_db(base_metadata) def _init_db(self, base_metadata: MetaData) -> None: """Initialize the tables of the file. Raises: RuntimeError : If a connection to the DB could not be acquired """ try: base_metadata.create_all(self._engine) except SQLAlchemyError: _logger.exception("Failed to initialize DB schema") raise @contextmanager
[docs] def session_scope(self) -> Generator[Session, None, None]: """Provide a transactional scope around a series of operations.""" session = self._Session() try: yield session session.commit() except Exception: session.rollback() raise finally: session.close()
[docs] def name(self) -> str: """Access the DB file name. Returns: the database name, empty string if in-memory """ return self._file_name
[docs] def close(self) -> None: """Dispose of the engine and clear connections.""" if self._engine: self._engine.dispose()