Coverage for src/lexigram/admin/config/app.py: 32%

62 statements  

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

1"""Top-level AdminConfig and factory helpers.""" 

2 

3from __future__ import annotations 

4 

5from dataclasses import dataclass 

6from typing import Any, ClassVar, Literal, cast 

7 

8from lexigram.admin.config.auth import AdminAuthConfig 

9from lexigram.admin.config.defaults import ( 

10 AdminNavigationGroup, 

11 FormDefaults, 

12 ResourceDefaults, 

13 ResourceYAMLConfig, 

14 TableDefaults, 

15) 

16from lexigram.admin.config.features import ( 

17 AdminFeaturesConfig, 

18 ContributorConfig, 

19 FrameworkPagesConfig, 

20) 

21from lexigram.admin.config.integrations import AdminIntegrationsConfig 

22from lexigram.admin.config.observability import AdminObservabilityConfig 

23from lexigram.admin.config.security import ( 

24 AdminAuditConfig, 

25 AdminRateLimitConfig, 

26 AdminRbacConfig, 

27) 

28from lexigram.admin.config.tenancy import TenancyConfig 

29from lexigram.admin.config.ui import ( 

30 AdminClustersConfig, 

31 AdminDataConfig, 

32 AdminUIConfig, 

33 DashboardLayoutConfig, 

34) 

35from lexigram.admin.constants import ENV_NESTED_DELIMITER, ENV_PREFIX 

36from lexigram.config import BaseConfig 

37from lexigram.validation import ConfigDict, Field 

38 

39 

40@dataclass(init=False) 

41class AdminConfig(BaseConfig): 

42 """Complete admin configuration - SINGLE SOURCE OF TRUTH. 

43 

44 This model represents the full configuration hierarchy for lexigram-admin. 

45 Configuration is loaded from: 

46 1. Pydantic defaults (this model) 

47 2. application.yaml (admin: section) 

48 3. Environment variables (LEX_ADMIN_*) 

49 4. Runtime config (hot-reloadable) 

50 

51 Attributes: 

52 name: Configuration name (default: "admin") 

53 enabled: Whether admin module is enabled 

54 title: Admin UI title 

55 prefix: Admin URL prefix 

56 htmx_prefix: HTMX endpoint prefix 

57 api_prefix: API endpoint prefix 

58 static_prefix: Static files prefix 

59 require_auth: Require authentication 

60 debug: Debug mode 

61 templates_dir: Templates directory path 

62 static_dir: Static files directory path 

63 auth: Authentication settings 

64 features: Feature toggles 

65 """ 

66 

67 model_config = cast( 

68 "ConfigDict", 

69 { 

70 "env_prefix": ENV_PREFIX, 

71 "env_nested_delimiter": ENV_NESTED_DELIMITER, 

72 "extra": "ignore", 

73 }, 

74 ) 

75 

76 config_section: ClassVar[str] = "admin" 

77 

78 # Section identifier (used by config discovery) 

79 name: str = "admin" 

80 enabled: bool = True 

81 

82 # Core Settings 

83 title: str = Field(default="Lexigram Admin") 

84 prefix: str = Field(default="/admin") 

85 htmx_prefix: str = Field(default="/admin/htmx") 

86 api_prefix: str = Field(default="/admin/api") 

87 static_prefix: str = Field(default="/admin/static") 

88 

89 # Security 

90 require_auth: bool = Field(default=True) 

91 debug: bool = Field(default=False) 

92 

93 # Paths 

94 templates_dir: str | None = Field(default=None) 

95 static_dir: str | None = Field(default=None) 

96 

97 # Sub-configs 

98 auth: AdminAuthConfig = Field(default_factory=AdminAuthConfig) 

99 features: AdminFeaturesConfig = Field(default_factory=AdminFeaturesConfig) 

100 rbac: AdminRbacConfig = Field(default_factory=AdminRbacConfig) 

101 data: AdminDataConfig = Field(default_factory=AdminDataConfig) 

102 clusters: AdminClustersConfig = Field(default_factory=AdminClustersConfig) 

103 observability: AdminObservabilityConfig = Field( 

104 default_factory=AdminObservabilityConfig 

105 ) 

106 

107 ui: AdminUIConfig = Field(default_factory=AdminUIConfig) 

108 rate_limit: AdminRateLimitConfig = Field(default_factory=AdminRateLimitConfig) 

109 resource_defaults: ResourceDefaults = Field(default_factory=ResourceDefaults) 

110 table_defaults: TableDefaults = Field(default_factory=TableDefaults) 

111 form_defaults: FormDefaults = Field(default_factory=FormDefaults) 

112 

113 # Resource & Navigation 

114 resources: dict[str, ResourceYAMLConfig] = Field(default_factory=dict) 

115 navigation_groups: dict[str, AdminNavigationGroup] = Field(default_factory=dict) 

116 commands: list[dict[str, Any]] = Field(default_factory=list) 

117 extensions: dict[str, Any] = Field(default_factory=dict) 

118 

119 # Audit 

120 audit: AdminAuditConfig = Field(default_factory=AdminAuditConfig) 

121 

122 # Multi-tenancy 

123 tenancy: TenancyConfig = Field(default_factory=TenancyConfig) 

124 

125 # Optional integrations 

126 integrations: AdminIntegrationsConfig = Field( 

127 default_factory=AdminIntegrationsConfig, 

128 ) 

129 

130 # Contributor system 

131 contributors: dict[str, ContributorConfig] = Field(default_factory=dict) 

132 dashboard_layout: DashboardLayoutConfig = Field( 

133 default_factory=DashboardLayoutConfig 

134 ) 

135 framework_pages: FrameworkPagesConfig = Field(default_factory=FrameworkPagesConfig) 

136 

137 contributor_collision_mode: Literal["warn", "error"] = Field( 

138 default="warn", 

139 description=( 

140 "How to handle name collisions when multiple contributors register " 

141 "widgets, pages, or routes with the same name. 'warn' (default) " 

142 "logs a warning and keeps the first registration; 'error' raises " 

143 "at boot time." 

144 ), 

145 ) 

146 

147 strict_resource_resolution: bool = Field( 

148 default=True, 

149 description=( 

150 "When True (production default), resource/controller resolution " 

151 "failures during AdminProvider.boot() raise immediately. " 

152 "When False, failures are logged and resolution continues with " 

153 "the remaining resources/controllers. Set to False in dev only." 

154 ), 

155 ) 

156 

157 def get_resource_config(self, name: str) -> ResourceYAMLConfig: 

158 """Get resource config with fallback to defaults.""" 

159 return self.resources.get(name, ResourceYAMLConfig()) 

160 

161 def get_navigation_group(self, name: str) -> AdminNavigationGroup | None: 

162 """Get navigation group by name.""" 

163 return self.navigation_groups.get(name) 

164 

165 def is_feature_enabled(self, feature: str) -> bool: 

166 """Check if a feature is enabled.""" 

167 return getattr(self.features, feature, False) 

168 

169 

170def make_admin_config(**kwargs: Any) -> AdminConfig: 

171 """Helper to create admin config from kwargs.""" 

172 return AdminConfig(**kwargs) 

173 

174 

175def default_admin_config() -> AdminConfig: 

176 """Factory for default admin configuration.""" 

177 return AdminConfig()