Coverage for src/lexigram/admin/data/adapters/repository/data_source.py: 96%
46 statements
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-21 14:56 +0800
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-21 14:56 +0800
1"""AdminRepositoryProtocol-backed IDataSource adapter."""
3from __future__ import annotations
5from typing import Any, Generic
7from lexigram.admin.data.adapters.repository.types import T
8from lexigram.admin.data.data_source import IDataSource, QueryResult
9from lexigram.admin.data.query import QuerySpec
10from lexigram.contracts.admin.repository import AdminRepositoryProtocol
11from lexigram.di.decorators import inject
12from lexigram.logging import get_logger
14logger = get_logger(__name__)
17@inject
18class RepositoryDataSource(IDataSource[T], Generic[T]):
19 """IDataSource adapter for AdminRepositoryProtocol repositories.
21 Translates QuerySpec into AdminRepositoryProtocol calls using
22 QuerySpec.resolved_sort and QuerySpec.to_repository_filters().
23 """
25 def __init__(self, repository: AdminRepositoryProtocol[T]) -> None:
26 self._repo = repository
28 async def find_one(self, item_id: Any) -> T | None:
29 return await self._repo.find_by_id(item_id)
31 async def find_many(self, query: QuerySpec) -> QueryResult[T]:
32 sort_field, sort_order = query.resolved_sort
33 filters = query.to_repository_filters()
35 items = await self._repo.find_many(
36 offset=query.offset,
37 limit=query.per_page,
38 order_by=[(sort_field, sort_order)] if sort_field else None,
39 filters=filters,
40 search=query.search or None,
41 search_fields=query.search_fields or None,
42 load=query.include or None,
43 )
45 total = await self.count(query)
47 return QueryResult(
48 items=items,
49 total=total,
50 page=query.page,
51 per_page=query.per_page,
52 has_next=(query.offset + query.per_page) < total,
53 has_prev=query.page > 1,
54 )
56 async def count(self, query: QuerySpec) -> int:
57 return await self._repo.count(
58 filters=query.to_repository_filters(),
59 search=query.search or None,
60 search_fields=query.search_fields or None,
61 )
63 async def create(self, data: dict[str, Any]) -> T:
64 return await self._repo.create(data)
66 async def update(self, item_id: Any, data: dict[str, Any]) -> T:
67 return await self._repo.update(item_id, data)
69 async def delete(self, item_id: Any) -> bool:
70 return await self._repo.delete(item_id)
72 async def bulk_create(self, items: list[dict[str, Any]]) -> list[T]:
73 return [await self._repo.create(item) for item in items]
75 async def bulk_update(self, ids: list[Any], data: dict[str, Any]) -> int:
76 count = 0
77 for item_id in ids:
78 try:
79 await self._repo.update(item_id, data)
80 count += 1
81 except Exception:
82 logger.warning("bulk_update: skipped id=%s", item_id)
83 return count
85 async def bulk_delete(self, ids: list[Any]) -> int:
86 count = 0
87 for item_id in ids:
88 if await self._repo.delete(item_id):
89 count += 1
90 return count