Coverage for src / lexigram / contracts / admin / types.py: 100%

127 statements  

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

1"""Admin contract types — shared value types for the admin contributor system. 

2 

3These frozen dataclasses cross package boundaries and are used by any 

4lexigram extension that contributes admin dashboard surfaces. 

5""" 

6 

7from __future__ import annotations 

8 

9from dataclasses import dataclass, field 

10from enum import StrEnum 

11from typing import TYPE_CHECKING 

12 

13from lexigram.contracts.admin.route_spec import AdminRouteSpec 

14from lexigram.contracts.admin.widget_content import WidgetContent, WidgetKind 

15 

16if TYPE_CHECKING: 

17 from lexigram.contracts.admin.page_handler import AdminPageHandlerProtocol 

18 

19 

20class WidgetSize(StrEnum): 

21 """Dashboard widget size.""" 

22 

23 SMALL = "small" 

24 MEDIUM = "medium" 

25 LARGE = "large" 

26 FULL = "full" 

27 

28 

29class WidgetCategory(StrEnum): 

30 """Dashboard widget category for grouping.""" 

31 

32 HEALTH = "health" 

33 METRICS = "metrics" 

34 ACTIVITY = "activity" 

35 RESOURCES = "resources" 

36 CUSTOM = "custom" 

37 

38 

39class PageCategory(StrEnum): 

40 """Management page category.""" 

41 

42 INFRASTRUCTURE = "infrastructure" 

43 SECURITY = "security" 

44 AI = "ai" 

45 DATA = "data" 

46 MONITORING = "monitoring" 

47 CONFIGURATION = "configuration" 

48 

49 

50@dataclass(frozen=True) 

51class DashboardWidgetDefinition: 

52 """Definition for a dashboard widget contributed by a package. 

53 

54 The ``render_endpoint`` is an HTMX endpoint that the dashboard shell 

55 will fetch via ``hx-get``. Each contributor owns its own rendering. 

56 """ 

57 

58 name: str 

59 title: str 

60 contributor: str 

61 render_endpoint: str 

62 view_kind: WidgetKind 

63 size: WidgetSize = WidgetSize.MEDIUM 

64 category: WidgetCategory = WidgetCategory.CUSTOM 

65 refresh_interval_seconds: int = 30 

66 order: int = 100 

67 permission: str | None = None 

68 icon: str | None = None 

69 description: str = "" 

70 live_resource_types: tuple[str, ...] = () 

71 

72 

73@dataclass(frozen=True) 

74class NavigationContribution: 

75 """Navigation entry contributed by a package.""" 

76 

77 label: str 

78 url: str 

79 icon: str = "box" 

80 group: str = "framework" 

81 order: int = 100 

82 permission: str | None = None 

83 badge_endpoint: str | None = None 

84 children: tuple[NavigationContribution, ...] = field(default_factory=tuple) 

85 

86 

87@dataclass(frozen=True) 

88class PageFilterField: 

89 """Schema field for a page-level dashboard filter (Filament 

90 ``HasFiltersForm``/``InteractsWithPageFilters`` parity). 

91 

92 Mirrors the admin-side ``ConfigField`` shape, but lives here because it 

93 crosses the contributor boundary: ``ManagementPageDefinition`` carries a 

94 filter schema that contributor packages declare. 

95 """ 

96 

97 name: str 

98 type: str # "select" | "number" | "text" | "boolean" 

99 label: str 

100 options: tuple[tuple[str, str], ...] = () # (value, display_label) for select 

101 default: str | int | bool | None = None 

102 description: str = "" 

103 

104 

105@dataclass(frozen=True) 

106class ManagementPageDefinition: 

107 """Full management page contributed by a package. 

108 

109 The ``handler`` is a dotted path to an async handler function, 

110 resolved at boot time to avoid import coupling. 

111 """ 

112 

113 name: str 

114 title: str 

115 contributor: str 

116 route_path: str 

117 handler: str | AdminPageHandlerProtocol 

118 category: PageCategory = PageCategory.INFRASTRUCTURE 

119 icon: str = "settings" 

120 permission: str | None = None 

121 description: str = "" 

122 order: int = 100 

123 filters: tuple[PageFilterField, ...] = field(default_factory=tuple) 

124 

125 

126@dataclass(frozen=True) 

127class SettingsPanelDefinition: 

128 """Settings panel contributed by a package.""" 

129 

130 name: str 

131 title: str 

132 contributor: str 

133 route_path: str 

134 handler: str | AdminPageHandlerProtocol 

135 icon: str = "sliders" 

136 category: str = "General" 

137 order: int = 100 

138 permission: str | None = None 

139 

140 

141@dataclass(frozen=True) 

142class AdminHealthDefinition: 

143 """Health check to surface in the admin dashboard.""" 

144 

145 name: str 

146 contributor: str 

147 component: str 

148 check_endpoint: str | None = None 

149 icon: str = "heart-pulse" 

150 description: str = "" 

151 permission: str | None = None 

152 

153 

154@dataclass(frozen=True) 

155class ActionParameterField: 

156 """A single parameter accepted by an admin action handler. 

157 

158 Used for auto-generated action forms and server-side input validation. 

159 """ 

160 

161 name: str 

162 type_hint: str # string repr e.g. "str", "int", "bool", "list[str]" 

163 required: bool = True 

164 default: object | None = None 

165 description: str = "" 

166 choices: tuple[str, ...] = field(default_factory=tuple) 

167 

168 

169@dataclass(frozen=True) 

170class ActionParameterSchema: 

171 """Schema describing the parameter surface of an admin action. 

172 

173 Enables auto-generated action forms in the admin UI and server-side 

174 validation before the handler is called. 

175 """ 

176 

177 fields: tuple[ActionParameterField, ...] 

178 description: str = "" 

179 

180 

181@dataclass(frozen=True) 

182class AdminActionDefinition: 

183 """Framework-level action (flush cache, reset circuit breaker, etc.). 

184 

185 The ``handler`` is a dotted path to an async function accepting 

186 ``(container, **params)``. 

187 """ 

188 

189 name: str 

190 title: str 

191 contributor: str 

192 handler: str 

193 icon: str = "zap" 

194 confirmation_message: str | None = None 

195 permission: str | None = None 

196 destructive: bool = False 

197 category: str = "operations" 

198 parameter_schema: ActionParameterSchema | None = None 

199 

200 

201@dataclass(frozen=True) 

202class WidgetViewModel: 

203 """Typed return value for widget rendering. 

204 

205 Provides a standard contract that all widget renderers follow. 

206 The ``content`` field carries structured widget content. If ``error`` is set, 

207 the widget is in an error state and ``content`` should be an error card. 

208 """ 

209 

210 content: WidgetContent 

211 title: str | None = None 

212 error: str | None = None 

213 

214 

215@dataclass(frozen=True) 

216class WidgetParams: 

217 """Typed query parameters passed to a widget handler. 

218 

219 Pure value object — no parsing, no validation logic, no I/O. 

220 Parsing belongs in ``lexigram.admin.params.parse_widget_params``. 

221 """ 

222 

223 page: int = 1 

224 page_size: int = 20 

225 time_window_minutes: int = 60 

226 raw: tuple[tuple[str, str], ...] = field(default_factory=tuple) 

227 tenant_id: str | None = None 

228 

229 

230__all__ = [ 

231 "ActionParameterField", 

232 "ActionParameterSchema", 

233 "AdminActionDefinition", 

234 "AdminHealthDefinition", 

235 "AdminRouteSpec", 

236 "DashboardWidgetDefinition", 

237 "ManagementPageDefinition", 

238 "NavigationContribution", 

239 "PageCategory", 

240 "SettingsPanelDefinition", 

241 "WidgetCategory", 

242 "WidgetContent", 

243 "WidgetKind", 

244 "WidgetParams", 

245 "WidgetSize", 

246 "WidgetViewModel", 

247]