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