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