Coverage for src / lexigram / ui / di / provider.py: 94%

36 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-08-10 04:11 +0800

1"""UIProvider — registers the UI component system in the DI container.""" 

2 

3from __future__ import annotations 

4 

5import time 

6from typing import TYPE_CHECKING, Any 

7 

8from lexigram.contracts.core import HealthCheckResult, HealthStatus, ProviderPriority 

9from lexigram.di.provider import Provider 

10from lexigram.ui.config import ( 

11 BaseLayoutConfig, 

12 FooterConfig, 

13 HeadConfig, 

14 HTMLDocumentConfig, 

15 ToastConfig, 

16 UIConfig, 

17) 

18 

19if TYPE_CHECKING: 

20 from lexigram.contracts.core.di import ( 

21 ContainerRegistrarProtocol, 

22 ContainerResolverProtocol, 

23 ) 

24 

25 

26class UIProvider(Provider): 

27 """Registers the lexigram-ui component system. 

28 

29 Bind into your application bootstrap:: 

30 

31 from lexigram.ui.di import UIProvider 

32 

33 app.add_provider(UIProvider()) 

34 

35 Configuration (``application.yaml``):: 

36 

37 ui: 

38 default_theme: my-theme 

39 debug_components: true 

40 

41 Registered services: 

42 

43 - ``UIConfig`` (singleton) — resolved UI configuration. 

44 - ``MetricsCollector`` (singleton) — in-memory UI metrics collection. 

45 - ``ResponseOptimizer`` (singleton) — ETag-based HTMX response optimization. 

46 - ``RenderCache`` (singleton) — LRU cache for rendered component fragments. 

47 """ 

48 

49 name = "ui" 

50 priority = ProviderPriority.PRESENTATION 

51 config_key: str | None = "ui" 

52 config_model: type | None = UIConfig 

53 

54 def __init__(self, config: UIConfig | None = None, **kwargs: Any) -> None: 

55 super().__init__(**kwargs) 

56 self._requested_config = config 

57 if config is not None: 

58 self._config = config 

59 

60 async def register(self, container: ContainerRegistrarProtocol) -> None: 

61 """Register UI services in the DI container. 

62 

63 Args: 

64 container: The DI container registrar. 

65 """ 

66 self._config = self._requested_config or ( 

67 self.config 

68 if isinstance(getattr(self, "config", None), UIConfig) 

69 else self._config 

70 ) 

71 config: UIConfig = ( 

72 self._config if isinstance(self._config, UIConfig) else UIConfig() 

73 ) 

74 container.singleton(UIConfig, config) 

75 

76 # Layout config singletons 

77 container.singleton(HTMLDocumentConfig, HTMLDocumentConfig()) 

78 container.singleton(BaseLayoutConfig, BaseLayoutConfig()) 

79 container.singleton(HeadConfig, HeadConfig()) 

80 container.singleton(FooterConfig, FooterConfig()) 

81 container.singleton(ToastConfig, ToastConfig()) 

82 

83 # Monitoring services — explicitly registered for DI traceability 

84 from lexigram.ui.performance.observability import MetricsCollector 

85 from lexigram.ui.performance.performance import RenderCache, ResponseOptimizer 

86 

87 container.singleton(MetricsCollector, MetricsCollector) 

88 container.singleton(ResponseOptimizer, ResponseOptimizer) 

89 container.singleton(RenderCache, RenderCache) 

90 

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

92 """No boot-time initialisation required for the UI module.""" 

93 

94 async def shutdown(self) -> None: 

95 """No shutdown work required for the UI module.""" 

96 

97 async def health_check(self, timeout: float = 5.0) -> HealthCheckResult: 

98 """Check provider health. 

99 

100 Args: 

101 timeout: Maximum seconds to wait for health check response. 

102 

103 Returns: 

104 HealthCheckResult with status and component details. 

105 """ 

106 start = time.perf_counter() 

107 return HealthCheckResult( 

108 component="ui", 

109 status=HealthStatus.HEALTHY, 

110 details={"components": {"ui": {"status": "healthy"}}}, 

111 duration_ms=(time.perf_counter() - start) * 1000, 

112 ) 

113 

114 

115__all__ = ["UIProvider"]