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

1"""Paginated query result container for admin data queries.""" 

2 

3from __future__ import annotations 

4 

5from dataclasses import dataclass 

6from typing import Any, Callable, Generic, TypeVar 

7 

8T = TypeVar("T") 

9 

10 

11@dataclass 

12class PagedResult(Generic[T]): 

13 """Paginated query result. 

14 

15 Contains the items for the current page along with pagination metadata. 

16 Supports both page-based and cursor-based pagination. 

17 

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 """ 

28 

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 

35 

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 

42 

43 @property 

44 def has_prev(self) -> bool: 

45 """Check if there are pages before current.""" 

46 return self.page > 1 

47 

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 

54 

55 @property 

56 def is_empty(self) -> bool: 

57 """Check if result has no items.""" 

58 return len(self.items) == 0 

59 

60 @property 

61 def count(self) -> int: 

62 """Number of items in current page.""" 

63 return len(self.items) 

64 

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 

71 

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 

78 

79 def map(self, fn: Any) -> PagedResult[Any]: 

80 """Transform items using a function. 

81 

82 Args: 

83 fn: Function to apply to each item 

84 

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 ) 

96 

97 @classmethod 

98 def empty(cls, per_page: int = 20) -> PagedResult[T]: 

99 """Create an empty PagedResult. 

100 

101 Args: 

102 per_page: Items per page for pagination metadata 

103 

104 Returns: 

105 Empty PagedResult 

106 """ 

107 return cls( 

108 items=[], 

109 total=0, 

110 page=1, 

111 per_page=per_page, 

112 ) 

113 

114 

115__all__ = [ 

116 "CombinedSpec", 

117 "EqualSpec", 

118 "FilterCondition", 

119 "FilterOperator", 

120 "FilterSpec", 

121 "GreaterThanOrEqualSpec", 

122 "InSpec", 

123 "LessThanOrEqualSpec", 

124 "PagedResult", 

125 "QuerySpec", 

126]