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.
- vortex/__init__.py +135 -0
- vortex/algo/__init__.py +12 -0
- vortex/algo/components.py +2136 -0
- vortex/algo/mpitools.py +1648 -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 +170 -0
- vortex/config.py +115 -0
- vortex/data/__init__.py +13 -0
- vortex/data/abstractstores.py +1572 -0
- vortex/data/containers.py +780 -0
- vortex/data/contents.py +596 -0
- vortex/data/executables.py +284 -0
- vortex/data/flow.py +113 -0
- vortex/data/geometries.ini +2689 -0
- vortex/data/geometries.py +703 -0
- vortex/data/handlers.py +1021 -0
- vortex/data/outflow.py +67 -0
- vortex/data/providers.py +465 -0
- vortex/data/resources.py +201 -0
- vortex/data/stores.py +1271 -0
- vortex/gloves.py +282 -0
- vortex/layout/__init__.py +27 -0
- vortex/layout/appconf.py +109 -0
- vortex/layout/contexts.py +511 -0
- vortex/layout/dataflow.py +1069 -0
- vortex/layout/jobs.py +1276 -0
- vortex/layout/monitor.py +833 -0
- vortex/layout/nodes.py +1424 -0
- vortex/layout/subjobs.py +464 -0
- vortex/nwp/__init__.py +11 -0
- vortex/nwp/algo/__init__.py +12 -0
- vortex/nwp/algo/assim.py +483 -0
- vortex/nwp/algo/clim.py +920 -0
- vortex/nwp/algo/coupling.py +609 -0
- vortex/nwp/algo/eda.py +632 -0
- vortex/nwp/algo/eps.py +613 -0
- vortex/nwp/algo/forecasts.py +745 -0
- vortex/nwp/algo/fpserver.py +927 -0
- vortex/nwp/algo/ifsnaming.py +403 -0
- vortex/nwp/algo/ifsroot.py +311 -0
- vortex/nwp/algo/monitoring.py +202 -0
- vortex/nwp/algo/mpitools.py +554 -0
- vortex/nwp/algo/odbtools.py +974 -0
- vortex/nwp/algo/oopsroot.py +735 -0
- vortex/nwp/algo/oopstests.py +186 -0
- vortex/nwp/algo/request.py +579 -0
- vortex/nwp/algo/stdpost.py +1285 -0
- vortex/nwp/data/__init__.py +12 -0
- vortex/nwp/data/assim.py +392 -0
- vortex/nwp/data/boundaries.py +261 -0
- vortex/nwp/data/climfiles.py +539 -0
- vortex/nwp/data/configfiles.py +149 -0
- vortex/nwp/data/consts.py +929 -0
- vortex/nwp/data/ctpini.py +133 -0
- vortex/nwp/data/diagnostics.py +181 -0
- vortex/nwp/data/eda.py +148 -0
- vortex/nwp/data/eps.py +383 -0
- vortex/nwp/data/executables.py +1039 -0
- vortex/nwp/data/fields.py +96 -0
- vortex/nwp/data/gridfiles.py +308 -0
- vortex/nwp/data/logs.py +551 -0
- vortex/nwp/data/modelstates.py +334 -0
- vortex/nwp/data/monitoring.py +220 -0
- vortex/nwp/data/namelists.py +644 -0
- vortex/nwp/data/obs.py +748 -0
- vortex/nwp/data/oopsexec.py +72 -0
- vortex/nwp/data/providers.py +182 -0
- vortex/nwp/data/query.py +217 -0
- vortex/nwp/data/stores.py +147 -0
- vortex/nwp/data/surfex.py +338 -0
- vortex/nwp/syntax/__init__.py +9 -0
- vortex/nwp/syntax/stdattrs.py +375 -0
- vortex/nwp/tools/__init__.py +10 -0
- vortex/nwp/tools/addons.py +35 -0
- vortex/nwp/tools/agt.py +55 -0
- vortex/nwp/tools/bdap.py +48 -0
- vortex/nwp/tools/bdcp.py +38 -0
- vortex/nwp/tools/bdm.py +21 -0
- vortex/nwp/tools/bdmp.py +49 -0
- vortex/nwp/tools/conftools.py +1311 -0
- vortex/nwp/tools/drhook.py +62 -0
- vortex/nwp/tools/grib.py +268 -0
- vortex/nwp/tools/gribdiff.py +99 -0
- vortex/nwp/tools/ifstools.py +163 -0
- vortex/nwp/tools/igastuff.py +249 -0
- vortex/nwp/tools/mars.py +56 -0
- vortex/nwp/tools/odb.py +548 -0
- vortex/nwp/tools/partitioning.py +234 -0
- vortex/nwp/tools/satrad.py +56 -0
- vortex/nwp/util/__init__.py +6 -0
- vortex/nwp/util/async.py +184 -0
- vortex/nwp/util/beacon.py +40 -0
- vortex/nwp/util/diffpygram.py +359 -0
- vortex/nwp/util/ens.py +198 -0
- vortex/nwp/util/hooks.py +128 -0
- vortex/nwp/util/taskdeco.py +81 -0
- vortex/nwp/util/usepygram.py +591 -0
- vortex/nwp/util/usetnt.py +87 -0
- vortex/proxy.py +6 -0
- vortex/sessions.py +341 -0
- vortex/syntax/__init__.py +9 -0
- vortex/syntax/stdattrs.py +628 -0
- vortex/syntax/stddeco.py +176 -0
- vortex/toolbox.py +982 -0
- vortex/tools/__init__.py +11 -0
- vortex/tools/actions.py +457 -0
- vortex/tools/addons.py +297 -0
- vortex/tools/arm.py +76 -0
- vortex/tools/compression.py +322 -0
- vortex/tools/date.py +20 -0
- vortex/tools/ddhpack.py +10 -0
- vortex/tools/delayedactions.py +672 -0
- vortex/tools/env.py +513 -0
- vortex/tools/folder.py +663 -0
- vortex/tools/grib.py +559 -0
- vortex/tools/lfi.py +746 -0
- vortex/tools/listings.py +354 -0
- vortex/tools/names.py +575 -0
- vortex/tools/net.py +1790 -0
- vortex/tools/odb.py +10 -0
- vortex/tools/parallelism.py +336 -0
- vortex/tools/prestaging.py +186 -0
- vortex/tools/rawfiles.py +10 -0
- vortex/tools/schedulers.py +413 -0
- vortex/tools/services.py +871 -0
- vortex/tools/storage.py +1061 -0
- vortex/tools/surfex.py +61 -0
- vortex/tools/systems.py +3396 -0
- vortex/tools/targets.py +384 -0
- vortex/util/__init__.py +9 -0
- vortex/util/config.py +1071 -0
- vortex/util/empty.py +24 -0
- vortex/util/helpers.py +184 -0
- vortex/util/introspection.py +63 -0
- vortex/util/iosponge.py +76 -0
- vortex/util/roles.py +51 -0
- vortex/util/storefunctions.py +103 -0
- vortex/util/structs.py +26 -0
- vortex/util/worker.py +150 -0
- vortex_nwp-2.0.0b1.dist-info/LICENSE +517 -0
- vortex_nwp-2.0.0b1.dist-info/METADATA +50 -0
- vortex_nwp-2.0.0b1.dist-info/RECORD +146 -0
- vortex_nwp-2.0.0b1.dist-info/WHEEL +5 -0
- vortex_nwp-2.0.0b1.dist-info/top_level.txt +1 -0
vortex/tools/addons.py
ADDED
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Abstract classes for System addons.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from collections import defaultdict
|
|
6
|
+
|
|
7
|
+
from bronx.fancies import loggers
|
|
8
|
+
from bronx.syntax.decorators import nicedeco
|
|
9
|
+
import footprints
|
|
10
|
+
|
|
11
|
+
from vortex.config import get_from_config_w_default
|
|
12
|
+
from vortex.layout import contexts
|
|
13
|
+
from vortex.tools.env import Environment
|
|
14
|
+
from vortex.tools.systems import OSExtended
|
|
15
|
+
|
|
16
|
+
logger = loggers.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
#: No automatic export
|
|
19
|
+
__all__ = []
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class Addon(footprints.FootprintBase):
|
|
23
|
+
"""Root class for any :class:`Addon` system subclasses."""
|
|
24
|
+
|
|
25
|
+
_abstract = True
|
|
26
|
+
_collector = ('addon',)
|
|
27
|
+
_footprint = dict(
|
|
28
|
+
info = 'Default add-on',
|
|
29
|
+
attr = dict(
|
|
30
|
+
kind = dict(),
|
|
31
|
+
sh = dict(
|
|
32
|
+
type = OSExtended,
|
|
33
|
+
alias = ('shell',),
|
|
34
|
+
access = 'rwx-weak',
|
|
35
|
+
),
|
|
36
|
+
env = dict(
|
|
37
|
+
type = Environment,
|
|
38
|
+
optional = True,
|
|
39
|
+
default = None,
|
|
40
|
+
access = 'rwx',
|
|
41
|
+
doc_visibility = footprints.doc.visibility.ADVANCED
|
|
42
|
+
),
|
|
43
|
+
cfginfo = dict(
|
|
44
|
+
optional = True,
|
|
45
|
+
default = '[kind]',
|
|
46
|
+
doc_visibility = footprints.doc.visibility.ADVANCED
|
|
47
|
+
),
|
|
48
|
+
cmd = dict(
|
|
49
|
+
optional = True,
|
|
50
|
+
default = None,
|
|
51
|
+
access = 'rwx',
|
|
52
|
+
),
|
|
53
|
+
path = dict(
|
|
54
|
+
optional = True,
|
|
55
|
+
default = None,
|
|
56
|
+
access = 'rwx',
|
|
57
|
+
),
|
|
58
|
+
cycle = dict(
|
|
59
|
+
optional = True,
|
|
60
|
+
default = None,
|
|
61
|
+
access = 'rwx',
|
|
62
|
+
),
|
|
63
|
+
toolkind = dict(
|
|
64
|
+
optional = True,
|
|
65
|
+
default = None
|
|
66
|
+
),
|
|
67
|
+
)
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
def __init__(self, *args, **kw):
|
|
71
|
+
"""Abstract Addon initialisation."""
|
|
72
|
+
logger.debug('Abstract Addon init %s', self.__class__)
|
|
73
|
+
super().__init__(*args, **kw)
|
|
74
|
+
self.sh.extend(self)
|
|
75
|
+
self._context_cache = defaultdict(dict)
|
|
76
|
+
self._cmd_xperms_cache = set()
|
|
77
|
+
if self.env is None:
|
|
78
|
+
self.env = Environment(active=False, clear=True)
|
|
79
|
+
clsenv = self.__class__.__dict__
|
|
80
|
+
for k in [x for x in clsenv.keys() if x.isupper()]:
|
|
81
|
+
self.env[k] = clsenv[k]
|
|
82
|
+
if self.path is None:
|
|
83
|
+
self.path = get_from_config_w_default(
|
|
84
|
+
section="nwp-tools", key=self.kind, default=None,
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
@classmethod
|
|
88
|
+
def in_shell(cls, shell):
|
|
89
|
+
"""Grep any active instance of that class in the specified shell."""
|
|
90
|
+
lx = [x for x in shell.search if isinstance(x, cls)]
|
|
91
|
+
return lx[0] if lx else None
|
|
92
|
+
|
|
93
|
+
def _query_context(self):
|
|
94
|
+
"""Return the path and cmd for the current context.
|
|
95
|
+
|
|
96
|
+
Results are cached so that the context's localtracker is explored only once.
|
|
97
|
+
|
|
98
|
+
.. note:: We use the localtracker instead of the sequence because, in
|
|
99
|
+
multistep jobs, the localtracker is preserved between steps. It's
|
|
100
|
+
less elegant but it plays nice with MTOOL.
|
|
101
|
+
"""
|
|
102
|
+
ctxtag = contexts.Context.tag_focus()
|
|
103
|
+
if ctxtag not in self._context_cache and self.toolkind is not None:
|
|
104
|
+
ltrack = contexts.current().localtracker
|
|
105
|
+
# NB: 'str' is important because local might be in unicode...
|
|
106
|
+
candidates = [str(self.sh.path.realpath(local))
|
|
107
|
+
for local, entry in ltrack.items()
|
|
108
|
+
if (entry.latest_rhdict('get').get('resource', dict()).get('kind', '') ==
|
|
109
|
+
self.toolkind)]
|
|
110
|
+
if candidates:
|
|
111
|
+
realpath = candidates.pop()
|
|
112
|
+
self._context_cache[ctxtag] = dict(path=self.sh.path.dirname(realpath),
|
|
113
|
+
cmd=self.sh.path.basename(realpath))
|
|
114
|
+
return self._context_cache[ctxtag]
|
|
115
|
+
|
|
116
|
+
@property
|
|
117
|
+
def actual_path(self):
|
|
118
|
+
"""The path that should be used in the current context."""
|
|
119
|
+
infos = self._query_context()
|
|
120
|
+
ctxpath = infos.get('path', None)
|
|
121
|
+
return self.path if ctxpath is None else ctxpath
|
|
122
|
+
|
|
123
|
+
@property
|
|
124
|
+
def actual_cmd(self):
|
|
125
|
+
"""The cmd that should be used in the current context."""
|
|
126
|
+
infos = self._query_context()
|
|
127
|
+
ctxcmd = infos.get('cmd', None)
|
|
128
|
+
return self.cmd if ctxcmd is None else ctxcmd
|
|
129
|
+
|
|
130
|
+
def _spawn_commons(self, cmd, **kw):
|
|
131
|
+
"""Internal method setting local environment and calling standard shell spawn."""
|
|
132
|
+
|
|
133
|
+
# Is there a need for an interpreter ?
|
|
134
|
+
if 'interpreter' in kw:
|
|
135
|
+
cmd.insert(0, kw.pop('interpreter'))
|
|
136
|
+
else:
|
|
137
|
+
# The first element of the command line needs to be executable
|
|
138
|
+
if cmd[0] not in self._cmd_xperms_cache:
|
|
139
|
+
self._cmd_xperms_cache.add(cmd[0])
|
|
140
|
+
self.sh.xperm(cmd[0], force=True)
|
|
141
|
+
|
|
142
|
+
# Overwrite global module env values with specific ones
|
|
143
|
+
with self.sh.env.clone() as localenv:
|
|
144
|
+
|
|
145
|
+
localenv.verbose(True, self.sh)
|
|
146
|
+
localenv.update(self.env)
|
|
147
|
+
|
|
148
|
+
# Check if a pipe is requested
|
|
149
|
+
inpipe = kw.pop('inpipe', False)
|
|
150
|
+
|
|
151
|
+
# Ask the attached shell to run the addon command
|
|
152
|
+
if inpipe:
|
|
153
|
+
kw.setdefault('stdout', True)
|
|
154
|
+
rc = self.sh.popen(cmd, **kw)
|
|
155
|
+
else:
|
|
156
|
+
rc = self.sh.spawn(cmd, **kw)
|
|
157
|
+
|
|
158
|
+
return rc
|
|
159
|
+
|
|
160
|
+
def _spawn(self, cmd, **kw):
|
|
161
|
+
"""Internal method setting local environment and calling standard shell spawn."""
|
|
162
|
+
|
|
163
|
+
# Insert the actual tool command as first argument
|
|
164
|
+
cmd.insert(0, self.actual_cmd)
|
|
165
|
+
if self.actual_path is not None:
|
|
166
|
+
cmd[0] = self.actual_path + '/' + cmd[0]
|
|
167
|
+
|
|
168
|
+
return self._spawn_commons(cmd, **kw)
|
|
169
|
+
|
|
170
|
+
def _spawn_wrap(self, cmd, **kw):
|
|
171
|
+
"""Internal method setting local environment and calling standard shell spawn."""
|
|
172
|
+
|
|
173
|
+
# Insert the tool path before the first argument
|
|
174
|
+
if self.actual_path is not None:
|
|
175
|
+
cmd[0] = self.actual_path + '/' + cmd[0]
|
|
176
|
+
|
|
177
|
+
return self._spawn_commons(cmd, **kw)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
class FtrawEnableAddon(Addon):
|
|
181
|
+
"""Root class for any :class:`Addon` system subclasses that needs to override rawftput."""
|
|
182
|
+
|
|
183
|
+
_abstract = True
|
|
184
|
+
_footprint = dict(
|
|
185
|
+
info = 'Default add-on with rawftput support.',
|
|
186
|
+
attr = dict(
|
|
187
|
+
rawftshell = dict(
|
|
188
|
+
info = "Path to ftserv's concatenation shell",
|
|
189
|
+
optional = True,
|
|
190
|
+
default = None,
|
|
191
|
+
access = 'rwx',
|
|
192
|
+
doc_visibility = footprints.doc.visibility.GURU,
|
|
193
|
+
),
|
|
194
|
+
)
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
def __init__(self, *args, **kw):
|
|
198
|
+
"""Abstract Addon initialisation."""
|
|
199
|
+
logger.debug('Abstract Addon init %s', self.__class__)
|
|
200
|
+
super().__init__(*args, **kw)
|
|
201
|
+
# If needed, look in the config file for the rawftshell
|
|
202
|
+
if self.rawftshell is None:
|
|
203
|
+
self.rawftshell = get_from_config_w_default(
|
|
204
|
+
section="rawftshell", key=self.kind, default=None,
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
class AddonGroup(footprints.FootprintBase):
|
|
209
|
+
"""Root class for any :class:`AddonGroup` system subclasses.
|
|
210
|
+
|
|
211
|
+
An AddonGroup is not really an Addon... it just loads a bunch of other
|
|
212
|
+
Addons or AddonGroups into the current shell.
|
|
213
|
+
"""
|
|
214
|
+
|
|
215
|
+
_abstract = True
|
|
216
|
+
_collector = ('addon',)
|
|
217
|
+
_footprint = dict(
|
|
218
|
+
info = 'Default add-on group',
|
|
219
|
+
attr = dict(
|
|
220
|
+
kind = dict(),
|
|
221
|
+
sh = dict(
|
|
222
|
+
type = OSExtended,
|
|
223
|
+
alias = ('shell',),
|
|
224
|
+
),
|
|
225
|
+
env = dict(
|
|
226
|
+
type = Environment,
|
|
227
|
+
optional = True,
|
|
228
|
+
default = None,
|
|
229
|
+
doc_visibility = footprints.doc.visibility.ADVANCED,
|
|
230
|
+
),
|
|
231
|
+
cycle = dict(
|
|
232
|
+
optional = True,
|
|
233
|
+
default = None,
|
|
234
|
+
),
|
|
235
|
+
verboseload = dict(
|
|
236
|
+
optional = True,
|
|
237
|
+
default = True,
|
|
238
|
+
type = bool,
|
|
239
|
+
),
|
|
240
|
+
)
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
_addonslist = None
|
|
244
|
+
|
|
245
|
+
def __init__(self, *args, **kw):
|
|
246
|
+
"""Abstract Addon initialisation."""
|
|
247
|
+
logger.debug('Abstract Addon init %s', self.__class__)
|
|
248
|
+
super().__init__(*args, **kw)
|
|
249
|
+
self._addons_load()
|
|
250
|
+
|
|
251
|
+
def _addons_load(self):
|
|
252
|
+
if self._addonslist is None:
|
|
253
|
+
raise RuntimeError("the _addonslist classe variable must be overriden.")
|
|
254
|
+
self._load_addons_from_list(self._addonslist)
|
|
255
|
+
|
|
256
|
+
def _load_addons_from_list(self, addons):
|
|
257
|
+
if self.verboseload:
|
|
258
|
+
logger.info("Loading the %s Addons group.", self.kind)
|
|
259
|
+
for addon in addons:
|
|
260
|
+
_shadd = footprints.proxy.addon(kind=addon, sh=self.sh, env=self.env,
|
|
261
|
+
cycle=self.cycle, verboseload=self.verboseload)
|
|
262
|
+
if self.verboseload:
|
|
263
|
+
logger.info("%s Addon is: %s", addon, repr(_shadd))
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def require_external_addon(*addons):
|
|
267
|
+
"""
|
|
268
|
+
A method decorator usable in addons, that will check if addons listed in
|
|
269
|
+
**addons** are properly loaded in the parent System object.
|
|
270
|
+
|
|
271
|
+
If not, a :class:`RuntimeError` exception will be raised.
|
|
272
|
+
"""
|
|
273
|
+
@nicedeco
|
|
274
|
+
def r_addon_decorator(method):
|
|
275
|
+
|
|
276
|
+
def decorated(self, *kargs, **kwargs):
|
|
277
|
+
# Create a cache in self... ugly but efficient !
|
|
278
|
+
if not hasattr(self, '_require_external_addon_check_cache'):
|
|
279
|
+
setattr(self, '_require_external_addon_check_cache', set())
|
|
280
|
+
ko_addons = set()
|
|
281
|
+
loaded_addons = None
|
|
282
|
+
for addon in addons:
|
|
283
|
+
if addon in self._require_external_addon_check_cache:
|
|
284
|
+
continue
|
|
285
|
+
if loaded_addons is None:
|
|
286
|
+
loaded_addons = self.sh.loaded_addons()
|
|
287
|
+
if addon in loaded_addons:
|
|
288
|
+
self._require_external_addon_check_cache.add(addon)
|
|
289
|
+
else:
|
|
290
|
+
ko_addons.add(addon)
|
|
291
|
+
if ko_addons:
|
|
292
|
+
raise RuntimeError('The following addons are needed to use the {:s} method: {:s}'
|
|
293
|
+
.format(method.__name__, ', '.join(ko_addons)))
|
|
294
|
+
return method(self, *kargs, **kwargs)
|
|
295
|
+
|
|
296
|
+
return decorated
|
|
297
|
+
return r_addon_decorator
|
vortex/tools/arm.py
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Module needed to work with ARM tools such as Forge.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from bronx.fancies import loggers
|
|
6
|
+
|
|
7
|
+
from vortex.util.config import load_template
|
|
8
|
+
|
|
9
|
+
#: No automatic export
|
|
10
|
+
__all__ = ['ArmForgeTool']
|
|
11
|
+
|
|
12
|
+
logger = loggers.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ArmForgeTool:
|
|
16
|
+
"""Work with the ARM tools such as DDT & MAP."""
|
|
17
|
+
|
|
18
|
+
def __init__(self, ticket):
|
|
19
|
+
"""
|
|
20
|
+
:param ticket: The current Vortex' session ticket.
|
|
21
|
+
"""
|
|
22
|
+
self._t = ticket
|
|
23
|
+
self._sh = self._t.sh
|
|
24
|
+
self._config = self._sh.default_target.items('armtools')
|
|
25
|
+
self._ddtpath = self._sh.env.get('VORTEX_ARM_DDT_PATH', None)
|
|
26
|
+
self._mappath = self._sh.env.get('VORTEX_ARM_MAP_PATH', None)
|
|
27
|
+
self._forgedir = self._sh.env.get('VORTEX_ARM_FORGE_DIR',
|
|
28
|
+
self.config.get('forgedir', None))
|
|
29
|
+
self._forgeversion = self._sh.env.get('VORTEX_ARM_FORGE_VERSION',
|
|
30
|
+
self.config.get('forgeversion', 999999))
|
|
31
|
+
self._forgeversion = int(self._forgeversion)
|
|
32
|
+
if self._forgedir and self._ddtpath is None:
|
|
33
|
+
self._ddtpath = self._sh.path.join(self._forgedir, 'bin', 'ddt')
|
|
34
|
+
if self._forgedir and self._mappath is None:
|
|
35
|
+
self._mappath = self._sh.path.join(self._forgedir, 'bin', 'map')
|
|
36
|
+
|
|
37
|
+
@property
|
|
38
|
+
def config(self):
|
|
39
|
+
"""The configuration dictionary."""
|
|
40
|
+
return self._config
|
|
41
|
+
|
|
42
|
+
@property
|
|
43
|
+
def ddtpath(self):
|
|
44
|
+
"""The path to the DDT debuger executable."""
|
|
45
|
+
if self._ddtpath is None:
|
|
46
|
+
raise RuntimeError('DDT requested but the DDT path is not configured.')
|
|
47
|
+
return self._ddtpath
|
|
48
|
+
|
|
49
|
+
@property
|
|
50
|
+
def mappath(self):
|
|
51
|
+
"""The path to the MAP profiler executable."""
|
|
52
|
+
if self._mappath is None:
|
|
53
|
+
raise RuntimeError('MAP requested but the MAP path is not configured.')
|
|
54
|
+
return self._mappath
|
|
55
|
+
|
|
56
|
+
def _dump_forge_session(self, sources=(), workdir=None):
|
|
57
|
+
"""Create the ARM Forge's session file to list source directories."""
|
|
58
|
+
targetfile = 'armforge-vortex-session-file.ddt'
|
|
59
|
+
if workdir:
|
|
60
|
+
targetfile = self._sh.path.join(workdir, targetfile)
|
|
61
|
+
tpl = load_template(self._t, '@armforge-session-conf.tpl',
|
|
62
|
+
encoding='utf-8', version=self._forgeversion)
|
|
63
|
+
sconf = tpl.substitute(sourcedirs='\n'.join([' <directory>{:s}</directory>'.format(d)
|
|
64
|
+
for d in sources]))
|
|
65
|
+
with open(targetfile, 'w') as fhs:
|
|
66
|
+
fhs.write(sconf)
|
|
67
|
+
return targetfile
|
|
68
|
+
|
|
69
|
+
def ddt_prefix_cmd(self, sources=(), workdir=None):
|
|
70
|
+
"""Generate the prefix command required to start DDT."""
|
|
71
|
+
if sources:
|
|
72
|
+
return [self.ddtpath,
|
|
73
|
+
'--session={:s}'.format(self._dump_forge_session(sources, workdir=workdir)),
|
|
74
|
+
'--connect']
|
|
75
|
+
else:
|
|
76
|
+
return [self.ddtpath, '--connect']
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Stream/File compression tools.
|
|
3
|
+
|
|
4
|
+
The user interface for such tools is the :class:`CompressionPipeline`.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from contextlib import contextmanager
|
|
8
|
+
import io
|
|
9
|
+
import functools
|
|
10
|
+
import operator
|
|
11
|
+
|
|
12
|
+
import footprints
|
|
13
|
+
from bronx.fancies import loggers
|
|
14
|
+
from vortex.util.iosponge import IoSponge
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
#: No automatic export
|
|
18
|
+
__all__ = []
|
|
19
|
+
|
|
20
|
+
logger = loggers.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class CompressionPipeline:
|
|
24
|
+
"""Main interface to data compression algorithms."""
|
|
25
|
+
|
|
26
|
+
def __init__(self, system, compression=''):
|
|
27
|
+
"""
|
|
28
|
+
:param System system: The system object that will be used to carry out
|
|
29
|
+
the task.
|
|
30
|
+
:param str compression: The description of the compression tools that
|
|
31
|
+
will be used in this compression pipeline.
|
|
32
|
+
(e.g. 'gzip' will just compress/uncompress using the gzip software,
|
|
33
|
+
'gzip|bzip2' will use the gzip software piped to the bzip2 software
|
|
34
|
+
(which is useless), 'gzip&complevel=5' will use the gzip software
|
|
35
|
+
with a compression factor of 5.)
|
|
36
|
+
|
|
37
|
+
:note: See the subclasses of :class:`CompressionUnit` for a description
|
|
38
|
+
of available compression tools (and their options).
|
|
39
|
+
"""
|
|
40
|
+
self._units = list()
|
|
41
|
+
self._sh = system
|
|
42
|
+
self.description_string = compression
|
|
43
|
+
for c in [c for c in compression.split('|') if c]:
|
|
44
|
+
c_raw = c.split('&')
|
|
45
|
+
ckind = c_raw.pop(0)
|
|
46
|
+
cargs = dict([arg.split('=', 1) for arg in c_raw])
|
|
47
|
+
self.add_compression_unit(ckind, **cargs)
|
|
48
|
+
|
|
49
|
+
def add_compression_unit(self, unit, **kwargs):
|
|
50
|
+
"""Add a new compression tool to the compression pipeline.
|
|
51
|
+
|
|
52
|
+
:param str unit: The kind of the compression tool (see :class:`CompressionUnit`
|
|
53
|
+
subclases
|
|
54
|
+
:param kwargs: Options that will be used during the compression tool
|
|
55
|
+
initialisation
|
|
56
|
+
"""
|
|
57
|
+
c_unit = footprints.proxy.compression_unit(kind=unit, **kwargs)
|
|
58
|
+
if c_unit is None:
|
|
59
|
+
raise ValueError("The {:s} compression unit could not be found.".
|
|
60
|
+
format(unit))
|
|
61
|
+
self._units.append(c_unit)
|
|
62
|
+
|
|
63
|
+
@property
|
|
64
|
+
def units(self):
|
|
65
|
+
"""The list of compression tools forming the compression pipeline."""
|
|
66
|
+
return self._units
|
|
67
|
+
|
|
68
|
+
@property
|
|
69
|
+
def _rawftp_shell(self):
|
|
70
|
+
"""The name of the corresponding rawftp specialshell (if relevant)."""
|
|
71
|
+
if len(self.units) == 1:
|
|
72
|
+
return self.units[0].rawftp_shell(self._sh)
|
|
73
|
+
else:
|
|
74
|
+
return None
|
|
75
|
+
|
|
76
|
+
@property
|
|
77
|
+
def suffix(self):
|
|
78
|
+
"""The suffix usualy associated with this compression pipeline."""
|
|
79
|
+
s = '.'.join([s.suffix for s in self.units])
|
|
80
|
+
return '.' + s if s else ''
|
|
81
|
+
|
|
82
|
+
@property
|
|
83
|
+
def compression_factor(self):
|
|
84
|
+
"""The minimal compression factor expected with such a compression pipeline."""
|
|
85
|
+
return functools.reduce(operator.mul, [s.cfactor for s in self.units], 1.)
|
|
86
|
+
|
|
87
|
+
@staticmethod
|
|
88
|
+
def _inputstream_size(stream):
|
|
89
|
+
"""Find out the size of a seekable input stream."""
|
|
90
|
+
estimated_size = 0
|
|
91
|
+
try:
|
|
92
|
+
stream.seek(0, io.SEEK_END)
|
|
93
|
+
estimated_size = stream.tell()
|
|
94
|
+
stream.seek(0)
|
|
95
|
+
except AttributeError:
|
|
96
|
+
logger.warning('Could not rewind <source:%s>', str(stream))
|
|
97
|
+
except OSError:
|
|
98
|
+
logger.debug('Seek trouble <source:%s>', str(stream))
|
|
99
|
+
return estimated_size
|
|
100
|
+
|
|
101
|
+
@contextmanager
|
|
102
|
+
def _openstream(self, local, mode='rb'):
|
|
103
|
+
"""If *local* is not an opened file, open it..."""
|
|
104
|
+
if isinstance(local, str):
|
|
105
|
+
localfh = open(local, mode)
|
|
106
|
+
yield localfh
|
|
107
|
+
localfh.close()
|
|
108
|
+
elif isinstance(local, io.IOBase):
|
|
109
|
+
yield local
|
|
110
|
+
else:
|
|
111
|
+
raise ValueError("Unknown type for {!s}".format(local))
|
|
112
|
+
|
|
113
|
+
def _genericstream_close(self, processes):
|
|
114
|
+
"""Close a list of Popen objects (and look for the returncode)."""
|
|
115
|
+
for i, p in enumerate(processes):
|
|
116
|
+
if not self._sh.pclose(p):
|
|
117
|
+
logger.error("Abnormal return code for one of the processes (#%d)", i)
|
|
118
|
+
|
|
119
|
+
@contextmanager
|
|
120
|
+
def compress2stream(self, local, iosponge=False):
|
|
121
|
+
"""Compress *local* into a pipe or an :class:`IoSponge` object.
|
|
122
|
+
|
|
123
|
+
*local* can be an opened file-like object or a filename.
|
|
124
|
+
|
|
125
|
+
This method creates a context manager. Example::
|
|
126
|
+
|
|
127
|
+
source='myfile'
|
|
128
|
+
cp = CompressionPipeline(systemobj, 'gzip')
|
|
129
|
+
ftp = systemobj.ftp('hendrix.meteo.fr')
|
|
130
|
+
with cp.compress2stream(source) as csource:
|
|
131
|
+
ftp.put(csource, 'remote_compressedfile')
|
|
132
|
+
|
|
133
|
+
When leaving the context, the gzip process that compresses the data will
|
|
134
|
+
be properly closed
|
|
135
|
+
"""
|
|
136
|
+
with self._openstream(local) as stream:
|
|
137
|
+
estimated_size = self._inputstream_size(stream) * self.compression_factor
|
|
138
|
+
processes = list()
|
|
139
|
+
lstream = stream
|
|
140
|
+
for unit in self.units:
|
|
141
|
+
p = unit.compress(self._sh, lstream)
|
|
142
|
+
lstream = p.stdout
|
|
143
|
+
processes.append(p)
|
|
144
|
+
if iosponge:
|
|
145
|
+
yield IoSponge(lstream, guessed_size=estimated_size)
|
|
146
|
+
else:
|
|
147
|
+
yield lstream
|
|
148
|
+
self._genericstream_close(processes)
|
|
149
|
+
|
|
150
|
+
def _xcopyfileobj(self, in_fh, out_fh):
|
|
151
|
+
try:
|
|
152
|
+
self._sh.copyfileobj(in_fh, out_fh)
|
|
153
|
+
except OSError:
|
|
154
|
+
return False
|
|
155
|
+
else:
|
|
156
|
+
return True
|
|
157
|
+
|
|
158
|
+
def compress2file(self, local, destination):
|
|
159
|
+
"""Compress *local* into a file (named *destination*)
|
|
160
|
+
|
|
161
|
+
*local* can be an opened file-like object or a filename.
|
|
162
|
+
*destination* is a filename.
|
|
163
|
+
"""
|
|
164
|
+
with open(destination, 'wb') as fhout:
|
|
165
|
+
with self.compress2stream(local) as fhcompressed:
|
|
166
|
+
return self._xcopyfileobj(fhcompressed, fhout)
|
|
167
|
+
|
|
168
|
+
def compress2rawftp(self, local):
|
|
169
|
+
"""
|
|
170
|
+
Return the name of the rawftp's specialshell that can be used to
|
|
171
|
+
compress the *local* data. It might return None.
|
|
172
|
+
"""
|
|
173
|
+
return self._rawftp_shell
|
|
174
|
+
|
|
175
|
+
@contextmanager
|
|
176
|
+
def stream2uncompress(self, destination):
|
|
177
|
+
"""Uncompress piped data to *destination*.
|
|
178
|
+
|
|
179
|
+
*destination* can be an opened file-like object or a filename.
|
|
180
|
+
|
|
181
|
+
This method creates a context manager. Example::
|
|
182
|
+
|
|
183
|
+
destination='mydownloadedfile'
|
|
184
|
+
cp = CompressionPipeline(systemobj, 'gzip')
|
|
185
|
+
ftp = systemobj.ftp('hendrix.meteo.fr')
|
|
186
|
+
with cp.stream2uncompress(destination) as cdestination:
|
|
187
|
+
ftp.get('remote_compressedfile', cdestination)
|
|
188
|
+
|
|
189
|
+
When leaving the context, the gunzip process that uncompresses the data
|
|
190
|
+
will be properly closed.
|
|
191
|
+
"""
|
|
192
|
+
with self._openstream(destination, 'wb') as dstream:
|
|
193
|
+
processes = list()
|
|
194
|
+
instream = True
|
|
195
|
+
nunits = len(self.units)
|
|
196
|
+
for i, unit in enumerate(reversed(self.units)):
|
|
197
|
+
outstream = dstream if i == nunits - 1 else True
|
|
198
|
+
p = unit.uncompress(self._sh, instream, outstream)
|
|
199
|
+
instream = p.stdout
|
|
200
|
+
processes.append(p)
|
|
201
|
+
yield processes[0].stdin
|
|
202
|
+
self._genericstream_close(processes)
|
|
203
|
+
|
|
204
|
+
def file2uncompress(self, local, destination):
|
|
205
|
+
"""Uncompress *local* into *destination*.
|
|
206
|
+
|
|
207
|
+
*local* is a filename.
|
|
208
|
+
*destination* can be an opened file-like object or a filename.
|
|
209
|
+
"""
|
|
210
|
+
with self.stream2uncompress(destination) as fhuncompressed:
|
|
211
|
+
with open(local, 'rb') as fhcompressed:
|
|
212
|
+
return self._xcopyfileobj(fhcompressed, fhuncompressed)
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
class CompressionUnit(footprints.FootprintBase):
|
|
216
|
+
"""Defines compress/uncompress methods for a given compression tool."""
|
|
217
|
+
|
|
218
|
+
_abstract = True
|
|
219
|
+
_collector = ('compression_unit',)
|
|
220
|
+
_footprint = dict(
|
|
221
|
+
info = 'Abstract Compression Unit',
|
|
222
|
+
attr = dict(
|
|
223
|
+
kind = dict(
|
|
224
|
+
info = "The name of the compression tool.",
|
|
225
|
+
),
|
|
226
|
+
suffix = dict(
|
|
227
|
+
info = "The usual file extension for this compression tool.",
|
|
228
|
+
optional = True,
|
|
229
|
+
),
|
|
230
|
+
cfactor = dict(
|
|
231
|
+
info = "The usual compression factor for this compression tool.",
|
|
232
|
+
type = float,
|
|
233
|
+
default = 1.,
|
|
234
|
+
optional =True,
|
|
235
|
+
),
|
|
236
|
+
),
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
def rawftp_shell(self, sh):
|
|
240
|
+
"""The rawftp's speciall shell that may carry out a comparable compression."""
|
|
241
|
+
return None
|
|
242
|
+
|
|
243
|
+
def _run_in_pipe(self, sh, cmd, stream, outstream=True):
|
|
244
|
+
"""Run *cmd* with the piped input *stream*."""
|
|
245
|
+
p = sh.popen(cmd, stdin=stream, stdout=outstream, bufsize=8192)
|
|
246
|
+
return p
|
|
247
|
+
|
|
248
|
+
def compress(self, sh, stream):
|
|
249
|
+
"""Compress the input *stream*. Returns a Popen object."""
|
|
250
|
+
raise NotImplementedError()
|
|
251
|
+
|
|
252
|
+
def uncompress(self, sh, stream, outstream=True):
|
|
253
|
+
"""Uncompress the input *stream*. Returns a Popen object."""
|
|
254
|
+
raise NotImplementedError()
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
class GzipCompressionUnit(CompressionUnit):
|
|
258
|
+
|
|
259
|
+
_footprint = dict(
|
|
260
|
+
info = 'Compress/Uncompress a stream using gzip',
|
|
261
|
+
attr = dict(
|
|
262
|
+
kind = dict(
|
|
263
|
+
values = ['gzip', 'gz']
|
|
264
|
+
),
|
|
265
|
+
suffix = dict(
|
|
266
|
+
default = 'gz',
|
|
267
|
+
),
|
|
268
|
+
complevel = dict(
|
|
269
|
+
info = "The gzip algorithm compression level (see 'man gzip')",
|
|
270
|
+
type = int,
|
|
271
|
+
values = range(1, 10),
|
|
272
|
+
default = 6,
|
|
273
|
+
optional = True
|
|
274
|
+
),
|
|
275
|
+
cfactor = dict(
|
|
276
|
+
default = 0.9
|
|
277
|
+
),
|
|
278
|
+
),
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
def compress(self, sh, stream):
|
|
282
|
+
"""Compress the input *stream*. Returns a Popen object."""
|
|
283
|
+
return self._run_in_pipe(sh, ['gzip', '--stdout', '-{!s}'.format(self.complevel)],
|
|
284
|
+
stream)
|
|
285
|
+
|
|
286
|
+
def uncompress(self, sh, stream, outstream=True):
|
|
287
|
+
"""Uncompress the input *stream*. Returns a Popen object."""
|
|
288
|
+
return self._run_in_pipe(sh, ['gunzip', '--stdout'], stream, outstream)
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
class Bzip2CompressionUnit(CompressionUnit):
|
|
292
|
+
|
|
293
|
+
_footprint = dict(
|
|
294
|
+
info = 'Compress/Uncompress a stream using bzip2',
|
|
295
|
+
attr = dict(
|
|
296
|
+
kind = dict(
|
|
297
|
+
values = ['bzip2', 'bz2']
|
|
298
|
+
),
|
|
299
|
+
suffix = dict(
|
|
300
|
+
default = 'bz2',
|
|
301
|
+
),
|
|
302
|
+
complevel = dict(
|
|
303
|
+
info = "The bzip2 algorithm compression level (see 'man bzip2')",
|
|
304
|
+
type = int,
|
|
305
|
+
values = range(1, 10),
|
|
306
|
+
default = 9,
|
|
307
|
+
optional = True
|
|
308
|
+
),
|
|
309
|
+
cfactor = dict(
|
|
310
|
+
default = 0.85,
|
|
311
|
+
),
|
|
312
|
+
),
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
def compress(self, sh, stream):
|
|
316
|
+
"""Compress the input *stream*. Returns a Popen object."""
|
|
317
|
+
return self._run_in_pipe(sh, ['bzip2', '--stdout', '-{!s}'.format(self.complevel)],
|
|
318
|
+
stream)
|
|
319
|
+
|
|
320
|
+
def uncompress(self, sh, stream, outstream=True):
|
|
321
|
+
"""Uncompress the input *stream*. Returns a Popen object."""
|
|
322
|
+
return self._run_in_pipe(sh, ['bunzip2', '--stdout'], stream, outstream)
|