vortex-nwp 2.0.0b1__py3-none-any.whl → 2.1.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 +75 -47
- vortex/algo/__init__.py +3 -2
- vortex/algo/components.py +944 -618
- vortex/algo/mpitools.py +802 -497
- vortex/algo/mpitools_templates/__init__.py +1 -0
- vortex/algo/serversynctools.py +34 -33
- vortex/config.py +19 -22
- vortex/data/__init__.py +9 -3
- vortex/data/abstractstores.py +593 -655
- vortex/data/containers.py +217 -162
- vortex/data/contents.py +65 -39
- vortex/data/executables.py +93 -102
- vortex/data/flow.py +40 -34
- vortex/data/geometries.py +228 -132
- vortex/data/handlers.py +436 -227
- vortex/data/outflow.py +15 -15
- vortex/data/providers.py +185 -163
- vortex/data/resources.py +48 -42
- vortex/data/stores.py +540 -417
- vortex/data/sync_templates/__init__.py +0 -0
- vortex/gloves.py +114 -87
- vortex/layout/__init__.py +1 -8
- vortex/layout/contexts.py +150 -84
- vortex/layout/dataflow.py +353 -202
- vortex/layout/monitor.py +264 -128
- vortex/nwp/__init__.py +5 -2
- vortex/nwp/algo/__init__.py +14 -5
- vortex/nwp/algo/assim.py +205 -151
- vortex/nwp/algo/clim.py +683 -517
- vortex/nwp/algo/coupling.py +447 -225
- vortex/nwp/algo/eda.py +437 -229
- vortex/nwp/algo/eps.py +403 -231
- vortex/nwp/algo/forecasts.py +416 -275
- vortex/nwp/algo/fpserver.py +683 -307
- vortex/nwp/algo/ifsnaming.py +205 -145
- vortex/nwp/algo/ifsroot.py +215 -122
- vortex/nwp/algo/monitoring.py +137 -76
- vortex/nwp/algo/mpitools.py +330 -190
- vortex/nwp/algo/odbtools.py +637 -353
- vortex/nwp/algo/oopsroot.py +454 -273
- vortex/nwp/algo/oopstests.py +90 -56
- vortex/nwp/algo/request.py +287 -206
- vortex/nwp/algo/stdpost.py +878 -522
- vortex/nwp/data/__init__.py +22 -4
- vortex/nwp/data/assim.py +125 -137
- vortex/nwp/data/boundaries.py +121 -68
- vortex/nwp/data/climfiles.py +193 -211
- vortex/nwp/data/configfiles.py +73 -69
- vortex/nwp/data/consts.py +426 -401
- vortex/nwp/data/ctpini.py +59 -43
- vortex/nwp/data/diagnostics.py +94 -66
- vortex/nwp/data/eda.py +50 -51
- vortex/nwp/data/eps.py +195 -146
- vortex/nwp/data/executables.py +440 -434
- vortex/nwp/data/fields.py +63 -48
- vortex/nwp/data/gridfiles.py +183 -111
- vortex/nwp/data/logs.py +250 -217
- vortex/nwp/data/modelstates.py +180 -151
- vortex/nwp/data/monitoring.py +72 -99
- vortex/nwp/data/namelists.py +254 -202
- vortex/nwp/data/obs.py +400 -308
- vortex/nwp/data/oopsexec.py +22 -20
- vortex/nwp/data/providers.py +90 -65
- vortex/nwp/data/query.py +71 -82
- vortex/nwp/data/stores.py +49 -36
- vortex/nwp/data/surfex.py +136 -137
- vortex/nwp/syntax/__init__.py +1 -1
- vortex/nwp/syntax/stdattrs.py +173 -111
- vortex/nwp/tools/__init__.py +2 -2
- vortex/nwp/tools/addons.py +22 -17
- vortex/nwp/tools/agt.py +24 -12
- vortex/nwp/tools/bdap.py +16 -5
- vortex/nwp/tools/bdcp.py +4 -1
- vortex/nwp/tools/bdm.py +3 -0
- vortex/nwp/tools/bdmp.py +14 -9
- vortex/nwp/tools/conftools.py +728 -378
- vortex/nwp/tools/drhook.py +12 -8
- vortex/nwp/tools/grib.py +65 -39
- vortex/nwp/tools/gribdiff.py +22 -17
- vortex/nwp/tools/ifstools.py +82 -42
- vortex/nwp/tools/igastuff.py +167 -143
- vortex/nwp/tools/mars.py +14 -2
- vortex/nwp/tools/odb.py +234 -125
- vortex/nwp/tools/partitioning.py +61 -37
- vortex/nwp/tools/satrad.py +27 -12
- vortex/nwp/util/async.py +83 -55
- vortex/nwp/util/beacon.py +10 -10
- vortex/nwp/util/diffpygram.py +174 -86
- vortex/nwp/util/ens.py +144 -63
- vortex/nwp/util/hooks.py +30 -19
- vortex/nwp/util/taskdeco.py +28 -24
- vortex/nwp/util/usepygram.py +278 -172
- vortex/nwp/util/usetnt.py +31 -17
- vortex/sessions.py +72 -39
- vortex/syntax/__init__.py +1 -1
- vortex/syntax/stdattrs.py +410 -171
- vortex/syntax/stddeco.py +31 -22
- vortex/toolbox.py +327 -192
- vortex/tools/__init__.py +11 -2
- vortex/tools/actions.py +110 -121
- vortex/tools/addons.py +111 -92
- vortex/tools/arm.py +42 -22
- vortex/tools/compression.py +72 -69
- vortex/tools/date.py +11 -4
- vortex/tools/delayedactions.py +242 -132
- vortex/tools/env.py +75 -47
- vortex/tools/folder.py +342 -171
- vortex/tools/grib.py +341 -162
- vortex/tools/lfi.py +423 -216
- vortex/tools/listings.py +109 -40
- vortex/tools/names.py +218 -156
- vortex/tools/net.py +655 -299
- vortex/tools/parallelism.py +93 -61
- vortex/tools/prestaging.py +55 -31
- vortex/tools/schedulers.py +172 -105
- vortex/tools/services.py +403 -334
- vortex/tools/storage.py +293 -358
- vortex/tools/surfex.py +24 -24
- vortex/tools/systems.py +1234 -643
- vortex/tools/targets.py +156 -100
- vortex/util/__init__.py +1 -1
- vortex/util/config.py +378 -327
- vortex/util/empty.py +2 -2
- vortex/util/helpers.py +56 -24
- vortex/util/introspection.py +18 -12
- vortex/util/iosponge.py +8 -4
- vortex/util/roles.py +4 -6
- vortex/util/storefunctions.py +39 -13
- vortex/util/structs.py +3 -3
- vortex/util/worker.py +29 -17
- vortex_nwp-2.1.0.dist-info/METADATA +67 -0
- vortex_nwp-2.1.0.dist-info/RECORD +144 -0
- {vortex_nwp-2.0.0b1.dist-info → vortex_nwp-2.1.0.dist-info}/WHEEL +1 -1
- vortex/layout/appconf.py +0 -109
- vortex/layout/jobs.py +0 -1276
- vortex/layout/nodes.py +0 -1424
- vortex/layout/subjobs.py +0 -464
- vortex_nwp-2.0.0b1.dist-info/METADATA +0 -50
- vortex_nwp-2.0.0b1.dist-info/RECORD +0 -146
- {vortex_nwp-2.0.0b1.dist-info → vortex_nwp-2.1.0.dist-info/licenses}/LICENSE +0 -0
- {vortex_nwp-2.0.0b1.dist-info → vortex_nwp-2.1.0.dist-info}/top_level.txt +0 -0
vortex/tools/grib.py
CHANGED
|
@@ -10,6 +10,7 @@ It also provdes an AlgoComponent's Mixin to properly setup the environment
|
|
|
10
10
|
when using the grib_api or ecCodes libraries.
|
|
11
11
|
"""
|
|
12
12
|
|
|
13
|
+
from pathlib import Path
|
|
13
14
|
from urllib import parse as urlparse
|
|
14
15
|
|
|
15
16
|
import re
|
|
@@ -19,7 +20,10 @@ import footprints
|
|
|
19
20
|
|
|
20
21
|
from . import addons
|
|
21
22
|
from vortex.config import get_from_config_w_default
|
|
22
|
-
from vortex.algo.components import
|
|
23
|
+
from vortex.algo.components import (
|
|
24
|
+
AlgoComponentDecoMixin,
|
|
25
|
+
algo_component_deco_mixin_autodoc,
|
|
26
|
+
)
|
|
23
27
|
from vortex.tools.net import DEFAULT_FTP_PORT
|
|
24
28
|
|
|
25
29
|
#: No automatic export
|
|
@@ -30,7 +34,7 @@ logger = loggers.getLogger(__name__)
|
|
|
30
34
|
|
|
31
35
|
def use_in_shell(sh, **kw):
|
|
32
36
|
"""Extend current shell with the LFI interface defined by optional arguments."""
|
|
33
|
-
kw[
|
|
37
|
+
kw["shell"] = sh
|
|
34
38
|
return footprints.proxy.addon(**kw)
|
|
35
39
|
|
|
36
40
|
|
|
@@ -38,13 +42,14 @@ class GRIB_Tool(addons.FtrawEnableAddon):
|
|
|
38
42
|
"""
|
|
39
43
|
Handle multipart-GRIB files properly.
|
|
40
44
|
"""
|
|
45
|
+
|
|
41
46
|
_footprint = dict(
|
|
42
|
-
info
|
|
43
|
-
attr
|
|
44
|
-
kind
|
|
45
|
-
values
|
|
47
|
+
info="Default GRIB system interface",
|
|
48
|
+
attr=dict(
|
|
49
|
+
kind=dict(
|
|
50
|
+
values=["grib"],
|
|
46
51
|
),
|
|
47
|
-
)
|
|
52
|
+
),
|
|
48
53
|
)
|
|
49
54
|
|
|
50
55
|
def _std_grib_index_get(self, source):
|
|
@@ -55,24 +60,33 @@ class GRIB_Tool(addons.FtrawEnableAddon):
|
|
|
55
60
|
xgrib_index_get = _std_grib_index_get
|
|
56
61
|
|
|
57
62
|
def _std_grib_index_write(self, destination, gribpaths):
|
|
58
|
-
gribparts = [
|
|
59
|
-
|
|
63
|
+
gribparts = [
|
|
64
|
+
str(urlparse.urlunparse(("file", "", path, "", "", "")))
|
|
65
|
+
for path in gribpaths
|
|
66
|
+
]
|
|
60
67
|
tmpfile = self.sh.safe_fileaddsuffix(destination)
|
|
61
|
-
with open(tmpfile,
|
|
62
|
-
fd.write(
|
|
68
|
+
with open(tmpfile, "w") as fd:
|
|
69
|
+
fd.write("\n".join(gribparts))
|
|
63
70
|
return self.sh.move(tmpfile, destination)
|
|
64
71
|
|
|
65
72
|
def is_xgrib(self, source):
|
|
66
73
|
"""Check if the given ``source`` is a multipart-GRIB file."""
|
|
67
74
|
rc = False
|
|
68
75
|
if source and isinstance(source, str) and self.sh.path.exists(source):
|
|
69
|
-
with open(source,
|
|
70
|
-
rc = fd.read(7) == b
|
|
76
|
+
with open(source, "rb") as fd:
|
|
77
|
+
rc = fd.read(7) == b"file://"
|
|
71
78
|
return rc
|
|
72
79
|
|
|
73
|
-
def _backend_cp(
|
|
74
|
-
|
|
75
|
-
|
|
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
|
+
)
|
|
76
90
|
|
|
77
91
|
def _backend_rm(self, *args):
|
|
78
92
|
return self.sh.rm(*args)
|
|
@@ -103,8 +117,15 @@ class GRIB_Tool(addons.FtrawEnableAddon):
|
|
|
103
117
|
|
|
104
118
|
grib_rm = grib_remove = _std_remove
|
|
105
119
|
|
|
106
|
-
def _std_copy(
|
|
107
|
-
|
|
120
|
+
def _std_copy(
|
|
121
|
+
self,
|
|
122
|
+
source,
|
|
123
|
+
destination,
|
|
124
|
+
smartcp_threshold=0,
|
|
125
|
+
intent="in",
|
|
126
|
+
pack=False,
|
|
127
|
+
silent=False,
|
|
128
|
+
):
|
|
108
129
|
"""Extended copy for (possibly) multi GRIB file."""
|
|
109
130
|
# Might be multipart
|
|
110
131
|
if self.is_xgrib(source):
|
|
@@ -112,22 +133,38 @@ class GRIB_Tool(addons.FtrawEnableAddon):
|
|
|
112
133
|
if isinstance(destination, str) and not pack:
|
|
113
134
|
with self.sh.mute_stderr():
|
|
114
135
|
idx = self._std_grib_index_get(source)
|
|
115
|
-
destdir = self.sh.path.abspath(
|
|
136
|
+
destdir = self.sh.path.abspath(
|
|
137
|
+
self.sh.path.expanduser(destination) + ".d"
|
|
138
|
+
)
|
|
116
139
|
rc = rc and self.sh.mkdir(destdir)
|
|
117
140
|
target_idx = list()
|
|
118
|
-
for
|
|
119
|
-
target_idx.append(
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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":
|
|
124
157
|
self.sh.chmod(destination, 0o444)
|
|
125
158
|
else:
|
|
126
159
|
rc = rc and self.xgrib_pack(source, destination)
|
|
127
160
|
else:
|
|
128
161
|
# Usual file or file descriptor
|
|
129
|
-
rc = self._backend_cp(
|
|
130
|
-
|
|
162
|
+
rc = self._backend_cp(
|
|
163
|
+
source,
|
|
164
|
+
destination,
|
|
165
|
+
smartcp_threshold=smartcp_threshold,
|
|
166
|
+
intent=intent,
|
|
167
|
+
)
|
|
131
168
|
return rc
|
|
132
169
|
|
|
133
170
|
grib_cp = grib_copy = _std_copy
|
|
@@ -136,7 +173,7 @@ class GRIB_Tool(addons.FtrawEnableAddon):
|
|
|
136
173
|
"""Extended mv for (possibly) multi GRIB file."""
|
|
137
174
|
# Might be multipart
|
|
138
175
|
if self.is_xgrib(source):
|
|
139
|
-
intent =
|
|
176
|
+
intent = "inout" if self.sh.access(source, self.sh.W_OK) else "in"
|
|
140
177
|
rc = self._std_copy(source, destination, intent=intent)
|
|
141
178
|
rc = rc and self._std_remove(source)
|
|
142
179
|
else:
|
|
@@ -146,7 +183,9 @@ class GRIB_Tool(addons.FtrawEnableAddon):
|
|
|
146
183
|
grib_mv = grib_move = _std_move
|
|
147
184
|
|
|
148
185
|
def _pack_stream(self, source, stdout=True):
|
|
149
|
-
cmd = [
|
|
186
|
+
cmd = [
|
|
187
|
+
"cat",
|
|
188
|
+
]
|
|
150
189
|
cmd.extend(self._std_grib_index_get(source))
|
|
151
190
|
return self.sh.popen(cmd, stdout=stdout, bufsize=8192)
|
|
152
191
|
|
|
@@ -159,14 +198,14 @@ class GRIB_Tool(addons.FtrawEnableAddon):
|
|
|
159
198
|
total += size
|
|
160
199
|
return total
|
|
161
200
|
|
|
162
|
-
def xgrib_pack(self, source, destination, intent=
|
|
201
|
+
def xgrib_pack(self, source, destination, intent="in"):
|
|
163
202
|
"""Manually pack a multi GRIB."""
|
|
164
203
|
if isinstance(destination, str):
|
|
165
204
|
tmpfile = self.sh.safe_fileaddsuffix(destination)
|
|
166
|
-
with open(tmpfile,
|
|
205
|
+
with open(tmpfile, "wb") as fd:
|
|
167
206
|
p = self._pack_stream(source, stdout=fd)
|
|
168
207
|
self.sh.pclose(p)
|
|
169
|
-
if intent ==
|
|
208
|
+
if intent == "in":
|
|
170
209
|
self.sh.chmod(tmpfile, 0o444)
|
|
171
210
|
return self.sh.move(tmpfile, destination)
|
|
172
211
|
else:
|
|
@@ -177,13 +216,16 @@ class GRIB_Tool(addons.FtrawEnableAddon):
|
|
|
177
216
|
def _std_forcepack(self, source, destination=None):
|
|
178
217
|
"""Returned a path to a packed data."""
|
|
179
218
|
if self.is_xgrib(source):
|
|
180
|
-
destination = (
|
|
181
|
-
|
|
219
|
+
destination = (
|
|
220
|
+
destination
|
|
221
|
+
if destination
|
|
222
|
+
else self.sh.safe_fileaddsuffix(source)
|
|
223
|
+
)
|
|
182
224
|
if not self.sh.path.exists(destination):
|
|
183
225
|
if self.xgrib_pack(source, destination):
|
|
184
226
|
return destination
|
|
185
227
|
else:
|
|
186
|
-
raise OSError(
|
|
228
|
+
raise OSError("XGrib packing failed")
|
|
187
229
|
else:
|
|
188
230
|
return destination
|
|
189
231
|
else:
|
|
@@ -191,8 +233,16 @@ class GRIB_Tool(addons.FtrawEnableAddon):
|
|
|
191
233
|
|
|
192
234
|
grib_forcepack = _std_forcepack
|
|
193
235
|
|
|
194
|
-
def _std_ftput(
|
|
195
|
-
|
|
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
|
+
):
|
|
196
246
|
"""On the fly packing and ftp."""
|
|
197
247
|
if self.is_xgrib(source):
|
|
198
248
|
if cpipeline is not None:
|
|
@@ -202,19 +252,35 @@ class GRIB_Tool(addons.FtrawEnableAddon):
|
|
|
202
252
|
if ftp:
|
|
203
253
|
packed_size = self._packed_size(source)
|
|
204
254
|
p = self._pack_stream(source)
|
|
205
|
-
rc = ftp.put(
|
|
255
|
+
rc = ftp.put(
|
|
256
|
+
p.stdout, destination, size=packed_size, exact=True
|
|
257
|
+
)
|
|
206
258
|
self.sh.pclose(p)
|
|
207
259
|
ftp.close()
|
|
208
260
|
else:
|
|
209
261
|
rc = False
|
|
210
262
|
return rc
|
|
211
263
|
else:
|
|
212
|
-
return self.sh.ftput(
|
|
213
|
-
|
|
214
|
-
|
|
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
|
+
)
|
|
215
273
|
|
|
216
|
-
def _std_rawftput(
|
|
217
|
-
|
|
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
|
+
):
|
|
218
284
|
"""Use ftserv as much as possible."""
|
|
219
285
|
if self.is_xgrib(source):
|
|
220
286
|
if cpipeline is not None:
|
|
@@ -222,52 +288,82 @@ class GRIB_Tool(addons.FtrawEnableAddon):
|
|
|
222
288
|
if self.sh.ftraw and self.rawftshell is not None:
|
|
223
289
|
# Copy the GRIB pieces individually
|
|
224
290
|
pieces = self.xgrib_index_get(source)
|
|
225
|
-
newsources = [
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
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))
|
|
229
297
|
self.sh.readonly(request)
|
|
230
|
-
rc = self.sh.ftserv_put(
|
|
231
|
-
|
|
232
|
-
|
|
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
|
+
)
|
|
233
307
|
self.sh.rm(request)
|
|
234
308
|
return rc
|
|
235
309
|
else:
|
|
236
310
|
if port is None:
|
|
237
311
|
port = DEFAULT_FTP_PORT
|
|
238
|
-
return self._std_ftput(
|
|
239
|
-
|
|
240
|
-
|
|
312
|
+
return self._std_ftput(
|
|
313
|
+
source,
|
|
314
|
+
destination,
|
|
315
|
+
hostname=hostname,
|
|
316
|
+
logname=logname,
|
|
317
|
+
port=port,
|
|
318
|
+
sync=sync,
|
|
319
|
+
)
|
|
241
320
|
else:
|
|
242
|
-
return self.sh.rawftput(
|
|
243
|
-
|
|
244
|
-
|
|
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
|
+
)
|
|
245
330
|
|
|
246
331
|
grib_ftput = _std_ftput
|
|
247
332
|
grib_rawftput = _std_rawftput
|
|
248
333
|
|
|
249
|
-
def _std_scpput(
|
|
334
|
+
def _std_scpput(
|
|
335
|
+
self, source, destination, hostname, logname=None, cpipeline=None
|
|
336
|
+
):
|
|
250
337
|
"""On the fly packing and scp."""
|
|
251
338
|
if self.is_xgrib(source):
|
|
252
339
|
if cpipeline is not None:
|
|
253
340
|
raise OSError("It's not allowed to compress xgrib files.")
|
|
254
|
-
logname = self.sh.fix_ftuser(
|
|
341
|
+
logname = self.sh.fix_ftuser(
|
|
342
|
+
hostname, logname, fatal=False, defaults_to_user=False
|
|
343
|
+
)
|
|
255
344
|
ssh = self.sh.ssh(hostname, logname)
|
|
256
345
|
permissions = ssh.get_permissions(source)
|
|
257
346
|
# remove the .d companion directory (scp_stream removes the destination)
|
|
258
347
|
# go on on failure : the .d lingers on, but the grib will be self-contained
|
|
259
|
-
ssh.remove(destination +
|
|
348
|
+
ssh.remove(destination + ".d")
|
|
260
349
|
p = self._pack_stream(source)
|
|
261
|
-
rc = ssh.scpput_stream(
|
|
350
|
+
rc = ssh.scpput_stream(
|
|
351
|
+
p.stdout, destination, permissions=permissions
|
|
352
|
+
)
|
|
262
353
|
self.sh.pclose(p)
|
|
263
354
|
return rc
|
|
264
355
|
else:
|
|
265
|
-
return self.sh.scpput(
|
|
266
|
-
|
|
356
|
+
return self.sh.scpput(
|
|
357
|
+
source,
|
|
358
|
+
destination,
|
|
359
|
+
hostname,
|
|
360
|
+
logname=logname,
|
|
361
|
+
cpipeline=cpipeline,
|
|
362
|
+
)
|
|
267
363
|
|
|
268
364
|
grib_scpput = _std_scpput
|
|
269
365
|
|
|
270
|
-
@addons.require_external_addon(
|
|
366
|
+
@addons.require_external_addon("ecfs")
|
|
271
367
|
def grib_ecfsput(self, source, target, cpipeline=None, options=None):
|
|
272
368
|
"""Put a grib resource using ECfs.
|
|
273
369
|
|
|
@@ -282,25 +378,33 @@ class GRIB_Tool(addons.FtrawEnableAddon):
|
|
|
282
378
|
raise OSError("It's not allowed to compress xgrib files.")
|
|
283
379
|
psource = self.sh.safe_fileaddsuffix(source)
|
|
284
380
|
try:
|
|
285
|
-
rc = self.xgrib_pack(source=source,
|
|
286
|
-
destination=psource)
|
|
381
|
+
rc = self.xgrib_pack(source=source, destination=psource)
|
|
287
382
|
dict_args = dict()
|
|
288
383
|
if rc:
|
|
289
|
-
rc, dict_args = self.sh.ecfsput(
|
|
290
|
-
|
|
291
|
-
|
|
384
|
+
rc, dict_args = self.sh.ecfsput(
|
|
385
|
+
source=psource, target=target, options=options
|
|
386
|
+
)
|
|
292
387
|
finally:
|
|
293
388
|
self.sh.rm(psource)
|
|
294
389
|
return rc, dict_args
|
|
295
390
|
else:
|
|
296
|
-
return self.sh.ecfsput(
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
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
|
+
):
|
|
304
408
|
"""Put a grib resource using ECtrans.
|
|
305
409
|
|
|
306
410
|
:param source: source file
|
|
@@ -316,25 +420,28 @@ class GRIB_Tool(addons.FtrawEnableAddon):
|
|
|
316
420
|
raise OSError("It's not allowed to compress xgrib files.")
|
|
317
421
|
psource = self.sh.safe_fileaddsuffix(source)
|
|
318
422
|
try:
|
|
319
|
-
rc = self.xgrib_pack(source=source,
|
|
320
|
-
destination=psource)
|
|
423
|
+
rc = self.xgrib_pack(source=source, destination=psource)
|
|
321
424
|
dict_args = dict()
|
|
322
425
|
if rc:
|
|
323
|
-
rc, dict_args = self.sh.raw_ectransput(
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
426
|
+
rc, dict_args = self.sh.raw_ectransput(
|
|
427
|
+
source=psource,
|
|
428
|
+
target=target,
|
|
429
|
+
gateway=gateway,
|
|
430
|
+
remote=remote,
|
|
431
|
+
sync=sync,
|
|
432
|
+
)
|
|
328
433
|
finally:
|
|
329
434
|
self.sh.rm(psource)
|
|
330
435
|
return rc, dict_args
|
|
331
436
|
else:
|
|
332
|
-
return self.sh.ectransput(
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
437
|
+
return self.sh.ectransput(
|
|
438
|
+
source=source,
|
|
439
|
+
target=target,
|
|
440
|
+
gateway=gateway,
|
|
441
|
+
remote=remote,
|
|
442
|
+
cpipeline=cpipeline,
|
|
443
|
+
sync=sync,
|
|
444
|
+
)
|
|
338
445
|
|
|
339
446
|
|
|
340
447
|
@algo_component_deco_mixin_autodoc
|
|
@@ -356,23 +463,37 @@ class EcGribDecoMixin(AlgoComponentDecoMixin):
|
|
|
356
463
|
gribapi_lib = None
|
|
357
464
|
if rh is not None:
|
|
358
465
|
if not isinstance(rh, (list, tuple)):
|
|
359
|
-
rh = [
|
|
466
|
+
rh = [
|
|
467
|
+
rh,
|
|
468
|
+
]
|
|
360
469
|
for a_rh in rh:
|
|
361
470
|
libs = self.system.ldd(a_rh.container.localpath())
|
|
362
471
|
a_eccodes_lib = None
|
|
363
472
|
a_gribapi_lib = None
|
|
364
473
|
for lib, path in libs.items():
|
|
365
|
-
if re.match(
|
|
474
|
+
if re.match(
|
|
475
|
+
r"^libeccodes(?:-[.0-9]+)?\.so(?:\.[.0-9]+)?$", lib
|
|
476
|
+
):
|
|
366
477
|
a_eccodes_lib = path
|
|
367
|
-
if re.match(
|
|
478
|
+
if re.match(
|
|
479
|
+
r"^libgrib_api(?:-[.0-9]+)?\.so(?:\.[.0-9]+)?$", lib
|
|
480
|
+
):
|
|
368
481
|
a_gribapi_lib = path
|
|
369
482
|
if a_eccodes_lib:
|
|
370
|
-
self.algoassert(
|
|
371
|
-
|
|
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
|
+
)
|
|
372
489
|
eccodes_lib = a_eccodes_lib
|
|
373
490
|
if a_gribapi_lib:
|
|
374
|
-
self.algoassert(
|
|
375
|
-
|
|
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
|
+
)
|
|
376
497
|
gribapi_lib = a_gribapi_lib
|
|
377
498
|
return eccodes_lib, gribapi_lib
|
|
378
499
|
|
|
@@ -380,72 +501,104 @@ class EcGribDecoMixin(AlgoComponentDecoMixin):
|
|
|
380
501
|
"""Add axtra definitions/samples to the library path."""
|
|
381
502
|
for gdef in self.context.sequence.effective_inputs(role=a_role):
|
|
382
503
|
local_path = gdef.rh.container.localpath()
|
|
383
|
-
new_path = (
|
|
384
|
-
|
|
504
|
+
new_path = (
|
|
505
|
+
local_path
|
|
506
|
+
if self.system.path.isdir(local_path)
|
|
507
|
+
else self.system.path.dirname(local_path)
|
|
508
|
+
)
|
|
385
509
|
# NB: Grib-API doesn't understand relative paths...
|
|
386
510
|
new_path = self.system.path.abspath(new_path)
|
|
387
511
|
self.env.setgenericpath(a_var, new_path, pos=0)
|
|
388
512
|
|
|
389
513
|
def _gribapi_envsetup(self, gribapi_lib):
|
|
390
514
|
"""Setup environment variables for grib_api."""
|
|
391
|
-
defvar =
|
|
392
|
-
samplevar =
|
|
515
|
+
defvar = "GRIB_DEFINITION_PATH"
|
|
516
|
+
samplevar = "GRIB_SAMPLES_PATH"
|
|
393
517
|
if gribapi_lib is not None:
|
|
394
518
|
gribapi_root = self.system.path.dirname(gribapi_lib)
|
|
395
519
|
gribapi_root = self.system.path.split(gribapi_root)[0]
|
|
396
|
-
gribapi_share = self.system.path.join(
|
|
520
|
+
gribapi_share = self.system.path.join(
|
|
521
|
+
gribapi_root, "share", "grib_api"
|
|
522
|
+
)
|
|
397
523
|
if defvar not in self.env:
|
|
398
524
|
# This one is for compatibility with old versions of the gribapi !
|
|
399
|
-
self.env.setgenericpath(
|
|
400
|
-
|
|
525
|
+
self.env.setgenericpath(
|
|
526
|
+
defvar,
|
|
527
|
+
self.system.path.join(
|
|
528
|
+
gribapi_root, "share", "definitions"
|
|
529
|
+
),
|
|
530
|
+
)
|
|
401
531
|
# This should be the lastest one:
|
|
402
|
-
self.env.setgenericpath(
|
|
403
|
-
|
|
532
|
+
self.env.setgenericpath(
|
|
533
|
+
defvar, self.system.path.join(gribapi_share, "definitions")
|
|
534
|
+
)
|
|
404
535
|
if samplevar not in self.env:
|
|
405
536
|
# This one is for compatibility with old versions of the gribapi !
|
|
406
|
-
self.env.setgenericpath(
|
|
407
|
-
|
|
537
|
+
self.env.setgenericpath(
|
|
538
|
+
samplevar,
|
|
539
|
+
self.system.path.join(
|
|
540
|
+
gribapi_root, "ifs_samples", "grib1"
|
|
541
|
+
),
|
|
542
|
+
)
|
|
408
543
|
# This should be the lastest one:
|
|
409
|
-
self.env.setgenericpath(
|
|
410
|
-
|
|
544
|
+
self.env.setgenericpath(
|
|
545
|
+
samplevar,
|
|
546
|
+
self.system.path.join(
|
|
547
|
+
gribapi_share, "ifs_samples", "grib1"
|
|
548
|
+
),
|
|
549
|
+
)
|
|
411
550
|
else:
|
|
412
551
|
# Use the default GRIB-API config if the ldd approach fails
|
|
413
|
-
self.export(
|
|
552
|
+
self.export("gribapi")
|
|
414
553
|
return defvar, samplevar
|
|
415
554
|
|
|
416
555
|
def gribapi_setup(self, rh, opts):
|
|
417
556
|
"""Setup the grib_api related stuff."""
|
|
418
557
|
_, gribapi_lib = self._ecgrib_libs_detext(rh)
|
|
419
558
|
defvar, samplevar = self._gribapi_envsetup(gribapi_lib)
|
|
420
|
-
self._ecgrib_additional_config(
|
|
421
|
-
self._ecgrib_additional_config(
|
|
559
|
+
self._ecgrib_additional_config("AdditionalGribAPIDefinitions", defvar)
|
|
560
|
+
self._ecgrib_additional_config("AdditionalGribAPISamples", samplevar)
|
|
422
561
|
# Recap
|
|
423
562
|
for a_var in (defvar, samplevar):
|
|
424
|
-
logger.info(
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
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
|
|
449
602
|
|
|
450
603
|
def eccodes_setup(self, rh, opts, compat=False, fatal=True):
|
|
451
604
|
"""Setup the grib_api related stuff.
|
|
@@ -457,31 +610,55 @@ class EcGribDecoMixin(AlgoComponentDecoMixin):
|
|
|
457
610
|
# Detect the library's path and setup appropriate variables
|
|
458
611
|
eccodes_lib, gribapi_lib = self._ecgrib_libs_detext(rh)
|
|
459
612
|
if eccodes_lib is not None:
|
|
460
|
-
defvar
|
|
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
|
+
)
|
|
461
626
|
elif compat:
|
|
462
627
|
defvar, samplevar = self._gribapi_envsetup(gribapi_lib)
|
|
463
628
|
else:
|
|
464
629
|
if fatal:
|
|
465
|
-
raise RuntimeError(
|
|
630
|
+
raise RuntimeError(
|
|
631
|
+
"No suitable configuration found for ecCodes."
|
|
632
|
+
)
|
|
466
633
|
else:
|
|
467
634
|
logger.error("ecCodes was not found !")
|
|
468
635
|
return
|
|
469
636
|
# Then, inspect the context to look for customised search paths
|
|
470
|
-
self._ecgrib_additional_config(
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
637
|
+
self._ecgrib_additional_config(
|
|
638
|
+
("AdditionalGribAPIDefinitions", "AdditionalEcCodesDefinitions"),
|
|
639
|
+
defvar,
|
|
640
|
+
)
|
|
641
|
+
self._ecgrib_additional_config(
|
|
642
|
+
("AdditionalGribAPISamples", "AdditionalEcCodesSamples"), samplevar
|
|
643
|
+
)
|
|
474
644
|
# Recap
|
|
475
645
|
for a_var in (defvar, samplevar):
|
|
476
|
-
logger.info(
|
|
477
|
-
|
|
646
|
+
logger.info(
|
|
647
|
+
"After eccodes_setup (compat=%s) : %s = %s",
|
|
648
|
+
str(compat),
|
|
649
|
+
a_var,
|
|
650
|
+
self.env.getvar(a_var),
|
|
651
|
+
)
|
|
478
652
|
|
|
479
653
|
def _ecgrib_mixin_setup(self, rh, opts):
|
|
480
|
-
self.eccodes_setup(
|
|
481
|
-
|
|
482
|
-
|
|
654
|
+
self.eccodes_setup(
|
|
655
|
+
rh,
|
|
656
|
+
opts,
|
|
657
|
+
compat=self._ECGRIB_SETUP_COMPAT,
|
|
658
|
+
fatal=self._ECGRIB_SETUP_FATAL,
|
|
659
|
+
)
|
|
483
660
|
|
|
484
|
-
_MIXIN_PREPARE_HOOKS = (_ecgrib_mixin_setup,
|
|
661
|
+
_MIXIN_PREPARE_HOOKS = (_ecgrib_mixin_setup,)
|
|
485
662
|
|
|
486
663
|
|
|
487
664
|
class GRIBAPI_Tool(addons.Addon):
|
|
@@ -490,12 +667,12 @@ class GRIBAPI_Tool(addons.Addon):
|
|
|
490
667
|
"""
|
|
491
668
|
|
|
492
669
|
_footprint = dict(
|
|
493
|
-
info
|
|
494
|
-
attr
|
|
495
|
-
kind
|
|
496
|
-
values
|
|
670
|
+
info="Default GRIBAPI system interface",
|
|
671
|
+
attr=dict(
|
|
672
|
+
kind=dict(
|
|
673
|
+
values=["gribapi"],
|
|
497
674
|
),
|
|
498
|
-
)
|
|
675
|
+
),
|
|
499
676
|
)
|
|
500
677
|
|
|
501
678
|
def __init__(self, *args, **kw):
|
|
@@ -513,17 +690,19 @@ class GRIBAPI_Tool(addons.Addon):
|
|
|
513
690
|
|
|
514
691
|
def _spawn_wrap(self, cmd, **kw):
|
|
515
692
|
"""Internal method calling standard shell spawn."""
|
|
516
|
-
cmd[0] =
|
|
693
|
+
cmd[0] = "bin" + self.sh.path.sep + cmd[0]
|
|
517
694
|
return super()._spawn_wrap(cmd, **kw)
|
|
518
695
|
|
|
519
696
|
def _actual_diff(self, grib1, grib2, skipkeys, **kw):
|
|
520
697
|
"""Run the actual GRIBAPI command."""
|
|
521
|
-
cmd = [
|
|
522
|
-
kw[
|
|
523
|
-
kw[
|
|
698
|
+
cmd = ["grib_compare", "-r", "-b", ",".join(skipkeys), grib1, grib2]
|
|
699
|
+
kw["fatal"] = False
|
|
700
|
+
kw["output"] = False
|
|
524
701
|
return self._spawn_wrap(cmd, **kw)
|
|
525
702
|
|
|
526
|
-
def grib_diff(
|
|
703
|
+
def grib_diff(
|
|
704
|
+
self, grib1, grib2, skipkeys=("generatingProcessIdentifier",), **kw
|
|
705
|
+
):
|
|
527
706
|
"""
|
|
528
707
|
Difference between two GRIB files (using the GRIB-API)
|
|
529
708
|
|
|
@@ -538,15 +717,15 @@ class GRIBAPI_Tool(addons.Addon):
|
|
|
538
717
|
"""
|
|
539
718
|
|
|
540
719
|
# Are multipart GRIB suported ?
|
|
541
|
-
xgrib_support =
|
|
720
|
+
xgrib_support = "grib" in self.sh.loaded_addons()
|
|
542
721
|
grib1_ori = grib1
|
|
543
722
|
grib2_ori = grib2
|
|
544
723
|
if xgrib_support:
|
|
545
724
|
if self.sh.is_xgrib(grib1):
|
|
546
|
-
grib1 = self.sh.safe_fileaddsuffix(grib1_ori) +
|
|
725
|
+
grib1 = self.sh.safe_fileaddsuffix(grib1_ori) + "_diffcat"
|
|
547
726
|
self.sh.xgrib_pack(grib1_ori, grib1)
|
|
548
727
|
if self.sh.is_xgrib(grib2):
|
|
549
|
-
grib2 = self.sh.safe_fileaddsuffix(grib2_ori) +
|
|
728
|
+
grib2 = self.sh.safe_fileaddsuffix(grib2_ori) + "_diffcat"
|
|
550
729
|
self.sh.xgrib_pack(grib2_ori, grib2)
|
|
551
730
|
|
|
552
731
|
rc = self._actual_diff(grib1, grib2, skipkeys, **kw)
|