Source code for loom.core.repository.sqlalchemy.registry
from __future__ import annotations
from collections.abc import Callable, Sequence
from dataclasses import dataclass
from typing import Any
from loom.core.di.container import LoomContainer
from loom.core.model import BaseModel
from loom.core.repository import (
DefaultRepositoryBuilder,
RepositoryBuildContext,
)
from loom.core.repository import (
build_repository_registration_module as build_main_repository_module,
)
from loom.core.repository.registry import (
RepositoryRegistration,
RepositoryToken,
get_repository_registration,
repository_for,
)
from loom.core.repository.sqlalchemy.repository import RepositorySQLAlchemy
from loom.core.repository.sqlalchemy.session_manager import SessionManager
__all__ = [
"RepositoryRegistration",
"RepositoryToken",
"SQLAlchemyDefaultRepositoryBuilder",
"build_sqlalchemy_repository_registration_module",
"get_repository_registration",
"repository_for",
]
[docs]
@dataclass(frozen=True)
class SQLAlchemyDefaultRepositoryBuilder:
"""Default repository builder for SQLAlchemy-backed models.
A frozen dataclass that receives a ``SessionManager`` at construction
time — injected by the SQLAlchemy DI module. The bootstrap and any
other infrastructure layer must not construct this class directly;
register it via the DI module so that the ``SessionManager`` singleton
is shared across all repositories.
Args:
session_manager: Shared SQLAlchemy session manager.
"""
session_manager: SessionManager
def __call__(self, context: RepositoryBuildContext) -> Any:
if not issubclass(context.model, BaseModel):
raise RuntimeError(
f"SQLAlchemyDefaultRepositoryBuilder cannot build a repository "
f"for non-persistible type {context.model.__qualname__}"
)
return RepositorySQLAlchemy(
session_manager=self.session_manager,
model=context.model,
)
[docs]
def build_sqlalchemy_repository_registration_module(
session_manager: SessionManager,
models: Sequence[type[BaseModel]],
*,
logical_models: Sequence[type[Any]] = (),
) -> Callable[[LoomContainer], None]:
"""Build a DI module that registers model repositories and their capability bindings.
The module self-declares its infrastructure dependencies: it registers both
``SessionManager`` and ``DefaultRepositoryBuilder`` in the container so that
the bootstrap does not need to know about SQLAlchemy internals.
To swap the default builder, register your own ``DefaultRepositoryBuilder``
in the container *before* loading this module — the module will not
overwrite an existing registration.
"""
def _registered_builder(
context: RepositoryBuildContext,
registration: RepositoryRegistration,
) -> Any:
if registration.builder is not None:
return registration.builder(context)
repository_type = registration.repository_type
if not isinstance(repository_type, type):
raise RuntimeError(
"Repository registration for "
f"{registration.model.__qualname__} must use a class type."
)
if issubclass(repository_type, RepositorySQLAlchemy):
if not issubclass(context.model, BaseModel):
raise RuntimeError(
f"{repository_type.__qualname__} requires BaseModel-compatible registrations."
)
return repository_type(
session_manager=session_manager,
model=context.model,
)
return repository_type()
register = build_main_repository_module(
models=models,
explicit_models=logical_models,
build_registered_repository=_registered_builder,
)
def _register_with_session(container: LoomContainer) -> None:
if not container.is_registered(SessionManager):
container.register_instance(SessionManager, session_manager)
if not container.is_registered(DefaultRepositoryBuilder):
container.register_instance(
DefaultRepositoryBuilder,
SQLAlchemyDefaultRepositoryBuilder(
session_manager=container.resolve(SessionManager),
),
)
register(container)
return _register_with_session