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
« 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.
3These frozen dataclasses cross package boundaries and are used by any
4lexigram extension that contributes admin dashboard surfaces.
5"""
7from __future__ import annotations
9from dataclasses import dataclass, field
10from enum import StrEnum
11from typing import TYPE_CHECKING
13from lexigram.contracts.admin.route_spec import AdminRouteSpec
14from lexigram.contracts.admin.widget_content import WidgetContent, WidgetKind
16if TYPE_CHECKING:
17 from lexigram.contracts.admin.page_handler import AdminPageHandlerProtocol
20class WidgetSize(StrEnum):
21 """Dashboard widget size."""
23 SMALL = "small"
24 MEDIUM = "medium"
25 LARGE = "large"
26 FULL = "full"
29class WidgetCategory(StrEnum):
30 """Dashboard widget category for grouping."""
32 HEALTH = "health"
33 METRICS = "metrics"
34 ACTIVITY = "activity"
35 RESOURCES = "resources"
36 CUSTOM = "custom"
39class PageCategory(StrEnum):
40 """Management page category."""
42 INFRASTRUCTURE = "infrastructure"
43 SECURITY = "security"
44 AI = "ai"
45 DATA = "data"
46 MONITORING = "monitoring"
47 CONFIGURATION = "configuration"
50@dataclass(frozen=True)
51class DashboardWidgetDefinition:
52 """Definition for a dashboard widget contributed by a package.
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 """
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, ...] = ()
73@dataclass(frozen=True)
74class NavigationContribution:
75 """Navigation entry contributed by a package."""
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)
87@dataclass(frozen=True)
88class PageFilterField:
89 """Schema field for a page-level dashboard filter (Filament
90 ``HasFiltersForm``/``InteractsWithPageFilters`` parity).
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 """
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 = ""
105@dataclass(frozen=True)
106class ManagementPageDefinition:
107 """Full management page contributed by a package.
109 The ``handler`` is a dotted path to an async handler function,
110 resolved at boot time to avoid import coupling.
111 """
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)
126@dataclass(frozen=True)
127class SettingsPanelDefinition:
128 """Settings panel contributed by a package."""
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
141@dataclass(frozen=True)
142class AdminHealthDefinition:
143 """Health check to surface in the admin dashboard."""
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
154@dataclass(frozen=True)
155class ActionParameterField:
156 """A single parameter accepted by an admin action handler.
158 Used for auto-generated action forms and server-side input validation.
159 """
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)
169@dataclass(frozen=True)
170class ActionParameterSchema:
171 """Schema describing the parameter surface of an admin action.
173 Enables auto-generated action forms in the admin UI and server-side
174 validation before the handler is called.
175 """
177 fields: tuple[ActionParameterField, ...]
178 description: str = ""
181@dataclass(frozen=True)
182class AdminActionDefinition:
183 """Framework-level action (flush cache, reset circuit breaker, etc.).
185 The ``handler`` is a dotted path to an async function accepting
186 ``(container, **params)``.
187 """
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
201@dataclass(frozen=True)
202class WidgetViewModel:
203 """Typed return value for widget rendering.
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 """
210 content: WidgetContent
211 title: str | None = None
212 error: str | None = None
215@dataclass(frozen=True)
216class WidgetParams:
217 """Typed query parameters passed to a widget handler.
219 Pure value object — no parsing, no validation logic, no I/O.
220 Parsing belongs in ``lexigram.admin.params.parse_widget_params``.
221 """
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
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]