Source code for loom.core.use_case.use_case

from __future__ import annotations

from abc import ABC, abstractmethod
from collections.abc import Sequence
from typing import TYPE_CHECKING, Any, ClassVar, Generic, TypeVar

from loom.core.repository.abc import RepoFor
from loom.core.use_case.compute import ComputeFn
from loom.core.use_case.rule import RuleFn

if TYPE_CHECKING:
    from loom.core.engine.plan import ExecutionPlan

ModelT = TypeVar("ModelT")
ResultT = TypeVar("ResultT")


[docs] class UseCase(ABC, Generic[ModelT, ResultT]): """Base class for all use cases. Subclass and implement ``execute`` with typed parameters. Parameter defaults declare the execution contract: - ``Input()`` — command payload, built from the raw request. - ``LoadById(EntityType, by="param")`` — entity prefetched by id. - ``Load(EntityType, ...)`` — entity prefetched by arbitrary field. - ``Exists(EntityType, ...)`` — boolean existence check by field. - No default — primitive param bound directly from the caller. Class attributes ``computes``, ``rules``, and ``read_only`` declare the pre-execution pipeline and execution policy. They are inspected once at startup by ``UseCaseCompiler`` and embedded in the immutable ``ExecutionPlan``. Attributes: computes: Compute transformations applied in order before rule checks. rules: Rule validations applied in order after computes. read_only: When ``True``, the executor skips opening a ``UnitOfWork`` transaction. Set this on query-only use cases that never mutate state. GET routes in :class:`RestInterface` always bypass the UoW regardless of this flag. Example:: class UpdateUserUseCase(UseCase[UserResponse]): computes = [set_updated_at] rules = [email_must_be_valid] def __init__(self, user_repo: UserRepository) -> None: self._user_repo = user_repo async def execute( self, user_id: int, cmd: UpdateUserCommand = Input(), user: User = LoadById(User, by="user_id"), ) -> UserResponse: ... """ __execution_plan__: ClassVar[ExecutionPlan | None] = None computes: ClassVar[Sequence[ComputeFn[Any]]] = () rules: ClassVar[Sequence[RuleFn]] = () read_only: ClassVar[bool] = False def __init__(self, main_repo: RepoFor[Any] | None = None) -> None: """Initialise the use case base dependencies. Args: main_repo: Optional main repository dependency. When provided, the factory may inject it from ``RepoFor[Model]`` constructor annotations. """ self._main_repo = main_repo def __class_getitem__(cls, params: object) -> object: """Backwards-compatible subscript support. Allows legacy ``UseCase[Result]`` at runtime by treating it as ``UseCase[Any, Result]`` while the codebase migrates to the explicit two-parameter form ``UseCase[Model, Result]``. """ if not isinstance(params, tuple): return super().__class_getitem__((Any, params)) # type: ignore[misc] return super().__class_getitem__(params) # type: ignore[misc] @property def main_repo(self) -> RepoFor[Any]: """Main repository injected by the factory.""" if self._main_repo is None: raise RuntimeError( f"{type(self).__qualname__} requires a main repository but it was not injected." ) return self._main_repo @main_repo.setter def main_repo(self, value: RepoFor[Any]) -> None: self._main_repo = value
[docs] @abstractmethod async def execute(self, *args: Any, **kwargs: Any) -> ResultT: """Execute core business logic. Override with an explicit typed signature. The compiler inspects this method once at startup to build the ``ExecutionPlan``. Returns: The result of the use case operation. """ ...