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