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
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-24 23:31 +0800
1"""Top-level AdminConfig and factory helpers."""
3from __future__ import annotations
5from dataclasses import dataclass
6from typing import Any, ClassVar, Literal, cast
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
40@dataclass(init=False)
41class AdminConfig(BaseConfig):
42 """Complete admin configuration - SINGLE SOURCE OF TRUTH.
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)
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 """
67 model_config = cast(
68 "ConfigDict",
69 {
70 "env_prefix": ENV_PREFIX,
71 "env_nested_delimiter": ENV_NESTED_DELIMITER,
72 "extra": "ignore",
73 },
74 )
76 config_section: ClassVar[str] = "admin"
78 # Section identifier (used by config discovery)
79 name: str = "admin"
80 enabled: bool = True
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")
89 # Security
90 require_auth: bool = Field(default=True)
91 debug: bool = Field(default=False)
93 # Paths
94 templates_dir: str | None = Field(default=None)
95 static_dir: str | None = Field(default=None)
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 )
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)
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)
119 # Audit
120 audit: AdminAuditConfig = Field(default_factory=AdminAuditConfig)
122 # Multi-tenancy
123 tenancy: TenancyConfig = Field(default_factory=TenancyConfig)
125 # Optional integrations
126 integrations: AdminIntegrationsConfig = Field(
127 default_factory=AdminIntegrationsConfig,
128 )
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)
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 )
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 )
157 def get_resource_config(self, name: str) -> ResourceYAMLConfig:
158 """Get resource config with fallback to defaults."""
159 return self.resources.get(name, ResourceYAMLConfig())
161 def get_navigation_group(self, name: str) -> AdminNavigationGroup | None:
162 """Get navigation group by name."""
163 return self.navigation_groups.get(name)
165 def is_feature_enabled(self, feature: str) -> bool:
166 """Check if a feature is enabled."""
167 return getattr(self.features, feature, False)
170def make_admin_config(**kwargs: Any) -> AdminConfig:
171 """Helper to create admin config from kwargs."""
172 return AdminConfig(**kwargs)
175def default_admin_config() -> AdminConfig:
176 """Factory for default admin configuration."""
177 return AdminConfig()