Coverage for src/lexigram/graphql/pagination/types.py: 100%

48 statements  

« prev     ^ index     » next       coverage.py v7.15.4, created at 2026-08-25 04:37 +0800

1"""Data structures used for GraphQL pagination. 

2 

3This module defines the dataclasses representing edges, page info, 

4connections and input/request objects. They were previously defined in the 

5package root but have been moved here for a cleaner API surface. 

6""" 

7 

8from __future__ import annotations 

9 

10from dataclasses import dataclass 

11from typing import Generic, TypeVar 

12 

13T = TypeVar("T") 

14 

15 

16@dataclass 

17class Edge(Generic[T]): 

18 """An edge in a connection.""" 

19 

20 node: T 

21 cursor: str 

22 

23 

24@dataclass 

25class PageInfo: 

26 """Pagination information.""" 

27 

28 has_next_page: bool 

29 has_previous_page: bool 

30 start_cursor: str | None = None 

31 end_cursor: str | None = None 

32 

33 

34@dataclass 

35class CursorConnection(Generic[T]): 

36 """A connection for cursor-based pagination.""" 

37 

38 edges: list[Edge[T]] 

39 page_info: PageInfo 

40 

41 

42@dataclass 

43class CursorPaginationInput: 

44 """Input for cursor-based pagination. 

45 

46 Attributes: 

47 first: Number of items to return (forward pagination). 

48 after: Cursor to start after (forward pagination). 

49 last: Number of items to return (backward pagination). 

50 before: Cursor to start before (backward pagination). 

51 """ 

52 

53 first: int | None = None 

54 after: str | None = None 

55 last: int | None = None 

56 before: str | None = None 

57 

58 def validate(self) -> None: 

59 """Validate pagination input. 

60 

61 Raises: 

62 ValueError: If the input is invalid. 

63 """ 

64 if self.first is not None and self.first < 0: 

65 raise ValueError("first must be non-negative") 

66 if self.last is not None and self.last < 0: 

67 raise ValueError("last must be non-negative") 

68 if self.first is not None and self.last is not None: 

69 raise ValueError("Cannot specify both first and last") 

70 if self.after is not None and self.before is not None: 

71 raise ValueError("Cannot specify both after and before") 

72 

73 

74@dataclass 

75class OffsetPaginationInput: 

76 """Input for offset-based pagination. 

77 

78 Attributes: 

79 offset: Number of items to skip. 

80 limit: Maximum number of items to return. 

81 """ 

82 

83 offset: int = 0 

84 limit: int = 10 

85 

86 def validate(self) -> None: 

87 """Validate pagination input. 

88 

89 Raises: 

90 ValueError: If the input is invalid. 

91 """ 

92 if self.offset < 0: 

93 raise ValueError("offset must be non-negative") 

94 if self.limit < 0: 

95 raise ValueError("limit must be non-negative") 

96 

97 

98@dataclass 

99class PaginationResult(Generic[T]): 

100 """Result of pagination operation. 

101 

102 Attributes: 

103 items: The paginated items. 

104 total_count: Total number of items available. 

105 has_next: Whether there are more items after this page. 

106 has_previous: Whether there are items before this page. 

107 """ 

108 

109 items: list[T] 

110 total_count: int 

111 has_next: bool = False 

112 has_previous: bool = False