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.
- vortex/__init__.py +159 -0
- vortex/algo/__init__.py +13 -0
- vortex/algo/components.py +2462 -0
- vortex/algo/mpitools.py +1953 -0
- vortex/algo/mpitools_templates/__init__.py +1 -0
- vortex/algo/mpitools_templates/envelope_wrapper_default.tpl +27 -0
- vortex/algo/mpitools_templates/envelope_wrapper_mpiauto.tpl +29 -0
- vortex/algo/mpitools_templates/wrapstd_wrapper_default.tpl +18 -0
- vortex/algo/serversynctools.py +171 -0
- vortex/config.py +112 -0
- vortex/data/__init__.py +19 -0
- vortex/data/abstractstores.py +1510 -0
- vortex/data/containers.py +835 -0
- vortex/data/contents.py +622 -0
- vortex/data/executables.py +275 -0
- vortex/data/flow.py +119 -0
- vortex/data/geometries.ini +2689 -0
- vortex/data/geometries.py +799 -0
- vortex/data/handlers.py +1230 -0
- vortex/data/outflow.py +67 -0
- vortex/data/providers.py +487 -0
- vortex/data/resources.py +207 -0
- vortex/data/stores.py +1390 -0
- vortex/data/sync_templates/__init__.py +0 -0
- vortex/gloves.py +309 -0
- vortex/layout/__init__.py +20 -0
- vortex/layout/contexts.py +577 -0
- vortex/layout/dataflow.py +1220 -0
- vortex/layout/monitor.py +969 -0
- vortex/nwp/__init__.py +14 -0
- vortex/nwp/algo/__init__.py +21 -0
- vortex/nwp/algo/assim.py +537 -0
- vortex/nwp/algo/clim.py +1086 -0
- vortex/nwp/algo/coupling.py +831 -0
- vortex/nwp/algo/eda.py +840 -0
- vortex/nwp/algo/eps.py +785 -0
- vortex/nwp/algo/forecasts.py +886 -0
- vortex/nwp/algo/fpserver.py +1303 -0
- vortex/nwp/algo/ifsnaming.py +463 -0
- vortex/nwp/algo/ifsroot.py +404 -0
- vortex/nwp/algo/monitoring.py +263 -0
- vortex/nwp/algo/mpitools.py +694 -0
- vortex/nwp/algo/odbtools.py +1258 -0
- vortex/nwp/algo/oopsroot.py +916 -0
- vortex/nwp/algo/oopstests.py +220 -0
- vortex/nwp/algo/request.py +660 -0
- vortex/nwp/algo/stdpost.py +1641 -0
- vortex/nwp/data/__init__.py +30 -0
- vortex/nwp/data/assim.py +380 -0
- vortex/nwp/data/boundaries.py +314 -0
- vortex/nwp/data/climfiles.py +521 -0
- vortex/nwp/data/configfiles.py +153 -0
- vortex/nwp/data/consts.py +954 -0
- vortex/nwp/data/ctpini.py +149 -0
- vortex/nwp/data/diagnostics.py +209 -0
- vortex/nwp/data/eda.py +147 -0
- vortex/nwp/data/eps.py +432 -0
- vortex/nwp/data/executables.py +1045 -0
- vortex/nwp/data/fields.py +111 -0
- vortex/nwp/data/gridfiles.py +380 -0
- vortex/nwp/data/logs.py +584 -0
- vortex/nwp/data/modelstates.py +363 -0
- vortex/nwp/data/monitoring.py +193 -0
- vortex/nwp/data/namelists.py +696 -0
- vortex/nwp/data/obs.py +840 -0
- vortex/nwp/data/oopsexec.py +74 -0
- vortex/nwp/data/providers.py +207 -0
- vortex/nwp/data/query.py +206 -0
- vortex/nwp/data/stores.py +160 -0
- vortex/nwp/data/surfex.py +337 -0
- vortex/nwp/syntax/__init__.py +9 -0
- vortex/nwp/syntax/stdattrs.py +437 -0
- vortex/nwp/tools/__init__.py +10 -0
- vortex/nwp/tools/addons.py +40 -0
- vortex/nwp/tools/agt.py +67 -0
- vortex/nwp/tools/bdap.py +59 -0
- vortex/nwp/tools/bdcp.py +41 -0
- vortex/nwp/tools/bdm.py +24 -0
- vortex/nwp/tools/bdmp.py +54 -0
- vortex/nwp/tools/conftools.py +1661 -0
- vortex/nwp/tools/drhook.py +66 -0
- vortex/nwp/tools/grib.py +294 -0
- vortex/nwp/tools/gribdiff.py +104 -0
- vortex/nwp/tools/ifstools.py +203 -0
- vortex/nwp/tools/igastuff.py +273 -0
- vortex/nwp/tools/mars.py +68 -0
- vortex/nwp/tools/odb.py +657 -0
- vortex/nwp/tools/partitioning.py +258 -0
- vortex/nwp/tools/satrad.py +71 -0
- vortex/nwp/util/__init__.py +6 -0
- vortex/nwp/util/async.py +212 -0
- vortex/nwp/util/beacon.py +40 -0
- vortex/nwp/util/diffpygram.py +447 -0
- vortex/nwp/util/ens.py +279 -0
- vortex/nwp/util/hooks.py +139 -0
- vortex/nwp/util/taskdeco.py +85 -0
- vortex/nwp/util/usepygram.py +697 -0
- vortex/nwp/util/usetnt.py +101 -0
- vortex/proxy.py +6 -0
- vortex/sessions.py +374 -0
- vortex/syntax/__init__.py +9 -0
- vortex/syntax/stdattrs.py +867 -0
- vortex/syntax/stddeco.py +185 -0
- vortex/toolbox.py +1117 -0
- vortex/tools/__init__.py +20 -0
- vortex/tools/actions.py +523 -0
- vortex/tools/addons.py +316 -0
- vortex/tools/arm.py +96 -0
- vortex/tools/compression.py +325 -0
- vortex/tools/date.py +27 -0
- vortex/tools/ddhpack.py +10 -0
- vortex/tools/delayedactions.py +782 -0
- vortex/tools/env.py +541 -0
- vortex/tools/folder.py +834 -0
- vortex/tools/grib.py +738 -0
- vortex/tools/lfi.py +953 -0
- vortex/tools/listings.py +423 -0
- vortex/tools/names.py +637 -0
- vortex/tools/net.py +2124 -0
- vortex/tools/odb.py +10 -0
- vortex/tools/parallelism.py +368 -0
- vortex/tools/prestaging.py +210 -0
- vortex/tools/rawfiles.py +10 -0
- vortex/tools/schedulers.py +480 -0
- vortex/tools/services.py +940 -0
- vortex/tools/storage.py +996 -0
- vortex/tools/surfex.py +61 -0
- vortex/tools/systems.py +3976 -0
- vortex/tools/targets.py +440 -0
- vortex/util/__init__.py +9 -0
- vortex/util/config.py +1122 -0
- vortex/util/empty.py +24 -0
- vortex/util/helpers.py +216 -0
- vortex/util/introspection.py +69 -0
- vortex/util/iosponge.py +80 -0
- vortex/util/roles.py +49 -0
- vortex/util/storefunctions.py +129 -0
- vortex/util/structs.py +26 -0
- vortex/util/worker.py +162 -0
- vortex_nwp-2.0.0.dist-info/METADATA +67 -0
- vortex_nwp-2.0.0.dist-info/RECORD +144 -0
- vortex_nwp-2.0.0.dist-info/WHEEL +5 -0
- vortex_nwp-2.0.0.dist-info/licenses/LICENSE +517 -0
- 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
|