"""Repository capability protocols for use-case dependency injection.
Each protocol represents an independent repository capability. Use cases
declare only the capabilities they need as their third generic parameter
(``UseCase[TModel, TResult, TCapability]``). Infrastructure repositories
declare which capabilities they implement via explicit inheritance so that
``@repository_for`` can auto-register them in the DI container.
``RepoFor`` remains as a full-surface composition for use cases that need
the complete standard CRUD interface.
"""
from __future__ import annotations
from typing import Any, Protocol, TypeVar
import msgspec
from loom.core.repository.abc.query import (
CursorResult,
FilterParams,
PageParams,
PageResult,
QuerySpec,
)
ModelT = TypeVar("ModelT", bound=msgspec.Struct, covariant=True)
[docs]
class Readable(Protocol[ModelT]):
"""Repository capability: fetch individual entities.
Implement this to expose single-entity read operations.
Example::
class IProductRepository(Readable[Product], Protocol):
async def find_by_sku(self, sku: str) -> Product | None: ...
"""
[docs]
async def get_by_id(self, obj_id: Any, profile: str = "default") -> ModelT | None:
"""Fetch one entity by primary key."""
...
[docs]
async def get_by(
self,
field: str,
value: Any,
profile: str = "default",
) -> ModelT | None:
"""Fetch one entity by arbitrary field."""
...
[docs]
class Creatable(Protocol[ModelT]):
"""Repository capability: persist new entities.
Example::
class CreateOrderUseCase(UseCase[Order, Order, Creatable[Order]]):
async def execute(self, cmd: CreateOrderCommand = Input()) -> Order:
return await self.main_repo.create(cmd)
"""
[docs]
async def create(self, data: msgspec.Struct) -> ModelT:
"""Persist one entity and return the persisted result."""
...
[docs]
class Updatable(Protocol[ModelT]):
"""Repository capability: mutate existing entities."""
[docs]
async def update(self, obj_id: Any, data: msgspec.Struct) -> ModelT | None:
"""Update one entity by primary key."""
...
[docs]
class Deletable(Protocol[ModelT]):
"""Repository capability: remove entities."""
[docs]
async def delete(self, obj_id: Any) -> bool:
"""Delete one entity by primary key.
Returns:
``True`` if the entity existed and was deleted.
"""
...
[docs]
class Listable(Protocol[ModelT]):
"""Repository capability: paginated and structured listing."""
[docs]
async def list_paginated(
self,
page_params: PageParams,
filter_params: FilterParams | None = None,
profile: str = "default",
) -> PageResult[ModelT]:
"""Fetch entities with offset pagination."""
...
[docs]
async def list_with_query(
self,
query: QuerySpec,
profile: str = "default",
) -> PageResult[ModelT] | CursorResult[ModelT]:
"""Fetch entities using a structured query (offset or cursor mode)."""
...
[docs]
class Countable(Protocol[ModelT]):
"""Repository capability: existence checks and counting."""
[docs]
async def exists_by(self, field: str, value: Any) -> bool:
"""Return ``True`` if any entity matches ``field == value``."""
...
[docs]
async def count(self) -> int:
"""Return the total number of entities."""
...
[docs]
class RepoFor(
Readable[ModelT],
Creatable[ModelT],
Updatable[ModelT],
Deletable[ModelT],
Listable[ModelT],
Countable[ModelT],
Protocol[ModelT],
):
"""Full-surface repository protocol for standard CRUD use cases.
Composes all capability protocols. Use this as the default ``RepoT``
when a use case needs the complete CRUD surface. For use cases that
only need a subset of operations, prefer the specific capability protocol
(``Readable``, ``Creatable``, etc.) to respect the Interface Segregation
Principle.
Example::
# Full surface — no third param needed, RepoFor[Any] is the default
class CreateProductUseCase(UseCase[Product, Product]):
async def execute(self, cmd: CreateProductCommand = Input()) -> Product:
return await self.main_repo.create(cmd)
# Specific capability — only what the use case needs
class GetProductUseCase(UseCase[Product, Product | None, Readable[Product]]):
async def execute(self, product_id: int) -> Product | None:
return await self.main_repo.get_by_id(product_id)
"""