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