vortex-nwp 2.0.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 (144) hide show
  1. vortex/__init__.py +159 -0
  2. vortex/algo/__init__.py +13 -0
  3. vortex/algo/components.py +2462 -0
  4. vortex/algo/mpitools.py +1953 -0
  5. vortex/algo/mpitools_templates/__init__.py +1 -0
  6. vortex/algo/mpitools_templates/envelope_wrapper_default.tpl +27 -0
  7. vortex/algo/mpitools_templates/envelope_wrapper_mpiauto.tpl +29 -0
  8. vortex/algo/mpitools_templates/wrapstd_wrapper_default.tpl +18 -0
  9. vortex/algo/serversynctools.py +171 -0
  10. vortex/config.py +112 -0
  11. vortex/data/__init__.py +19 -0
  12. vortex/data/abstractstores.py +1510 -0
  13. vortex/data/containers.py +835 -0
  14. vortex/data/contents.py +622 -0
  15. vortex/data/executables.py +275 -0
  16. vortex/data/flow.py +119 -0
  17. vortex/data/geometries.ini +2689 -0
  18. vortex/data/geometries.py +799 -0
  19. vortex/data/handlers.py +1230 -0
  20. vortex/data/outflow.py +67 -0
  21. vortex/data/providers.py +487 -0
  22. vortex/data/resources.py +207 -0
  23. vortex/data/stores.py +1390 -0
  24. vortex/data/sync_templates/__init__.py +0 -0
  25. vortex/gloves.py +309 -0
  26. vortex/layout/__init__.py +20 -0
  27. vortex/layout/contexts.py +577 -0
  28. vortex/layout/dataflow.py +1220 -0
  29. vortex/layout/monitor.py +969 -0
  30. vortex/nwp/__init__.py +14 -0
  31. vortex/nwp/algo/__init__.py +21 -0
  32. vortex/nwp/algo/assim.py +537 -0
  33. vortex/nwp/algo/clim.py +1086 -0
  34. vortex/nwp/algo/coupling.py +831 -0
  35. vortex/nwp/algo/eda.py +840 -0
  36. vortex/nwp/algo/eps.py +785 -0
  37. vortex/nwp/algo/forecasts.py +886 -0
  38. vortex/nwp/algo/fpserver.py +1303 -0
  39. vortex/nwp/algo/ifsnaming.py +463 -0
  40. vortex/nwp/algo/ifsroot.py +404 -0
  41. vortex/nwp/algo/monitoring.py +263 -0
  42. vortex/nwp/algo/mpitools.py +694 -0
  43. vortex/nwp/algo/odbtools.py +1258 -0
  44. vortex/nwp/algo/oopsroot.py +916 -0
  45. vortex/nwp/algo/oopstests.py +220 -0
  46. vortex/nwp/algo/request.py +660 -0
  47. vortex/nwp/algo/stdpost.py +1641 -0
  48. vortex/nwp/data/__init__.py +30 -0
  49. vortex/nwp/data/assim.py +380 -0
  50. vortex/nwp/data/boundaries.py +314 -0
  51. vortex/nwp/data/climfiles.py +521 -0
  52. vortex/nwp/data/configfiles.py +153 -0
  53. vortex/nwp/data/consts.py +954 -0
  54. vortex/nwp/data/ctpini.py +149 -0
  55. vortex/nwp/data/diagnostics.py +209 -0
  56. vortex/nwp/data/eda.py +147 -0
  57. vortex/nwp/data/eps.py +432 -0
  58. vortex/nwp/data/executables.py +1045 -0
  59. vortex/nwp/data/fields.py +111 -0
  60. vortex/nwp/data/gridfiles.py +380 -0
  61. vortex/nwp/data/logs.py +584 -0
  62. vortex/nwp/data/modelstates.py +363 -0
  63. vortex/nwp/data/monitoring.py +193 -0
  64. vortex/nwp/data/namelists.py +696 -0
  65. vortex/nwp/data/obs.py +840 -0
  66. vortex/nwp/data/oopsexec.py +74 -0
  67. vortex/nwp/data/providers.py +207 -0
  68. vortex/nwp/data/query.py +206 -0
  69. vortex/nwp/data/stores.py +160 -0
  70. vortex/nwp/data/surfex.py +337 -0
  71. vortex/nwp/syntax/__init__.py +9 -0
  72. vortex/nwp/syntax/stdattrs.py +437 -0
  73. vortex/nwp/tools/__init__.py +10 -0
  74. vortex/nwp/tools/addons.py +40 -0
  75. vortex/nwp/tools/agt.py +67 -0
  76. vortex/nwp/tools/bdap.py +59 -0
  77. vortex/nwp/tools/bdcp.py +41 -0
  78. vortex/nwp/tools/bdm.py +24 -0
  79. vortex/nwp/tools/bdmp.py +54 -0
  80. vortex/nwp/tools/conftools.py +1661 -0
  81. vortex/nwp/tools/drhook.py +66 -0
  82. vortex/nwp/tools/grib.py +294 -0
  83. vortex/nwp/tools/gribdiff.py +104 -0
  84. vortex/nwp/tools/ifstools.py +203 -0
  85. vortex/nwp/tools/igastuff.py +273 -0
  86. vortex/nwp/tools/mars.py +68 -0
  87. vortex/nwp/tools/odb.py +657 -0
  88. vortex/nwp/tools/partitioning.py +258 -0
  89. vortex/nwp/tools/satrad.py +71 -0
  90. vortex/nwp/util/__init__.py +6 -0
  91. vortex/nwp/util/async.py +212 -0
  92. vortex/nwp/util/beacon.py +40 -0
  93. vortex/nwp/util/diffpygram.py +447 -0
  94. vortex/nwp/util/ens.py +279 -0
  95. vortex/nwp/util/hooks.py +139 -0
  96. vortex/nwp/util/taskdeco.py +85 -0
  97. vortex/nwp/util/usepygram.py +697 -0
  98. vortex/nwp/util/usetnt.py +101 -0
  99. vortex/proxy.py +6 -0
  100. vortex/sessions.py +374 -0
  101. vortex/syntax/__init__.py +9 -0
  102. vortex/syntax/stdattrs.py +867 -0
  103. vortex/syntax/stddeco.py +185 -0
  104. vortex/toolbox.py +1117 -0
  105. vortex/tools/__init__.py +20 -0
  106. vortex/tools/actions.py +523 -0
  107. vortex/tools/addons.py +316 -0
  108. vortex/tools/arm.py +96 -0
  109. vortex/tools/compression.py +325 -0
  110. vortex/tools/date.py +27 -0
  111. vortex/tools/ddhpack.py +10 -0
  112. vortex/tools/delayedactions.py +782 -0
  113. vortex/tools/env.py +541 -0
  114. vortex/tools/folder.py +834 -0
  115. vortex/tools/grib.py +738 -0
  116. vortex/tools/lfi.py +953 -0
  117. vortex/tools/listings.py +423 -0
  118. vortex/tools/names.py +637 -0
  119. vortex/tools/net.py +2124 -0
  120. vortex/tools/odb.py +10 -0
  121. vortex/tools/parallelism.py +368 -0
  122. vortex/tools/prestaging.py +210 -0
  123. vortex/tools/rawfiles.py +10 -0
  124. vortex/tools/schedulers.py +480 -0
  125. vortex/tools/services.py +940 -0
  126. vortex/tools/storage.py +996 -0
  127. vortex/tools/surfex.py +61 -0
  128. vortex/tools/systems.py +3976 -0
  129. vortex/tools/targets.py +440 -0
  130. vortex/util/__init__.py +9 -0
  131. vortex/util/config.py +1122 -0
  132. vortex/util/empty.py +24 -0
  133. vortex/util/helpers.py +216 -0
  134. vortex/util/introspection.py +69 -0
  135. vortex/util/iosponge.py +80 -0
  136. vortex/util/roles.py +49 -0
  137. vortex/util/storefunctions.py +129 -0
  138. vortex/util/structs.py +26 -0
  139. vortex/util/worker.py +162 -0
  140. vortex_nwp-2.0.0.dist-info/METADATA +67 -0
  141. vortex_nwp-2.0.0.dist-info/RECORD +144 -0
  142. vortex_nwp-2.0.0.dist-info/WHEEL +5 -0
  143. vortex_nwp-2.0.0.dist-info/licenses/LICENSE +517 -0
  144. vortex_nwp-2.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,440 @@
1
+ """
2
+ This package handles targets computers objects that could in charge of
3
+ hosting a specific execution. Target objects use the :mod:`footprints` mechanism.
4
+ """
5
+
6
+ import contextlib
7
+ import logging
8
+ import re
9
+ import platform
10
+ import socket
11
+
12
+ from bronx.fancies import loggers
13
+ from bronx.syntax.decorators import secure_getattr
14
+ import footprints as fp
15
+
16
+ from vortex.util.config import GenericConfigParser
17
+ from vortex import sessions
18
+
19
+ #: No automatic export
20
+ __all__ = []
21
+
22
+ logger = loggers.getLogger(__name__)
23
+
24
+
25
+ def default_fqdn():
26
+ """Tries to find the Fully-Qualified Domain Name of the host."""
27
+ try:
28
+ fqdn = socket.getfqdn()
29
+ except OSError:
30
+ fqdn = platform.node()
31
+ return fqdn
32
+
33
+
34
+ class Target(fp.FootprintBase):
35
+ """Root class for any :class:`Target` subclasses.
36
+
37
+ Target classes are used to define specific settings and/or behaviour for a
38
+ given host (*e.g.* your own workstation) or group of hosts (*e.g.* each of
39
+ the nodes of a cluster).
40
+
41
+ Through the :meth:`get` method, it gives access to the **Target**'s specific
42
+ configuration file (``target-[hostname].ini`` by default).
43
+ """
44
+
45
+ _abstract = True
46
+ _explicit = False
47
+ _collector = ("target",)
48
+ _footprint = dict(
49
+ info="Default target description",
50
+ attr=dict(
51
+ hostname=dict(
52
+ optional=True,
53
+ default=platform.node(),
54
+ alias=("nodename", "computer"),
55
+ ),
56
+ inetname=dict(
57
+ optional=True,
58
+ default=platform.node(),
59
+ ),
60
+ fqdn=dict(
61
+ optional=True,
62
+ default=default_fqdn(),
63
+ ),
64
+ sysname=dict(
65
+ optional=True,
66
+ default=platform.system(),
67
+ ),
68
+ userconfig=dict(
69
+ type=GenericConfigParser,
70
+ optional=True,
71
+ default=None,
72
+ ),
73
+ inifile=dict(
74
+ optional=True,
75
+ default="@target-[hostname].ini",
76
+ ),
77
+ defaultinifile=dict(
78
+ optional=True,
79
+ default="target-commons.ini",
80
+ ),
81
+ iniauto=dict(
82
+ type=bool,
83
+ optional=True,
84
+ default=True,
85
+ ),
86
+ ),
87
+ )
88
+
89
+ _re_nodes_property = re.compile(r"(\w+)(nodes)$")
90
+ _re_proxies_property = re.compile(r"(\w+)(proxies)$")
91
+ _re_isnode_property = re.compile(r"is(\w+)node$")
92
+ _re_glove_rk_id = re.compile(r"^(.*)@\w+$")
93
+
94
+ def __init__(self, *args, **kw):
95
+ logger.debug("Abstract target computer init %s", self.__class__)
96
+ super().__init__(*args, **kw)
97
+ self._actualconfig = self.userconfig
98
+ self._specialnodes = None
99
+ self._sepcialnodesaliases = None
100
+ self._specialproxies = None
101
+
102
+ @property
103
+ def realkind(self):
104
+ return "target"
105
+
106
+ @property
107
+ def config(self):
108
+ return self._actualconfig
109
+
110
+ def generic(self):
111
+ """Generic name is inetname by default."""
112
+ return self.inetname
113
+
114
+ def cache_storage_alias(self):
115
+ """The tag used when reading Cache Storage configuration files."""
116
+ return self.inetname
117
+
118
+ def get(self, key, default=None):
119
+ """Get the actual value of the specified ``key`` ( ``section:option`` ).
120
+
121
+ Sections of the configuration file may be overwritten with sections
122
+ specific to a given user's group (identified by the Glove's realkind
123
+ property).
124
+
125
+ :example:
126
+
127
+ Let's consider a user with the *opuser* Glove's realkind and
128
+ the following configuration file::
129
+
130
+ [sectionname]
131
+ myoption = generic
132
+ [sectionname@opuser]
133
+ myoption = operations
134
+
135
+ The :meth:`get` method called whith ``key='sectionname:myoption'`` will
136
+ return 'operations'.
137
+ """
138
+ my_glove_rk = "@" + sessions.current().glove.realkind
139
+ if ":" in key:
140
+ section, option = (x.strip() for x in key.split(":", 1))
141
+ # Check if an override section exists
142
+ sections = [
143
+ x
144
+ for x in (section + my_glove_rk, section)
145
+ if x in self.config.sections()
146
+ ]
147
+ else:
148
+ option = key
149
+ # First look in override sections, then in default one
150
+ sections = [
151
+ s for s in self.config.sections() if s.endswith(my_glove_rk)
152
+ ] + [
153
+ s
154
+ for s in self.config.sections()
155
+ if not self._re_glove_rk_id.match(s)
156
+ ]
157
+ # Return the first matching section/option
158
+ for section in [
159
+ x for x in sections if self.config.has_option(x, option)
160
+ ]:
161
+ return self.config.get(section, option)
162
+ return default
163
+
164
+ def getx(
165
+ self, key, default=None, env_key=None, silent=False, aslist=False
166
+ ):
167
+ r"""Return a value from several sources.
168
+
169
+ In turn, the following sources are considered:
170
+
171
+ - a shell environment variable
172
+ - this configuration handler (key = 'section:option') (see the :meth:`get` method)
173
+ - a default value
174
+
175
+ Unless **silent** is set, ``KeyError`` is raised if the value cannot be found.
176
+
177
+ **aslist** forces the result into a list (be it with a unique element).
178
+ separators are spaces, commas, carriage returns or antislashes.
179
+ e.g. these notations are equivalent::
180
+
181
+ alist = val1 val2 val3 val4 val5
182
+ alist = val1, val2 val3 \
183
+ val4,
184
+ val5
185
+
186
+ """
187
+ if env_key is not None:
188
+ env_key = env_key.upper()
189
+ value = sessions.system().env.get(env_key, None)
190
+ else:
191
+ value = None
192
+
193
+ if value is None:
194
+ if ":" not in key:
195
+ if silent:
196
+ return None
197
+ msg = 'Configuration key should be "section:option" not "{}"'.format(
198
+ key
199
+ )
200
+ raise KeyError(msg)
201
+ value = self.get(key, default)
202
+
203
+ if value is None:
204
+ if silent:
205
+ return None
206
+ msg = 'Please define "{}" in "{}"'.format(key, self.config.file)
207
+ if env_key is not None:
208
+ msg += ' or "{}" in the environment.'.format(env_key)
209
+ logger.error(msg)
210
+ raise KeyError(msg)
211
+
212
+ if aslist:
213
+ value = (
214
+ value.replace("\n", " ")
215
+ .replace("\\", " ")
216
+ .replace(",", " ")
217
+ .split()
218
+ )
219
+
220
+ return value
221
+
222
+ def sections(self):
223
+ """Returns the list of sections contained in the config file."""
224
+ my_glove_rk = "@" + sessions.current().glove.realkind
225
+ return sorted(
226
+ {
227
+ self._re_glove_rk_id.sub(r"\1", x)
228
+ for x in self.config.sections()
229
+ if (
230
+ (not self._re_glove_rk_id.match(x))
231
+ or x.endswith(my_glove_rk)
232
+ )
233
+ }
234
+ )
235
+
236
+ def options(self, key):
237
+ """For a given section, returns the list of available options.
238
+
239
+ The result may depend on the current glove (see the :meth:`get`
240
+ method documentation).
241
+ """
242
+ my_glove_rk = "@" + sessions.current().glove.realkind
243
+ sections = [
244
+ x for x in (key, key + my_glove_rk) if x in self.config.sections()
245
+ ]
246
+ options = set()
247
+ for section in sections:
248
+ options.update(self.config.options(section))
249
+ return sorted(options)
250
+
251
+ def items(self, key):
252
+ """For a given section, returns a dict that contains all options.
253
+
254
+ The result may depend on the current glove (see the :meth:`get`
255
+ method documentation).
256
+ """
257
+ items = dict()
258
+ if key is not None:
259
+ my_glove_rk = "@" + sessions.current().glove.realkind
260
+ sections = [
261
+ x
262
+ for x in (key, key + my_glove_rk)
263
+ if x in self.config.sections()
264
+ ]
265
+ for section in sections:
266
+ items.update(self.config.items(section))
267
+ return items
268
+
269
+ @classmethod
270
+ def is_anonymous(cls):
271
+ """Return a boolean either the current footprint define or not a mandatory set of hostname values."""
272
+ fp = cls.footprint_retrieve()
273
+ return not bool(fp.attr["hostname"]["values"])
274
+
275
+ def spawn_hook(self, sh):
276
+ """Specific target hook before any serious execution."""
277
+ pass
278
+
279
+ @contextlib.contextmanager
280
+ def algo_run_context(self, ticket, *kmappings):
281
+ """Specific target hook before any component run."""
282
+ yield
283
+
284
+ def _init_supernodes(
285
+ self,
286
+ main_re,
287
+ rangeid="range",
288
+ baseid="base",
289
+ ):
290
+ """Read the configuration file in order to initialize the specialnodes
291
+ and specialproxies lists.
292
+
293
+ To define a node list, the XXXnodes configuration key must be
294
+ specified. It can be an hardcoded coma-separated list, or the
295
+ *generic_nodes* keyword. In such a case, the node list will be
296
+ auto-generated using the XXXrange and XXXbase configuration keys.
297
+ """
298
+ confsection = "generic_nodes"
299
+ confoptions = self.options(confsection)
300
+ nodetypes = [
301
+ (m.group(1), m.group(2))
302
+ for m in [main_re.match(k) for k in confoptions]
303
+ if m is not None
304
+ ]
305
+ outdict = dict()
306
+ for nodetype, nodelistid in nodetypes:
307
+ nodelist = self.get(confsection + ":" + nodetype + nodelistid)
308
+ if nodelist == "no_generic":
309
+ noderanges = self.get(
310
+ confsection + ":" + nodetype + rangeid, None
311
+ )
312
+ if noderanges is None:
313
+ raise ValueError(
314
+ "when {0:s}{1:s} == no_generic, {0:s}{2:s} must be provided".format(
315
+ nodetype, nodelistid, rangeid
316
+ )
317
+ )
318
+ nodebases = self.get(
319
+ confsection + ":" + nodetype + baseid,
320
+ self.inetname + nodetype + "{:d}",
321
+ )
322
+ outdict[nodetype] = list()
323
+ for r, b in zip(noderanges.split("+"), nodebases.split("+")):
324
+ outdict[nodetype].extend(
325
+ [b.format(int(i)) for i in r.split(",")]
326
+ )
327
+ else:
328
+ outdict[nodetype] = nodelist.split(",")
329
+ return outdict
330
+
331
+ @property
332
+ def specialnodesaliases(self):
333
+ """Return the list of known aliases."""
334
+ if self._sepcialnodesaliases is None:
335
+ confsection = "generic_nodes"
336
+ confoptions = self.options(confsection)
337
+ aliases_re = re.compile(r"(\w+)(aliases)")
338
+ nodetypes = [
339
+ (m.group(1), m.group(2))
340
+ for m in [aliases_re.match(k) for k in confoptions]
341
+ if m is not None
342
+ ]
343
+ rdict = {
344
+ ntype: self.get(confsection + ":" + ntype + key, "").split(",")
345
+ for ntype, key in nodetypes
346
+ }
347
+ self._sepcialnodesaliases = rdict
348
+ return self._sepcialnodesaliases
349
+
350
+ @property
351
+ def specialnodes(self):
352
+ """
353
+ Returns a dictionary that contains the list of nodes for a given
354
+ node-type.
355
+ """
356
+ if self._specialnodes is None:
357
+ self._specialnodes = self._init_supernodes(self._re_nodes_property)
358
+ for ntype, aliases in self.specialnodesaliases.items():
359
+ for alias in aliases:
360
+ self._specialnodes[alias] = self._specialnodes[ntype]
361
+ return self._specialnodes
362
+
363
+ @property
364
+ def specialproxies(self):
365
+ """Returns a dictionary that contains the proxy-nodes list for a given
366
+ node-type.
367
+
368
+ If the proxy-nodes are not defined in the configuration file, it is
369
+ equal to the specialnodes list.
370
+ """
371
+ if self._specialproxies is None:
372
+ self._specialproxies = self._init_supernodes(
373
+ self._re_proxies_property, "proxiesrange", "proxiesbase"
374
+ )
375
+ for nodetype, nodelist in self.specialnodes.items():
376
+ if nodetype not in self._specialproxies:
377
+ self._specialproxies[nodetype] = nodelist
378
+ for ntype, aliases in self.specialnodesaliases.items():
379
+ for alias in aliases:
380
+ self._specialproxies[alias] = self._specialproxies[ntype]
381
+ return self._specialproxies
382
+
383
+ @secure_getattr
384
+ def __getattr__(self, key):
385
+ """Create attributes on the fly.
386
+
387
+ * XXXnodes: returns the list of nodes for a given node-type
388
+ (e.g loginnodes). If the XXX node-type is not defined in the
389
+ configuration file, it returns an empty list.
390
+ * XXXproxies: returns the list of proxy nodes for a given node-type
391
+ (e.g loginproxies). If the XXX node-type is not defined in the
392
+ configuration file, it returns an empty list.
393
+ * isXXXnode: Return True if the current host is of XXX node-type.
394
+ If the XXX node-type is not defined in the configuration file,
395
+ it returns True.
396
+
397
+ """
398
+ kmatch = self._re_nodes_property.match(key)
399
+ if kmatch is not None:
400
+ return fp.stdtypes.FPList(
401
+ self.specialnodes.get(kmatch.group(1), [])
402
+ )
403
+ kmatch = self._re_proxies_property.match(key)
404
+ if kmatch is not None:
405
+ return fp.stdtypes.FPList(
406
+ self.specialproxies.get(kmatch.group(1), [])
407
+ )
408
+ kmatch = self._re_isnode_property.match(key)
409
+ if kmatch is not None:
410
+ return (kmatch.group(1) not in self.specialnodes) or any(
411
+ [
412
+ self.hostname.startswith(s)
413
+ for s in self.specialnodes[kmatch.group(1)]
414
+ ]
415
+ )
416
+ raise AttributeError('The key "{:s}" does not exist.'.format(key))
417
+
418
+ @property
419
+ def ftraw_default(self):
420
+ """The default value for the System object ftraw attribute."""
421
+ return "ftraw" in self.specialnodes and any(
422
+ [self.hostname.startswith(s) for s in self.specialnodes["ftraw"]]
423
+ )
424
+
425
+
426
+ class LocalTarget(Target):
427
+ """A very generic class usable for most computers."""
428
+
429
+ _footprint = dict(
430
+ info="Nice local target",
431
+ attr=dict(
432
+ sysname=dict(values=["Linux", "Darwin", "Local", "Localhost"]),
433
+ ),
434
+ )
435
+
436
+
437
+ # Disable priority warnings on the target collector
438
+ fcollect = fp.collectors.get(tag="target")
439
+ fcollect.non_ambiguous_loglevel = logging.DEBUG
440
+ del fcollect
@@ -0,0 +1,9 @@
1
+ """
2
+ Miscaleneous collection of small tools somehow extending standard Python's
3
+ capabilities.
4
+ """
5
+
6
+ #: No automatic export
7
+ __all__ = []
8
+
9
+ __tocinfoline__ = "VORTEX generic utility classes and methods"