Coverage for src / lexigram / contracts / data / types.py: 1%

73 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-08-19 05:41 +0800

1"""Value types for data access contracts.""" 

2 

3from __future__ import annotations 

4 

5from dataclasses import dataclass 

6from dataclasses import field as dc_field 

7from enum import StrEnum 

8from typing import Any, Literal, TypeAlias 

9 

10__all__ = [ 

11 "AndExpr", 

12 "CursorPaginationSpec", 

13 "FieldContains", 

14 "FieldEq", 

15 "FieldGt", 

16 "FieldGte", 

17 "FieldIn", 

18 "FieldLt", 

19 "FieldLte", 

20 "FieldNeq", 

21 "FilterExpression", 

22 "LogicalOperator", 

23 "NotExpr", 

24 "OrExpr", 

25 "PaginationSpec", 

26 "ProjectionSpec", 

27 "SortSpecification", 

28] 

29 

30 

31class LogicalOperator(StrEnum): 

32 """Logical combination operator for filter groups.""" 

33 

34 AND = "and" 

35 OR = "or" 

36 

37 

38@dataclass(frozen=True) 

39class FieldEq: 

40 """Equality predicate: ``field == value``.""" 

41 

42 field: str 

43 value: Any 

44 

45 

46@dataclass(frozen=True) 

47class FieldGt: 

48 """Greater-than predicate: ``field > value``.""" 

49 

50 field: str 

51 value: Any 

52 

53 

54@dataclass(frozen=True) 

55class FieldLt: 

56 """Less-than predicate: ``field < value``.""" 

57 

58 field: str 

59 value: Any 

60 

61 

62@dataclass(frozen=True) 

63class FieldGte: 

64 """Greater-than-or-equal predicate: ``field >= value``.""" 

65 

66 field: str 

67 value: Any 

68 

69 

70@dataclass(frozen=True) 

71class FieldLte: 

72 """Less-than-or-equal predicate: ``field <= value``.""" 

73 

74 field: str 

75 value: Any 

76 

77 

78@dataclass(frozen=True) 

79class FieldNeq: 

80 """Not-equal predicate: ``field != value``.""" 

81 

82 field: str 

83 value: Any 

84 

85 

86@dataclass(frozen=True) 

87class FieldContains: 

88 """Substring predicate: ``value in field``.""" 

89 

90 field: str 

91 value: Any 

92 

93 

94@dataclass(frozen=True) 

95class FieldIn: 

96 """Membership predicate: ``field in values``.""" 

97 

98 field: str 

99 values: tuple[Any, ...] 

100 

101 

102@dataclass(frozen=True) 

103class AndExpr: 

104 """Logical conjunction of two filter expressions.""" 

105 

106 left: FilterExpression 

107 right: FilterExpression 

108 

109 

110@dataclass(frozen=True) 

111class OrExpr: 

112 """Logical disjunction of two filter expressions.""" 

113 

114 left: FilterExpression 

115 right: FilterExpression 

116 

117 

118@dataclass(frozen=True) 

119class NotExpr: 

120 """Logical negation of a filter expression.""" 

121 

122 expr: FilterExpression 

123 

124 

125FilterExpression: TypeAlias = ( 

126 FieldEq 

127 | FieldGt 

128 | FieldGte 

129 | FieldLt 

130 | FieldLte 

131 | FieldNeq 

132 | FieldContains 

133 | FieldIn 

134 | AndExpr 

135 | OrExpr 

136 | NotExpr 

137) 

138"""Composable predicate tree for repository queries. 

139 

140Build expressions using the concrete leaf/node types: 

141 

142 >>> expr = AndExpr(FieldEq("status", "active"), FieldGt("age", 18)) 

143""" 

144 

145 

146# --------------------------------------------------------------------------- 

147# Sort specification 

148# --------------------------------------------------------------------------- 

149 

150 

151@dataclass(frozen=True) 

152class SortSpecification: 

153 """Specifies ordering for a query result. 

154 

155 Attributes: 

156 field: Name of the field to sort on. 

157 direction: Sort direction — ``"asc"`` or ``"desc"``. 

158 """ 

159 

160 field: str 

161 direction: Literal["asc", "desc"] = "asc" 

162 

163 

164# --------------------------------------------------------------------------- 

165# Pagination specifications 

166# --------------------------------------------------------------------------- 

167 

168 

169@dataclass(frozen=True) 

170class PaginationSpec: 

171 """Offset-based pagination parameters. 

172 

173 Attributes: 

174 page: 1-based page number. 

175 size: Number of results per page. 

176 """ 

177 

178 page: int = 1 

179 size: int = 20 

180 

181 @property 

182 def offset(self) -> int: 

183 """Zero-based offset for the current page.""" 

184 return (self.page - 1) * self.size 

185 

186 

187@dataclass(frozen=True) 

188class CursorPaginationSpec: 

189 """Cursor-based (keyset) pagination parameters. 

190 

191 Attributes: 

192 cursor: Opaque cursor string returned from a previous response. 

193 size: Number of results per page. 

194 """ 

195 

196 cursor: str | None = None 

197 size: int = 20 

198 

199 

200# --------------------------------------------------------------------------- 

201# ProjectionProtocol specification 

202# --------------------------------------------------------------------------- 

203 

204 

205@dataclass(frozen=True) 

206class ProjectionSpec: 

207 """Describes which fields to include or exclude from query results. 

208 

209 Attributes: 

210 include: Explicit set of field names to return. 

211 exclude: Names of fields to omit from the result. 

212 """ 

213 

214 include: frozenset[str] = dc_field(default_factory=frozenset) 

215 exclude: frozenset[str] = dc_field(default_factory=frozenset)