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