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,835 @@
1
+ """
2
+ Abstract and generic classes for any "Container". "Container" objects
3
+ describe where to store the data localy.
4
+
5
+ Roughly there are :class:`Virtual` and concrete containers. With :class:`Virtual`
6
+ containers such as :class:`InCore` or :class:`MayFly`, the data may lie in
7
+ memory. On the oposite, with concrete containers, data lie on disk within the
8
+ working directory.
9
+
10
+ The :class:`SingleFile` container is by far the most commonly used.
11
+ """
12
+
13
+ import contextlib
14
+ import os
15
+ import re
16
+ import tempfile
17
+ import uuid
18
+
19
+ from bronx.fancies import loggers
20
+ from bronx.syntax.decorators import secure_getattr
21
+ import footprints
22
+
23
+ from vortex import sessions
24
+ from vortex.syntax.stdattrs import a_actualfmt
25
+
26
+ #: Automatic export
27
+ __all__ = ["Container"]
28
+
29
+ logger = loggers.getLogger(__name__)
30
+
31
+ CONTAINER_INCORELIMIT = 1048576 * 8
32
+ CONTAINER_MAXREADSIZE = 1048576 * 200
33
+
34
+
35
+ class DataSizeTooBig(IOError):
36
+ """Exception raised when totasize is over the container MaxReadSize limit."""
37
+
38
+ pass
39
+
40
+
41
+ class Container(footprints.FootprintBase):
42
+ """Abstract class for any Container."""
43
+
44
+ _abstract = True
45
+ _collector = ("container",)
46
+ _footprint = dict(
47
+ info="Abstract Container",
48
+ attr=dict(
49
+ actualfmt=a_actualfmt,
50
+ maxreadsize=dict(
51
+ info="The maximum amount of data that can be read (in bytes).",
52
+ type=int,
53
+ optional=True,
54
+ default=CONTAINER_MAXREADSIZE,
55
+ doc_visibility=footprints.doc.visibility.GURU,
56
+ ),
57
+ mode=dict(
58
+ info="The file mode used to open the container.",
59
+ optional=True,
60
+ values=[
61
+ "a",
62
+ "a+",
63
+ "ab",
64
+ "a+b",
65
+ "ab+",
66
+ "r",
67
+ "r+",
68
+ "rb",
69
+ "rb+",
70
+ "r+b",
71
+ "w",
72
+ "w+",
73
+ "wb",
74
+ "w+b",
75
+ "wb+",
76
+ ],
77
+ remap={"a+b": "ab+", "r+b": "rb+", "w+b": "wb+"},
78
+ doc_visibility=footprints.doc.visibility.ADVANCED,
79
+ ),
80
+ encoding=dict(
81
+ info="When opened in text mode, the encoding that will be used.",
82
+ optional=True,
83
+ doc_visibility=footprints.doc.visibility.ADVANCED,
84
+ ),
85
+ ),
86
+ )
87
+
88
+ _DEFAULTMODE = "rb"
89
+
90
+ @property
91
+ def realkind(self):
92
+ return "container"
93
+
94
+ def __init__(self, *args, **kw):
95
+ """Preset to None or False hidden attributes ``iod``, ``iomode`` and ``filled``."""
96
+ logger.debug("Container %s init", self.__class__)
97
+ super().__init__(*args, **kw)
98
+ self._iod = None
99
+ self._iomode = None
100
+ self._ioencoding = None
101
+ self._acmode = None
102
+ self._acencoding = None
103
+ self._pref_byte = None
104
+ self._pref_encoding = None
105
+ self._pref_write = None
106
+ self._filled = False
107
+
108
+ def __getstate__(self):
109
+ d = super().__getstate__()
110
+ # Start from a clean slate regarding IO descriptors
111
+ d["_iod"] = None
112
+ d["_acmode"] = None
113
+ d["_acencoding"] = None
114
+ return d
115
+
116
+ @secure_getattr
117
+ def __getattr__(self, key):
118
+ """Gateway to undefined method or attributes if present in internal io descriptor."""
119
+ # It avoids to call self.iodesc() when footprint_export is called...
120
+ if key.startswith("footprint_export") or key in (
121
+ "export_dict",
122
+ "as_dump",
123
+ "as_dict",
124
+ "_iod",
125
+ ):
126
+ raise AttributeError("Could not get an io descriptor")
127
+ # Normal processing
128
+ iod = self.iodesc()
129
+ if iod:
130
+ return getattr(iod, key)
131
+ else:
132
+ raise AttributeError("Could not get an io descriptor")
133
+
134
+ @contextlib.contextmanager
135
+ def iod_context(self):
136
+ """Ensure that any opened IO descriptor is closed after use."""
137
+ if self.is_virtual():
138
+ # With virtual container, this is not a good idea since closing
139
+ # the io descriptor might result in data losses
140
+ yield
141
+ else:
142
+ try:
143
+ yield
144
+ finally:
145
+ self.close()
146
+
147
+ def localpath(self):
148
+ """Abstract method to be overwritten."""
149
+ raise NotImplementedError
150
+
151
+ def iodesc(self, mode=None, encoding=None):
152
+ """Returns the file object descriptor."""
153
+ mode1, encoding1 = self._get_mode(mode, encoding)
154
+ if not (
155
+ self._iod
156
+ and not self._iod.closed
157
+ and mode1 == self._acmode
158
+ and encoding1 == self._acencoding
159
+ ):
160
+ if self._iod and not self._iod.closed:
161
+ self.close()
162
+ mode1, encoding1 = self._get_mode(mode, encoding)
163
+ self._iod = self._new_iodesc(mode1, encoding1)
164
+ self._acmode = mode1
165
+ self._acencoding = encoding1
166
+ return self._iod
167
+
168
+ def _new_iodesc(self, mode, encoding):
169
+ """Returns a new file object descriptor."""
170
+ raise NotImplementedError
171
+
172
+ def iotarget(self):
173
+ """Abstract method to be overwritten."""
174
+ raise NotImplementedError
175
+
176
+ @property
177
+ def filled(self):
178
+ """
179
+ Returns a boolean value according to the fact that
180
+ the container has been correctly filled with data.
181
+ """
182
+ return self._filled
183
+
184
+ def updfill(self, getrc=None):
185
+ """Change current filled status according to return code of the get command."""
186
+ if getrc is not None and getrc:
187
+ self._filled = True
188
+
189
+ def clear(self, fmt=None):
190
+ """Delete the container content."""
191
+ self.close()
192
+ self._filled = False
193
+ return True
194
+
195
+ @property
196
+ def totalsize(self):
197
+ """Returns the complete size of the container."""
198
+ iod = self.iodesc()
199
+ pos = iod.tell()
200
+ iod.seek(0, 2)
201
+ ts = iod.tell()
202
+ iod.seek(pos)
203
+ return ts
204
+
205
+ def rewind(self):
206
+ """Performs the rewind of the current io descriptor of the container."""
207
+ self.seek(0)
208
+
209
+ def endoc(self):
210
+ """Go to the end of the container."""
211
+ self.seek(0, 2)
212
+
213
+ def read(self, n=-1, mode=None, encoding=None):
214
+ """Read in one jump all the data as long as the data is not too big."""
215
+ iod = self.iodesc(mode, encoding)
216
+ if self.totalsize < self.maxreadsize or (0 < n < self.maxreadsize):
217
+ return iod.read(n)
218
+ else:
219
+ raise DataSizeTooBig(
220
+ "Input is more than {:d} bytes.".format(self.maxreadsize)
221
+ )
222
+
223
+ def dataread(self, mode=None, encoding=None):
224
+ """
225
+ Reads the next data line or unit of the container.
226
+ Returns a tuple with this line and a boolean
227
+ to tell whether the end of container is reached.
228
+ """
229
+ with self.preferred_decoding(byte=False):
230
+ iod = self.iodesc(mode, encoding)
231
+ line = iod.readline()
232
+ return (line, bool(iod.tell() == self.totalsize))
233
+
234
+ def head(self, nlines, mode=None, encoding=None):
235
+ """Read in one *nlines* of the data as long as the data is not too big."""
236
+ with self.preferred_decoding(byte=False):
237
+ iod = self.iodesc(mode, encoding)
238
+ self.rewind()
239
+ nread = 0
240
+ lines = list()
241
+ lsize = 0
242
+ while nread < nlines:
243
+ lines.append(iod.readline())
244
+ lsize += len(lines[-1])
245
+ if lsize > self.maxreadsize:
246
+ raise DataSizeTooBig(
247
+ "Input is more than {:d} bytes.".format(
248
+ self.maxreadsize
249
+ )
250
+ )
251
+ nread += 1
252
+ return lines
253
+
254
+ def readlines(self, mode=None, encoding=None):
255
+ """Read in one jump all the data as a sequence of lines as long as the data is not too big."""
256
+ with self.preferred_decoding(byte=False):
257
+ iod = self.iodesc(mode, encoding)
258
+ if self.totalsize < self.maxreadsize:
259
+ self.rewind()
260
+ return iod.readlines()
261
+ else:
262
+ raise DataSizeTooBig(
263
+ "Input is more than {:d} bytes.".format(self.maxreadsize)
264
+ )
265
+
266
+ def __iter__(self):
267
+ with self.preferred_decoding(byte=False):
268
+ iod = self.iodesc()
269
+ iod.seek(0)
270
+ yield from iod
271
+
272
+ def close(self):
273
+ """Close the logical io descriptor."""
274
+ if self._iod:
275
+ self._iod.close()
276
+ self._iod = None
277
+ self._iomode = None
278
+ self._ioencoding = None
279
+ self._acmode = None
280
+ self._acencoding = None
281
+
282
+ @property
283
+ def defaultmode(self):
284
+ return self._iomode or self.mode
285
+
286
+ @property
287
+ def defaultencoding(self):
288
+ return self._ioencoding or self.encoding
289
+
290
+ @property
291
+ def actualmode(self):
292
+ return self._acmode or self._get_mode(None, None)[0]
293
+
294
+ @property
295
+ def actualencoding(self):
296
+ return self._acencoding or self._get_mode(None, None)[1]
297
+
298
+ @staticmethod
299
+ def _set_amode(actualmode):
300
+ """Upgrade the ``actualmode`` to a append-compatible mode."""
301
+ am = re.sub("[rw]", "a", actualmode)
302
+ am = am.replace("+", "")
303
+ return am + "+"
304
+
305
+ @staticmethod
306
+ def _set_wmode(actualmode):
307
+ """Upgrade the ``actualmode`` to a write-compatible mode."""
308
+ wm = re.sub("r", "w", actualmode)
309
+ wm = wm.replace("+", "")
310
+ return wm + "+"
311
+
312
+ @staticmethod
313
+ def _set_bmode(actualmode):
314
+ """Upgrade the ``actualmode`` to byte mode."""
315
+ if "b" not in actualmode:
316
+ wm = re.sub(r"([arw])", r"\1b", actualmode)
317
+ return wm
318
+ else:
319
+ return actualmode
320
+
321
+ @staticmethod
322
+ def _set_tmode(actualmode):
323
+ """Upgrade the ``actualmode`` to a text-mode."""
324
+ wm = actualmode.replace("b", "")
325
+ return wm
326
+
327
+ @contextlib.contextmanager
328
+ def preferred_decoding(self, byte=True, encoding=None):
329
+ assert byte in [True, False]
330
+ prev_byte = self._pref_byte
331
+ self._pref_byte = byte
332
+ if encoding is not None:
333
+ prev_enc = self._pref_encoding
334
+ self._pref_encoding = encoding
335
+ yield
336
+ self._pref_byte = prev_byte
337
+ if encoding is not None:
338
+ self._pref_encoding = prev_enc
339
+
340
+ @contextlib.contextmanager
341
+ def preferred_write(self, append=False):
342
+ assert append in [True, False]
343
+ prev_write = self._pref_write
344
+ self._pref_write = append
345
+ yield
346
+ self._pref_write = prev_write
347
+
348
+ def _get_mode(self, mode, encoding):
349
+ # Find out a mode
350
+ if mode:
351
+ tmode = mode
352
+ self._iomode = mode
353
+ else:
354
+ tmode = self.defaultmode
355
+ if tmode is None:
356
+ tmode = self._acmode or self._DEFAULTMODE
357
+ if self._pref_write is True:
358
+ tmode = self._set_amode(tmode)
359
+ elif self._pref_write is False:
360
+ tmode = self._set_wmode(tmode)
361
+ if self._pref_byte is True:
362
+ tmode = self._set_bmode(tmode)
363
+ elif self._pref_byte is False:
364
+ tmode = self._set_tmode(tmode)
365
+ # Find out the encoding
366
+ if encoding:
367
+ tencoding = encoding
368
+ self._ioencoding = encoding
369
+ else:
370
+ tencoding = self.defaultencoding
371
+ if tencoding is None:
372
+ tencoding = self._acencoding
373
+ if self._pref_encoding is not None:
374
+ tencoding = self._pref_encoding
375
+ return tmode, tencoding
376
+
377
+ def write(self, data, mode=None, encoding=None):
378
+ """Write the data content in container."""
379
+ with self.preferred_write():
380
+ iod = self.iodesc(mode, encoding)
381
+ iod.write(data)
382
+ self._filled = True
383
+
384
+ def append(self, data, mode=None, encoding=None):
385
+ """Write the data content at the end of the container."""
386
+ with self.preferred_write(append=True):
387
+ iod = self.iodesc(mode, encoding)
388
+ self.endoc()
389
+ iod.write(data)
390
+ self._filled = True
391
+
392
+ def cat(self, mode=None, encoding=None):
393
+ """Perform a trivial cat of the container."""
394
+ if self.filled:
395
+ with self.preferred_decoding(byte=False):
396
+ iod = self.iodesc(mode, encoding)
397
+ pos = iod.tell()
398
+ iod.seek(0)
399
+ for xchunk in iod:
400
+ print(xchunk.rstrip("\n"))
401
+ iod.seek(pos)
402
+
403
+ def is_virtual(self):
404
+ """Check if the current container has some physical reality or not."""
405
+ return False
406
+
407
+ def __del__(self):
408
+ self.close()
409
+
410
+
411
+ class Virtual(Container):
412
+ _abstract = True
413
+ _footprint = dict(
414
+ info="Abstract Virtual Container",
415
+ attr=dict(
416
+ prefix=dict(
417
+ info="Prefix used if a temporary file needs to be written.",
418
+ optional=True,
419
+ default="vortex.tmp.",
420
+ doc_visibility=footprints.doc.visibility.GURU,
421
+ )
422
+ ),
423
+ )
424
+
425
+ _DEFAULTMODE = "wb+"
426
+
427
+ def is_virtual(self):
428
+ """
429
+ Check if the current container has some physical reality or not.
430
+ In that case, the answer is ``True``!
431
+ """
432
+ return True
433
+
434
+ def exists(self):
435
+ """In case of a virtual container, always true."""
436
+ return self.filled
437
+
438
+ def iotarget(self):
439
+ """Virtual container's io target is an io descriptor."""
440
+ return self.iodesc()
441
+
442
+
443
+ class InCore(Virtual):
444
+ _footprint = dict(
445
+ info="Incore container (data are kept in memory as long as possible).",
446
+ attr=dict(
447
+ incore=dict(
448
+ info="Activate the incore container.",
449
+ type=bool,
450
+ values=[
451
+ True,
452
+ ],
453
+ alias=("mem", "memory"),
454
+ doc_zorder=90,
455
+ ),
456
+ incorelimit=dict(
457
+ info="If this limit (in bytes) is exceeded, data are flushed to file.",
458
+ type=int,
459
+ optional=True,
460
+ default=CONTAINER_INCORELIMIT,
461
+ alias=("memlimit", "spooledlimit", "maxsize"),
462
+ doc_visibility=footprints.doc.visibility.ADVANCED,
463
+ ),
464
+ ),
465
+ fastkeys={"incore"},
466
+ )
467
+
468
+ def __init__(self, *args, **kw):
469
+ logger.debug("InCore container init %s", self.__class__)
470
+ kw.setdefault("incore", True)
471
+ super().__init__(*args, **kw)
472
+ self._tempo = False
473
+
474
+ def actualpath(self):
475
+ """Returns path information, if any, of the spooled object."""
476
+ if self._iod:
477
+ if self._tempo or self._iod._rolled:
478
+ actualfile = self._iod.name
479
+ else:
480
+ actualfile = "MemoryResident"
481
+ else:
482
+ actualfile = "NotSpooled"
483
+ return actualfile
484
+
485
+ def _str_more(self):
486
+ """Additional information to print representation."""
487
+ return 'incorelimit={:d} tmpfile="{:s}"'.format(
488
+ self.incorelimit, self.actualpath()
489
+ )
490
+
491
+ def _new_iodesc(self, mode, encoding):
492
+ """Returns an active (opened) spooled file descriptor in binary read mode by default."""
493
+ self.close()
494
+ if self._tempo:
495
+ iod = tempfile.NamedTemporaryFile(
496
+ mode=mode,
497
+ prefix=self.prefix,
498
+ dir=os.getcwd(),
499
+ delete=True,
500
+ encoding=encoding,
501
+ )
502
+ else:
503
+ iod = tempfile.SpooledTemporaryFile(
504
+ mode=mode,
505
+ prefix=self.prefix,
506
+ dir=os.getcwd(),
507
+ max_size=self.incorelimit,
508
+ encoding=encoding,
509
+ )
510
+ return iod
511
+
512
+ @property
513
+ def temporized(self):
514
+ return self._tempo
515
+
516
+ def temporize(self):
517
+ """Migrate any memory data to a :class:`NamedTemporaryFile`."""
518
+ if not self.temporized:
519
+ iomem = self.iodesc()
520
+ self.rewind()
521
+ self._tempo = True
522
+ self._iod = tempfile.NamedTemporaryFile(
523
+ mode=self._acmode,
524
+ prefix=self.prefix,
525
+ dir=os.getcwd(),
526
+ delete=True,
527
+ encoding=self._acencoding,
528
+ )
529
+ for data in iomem:
530
+ self._iod.write(data)
531
+ self._iod.flush()
532
+ iomem.close()
533
+
534
+ def unroll(self):
535
+ """Replace rolled data to memory (when possible)."""
536
+ if self.temporized and self.totalsize < self.incorelimit:
537
+ iotmp = self.iodesc()
538
+ self.rewind()
539
+ self._tempo = False
540
+ self._iod = tempfile.SpooledTemporaryFile(
541
+ mode=self._acmode,
542
+ prefix=self.prefix,
543
+ dir=os.getcwd(),
544
+ max_size=self.incorelimit,
545
+ encoding=self._acencoding,
546
+ )
547
+ for data in iotmp:
548
+ self._iod.write(data)
549
+ iotmp.close()
550
+
551
+ def localpath(self):
552
+ """
553
+ Roll the current memory file in a :class:`NamedTemporaryFile`
554
+ and returns associated file name.
555
+ """
556
+ self.temporize()
557
+ iod = self.iodesc()
558
+ try:
559
+ return iod.name
560
+ except Exception:
561
+ logger.warning(
562
+ "Could not get local temporary rolled file pathname %s", self
563
+ )
564
+ raise
565
+
566
+
567
+ class MayFly(Virtual):
568
+ _footprint = dict(
569
+ info="MayFly container (a temporary file is created only when needed).",
570
+ attr=dict(
571
+ mayfly=dict(
572
+ info="Activate the mayfly container.",
573
+ type=bool,
574
+ values=[
575
+ True,
576
+ ],
577
+ alias=("tempo",),
578
+ doc_zorder=90,
579
+ ),
580
+ delete=dict(
581
+ info="Delete the file when the container object is destroyed.",
582
+ type=bool,
583
+ optional=True,
584
+ default=True,
585
+ doc_visibility=footprints.doc.visibility.ADVANCED,
586
+ ),
587
+ ),
588
+ fastkeys={"mayfly"},
589
+ )
590
+
591
+ def __init__(self, *args, **kw):
592
+ logger.debug("MayFly container init %s", self.__class__)
593
+ kw.setdefault("mayfly", True)
594
+ super().__init__(*args, **kw)
595
+
596
+ def actualpath(self):
597
+ """Returns path information, if any, of the spooled object."""
598
+ if self._iod:
599
+ return self._iod.name
600
+ else:
601
+ return "NotDefined"
602
+
603
+ def _str_more(self):
604
+ """Additional information to internal representation."""
605
+
606
+ return 'delete={!s} tmpfile="{:s}"'.format(
607
+ self.delete, self.actualpath()
608
+ )
609
+
610
+ def _new_iodesc(self, mode, encoding):
611
+ """Returns an active (opened) temporary file descriptor in binary read mode by default."""
612
+ self.close()
613
+ return tempfile.NamedTemporaryFile(
614
+ mode=mode,
615
+ prefix=self.prefix,
616
+ dir=os.getcwd(),
617
+ delete=self.delete,
618
+ encoding=encoding,
619
+ )
620
+
621
+ def localpath(self):
622
+ """
623
+ Returns the actual name of the temporary file object
624
+ which is created if not yet defined.
625
+ """
626
+ iod = self.iodesc()
627
+ try:
628
+ return iod.name
629
+ except Exception:
630
+ logger.warning(
631
+ "Could not get local temporary file pathname %s", self
632
+ )
633
+ raise
634
+
635
+
636
+ class _SingleFileStyle(Container):
637
+ """
638
+ Template for any file container. Data is stored as a file object.
639
+ """
640
+
641
+ _abstract = (True,)
642
+ _footprint = dict(
643
+ info="File container",
644
+ attr=dict(
645
+ cwdtied=dict(
646
+ info="If *filename* is a relative path, replace it by its absolute path.",
647
+ type=bool,
648
+ optional=True,
649
+ default=False,
650
+ doc_visibility=footprints.doc.visibility.ADVANCED,
651
+ ),
652
+ ),
653
+ )
654
+
655
+ def __init__(self, *args, **kw):
656
+ """Business as usual... but define actualpath according to ``cwdtied`` attribute."""
657
+ logger.debug("_SingleFileStyle container init %s", self.__class__)
658
+ super().__init__(*args, **kw)
659
+ if self.cwdtied:
660
+ self._actualpath = os.path.realpath(self.filename)
661
+ else:
662
+ self._actualpath = self.filename
663
+
664
+ def actualpath(self):
665
+ """Returns the actual pathname of the file object."""
666
+ return self._actualpath
667
+
668
+ @property
669
+ def abspath(self):
670
+ """Shortcut to realpath of the actualpath."""
671
+ return os.path.realpath(self.actualpath())
672
+
673
+ @property
674
+ def absdir(self):
675
+ """Shortcut to dirname of the abspath."""
676
+ return os.path.dirname(self.abspath)
677
+
678
+ @property
679
+ def dirname(self):
680
+ """Shortcut to dirname of the actualpath."""
681
+ return os.path.dirname(self.actualpath())
682
+
683
+ @property
684
+ def basename(self):
685
+ """Shortcut to basename of the abspath."""
686
+ return os.path.basename(self.abspath)
687
+
688
+ def _str_more(self):
689
+ """Additional information to print representation."""
690
+ return "path='{:s}'".format(self.actualpath())
691
+
692
+ def localpath(self):
693
+ """Returns the actual name of the file object."""
694
+ return self.actualpath()
695
+
696
+ def _new_iodesc(self, mode, encoding):
697
+ """Returns an active (opened) file descriptor in binary read mode by default."""
698
+ self.close()
699
+ currentpath = (
700
+ self._actualpath
701
+ if self.cwdtied
702
+ else os.path.realpath(self.filename)
703
+ )
704
+ return open(currentpath, mode, encoding=encoding)
705
+
706
+ def iotarget(self):
707
+ """File container's io target is a plain pathname."""
708
+ return self.localpath()
709
+
710
+ def clear(self, *kargs, **kw):
711
+ """Delete the container content (in this case the actual file)."""
712
+ rst = super().clear(*kargs, **kw)
713
+ # Physically delete the file if it exists
714
+ if self.exists():
715
+ sh = kw.pop("system", sessions.system())
716
+ rst = rst and sh.remove(self.localpath(), fmt=self.actualfmt)
717
+ return rst
718
+
719
+ def exists(self):
720
+ """Check the existence of the actual file."""
721
+ return os.path.exists(self.localpath())
722
+
723
+
724
+ class SingleFile(_SingleFileStyle):
725
+ """
726
+ Default file container. Data is stored as a file object.
727
+ """
728
+
729
+ _footprint = dict(
730
+ attr=dict(
731
+ filename=dict(
732
+ info="Path to the file where data are stored.",
733
+ alias=("filepath", "local"),
734
+ doc_zorder=50,
735
+ ),
736
+ ),
737
+ fastkeys={"filename"},
738
+ )
739
+
740
+
741
+ class UnnamedSingleFile(_SingleFileStyle):
742
+ """Unnamed file container. Data is stored as a file object.
743
+
744
+ The filename is chosen arbitrarily when the object is created.
745
+ """
746
+
747
+ _footprint = dict(
748
+ info="File container (a temporary filename is chosen at runtime)",
749
+ attr=dict(
750
+ shouldfly=dict(
751
+ info="Activate the UnnamedSingleFile container",
752
+ type=bool,
753
+ values=[
754
+ True,
755
+ ],
756
+ doc_zorder=90,
757
+ ),
758
+ cwdtied=dict(
759
+ default=True,
760
+ doc_visibility=footprints.doc.visibility.GURU,
761
+ ),
762
+ ),
763
+ fastkeys={"shouldfly"},
764
+ )
765
+
766
+ def __init__(self, *args, **kw):
767
+ logger.debug("UnnamedSingleFile container init %s", self.__class__)
768
+ self._auto_filename = None
769
+ kw.setdefault("shouldfly", True)
770
+ super().__init__(*args, **kw)
771
+
772
+ @property
773
+ def filename(self):
774
+ if self._auto_filename is None:
775
+ fh, fpath = tempfile.mkstemp(prefix="shouldfly-", dir=os.getcwd())
776
+ os.close(fh) # mkstemp opens the file but we do not really care...
777
+ self._auto_filename = os.path.basename(fpath)
778
+ return self._auto_filename
779
+
780
+ def __getstate__(self):
781
+ st = super().__getstate__()
782
+ st["_auto_filename"] = None
783
+ return st
784
+
785
+ def exists(self, empty=False):
786
+ """Check the existence of the actual file."""
787
+ return os.path.exists(self.localpath()) and (
788
+ empty or os.path.getsize(self.localpath())
789
+ )
790
+
791
+
792
+ class Uuid4UnamedSingleFile(_SingleFileStyle):
793
+ """Unamed file container created in a temporary diectory."""
794
+
795
+ _footprint = dict(
796
+ info="File container (a temporary filename is chosen at runtime)",
797
+ attr=dict(
798
+ uuid4fly=dict(
799
+ info="Activate the Uuid4UnnamedSingleFile container",
800
+ type=bool,
801
+ values=[
802
+ True,
803
+ ],
804
+ doc_zorder=90,
805
+ ),
806
+ uuid4flydir=dict(
807
+ info="The subdirectory where to create the unamed file",
808
+ optional=True,
809
+ default=".",
810
+ doc_zorder=90,
811
+ ),
812
+ ),
813
+ fastkeys={"uuid4fly"},
814
+ )
815
+
816
+ def __init__(self, *args, **kw):
817
+ logger.debug(
818
+ "UuidBasedUnamedSingleFile container init %s", self.__class__
819
+ )
820
+ self._auto_filename = None
821
+ kw.setdefault("uuid4fly", True)
822
+ super().__init__(*args, **kw)
823
+
824
+ @property
825
+ def filename(self):
826
+ if self._auto_filename is None:
827
+ self._auto_filename = os.path.join(
828
+ self.uuid4flydir, uuid.uuid4().hex
829
+ )
830
+ return self._auto_filename
831
+
832
+ def __getstate__(self):
833
+ st = super().__getstate__()
834
+ st["_auto_filename"] = None
835
+ return st