Coverage for src/lexigram/admin/di/sub_providers/dashboard.py: 97%
38 statements
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-21 14:56 +0800
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-21 14:56 +0800
1"""Admin dashboard sub-provider — assembles dashboard from contributors."""
3from __future__ import annotations
5from typing import TYPE_CHECKING
7from lexigram.admin.contributors.registry import ContributorRegistry
8from lexigram.admin.dashboard.assembler import DashboardAssembler
9from lexigram.admin.dashboard.naming_policy import NamingPolicy
10from lexigram.admin.dashboard.page_assembler import PageAssembler
11from lexigram.admin.dashboard.permission_filter import PermissionFilter
12from lexigram.admin.dashboard.settings_assembler import SettingsPanelAssembler
13from lexigram.admin.dashboard.widgets import WidgetRegistry
14from lexigram.contracts.admin.protocols import AdminDashboardProtocol
15from lexigram.contracts.core.health import HealthCheckResult, HealthStatus
17if TYPE_CHECKING:
18 from lexigram.admin.config import AdminConfig
19 from lexigram.contracts.core.di import (
20 ContainerRegistrarProtocol,
21 ContainerResolverProtocol,
22 )
25class AdminDashboardSubProvider:
26 """Manages dashboard assembly from contributor definitions."""
28 def __init__(
29 self,
30 config: AdminConfig,
31 contributor_registry: ContributorRegistry,
32 ) -> None:
33 self._config = config
34 self._registry = contributor_registry
35 self._assembler: DashboardAssembler | None = None
37 @property
38 def config(self) -> AdminConfig:
39 """Return current admin config."""
40 return self._config
42 async def register(self, container: ContainerRegistrarProtocol) -> None:
43 """Register dashboard assembler and services in the DI container."""
44 from lexigram.admin.services.charts import ChartFactory
45 from lexigram.admin.services.user_dashboard import UserDashboardService
47 naming = NamingPolicy(mode=self._config.contributor_collision_mode)
48 perms = PermissionFilter()
49 page_asm = PageAssembler(naming_policy=naming, permission_filter=perms)
50 settings_asm = SettingsPanelAssembler(
51 naming_policy=naming, permission_filter=perms
52 )
54 self._assembler = DashboardAssembler(
55 contributors=list(self._registry.get_all()),
56 page_assembler=page_asm,
57 settings_assembler=settings_asm,
58 )
59 container.singleton(AdminDashboardProtocol, self._assembler)
60 container.singleton(DashboardAssembler, self._assembler)
62 # Pre-construct to avoid the container trying to resolve `Any` as a
63 # dependency type (builder defaults to None, which is fine).
64 container.singleton(UserDashboardService, UserDashboardService())
65 container.singleton(ChartFactory, ChartFactory())
66 container.singleton(WidgetRegistry, WidgetRegistry())
68 async def boot(self, container: ContainerResolverProtocol) -> None:
69 """Dashboard boot — no additional init required."""
71 async def shutdown(self) -> None:
72 """Dashboard shutdown."""
73 self._assembler = None
75 async def health_check(self, timeout: float = 5.0) -> HealthCheckResult:
76 """Return dashboard assembly health."""
77 return HealthCheckResult(
78 component="admin_dashboard",
79 status=HealthStatus.HEALTHY if self._assembler else HealthStatus.UNKNOWN,
80 message="Dashboard assembler ready"
81 if self._assembler
82 else "Not initialized",
83 )
86__all__ = ["AdminDashboardSubProvider"]