Source code for httk.store.mongo.database
"""MongoDB client lifecycle and transaction-mode probing."""
from types import TracebackType
from typing import Any, Self
from pymongo import MongoClient
from pymongo.read_preferences import ReadPreference
__all__ = ["MongoDatabase", "TransactionsUnavailableError"]
[docs]
class TransactionsUnavailableError(RuntimeError):
"""An explicit transaction was requested without replica-set support."""
[docs]
class MongoDatabase:
"""A named MongoDB database reached through a wrapped PyMongo client.
The wrapper performs one ``hello`` probe when it is constructed. A
replica-set name in that reply enables transaction mode unless the caller
explicitly pins degraded mode.
:param client: The PyMongo client that owns the connection pool.
:param database: The database name.
:param transactions: ``"auto"``, ``"require"``, or ``"never"``.
:raises ValueError: If ``transactions`` is invalid or required transactions
are unavailable.
"""
def __init__(self, client: MongoClient, database: str, *, transactions: str = "auto") -> None:
if transactions not in {"auto", "require", "never"}:
raise ValueError("transactions must be one of 'auto', 'require', or 'never'")
self._client = client
# Store-level handles must never inherit a caller's secondary-preferred
# default: dedup and metadata observations are primary-authoritative.
self._database = client.get_database(database, read_preference=ReadPreference.PRIMARY)
hello = client.admin.command("hello")
replica_set = bool(hello.get("setName"))
if transactions == "require" and not replica_set:
raise ValueError("transactions='require' needs a MongoDB replica set")
self._supports_transactions = replica_set and transactions != "never"
self._transactions_mode = transactions
@classmethod
[docs]
def connect(cls, uri: str, *, database: str, transactions: str = "auto") -> Self:
"""Connect to a MongoDB URI with durable majority defaults.
:param uri: MongoDB connection URI.
:param database: Database name to expose through the wrapper.
:param transactions: ``"auto"``, ``"require"``, or ``"never"``.
:return: A connected database wrapper.
:raises ValueError: If the requested transaction mode is unavailable.
"""
client: MongoClient[dict[str, Any]] = MongoClient(uri, w="majority", journal=True, readConcernLevel="majority")
try:
return cls(client, database, transactions=transactions)
except BaseException:
client.close()
raise
@property
[docs]
def client(self) -> MongoClient:
"""Return the wrapped PyMongo client.
:return: The client that owns this database connection pool.
"""
return self._client
@property
[docs]
def database(self) -> Any:
"""Return the selected PyMongo database handle.
:return: The PyMongo database handle.
"""
return self._database
@property
[docs]
def supports_transactions(self) -> bool:
"""Whether this wrapper is configured to use MongoDB transactions.
:return: ``True`` when the mode probe found a replica set and the mode
was not pinned to ``"never"``.
"""
return self._supports_transactions
[docs]
def dispose(self) -> None:
"""Close the client and its connection pool.
:return: None.
"""
self._client.close()
def __enter__(self) -> Self:
"""Enter a context that owns this client's connection pool.
:return: This database wrapper.
"""
return self
def __exit__(
self,
exc_type: type[BaseException] | None,
exc_value: BaseException | None,
traceback: TracebackType | None,
) -> None:
"""Close the client when leaving the context.
:param exc_type: The exception class raised in the context, if any.
:param exc_value: The exception instance raised in the context, if any.
:param traceback: The traceback for the context exception, if any.
:return: None.
"""
self.dispose()
def __repr__(self) -> str:
return f"{type(self).__name__}({self._database.name!r})"