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.
Files changed (141) hide show
  1. vortex/__init__.py +75 -47
  2. vortex/algo/__init__.py +3 -2
  3. vortex/algo/components.py +944 -618
  4. vortex/algo/mpitools.py +802 -497
  5. vortex/algo/mpitools_templates/__init__.py +1 -0
  6. vortex/algo/serversynctools.py +34 -33
  7. vortex/config.py +19 -22
  8. vortex/data/__init__.py +9 -3
  9. vortex/data/abstractstores.py +593 -655
  10. vortex/data/containers.py +217 -162
  11. vortex/data/contents.py +65 -39
  12. vortex/data/executables.py +93 -102
  13. vortex/data/flow.py +40 -34
  14. vortex/data/geometries.py +228 -132
  15. vortex/data/handlers.py +436 -227
  16. vortex/data/outflow.py +15 -15
  17. vortex/data/providers.py +185 -163
  18. vortex/data/resources.py +48 -42
  19. vortex/data/stores.py +540 -417
  20. vortex/data/sync_templates/__init__.py +0 -0
  21. vortex/gloves.py +114 -87
  22. vortex/layout/__init__.py +1 -8
  23. vortex/layout/contexts.py +150 -84
  24. vortex/layout/dataflow.py +353 -202
  25. vortex/layout/monitor.py +264 -128
  26. vortex/nwp/__init__.py +5 -2
  27. vortex/nwp/algo/__init__.py +14 -5
  28. vortex/nwp/algo/assim.py +205 -151
  29. vortex/nwp/algo/clim.py +683 -517
  30. vortex/nwp/algo/coupling.py +447 -225
  31. vortex/nwp/algo/eda.py +437 -229
  32. vortex/nwp/algo/eps.py +403 -231
  33. vortex/nwp/algo/forecasts.py +416 -275
  34. vortex/nwp/algo/fpserver.py +683 -307
  35. vortex/nwp/algo/ifsnaming.py +205 -145
  36. vortex/nwp/algo/ifsroot.py +215 -122
  37. vortex/nwp/algo/monitoring.py +137 -76
  38. vortex/nwp/algo/mpitools.py +330 -190
  39. vortex/nwp/algo/odbtools.py +637 -353
  40. vortex/nwp/algo/oopsroot.py +454 -273
  41. vortex/nwp/algo/oopstests.py +90 -56
  42. vortex/nwp/algo/request.py +287 -206
  43. vortex/nwp/algo/stdpost.py +878 -522
  44. vortex/nwp/data/__init__.py +22 -4
  45. vortex/nwp/data/assim.py +125 -137
  46. vortex/nwp/data/boundaries.py +121 -68
  47. vortex/nwp/data/climfiles.py +193 -211
  48. vortex/nwp/data/configfiles.py +73 -69
  49. vortex/nwp/data/consts.py +426 -401
  50. vortex/nwp/data/ctpini.py +59 -43
  51. vortex/nwp/data/diagnostics.py +94 -66
  52. vortex/nwp/data/eda.py +50 -51
  53. vortex/nwp/data/eps.py +195 -146
  54. vortex/nwp/data/executables.py +440 -434
  55. vortex/nwp/data/fields.py +63 -48
  56. vortex/nwp/data/gridfiles.py +183 -111
  57. vortex/nwp/data/logs.py +250 -217
  58. vortex/nwp/data/modelstates.py +180 -151
  59. vortex/nwp/data/monitoring.py +72 -99
  60. vortex/nwp/data/namelists.py +254 -202
  61. vortex/nwp/data/obs.py +400 -308
  62. vortex/nwp/data/oopsexec.py +22 -20
  63. vortex/nwp/data/providers.py +90 -65
  64. vortex/nwp/data/query.py +71 -82
  65. vortex/nwp/data/stores.py +49 -36
  66. vortex/nwp/data/surfex.py +136 -137
  67. vortex/nwp/syntax/__init__.py +1 -1
  68. vortex/nwp/syntax/stdattrs.py +173 -111
  69. vortex/nwp/tools/__init__.py +2 -2
  70. vortex/nwp/tools/addons.py +22 -17
  71. vortex/nwp/tools/agt.py +24 -12
  72. vortex/nwp/tools/bdap.py +16 -5
  73. vortex/nwp/tools/bdcp.py +4 -1
  74. vortex/nwp/tools/bdm.py +3 -0
  75. vortex/nwp/tools/bdmp.py +14 -9
  76. vortex/nwp/tools/conftools.py +728 -378
  77. vortex/nwp/tools/drhook.py +12 -8
  78. vortex/nwp/tools/grib.py +65 -39
  79. vortex/nwp/tools/gribdiff.py +22 -17
  80. vortex/nwp/tools/ifstools.py +82 -42
  81. vortex/nwp/tools/igastuff.py +167 -143
  82. vortex/nwp/tools/mars.py +14 -2
  83. vortex/nwp/tools/odb.py +234 -125
  84. vortex/nwp/tools/partitioning.py +61 -37
  85. vortex/nwp/tools/satrad.py +27 -12
  86. vortex/nwp/util/async.py +83 -55
  87. vortex/nwp/util/beacon.py +10 -10
  88. vortex/nwp/util/diffpygram.py +174 -86
  89. vortex/nwp/util/ens.py +144 -63
  90. vortex/nwp/util/hooks.py +30 -19
  91. vortex/nwp/util/taskdeco.py +28 -24
  92. vortex/nwp/util/usepygram.py +278 -172
  93. vortex/nwp/util/usetnt.py +31 -17
  94. vortex/sessions.py +72 -39
  95. vortex/syntax/__init__.py +1 -1
  96. vortex/syntax/stdattrs.py +410 -171
  97. vortex/syntax/stddeco.py +31 -22
  98. vortex/toolbox.py +327 -192
  99. vortex/tools/__init__.py +11 -2
  100. vortex/tools/actions.py +110 -121
  101. vortex/tools/addons.py +111 -92
  102. vortex/tools/arm.py +42 -22
  103. vortex/tools/compression.py +72 -69
  104. vortex/tools/date.py +11 -4
  105. vortex/tools/delayedactions.py +242 -132
  106. vortex/tools/env.py +75 -47
  107. vortex/tools/folder.py +342 -171
  108. vortex/tools/grib.py +341 -162
  109. vortex/tools/lfi.py +423 -216
  110. vortex/tools/listings.py +109 -40
  111. vortex/tools/names.py +218 -156
  112. vortex/tools/net.py +655 -299
  113. vortex/tools/parallelism.py +93 -61
  114. vortex/tools/prestaging.py +55 -31
  115. vortex/tools/schedulers.py +172 -105
  116. vortex/tools/services.py +403 -334
  117. vortex/tools/storage.py +293 -358
  118. vortex/tools/surfex.py +24 -24
  119. vortex/tools/systems.py +1234 -643
  120. vortex/tools/targets.py +156 -100
  121. vortex/util/__init__.py +1 -1
  122. vortex/util/config.py +378 -327
  123. vortex/util/empty.py +2 -2
  124. vortex/util/helpers.py +56 -24
  125. vortex/util/introspection.py +18 -12
  126. vortex/util/iosponge.py +8 -4
  127. vortex/util/roles.py +4 -6
  128. vortex/util/storefunctions.py +39 -13
  129. vortex/util/structs.py +3 -3
  130. vortex/util/worker.py +29 -17
  131. vortex_nwp-2.1.0.dist-info/METADATA +67 -0
  132. vortex_nwp-2.1.0.dist-info/RECORD +144 -0
  133. {vortex_nwp-2.0.0b1.dist-info → vortex_nwp-2.1.0.dist-info}/WHEEL +1 -1
  134. vortex/layout/appconf.py +0 -109
  135. vortex/layout/jobs.py +0 -1276
  136. vortex/layout/nodes.py +0 -1424
  137. vortex/layout/subjobs.py +0 -464
  138. vortex_nwp-2.0.0b1.dist-info/METADATA +0 -50
  139. vortex_nwp-2.0.0b1.dist-info/RECORD +0 -146
  140. {vortex_nwp-2.0.0b1.dist-info → vortex_nwp-2.1.0.dist-info/licenses}/LICENSE +0 -0
  141. {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'^@(([^/].*)\.tpl)$')
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
- KIND = None
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 (when read from disk)
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(** todo)
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(kwargs)
115
+ return string.Template(self._tpl_obj.substitute(kwargs)).substitute(
116
+ kwargs
117
+ )
125
118
 
126
119
 
127
- class Jinja2TemplatingAdapter(AbstractTemplatingAdapter):
128
- """Use the jinja2 templating engine to render the template.
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
- See :class:`AbstractTemplatingAdapter` for more details on this class usage.
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
- def __call__(self, **kwargs):
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 be used.
181
- The content of the template file is always searched
182
- in order to detect a "# vortex-templating:" comment
183
- that will override this default.
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
- *tplfile* can be a relative or absolute filename. However, most of the time,
187
- it is a string like ``@foo.tpl``. In such a case, a file named ``foo.tpl`` will
188
- be looked for in the ``~/.vortexrc/templates`` and in the ``templates``
189
- sub-directory of the Vortex source code distribution.
190
-
191
- The characters encoding of the template file may be specified. If *encoding*
192
- equals ``script``, a line looking like ``# encoding:special-encoding`` will be
193
- searched for in the first ten lines of the template file. If it exists, the
194
- ``special-encoding`` will be used as an encoding and the
195
- ``# encoding:special-encoding`` line will be stripped from the template.
196
-
197
- Different templating engine may be used to render the template file. It
198
- defaults to ``legacy`` that is compatible with Python's :class:`string.Template`
199
- class. However, another default may be provided using the
200
- *default_templating* argument. In any case, a line looking like
201
- ``# vortex-templating:kind`` will be searched for in the first ten lines
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
- persodir = t.sh.path.join(t.glove.configrc, 'templates')
212
- sitedir = t.sh.path.join(t.glove.siteroot, 'templates')
213
- with importlib.resources.as_file(
214
- importlib.resources.files("vortex.algo")
215
- ) as path:
216
- pkgdir = path / "mpitools_templates"
217
- searchdirs = list()
218
- autofile = _RE_AUTO_TPL.match(tplfile)
219
- if autofile is None:
220
- if t.sh.path.exists(tplfile):
221
- tplfile = t.sh.path.abspath(tplfile)
222
- else:
223
- raise ValueError('Template file not found: <{}>'.format(tplfile))
224
- else:
225
- searchdirs = (persodir, pkgdir, sitedir)
226
- new_tplfile = None
227
- if version is None:
228
- autofile = autofile.group(1)
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
- actual_templating = templating_match.group(1)
281
- # Read the template and delete the encoding line if present
282
- logger.debug('Opening %s with encoding %s', tplfile, str(actual_encoding))
283
- with open(tplfile, encoding=actual_encoding) as tpfld:
284
- tpl_txt = "".join([l for (i, l) in enumerate(tpfld)
285
- if i not in ignored_lines])
286
-
287
- template_rendering_classes = {cls.KIND: cls for cls in globals().values()
288
- if (isinstance(cls, type) and
289
- issubclass(cls, AbstractTemplatingAdapter) and
290
- cls.KIND)}
291
- try:
292
- template_rendering_cls = template_rendering_classes[actual_templating]
293
- except KeyError:
294
- raise ValueError('Unknown templating system < {:s} >'.format(actual_templating))
295
- tpl = template_rendering_cls(tpl_txt, tplfile, actual_encoding, searchdirs)
296
- except Exception as pb:
297
- logger.error('Could not read template <%s>', str(pb))
298
- raise
299
- return tpl
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'^@([^/]+\.ini)$')
328
-
329
- def __init__(self, inifile=None, parser=None, mkforce=False,
330
- clsparser=_DEFAULT_CONFIG_PARSER, encoding=None, defaultinifile=None):
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 'file={!s}'.format(self.file)
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 + '/' + self.defaultinifile
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('Configuration file ' + sitedefaultinifile + ' not found')
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('Configuration file ' + inifile + ' not found')
331
+ raise ValueError(
332
+ "Configuration file " + inifile + " not found"
333
+ )
410
334
  else:
411
335
  autofile = autofile.group(1)
412
- sitefile = glove.siteconf + '/' + autofile
413
- persofile = glove.configrc + '/' + autofile
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('Configuration file ' + inifile + ' not found')
348
+ raise ValueError(
349
+ "Configuration file " + inifile + " not found"
350
+ )
425
351
  if self.defaultinifile:
426
- sitedefaultinifile = glove.siteconf + '/' + self.defaultinifile
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('Configuration file ' + sitedefaultinifile + ' not found')
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] = {k: v for k, v in self.items(section)
448
- if k in self.parser._sections[section]}
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('get') or attr in ('defaults', 'sections', 'options', 'items',
454
- 'has_section', 'has_option'):
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(self.__class__.__name__ + " instance has no attribute '" +
458
- str(attr) + "'")
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'([\w-]+)[ \t]*:?')
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'[ \t]*:[ \t]*', section)
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(self.options(section), section, rawval)
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 = [s for s in self._get_section_list(section) if s is not None]
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(isection, option, raw=True, vars=myvars)
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 [self._RE_VALIDATE.match(s) for s in self.parser.sections()]:
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(section) # A realistic exception will be thrown !
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 [(o, self.get(section, o, raw, myvars)) for o in self.options(section)]
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 ('defaults',):
546
+ if attr in ("defaults",):
591
547
  return getattr(self.parser, attr)
592
548
  else:
593
- raise AttributeError(self.__class__.__name__ + " instance has no attribute '" +
594
- str(attr) + "'")
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("merged=False is not allowed with ExtendedReadOnlyConfigParser.")
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__(self, inifile=None, parser=None, mkforce=False,
626
- clsparser=_DEFAULT_CONFIG_PARSER, encoding=None, defaultinifile=None):
627
- super().__init__(inifile, parser, mkforce, clsparser, encoding, defaultinifile)
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(), 'wb') as configfile:
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(self.__class__.__name__ + " instance has no attribute '" +
655
- str(attr) + "'")
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('Getattr %s < %s >', attr, self)
682
- if attr in filter(lambda x: not x.startswith('_'),
683
- dir(_DEFAULT_CONFIG_PARSER) + ['setall', 'save']):
684
- object.__getattribute__(self, 'refresh')()
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('Trouble getattr %s < %s >', attr, self)
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(' ', '').split(',')
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 + ['geometry', 'date', 'time',
742
- 'rangex', 'daterangex',
743
- 'iniconf', 'conftool']
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=' ', keysep=':')
788
- if all([k in ('start', 'end', 'step', 'shift', 'fmt', 'prefix')
789
- for k in values.keys()]):
790
- return cb(**{k: self._value_expand(v, remap, subs)
791
- for k, v in values.items()})
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([self._value_expand(v, remap, subs)
796
- for v in self._sparser(value, itemsep=',')])
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(bdate.timeintrangex, value, remap, subs)
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 = {k: self._value_expand(v, remap, subs)
808
- for k, v in self._sparser(value, itemsep=' ', keysep=':').items()}
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(value,
812
- 'No object could be created from the {} collector'.
813
- format(collector))
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, 'iniconf')
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, 'conftool')
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
- _collector = ('iniconf',)
831
+
832
+ _collector = ("iniconf",)
828
833
  _abstract = True
829
834
  _footprint = dict(
830
- info='Abstract Python Inifile',
835
+ info="Abstract Python Inifile",
831
836
  attr=dict(
832
- kind = dict(
833
- info = "The configuration object kind.",
834
- values = ['generic', ],
837
+ kind=dict(
838
+ info="The configuration object kind.",
839
+ values=[
840
+ "generic",
841
+ ],
835
842
  ),
836
- clsconfig = dict(
837
- type = GenericReadOnlyConfigParser,
838
- isclass = True,
839
- optional = True,
840
- default = GenericReadOnlyConfigParser,
841
- doc_visibility = footprints.doc.visibility.ADVANCED,
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 = dict(
844
- kind = 'The configuration file to look for.',
845
- optional = True,
846
- default = '@[kind].ini',
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('Ini Conf %s', self.__class__)
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 = 'Abstract configuration tables',
870
- attr = dict(
871
- kind = dict(
872
- info = "The configuration's table kind.",
877
+ info="Abstract configuration tables",
878
+ attr=dict(
879
+ kind=dict(
880
+ info="The configuration's table kind.",
873
881
  ),
874
- family = dict(
875
- info = "The configuration's table family.",
882
+ family=dict(
883
+ info="The configuration's table family.",
876
884
  ),
877
- version = dict(
878
- info = "The configuration's table version.",
879
- optional = True,
880
- default = 'std',
885
+ version=dict(
886
+ info="The configuration's table version.",
887
+ optional=True,
888
+ default="std",
881
889
  ),
882
- searchkeys = dict(
883
- info = "Item's attributes used to perform the lookup in the find method.",
884
- type = footprints.FPTuple,
885
- optional = True,
886
- default = footprints.FPTuple(),
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 = dict(
889
- info = "The class attribute matching the configuration file groupname",
890
- optional = True,
891
- default = 'family',
896
+ groupname=dict(
897
+ info="The class attribute matching the configuration file groupname",
898
+ optional=True,
899
+ default="family",
892
900
  ),
893
- inifile = dict(
894
- optional = True,
895
- default = '@[family]-[kind]-[version].ini',
901
+ inifile=dict(
902
+ optional=True,
903
+ default="@[family]-[kind]-[version].ini",
896
904
  ),
897
- clsconfig = dict(
898
- default = ExtendedReadOnlyConfigParser,
905
+ clsconfig=dict(
906
+ default=ExtendedReadOnlyConfigParser,
899
907
  ),
900
- language = dict(
901
- info = "The default language for the translator property.",
902
- optional = True,
903
- default = 'en',
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 'configuration-table'
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 [x for x in self.config.parser.sections()
915
- if ':' not in x and not x.startswith('lang_')]
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 [x for x in self.config.sections()
920
- if x not in self.groups() and not x.startswith('lang_')]
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, '_translator'):
926
- if self.config.has_section('lang_' + self.language):
927
- self._translator = self.config.as_dict()['lang_' + self.language]
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, '_tablelist'):
951
+ if not hasattr(self, "_tablelist"):
936
952
  self._tablelist = list()
937
953
  d = self.config.as_dict()
938
- for item, group in [x.split(':') for x in self.config.parser.sections() if ':' in x]:
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('none$', v, re.IGNORECASE):
961
+ if re.match("none$", v, re.IGNORECASE):
944
962
  d[item][k] = None
945
- if re.search('[a-z]_[a-z]', v, re.IGNORECASE):
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]['translator'] = self.translator
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("Unable to create the %s item object. Check the footprint !", item)
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('Some item description could not match')
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 = [x for x in self.tablelist
962
- if x.footprint_getattr(self.searchkeys[0]) == item]
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 = [x for x in self.tablelist
971
- if x.footprint_getattr(self.searchkeys[0]).lower().startswith(item.lower())]
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 [x for x in self.tablelist
980
- if re.search(item, x.footprint_getattr(self.searchkeys[0]), re.IGNORECASE)]
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 [x for x in self.tablelist
985
- if any([re.search(item, x.footprint_getattr(thiskey), re.IGNORECASE)
986
- for thiskey in self.searchkeys])]
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 = ('tableitem',)
1043
+ _collector = ("tableitem",)
1001
1044
  _footprint = dict(
1002
- info = "Abstract configuration table's item.",
1003
- attr = dict(
1045
+ info="Abstract configuration table's item.",
1046
+ attr=dict(
1004
1047
  # Define your own...
1005
- translator = dict(
1006
- optional = True,
1007
- type = footprints.FPDict,
1008
- default = None,
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 'tableitem'
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('ordered_dump', '').split(','):
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((self.translator.get(k, k.replace('_', ' ').title()),
1027
- str(self.footprint_getattr(k)), k))
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 ((not mkshort or self.footprint_getattr(k) is not None) and k != 'translator'):
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 = '{0:' + str(max_keylen) + 's} : {1:s}'
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 '\n'.join(output_list)
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 '**{}** : `{}`\n\n{}\n\n'.format(i_name[1],
1067
- ', '.join(['{:s}={:s}'.format(*i)
1068
- for i in i_hot]),
1069
- '\n'.join([' * {:s}: {:s}'.format(*i)
1070
- for i in i_other])
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
+ )