Coverage for gws-app/gws/config/loader.py: 25%
195 statements
« prev ^ index » next coverage.py v7.11.0, created at 2025-10-16 22:59 +0200
« prev ^ index » next coverage.py v7.11.0, created at 2025-10-16 22:59 +0200
1from typing import Optional
2import sys
4import gws
5import gws.spec.runtime
6import gws.lib.jsonx
7import gws.lib.osx
8import gws.lib.importer
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: 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()
48 self.ctx = gws.ConfigContext(
49 errors=[],
50 )
52 self.manifestPath = real_manifest_path(manifest_path)
53 if self.manifestPath:
54 gws.log.info(f'using manifest {self.manifestPath!r}...')
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 []
62 self.config = None
63 self.root = None
65 def configure(self) -> Optional[gws.Root]:
66 if not self._init_specs():
67 self._print_report(ok=False)
68 return
70 self._run_hook('preConfigure')
71 if not self.config:
72 self.config = self._create_config()
73 self._run_hook('postConfigure')
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')
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)
85 if not self.root:
86 self._print_report(ok=False)
87 return
89 if self.ctx.errors and self.ctx.specs.manifest.withStrictConfig:
90 self._print_report(ok=False)
91 return
93 self._print_report(ok=True)
95 self.root.configPaths = list(self.ctx.paths)
96 return self.root
98 def parse(self) -> Optional[gws.Config]:
99 if not self._init_specs():
100 self._print_report(ok=False)
101 return
103 cfg = self._create_config()
104 if not cfg:
105 self._print_report(ok=False)
106 return
108 self._print_report(ok=True)
109 return cfg
111 ##
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
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)
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
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)
154 def _error(self, exc):
155 self.ctx.errors.append(getattr(exc, 'args', repr(exc)))
157 def _print_report(self, ok):
158 err_cnt = len(self.ctx.errors)
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}')
166 info = _info_string(self.root, self.tm1)
168 if not ok:
169 gws.log.error(f'configuration FAILED, {err_cnt} errors, {info}')
170 return
172 if err_cnt > 0:
173 gws.log.warning(f'configuration complete, {err_cnt} error(s), {info}')
174 return
176 gws.log.info(f'configuration ok, {info}')
178 def _log(self, a):
179 if isinstance(a, (list, tuple)):
180 for item in a:
181 self._log(item)
182 return
184 if hasattr(a, 'args'):
185 lines = [str(x) for x in a.args]
186 else:
187 lines = str(a).split('\n')
189 for s in lines:
190 gws.log.error(f'{_ERROR_PREFIX}: {s}')
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."""
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()
214def parse(manifest_path='', config_path='') -> Optional[gws.Config]:
215 """Parse input configuration and return a config object."""
217 ldr = Object(
218 manifest_path,
219 config_path,
220 )
221 return ldr.parse()
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
231def activate(root: gws.Root):
232 root.activate()
233 return gws.u.set_app_global(_ROOT_NAME, root)
236def deactivate():
237 return gws.u.delete_app_global(_ROOT_NAME)
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
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
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')
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}')
274 return root
277def get_root() -> gws.Root:
278 def _err():
279 raise gws.Error('no configuration root found')
281 return gws.u.get_app_global(_ROOT_NAME, _err)
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 ''
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 ''
304def _time_and_memory():
305 return gws.u.stime(), gws.lib.osx.process_rss_size()
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 )