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,622 @@
1
+ """
2
+ A handful of abstract and generic :class:`DataContent` classes designed to access
3
+ and modify data of a given "Resource".
4
+
5
+ These classes are not meant to be used directly. To retrieve a
6
+ :class:`DataContent` object on a given "Resource", please use the
7
+ :data:`vortex.data.handlers.Handler.contents` property.
8
+ """
9
+
10
+ import collections
11
+ from string import Template
12
+
13
+ from bronx.fancies import loggers
14
+ from bronx.stdtypes.dictionaries import ReadOnlyDict
15
+ from bronx.stdtypes.xtemplates import DefaultTemplate
16
+ from bronx.syntax.decorators import secure_getattr
17
+ import footprints
18
+
19
+ from vortex import sessions
20
+
21
+ #: No automatic export
22
+ __all__ = []
23
+
24
+ logger = loggers.getLogger(__name__)
25
+
26
+
27
+ class DataContentError(ValueError):
28
+ """General content error."""
29
+
30
+ pass
31
+
32
+
33
+ class DataContent:
34
+ """Root class for data contents used by resources."""
35
+
36
+ _diffable = False
37
+
38
+ def __init__(self, **kw):
39
+ self._datafmt = None
40
+ self._data = None
41
+ self._metadata = ReadOnlyDict()
42
+ self._size = 0
43
+ for k, v in kw.items():
44
+ self.__dict__["_" + k] = v
45
+
46
+ @secure_getattr
47
+ def __getattr__(self, attr):
48
+ """Forward get attribute request to internal data object."""
49
+ if attr not in ("__getstate__", "__deepcopy__"):
50
+ return getattr(self.data, attr)
51
+ else:
52
+ raise AttributeError(attr)
53
+
54
+ def __enter__(self):
55
+ """Enter a :keyword:`with` context."""
56
+ pass
57
+
58
+ def __exit__(self, exc_type, exc_value, traceback):
59
+ """Exit from :keyword:`with` context."""
60
+ pass
61
+
62
+ @property
63
+ def data(self):
64
+ """The internal data encapsulated."""
65
+ return self._data
66
+
67
+ @property
68
+ def size(self):
69
+ """The actual size of the contents."""
70
+ return self._size
71
+
72
+ @property
73
+ def metadata(self):
74
+ """Return the metadata of the ressource in the container (maybe empty)."""
75
+ return self._metadata
76
+
77
+ def metadata_check(self, resource, delta=None):
78
+ """Check that the metadata of the resource in the container matches
79
+ the attributes of the resource given as an argument.
80
+
81
+ Prior to the comparison, the delta argument will be added to the
82
+ attribute read in the container. For example, if delta=dict(date='-PT1H'),
83
+ we will in fact check that the date of the resource in the container is
84
+ one hour before the date specified in the resource's footprint.
85
+ """
86
+ if not len(self.metadata):
87
+ logger.error(
88
+ "Metadata check is not implemented for this format. "
89
+ + "The check will always succeed..."
90
+ )
91
+ delta = delta or {}
92
+ outcome = True
93
+ for mkey, mval in self.metadata.items():
94
+ if hasattr(resource, mkey):
95
+ cval = getattr(resource, mkey)
96
+ if mkey in delta:
97
+ cval += delta[mkey]
98
+ outcome = outcome and cval == mval
99
+ if not outcome:
100
+ logger.warning(
101
+ "The ressource in the container doesn't match the resource footprint: %s",
102
+ str(self.metadata),
103
+ )
104
+ return outcome
105
+
106
+ @property
107
+ def datafmt(self):
108
+ """The initial format of the contents."""
109
+ return self._datafmt
110
+
111
+ @classmethod
112
+ def export_dict(cls):
113
+ """Return current class name for shell or pure dict export mechanism."""
114
+ return (cls.__module__, cls.__name__)
115
+
116
+ @property
117
+ def updated(self):
118
+ return False
119
+
120
+ def slurp(self, container):
121
+ """Should be overwritten. Basically get the totalsize of the actual container."""
122
+ self._size = container.totalsize
123
+
124
+ def _merge_checkclass(self, *kargs):
125
+ """Utility method to check that all the kargs objects are compatible self."""
126
+ if not all([isinstance(obj, self.__class__) for obj in kargs]):
127
+ raise DataContentError(
128
+ "The object's types are not compatible with self"
129
+ )
130
+
131
+ def merge(self, *kargs):
132
+ """Merge several DataContents into one.
133
+
134
+ This method have to be implemented and _merge_checkclass should be called
135
+ to ensure that the object's types are compatible with self.
136
+ """
137
+ raise NotImplementedError("Merge is not implemented for this content.")
138
+
139
+ @classmethod
140
+ def is_diffable(cls):
141
+ """Is the diff operation implemented for this content class ?"""
142
+ return cls._diffable
143
+
144
+ def diff(self, ref):
145
+ """Compare the present content with the ``ref`` content."""
146
+ if not self.is_diffable():
147
+ raise NotImplementedError(
148
+ "Diff is not implemented for this content"
149
+ )
150
+ else:
151
+ return self._actual_diff(ref)
152
+
153
+ def _actual_diff(self, ref):
154
+ """A very simple kind of comparison... but it might work !"""
155
+ return self.data == ref.data
156
+
157
+ def rewrite(self, container):
158
+ """Abstract method."""
159
+ pass
160
+
161
+
162
+ class UnknownContent(DataContent):
163
+ """Fake DataContent subclass."""
164
+
165
+ pass
166
+
167
+
168
+ class AlmostDictContent(DataContent):
169
+ """Implement some dictionary-like functions."""
170
+
171
+ # The very simple diff method form DataContent should do the job.
172
+ _diffable = True
173
+
174
+ def __init__(self, **kw):
175
+ super().__init__(**kw)
176
+ if self._data is None:
177
+ self._data = dict()
178
+
179
+ def fmtkey(self, key):
180
+ """Reshape entry keys of the internal dictionary."""
181
+ return key
182
+
183
+ def __getitem__(self, idx):
184
+ return self._data[self.fmtkey(idx)]
185
+
186
+ def __setitem__(self, idx, value):
187
+ self._data[self.fmtkey(idx)] = value
188
+
189
+ def __delitem__(self, idx):
190
+ del self._data[self.fmtkey(idx)]
191
+
192
+ def __len__(self):
193
+ return len(self._data)
194
+
195
+ def __iter__(self):
196
+ yield from self._data.keys()
197
+
198
+ def __contains__(self, item):
199
+ return self.fmtkey(item) in self._data
200
+
201
+ def has_key(self, item):
202
+ """Dict-like behavior looking for the formatted ``item`` in internal data."""
203
+ return item in self
204
+
205
+ def merge(self, *kargs):
206
+ """Merge several data contents into one."""
207
+ self._merge_checkclass(*kargs)
208
+ for obj in kargs:
209
+ self._data.update(obj.data)
210
+ self._size += obj.size
211
+
212
+
213
+ class IndexedTable(AlmostDictContent):
214
+ """
215
+ Multi-columns table indexed by first column.
216
+ Behaves mostly as a dictionary.
217
+ """
218
+
219
+ def append(self, item):
220
+ """Insert data according to index position given as the first element of the ``item`` list."""
221
+ if len(item) > 0:
222
+ i = item.pop(0)
223
+ self._data[self.fmtkey(i)] = item
224
+
225
+ def extend(self, addlist):
226
+ """Insert data according to index position given as the first item of ``addlist``."""
227
+ for idxinput in addlist:
228
+ self.append(idxinput)
229
+
230
+ def slurp(self, container):
231
+ """Get data from the ``container``."""
232
+ with container.preferred_decoding(byte=False):
233
+ container.rewind()
234
+ self.extend(
235
+ [
236
+ x.split()
237
+ for x in container.readlines()
238
+ if not x.startswith("#")
239
+ ]
240
+ )
241
+ self._size = container.totalsize
242
+
243
+
244
+ class JsonDictContent(AlmostDictContent):
245
+ """
246
+ The internal data is supposed to be read from a json file.
247
+ """
248
+
249
+ def __init__(self, **kw):
250
+ self._bronx_tpl = None
251
+ super().__init__(**kw)
252
+
253
+ def slurp(self, container):
254
+ """Get data from the ``container``."""
255
+ t = sessions.current()
256
+ with container.preferred_decoding(byte=False):
257
+ container.rewind()
258
+ self._data = t.sh.json_load(container.iotarget())
259
+ self._size = container.totalsize
260
+
261
+ def bronx_tpl_render(self, **kwargs):
262
+ """Use :mod:`bronx.stdtypes.xtemplates` to render a JSON template."""
263
+ if self._bronx_tpl is None:
264
+ # Freeze the original data
265
+ self._bronx_tpl = self._data
266
+ # Start rendering and overwrite data
267
+ dt = DefaultTemplate(self._bronx_tpl)
268
+ self._data = dt.render(**kwargs)
269
+
270
+ def rewrite(self, container):
271
+ """Write the list contents in the specified container."""
272
+ t = sessions.current()
273
+ container.close()
274
+ # In Python 2, json.dumps returns 'str', not unicode...
275
+ with container.iod_context():
276
+ with container.preferred_decoding(byte=False):
277
+ with container.preferred_write():
278
+ iod = container.iodesc()
279
+ t.sh.json_dump(self.data, iod, indent=4)
280
+ container.updfill(True)
281
+
282
+
283
+ class AlmostListContent(DataContent):
284
+ """
285
+ Implement some list-like functions.
286
+ The argument maxprint is used for the maximum number of lines
287
+ to display through the str function.
288
+ """
289
+
290
+ # The very simple diff method form DataContent should do the job.
291
+ _diffable = True
292
+
293
+ def __init__(self, **kw):
294
+ self._maxprint = kw.pop("maxprint", 20)
295
+ super().__init__(**kw)
296
+ if self._data is None:
297
+ self._data = list()
298
+
299
+ def __delitem__(self, idx):
300
+ del self.data[idx]
301
+
302
+ def __delslice__(self, istart, iend):
303
+ del self.data[istart:iend]
304
+
305
+ def __setitem__(self, idx, value):
306
+ self.data[idx] = value
307
+
308
+ def __setslice__(self, istart, iend, value):
309
+ self.data[istart:iend] = value
310
+
311
+ def __getitem__(self, idx):
312
+ return self.data[idx]
313
+
314
+ def __getslice__(self, istart, iend):
315
+ return self.data[istart:iend]
316
+
317
+ def __sizeof__(self):
318
+ return self.data.__sizeof__()
319
+
320
+ def __len__(self):
321
+ return len(self.data)
322
+
323
+ def __iter__(self):
324
+ yield from self.data
325
+
326
+ def __call__(self):
327
+ return self.data
328
+
329
+ def _get_maxprint(self):
330
+ return self._maxprint
331
+
332
+ def _set_maxprint(self, value):
333
+ try:
334
+ self._maxprint = abs(int(value))
335
+ except (ValueError, TypeError):
336
+ pass
337
+ self._maxprint = max(10, self._maxprint)
338
+
339
+ maxprint = property(_get_maxprint, _set_maxprint, None)
340
+
341
+ def clear(self):
342
+ """Clear all internal data contents."""
343
+ self._data[:] = []
344
+
345
+ def slurp(self, container):
346
+ """Get data from the ``container``."""
347
+ with container.preferred_decoding(byte=False):
348
+ self._data.extend(container.readlines())
349
+ self._size = container.totalsize
350
+
351
+ def rewrite(self, container):
352
+ """Write the list contents in the specified container."""
353
+ container.close()
354
+ with container.iod_context():
355
+ with container.preferred_decoding(byte=False):
356
+ for xline in self:
357
+ container.write(xline)
358
+
359
+ def sort(self, **sort_opts):
360
+ """Sort the current object."""
361
+ self._data.sort(**sort_opts)
362
+
363
+ def merge(self, *kargs, **kwargs):
364
+ """Merge several data contents into one."""
365
+ unique = kwargs.get("unique", False)
366
+ self._merge_checkclass(*kargs)
367
+ for obj in kargs:
368
+ self.data.extend(obj.data)
369
+ self._size += obj.size
370
+ # Check if the item are unique, raise an error if not (option unique = True)
371
+ if unique:
372
+ arg_elements = collections.Counter(self.data)
373
+ repeated_elements = [
374
+ element for element, count in arg_elements.items() if count > 1
375
+ ]
376
+ if len(repeated_elements) > 0:
377
+ logger.exception(
378
+ "Repeated argument are present. It should not. Stop."
379
+ + "The list of the repeated elements follows: %s",
380
+ str(sorted(repeated_elements)),
381
+ )
382
+ raise DataContentError(
383
+ "Repeated argument are present. It should not."
384
+ )
385
+
386
+
387
+ class TextContent(AlmostListContent):
388
+ """
389
+ Multi-lines input text data split through blank seperator.
390
+ Behaves mostly as a list.
391
+ """
392
+
393
+ def __init__(self, **kw):
394
+ kw.setdefault("fmt", None)
395
+ super().__init__(**kw)
396
+
397
+ def __str__(self):
398
+ if len(self) > self.maxprint:
399
+ catlist = self[0:3] + ["..."] + self[-3:]
400
+ else:
401
+ catlist = self[:]
402
+ return "\n".join([str(x) for x in catlist])
403
+
404
+ def slurp(self, container):
405
+ with container.preferred_decoding(byte=False):
406
+ self._data.extend(
407
+ [x.split() for x in container if not x.startswith("#")]
408
+ )
409
+ self._size = container.totalsize
410
+
411
+ def formatted_data(self, item):
412
+ """Return a formatted string according to optional internal fmt."""
413
+ if self._fmt is None:
414
+ return " ".join([str(x) for x in item])
415
+ else:
416
+ return self._fmt.format(*item)
417
+
418
+ def rewrite(self, container):
419
+ """Write the text contents in the specified container."""
420
+ container.close()
421
+ with container.iod_context():
422
+ with container.preferred_decoding(byte=False):
423
+ for item in self:
424
+ container.write(self.formatted_data(item) + "\n")
425
+
426
+
427
+ class DataRaw(AlmostListContent):
428
+ """
429
+ Multi-lines raw data (no format assumed).
430
+ Behaves mostly as a list.
431
+ """
432
+
433
+ def __init__(self, data=None, window=0, datafmt=None):
434
+ if not data and window:
435
+ data = collections.deque(maxlen=window)
436
+ super().__init__(data=data, window=window, datafmt=datafmt)
437
+
438
+ def slurp(self, container):
439
+ with container.preferred_decoding(byte=False):
440
+ container.rewind()
441
+ end = False
442
+ while not end:
443
+ data, end = container.dataread()
444
+ self._data.append(data)
445
+ if self._window and len(self._data) >= self._window:
446
+ end = True
447
+
448
+
449
+ class DataTemplate(DataContent):
450
+ """
451
+ Multi-lines data which fits to a template.
452
+ Behave mostly as a list.
453
+ """
454
+
455
+ def slurp(self, container):
456
+ """Actually read a container."""
457
+ with container.preferred_decoding(byte=False):
458
+ container.rewind()
459
+ self._data = container.read()
460
+ super().slurp(container)
461
+
462
+ def setitems(self, keyvaluedict):
463
+ """
464
+ Substitute the different keys contained in a dictionary into
465
+ the data content using a template.
466
+
467
+ :param dict keyvaluedict: things to be substituted.
468
+ """
469
+ data_tmp = Template(self._data)
470
+ self._data = data_tmp.substitute(keyvaluedict)
471
+
472
+ def rewrite(self, container):
473
+ """Write the list contents in the specified container."""
474
+ container.close()
475
+ with container.iod_context():
476
+ with container.preferred_decoding(byte=False):
477
+ container.write(self.data)
478
+
479
+
480
+ class FormatAdapter(DataContent):
481
+ """Adapter to objects that could manage a dedicated format."""
482
+
483
+ def __init__(self, **kw):
484
+ super().__init__(**kw)
485
+ if self._data is None and footprints.proxy.dataformats is None:
486
+ logger.warning("No collector for data formats")
487
+ self._datafmt = None
488
+
489
+ def __enter__(self):
490
+ """
491
+ Enter a :keyword:`with` context, setting some FORTRAN variables
492
+ in order to switch off parallelism (in case of execution).
493
+ """
494
+ t = sessions.current()
495
+ t.env.delta(
496
+ LFI_HNDL_SPEC=":1",
497
+ DR_HOOK_SILENT=1,
498
+ DR_HOOK_NOT_MPI=1,
499
+ OMP_NUM_THREADS=1,
500
+ )
501
+ return self
502
+
503
+ def __exit__(self, exc_type, exc_value, traceback):
504
+ """Exit from :keyword:`with` context."""
505
+ t = sessions.current()
506
+ t.env.rewind()
507
+
508
+ def slurp(self, container):
509
+ """Load a dataformat object."""
510
+ if self.datafmt:
511
+ with self:
512
+ self._data = footprints.proxy.dataformat(
513
+ filename=container.abspath,
514
+ openmode="r",
515
+ fmtdelayedopen=True,
516
+ format=container.actualfmt.upper(),
517
+ )
518
+ # Look for a metadatareader object
519
+ if (
520
+ self._data is not None
521
+ and footprints.proxy.metadatareaders is not None
522
+ ):
523
+ mreader = footprints.proxy.metadatareader(
524
+ format=container.actualfmt.upper(),
525
+ _emptywarning=False,
526
+ )
527
+ if mreader is not None:
528
+ mreader.content_init(self._data)
529
+ self._metadata = mreader
530
+
531
+
532
+ class MetaDataReader(footprints.FootprintBase):
533
+ """
534
+ Abstract class for any MetaDataReader.
535
+
536
+ Note: _do_delayed_init have to be subclassed. That's where the content of the
537
+ container is actually read.
538
+ """
539
+
540
+ _abstract = True
541
+ _collector = ("metadatareader",)
542
+ _footprint = dict(
543
+ info="Abstract MetaDataReader",
544
+ attr=dict(
545
+ format=dict(
546
+ type=str,
547
+ )
548
+ ),
549
+ )
550
+
551
+ def __init__(self, *kargs, **kwargs):
552
+ self._content_in = None
553
+ self._datahide = None
554
+ super().__init__(*kargs, **kwargs)
555
+
556
+ @property
557
+ def _data(self):
558
+ """Internal: check if one needs to intialise the _datahide dict."""
559
+ if self._datahide is None and self._content_in is not None:
560
+ self._do_delayed_init()
561
+ return self._datahide
562
+
563
+ def content_init(self, thecontent):
564
+ """Set the data content that will be used to read the metadata"""
565
+ self._content_in = thecontent
566
+
567
+ def _do_delayed_init(self):
568
+ """Internal: actually initialise the _data array. Have to be subclassed !"""
569
+ raise NotImplementedError
570
+
571
+ def __getitem__(self, key):
572
+ return self._data[key]
573
+
574
+ def __len__(self):
575
+ return len(self._data)
576
+
577
+ def __iter__(self):
578
+ return iter(self._data)
579
+
580
+ def items(self):
581
+ """Iterate over the metadata."""
582
+ for k in self:
583
+ yield k, self[k]
584
+
585
+ def __repr__(self):
586
+ if self._datahide is None:
587
+ return "{}: Not yet initialised".format(self.__class__)
588
+ else:
589
+ return repr(self._data)
590
+
591
+ def __str__(self):
592
+ return str(self._data)
593
+
594
+
595
+ class FormatAdapterAbstractImplementation(footprints.FootprintBase):
596
+ """
597
+ The minimal set of attributes needed to create a class that is compatible
598
+ with the :class:`FormatAdapter` content.
599
+ """
600
+
601
+ _abstract = True
602
+ _collector = ("dataformat",)
603
+ _footprint = dict(
604
+ attr=dict(
605
+ filename=dict(
606
+ info="Path to the target data.",
607
+ ),
608
+ openmode=dict(
609
+ info="File open-mode.",
610
+ values=["r", "rw"],
611
+ default="r",
612
+ optional=True,
613
+ ),
614
+ fmtdelayedopen=dict(
615
+ info="Delay the opening of the listing file.",
616
+ type=bool,
617
+ default=True,
618
+ optional=True,
619
+ ),
620
+ format=dict(),
621
+ )
622
+ )