Coverage for src/lexigram/admin/data/paged_result.py: 23%
47 statements
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-24 23:31 +0800
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-24 23:31 +0800
1"""Paginated query result container for admin data queries."""
3from __future__ import annotations
5from dataclasses import dataclass
6from typing import Any, Callable, Generic, TypeVar
8T = TypeVar("T")
11@dataclass
12class PagedResult(Generic[T]):
13 """Paginated query result.
15 Contains the items for the current page along with pagination metadata.
16 Supports both page-based and cursor-based pagination.
18 Example:
19 >>> result = PagedResult(
20 ... items=[user1, user2],
21 ... total=100,
22 ... page=1,
23 ... per_page=20,
24 ... )
25 >>> result.has_next # True
26 >>> result.total_pages # 5
27 """
29 items: list[T]
30 total: int
31 page: int
32 per_page: int
33 cursor: str | None = None
34 next_cursor: str | None = None
36 @property
37 def has_next(self) -> bool:
38 """Check if there are more pages after current."""
39 if self.next_cursor is not None:
40 return True
41 return self.page * self.per_page < self.total
43 @property
44 def has_prev(self) -> bool:
45 """Check if there are pages before current."""
46 return self.page > 1
48 @property
49 def total_pages(self) -> int:
50 """Calculate total number of pages."""
51 if self.per_page <= 0:
52 return 0
53 return (self.total + self.per_page - 1) // self.per_page
55 @property
56 def is_empty(self) -> bool:
57 """Check if result has no items."""
58 return len(self.items) == 0
60 @property
61 def count(self) -> int:
62 """Number of items in current page."""
63 return len(self.items)
65 @property
66 def start_index(self) -> int:
67 """1-indexed start position of first item."""
68 if self.is_empty:
69 return 0
70 return (self.page - 1) * self.per_page + 1
72 @property
73 def end_index(self) -> int:
74 """1-indexed position of last item."""
75 if self.is_empty:
76 return 0
77 return self.start_index + self.count - 1
79 def map(self, fn: Any) -> PagedResult[Any]:
80 """Transform items using a function.
82 Args:
83 fn: Function to apply to each item
85 Returns:
86 New PagedResult with transformed items
87 """
88 return PagedResult(
89 items=list(map(fn, self.items)),
90 total=self.total,
91 page=self.page,
92 per_page=self.per_page,
93 cursor=self.cursor,
94 next_cursor=self.next_cursor,
95 )
97 @classmethod
98 def empty(cls, per_page: int = 20) -> PagedResult[T]:
99 """Create an empty PagedResult.
101 Args:
102 per_page: Items per page for pagination metadata
104 Returns:
105 Empty PagedResult
106 """
107 return cls(
108 items=[],
109 total=0,
110 page=1,
111 per_page=per_page,
112 )
115__all__ = [
116 "CombinedSpec",
117 "EqualSpec",
118 "FilterCondition",
119 "FilterOperator",
120 "FilterSpec",
121 "GreaterThanOrEqualSpec",
122 "InSpec",
123 "LessThanOrEqualSpec",
124 "PagedResult",
125 "QuerySpec",
126]