Coverage for src/lexigram/admin/di/sub_providers/dashboard.py: 0%

38 statements  

« prev     ^ index     » next       coverage.py v7.15.4, created at 2026-08-24 23:18 +0800

1"""Admin dashboard sub-provider — assembles dashboard from contributors.""" 

2 

3from __future__ import annotations 

4 

5from typing import TYPE_CHECKING 

6 

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 

16 

17if TYPE_CHECKING: 

18 from lexigram.admin.config import AdminConfig 

19 from lexigram.contracts.core.di import ( 

20 ContainerRegistrarProtocol, 

21 ContainerResolverProtocol, 

22 ) 

23 

24 

25class AdminDashboardSubProvider: 

26 """Manages dashboard assembly from contributor definitions.""" 

27 

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 

36 

37 @property 

38 def config(self) -> AdminConfig: 

39 """Return current admin config.""" 

40 return self._config 

41 

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 

46 

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 ) 

53 

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) 

61 

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()) 

67 

68 async def boot(self, container: ContainerResolverProtocol) -> None: 

69 """Dashboard boot — no additional init required.""" 

70 

71 async def shutdown(self) -> None: 

72 """Dashboard shutdown.""" 

73 self._assembler = None 

74 

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 ) 

84 

85 

86__all__ = ["AdminDashboardSubProvider"]