Coverage for gws-app/gws/config/loader.py: 23%
216 statements
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-24 12:46 +0200
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-24 12:46 +0200
1from typing import Optional
2import sys
4import gws
5import gws.spec.runtime
6import gws.lib.jsonx
7import gws.lib.osx
8import gws.lib.dynimport
10from . import parser
12_ERROR_PREFIX = 'CONFIGURATION ERROR'
14_ROOT_NAME = 'gws_root_object'
16_DEFAULT_STORE_PATH = gws.c.CONFIG_DIR + '/config.pickle'
18_DEFAULT_CONFIG_PATHS = [
19 '/data/config.cx',
20 '/data/config.json',
21 '/data/config.yaml',
22 '/data/config.py',
23]
25_DEFAULT_MANIFEST_PATHS = [
26 '/data/MANIFEST.json',
27]
30class Object:
31 ctx: gws.ConfigContext
32 manifestPath: str
33 configPath: str
34 fallbackConfig: Optional[gws.Config]
35 withSpecCache: bool
37 def __init__(
38 self,
39 manifest_path='',
40 config_path='',
41 specs=None,
42 raw_config=None,
43 fallback_config=None,
44 with_spec_cache=False,
45 hooks=None,
46 ):
47 self.tm1 = _time_and_memory()
49 self.ctx = gws.ConfigContext(
50 errors=[],
51 )
53 self.manifestPath = real_manifest_path(manifest_path)
54 if self.manifestPath:
55 gws.log.info(f'using manifest {self.manifestPath!r}...')
57 self.configPath = real_config_path(config_path)
58 self.rawConfig = raw_config
59 self.fallbackConfig = fallback_config
60 self.withSpecCache = with_spec_cache
61 self.hooks = hooks or []
62 self.specs = specs
64 self.config = None
65 self.root = None
67 def configure(self) -> gws.ConfigResult:
68 if not self._init_specs():
69 return self._result()
71 self._run_hook('preConfigure')
72 if not self.config:
73 self.config = self._create_config()
74 self._run_hook('postConfigure')
76 if self.config:
77 self._run_hook('preInitialize')
78 if not self.root:
79 self.root = self._create_root(self.config)
80 self._run_hook('postInitialize')
82 if not self.root and self.ctx.specs.manifest.withFallbackConfig and self.fallbackConfig:
83 gws.log.warning(f'using fallback config')
84 self.root = self._create_root(self.fallbackConfig)
86 if not self.root:
87 return self._result()
89 if self.ctx.errors and self.ctx.specs.manifest.withStrictConfig:
90 self.root = None
91 return self._result()
93 self.root.configPaths = list(self.ctx.paths)
94 return self._result()
96 def parse(self) -> gws.ConfigResult:
97 if not self._init_specs():
98 return self._result()
100 self.config = self._create_config()
101 if not self.config:
102 return self._result()
104 return self._result()
106 ##
108 def _init_specs(self):
109 if self.specs:
110 self.ctx.specs = self.specs
111 return True
113 try:
114 self.ctx.specs = gws.spec.runtime.create(
115 manifest_path=self.manifestPath,
116 read_cache=self.withSpecCache,
117 write_cache=self.withSpecCache,
118 )
119 return True
120 except Exception as exc:
121 gws.log.exception()
122 self._error(exc)
123 return False
125 def _create_config(self):
126 if self.rawConfig:
127 return parser.parse_app_dict(self.rawConfig, '', self.ctx)
128 if not self.configPath:
129 self._error(gws.ConfigurationError('no configuration file found'))
130 return
131 gws.log.info(f'using config {self.configPath!r}...')
132 return parser.parse_app_from_path(self.configPath, self.ctx)
134 def _create_root(self, cfg):
135 root = initialize(self.ctx.specs, cfg)
136 if root:
137 for ce in root.configErrors:
138 self.ctx.errors.append(gws.ConfigErrorInfo(ce))
139 return root
141 def _run_hook(self, event):
142 for evt, fn in self.hooks:
143 if event != evt:
144 continue
145 try:
146 fn(self)
147 except Exception as exc:
148 gws.log.exception()
149 self._error(exc)
151 def _error(self, exc):
152 cei = gws.ConfigErrorInfo(message=str(exc))
153 if exc.__cause__:
154 cei.cause = repr(exc.__cause__)
155 self.ctx.errors.append(cei)
157 def _result(self):
158 return gws.ConfigResult(
159 errors=self.ctx.errors,
160 root=self.root,
161 config=self.config,
162 info=_info_string(self.root, self.tm1),
163 )
166def configure(
167 manifest_path='',
168 config_path='',
169 specs: Optional[gws.SpecRuntime] = None,
170 raw_config: dict | gws.Data = None,
171 fallback_config: dict | gws.Data = None,
172 with_spec_cache=False,
173 hooks: list = None,
174) -> gws.ConfigResult:
175 """Configure the server."""
177 ldr = Object(
178 manifest_path,
179 config_path,
180 specs,
181 raw_config,
182 fallback_config,
183 with_spec_cache,
184 hooks,
185 )
186 return ldr.configure()
189def parse(
190 manifest_path='',
191 config_path='',
192 specs: Optional[gws.SpecRuntime] = None,
193) -> gws.ConfigResult:
194 """Parse input configuration."""
196 ldr = Object(
197 manifest_path,
198 config_path,
199 specs,
200 )
201 return ldr.parse()
204def initialize(specs: gws.SpecRuntime, config: gws.Config) -> gws.Root:
205 root = gws.create_root(specs)
206 root.create_application(config)
207 root.post_initialize()
208 return root
211def activate(root: gws.Root):
212 root.activate()
213 return gws.u.set_app_global(_ROOT_NAME, root)
216def deactivate():
217 return gws.u.delete_app_global(_ROOT_NAME)
220def store(root: gws.Root, path=None) -> str:
221 path = path or _DEFAULT_STORE_PATH
222 gws.log.debug(f'writing config to {path!r}')
223 try:
224 gws.lib.jsonx.to_path(f'{path}.syspath.json', sys.path)
225 gws.u.serialize_to_path(root, path)
226 return path
227 except Exception as exc:
228 raise gws.ConfigurationError('unable to store configuration') from exc
231def load(path=None) -> gws.Root:
232 ui = gws.lib.osx.user_info()
233 path = path or _DEFAULT_STORE_PATH
234 gws.log.info(f'loading config from {path!r}, user {ui["pw_name"]} ({ui["pw_uid"]}:{ui["pw_gid"]})')
235 try:
236 return _load(path)
237 except Exception as exc:
238 raise gws.ConfigurationError('unable to load configuration') from exc
241def _load(path) -> gws.Root:
242 sys_path = gws.lib.jsonx.from_path(f'{path}.syspath.json')
243 for p in sys_path:
244 if p not in sys.path:
245 sys.path.insert(0, p)
246 gws.log.debug(f'path {p!r} added to sys.path')
248 tm1 = _time_and_memory()
249 root = gws.u.unserialize_from_path(path)
250 activate(root)
251 info = _info_string(root, tm1)
252 gws.log.info(f'configuration loaded, {info}')
254 return root
257def get_root() -> gws.Root:
258 def _err():
259 raise gws.Error('no configuration root found')
261 return gws.u.get_app_global(_ROOT_NAME, _err)
264def real_config_path(config_path: str) -> str:
265 p = config_path or gws.env.GWS_CONFIG
266 if p:
267 for s in p.split(','):
268 s = s.strip()
269 if gws.u.is_file(s):
270 return s
271 return ''
272 for p in _DEFAULT_CONFIG_PATHS:
273 if gws.u.is_file(p):
274 return p
275 return ''
278def real_manifest_path(manifest_path: str) -> str:
279 p = manifest_path or gws.env.GWS_MANIFEST
280 if p:
281 return p
282 for p in _DEFAULT_MANIFEST_PATHS:
283 if gws.u.is_file(p):
284 return p
285 return ''
288def log_report(cr: gws.ConfigResult):
289 err_cnt = len(cr.errors) if cr.errors else 0
290 ln = '*' * 80
292 if err_cnt == 0:
293 gws.log.info(ln)
294 gws.log.info(f'configured: {cr.info}')
295 gws.log.info(ln)
296 return
298 gws.log.error(ln)
299 gws.log.error(f'configured wth errors: errors: {err_cnt}, {cr.info}')
300 gws.log.error(ln)
302 # cr.errors.sort(key=lambda ce: ce.message)
304 for n, cei in enumerate(cr.errors, 1):
305 gws.log.error(f'{_ERROR_PREFIX}: {n} of {err_cnt}')
306 _log_error(cei)
307 gws.log.error(f'{_ERROR_PREFIX}: ')
309 gws.log.error(ln)
312def _log_error(cei: gws.ConfigErrorInfo):
313 ls = []
314 ls.append(cei.message)
315 tab = ' ' * 4
317 if cei.path:
318 ls.append(f'PATH: {cei.path}')
319 if cei.line:
320 ls.append(f'LINE: {cei.line}')
321 if cei.value:
322 ls.append(f'VALUE: {cei.value}')
323 if cei.cause:
324 ls.append(f'CAUSE: {cei.cause}')
325 if cei.stack:
326 for loc in cei.stack:
327 p = [
328 loc.objectType,
329 repr(loc.objectName) if loc.objectName else None,
330 f'uid={loc.objectUid}' if loc.objectUid else None,
331 ]
332 p = '<' + ' '.join(gws.u.compact(p)) + '>'
333 if loc.propName:
334 p = f'{loc.propName!r} {p}'
335 ls.append(f'{tab}in {p}')
336 if cei.contextLines:
337 ls.extend(cei.contextLines)
339 for s in ls:
340 gws.log.error(f'{_ERROR_PREFIX}: {s}')
343def _time_and_memory():
344 return gws.u.stime(), gws.lib.osx.process_rss_size()
347def _info_string(root, tm1):
348 tm2 = _time_and_memory()
349 return 'objects: {:d}, time: {:d}s., memory: {:.2f} MB'.format(
350 root.object_count() if root else 0,
351 tm2[0] - tm1[0],
352 tm2[1] - tm1[1],
353 )