Coverage for src / lexigram / admin / builders / builder.py: 0%

117 statements  

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

1from __future__ import annotations 

2 

3from collections.abc import Callable, Sequence 

4from typing import TYPE_CHECKING, Any, Literal 

5 

6from lexigram.admin.config import ( 

7 AdminConfig, 

8 AdminFeaturesConfig, 

9 AdminNavigationGroup, 

10) 

11from lexigram.primitives.builder import BaseBuilder # type: ignore[attr-defined] 

12 

13if TYPE_CHECKING: 

14 from lexigram.admin.di.bundle_provider import AdminProvider 

15 

16 

17class AdminBuilder(BaseBuilder["AdminProvider"]): 

18 """Fluent builder for AdminProvider.""" 

19 

20 def __init__(self) -> None: 

21 super().__init__() 

22 self._title: str = "Admin" 

23 self._prefix: str = "/admin" 

24 self._require_auth: bool = True 

25 self._theme: Literal["light", "dark", "system"] = "system" 

26 self._debug: bool = False 

27 self._features: dict[str, Any] = {} 

28 self._extensions: dict[str, dict[str, Any]] = {} 

29 self._resources: list[Any] = [] 

30 self._controllers: list[Any] = [] 

31 self._navigation_groups: dict[str, AdminNavigationGroup] = {} 

32 self._commands: list[Any] = [] 

33 self._service_factories: dict[type, Callable[..., Any]] = {} 

34 self._base_config: AdminConfig | None = None 

35 

36 @classmethod 

37 def create(cls) -> AdminBuilder: 

38 return cls() 

39 

40 @classmethod 

41 def from_config(cls, config: AdminConfig) -> AdminBuilder: 

42 builder = cls() 

43 builder._title = config.title 

44 builder._prefix = config.prefix 

45 builder._require_auth = config.require_auth 

46 builder._debug = config.debug 

47 builder._theme = config.ui.theme 

48 

49 if config.features: 

50 builder._features = config.features.model_dump() 

51 

52 builder._navigation_groups = dict(config.navigation_groups) 

53 builder._commands = list(config.commands) 

54 builder._extensions = dict(config.extensions) if config.extensions else {} 

55 builder._base_config = config 

56 

57 return builder 

58 

59 def title(self, title: str) -> AdminBuilder: 

60 self._title = title 

61 return self 

62 

63 def prefix(self, prefix: str) -> AdminBuilder: 

64 self._prefix = prefix 

65 return self 

66 

67 def require_auth(self, require: bool) -> AdminBuilder: 

68 self._require_auth = require 

69 return self 

70 

71 def theme(self, theme: Literal["light", "dark", "system"]) -> AdminBuilder: 

72 self._theme = theme 

73 return self 

74 

75 def debug(self, enabled: bool = True) -> AdminBuilder: 

76 self._debug = enabled 

77 return self 

78 

79 def features(self, features: dict[str, Any]) -> AdminBuilder: 

80 self._features.update(features) 

81 return self 

82 

83 def feature(self, name: str, enabled: bool = True) -> AdminBuilder: 

84 self._features[name] = enabled 

85 return self 

86 

87 def extension( 

88 self, 

89 name: str, 

90 enabled: bool = True, 

91 **options: Any, 

92 ) -> AdminBuilder: 

93 self._extensions[name] = {"enabled": enabled, **options} 

94 return self 

95 

96 def resources(self, resources: Sequence[Any]) -> AdminBuilder: 

97 self._resources.extend(resources) 

98 return self 

99 

100 def resource( 

101 self, 

102 resource: Any, 

103 *, 

104 name: str | None = None, 

105 group: str | None = None, 

106 ) -> AdminBuilder: 

107 self._resources.append((resource, name, group)) 

108 return self 

109 

110 def controllers(self, controllers: Sequence[Any]) -> AdminBuilder: 

111 self._controllers.extend(controllers) 

112 return self 

113 

114 def controller(self, controller: Any) -> AdminBuilder: 

115 self._controllers.append(controller) 

116 return self 

117 

118 def navigation_groups( 

119 self, 

120 groups: dict[str, AdminNavigationGroup], 

121 ) -> AdminBuilder: 

122 self._navigation_groups.update(groups) 

123 return self 

124 

125 def navigation_group( 

126 self, 

127 name: str, 

128 group: AdminNavigationGroup, 

129 ) -> AdminBuilder: 

130 self._navigation_groups[name] = group 

131 return self 

132 

133 def commands(self, commands: Sequence[Any]) -> AdminBuilder: 

134 self._commands.extend(commands) 

135 return self 

136 

137 def command( 

138 self, 

139 label: str, 

140 href: str, 

141 *, 

142 icon: str = "", 

143 shortcut: str = "", 

144 ) -> AdminBuilder: 

145 self._commands.append( 

146 {"label": label, "href": href, "icon": icon, "shortcut": shortcut}, 

147 ) 

148 return self 

149 

150 def service( 

151 self, 

152 service_type: type, 

153 factory: Callable[..., Any], 

154 ) -> AdminBuilder: 

155 self._service_factories[service_type] = factory 

156 return self 

157 

158 def build(self) -> AdminProvider: 

159 from lexigram.admin.di.bundle_provider import AdminProvider 

160 

161 if self._base_config is not None: 

162 config = self._base_config.model_copy(deep=True) 

163 config.title = self._title 

164 config.prefix = self._prefix 

165 config.require_auth = self._require_auth 

166 config.debug = self._debug 

167 else: 

168 features_config = AdminFeaturesConfig(**self._features) 

169 config_data: dict[str, Any] = { 

170 "title": self._title, 

171 "prefix": self._prefix, 

172 "require_auth": self._require_auth, 

173 "debug": self._debug, 

174 "features": features_config, 

175 "ui": {"theme": self._theme}, 

176 } 

177 if self._extensions: 

178 config_data["extensions"] = self._extensions 

179 config = AdminConfig.model_validate(config_data) 

180 

181 for name, group in self._navigation_groups.items(): 

182 config.navigation_groups[name] = group 

183 

184 for cmd in self._commands: 

185 if isinstance(cmd, dict): 

186 from lexigram.admin.models.provider_models import Command 

187 

188 config.commands.append( 

189 Command( 

190 label=cmd.get("label", ""), 

191 href=cmd.get("href", ""), 

192 icon=cmd.get("icon", ""), 

193 shortcut=cmd.get("shortcut", ""), 

194 ) 

195 ) 

196 else: 

197 config.commands.append(cmd) 

198 

199 resources_list = [] 

200 for res in self._resources: 

201 if isinstance(res, tuple): 

202 resource, name, group = res 

203 resources_list.append(resource) 

204 else: 

205 resources_list.append(res) 

206 

207 return AdminProvider( 

208 config=config, 

209 resources=resources_list, 

210 controllers=list(self._controllers), 

211 )