"""# Summary Generic CRUD service adaptor over a Mongo repository. Bridge pydantic models and mongo-ops documents, exposing create, read, update, patch, delete, and search operations for every domain that needs them. """ from typing import Generic, Type, TypeVar from infra.db.repos import mongo from pydantic import BaseModel from mongo_ops import BaseDocument from mongo_ops.models import PyObjectId Model = TypeVar("Model", bound=BaseModel) Schema = TypeVar("Schema", bound=BaseDocument) class CrudsServiceAdaptor(Generic[Model, Schema]): """Adapt a generic Mongo repository to a model/schema CRUD service. Attributes: repo (mongo.BaseRepository): Repository backing all CRUD operations. model (Type[Model]): Pydantic model type handled by this service. schema (Type[Schema]): Document schema type persisted by the repository. """ repo: mongo.BaseRepository model: Type[Model] schema: Type[Schema] def __init__(self, *, repo=None): """Initialize the adaptor with a backing repository. Args: repo (mongo.BaseRepository): Repository used for all operations. """ self.repo = repo @staticmethod def model_to_dict(model_obj: Model) -> dict: """Serialize a model into a sparse dictionary. Args: model_obj (Model): Model instance to serialize. Returns: dict: Fields that are set and not ``None``. """ return model_obj.model_dump(exclude_unset=True, exclude_none=True) async def create(self, data: Model | dict) -> Schema: """Create a document from a model or raw mapping. Args: data (Model | dict): Model instance or field mapping to persist. Returns: Schema: Persisted document. """ if isinstance(data, dict): data = self.schema(**data) return await self.repo.create(data) async def read(self, id: PyObjectId) -> Schema | None: """Read a document by primary key. Args: id (PyObjectId): Document identifier. Returns: Schema | None: Matching document, or ``None`` when absent. """ return await self.repo.get_by_id(id) async def update(self, id: PyObjectId, data: Model | dict) -> Schema | None: """Replace a document by primary key. Args: id (PyObjectId): Document identifier. data (Model | dict): Replacement model or field mapping. Returns: Schema | None: Updated document, or ``None`` when absent. """ return await self.repo.update( id, data if isinstance(data, dict) else self.model_to_dict(data), ) async def patch(self, id: PyObjectId, data: dict) -> Schema | None: """Partially update a document by primary key. Args: id (PyObjectId): Document identifier. data (dict): Fields to patch. Returns: Schema | None: Updated document, or ``None`` when absent. """ return await self.repo.patch(id, data) async def delete(self, id: PyObjectId) -> bool: """Delete a document by primary key. Args: id (PyObjectId): Document identifier. Returns: bool: ``True`` when a document was deleted. """ return await self.repo.delete(id) async def search(self, filter: dict | None = None, *args, **kwargs) -> list[Schema]: """List documents matching an optional filter. Args: filter (dict | None): Query filter passed to the repository. Returns: list[Schema]: Matching documents. Notes: Extra positional and keyword arguments are forwarded to the repository. """ return await self.repo.get_many(filter=filter, *args, **kwargs)