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

46 statements  

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

1"""Convenience base class for admin contributor implementations.""" 

2 

3from __future__ import annotations 

4 

5from collections.abc import Sequence 

6from typing import TYPE_CHECKING, cast 

7 

8from lexigram.contracts.admin.types import ( 

9 AdminActionDefinition, 

10 AdminHealthDefinition, 

11 AdminRouteSpec, 

12 DashboardWidgetDefinition, 

13 ManagementPageDefinition, 

14 NavigationContribution, 

15 SettingsPanelDefinition, 

16 WidgetParams, 

17) 

18 

19if TYPE_CHECKING: 

20 from lexigram.contracts.admin.errors import AdminError 

21 from lexigram.contracts.admin.health_payload import HealthCheckPayload 

22 from lexigram.contracts.admin.types import WidgetViewModel 

23 from lexigram.contracts.core.di import ContainerResolverProtocol 

24 from lexigram.contracts.core.result import Result 

25 

26 

27class BaseAdminContributor: 

28 """Convenience base class for admin contributors. 

29 

30 Provides no-op defaults for all ``AdminContributorProtocol`` methods. 

31 Subclasses override only the methods they need. 

32 """ 

33 

34 name: str = "" 

35 display_name: str = "" 

36 group: str = "framework" 

37 depends_on: tuple[str, ...] = () 

38 icon: str = "box" 

39 priority: int = 100 

40 version: str = "0.0.0" 

41 package_source: str = "built-in" 

42 required_permissions: frozenset[str] = frozenset() 

43 

44 @property 

45 def contributor_id(self) -> str: 

46 """Stable identifier for RBAC lookup — equals ``name``.""" 

47 return self.name 

48 

49 def get_resources(self) -> Sequence[type]: 

50 """Return an empty sequence by default.""" 

51 return [] 

52 

53 def get_routes(self) -> Sequence[AdminRouteSpec]: 

54 """Return an empty sequence by default.""" 

55 return [] 

56 

57 def get_dashboard_widgets(self) -> Sequence[DashboardWidgetDefinition]: 

58 """Return an empty list by default.""" 

59 return [] 

60 

61 def get_navigation_items(self) -> Sequence[NavigationContribution]: 

62 """Return an empty list by default.""" 

63 return [] 

64 

65 def get_management_pages(self) -> Sequence[ManagementPageDefinition]: 

66 """Return an empty list by default.""" 

67 return [] 

68 

69 def get_settings_panels(self) -> Sequence[SettingsPanelDefinition]: 

70 """Return an empty list by default.""" 

71 return [] 

72 

73 def get_health_definitions(self) -> Sequence[AdminHealthDefinition]: 

74 """Return an empty list by default.""" 

75 return [] 

76 

77 def get_actions(self) -> Sequence[AdminActionDefinition]: 

78 """Return an empty list by default.""" 

79 return [] 

80 

81 async def on_admin_boot(self, container: ContainerResolverProtocol) -> None: 

82 """No-op boot hook.""" 

83 

84 async def on_admin_shutdown(self) -> None: 

85 """No-op shutdown hook.""" 

86 

87 async def render_widget( 

88 self, 

89 widget_name: str, 

90 params: WidgetParams, 

91 resolver: ContainerResolverProtocol | None = None, 

92 ) -> Result[WidgetViewModel, AdminError]: 

93 """Return a not-found error by default — override in subclasses.""" 

94 from lexigram.contracts.admin.errors import WidgetNotFoundError 

95 from lexigram.contracts.core.result import Err 

96 

97 result: Result[WidgetViewModel, AdminError] = cast( 

98 "Result[WidgetViewModel, AdminError]", 

99 Err(WidgetNotFoundError(self.name, widget_name)), 

100 ) 

101 return result 

102 

103 async def render_health_check( 

104 self, 

105 check_name: str, 

106 ) -> Result[HealthCheckPayload, AdminError]: 

107 """Default: this contributor does not serve the requested health check. 

108 

109 Args: 

110 check_name: Name of the health check requested. 

111 

112 Returns: 

113 ``Err(HealthCheckNotFoundError)`` — contributor does not provide this check. 

114 """ 

115 from lexigram.contracts.admin.errors import HealthCheckNotFoundError 

116 from lexigram.contracts.core.result import Err 

117 

118 result: Result[HealthCheckPayload, AdminError] = cast( 

119 "Result[HealthCheckPayload, AdminError]", 

120 Err(HealthCheckNotFoundError(self.name, check_name)), 

121 ) 

122 return result 

123 

124 

125__all__ = ["BaseAdminContributor"]