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
vortex/tools/grib.py ADDED
@@ -0,0 +1,559 @@
1
+ """
2
+ Module needed to interact with GRIB files.
3
+
4
+ It provides shell addons to deal with:
5
+
6
+ * Splitted GRIB files (as produced by the Arpege/IFS IO server)
7
+ * The ability to compare GRIB files
8
+
9
+ It also provdes an AlgoComponent's Mixin to properly setup the environment
10
+ when using the grib_api or ecCodes libraries.
11
+ """
12
+
13
+ from urllib import parse as urlparse
14
+
15
+ import re
16
+
17
+ from bronx.fancies import loggers
18
+ import footprints
19
+
20
+ from . import addons
21
+ from vortex.config import get_from_config_w_default
22
+ from vortex.algo.components import AlgoComponentDecoMixin, algo_component_deco_mixin_autodoc
23
+ from vortex.tools.net import DEFAULT_FTP_PORT
24
+
25
+ #: No automatic export
26
+ __all__ = []
27
+
28
+ logger = loggers.getLogger(__name__)
29
+
30
+
31
+ def use_in_shell(sh, **kw):
32
+ """Extend current shell with the LFI interface defined by optional arguments."""
33
+ kw['shell'] = sh
34
+ return footprints.proxy.addon(**kw)
35
+
36
+
37
+ class GRIB_Tool(addons.FtrawEnableAddon):
38
+ """
39
+ Handle multipart-GRIB files properly.
40
+ """
41
+ _footprint = dict(
42
+ info = 'Default GRIB system interface',
43
+ attr = dict(
44
+ kind = dict(
45
+ values = ['grib'],
46
+ ),
47
+ )
48
+ )
49
+
50
+ def _std_grib_index_get(self, source):
51
+ with open(source) as fd:
52
+ gribparts = fd.read().splitlines()
53
+ return [urlparse.urlparse(url).path for url in gribparts]
54
+
55
+ xgrib_index_get = _std_grib_index_get
56
+
57
+ def _std_grib_index_write(self, destination, gribpaths):
58
+ gribparts = [str(urlparse.urlunparse(('file', '', path, '', '', '')))
59
+ for path in gribpaths]
60
+ tmpfile = self.sh.safe_fileaddsuffix(destination)
61
+ with open(tmpfile, 'w') as fd:
62
+ fd.write('\n'.join(gribparts))
63
+ return self.sh.move(tmpfile, destination)
64
+
65
+ def is_xgrib(self, source):
66
+ """Check if the given ``source`` is a multipart-GRIB file."""
67
+ rc = False
68
+ if source and isinstance(source, str) and self.sh.path.exists(source):
69
+ with open(source, 'rb') as fd:
70
+ rc = fd.read(7) == b'file://'
71
+ return rc
72
+
73
+ def _backend_cp(self, source, destination, smartcp_threshold=0, intent='in'):
74
+ return self.sh.cp(source, destination,
75
+ smartcp_threshold=smartcp_threshold, intent=intent, smartcp=True)
76
+
77
+ def _backend_rm(self, *args):
78
+ return self.sh.rm(*args)
79
+
80
+ def _backend_mv(self, source, destination):
81
+ return self.sh.mv(source, destination)
82
+
83
+ def _std_remove(self, *args):
84
+ """Remove (possibly) multi GRIB files."""
85
+ rc = True
86
+ for pname in args:
87
+ for objpath in self.sh.glob(pname):
88
+ if self.is_xgrib(objpath):
89
+ with self.sh.mute_stderr():
90
+ idx = self._std_grib_index_get(objpath)
91
+ target_dirs = set()
92
+ for a_mpart in idx:
93
+ target_dirs.add(self.sh.path.dirname(a_mpart))
94
+ rc = rc and self._backend_rm(a_mpart)
95
+ for a_dir in target_dirs:
96
+ # Only if the directory is empty
97
+ if not self.sh.listdir(a_dir):
98
+ rc = rc and self._backend_rm(a_dir)
99
+ rc = rc and self._backend_rm(objpath)
100
+ else:
101
+ rc = rc and self._backend_rm(objpath)
102
+ return rc
103
+
104
+ grib_rm = grib_remove = _std_remove
105
+
106
+ def _std_copy(self, source, destination,
107
+ smartcp_threshold=0, intent='in', pack=False, silent=False):
108
+ """Extended copy for (possibly) multi GRIB file."""
109
+ # Might be multipart
110
+ if self.is_xgrib(source):
111
+ rc = True
112
+ if isinstance(destination, str) and not pack:
113
+ with self.sh.mute_stderr():
114
+ idx = self._std_grib_index_get(source)
115
+ destdir = self.sh.path.abspath(self.sh.path.expanduser(destination) + ".d")
116
+ rc = rc and self.sh.mkdir(destdir)
117
+ target_idx = list()
118
+ for (i, a_mpart) in enumerate(idx):
119
+ target_idx.append(self.sh.path.join(destdir, 'GRIB_mpart{:06d}'.format(i)))
120
+ rc = rc and self._backend_cp(a_mpart, target_idx[-1],
121
+ smartcp_threshold=smartcp_threshold, intent=intent)
122
+ rc = rc and self._std_grib_index_write(destination, target_idx)
123
+ if intent == 'in':
124
+ self.sh.chmod(destination, 0o444)
125
+ else:
126
+ rc = rc and self.xgrib_pack(source, destination)
127
+ else:
128
+ # Usual file or file descriptor
129
+ rc = self._backend_cp(source, destination,
130
+ smartcp_threshold=smartcp_threshold, intent=intent)
131
+ return rc
132
+
133
+ grib_cp = grib_copy = _std_copy
134
+
135
+ def _std_move(self, source, destination):
136
+ """Extended mv for (possibly) multi GRIB file."""
137
+ # Might be multipart
138
+ if self.is_xgrib(source):
139
+ intent = 'inout' if self.sh.access(source, self.sh.W_OK) else 'in'
140
+ rc = self._std_copy(source, destination, intent=intent)
141
+ rc = rc and self._std_remove(source)
142
+ else:
143
+ rc = self._backend_mv(source, destination)
144
+ return rc
145
+
146
+ grib_mv = grib_move = _std_move
147
+
148
+ def _pack_stream(self, source, stdout=True):
149
+ cmd = ['cat', ]
150
+ cmd.extend(self._std_grib_index_get(source))
151
+ return self.sh.popen(cmd, stdout=stdout, bufsize=8192)
152
+
153
+ def _packed_size(self, source):
154
+ total = 0
155
+ for filepath in self._std_grib_index_get(source):
156
+ size = self.sh.size(filepath)
157
+ if size == -1:
158
+ return None
159
+ total += size
160
+ return total
161
+
162
+ def xgrib_pack(self, source, destination, intent='in'):
163
+ """Manually pack a multi GRIB."""
164
+ if isinstance(destination, str):
165
+ tmpfile = self.sh.safe_fileaddsuffix(destination)
166
+ with open(tmpfile, 'wb') as fd:
167
+ p = self._pack_stream(source, stdout=fd)
168
+ self.sh.pclose(p)
169
+ if intent == 'in':
170
+ self.sh.chmod(tmpfile, 0o444)
171
+ return self.sh.move(tmpfile, destination)
172
+ else:
173
+ p = self._pack_stream(source, stdout=destination)
174
+ self.sh.pclose(p)
175
+ return True
176
+
177
+ def _std_forcepack(self, source, destination=None):
178
+ """Returned a path to a packed data."""
179
+ if self.is_xgrib(source):
180
+ destination = (destination if destination else
181
+ self.sh.safe_fileaddsuffix(source))
182
+ if not self.sh.path.exists(destination):
183
+ if self.xgrib_pack(source, destination):
184
+ return destination
185
+ else:
186
+ raise OSError('XGrib packing failed')
187
+ else:
188
+ return destination
189
+ else:
190
+ return source
191
+
192
+ grib_forcepack = _std_forcepack
193
+
194
+ def _std_ftput(self, source, destination, hostname=None, logname=None,
195
+ port=DEFAULT_FTP_PORT, cpipeline=None, sync=False):
196
+ """On the fly packing and ftp."""
197
+ if self.is_xgrib(source):
198
+ if cpipeline is not None:
199
+ raise OSError("It's not allowed to compress xgrib files.")
200
+ hostname = self.sh.fix_fthostname(hostname)
201
+ ftp = self.sh.ftp(hostname, logname, port=port)
202
+ if ftp:
203
+ packed_size = self._packed_size(source)
204
+ p = self._pack_stream(source)
205
+ rc = ftp.put(p.stdout, destination, size=packed_size, exact=True)
206
+ self.sh.pclose(p)
207
+ ftp.close()
208
+ else:
209
+ rc = False
210
+ return rc
211
+ else:
212
+ return self.sh.ftput(source, destination, hostname=hostname,
213
+ logname=logname, port=port,
214
+ cpipeline=cpipeline, sync=sync)
215
+
216
+ def _std_rawftput(self, source, destination, hostname=None, logname=None,
217
+ port=None, cpipeline=None, sync=False):
218
+ """Use ftserv as much as possible."""
219
+ if self.is_xgrib(source):
220
+ if cpipeline is not None:
221
+ raise OSError("It's not allowed to compress xgrib files.")
222
+ if self.sh.ftraw and self.rawftshell is not None:
223
+ # Copy the GRIB pieces individually
224
+ pieces = self.xgrib_index_get(source)
225
+ newsources = [str(self.sh.copy2ftspool(piece)) for piece in pieces]
226
+ request = newsources[0] + '.request'
227
+ with open(request, 'w') as request_fh:
228
+ request_fh.writelines('\n'.join(newsources))
229
+ self.sh.readonly(request)
230
+ rc = self.sh.ftserv_put(request, destination,
231
+ hostname=hostname, logname=logname, port=port,
232
+ specialshell=self.rawftshell, sync=sync)
233
+ self.sh.rm(request)
234
+ return rc
235
+ else:
236
+ if port is None:
237
+ port = DEFAULT_FTP_PORT
238
+ return self._std_ftput(source, destination,
239
+ hostname=hostname, logname=logname,
240
+ port=port, sync=sync)
241
+ else:
242
+ return self.sh.rawftput(source, destination, hostname=hostname,
243
+ logname=logname, port=port,
244
+ cpipeline=cpipeline, sync=sync)
245
+
246
+ grib_ftput = _std_ftput
247
+ grib_rawftput = _std_rawftput
248
+
249
+ def _std_scpput(self, source, destination, hostname, logname=None, cpipeline=None):
250
+ """On the fly packing and scp."""
251
+ if self.is_xgrib(source):
252
+ if cpipeline is not None:
253
+ raise OSError("It's not allowed to compress xgrib files.")
254
+ logname = self.sh.fix_ftuser(hostname, logname, fatal=False, defaults_to_user=False)
255
+ ssh = self.sh.ssh(hostname, logname)
256
+ permissions = ssh.get_permissions(source)
257
+ # remove the .d companion directory (scp_stream removes the destination)
258
+ # go on on failure : the .d lingers on, but the grib will be self-contained
259
+ ssh.remove(destination + '.d')
260
+ p = self._pack_stream(source)
261
+ rc = ssh.scpput_stream(p.stdout, destination, permissions=permissions)
262
+ self.sh.pclose(p)
263
+ return rc
264
+ else:
265
+ return self.sh.scpput(source, destination, hostname,
266
+ logname=logname, cpipeline=cpipeline)
267
+
268
+ grib_scpput = _std_scpput
269
+
270
+ @addons.require_external_addon('ecfs')
271
+ def grib_ecfsput(self, source, target, cpipeline=None, options=None):
272
+ """Put a grib resource using ECfs.
273
+
274
+ :param source: source file
275
+ :param target: target file
276
+ :param cpipeline: compression pipeline used, if provided
277
+ :param options: list of options to be used
278
+ :return: return code and additional attributes used
279
+ """
280
+ if self.is_xgrib(source):
281
+ if cpipeline is not None:
282
+ raise OSError("It's not allowed to compress xgrib files.")
283
+ psource = self.sh.safe_fileaddsuffix(source)
284
+ try:
285
+ rc = self.xgrib_pack(source=source,
286
+ destination=psource)
287
+ dict_args = dict()
288
+ if rc:
289
+ rc, dict_args = self.sh.ecfsput(source=psource,
290
+ target=target,
291
+ options=options)
292
+ finally:
293
+ self.sh.rm(psource)
294
+ return rc, dict_args
295
+ else:
296
+ return self.sh.ecfsput(source=source,
297
+ target=target,
298
+ options=options,
299
+ cpipeline=cpipeline)
300
+
301
+ @addons.require_external_addon('ectrans')
302
+ def grib_ectransput(self, source, target, gateway=None, remote=None,
303
+ cpipeline=None, sync=False):
304
+ """Put a grib resource using ECtrans.
305
+
306
+ :param source: source file
307
+ :param target: target file
308
+ :param gateway: gateway used by ECtrans
309
+ :param remote: remote used by ECtrans
310
+ :param cpipeline: compression pipeline used, if provided
311
+ :param bool sync: If False, allow asynchronous transfers
312
+ :return: return code and additional attributes used
313
+ """
314
+ if self.is_xgrib(source):
315
+ if cpipeline is not None:
316
+ raise OSError("It's not allowed to compress xgrib files.")
317
+ psource = self.sh.safe_fileaddsuffix(source)
318
+ try:
319
+ rc = self.xgrib_pack(source=source,
320
+ destination=psource)
321
+ dict_args = dict()
322
+ if rc:
323
+ rc, dict_args = self.sh.raw_ectransput(source=psource,
324
+ target=target,
325
+ gateway=gateway,
326
+ remote=remote,
327
+ sync=sync)
328
+ finally:
329
+ self.sh.rm(psource)
330
+ return rc, dict_args
331
+ else:
332
+ return self.sh.ectransput(source=source,
333
+ target=target,
334
+ gateway=gateway,
335
+ remote=remote,
336
+ cpipeline=cpipeline,
337
+ sync=sync)
338
+
339
+
340
+ @algo_component_deco_mixin_autodoc
341
+ class EcGribDecoMixin(AlgoComponentDecoMixin):
342
+ """Extend Algo Components with EcCodes/GribApi features."
343
+
344
+ This mixin class is intended to be used with AlgoComponent classes. It will
345
+ automatically set up the ecCodes/GribApi environment variable given the
346
+ path to the EcCodes/GribApi library (which is found by performing a ``ldd``
347
+ on the AlgoComponent's target binary).
348
+ """
349
+
350
+ _ECGRIB_SETUP_COMPAT = True
351
+ _ECGRIB_SETUP_FATAL = True
352
+
353
+ def _ecgrib_libs_detext(self, rh):
354
+ """Run ldd and tries to find ecCodes or grib_api libraries locations."""
355
+ eccodes_lib = None
356
+ gribapi_lib = None
357
+ if rh is not None:
358
+ if not isinstance(rh, (list, tuple)):
359
+ rh = [rh, ]
360
+ for a_rh in rh:
361
+ libs = self.system.ldd(a_rh.container.localpath())
362
+ a_eccodes_lib = None
363
+ a_gribapi_lib = None
364
+ for lib, path in libs.items():
365
+ if re.match(r'^libeccodes(?:-[.0-9]+)?\.so(?:\.[.0-9]+)?$', lib):
366
+ a_eccodes_lib = path
367
+ if re.match(r'^libgrib_api(?:-[.0-9]+)?\.so(?:\.[.0-9]+)?$', lib):
368
+ a_gribapi_lib = path
369
+ if a_eccodes_lib:
370
+ self.algoassert(eccodes_lib is None or (eccodes_lib == a_eccodes_lib),
371
+ "ecCodes library inconsistency (rh: {!s})".format(a_rh))
372
+ eccodes_lib = a_eccodes_lib
373
+ if a_gribapi_lib:
374
+ self.algoassert(gribapi_lib is None or (gribapi_lib == a_gribapi_lib),
375
+ "grib_api library inconsistency (rh: {!s})".format(a_rh))
376
+ gribapi_lib = a_gribapi_lib
377
+ return eccodes_lib, gribapi_lib
378
+
379
+ def _ecgrib_additional_config(self, a_role, a_var):
380
+ """Add axtra definitions/samples to the library path."""
381
+ for gdef in self.context.sequence.effective_inputs(role=a_role):
382
+ local_path = gdef.rh.container.localpath()
383
+ new_path = (local_path if self.system.path.isdir(local_path)
384
+ else self.system.path.dirname(local_path))
385
+ # NB: Grib-API doesn't understand relative paths...
386
+ new_path = self.system.path.abspath(new_path)
387
+ self.env.setgenericpath(a_var, new_path, pos=0)
388
+
389
+ def _gribapi_envsetup(self, gribapi_lib):
390
+ """Setup environment variables for grib_api."""
391
+ defvar = 'GRIB_DEFINITION_PATH'
392
+ samplevar = 'GRIB_SAMPLES_PATH'
393
+ if gribapi_lib is not None:
394
+ gribapi_root = self.system.path.dirname(gribapi_lib)
395
+ gribapi_root = self.system.path.split(gribapi_root)[0]
396
+ gribapi_share = self.system.path.join(gribapi_root, 'share', 'grib_api')
397
+ if defvar not in self.env:
398
+ # This one is for compatibility with old versions of the gribapi !
399
+ self.env.setgenericpath(defvar,
400
+ self.system.path.join(gribapi_root, 'share', 'definitions'))
401
+ # This should be the lastest one:
402
+ self.env.setgenericpath(defvar,
403
+ self.system.path.join(gribapi_share, 'definitions'))
404
+ if samplevar not in self.env:
405
+ # This one is for compatibility with old versions of the gribapi !
406
+ self.env.setgenericpath(samplevar,
407
+ self.system.path.join(gribapi_root, 'ifs_samples', 'grib1'))
408
+ # This should be the lastest one:
409
+ self.env.setgenericpath(samplevar,
410
+ self.system.path.join(gribapi_share, 'ifs_samples', 'grib1'))
411
+ else:
412
+ # Use the default GRIB-API config if the ldd approach fails
413
+ self.export('gribapi')
414
+ return defvar, samplevar
415
+
416
+ def gribapi_setup(self, rh, opts):
417
+ """Setup the grib_api related stuff."""
418
+ _, gribapi_lib = self._ecgrib_libs_detext(rh)
419
+ defvar, samplevar = self._gribapi_envsetup(gribapi_lib)
420
+ self._ecgrib_additional_config('AdditionalGribAPIDefinitions', defvar)
421
+ self._ecgrib_additional_config('AdditionalGribAPISamples', samplevar)
422
+ # Recap
423
+ for a_var in (defvar, samplevar):
424
+ logger.info('After gribapi_setup %s = %s', a_var, self.env.getvar(a_var))
425
+
426
+ def _eccodes_envsetup(self, eccodes_lib):
427
+ """Setup environment variables for ecCodes."""
428
+ dep_warn = ("%s is left unconfigured because the old grib_api's variable is defined." +
429
+ "Please remove that !")
430
+ eccodes_root = self.system.path.dirname(eccodes_lib)
431
+ eccodes_root = self.system.path.split(eccodes_root)[0]
432
+ eccodes_share = self.system.path.join(eccodes_root, 'share', 'eccodes')
433
+ defvar = 'ECCODES_DEFINITION_PATH'
434
+ if defvar not in self.env:
435
+ if 'GRIB_DEFINITION_PATH' in self.env:
436
+ logger.warning(dep_warn, defvar)
437
+ defvar = 'GRIB_DEFINITION_PATH'
438
+ else:
439
+ self.env.setgenericpath(defvar, self.system.path.join(eccodes_share, 'definitions'))
440
+ samplevar = 'ECCODES_SAMPLES_PATH'
441
+ if samplevar not in self.env:
442
+ if 'GRIB_SAMPLES_PATH' in self.env:
443
+ logger.warning(dep_warn, samplevar)
444
+ samplevar = 'GRIB_SAMPLES_PATH'
445
+ else:
446
+ self.env.setgenericpath(samplevar,
447
+ self.system.path.join(eccodes_share, 'ifs_samples', 'grib1'))
448
+ return defvar, samplevar
449
+
450
+ def eccodes_setup(self, rh, opts, compat=False, fatal=True):
451
+ """Setup the grib_api related stuff.
452
+
453
+ If **compat** is ``True`` and ecCodes is not found, the old grib_api
454
+ will be set-up. Otherwise, it will just return (if **fatal** is ``False``)
455
+ or raise an exception (if **fatal** is ``True``).
456
+ """
457
+ # Detect the library's path and setup appropriate variables
458
+ eccodes_lib, gribapi_lib = self._ecgrib_libs_detext(rh)
459
+ if eccodes_lib is not None:
460
+ defvar, samplevar = self._eccodes_envsetup(eccodes_lib)
461
+ elif compat:
462
+ defvar, samplevar = self._gribapi_envsetup(gribapi_lib)
463
+ else:
464
+ if fatal:
465
+ raise RuntimeError('No suitable configuration found for ecCodes.')
466
+ else:
467
+ logger.error("ecCodes was not found !")
468
+ return
469
+ # Then, inspect the context to look for customised search paths
470
+ self._ecgrib_additional_config(('AdditionalGribAPIDefinitions', 'AdditionalEcCodesDefinitions'),
471
+ defvar)
472
+ self._ecgrib_additional_config(('AdditionalGribAPISamples', 'AdditionalEcCodesSamples'),
473
+ samplevar)
474
+ # Recap
475
+ for a_var in (defvar, samplevar):
476
+ logger.info('After eccodes_setup (compat=%s) : %s = %s', str(compat),
477
+ a_var, self.env.getvar(a_var))
478
+
479
+ def _ecgrib_mixin_setup(self, rh, opts):
480
+ self.eccodes_setup(rh, opts,
481
+ compat=self._ECGRIB_SETUP_COMPAT,
482
+ fatal=self._ECGRIB_SETUP_FATAL)
483
+
484
+ _MIXIN_PREPARE_HOOKS = (_ecgrib_mixin_setup, )
485
+
486
+
487
+ class GRIBAPI_Tool(addons.Addon):
488
+ """
489
+ Interface to gribapi commands (designed as a shell Addon).
490
+ """
491
+
492
+ _footprint = dict(
493
+ info = 'Default GRIBAPI system interface',
494
+ attr = dict(
495
+ kind = dict(
496
+ values = ['gribapi'],
497
+ ),
498
+ )
499
+ )
500
+
501
+ def __init__(self, *args, **kw):
502
+ """Addon initialisation."""
503
+ super().__init__(*args, **kw)
504
+ # Additionaly, check for the GRIB_API_ROOTDIR key in the config file
505
+ if self.path is None and self.cfginfo is not None:
506
+ addon_rootdir = get_from_config_w_default(
507
+ section=self.cfginfo,
508
+ key="grib_api_rootdir",
509
+ default=None,
510
+ )
511
+ if addon_rootdir is not None:
512
+ self.path = addon_rootdir
513
+
514
+ def _spawn_wrap(self, cmd, **kw):
515
+ """Internal method calling standard shell spawn."""
516
+ cmd[0] = 'bin' + self.sh.path.sep + cmd[0]
517
+ return super()._spawn_wrap(cmd, **kw)
518
+
519
+ def _actual_diff(self, grib1, grib2, skipkeys, **kw):
520
+ """Run the actual GRIBAPI command."""
521
+ cmd = ['grib_compare', '-r', '-b', ','.join(skipkeys), grib1, grib2]
522
+ kw['fatal'] = False
523
+ kw['output'] = False
524
+ return self._spawn_wrap(cmd, **kw)
525
+
526
+ def grib_diff(self, grib1, grib2, skipkeys=('generatingProcessIdentifier',), **kw):
527
+ """
528
+ Difference between two GRIB files (using the GRIB-API)
529
+
530
+ :param grib1: first file to compare
531
+ :param grib2: second file to compare
532
+ :param skipkeys: List of GRIB keys that will be ignored
533
+
534
+ GRIB messages may not be in the same order in both files.
535
+
536
+ If *grib1* or *grib2* are multipart files, they will be concatenated
537
+ prior to the comparison.
538
+ """
539
+
540
+ # Are multipart GRIB suported ?
541
+ xgrib_support = 'grib' in self.sh.loaded_addons()
542
+ grib1_ori = grib1
543
+ grib2_ori = grib2
544
+ if xgrib_support:
545
+ if self.sh.is_xgrib(grib1):
546
+ grib1 = self.sh.safe_fileaddsuffix(grib1_ori) + '_diffcat'
547
+ self.sh.xgrib_pack(grib1_ori, grib1)
548
+ if self.sh.is_xgrib(grib2):
549
+ grib2 = self.sh.safe_fileaddsuffix(grib2_ori) + '_diffcat'
550
+ self.sh.xgrib_pack(grib2_ori, grib2)
551
+
552
+ rc = self._actual_diff(grib1, grib2, skipkeys, **kw)
553
+
554
+ if xgrib_support and grib1 != grib1_ori:
555
+ self.sh.grib_rm(grib1)
556
+ if xgrib_support and grib2 != grib2_ori:
557
+ self.sh.grib_rm(grib2)
558
+
559
+ return rc