Coverage for gws-app/gws/config/loader.py: 25%

195 statements  

« prev     ^ index     » next       coverage.py v7.11.0, created at 2025-10-16 23:09 +0200

1from typing import Optional 

2import sys 

3 

4import gws 

5import gws.spec.runtime 

6import gws.lib.jsonx 

7import gws.lib.osx 

8import gws.lib.importer 

9 

10from . import parser 

11 

12_ERROR_PREFIX = 'CONFIGURATION ERROR' 

13 

14_ROOT_NAME = 'gws_root_object' 

15 

16_DEFAULT_STORE_PATH = gws.c.CONFIG_DIR + '/config.pickle' 

17 

18_DEFAULT_CONFIG_PATHS = [ 

19 '/data/config.cx', 

20 '/data/config.json', 

21 '/data/config.yaml', 

22 '/data/config.py', 

23] 

24 

25_DEFAULT_MANIFEST_PATHS = [ 

26 '/data/MANIFEST.json', 

27] 

28 

29 

30class Object: 

31 ctx: gws.ConfigContext 

32 manifestPath: str 

33 configPath: str 

34 fallbackConfig: Optional[gws.Config] 

35 withSpecCache: bool 

36 

37 def __init__( 

38 self, 

39 manifest_path: str = '', 

40 config_path: str = '', 

41 raw_config=None, 

42 fallback_config=None, 

43 with_spec_cache=True, 

44 hooks: list = None, 

45 ): 

46 self.tm1 = _time_and_memory() 

47 

48 self.ctx = gws.ConfigContext( 

49 errors=[], 

50 ) 

51 

52 self.manifestPath = real_manifest_path(manifest_path) 

53 if self.manifestPath: 

54 gws.log.info(f'using manifest {self.manifestPath!r}...') 

55 

56 self.configPath = real_config_path(config_path) 

57 self.rawConfig = raw_config 

58 self.fallbackConfig = fallback_config 

59 self.withSpecCache = with_spec_cache 

60 self.hooks = hooks or [] 

61 

62 self.config = None 

63 self.root = None 

64 

65 def configure(self) -> Optional[gws.Root]: 

66 if not self._init_specs(): 

67 self._print_report(ok=False) 

68 return 

69 

70 self._run_hook('preConfigure') 

71 if not self.config: 

72 self.config = self._create_config() 

73 self._run_hook('postConfigure') 

74 

75 if self.config: 

76 self._run_hook('preInitialize') 

77 if not self.root: 

78 self.root = self._create_root(self.config) 

79 self._run_hook('postInitialize') 

80 

81 if not self.root and self.ctx.specs.manifest.withFallbackConfig and self.fallbackConfig: 

82 gws.log.warning(f'using fallback config') 

83 self.root = self._create_root(self.fallbackConfig) 

84 

85 if not self.root: 

86 self._print_report(ok=False) 

87 return 

88 

89 if self.ctx.errors and self.ctx.specs.manifest.withStrictConfig: 

90 self._print_report(ok=False) 

91 return 

92 

93 self._print_report(ok=True) 

94 

95 self.root.configPaths = list(self.ctx.paths) 

96 return self.root 

97 

98 def parse(self) -> Optional[gws.Config]: 

99 if not self._init_specs(): 

100 self._print_report(ok=False) 

101 return 

102 

103 cfg = self._create_config() 

104 if not cfg: 

105 self._print_report(ok=False) 

106 return 

107 

108 self._print_report(ok=True) 

109 return cfg 

110 

111 ## 

112 

113 def _init_specs(self): 

114 # if not self.manifestPath: 

115 # self._error(gws.ConfigurationError('no manifest file found')) 

116 try: 

117 self.ctx.specs = gws.spec.runtime.create( 

118 manifest_path=self.manifestPath, 

119 # read_cache=self.withSpecCache, 

120 # write_cache=self.withSpecCache, 

121 ) 

122 return True 

123 except Exception as exc: 

124 gws.log.exception() 

125 self._error(exc) 

126 return False 

127 

128 def _create_config(self): 

129 if self.rawConfig: 

130 return parser.parse_app_dict(self.rawConfig, '', self.ctx) 

131 if not self.configPath: 

132 self._error(gws.ConfigurationError('no configuration file found')) 

133 return 

134 gws.log.info(f'using config {self.configPath!r}...') 

135 return parser.parse_app_from_path(self.configPath, self.ctx) 

136 

137 def _create_root(self, cfg): 

138 root = initialize(self.ctx.specs, cfg) 

139 if root: 

140 for err in root.configErrors: 

141 self._error(err) 

142 return root 

143 

144 def _run_hook(self, event): 

145 for evt, fn in self.hooks: 

146 if event != evt: 

147 continue 

148 try: 

149 fn(self) 

150 except Exception as exc: 

151 gws.log.exception() 

152 self._error(exc) 

153 

154 def _error(self, exc): 

155 self.ctx.errors.append(getattr(exc, 'args', repr(exc))) 

156 

157 def _print_report(self, ok): 

158 err_cnt = len(self.ctx.errors) 

159 

160 if err_cnt > 0: 

161 for n, err in enumerate(self.ctx.errors, 1): 

162 gws.log.error(f'{_ERROR_PREFIX}: {n} of {err_cnt}') 

163 self._log(err) 

164 gws.log.error(f'{_ERROR_PREFIX}: {"-" * 60}') 

165 

166 info = _info_string(self.root, self.tm1) 

167 

168 if not ok: 

169 gws.log.error(f'configuration FAILED, {err_cnt} errors, {info}') 

170 return 

171 

172 if err_cnt > 0: 

173 gws.log.warning(f'configuration complete, {err_cnt} error(s), {info}') 

174 return 

175 

176 gws.log.info(f'configuration ok, {info}') 

177 

178 def _log(self, a): 

179 if isinstance(a, (list, tuple)): 

180 for item in a: 

181 self._log(item) 

182 return 

183 

184 if hasattr(a, 'args'): 

185 lines = [str(x) for x in a.args] 

186 else: 

187 lines = str(a).split('\n') 

188 

189 for s in lines: 

190 gws.log.error(f'{_ERROR_PREFIX}: {s}') 

191 

192 

193def configure( 

194 manifest_path='', 

195 config_path='', 

196 raw_config=None, 

197 fallback_config=None, 

198 with_spec_cache=True, 

199 hooks: list = None, 

200) -> Optional[gws.Root]: 

201 """Configure the server, return the Root object.""" 

202 

203 ldr = Object( 

204 manifest_path, 

205 config_path, 

206 raw_config, 

207 fallback_config, 

208 with_spec_cache, 

209 hooks, 

210 ) 

211 return ldr.configure() 

212 

213 

214def parse(manifest_path='', config_path='') -> Optional[gws.Config]: 

215 """Parse input configuration and return a config object.""" 

216 

217 ldr = Object( 

218 manifest_path, 

219 config_path, 

220 ) 

221 return ldr.parse() 

222 

223 

224def initialize(specs: gws.SpecRuntime, config: gws.Config) -> gws.Root: 

225 root = gws.create_root(specs) 

226 root.create_application(config) 

227 root.post_initialize() 

228 return root 

229 

230 

231def activate(root: gws.Root): 

232 root.activate() 

233 return gws.u.set_app_global(_ROOT_NAME, root) 

234 

235 

236def deactivate(): 

237 return gws.u.delete_app_global(_ROOT_NAME) 

238 

239 

240def store(root: gws.Root, path=None) -> str: 

241 path = path or _DEFAULT_STORE_PATH 

242 gws.log.debug(f'writing config to {path!r}') 

243 try: 

244 gws.lib.jsonx.to_path(f'{path}.syspath.json', sys.path) 

245 gws.u.serialize_to_path(root, path) 

246 return path 

247 except Exception as exc: 

248 raise gws.ConfigurationError('unable to store configuration') from exc 

249 

250 

251def load(path=None) -> gws.Root: 

252 ui = gws.lib.osx.user_info() 

253 path = path or _DEFAULT_STORE_PATH 

254 gws.log.info(f'loading config from {path!r}, user {ui["pw_name"]} ({ui["pw_uid"]}:{ui["pw_gid"]})') 

255 try: 

256 return _load(path) 

257 except Exception as exc: 

258 raise gws.ConfigurationError('unable to load configuration') from exc 

259 

260 

261def _load(path) -> gws.Root: 

262 sys_path = gws.lib.jsonx.from_path(f'{path}.syspath.json') 

263 for p in sys_path: 

264 if p not in sys.path: 

265 sys.path.insert(0, p) 

266 gws.log.debug(f'path {p!r} added to sys.path') 

267 

268 tm1 = _time_and_memory() 

269 root = gws.u.unserialize_from_path(path) 

270 activate(root) 

271 info = _info_string(root, tm1) 

272 gws.log.info(f'configuration loaded, {info}') 

273 

274 return root 

275 

276 

277def get_root() -> gws.Root: 

278 def _err(): 

279 raise gws.Error('no configuration root found') 

280 

281 return gws.u.get_app_global(_ROOT_NAME, _err) 

282 

283 

284def real_config_path(config_path: str) -> str: 

285 p = config_path or gws.env.GWS_CONFIG 

286 if p: 

287 return p 

288 for p in _DEFAULT_CONFIG_PATHS: 

289 if gws.u.is_file(p): 

290 return p 

291 return '' 

292 

293 

294def real_manifest_path(manifest_path: str) -> str: 

295 p = manifest_path or gws.env.GWS_MANIFEST 

296 if p: 

297 return p 

298 for p in _DEFAULT_MANIFEST_PATHS: 

299 if gws.u.is_file(p): 

300 return p 

301 return '' 

302 

303 

304def _time_and_memory(): 

305 return gws.u.stime(), gws.lib.osx.process_rss_size() 

306 

307 

308def _info_string(root, tm1): 

309 tm2 = _time_and_memory() 

310 return '{:d} objects, time: {:d} s., memory: {:.2f} MB'.format( 

311 root.object_count() if root else 0, 

312 tm2[0] - tm1[0], 

313 tm2[1] - tm1[1], 

314 )