vortex-nwp 2.0.0b1__py3-none-any.whl → 2.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- vortex/__init__.py +75 -47
- vortex/algo/__init__.py +3 -2
- vortex/algo/components.py +944 -618
- vortex/algo/mpitools.py +802 -497
- vortex/algo/mpitools_templates/__init__.py +1 -0
- vortex/algo/serversynctools.py +34 -33
- vortex/config.py +19 -22
- vortex/data/__init__.py +9 -3
- vortex/data/abstractstores.py +593 -655
- vortex/data/containers.py +217 -162
- vortex/data/contents.py +65 -39
- vortex/data/executables.py +93 -102
- vortex/data/flow.py +40 -34
- vortex/data/geometries.py +228 -132
- vortex/data/handlers.py +436 -227
- vortex/data/outflow.py +15 -15
- vortex/data/providers.py +185 -163
- vortex/data/resources.py +48 -42
- vortex/data/stores.py +540 -417
- vortex/data/sync_templates/__init__.py +0 -0
- vortex/gloves.py +114 -87
- vortex/layout/__init__.py +1 -8
- vortex/layout/contexts.py +150 -84
- vortex/layout/dataflow.py +353 -202
- vortex/layout/monitor.py +264 -128
- vortex/nwp/__init__.py +5 -2
- vortex/nwp/algo/__init__.py +14 -5
- vortex/nwp/algo/assim.py +205 -151
- vortex/nwp/algo/clim.py +683 -517
- vortex/nwp/algo/coupling.py +447 -225
- vortex/nwp/algo/eda.py +437 -229
- vortex/nwp/algo/eps.py +403 -231
- vortex/nwp/algo/forecasts.py +416 -275
- vortex/nwp/algo/fpserver.py +683 -307
- vortex/nwp/algo/ifsnaming.py +205 -145
- vortex/nwp/algo/ifsroot.py +215 -122
- vortex/nwp/algo/monitoring.py +137 -76
- vortex/nwp/algo/mpitools.py +330 -190
- vortex/nwp/algo/odbtools.py +637 -353
- vortex/nwp/algo/oopsroot.py +454 -273
- vortex/nwp/algo/oopstests.py +90 -56
- vortex/nwp/algo/request.py +287 -206
- vortex/nwp/algo/stdpost.py +878 -522
- vortex/nwp/data/__init__.py +22 -4
- vortex/nwp/data/assim.py +125 -137
- vortex/nwp/data/boundaries.py +121 -68
- vortex/nwp/data/climfiles.py +193 -211
- vortex/nwp/data/configfiles.py +73 -69
- vortex/nwp/data/consts.py +426 -401
- vortex/nwp/data/ctpini.py +59 -43
- vortex/nwp/data/diagnostics.py +94 -66
- vortex/nwp/data/eda.py +50 -51
- vortex/nwp/data/eps.py +195 -146
- vortex/nwp/data/executables.py +440 -434
- vortex/nwp/data/fields.py +63 -48
- vortex/nwp/data/gridfiles.py +183 -111
- vortex/nwp/data/logs.py +250 -217
- vortex/nwp/data/modelstates.py +180 -151
- vortex/nwp/data/monitoring.py +72 -99
- vortex/nwp/data/namelists.py +254 -202
- vortex/nwp/data/obs.py +400 -308
- vortex/nwp/data/oopsexec.py +22 -20
- vortex/nwp/data/providers.py +90 -65
- vortex/nwp/data/query.py +71 -82
- vortex/nwp/data/stores.py +49 -36
- vortex/nwp/data/surfex.py +136 -137
- vortex/nwp/syntax/__init__.py +1 -1
- vortex/nwp/syntax/stdattrs.py +173 -111
- vortex/nwp/tools/__init__.py +2 -2
- vortex/nwp/tools/addons.py +22 -17
- vortex/nwp/tools/agt.py +24 -12
- vortex/nwp/tools/bdap.py +16 -5
- vortex/nwp/tools/bdcp.py +4 -1
- vortex/nwp/tools/bdm.py +3 -0
- vortex/nwp/tools/bdmp.py +14 -9
- vortex/nwp/tools/conftools.py +728 -378
- vortex/nwp/tools/drhook.py +12 -8
- vortex/nwp/tools/grib.py +65 -39
- vortex/nwp/tools/gribdiff.py +22 -17
- vortex/nwp/tools/ifstools.py +82 -42
- vortex/nwp/tools/igastuff.py +167 -143
- vortex/nwp/tools/mars.py +14 -2
- vortex/nwp/tools/odb.py +234 -125
- vortex/nwp/tools/partitioning.py +61 -37
- vortex/nwp/tools/satrad.py +27 -12
- vortex/nwp/util/async.py +83 -55
- vortex/nwp/util/beacon.py +10 -10
- vortex/nwp/util/diffpygram.py +174 -86
- vortex/nwp/util/ens.py +144 -63
- vortex/nwp/util/hooks.py +30 -19
- vortex/nwp/util/taskdeco.py +28 -24
- vortex/nwp/util/usepygram.py +278 -172
- vortex/nwp/util/usetnt.py +31 -17
- vortex/sessions.py +72 -39
- vortex/syntax/__init__.py +1 -1
- vortex/syntax/stdattrs.py +410 -171
- vortex/syntax/stddeco.py +31 -22
- vortex/toolbox.py +327 -192
- vortex/tools/__init__.py +11 -2
- vortex/tools/actions.py +110 -121
- vortex/tools/addons.py +111 -92
- vortex/tools/arm.py +42 -22
- vortex/tools/compression.py +72 -69
- vortex/tools/date.py +11 -4
- vortex/tools/delayedactions.py +242 -132
- vortex/tools/env.py +75 -47
- vortex/tools/folder.py +342 -171
- vortex/tools/grib.py +341 -162
- vortex/tools/lfi.py +423 -216
- vortex/tools/listings.py +109 -40
- vortex/tools/names.py +218 -156
- vortex/tools/net.py +655 -299
- vortex/tools/parallelism.py +93 -61
- vortex/tools/prestaging.py +55 -31
- vortex/tools/schedulers.py +172 -105
- vortex/tools/services.py +403 -334
- vortex/tools/storage.py +293 -358
- vortex/tools/surfex.py +24 -24
- vortex/tools/systems.py +1234 -643
- vortex/tools/targets.py +156 -100
- vortex/util/__init__.py +1 -1
- vortex/util/config.py +378 -327
- vortex/util/empty.py +2 -2
- vortex/util/helpers.py +56 -24
- vortex/util/introspection.py +18 -12
- vortex/util/iosponge.py +8 -4
- vortex/util/roles.py +4 -6
- vortex/util/storefunctions.py +39 -13
- vortex/util/structs.py +3 -3
- vortex/util/worker.py +29 -17
- vortex_nwp-2.1.0.dist-info/METADATA +67 -0
- vortex_nwp-2.1.0.dist-info/RECORD +144 -0
- {vortex_nwp-2.0.0b1.dist-info → vortex_nwp-2.1.0.dist-info}/WHEEL +1 -1
- vortex/layout/appconf.py +0 -109
- vortex/layout/jobs.py +0 -1276
- vortex/layout/nodes.py +0 -1424
- vortex/layout/subjobs.py +0 -464
- vortex_nwp-2.0.0b1.dist-info/METADATA +0 -50
- vortex_nwp-2.0.0b1.dist-info/RECORD +0 -146
- {vortex_nwp-2.0.0b1.dist-info → vortex_nwp-2.1.0.dist-info/licenses}/LICENSE +0 -0
- {vortex_nwp-2.0.0b1.dist-info → vortex_nwp-2.1.0.dist-info}/top_level.txt +0 -0
vortex/util/config.py
CHANGED
|
@@ -9,9 +9,8 @@ It returns an object compliant with the interface defined in
|
|
|
9
9
|
import abc
|
|
10
10
|
from configparser import NoOptionError, NoSectionError, InterpolationDepthError
|
|
11
11
|
from configparser import ConfigParser
|
|
12
|
-
import contextlib
|
|
13
|
-
import importlib
|
|
14
12
|
import itertools
|
|
13
|
+
from pathlib import Path
|
|
15
14
|
import re
|
|
16
15
|
import string
|
|
17
16
|
|
|
@@ -25,7 +24,7 @@ __all__ = []
|
|
|
25
24
|
|
|
26
25
|
logger = loggers.getLogger(__name__)
|
|
27
26
|
|
|
28
|
-
_RE_AUTO_TPL = re.compile(r
|
|
27
|
+
_RE_AUTO_TPL = re.compile(r"^@(([^/].*)\.tpl)$")
|
|
29
28
|
|
|
30
29
|
_RE_ENCODING = re.compile(r"^\s*#.*?coding[=:]\s*([-\w.]+)")
|
|
31
30
|
|
|
@@ -41,24 +40,20 @@ class AbstractTemplatingAdapter(metaclass=abc.ABCMeta):
|
|
|
41
40
|
that should be used during template rendering.
|
|
42
41
|
"""
|
|
43
42
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
def __init__(self, tpl_str, tpl_file, tpl_encoding, tpl_dirs):
|
|
43
|
+
def __init__(self, tpl_str, tpl_file, tpl_encoding):
|
|
47
44
|
"""
|
|
48
45
|
:param tpl_str: The template (as a string)
|
|
49
|
-
:param tpl_file: The template filename (
|
|
46
|
+
:param tpl_file: The template filename (path object)
|
|
50
47
|
:param tpl_encoding: The template encoding (when read from disk)
|
|
51
|
-
:param tpl_dirs: The lookup directories for additional templates
|
|
52
48
|
"""
|
|
53
49
|
self._tpl_file = tpl_file
|
|
54
50
|
self._tpl_encoding = tpl_encoding
|
|
55
|
-
self._tpl_dirs = tpl_dirs
|
|
56
51
|
self._tpl_obj = self._rendering_tool_init(tpl_str)
|
|
57
52
|
|
|
58
53
|
@property
|
|
59
54
|
def srcfile(self):
|
|
60
55
|
"""The template filename (when read from disk)."""
|
|
61
|
-
return self._tpl_file
|
|
56
|
+
return str(self._tpl_file)
|
|
62
57
|
|
|
63
58
|
@abc.abstractmethod
|
|
64
59
|
def _rendering_tool_init(self, tpl_str):
|
|
@@ -70,7 +65,7 @@ class AbstractTemplatingAdapter(metaclass=abc.ABCMeta):
|
|
|
70
65
|
for m in kargs:
|
|
71
66
|
todo.update(m)
|
|
72
67
|
todo.update(kwargs)
|
|
73
|
-
return self(**
|
|
68
|
+
return self(**todo)
|
|
74
69
|
|
|
75
70
|
safe_substitute = substitute
|
|
76
71
|
|
|
@@ -86,8 +81,6 @@ class LegacyTemplatingAdapter(AbstractTemplatingAdapter):
|
|
|
86
81
|
See :class:`AbstractTemplatingAdapter` for more details on this class usage.
|
|
87
82
|
"""
|
|
88
83
|
|
|
89
|
-
KIND = 'legacy'
|
|
90
|
-
|
|
91
84
|
def _rendering_tool_init(self, tpl_str):
|
|
92
85
|
return string.Template(tpl_str)
|
|
93
86
|
|
|
@@ -108,8 +101,6 @@ class TwoPassLegacyTemplatingAdapter(AbstractTemplatingAdapter):
|
|
|
108
101
|
See :class:`AbstractTemplatingAdapter` for more details on this class usage.
|
|
109
102
|
"""
|
|
110
103
|
|
|
111
|
-
KIND = 'twopasslegacy'
|
|
112
|
-
|
|
113
104
|
def _rendering_tool_init(self, tpl_str):
|
|
114
105
|
return string.Template(tpl_str)
|
|
115
106
|
|
|
@@ -121,86 +112,49 @@ class TwoPassLegacyTemplatingAdapter(AbstractTemplatingAdapter):
|
|
|
121
112
|
|
|
122
113
|
def __call__(self, **kwargs):
|
|
123
114
|
"""Render the template using the kwargs dictionary."""
|
|
124
|
-
return string.Template(self._tpl_obj.substitute(kwargs)).substitute(
|
|
115
|
+
return string.Template(self._tpl_obj.substitute(kwargs)).substitute(
|
|
116
|
+
kwargs
|
|
117
|
+
)
|
|
125
118
|
|
|
126
119
|
|
|
127
|
-
|
|
128
|
-
""
|
|
120
|
+
_TEMPLATE_RENDERING_CLASSES = {
|
|
121
|
+
"legacy": LegacyTemplatingAdapter,
|
|
122
|
+
"twopasslegacy": TwoPassLegacyTemplatingAdapter,
|
|
123
|
+
}
|
|
129
124
|
|
|
130
|
-
It requires the, external, :mod:`jinja2` package. Please refer to the
|
|
131
|
-
jinja2 documentation for more details on the jinja2 templating language.
|
|
132
125
|
|
|
133
|
-
|
|
134
|
-
|
|
126
|
+
def register_template_renderer(key, cls):
|
|
127
|
+
_TEMPLATE_RENDERING_CLASSES[key] = cls
|
|
135
128
|
|
|
136
|
-
KIND = 'jinja2'
|
|
137
|
-
|
|
138
|
-
@contextlib.contextmanager
|
|
139
|
-
def _elaborate_on_jinja2_error(self):
|
|
140
|
-
import jinja2
|
|
141
|
-
try:
|
|
142
|
-
yield
|
|
143
|
-
except jinja2.exceptions.TemplateError as e:
|
|
144
|
-
if isinstance(e, jinja2.exceptions.TemplateSyntaxError):
|
|
145
|
-
logger.error("%s exception while processing Jinja2 templates:\n" +
|
|
146
|
-
" Toplevel template file: %s\n"
|
|
147
|
-
" Jinja2 template info : %s (line %d)\n" +
|
|
148
|
-
" Jinja2 error message : %s",
|
|
149
|
-
e.__class__, self._tpl_file, e.name, e.lineno, e.message)
|
|
150
|
-
else:
|
|
151
|
-
logger.error("%s exception while processing Jinja2 templates:\n" +
|
|
152
|
-
" Toplevel template file: %s",
|
|
153
|
-
e.__class__, self._tpl_file)
|
|
154
|
-
raise
|
|
155
|
-
|
|
156
|
-
def _rendering_tool_init(self, tpl_str):
|
|
157
|
-
import jinja2
|
|
158
|
-
loader = jinja2.FileSystemLoader(
|
|
159
|
-
self._tpl_dirs,
|
|
160
|
-
encoding=self._tpl_encoding,
|
|
161
|
-
followlinks=True) if self._tpl_dirs else None
|
|
162
|
-
j_env = jinja2.Environment(loader=loader, autoescape=False)
|
|
163
|
-
with self._elaborate_on_jinja2_error():
|
|
164
|
-
return j_env.from_string(tpl_str)
|
|
165
129
|
|
|
166
|
-
|
|
167
|
-
"""Render the template using the kwargs dictionary."""
|
|
168
|
-
with self._elaborate_on_jinja2_error():
|
|
169
|
-
return self._tpl_obj.render(kwargs)
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
def load_template(t, tplfile, encoding=None, version=None, default_templating='legacy'):
|
|
130
|
+
def load_template(tplpath, encoding=None, default_templating="legacy"):
|
|
173
131
|
"""Load a template according to *tplfile*.
|
|
174
132
|
|
|
175
|
-
|
|
176
|
-
:param vortex.sessions.Ticket t: The Vortex' session to be used
|
|
177
|
-
:param str tplfile: The name of the desired template file
|
|
133
|
+
:param str tplpath: path-like object for the template file
|
|
178
134
|
:param str encoding: The characters encoding of the template file
|
|
179
135
|
:param int version: Find a template file with version >= to version
|
|
180
|
-
:param str default_templating: The default templating engine that will
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
136
|
+
:param str default_templating: The default templating engine that will
|
|
137
|
+
be used. The content of the template file is always searched in
|
|
138
|
+
order to detect a "# vortex-templating:" comment that will overrid
|
|
139
|
+
this default.
|
|
184
140
|
:return: A :class:`AbstractTemplatingAdapter` object
|
|
185
141
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
be
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
of the template file. If it exists, the ``kind`` templating engine will be
|
|
203
|
-
used and the ``# vortex-templating:kind`` line will be stripped.
|
|
142
|
+
The characters encoding of the template file may be specified. If
|
|
143
|
+
*encoding* equals ``script``, a line looking like ``#
|
|
144
|
+
encoding:special-encoding`` will be searched for in the first ten
|
|
145
|
+
lines of the template file. If it exists, the ``special-encoding``
|
|
146
|
+
will be used as an encoding and the ``#
|
|
147
|
+
encoding:special-encoding`` line will be stripped from the
|
|
148
|
+
template.
|
|
149
|
+
|
|
150
|
+
Different templating engine may be used to render the template
|
|
151
|
+
file. It defaults to ``legacy`` that is compatible with Python's
|
|
152
|
+
:class:`string.Template` class. However, another default may be
|
|
153
|
+
provided using the *default_templating* argument. In any case, a
|
|
154
|
+
line looking like ``# vortex-templating:kind`` will be searched
|
|
155
|
+
for in the first ten lines of the template file. If it exists, the
|
|
156
|
+
``kind`` templating engine will be used and the ``#
|
|
157
|
+
vortex-templating:kind`` line will be stripped.
|
|
204
158
|
|
|
205
159
|
Currently, only few templating engines are supported:
|
|
206
160
|
|
|
@@ -208,95 +162,52 @@ def load_template(t, tplfile, encoding=None, version=None, default_templating='l
|
|
|
208
162
|
* ``twopasslegacy``: see :class:`TwoPassLegacyTemplatingAdapter`
|
|
209
163
|
* ``jinja2``: see :class:`Jinja2TemplatingAdapter`
|
|
210
164
|
"""
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
for dirname in searchdirs:
|
|
230
|
-
filename = t.sh.path.join(dirname, autofile)
|
|
231
|
-
if t.sh.path.exists(filename):
|
|
232
|
-
new_tplfile = filename
|
|
233
|
-
break
|
|
234
|
-
else:
|
|
235
|
-
autofile = autofile.group(2)
|
|
236
|
-
autodir = t.sh.path.dirname(autofile)
|
|
237
|
-
if autodir:
|
|
238
|
-
persodir = t.sh.path.join(persodir, autodir)
|
|
239
|
-
sitedir = t.sh.path.join(sitedir, sitedir)
|
|
240
|
-
autofile = t.sh.path.basename(autofile)
|
|
241
|
-
allowedre = re.compile(autofile + r'-v(\d+).tpl')
|
|
242
|
-
alloweditems = dict()
|
|
243
|
-
for inputdir in (sitedir, persodir):
|
|
244
|
-
if not t.sh.path.exists(inputdir):
|
|
245
|
-
continue
|
|
246
|
-
for fs_item in t.sh.listdir(inputdir):
|
|
247
|
-
fs_match = allowedre.match(fs_item)
|
|
248
|
-
if fs_match:
|
|
249
|
-
alloweditems[int(fs_match.group(1))] = t.sh.path.join(inputdir, fs_item)
|
|
250
|
-
for item_version in sorted(alloweditems.keys(), reverse=True):
|
|
251
|
-
if item_version <= version:
|
|
252
|
-
new_tplfile = alloweditems[item_version]
|
|
253
|
-
break
|
|
254
|
-
if not new_tplfile:
|
|
255
|
-
raise ValueError('Template file not found: <{}> with version >= {!s}.'
|
|
256
|
-
.format(tplfile, version))
|
|
257
|
-
else:
|
|
258
|
-
tplfile = new_tplfile
|
|
259
|
-
try:
|
|
260
|
-
ignored_lines = set()
|
|
261
|
-
actual_encoding = None if encoding == 'script' else encoding
|
|
262
|
-
actual_templating = default_templating
|
|
263
|
-
# To determine the encoding & templating open the file with the default
|
|
264
|
-
# encoding (ignoring decoding errors) and look for comments
|
|
265
|
-
with open(tplfile, errors='replace') as tpfld_tmp:
|
|
266
|
-
if encoding is None:
|
|
267
|
-
actual_encoding = tpfld_tmp.encoding
|
|
268
|
-
# Only inspect the first 10 lines
|
|
269
|
-
for iline, line in enumerate(itertools.islice(tpfld_tmp, 10)):
|
|
270
|
-
# Encoding
|
|
271
|
-
if encoding == 'script':
|
|
272
|
-
encoding_match = _RE_ENCODING.match(line)
|
|
273
|
-
if encoding_match:
|
|
274
|
-
ignored_lines.add(iline)
|
|
275
|
-
actual_encoding = encoding_match.group(1)
|
|
276
|
-
# Templating
|
|
277
|
-
templating_match = _RE_TEMPLATING.match(line)
|
|
278
|
-
if templating_match:
|
|
165
|
+
tplpath = Path(tplpath).absolute()
|
|
166
|
+
if not tplpath.exists():
|
|
167
|
+
msg = f"Template file {tplpath} not found"
|
|
168
|
+
raise FileNotFoundError(msg)
|
|
169
|
+
ignored_lines = set()
|
|
170
|
+
actual_encoding = None if encoding == "script" else encoding
|
|
171
|
+
actual_templating = default_templating
|
|
172
|
+
# To determine the encoding & templating open the file with the default
|
|
173
|
+
# encoding (ignoring decoding errors) and look for comments
|
|
174
|
+
with open(tplpath, errors="replace") as tpfld_tmp:
|
|
175
|
+
if encoding is None:
|
|
176
|
+
actual_encoding = tpfld_tmp.encoding
|
|
177
|
+
# Only inspect the first 10 lines
|
|
178
|
+
for iline, line in enumerate(itertools.islice(tpfld_tmp, 10)):
|
|
179
|
+
# Encoding
|
|
180
|
+
if encoding == "script":
|
|
181
|
+
encoding_match = _RE_ENCODING.match(line)
|
|
182
|
+
if encoding_match:
|
|
279
183
|
ignored_lines.add(iline)
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
184
|
+
actual_encoding = encoding_match.group(1)
|
|
185
|
+
# Templating
|
|
186
|
+
templating_match = _RE_TEMPLATING.match(line)
|
|
187
|
+
if templating_match:
|
|
188
|
+
ignored_lines.add(iline)
|
|
189
|
+
actual_templating = templating_match.group(1)
|
|
190
|
+
# Read the template and delete the encoding line if present
|
|
191
|
+
logger.debug("Opening %s with encoding %s", tplpath, str(actual_encoding))
|
|
192
|
+
with open(tplpath, encoding=actual_encoding) as tpfld:
|
|
193
|
+
tpl_txt = "".join(
|
|
194
|
+
[l for (i, l) in enumerate(tpfld) if i not in ignored_lines]
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
try:
|
|
198
|
+
template_rendering_cls = _TEMPLATE_RENDERING_CLASSES[actual_templating]
|
|
199
|
+
except KeyError:
|
|
200
|
+
msg = (
|
|
201
|
+
f"Unknown templating systes < {actual_templating} >"
|
|
202
|
+
f"when trying to load template {tplpath}"
|
|
203
|
+
)
|
|
204
|
+
logger.error(msg)
|
|
205
|
+
|
|
206
|
+
return template_rendering_cls(
|
|
207
|
+
tpl_txt,
|
|
208
|
+
tplpath,
|
|
209
|
+
actual_encoding,
|
|
210
|
+
)
|
|
300
211
|
|
|
301
212
|
|
|
302
213
|
class GenericReadOnlyConfigParser:
|
|
@@ -324,10 +235,17 @@ class GenericReadOnlyConfigParser:
|
|
|
324
235
|
documentation for more details.
|
|
325
236
|
"""
|
|
326
237
|
|
|
327
|
-
_RE_AUTO_SETFILE = re.compile(r
|
|
328
|
-
|
|
329
|
-
def __init__(
|
|
330
|
-
|
|
238
|
+
_RE_AUTO_SETFILE = re.compile(r"^@([^/]+\.ini)$")
|
|
239
|
+
|
|
240
|
+
def __init__(
|
|
241
|
+
self,
|
|
242
|
+
inifile=None,
|
|
243
|
+
parser=None,
|
|
244
|
+
mkforce=False,
|
|
245
|
+
clsparser=_DEFAULT_CONFIG_PARSER,
|
|
246
|
+
encoding=None,
|
|
247
|
+
defaultinifile=None,
|
|
248
|
+
):
|
|
331
249
|
self.parser = parser
|
|
332
250
|
self.mkforce = mkforce
|
|
333
251
|
self.clsparser = clsparser
|
|
@@ -345,7 +263,7 @@ class GenericReadOnlyConfigParser:
|
|
|
345
263
|
|
|
346
264
|
def as_dump(self):
|
|
347
265
|
"""Return a nicely formated class name for dump in footprint."""
|
|
348
|
-
return
|
|
266
|
+
return "file={!s}".format(self.file)
|
|
349
267
|
|
|
350
268
|
def setfile(self, inifile, encoding=None):
|
|
351
269
|
"""Read the specified **inifile** as new configuration.
|
|
@@ -387,12 +305,16 @@ class GenericReadOnlyConfigParser:
|
|
|
387
305
|
glove = sessions.current().glove
|
|
388
306
|
if not isinstance(inifile, str):
|
|
389
307
|
if self.defaultinifile:
|
|
390
|
-
sitedefaultinifile = glove.siteconf +
|
|
308
|
+
sitedefaultinifile = glove.siteconf + "/" + self.defaultinifile
|
|
391
309
|
if local.path.exists(sitedefaultinifile):
|
|
392
310
|
with open(sitedefaultinifile, encoding=encoding) as a_fh:
|
|
393
311
|
self.parser.read_file(a_fh)
|
|
394
312
|
else:
|
|
395
|
-
raise ValueError(
|
|
313
|
+
raise ValueError(
|
|
314
|
+
"Configuration file "
|
|
315
|
+
+ sitedefaultinifile
|
|
316
|
+
+ " not found"
|
|
317
|
+
)
|
|
396
318
|
# Assume it's an IO descriptor
|
|
397
319
|
inifile.seek(0)
|
|
398
320
|
self.parser.read_file(inifile)
|
|
@@ -406,11 +328,13 @@ class GenericReadOnlyConfigParser:
|
|
|
406
328
|
if local.path.exists(inifile):
|
|
407
329
|
filestack.append(local.path.abspath(inifile))
|
|
408
330
|
else:
|
|
409
|
-
raise ValueError(
|
|
331
|
+
raise ValueError(
|
|
332
|
+
"Configuration file " + inifile + " not found"
|
|
333
|
+
)
|
|
410
334
|
else:
|
|
411
335
|
autofile = autofile.group(1)
|
|
412
|
-
sitefile = glove.siteconf +
|
|
413
|
-
persofile = glove.configrc +
|
|
336
|
+
sitefile = glove.siteconf + "/" + autofile
|
|
337
|
+
persofile = glove.configrc + "/" + autofile
|
|
414
338
|
if local.path.exists(sitefile):
|
|
415
339
|
filestack.append(sitefile)
|
|
416
340
|
if local.path.exists(persofile):
|
|
@@ -421,14 +345,20 @@ class GenericReadOnlyConfigParser:
|
|
|
421
345
|
local.filecocoon(persofile)
|
|
422
346
|
local.touch(persofile)
|
|
423
347
|
else:
|
|
424
|
-
raise ValueError(
|
|
348
|
+
raise ValueError(
|
|
349
|
+
"Configuration file " + inifile + " not found"
|
|
350
|
+
)
|
|
425
351
|
if self.defaultinifile:
|
|
426
|
-
sitedefaultinifile = glove.siteconf +
|
|
352
|
+
sitedefaultinifile = glove.siteconf + "/" + self.defaultinifile
|
|
427
353
|
if local.path.exists(sitedefaultinifile):
|
|
428
354
|
# Insert at the beginning (i.e. smallest priority)
|
|
429
355
|
filestack.insert(0, local.path.abspath(sitedefaultinifile))
|
|
430
356
|
else:
|
|
431
|
-
raise ValueError(
|
|
357
|
+
raise ValueError(
|
|
358
|
+
"Configuration file "
|
|
359
|
+
+ sitedefaultinifile
|
|
360
|
+
+ " not found"
|
|
361
|
+
)
|
|
432
362
|
self.file = ",".join(filestack)
|
|
433
363
|
for a_file in filestack:
|
|
434
364
|
with open(a_file, encoding=encoding) as a_fh:
|
|
@@ -444,18 +374,31 @@ class GenericReadOnlyConfigParser:
|
|
|
444
374
|
if merged:
|
|
445
375
|
dico[section] = dict(self.items(section))
|
|
446
376
|
else:
|
|
447
|
-
dico[section] = {
|
|
448
|
-
|
|
377
|
+
dico[section] = {
|
|
378
|
+
k: v
|
|
379
|
+
for k, v in self.items(section)
|
|
380
|
+
if k in self.parser._sections[section]
|
|
381
|
+
}
|
|
449
382
|
return dico
|
|
450
383
|
|
|
451
384
|
def __getattr__(self, attr):
|
|
452
385
|
# Give access to a very limited set of methods
|
|
453
|
-
if attr.startswith(
|
|
454
|
-
|
|
386
|
+
if attr.startswith("get") or attr in (
|
|
387
|
+
"defaults",
|
|
388
|
+
"sections",
|
|
389
|
+
"options",
|
|
390
|
+
"items",
|
|
391
|
+
"has_section",
|
|
392
|
+
"has_option",
|
|
393
|
+
):
|
|
455
394
|
return getattr(self.parser, attr)
|
|
456
395
|
else:
|
|
457
|
-
raise AttributeError(
|
|
458
|
-
|
|
396
|
+
raise AttributeError(
|
|
397
|
+
self.__class__.__name__
|
|
398
|
+
+ " instance has no attribute '"
|
|
399
|
+
+ str(attr)
|
|
400
|
+
+ "'"
|
|
401
|
+
)
|
|
459
402
|
|
|
460
403
|
def footprint_export(self):
|
|
461
404
|
return self.file
|
|
@@ -489,7 +432,7 @@ class ExtendedReadOnlyConfigParser(GenericReadOnlyConfigParser):
|
|
|
489
432
|
and ``base2``. In case of a conflict, ``base1`` takes precedence over ``base2``.
|
|
490
433
|
"""
|
|
491
434
|
|
|
492
|
-
_RE_VALIDATE = re.compile(r
|
|
435
|
+
_RE_VALIDATE = re.compile(r"([\w-]+)[ \t]*:?")
|
|
493
436
|
_RE_KEYC = re.compile(r"%\(([^)]+)\)s")
|
|
494
437
|
|
|
495
438
|
_max_interpolation_depth = 20
|
|
@@ -503,7 +446,7 @@ class ExtendedReadOnlyConfigParser(GenericReadOnlyConfigParser):
|
|
|
503
446
|
if self.parser.has_section(zend_section):
|
|
504
447
|
found_sections.append(zend_section)
|
|
505
448
|
for section in self.parser.sections():
|
|
506
|
-
pieces = re.split(r
|
|
449
|
+
pieces = re.split(r"[ \t]*:[ \t]*", section)
|
|
507
450
|
if len(pieces) >= 2 and pieces[0] == zend_section:
|
|
508
451
|
found_sections.append(section)
|
|
509
452
|
for inherited in pieces[1:]:
|
|
@@ -527,12 +470,16 @@ class ExtendedReadOnlyConfigParser(GenericReadOnlyConfigParser):
|
|
|
527
470
|
else:
|
|
528
471
|
break
|
|
529
472
|
if value and self._RE_KEYC.match(value):
|
|
530
|
-
raise InterpolationDepthError(
|
|
473
|
+
raise InterpolationDepthError(
|
|
474
|
+
self.options(section), section, rawval
|
|
475
|
+
)
|
|
531
476
|
return value
|
|
532
477
|
|
|
533
478
|
def get(self, section, option, raw=False, myvars=None):
|
|
534
479
|
"""Behaves like the GenericConfigParser's ``get`` method."""
|
|
535
|
-
expanded = [
|
|
480
|
+
expanded = [
|
|
481
|
+
s for s in self._get_section_list(section) if s is not None
|
|
482
|
+
]
|
|
536
483
|
if not expanded:
|
|
537
484
|
raise NoSectionError(section)
|
|
538
485
|
expanded.reverse()
|
|
@@ -541,7 +488,9 @@ class ExtendedReadOnlyConfigParser(GenericReadOnlyConfigParser):
|
|
|
541
488
|
mydefault = self.defaults().get(option, None)
|
|
542
489
|
for isection in expanded:
|
|
543
490
|
try:
|
|
544
|
-
tmp_result = self.parser.get(
|
|
491
|
+
tmp_result = self.parser.get(
|
|
492
|
+
isection, option, raw=True, vars=myvars
|
|
493
|
+
)
|
|
545
494
|
if tmp_result is not mydefault:
|
|
546
495
|
acc_result = tmp_result
|
|
547
496
|
except NoOptionError as err:
|
|
@@ -558,7 +507,9 @@ class ExtendedReadOnlyConfigParser(GenericReadOnlyConfigParser):
|
|
|
558
507
|
def sections(self):
|
|
559
508
|
"""Behaves like the Python ConfigParser's ``section`` method."""
|
|
560
509
|
seen = set()
|
|
561
|
-
for section_m in [
|
|
510
|
+
for section_m in [
|
|
511
|
+
self._RE_VALIDATE.match(s) for s in self.parser.sections()
|
|
512
|
+
]:
|
|
562
513
|
if section_m is not None:
|
|
563
514
|
seen.add(section_m.group(1))
|
|
564
515
|
return list(seen)
|
|
@@ -571,7 +522,9 @@ class ExtendedReadOnlyConfigParser(GenericReadOnlyConfigParser):
|
|
|
571
522
|
"""Behaves like the Python ConfigParser's ``options`` method."""
|
|
572
523
|
expanded = self._get_section_list(section)
|
|
573
524
|
if not expanded:
|
|
574
|
-
return self.parser.options(
|
|
525
|
+
return self.parser.options(
|
|
526
|
+
section
|
|
527
|
+
) # A realistic exception will be thrown !
|
|
575
528
|
options = set()
|
|
576
529
|
for isection in [s for s in expanded]:
|
|
577
530
|
options.update(set(self.parser.options(isection)))
|
|
@@ -583,20 +536,29 @@ class ExtendedReadOnlyConfigParser(GenericReadOnlyConfigParser):
|
|
|
583
536
|
|
|
584
537
|
def items(self, section, raw=False, myvars=None):
|
|
585
538
|
"""Behaves like the Python ConfigParser's ``items`` method."""
|
|
586
|
-
return [
|
|
539
|
+
return [
|
|
540
|
+
(o, self.get(section, o, raw, myvars))
|
|
541
|
+
for o in self.options(section)
|
|
542
|
+
]
|
|
587
543
|
|
|
588
544
|
def __getattr__(self, attr):
|
|
589
545
|
# Give access to a very limited set of methods
|
|
590
|
-
if attr in (
|
|
546
|
+
if attr in ("defaults",):
|
|
591
547
|
return getattr(self.parser, attr)
|
|
592
548
|
else:
|
|
593
|
-
raise AttributeError(
|
|
594
|
-
|
|
549
|
+
raise AttributeError(
|
|
550
|
+
self.__class__.__name__
|
|
551
|
+
+ " instance has no attribute '"
|
|
552
|
+
+ str(attr)
|
|
553
|
+
+ "'"
|
|
554
|
+
)
|
|
595
555
|
|
|
596
556
|
def as_dict(self, merged=True):
|
|
597
557
|
"""Export the configuration file as a dictionary."""
|
|
598
558
|
if not merged:
|
|
599
|
-
raise ValueError(
|
|
559
|
+
raise ValueError(
|
|
560
|
+
"merged=False is not allowed with ExtendedReadOnlyConfigParser."
|
|
561
|
+
)
|
|
600
562
|
return super().as_dict(merged=True)
|
|
601
563
|
|
|
602
564
|
|
|
@@ -622,9 +584,18 @@ class GenericConfigParser(GenericReadOnlyConfigParser):
|
|
|
622
584
|
documentation for more details.
|
|
623
585
|
"""
|
|
624
586
|
|
|
625
|
-
def __init__(
|
|
626
|
-
|
|
627
|
-
|
|
587
|
+
def __init__(
|
|
588
|
+
self,
|
|
589
|
+
inifile=None,
|
|
590
|
+
parser=None,
|
|
591
|
+
mkforce=False,
|
|
592
|
+
clsparser=_DEFAULT_CONFIG_PARSER,
|
|
593
|
+
encoding=None,
|
|
594
|
+
defaultinifile=None,
|
|
595
|
+
):
|
|
596
|
+
super().__init__(
|
|
597
|
+
inifile, parser, mkforce, clsparser, encoding, defaultinifile
|
|
598
|
+
)
|
|
628
599
|
self.updates = list()
|
|
629
600
|
|
|
630
601
|
def setall(self, kw):
|
|
@@ -636,7 +607,7 @@ class GenericConfigParser(GenericReadOnlyConfigParser):
|
|
|
636
607
|
|
|
637
608
|
def save(self):
|
|
638
609
|
"""Write the current state of the configuration in the inital file."""
|
|
639
|
-
with open(self.file.split(",").pop(),
|
|
610
|
+
with open(self.file.split(",").pop(), "wb") as configfile:
|
|
640
611
|
self.write(configfile)
|
|
641
612
|
|
|
642
613
|
@property
|
|
@@ -650,9 +621,13 @@ class GenericConfigParser(GenericReadOnlyConfigParser):
|
|
|
650
621
|
|
|
651
622
|
def __getattr__(self, attr):
|
|
652
623
|
# Give access to all of the parser's methods
|
|
653
|
-
if attr.startswith(
|
|
654
|
-
raise AttributeError(
|
|
655
|
-
|
|
624
|
+
if attr.startswith("__"):
|
|
625
|
+
raise AttributeError(
|
|
626
|
+
self.__class__.__name__
|
|
627
|
+
+ " instance has no attribute '"
|
|
628
|
+
+ str(attr)
|
|
629
|
+
+ "'"
|
|
630
|
+
)
|
|
656
631
|
return getattr(self.parser, attr)
|
|
657
632
|
|
|
658
633
|
|
|
@@ -678,12 +653,14 @@ class DelayedConfigParser(GenericConfigParser):
|
|
|
678
653
|
|
|
679
654
|
def __getattribute__(self, attr):
|
|
680
655
|
try:
|
|
681
|
-
logger.debug(
|
|
682
|
-
if attr in filter(
|
|
683
|
-
|
|
684
|
-
|
|
656
|
+
logger.debug("Getattr %s < %s >", attr, self)
|
|
657
|
+
if attr in filter(
|
|
658
|
+
lambda x: not x.startswith("_"),
|
|
659
|
+
dir(_DEFAULT_CONFIG_PARSER) + ["setall", "save"],
|
|
660
|
+
):
|
|
661
|
+
object.__getattribute__(self, "refresh")()
|
|
685
662
|
except Exception:
|
|
686
|
-
logger.critical(
|
|
663
|
+
logger.critical("Trouble getattr %s < %s >", attr, self)
|
|
687
664
|
return object.__getattribute__(self, attr)
|
|
688
665
|
|
|
689
666
|
|
|
@@ -709,7 +686,7 @@ class JacketConfigParser(GenericConfigParser):
|
|
|
709
686
|
build on the basis of a comma separated list.
|
|
710
687
|
"""
|
|
711
688
|
s = _DEFAULT_CONFIG_PARSER.get(self, section, option)
|
|
712
|
-
tmplist = s.replace(
|
|
689
|
+
tmplist = s.replace(" ", "").split(",")
|
|
713
690
|
if len(tmplist) > 1:
|
|
714
691
|
return tmplist
|
|
715
692
|
else:
|
|
@@ -738,13 +715,20 @@ class AppConfigStringDecoder(StringDecoder):
|
|
|
738
715
|
|
|
739
716
|
"""
|
|
740
717
|
|
|
741
|
-
BUILDERS = StringDecoder.BUILDERS + [
|
|
742
|
-
|
|
743
|
-
|
|
718
|
+
BUILDERS = StringDecoder.BUILDERS + [
|
|
719
|
+
"geometry",
|
|
720
|
+
"date",
|
|
721
|
+
"time",
|
|
722
|
+
"rangex",
|
|
723
|
+
"daterangex",
|
|
724
|
+
"iniconf",
|
|
725
|
+
"conftool",
|
|
726
|
+
]
|
|
744
727
|
|
|
745
728
|
def remap_geometry(self, value):
|
|
746
729
|
"""Convert all values to Geometry objects."""
|
|
747
730
|
from vortex.data import geometries
|
|
731
|
+
|
|
748
732
|
try:
|
|
749
733
|
value = geometries.get(tag=value)
|
|
750
734
|
except ValueError:
|
|
@@ -770,6 +754,7 @@ class AppConfigStringDecoder(StringDecoder):
|
|
|
770
754
|
def _build_geometry(self, value, remap, subs):
|
|
771
755
|
val = self._value_expand(value, remap, subs)
|
|
772
756
|
from vortex.data import geometries
|
|
757
|
+
|
|
773
758
|
return geometries.get(tag=val)
|
|
774
759
|
|
|
775
760
|
def _build_date(self, value, remap, subs):
|
|
@@ -784,72 +769,94 @@ class AppConfigStringDecoder(StringDecoder):
|
|
|
784
769
|
"""Build a rangex or daterangex from the **value** string."""
|
|
785
770
|
# Try to read names arguments
|
|
786
771
|
try:
|
|
787
|
-
values = self._sparser(value, itemsep=
|
|
788
|
-
if all(
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
772
|
+
values = self._sparser(value, itemsep=" ", keysep=":")
|
|
773
|
+
if all(
|
|
774
|
+
[
|
|
775
|
+
k in ("start", "end", "step", "shift", "fmt", "prefix")
|
|
776
|
+
for k in values.keys()
|
|
777
|
+
]
|
|
778
|
+
):
|
|
779
|
+
return cb(
|
|
780
|
+
**{
|
|
781
|
+
k: self._value_expand(v, remap, subs)
|
|
782
|
+
for k, v in values.items()
|
|
783
|
+
}
|
|
784
|
+
)
|
|
792
785
|
except StringDecoderSyntaxError:
|
|
793
786
|
pass
|
|
794
787
|
# The usual case...
|
|
795
|
-
return cb(
|
|
796
|
-
|
|
788
|
+
return cb(
|
|
789
|
+
[
|
|
790
|
+
self._value_expand(v, remap, subs)
|
|
791
|
+
for v in self._sparser(value, itemsep=",")
|
|
792
|
+
]
|
|
793
|
+
)
|
|
797
794
|
|
|
798
795
|
def _build_rangex(self, value, remap, subs):
|
|
799
796
|
"""Build a rangex from the **value** string."""
|
|
800
|
-
return self._build_generic_rangex(
|
|
797
|
+
return self._build_generic_rangex(
|
|
798
|
+
bdate.timeintrangex, value, remap, subs
|
|
799
|
+
)
|
|
801
800
|
|
|
802
801
|
def _build_daterangex(self, value, remap, subs):
|
|
803
802
|
"""Build a daterangex from the **value** string."""
|
|
804
803
|
return self._build_generic_rangex(bdate.daterangex, value, remap, subs)
|
|
805
804
|
|
|
806
805
|
def _build_fpgeneric(self, value, remap, subs, collector):
|
|
807
|
-
fp = {
|
|
808
|
-
|
|
806
|
+
fp = {
|
|
807
|
+
k: self._value_expand(v, remap, subs)
|
|
808
|
+
for k, v in self._sparser(value, itemsep=" ", keysep=":").items()
|
|
809
|
+
}
|
|
809
810
|
obj = footprints.collectors.get(tag=collector).load(**fp)
|
|
810
811
|
if obj is None:
|
|
811
|
-
raise StringDecoderSyntaxError(
|
|
812
|
-
|
|
813
|
-
|
|
812
|
+
raise StringDecoderSyntaxError(
|
|
813
|
+
value,
|
|
814
|
+
"No object could be created from the {} collector".format(
|
|
815
|
+
collector
|
|
816
|
+
),
|
|
817
|
+
)
|
|
814
818
|
return obj
|
|
815
819
|
|
|
816
820
|
def _build_iniconf(self, value, remap, subs):
|
|
817
|
-
return self._build_fpgeneric(value, remap, subs,
|
|
821
|
+
return self._build_fpgeneric(value, remap, subs, "iniconf")
|
|
818
822
|
|
|
819
823
|
def _build_conftool(self, value, remap, subs):
|
|
820
|
-
return self._build_fpgeneric(value, remap, subs,
|
|
824
|
+
return self._build_fpgeneric(value, remap, subs, "conftool")
|
|
821
825
|
|
|
822
826
|
|
|
823
827
|
class IniConf(footprints.FootprintBase):
|
|
824
828
|
"""
|
|
825
829
|
Generic Python configuration file.
|
|
826
830
|
"""
|
|
827
|
-
|
|
831
|
+
|
|
832
|
+
_collector = ("iniconf",)
|
|
828
833
|
_abstract = True
|
|
829
834
|
_footprint = dict(
|
|
830
|
-
info=
|
|
835
|
+
info="Abstract Python Inifile",
|
|
831
836
|
attr=dict(
|
|
832
|
-
kind
|
|
833
|
-
info
|
|
834
|
-
values
|
|
837
|
+
kind=dict(
|
|
838
|
+
info="The configuration object kind.",
|
|
839
|
+
values=[
|
|
840
|
+
"generic",
|
|
841
|
+
],
|
|
835
842
|
),
|
|
836
|
-
clsconfig
|
|
837
|
-
type
|
|
838
|
-
isclass
|
|
839
|
-
optional
|
|
840
|
-
default
|
|
841
|
-
doc_visibility
|
|
843
|
+
clsconfig=dict(
|
|
844
|
+
type=GenericReadOnlyConfigParser,
|
|
845
|
+
isclass=True,
|
|
846
|
+
optional=True,
|
|
847
|
+
default=GenericReadOnlyConfigParser,
|
|
848
|
+
doc_visibility=footprints.doc.visibility.ADVANCED,
|
|
842
849
|
),
|
|
843
|
-
inifile
|
|
844
|
-
kind
|
|
845
|
-
optional
|
|
846
|
-
default
|
|
850
|
+
inifile=dict(
|
|
851
|
+
kind="The configuration file to look for.",
|
|
852
|
+
optional=True,
|
|
853
|
+
default="@[kind].ini",
|
|
847
854
|
),
|
|
848
|
-
)
|
|
855
|
+
),
|
|
849
856
|
)
|
|
850
857
|
|
|
851
858
|
def __init__(self, *args, **kw):
|
|
852
|
-
logger.debug(
|
|
859
|
+
logger.debug("Ini Conf %s", self.__class__)
|
|
853
860
|
super().__init__(*args, **kw)
|
|
854
861
|
self._config = self.clsconfig(inifile=self.inifile)
|
|
855
862
|
|
|
@@ -864,67 +871,76 @@ class ConfigurationTable(IniConf):
|
|
|
864
871
|
items (instantiated from the tableitem footprint's collector) from a given
|
|
865
872
|
configuration file.
|
|
866
873
|
"""
|
|
874
|
+
|
|
867
875
|
_abstract = True
|
|
868
876
|
_footprint = dict(
|
|
869
|
-
info
|
|
870
|
-
attr
|
|
871
|
-
kind
|
|
872
|
-
info
|
|
877
|
+
info="Abstract configuration tables",
|
|
878
|
+
attr=dict(
|
|
879
|
+
kind=dict(
|
|
880
|
+
info="The configuration's table kind.",
|
|
873
881
|
),
|
|
874
|
-
family
|
|
875
|
-
info
|
|
882
|
+
family=dict(
|
|
883
|
+
info="The configuration's table family.",
|
|
876
884
|
),
|
|
877
|
-
version
|
|
878
|
-
info
|
|
879
|
-
optional
|
|
880
|
-
default
|
|
885
|
+
version=dict(
|
|
886
|
+
info="The configuration's table version.",
|
|
887
|
+
optional=True,
|
|
888
|
+
default="std",
|
|
881
889
|
),
|
|
882
|
-
searchkeys
|
|
883
|
-
info
|
|
884
|
-
type
|
|
885
|
-
optional
|
|
886
|
-
default
|
|
890
|
+
searchkeys=dict(
|
|
891
|
+
info="Item's attributes used to perform the lookup in the find method.",
|
|
892
|
+
type=footprints.FPTuple,
|
|
893
|
+
optional=True,
|
|
894
|
+
default=footprints.FPTuple(),
|
|
887
895
|
),
|
|
888
|
-
groupname
|
|
889
|
-
info
|
|
890
|
-
optional
|
|
891
|
-
default
|
|
896
|
+
groupname=dict(
|
|
897
|
+
info="The class attribute matching the configuration file groupname",
|
|
898
|
+
optional=True,
|
|
899
|
+
default="family",
|
|
892
900
|
),
|
|
893
|
-
inifile
|
|
894
|
-
optional
|
|
895
|
-
default
|
|
901
|
+
inifile=dict(
|
|
902
|
+
optional=True,
|
|
903
|
+
default="@[family]-[kind]-[version].ini",
|
|
896
904
|
),
|
|
897
|
-
clsconfig
|
|
898
|
-
default
|
|
905
|
+
clsconfig=dict(
|
|
906
|
+
default=ExtendedReadOnlyConfigParser,
|
|
899
907
|
),
|
|
900
|
-
language
|
|
901
|
-
info
|
|
902
|
-
optional
|
|
903
|
-
default
|
|
908
|
+
language=dict(
|
|
909
|
+
info="The default language for the translator property.",
|
|
910
|
+
optional=True,
|
|
911
|
+
default="en",
|
|
904
912
|
),
|
|
905
|
-
)
|
|
913
|
+
),
|
|
906
914
|
)
|
|
907
915
|
|
|
908
916
|
@property
|
|
909
917
|
def realkind(self):
|
|
910
|
-
return
|
|
918
|
+
return "configuration-table"
|
|
911
919
|
|
|
912
920
|
def groups(self):
|
|
913
921
|
"""Actual list of items groups described in the current iniconf."""
|
|
914
|
-
return [
|
|
915
|
-
|
|
922
|
+
return [
|
|
923
|
+
x
|
|
924
|
+
for x in self.config.parser.sections()
|
|
925
|
+
if ":" not in x and not x.startswith("lang_")
|
|
926
|
+
]
|
|
916
927
|
|
|
917
928
|
def keys(self):
|
|
918
929
|
"""Actual list of different items in the current iniconf."""
|
|
919
|
-
return [
|
|
920
|
-
|
|
930
|
+
return [
|
|
931
|
+
x
|
|
932
|
+
for x in self.config.sections()
|
|
933
|
+
if x not in self.groups() and not x.startswith("lang_")
|
|
934
|
+
]
|
|
921
935
|
|
|
922
936
|
@property
|
|
923
937
|
def translator(self):
|
|
924
938
|
"""The special section of the iniconf dedicated to translation, as a dict."""
|
|
925
|
-
if not hasattr(self,
|
|
926
|
-
if self.config.has_section(
|
|
927
|
-
self._translator = self.config.as_dict()[
|
|
939
|
+
if not hasattr(self, "_translator"):
|
|
940
|
+
if self.config.has_section("lang_" + self.language):
|
|
941
|
+
self._translator = self.config.as_dict()[
|
|
942
|
+
"lang_" + self.language
|
|
943
|
+
]
|
|
928
944
|
else:
|
|
929
945
|
self._translator = None
|
|
930
946
|
return self._translator
|
|
@@ -932,34 +948,42 @@ class ConfigurationTable(IniConf):
|
|
|
932
948
|
@property
|
|
933
949
|
def tablelist(self):
|
|
934
950
|
"""List of unique instances of items described in the current iniconf."""
|
|
935
|
-
if not hasattr(self,
|
|
951
|
+
if not hasattr(self, "_tablelist"):
|
|
936
952
|
self._tablelist = list()
|
|
937
953
|
d = self.config.as_dict()
|
|
938
|
-
for item, group in [
|
|
954
|
+
for item, group in [
|
|
955
|
+
x.split(":") for x in self.config.parser.sections() if ":" in x
|
|
956
|
+
]:
|
|
939
957
|
try:
|
|
940
958
|
for k, v in d[item].items():
|
|
941
959
|
# Can occur in case of a redundant entry in the config file
|
|
942
960
|
if isinstance(v, str) and v:
|
|
943
|
-
if re.match(
|
|
961
|
+
if re.match("none$", v, re.IGNORECASE):
|
|
944
962
|
d[item][k] = None
|
|
945
|
-
if re.search(
|
|
946
|
-
d[item][k] = v.replace(
|
|
963
|
+
if re.search("[a-z]_[a-z]", v, re.IGNORECASE):
|
|
964
|
+
d[item][k] = v.replace("_", "'")
|
|
947
965
|
d[item][self.searchkeys[0]] = item
|
|
948
966
|
d[item][self.groupname] = group
|
|
949
|
-
d[item][
|
|
967
|
+
d[item]["translator"] = self.translator
|
|
950
968
|
itemobj = footprints.proxy.tableitem(**d[item])
|
|
951
969
|
if itemobj is not None:
|
|
952
970
|
self._tablelist.append(itemobj)
|
|
953
971
|
else:
|
|
954
|
-
logger.error(
|
|
972
|
+
logger.error(
|
|
973
|
+
"Unable to create the %s item object. Check the footprint !",
|
|
974
|
+
item,
|
|
975
|
+
)
|
|
955
976
|
except (KeyError, IndexError):
|
|
956
|
-
logger.warning(
|
|
977
|
+
logger.warning("Some item description could not match")
|
|
957
978
|
return self._tablelist
|
|
958
979
|
|
|
959
980
|
def get(self, item):
|
|
960
981
|
"""Return the item with main key exactly matching the given argument."""
|
|
961
|
-
candidates = [
|
|
962
|
-
|
|
982
|
+
candidates = [
|
|
983
|
+
x
|
|
984
|
+
for x in self.tablelist
|
|
985
|
+
if x.footprint_getattr(self.searchkeys[0]) == item
|
|
986
|
+
]
|
|
963
987
|
if candidates:
|
|
964
988
|
return candidates[0]
|
|
965
989
|
else:
|
|
@@ -967,8 +991,13 @@ class ConfigurationTable(IniConf):
|
|
|
967
991
|
|
|
968
992
|
def match(self, item):
|
|
969
993
|
"""Return the item with main key matching the given argument without case consideration."""
|
|
970
|
-
candidates = [
|
|
971
|
-
|
|
994
|
+
candidates = [
|
|
995
|
+
x
|
|
996
|
+
for x in self.tablelist
|
|
997
|
+
if x.footprint_getattr(self.searchkeys[0])
|
|
998
|
+
.lower()
|
|
999
|
+
.startswith(item.lower())
|
|
1000
|
+
]
|
|
972
1001
|
if candidates:
|
|
973
1002
|
return candidates[0]
|
|
974
1003
|
else:
|
|
@@ -976,14 +1005,28 @@ class ConfigurationTable(IniConf):
|
|
|
976
1005
|
|
|
977
1006
|
def grep(self, item):
|
|
978
1007
|
"""Return a list of items with main key loosely matching the given argument."""
|
|
979
|
-
return [
|
|
980
|
-
|
|
1008
|
+
return [
|
|
1009
|
+
x
|
|
1010
|
+
for x in self.tablelist
|
|
1011
|
+
if re.search(
|
|
1012
|
+
item, x.footprint_getattr(self.searchkeys[0]), re.IGNORECASE
|
|
1013
|
+
)
|
|
1014
|
+
]
|
|
981
1015
|
|
|
982
1016
|
def find(self, item):
|
|
983
1017
|
"""Return a list of items with main key or name loosely matching the given argument."""
|
|
984
|
-
return [
|
|
985
|
-
|
|
986
|
-
|
|
1018
|
+
return [
|
|
1019
|
+
x
|
|
1020
|
+
for x in self.tablelist
|
|
1021
|
+
if any(
|
|
1022
|
+
[
|
|
1023
|
+
re.search(
|
|
1024
|
+
item, x.footprint_getattr(thiskey), re.IGNORECASE
|
|
1025
|
+
)
|
|
1026
|
+
for thiskey in self.searchkeys
|
|
1027
|
+
]
|
|
1028
|
+
)
|
|
1029
|
+
]
|
|
987
1030
|
|
|
988
1031
|
|
|
989
1032
|
class TableItem(footprints.FootprintBase):
|
|
@@ -992,27 +1035,27 @@ class TableItem(footprints.FootprintBase):
|
|
|
992
1035
|
"""
|
|
993
1036
|
|
|
994
1037
|
#: Attribute describing the item's name during RST exports
|
|
995
|
-
_RST_NAME =
|
|
1038
|
+
_RST_NAME = ""
|
|
996
1039
|
#: Attributes that will appear on the top line of RST exports
|
|
997
1040
|
_RST_HOTKEYS = []
|
|
998
1041
|
|
|
999
1042
|
_abstract = True
|
|
1000
|
-
_collector = (
|
|
1043
|
+
_collector = ("tableitem",)
|
|
1001
1044
|
_footprint = dict(
|
|
1002
|
-
info
|
|
1003
|
-
attr
|
|
1045
|
+
info="Abstract configuration table's item.",
|
|
1046
|
+
attr=dict(
|
|
1004
1047
|
# Define your own...
|
|
1005
|
-
translator
|
|
1006
|
-
optional
|
|
1007
|
-
type
|
|
1008
|
-
default
|
|
1048
|
+
translator=dict(
|
|
1049
|
+
optional=True,
|
|
1050
|
+
type=footprints.FPDict,
|
|
1051
|
+
default=None,
|
|
1009
1052
|
),
|
|
1010
|
-
)
|
|
1053
|
+
),
|
|
1011
1054
|
)
|
|
1012
1055
|
|
|
1013
1056
|
@property
|
|
1014
1057
|
def realkind(self):
|
|
1015
|
-
return
|
|
1058
|
+
return "tableitem"
|
|
1016
1059
|
|
|
1017
1060
|
def _translated_items(self, mkshort=True):
|
|
1018
1061
|
"""Returns a list of 3-elements tuples describing the item attributes.
|
|
@@ -1021,13 +1064,22 @@ class TableItem(footprints.FootprintBase):
|
|
|
1021
1064
|
"""
|
|
1022
1065
|
output_stack = list()
|
|
1023
1066
|
if self.translator:
|
|
1024
|
-
for k in self.translator.get(
|
|
1067
|
+
for k in self.translator.get("ordered_dump", "").split(","):
|
|
1025
1068
|
if not mkshort or self.footprint_getattr(k) is not None:
|
|
1026
|
-
output_stack.append(
|
|
1027
|
-
|
|
1069
|
+
output_stack.append(
|
|
1070
|
+
(
|
|
1071
|
+
self.translator.get(
|
|
1072
|
+
k, k.replace("_", " ").title()
|
|
1073
|
+
),
|
|
1074
|
+
str(self.footprint_getattr(k)),
|
|
1075
|
+
k,
|
|
1076
|
+
)
|
|
1077
|
+
)
|
|
1028
1078
|
else:
|
|
1029
1079
|
for k in self.footprint_attributes:
|
|
1030
|
-
if (
|
|
1080
|
+
if (
|
|
1081
|
+
not mkshort or self.footprint_getattr(k) is not None
|
|
1082
|
+
) and k != "translator":
|
|
1031
1083
|
output_stack.append((k, str(self.footprint_getattr(k)), k))
|
|
1032
1084
|
return output_stack
|
|
1033
1085
|
|
|
@@ -1037,10 +1089,10 @@ class TableItem(footprints.FootprintBase):
|
|
|
1037
1089
|
output_list = []
|
|
1038
1090
|
if output_stack:
|
|
1039
1091
|
max_keylen = max([len(i[0]) for i in output_stack])
|
|
1040
|
-
print_fmt =
|
|
1092
|
+
print_fmt = "{0:" + str(max_keylen) + "s} : {1:s}"
|
|
1041
1093
|
for item in output_stack:
|
|
1042
1094
|
output_list.append(print_fmt.format(*item))
|
|
1043
|
-
return
|
|
1095
|
+
return "\n".join(output_list)
|
|
1044
1096
|
|
|
1045
1097
|
def __str__(self):
|
|
1046
1098
|
return self.nice_str()
|
|
@@ -1053,7 +1105,7 @@ class TableItem(footprints.FootprintBase):
|
|
|
1053
1105
|
"""Produces a nice ordered RST output of the item attributes."""
|
|
1054
1106
|
assert self._RST_NAME, "Please override _RST_NAME"
|
|
1055
1107
|
output_stack = self._translated_items(mkshort=mkshort)
|
|
1056
|
-
i_name =
|
|
1108
|
+
i_name = "????"
|
|
1057
1109
|
i_hot = []
|
|
1058
1110
|
i_other = []
|
|
1059
1111
|
for item in output_stack:
|
|
@@ -1063,9 +1115,8 @@ class TableItem(footprints.FootprintBase):
|
|
|
1063
1115
|
i_hot.append(item)
|
|
1064
1116
|
else:
|
|
1065
1117
|
i_other.append(item)
|
|
1066
|
-
return
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
)
|
|
1118
|
+
return "**{}** : `{}`\n\n{}\n\n".format(
|
|
1119
|
+
i_name[1],
|
|
1120
|
+
", ".join(["{:s}={:s}".format(*i) for i in i_hot]),
|
|
1121
|
+
"\n".join([" * {:s}: {:s}".format(*i) for i in i_other]),
|
|
1122
|
+
)
|