vortex-nwp 2.0.0b1__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 (146) hide show
  1. vortex/__init__.py +135 -0
  2. vortex/algo/__init__.py +12 -0
  3. vortex/algo/components.py +2136 -0
  4. vortex/algo/mpitools.py +1648 -0
  5. vortex/algo/mpitools_templates/envelope_wrapper_default.tpl +27 -0
  6. vortex/algo/mpitools_templates/envelope_wrapper_mpiauto.tpl +29 -0
  7. vortex/algo/mpitools_templates/wrapstd_wrapper_default.tpl +18 -0
  8. vortex/algo/serversynctools.py +170 -0
  9. vortex/config.py +115 -0
  10. vortex/data/__init__.py +13 -0
  11. vortex/data/abstractstores.py +1572 -0
  12. vortex/data/containers.py +780 -0
  13. vortex/data/contents.py +596 -0
  14. vortex/data/executables.py +284 -0
  15. vortex/data/flow.py +113 -0
  16. vortex/data/geometries.ini +2689 -0
  17. vortex/data/geometries.py +703 -0
  18. vortex/data/handlers.py +1021 -0
  19. vortex/data/outflow.py +67 -0
  20. vortex/data/providers.py +465 -0
  21. vortex/data/resources.py +201 -0
  22. vortex/data/stores.py +1271 -0
  23. vortex/gloves.py +282 -0
  24. vortex/layout/__init__.py +27 -0
  25. vortex/layout/appconf.py +109 -0
  26. vortex/layout/contexts.py +511 -0
  27. vortex/layout/dataflow.py +1069 -0
  28. vortex/layout/jobs.py +1276 -0
  29. vortex/layout/monitor.py +833 -0
  30. vortex/layout/nodes.py +1424 -0
  31. vortex/layout/subjobs.py +464 -0
  32. vortex/nwp/__init__.py +11 -0
  33. vortex/nwp/algo/__init__.py +12 -0
  34. vortex/nwp/algo/assim.py +483 -0
  35. vortex/nwp/algo/clim.py +920 -0
  36. vortex/nwp/algo/coupling.py +609 -0
  37. vortex/nwp/algo/eda.py +632 -0
  38. vortex/nwp/algo/eps.py +613 -0
  39. vortex/nwp/algo/forecasts.py +745 -0
  40. vortex/nwp/algo/fpserver.py +927 -0
  41. vortex/nwp/algo/ifsnaming.py +403 -0
  42. vortex/nwp/algo/ifsroot.py +311 -0
  43. vortex/nwp/algo/monitoring.py +202 -0
  44. vortex/nwp/algo/mpitools.py +554 -0
  45. vortex/nwp/algo/odbtools.py +974 -0
  46. vortex/nwp/algo/oopsroot.py +735 -0
  47. vortex/nwp/algo/oopstests.py +186 -0
  48. vortex/nwp/algo/request.py +579 -0
  49. vortex/nwp/algo/stdpost.py +1285 -0
  50. vortex/nwp/data/__init__.py +12 -0
  51. vortex/nwp/data/assim.py +392 -0
  52. vortex/nwp/data/boundaries.py +261 -0
  53. vortex/nwp/data/climfiles.py +539 -0
  54. vortex/nwp/data/configfiles.py +149 -0
  55. vortex/nwp/data/consts.py +929 -0
  56. vortex/nwp/data/ctpini.py +133 -0
  57. vortex/nwp/data/diagnostics.py +181 -0
  58. vortex/nwp/data/eda.py +148 -0
  59. vortex/nwp/data/eps.py +383 -0
  60. vortex/nwp/data/executables.py +1039 -0
  61. vortex/nwp/data/fields.py +96 -0
  62. vortex/nwp/data/gridfiles.py +308 -0
  63. vortex/nwp/data/logs.py +551 -0
  64. vortex/nwp/data/modelstates.py +334 -0
  65. vortex/nwp/data/monitoring.py +220 -0
  66. vortex/nwp/data/namelists.py +644 -0
  67. vortex/nwp/data/obs.py +748 -0
  68. vortex/nwp/data/oopsexec.py +72 -0
  69. vortex/nwp/data/providers.py +182 -0
  70. vortex/nwp/data/query.py +217 -0
  71. vortex/nwp/data/stores.py +147 -0
  72. vortex/nwp/data/surfex.py +338 -0
  73. vortex/nwp/syntax/__init__.py +9 -0
  74. vortex/nwp/syntax/stdattrs.py +375 -0
  75. vortex/nwp/tools/__init__.py +10 -0
  76. vortex/nwp/tools/addons.py +35 -0
  77. vortex/nwp/tools/agt.py +55 -0
  78. vortex/nwp/tools/bdap.py +48 -0
  79. vortex/nwp/tools/bdcp.py +38 -0
  80. vortex/nwp/tools/bdm.py +21 -0
  81. vortex/nwp/tools/bdmp.py +49 -0
  82. vortex/nwp/tools/conftools.py +1311 -0
  83. vortex/nwp/tools/drhook.py +62 -0
  84. vortex/nwp/tools/grib.py +268 -0
  85. vortex/nwp/tools/gribdiff.py +99 -0
  86. vortex/nwp/tools/ifstools.py +163 -0
  87. vortex/nwp/tools/igastuff.py +249 -0
  88. vortex/nwp/tools/mars.py +56 -0
  89. vortex/nwp/tools/odb.py +548 -0
  90. vortex/nwp/tools/partitioning.py +234 -0
  91. vortex/nwp/tools/satrad.py +56 -0
  92. vortex/nwp/util/__init__.py +6 -0
  93. vortex/nwp/util/async.py +184 -0
  94. vortex/nwp/util/beacon.py +40 -0
  95. vortex/nwp/util/diffpygram.py +359 -0
  96. vortex/nwp/util/ens.py +198 -0
  97. vortex/nwp/util/hooks.py +128 -0
  98. vortex/nwp/util/taskdeco.py +81 -0
  99. vortex/nwp/util/usepygram.py +591 -0
  100. vortex/nwp/util/usetnt.py +87 -0
  101. vortex/proxy.py +6 -0
  102. vortex/sessions.py +341 -0
  103. vortex/syntax/__init__.py +9 -0
  104. vortex/syntax/stdattrs.py +628 -0
  105. vortex/syntax/stddeco.py +176 -0
  106. vortex/toolbox.py +982 -0
  107. vortex/tools/__init__.py +11 -0
  108. vortex/tools/actions.py +457 -0
  109. vortex/tools/addons.py +297 -0
  110. vortex/tools/arm.py +76 -0
  111. vortex/tools/compression.py +322 -0
  112. vortex/tools/date.py +20 -0
  113. vortex/tools/ddhpack.py +10 -0
  114. vortex/tools/delayedactions.py +672 -0
  115. vortex/tools/env.py +513 -0
  116. vortex/tools/folder.py +663 -0
  117. vortex/tools/grib.py +559 -0
  118. vortex/tools/lfi.py +746 -0
  119. vortex/tools/listings.py +354 -0
  120. vortex/tools/names.py +575 -0
  121. vortex/tools/net.py +1790 -0
  122. vortex/tools/odb.py +10 -0
  123. vortex/tools/parallelism.py +336 -0
  124. vortex/tools/prestaging.py +186 -0
  125. vortex/tools/rawfiles.py +10 -0
  126. vortex/tools/schedulers.py +413 -0
  127. vortex/tools/services.py +871 -0
  128. vortex/tools/storage.py +1061 -0
  129. vortex/tools/surfex.py +61 -0
  130. vortex/tools/systems.py +3396 -0
  131. vortex/tools/targets.py +384 -0
  132. vortex/util/__init__.py +9 -0
  133. vortex/util/config.py +1071 -0
  134. vortex/util/empty.py +24 -0
  135. vortex/util/helpers.py +184 -0
  136. vortex/util/introspection.py +63 -0
  137. vortex/util/iosponge.py +76 -0
  138. vortex/util/roles.py +51 -0
  139. vortex/util/storefunctions.py +103 -0
  140. vortex/util/structs.py +26 -0
  141. vortex/util/worker.py +150 -0
  142. vortex_nwp-2.0.0b1.dist-info/LICENSE +517 -0
  143. vortex_nwp-2.0.0b1.dist-info/METADATA +50 -0
  144. vortex_nwp-2.0.0b1.dist-info/RECORD +146 -0
  145. vortex_nwp-2.0.0b1.dist-info/WHEEL +5 -0
  146. vortex_nwp-2.0.0b1.dist-info/top_level.txt +1 -0
vortex/toolbox.py ADDED
@@ -0,0 +1,982 @@
1
+ """
2
+ Top level interface for accessing the VORTEX facilities.
3
+
4
+ This module does not provides any class, constant, or any nice object.
5
+ It defines a very basic interface to some (possibly) powerful capacities
6
+ of the :mod:`vortex` toolbox.
7
+ """
8
+
9
+ from contextlib import contextmanager
10
+ import re
11
+ import traceback
12
+
13
+ from bronx.fancies import loggers
14
+ from bronx.stdtypes.history import History
15
+ from bronx.syntax import mktuple
16
+
17
+ import footprints
18
+
19
+ from vortex import sessions, data, proxy, VortexForceComplete
20
+ from vortex.layout.dataflow import stripargs_section, intent, ixo, Section
21
+
22
+ #: Automatic export of superstar interface.
23
+ __all__ = ['rload', 'rget', 'rput']
24
+
25
+ logger = loggers.getLogger(__name__)
26
+
27
+ #: Shortcut to footprint env defaults
28
+ defaults = footprints.setup.defaults
29
+
30
+ sectionmap = {'input': 'get', 'output': 'put', 'executable': 'get'}
31
+
32
+
33
+ # Toolbox defaults
34
+
35
+ #: Default value for the **now** attribute of :func:`input`, :func:`executable`
36
+ #: and :func:`output` functions
37
+ active_now = False
38
+ #: Default value for the **insitu** attribute of the :func:`input` and
39
+ #: :func:`executable` functions
40
+ active_insitu = False
41
+ #: If *False*, drastically reduces the amount of messages printed by the
42
+ #: toolbox module
43
+ active_verbose = True
44
+ #: If *False*, do not try to create/make any promise
45
+ active_promise = True
46
+ #: If *False*, this makes the :func:`clear_promises` function inactive
47
+ active_clear = False
48
+ #: If *False*, this will reset to *False* any ``metadatacheck`` attribute
49
+ #: passed to the :func:`input` or :func:`executable` functions
50
+ active_metadatacheck = True
51
+ #: If *True*, archive stores will not be used at all (only cache stores will
52
+ #: be used)
53
+ active_incache = False
54
+ #: Use the earlyget feature during :func:`input` calls
55
+ active_batchinputs = True
56
+
57
+ #: History recording
58
+ history = History(tag='rload')
59
+
60
+
61
+ # Most commonly used functions
62
+
63
+ def show_toolbox_settings(ljust=24):
64
+ """Print the current settings of the toolbox."""
65
+ for key in ['active_{}'.format(act) for act in
66
+ ('now', 'insitu', 'verbose', 'promise', 'clear',
67
+ 'metadatacheck', 'incache')]:
68
+ kval = globals().get(key, None)
69
+ if kval is not None:
70
+ print('+', key.ljust(ljust), '=', kval)
71
+
72
+
73
+ def quickview(args, nb=0, indent=0):
74
+ """Recursive call to any quick view of objects specified as arguments."""
75
+ if not isinstance(args, list) and not isinstance(args, tuple):
76
+ args = (args, )
77
+ for x in args:
78
+ if nb:
79
+ print()
80
+ nb += 1
81
+ quickview = getattr(x, 'quickview', None)
82
+ if quickview:
83
+ quickview(nb, indent)
84
+ else:
85
+ print('{:02d}. {:s}'.format(nb, x))
86
+
87
+
88
+ class VortexToolboxDescError(Exception):
89
+ pass
90
+
91
+
92
+ def rload(*args, **kw):
93
+ """
94
+ Resource Loader.
95
+
96
+ This function behaves as a factory for any possible pre-defined family
97
+ of VORTEX resources (described by an aggregation of Resource, Provider and
98
+ Container objects).
99
+
100
+ Arguments could be a mix of a list of dictionary-type objects and key/value
101
+ parameters. Other type of arguments will be discarded.
102
+
103
+ An abstract resource descriptor is built as the aggregation of these
104
+ arguments and then expanded according to rules defined by the
105
+ :func:`footprints.util.expand` function.
106
+
107
+ For each expanded descriptor, the ``rload`` method will try to pickup the
108
+ best candidates (if any) that could match the description (*i.e.* Resource,
109
+ Provider, Container). If no match is found for one of the Resource, Provider
110
+ or Container objects, a :class:`VortexToolboxDescError` exception is raised.
111
+ Otherwise,the resource's :class:`~vortex.data.handlers.Handler` built from
112
+ those three objects is added to the result's list.
113
+
114
+ :return: A list of :class:`vortex.data.handlers.Handler` objects.
115
+ """
116
+ rd = dict()
117
+ for a in args:
118
+ if isinstance(a, dict):
119
+ rd.update(a)
120
+ else:
121
+ logger.warning('Discard rload argument <%s>', a)
122
+ rd.update(kw)
123
+ if rd:
124
+ history.append(rd.copy())
125
+ rhx = []
126
+ for x in footprints.util.expand(rd):
127
+ picked_up = proxy.containers.pickup( # @UndefinedVariable
128
+ * proxy.providers.pickup_and_cache( # @UndefinedVariable
129
+ * proxy.resources.pickup_and_cache(x) # @UndefinedVariable
130
+ )
131
+ )
132
+ logger.debug('Resource desc %s', picked_up)
133
+ picked_rh = data.handlers.Handler(picked_up)
134
+ if not picked_rh.complete:
135
+ raise VortexToolboxDescError("The ResourceHandler is incomplete")
136
+ rhx.append(picked_rh)
137
+
138
+ return rhx
139
+
140
+
141
+ def rh(*args, **kw):
142
+ """
143
+ This function selects the first resource's handler as returned by the
144
+ :func:`rload` function.
145
+ """
146
+ return rload(*args, **kw)[0]
147
+
148
+
149
+ def rget(*args, **kw):
150
+ """
151
+ This function calls the :meth:`get` method on any resource handler returned
152
+ by the :func:`rload` function.
153
+ """
154
+ loc_incache = kw.pop('incache', active_incache)
155
+ rl = rload(*args, **kw)
156
+ for rh in rl:
157
+ rh.get(incache=loc_incache)
158
+ return rl
159
+
160
+
161
+ def rput(*args, **kw):
162
+ """
163
+ This function calls the :meth:`put` method on any resource handler returned
164
+ by the :func:`rload` function.
165
+ """
166
+ loc_incache = kw.pop('incache', active_incache)
167
+ rl = rload(*args, **kw)
168
+ for rh in rl:
169
+ rh.put(incache=loc_incache)
170
+ return rl
171
+
172
+
173
+ def nicedump(msg, **kw):
174
+ """Simple dump the **kw** dict content with ``msg`` as header."""
175
+ print('#', msg, ':')
176
+ for k, v in sorted(kw.items()):
177
+ print('+', k.ljust(12), '=', str(v))
178
+ print()
179
+
180
+
181
+ @contextmanager
182
+ def _tb_isolate(t, loglevel):
183
+ """Handle the context and logger (internal use only)."""
184
+ # Switch off autorecording of the current context
185
+ ctx = t.context
186
+ recordswitch = ctx.record
187
+ if recordswitch:
188
+ ctx.record_off()
189
+ try:
190
+ if loglevel is not None:
191
+ # Possibly change the log level if necessary
192
+ with loggers.contextboundGlobalLevel(loglevel):
193
+ yield
194
+ else:
195
+ yield
196
+ finally:
197
+ if recordswitch:
198
+ ctx.record_on()
199
+
200
+
201
+ def add_section(section, args, kw):
202
+ """
203
+ Add a :class:`~vortex.layout.dataflow.Section` object (of kind **section**)
204
+ to the current sequence.
205
+
206
+ 1. The **kw** dictionary may contain keys that influence this function
207
+ behaviour (such attributes are popped from **kw** before going further):
208
+
209
+ * **now**: If *True*, call the appropriate action (``get()`` or ``put()``)
210
+ on each added :class:`~vortex.layout.dataflow.Section`. (The default
211
+ is given by :data:`active_now`).
212
+ * **loglevel**: The logging facility verbosity level that will be used
213
+ during the :class:`~vortex.layout.dataflow.Section` creation process.
214
+ If *None*, nothing is done (i.e. the current verbosity level is
215
+ preserved). (default: *None*).
216
+ * **verbose**: If *True*, print some informations on the standard output
217
+ (The default is given by :data:`active_verbose`).
218
+ * **complete**: If *True*, force the task to complete (the
219
+ :class:`VortexForceComplete` exception is raised) whenever an error
220
+ occurs. (default: False).
221
+ * **insitu**: It *True*, before actually getting data, we first check
222
+ if the data is already there (in the context of a multi-step job,
223
+ it might have been fetched during a previous step). (default: *False*).
224
+ * **incache**: It *True*, archive stores will not be used at all (only cache
225
+ stores will be used). (The default is given by :data:`active_incache`).
226
+
227
+ 2. **kw** is then looked for items relevant to the
228
+ :class:`~vortex.layout.dataflow.Section` constructor (``role``, ``intent``,
229
+ ...). (such items are popped from kw before going further).
230
+
231
+ 3. The remaining **kw** items are passed directly to the :func:`rload`
232
+ function in order to create the resource's
233
+ :class:`~vortex.data.handlers.Handler`.
234
+
235
+ :return: A list of :class:`vortex.data.handlers.Handler` objects.
236
+ """
237
+
238
+ t = sessions.current()
239
+
240
+ # First, retrieve arguments of the toolbox command itself
241
+ now = kw.pop('now', active_now)
242
+ loglevel = kw.pop('loglevel', None)
243
+ talkative = kw.pop('verbose', active_verbose)
244
+ complete = kw.pop('complete', False)
245
+ insitu = kw.get('insitu', False)
246
+ batch = kw.pop('batch', False)
247
+ lastfatal = kw.pop('lastfatal', None)
248
+
249
+ if complete:
250
+ kw['fatal'] = False
251
+
252
+ if batch:
253
+ if section not in ('input', 'excutable'):
254
+ logger.info("batch=True is not implemented for section=%s. overwriting to batch=Fase.",
255
+ section)
256
+ batch = False
257
+
258
+ # Second, retrieve arguments that could be used by the now command
259
+ cmdopts = dict(
260
+ incache=kw.pop('incache', active_incache),
261
+ force=kw.pop('force', False)
262
+ )
263
+
264
+ # Third, collect arguments for triggering some hook
265
+ hooks = dict()
266
+ for ahook in [x for x in kw.keys() if x.startswith('hook_')]:
267
+ cbhook = mktuple(kw.pop(ahook))
268
+ cbfunc = cbhook[0]
269
+ if not callable(cbfunc):
270
+ cbfunc = t.sh.import_function(cbfunc)
271
+ hooks[ahook] = footprints.FPTuple((cbfunc, cbhook[1:]))
272
+
273
+ # Print the user inputs
274
+ def print_user_inputs():
275
+ nicedump('New {:s} section with options'.format(section), **opts)
276
+ nicedump('Resource handler description', **kwclean)
277
+ nicedump(
278
+ 'This command options',
279
+ complete=complete,
280
+ loglevel=loglevel,
281
+ now=now,
282
+ verbose=talkative,
283
+ )
284
+ if hooks:
285
+ nicedump('Hooks triggered', **hooks)
286
+
287
+ with _tb_isolate(t, loglevel):
288
+
289
+ # Distinguish between section arguments, and resource loader arguments
290
+ opts, kwclean = stripargs_section(**kw)
291
+
292
+ # Strip the metadatacheck option depending on active_metadatacheck
293
+ if not active_metadatacheck and not insitu:
294
+ if kwclean.get('metadatacheck', False):
295
+ logger.info("The metadatacheck option is forced to False since " +
296
+ "active_metadatacheck=False.")
297
+ kwclean['metadatacheck'] = False
298
+
299
+ # Show the actual set of arguments
300
+ if talkative and not insitu:
301
+ print_user_inputs()
302
+
303
+ # Let the magic of footprints resolution operate...
304
+ kwclean.update(hooks)
305
+ rl = rload(*args, **kwclean)
306
+ rlok = list()
307
+
308
+ # Prepare the references to the actual section method to perform
309
+ push = getattr(t.context.sequence, section)
310
+ doitmethod = sectionmap[section]
311
+
312
+ # Create a section for each resource handler
313
+ if rl and lastfatal is not None:
314
+ newsections = [push(rh=rhandler, **opts)[0] for rhandler in rl[:-1]]
315
+ tmpopts = opts.copy()
316
+ tmpopts['fatal'] = lastfatal
317
+ newsections.append(push(rh=rl[-1], **tmpopts)[0])
318
+ else:
319
+ newsections = [push(rh=rhandler, **opts)[0] for rhandler in rl]
320
+
321
+ # If insitu and now, try a quiet get...
322
+ do_quick_insitu = section in ('input', 'executable') and insitu and now
323
+ if do_quick_insitu:
324
+ quickget = [sec.rh.insitu_quickget(alternate=sec.alternate, **cmdopts) for sec in newsections]
325
+ if all(quickget):
326
+ if len(quickget) > 1:
327
+ logger.info("The insitu get succeeded for all of the %d resource handlers.",
328
+ len(rl))
329
+ else:
330
+ logger.info("The insitu get succeeded for this resource handler.")
331
+ rlok = [sec.rh for sec in newsections]
332
+ else:
333
+ # Start again with the usual get sequence
334
+ print_user_inputs()
335
+
336
+ # If not insitu, not now, or if the quiet get failed
337
+ if not (do_quick_insitu and all(quickget)):
338
+ if now:
339
+ with t.sh.ftppool():
340
+ # Create a section for each resource handler, and perform action on demand
341
+ batchflags = [None, ] * len(newsections)
342
+ if batch:
343
+ if talkative:
344
+ t.sh.subtitle('Early-{:s} for all resources.'.format(doitmethod))
345
+ for ir, newsection in enumerate(newsections):
346
+ rhandler = newsection.rh
347
+ batchflags[ir] = getattr(newsection, 'early' + doitmethod)(**cmdopts)
348
+ if talkative:
349
+ if any(batchflags):
350
+ for ir, newsection in enumerate(newsections):
351
+ if talkative and batchflags[ir]:
352
+ logger.info('Resource no %02d/%02d: Early-%s registered with id: %s.',
353
+ ir + 1, len(rl), doitmethod, str(batchflags[ir]))
354
+ else:
355
+ logger.debug('Resource no %02d/%02d: Early-%s registered with id: %s.',
356
+ ir + 1, len(rl), doitmethod, str(batchflags[ir]))
357
+ else:
358
+ logger.info('Early-%s was unavailable for all of the resources.',
359
+ doitmethod)
360
+ # trigger finalise for all of the DelayedActions
361
+ tofinalise = [r_id for r_id in batchflags if r_id and r_id is not True]
362
+ if tofinalise:
363
+ if talkative:
364
+ t.sh.subtitle('Finalising all of the delayed actions...')
365
+ t.context.delayedactions_hub.finalise(* tofinalise)
366
+ secok = list()
367
+ for ir, newsection in enumerate(newsections):
368
+ rhandler = newsection.rh
369
+ # If quick get was ok for this resource don't call get again...
370
+ if talkative:
371
+ t.sh.subtitle('Resource no {:02d}/{:02d}'.format(ir + 1, len(rl)))
372
+ rhandler.quickview(nb=ir + 1, indent=0)
373
+ if batchflags[ir] is not True or (do_quick_insitu and quickget[ir]):
374
+ t.sh.highlight('Action {:s} on {:s}'.format(doitmethod.upper(),
375
+ rhandler.location(fatal=False)))
376
+ ok = do_quick_insitu and quickget[ir]
377
+ if batchflags[ir]:
378
+ actual_doitmethod = 'finalise' + doitmethod
379
+ ok = ok or getattr(newsection, actual_doitmethod)()
380
+ else:
381
+ actual_doitmethod = doitmethod
382
+ ok = ok or getattr(newsection, actual_doitmethod)(**cmdopts)
383
+ if talkative:
384
+ t.sh.highlight('Result from {:s}: [{!s}]'.format(actual_doitmethod, ok))
385
+ if talkative and not ok:
386
+ logger.error('Could not %s resource %s',
387
+ doitmethod, rhandler.container.localpath())
388
+ if not ok:
389
+ if complete:
390
+ logger.warning('Force complete for %s',
391
+ rhandler.location(fatal=False))
392
+ raise VortexForceComplete('Force task complete on resource error')
393
+ else:
394
+ secok.append(newsection)
395
+ if t.sh.trace:
396
+ print()
397
+ rlok.extend([newsection.rh for newsection in secok
398
+ if newsection.any_coherentgroup_opened])
399
+ else:
400
+ rlok.extend([newsection.rh for newsection in newsections])
401
+
402
+ return rlok
403
+
404
+
405
+ # noinspection PyShadowingBuiltins
406
+ def input(*args, **kw): # @ReservedAssignment
407
+ """Add input :class:`~vortex.layout.dataflow.Section` objects to the current sequence.
408
+
409
+ Relies on the :func:`add_section` function (see its documentation), with:
410
+
411
+ * It's ``section`` attribute is automatically set to 'input';
412
+ * The ``kw``'s *insitu* item is set to :data:`active_insitu` by default.
413
+
414
+ :return: A list of :class:`vortex.data.handlers.Handler` objects (associated
415
+ with the newly created class:`~vortex.layout.dataflow.Section` objects).
416
+ """
417
+ kw.setdefault('insitu', active_insitu)
418
+ kw.setdefault('batch', active_batchinputs)
419
+ return add_section('input', args, kw)
420
+
421
+
422
+ def inputs(ticket=None, context=None):
423
+ """Return effective inputs for the specified context.
424
+
425
+ It actually returns both *inputs* and *executables*.
426
+
427
+ :param ~vortex.sessions.Ticket ticket: A session's Ticket. If set to *None*,
428
+ the current active session will be used (default: *None*)
429
+ :param ~vortex.layout.contexts.Context context: A context object. If set to *None*,
430
+ the current active context will be used (default: *None*)
431
+ :return: A list of :class:`~vortex.layout.dataflow.Section` objects.
432
+ """
433
+ if context is None:
434
+ if ticket is None:
435
+ ticket = sessions.current()
436
+ context = ticket.context
437
+ return context.sequence.effective_inputs()
438
+
439
+
440
+ def show_inputs(context=None):
441
+ """Dump a summary of inputs (+ executables) sections.
442
+
443
+ :param ~vortex.layout.contexts.Context context: A context object. If set to *None*,
444
+ the current active context will be used (default: *None*)
445
+ """
446
+ t = sessions.current()
447
+ for csi in inputs(ticket=t):
448
+ t.sh.header('Input ' + str(csi))
449
+ csi.show(ticket=t, context=context)
450
+ print()
451
+
452
+
453
+ def output(*args, **kw):
454
+ """Add output :class:`~vortex.layout.dataflow.Section` objects to the current sequence.
455
+
456
+ Relies on the :func:`add_section` function (see its documentation), with:
457
+
458
+ * It's ``section`` attribute is automatically set to 'output';
459
+
460
+ :return: A list of :class:`vortex.data.handlers.Handler` objects (associated
461
+ with the newly created class:`~vortex.layout.dataflow.Section` objects).
462
+ """
463
+ # Strip the metadatacheck option depending on active_metadatacheck
464
+ if not active_promise:
465
+ for target in ('promised', 'expected'):
466
+ if target in kw and kw[target]:
467
+ logger.info("The %s argument is removed since active_promise=False.", target)
468
+ del kw[target]
469
+ return add_section('output', args, kw)
470
+
471
+
472
+ def outputs(ticket=None, context=None):
473
+ """Return effective outputs in specified context.
474
+
475
+ :param ~vortex.sessions.Ticket ticket: A session's Ticket. If set to *None*,
476
+ the current active session will be used (default: *None*)
477
+ :param ~vortex.layout.contexts.Context context: A context object. If set to *None*,
478
+ the current active context will be used (default: *None*)
479
+ :return: A list of :class:`~vortex.layout.dataflow.Section` objects.
480
+ """
481
+ if context is None:
482
+ if ticket is None:
483
+ ticket = sessions.current()
484
+ context = ticket.context
485
+ return context.sequence.effective_outputs()
486
+
487
+
488
+ def show_outputs(context=None):
489
+ """Dump a summary of outputs sections.
490
+
491
+ :param ~vortex.layout.contexts.Context context: A context object. If set to *None*,
492
+ the current active context will be used (default: *None*)
493
+ """
494
+ t = sessions.current()
495
+ for cso in outputs(ticket=t):
496
+ t.sh.header('Output ' + str(cso))
497
+ cso.show(ticket=t, context=context)
498
+ print()
499
+
500
+
501
+ def promise(*args, **kw):
502
+ """Log promises before execution.
503
+
504
+ Relies on the :func:`add_section` function (see its documentation), with:
505
+
506
+ * It's ``section`` attribute is automatically set to 'output';
507
+ * The ``kw``'s *promised* item is set to *True*;
508
+ * The ``kw``'s *force* item is set to *True*;
509
+ * The ``kw``'s *now* item is set to :data:`active_promise`.
510
+
511
+ :return: A list of :class:`vortex.data.handlers.Handler` objects (associated
512
+ with the newly created class:`~vortex.layout.dataflow.Section` objects).
513
+ """
514
+ kw.update(
515
+ promised=True,
516
+ force=True,
517
+ now=active_promise,
518
+ )
519
+ if not active_promise:
520
+ kw.setdefault('verbose', False)
521
+ logger.warning('Promise flag is <%s> in that context', active_promise)
522
+ return add_section('output', args, kw)
523
+
524
+
525
+ def executable(*args, **kw):
526
+ """Add executable :class:`~vortex.layout.dataflow.Section` objects to the current sequence.
527
+
528
+ Relies on the :func:`add_section` function (see its documentation), with:
529
+
530
+ * It's ``section`` attribute is automatically set to 'executable';
531
+ * The ``kw``'s *insitu* item is set to :data:`active_insitu` by default.
532
+
533
+ :return: A list of :class:`vortex.data.handlers.Handler` objects (associated
534
+ with the newly created class:`~vortex.layout.dataflow.Section` objects).
535
+ """
536
+ kw.setdefault('insitu', active_insitu)
537
+ return add_section('executable', args, kw)
538
+
539
+
540
+ def algo(*args, **kw):
541
+ """Load an algo component and display its description (if **verbose**).
542
+
543
+ 1. The **kw** dictionary may contain keys that influence this function
544
+ behaviour (such attributes are popped from **kw** before going further):
545
+
546
+ * **loglevel**: The logging facility verbosity level that will be used
547
+ during the :class:`~vortex.layout.dataflow.Section` creation process.
548
+ If *None*, nothing is done (i.e. the current verbosity level is
549
+ preserved). (default: *None*).
550
+ * **verbose**: If *True*, print some informations on the standard output
551
+ (The default is given by :data:`active_verbose`).
552
+
553
+ 2. The remaining **kw** items are passed directly to the "algo" footprint's
554
+ proxy in order to create the AlgoComponent object.
555
+
556
+ :return: an object that is a subtype of :class:`vortex.algo.components.AlgoComponent`
557
+ """
558
+
559
+ t = sessions.current()
560
+
561
+ # First, retrieve arguments of the toolbox command itself
562
+ loglevel = kw.pop('loglevel', None)
563
+ talkative = kw.pop('verbose', active_verbose)
564
+
565
+ with _tb_isolate(t, loglevel):
566
+
567
+ if talkative:
568
+ nicedump('Loading algo component with description:', **kw)
569
+
570
+ ok = proxy.component(**kw) # @UndefinedVariable
571
+ if ok and talkative:
572
+ print(t.line)
573
+ ok.quickview(nb=1, indent=0)
574
+
575
+ return ok
576
+
577
+
578
+ def diff(*args, **kw):
579
+ """Perform a diff with a resource with the same local name.
580
+
581
+ 1. The **kw** dictionary may contain keys that influence this function
582
+ behaviour (such attributes are popped from **kw** before going further):
583
+
584
+ * **fatal**: If *True*, a :class:`ValueError` exception will be raised
585
+ whenever the "diff" detects differences.
586
+ * **loglevel**: The logging facility verbosity level that will be used
587
+ during the :class:`~vortex.layout.dataflow.Section` creation process.
588
+ If *None*, nothing is done (i.e. the current verbosity level is
589
+ preserved). (default: *None*).
590
+ * **verbose**: If *True*, print some informations on the standard output
591
+ (The default is given by :data:`active_verbose`).
592
+
593
+ 2. The remaining **kw** items are passed directly to the :func:`rload`
594
+ function in order to create the resource's
595
+ :class:`~vortex.data.handlers.Handler` objects for the reference files.
596
+
597
+ 3. The reference files resource's :class:`~vortex.data.handlers.Handler` objects
598
+ are altered so that the reference files are stored in temporary Containers.
599
+
600
+ 4. The reference files are fetched.
601
+
602
+ 5. The diff between the containers described in the resource's description
603
+ and the reference files is computed.
604
+
605
+ :return: A list of *diff* results.
606
+ """
607
+
608
+ # First, retrieve arguments of the toolbox command itself
609
+ fatal = kw.pop('fatal', True)
610
+ loglevel = kw.pop('loglevel', None)
611
+ talkative = kw.pop('verbose', active_verbose)
612
+ batch = kw.pop('batch', active_batchinputs)
613
+
614
+ # Distinguish between section arguments, and resource loader arguments
615
+ opts, kwclean = stripargs_section(**kw)
616
+
617
+ # Show the actual set of arguments
618
+ if talkative:
619
+ nicedump('Discard section options', **opts)
620
+ nicedump('Resource handler description', **kwclean)
621
+
622
+ # Fast exit in case of undefined value
623
+ rlok = list()
624
+ none_skip = {k for k, v in kwclean.items()
625
+ if v is None and k in ('experiment', 'namespace')}
626
+ if none_skip:
627
+ logger.warning('Skip diff because of undefined argument(s)')
628
+ return rlok
629
+
630
+ t = sessions.current()
631
+
632
+ # Swich off autorecording of the current context + deal with loggging
633
+ with _tb_isolate(t, loglevel):
634
+
635
+ # Do not track the reference files
636
+ kwclean['storetrack'] = False
637
+
638
+ rhandlers = rload(*args, **kwclean)
639
+ sections = list()
640
+ earlyget_id = list()
641
+ source_container = list()
642
+ lazzy_container = list()
643
+
644
+ if batch:
645
+ # Early get
646
+ print(t.line)
647
+ for ir, rhandler in enumerate(rhandlers):
648
+ source_container.append(rhandler.container)
649
+ # Create a new container to hold the reference file
650
+ lazzy_container.append(footprints.proxy.container(shouldfly=True,
651
+ actualfmt=rhandler.container.actualfmt))
652
+ # Swapp the original container with the lazzy one
653
+ rhandler.container = lazzy_container[-1]
654
+ # Create a new section
655
+ sec = Section(rh=rhandler, kind=ixo.INPUT, intent=intent.IN, fatal=False)
656
+ sections.append(sec)
657
+ # Early-get
658
+ if rhandler.complete:
659
+ earlyget_id.append(sec.earlyget())
660
+ else:
661
+ earlyget_id.append(None)
662
+ # Finalising
663
+ if any([r_id and r_id is not True for r_id in earlyget_id]):
664
+ t.sh.highlight('Finalising Early-gets')
665
+ t.context.delayedactions_hub.finalise(* [r_id for r_id in earlyget_id
666
+ if r_id and r_id is not True])
667
+
668
+ for ir, rhandler in enumerate(rhandlers):
669
+ if talkative:
670
+ print(t.line)
671
+ rhandler.quickview(nb=ir + 1, indent=0)
672
+ print(t.line)
673
+ if not rhandler.complete:
674
+ logger.error('Incomplete Resource Handler for diff [%s]', rhandler)
675
+ if fatal:
676
+ raise ValueError('Incomplete Resource Handler for diff')
677
+ else:
678
+ rlok.append(False)
679
+ continue
680
+
681
+ # Get the reference file through a Section so that intent + fatal
682
+ # is properly dealt with... The section is discarded afterwards.
683
+ if batch:
684
+ rc = sections[ir].finaliseget()
685
+ else:
686
+ rc = sections[ir].get()
687
+ if not rc:
688
+ try:
689
+ logger.error('Cannot get the reference resource: %s',
690
+ rhandler.locate())
691
+ except Exception:
692
+ logger.error('Cannot get the reference resource: ???')
693
+ if fatal:
694
+ raise ValueError('Cannot get the reference resource')
695
+ else:
696
+ logger.info('The reference file is stored under: %s',
697
+ rhandler.container.localpath())
698
+
699
+ # What are the differences ?
700
+ if rc:
701
+ # priority is given to the diff implemented in the DataContent
702
+ if rhandler.resource.clscontents.is_diffable():
703
+ source_contents = rhandler.resource.contents_handler(
704
+ datafmt=source_container[ir].actualfmt)
705
+ source_contents.slurp(source_container[ir])
706
+ ref_contents = rhandler.contents
707
+ rc = source_contents.diff(ref_contents)
708
+ else:
709
+ rc = t.sh.diff(source_container[ir].localpath(),
710
+ rhandler.container.localpath(),
711
+ fmt=rhandler.container.actualfmt)
712
+
713
+ # Delete the reference file
714
+ lazzy_container[ir].clear()
715
+
716
+ # Now proceed with the result
717
+ logger.info('Diff return %s', str(rc))
718
+ t.context.diff_history.append_record(rc, source_container[ir], rhandler)
719
+ try:
720
+ logger.info('Diff result %s', str(rc.result))
721
+ except AttributeError:
722
+ pass
723
+ if not rc:
724
+ try:
725
+ logger.warning('Some diff occurred with %s', rhandler.locate())
726
+ except Exception:
727
+ logger.warning('Some diff occurred with ???')
728
+ try:
729
+ rc.result.differences()
730
+ except Exception:
731
+ pass
732
+ if fatal:
733
+ logger.critical('Difference in resource comparison is fatal')
734
+ raise ValueError('Fatal diff')
735
+ if t.sh.trace:
736
+ print()
737
+ rlok.append(rc)
738
+
739
+ return rlok
740
+
741
+
742
+ def magic(localpath, **kw):
743
+ """
744
+ Return a minimal resource handler build with an unknown resource,
745
+ a file container and an anonymous provider described with its URL.
746
+ """
747
+ kw.update(
748
+ unknown=True,
749
+ magic='magic://localhost/' + localpath,
750
+ filename=localpath,
751
+ )
752
+ rhmagic = rh(**kw)
753
+ rhmagic.get()
754
+ return rhmagic
755
+
756
+
757
+ def archive_refill(*args, **kw):
758
+ """
759
+ Get a ressource in cache and upload it into the archive.
760
+
761
+ This will only have effect when working on a multistore.
762
+
763
+ The **kw** items are passed directly to the :func:`rload` function in
764
+ order to create the resource's :class:`~vortex.data.handlers.Handler`.
765
+ No "container" description is needed. One will be created by default.
766
+
767
+ :return: A list of :class:`vortex.data.handlers.Handler` objects.
768
+ """
769
+
770
+ t = sessions.current()
771
+
772
+ # First, retrieve arguments of the toolbox command itself
773
+ loglevel = kw.pop('loglevel', None)
774
+ talkative = kw.pop('verbose', active_verbose)
775
+
776
+ with _tb_isolate(t, loglevel):
777
+
778
+ # Distinguish between section arguments, and resource loader arguments
779
+ opts, kwclean = stripargs_section(**kw)
780
+ fatal = opts.get('fatal', True)
781
+
782
+ # Print the user inputs
783
+ if talkative:
784
+ nicedump('Archive Refill Ressource+Provider description', **kwclean)
785
+
786
+ # Create the resource handlers
787
+ kwclean['container'] = footprints.proxy.container(uuid4fly=True,
788
+ uuid4flydir="archive_refills")
789
+ rl = rload(*args, **kwclean)
790
+
791
+ @contextmanager
792
+ def _fatal_wrap(action):
793
+ """Handle errors during the calls to get or put."""
794
+ wrap_rc = dict(rc=True)
795
+ try:
796
+ yield wrap_rc
797
+ except Exception as e:
798
+ logger.error('Something wrong (action %s): %s. %s',
799
+ action, str(e), traceback.format_exc())
800
+ wrap_rc['rc'] = False
801
+ wrap_rc['exc'] = e
802
+ if fatal and not wrap_rc['rc']:
803
+ logger.critical('Fatal error with action %s.', action)
804
+ raise RuntimeError('Could not {:s} resource: {!s}'.format(action,
805
+ wrap_rc['rc']))
806
+
807
+ with t.sh.ftppool():
808
+ for ir, rhandler in enumerate(rl):
809
+ if talkative:
810
+ t.sh.subtitle('Resource no {:02d}/{:02d}'.format(ir + 1, len(rl)))
811
+ rhandler.quickview(nb=ir + 1, indent=0)
812
+ if not (rhandler.store.use_cache() and rhandler.store.use_archive()):
813
+ logger.info('The requested store does not have both the cache and archive capabilities. ' +
814
+ 'Skipping this ressource handler.')
815
+ continue
816
+ with _fatal_wrap('get') as get_status:
817
+ get_status['rc'] = rhandler.get(incache=True, intent=intent.IN,
818
+ fmt=rhandler.resource.nativefmt)
819
+ put_status = dict(rc=False)
820
+ if get_status['rc']:
821
+ with _fatal_wrap('put') as put_status:
822
+ put_status['rc'] = rhandler.put(inarchive=True,
823
+ fmt=rhandler.resource.nativefmt)
824
+ rhandler.container.clear()
825
+ if talkative:
826
+ t.sh.highlight('Result from get: [{!s}], from put: [{!s}]'
827
+ .format(get_status['rc'], put_status['rc']))
828
+
829
+ return rl
830
+
831
+
832
+ def stack_archive_refill(*args, **kw):
833
+ """Get a stack ressource in cache and upload it into the archive.
834
+
835
+ This will only have effect when working on a multistore.
836
+
837
+ The **kw** items are passed directly to the :func:`rload` function in
838
+ order to create the resource's :class:`~vortex.data.handlers.Handler`.
839
+
840
+ * No "container" description is needed. One will be created by default.
841
+ * The **block** attribute will be set automaticaly
842
+
843
+ :return: A list of :class:`vortex.data.handlers.Handler` objects.
844
+ """
845
+ kw['block'] = 'stacks'
846
+ return archive_refill(*args, **kw)
847
+
848
+
849
+ def namespaces(**kw):
850
+ """
851
+ Some kind of interactive help to find out quickly which namespaces are in
852
+ used. By default tracks ``stores`` and ``providers`` but one could give an
853
+ ``only`` argument.
854
+ """
855
+ rematch = re.compile('|'.join(kw.get('match', '.').split(',')),
856
+ re.IGNORECASE)
857
+ if 'only' in kw:
858
+ usedcat = kw['only'].split(',')
859
+ else:
860
+ usedcat = ('provider', 'store')
861
+ nameseen = dict()
862
+ for cat in [footprints.collectors.get(tag=x) for x in usedcat]:
863
+ for cls in cat():
864
+ fp = cls.footprint_retrieve().attr
865
+ netattr = fp.get('namespace', None)
866
+ if not netattr:
867
+ netattr = fp.get('netloc', None)
868
+ if netattr and 'values' in netattr:
869
+ for netname in filter(lambda x: rematch.search(x), netattr['values']):
870
+ if netname not in nameseen:
871
+ nameseen[netname] = list()
872
+ nameseen[netname].append(cls.fullname())
873
+ return nameseen
874
+
875
+
876
+ def print_namespaces(**kw):
877
+ """Formatted print of current namespaces."""
878
+ prefix = kw.pop('prefix', '+ ')
879
+ nd = namespaces(**kw)
880
+ justify = max([len(x) for x in nd.keys()])
881
+ linesep = ",\n" + ' ' * (justify + len(prefix) + 2)
882
+ for k, v in sorted(nd.items()):
883
+ nice_v = linesep.join(v) if len(v) > 1 else v[0]
884
+ print(prefix + k.ljust(justify), '[' + nice_v + ']')
885
+
886
+
887
+ def clear_promises(clear=None, netloc='promise.cache.fr', scheme='vortex',
888
+ storeoptions=None):
889
+ """Remove all promises that have been made in the current session.
890
+
891
+ :param netloc: Netloc of the promise's cache store to clean up
892
+ :param scheme: Scheme of the promise's cache store to clean up
893
+ :param storeoptions: Option dictionary passed to the store (may be None)
894
+ """
895
+ if clear is None:
896
+ clear = active_clear
897
+ if clear:
898
+ t = sessions.current()
899
+ myctx = t.context
900
+ for ctx in t.subcontexts:
901
+ ctx.activate()
902
+ ctx.clear_promises(netloc, scheme, storeoptions)
903
+ # Switch back to the previous context
904
+ myctx.activate()
905
+
906
+
907
+ def rescue(*files, **opts):
908
+ """Action to be undertaken when things really went bad."""
909
+
910
+ t = sessions.current()
911
+ sh = t.sh
912
+ env = t.env
913
+
914
+ # Summarise diffs...
915
+ if len(t.context.diff_history):
916
+ sh.header('Summary of automatic toolbox diffs')
917
+ t.context.diff_history.show()
918
+
919
+ # Force clearing of all promises
920
+ clear_promises(clear=True)
921
+
922
+ sh.header('Rescuing current dir')
923
+ sh.dir(output=False, fatal=False)
924
+
925
+ logger.info('Rescue files %s', files)
926
+
927
+ if 'VORTEX_RESCUE' in env and env.false('VORTEX_RESCUE'):
928
+ logger.warning('Skip rescue <VORTEX_RESCUE=%s>', env.VORTEX_RESCUE)
929
+ return False
930
+
931
+ if files:
932
+ items = list(files)
933
+ else:
934
+ items = sh.glob('*')
935
+
936
+ rfilter = opts.get('filter', env.VORTEX_RESCUE_FILTER)
937
+ if rfilter is not None:
938
+ logger.warning('Rescue filter <%s>', rfilter)
939
+ select = '|'.join(re.split(r'[,;:]+', rfilter))
940
+ items = [x for x in items if re.search(select, x, re.IGNORECASE)]
941
+ logger.info('Rescue filter [%s]', select)
942
+
943
+ rdiscard = opts.get('discard', env.VORTEX_RESCUE_DISCARD)
944
+ if rdiscard is not None:
945
+ logger.warning('Rescue discard <%s>', rdiscard)
946
+ select = '|'.join(re.split(r'[,;:]+', rdiscard))
947
+ items = [x for x in items if not re.search(select, x, re.IGNORECASE)]
948
+ logger.info('Rescue discard [%s]', select)
949
+
950
+ if items:
951
+
952
+ bkupdir = opts.get('bkupdir', env.VORTEX_RESCUE_PATH)
953
+
954
+ if bkupdir is None:
955
+ logger.error('No rescue directory defined.')
956
+ else:
957
+ logger.info('Backup directory defined by user < %s >', bkupdir)
958
+ items.sort()
959
+ logger.info('Rescue items %s', str(items))
960
+ sh.mkdir(bkupdir)
961
+ mkmove = False
962
+ st1 = sh.stat(sh.getcwd())
963
+ st2 = sh.stat(bkupdir)
964
+ if st1 and st2 and st1.st_dev == st2.st_dev:
965
+ mkmove = True
966
+ if mkmove:
967
+ thisrescue = sh.mv
968
+ else:
969
+ thisrescue = sh.cp
970
+ for ritem in items:
971
+ rtarget = sh.path.join(bkupdir, ritem)
972
+ if sh.path.exists(ritem) and not sh.path.islink(ritem):
973
+ if sh.path.isfile(ritem):
974
+ sh.rm(rtarget)
975
+ thisrescue(ritem, rtarget)
976
+ else:
977
+ thisrescue(ritem, rtarget)
978
+
979
+ else:
980
+ logger.warning('No item to rescue.')
981
+
982
+ return bool(items)