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/data/handlers.py
ADDED
|
@@ -0,0 +1,1230 @@
|
|
|
1
|
+
"""
|
|
2
|
+
:class:`Handler` class is a cornerstone in any Vortex script. :class:`Handler`
|
|
3
|
+
objects are in charge of manipulating data between the working directory and
|
|
4
|
+
the various caches or archives".
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import functools
|
|
8
|
+
import importlib
|
|
9
|
+
import re
|
|
10
|
+
import sys
|
|
11
|
+
|
|
12
|
+
import bronx.fancies.dump
|
|
13
|
+
from bronx.fancies import loggers
|
|
14
|
+
from bronx.patterns import observer
|
|
15
|
+
from bronx.stdtypes.history import History
|
|
16
|
+
|
|
17
|
+
import footprints
|
|
18
|
+
|
|
19
|
+
from vortex import sessions
|
|
20
|
+
|
|
21
|
+
from vortex.tools import net
|
|
22
|
+
from vortex.util import config
|
|
23
|
+
from vortex.layout import contexts, dataflow
|
|
24
|
+
from vortex.data import containers, resources, providers
|
|
25
|
+
|
|
26
|
+
#: No automatic export
|
|
27
|
+
__all__ = []
|
|
28
|
+
|
|
29
|
+
logger = loggers.getLogger(__name__)
|
|
30
|
+
|
|
31
|
+
OBSERVER_TAG = "Resources-Handlers"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class HandlerError(RuntimeError):
|
|
35
|
+
"""Exception in case of missing resource during the wait mechanism."""
|
|
36
|
+
|
|
37
|
+
pass
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def observer_board(obsname=None):
|
|
41
|
+
"""Proxy to :func:`footprints.observers.get`."""
|
|
42
|
+
if obsname is None:
|
|
43
|
+
obsname = OBSERVER_TAG
|
|
44
|
+
return observer.get(tag=obsname)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class IdCardAttrDumper(bronx.fancies.dump.TxtDumper):
|
|
48
|
+
"""Dump a text representation of almost any footprint object..."""
|
|
49
|
+
|
|
50
|
+
indent_size = 2
|
|
51
|
+
max_depth = 2
|
|
52
|
+
|
|
53
|
+
def __init__(self):
|
|
54
|
+
self._indent_first = 4
|
|
55
|
+
|
|
56
|
+
def _get_indent_first(self):
|
|
57
|
+
return self._indent_first
|
|
58
|
+
|
|
59
|
+
def _set_indent_first(self, val):
|
|
60
|
+
self._indent_first = val
|
|
61
|
+
|
|
62
|
+
indent_first = property(_get_indent_first, _set_indent_first)
|
|
63
|
+
|
|
64
|
+
def dump_fpattrs(self, fpobj, level=0):
|
|
65
|
+
"""Dump the attributes of a footprint based object."""
|
|
66
|
+
if level + 1 > self.max_depth:
|
|
67
|
+
return "{}{{...}}{}".format(
|
|
68
|
+
self._indent(level, self.break_before_dict_begin),
|
|
69
|
+
self._indent(level, self.break_after_dict_end),
|
|
70
|
+
)
|
|
71
|
+
else:
|
|
72
|
+
items = [
|
|
73
|
+
"{}{} = {}{},".format(
|
|
74
|
+
self._indent(level + 1, self.break_before_dict_key),
|
|
75
|
+
str(k),
|
|
76
|
+
self._indent(level + 2, self.break_before_dict_value),
|
|
77
|
+
self._recursive_dump(v, level + 1),
|
|
78
|
+
)
|
|
79
|
+
for k, v in sorted(fpobj.footprint_as_shallow_dict().items())
|
|
80
|
+
]
|
|
81
|
+
return " ".join(items)
|
|
82
|
+
|
|
83
|
+
def dump_default(self, obj, level=0, nextline=True):
|
|
84
|
+
"""Generic dump function. Concise view for GetByTag objects."""
|
|
85
|
+
if level + 1 > self.max_depth:
|
|
86
|
+
return " <%s...>" % type(obj).__class__
|
|
87
|
+
else:
|
|
88
|
+
if hasattr(obj, "tag"):
|
|
89
|
+
return "{:s} obj: tag={:s}".format(type(obj).__name__, obj.tag)
|
|
90
|
+
else:
|
|
91
|
+
parent_dump = super(
|
|
92
|
+
bronx.fancies.dump.TxtDumper, self
|
|
93
|
+
).dump_default(obj, level, nextline and self.break_default)
|
|
94
|
+
return "{:s} obj: {!s}".format(type(obj).__name__, parent_dump)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class Handler:
|
|
98
|
+
"""
|
|
99
|
+
The resource handler object gathers a provider, a resource and a container
|
|
100
|
+
for any specific resource.
|
|
101
|
+
|
|
102
|
+
Other parameters given at construct time are stored as options.
|
|
103
|
+
"""
|
|
104
|
+
|
|
105
|
+
def __init__(self, rd, **kw):
|
|
106
|
+
if "glove" in rd:
|
|
107
|
+
del rd["glove"]
|
|
108
|
+
self._resource = rd.pop("resource", None)
|
|
109
|
+
self._provider = rd.pop("provider", None)
|
|
110
|
+
self._container = rd.pop("container", None)
|
|
111
|
+
self._empty = rd.pop("empty", False)
|
|
112
|
+
self._contents = None
|
|
113
|
+
self._uridata = None
|
|
114
|
+
self._options = rd.copy()
|
|
115
|
+
self._observer = observer_board(obsname=kw.pop("observer", None))
|
|
116
|
+
self._options.update(kw)
|
|
117
|
+
self._mdcheck = self._options.pop("metadatacheck", False)
|
|
118
|
+
self._mddelta = self._options.pop("metadatadelta", dict())
|
|
119
|
+
self._ghost = self._options.pop("ghost", False)
|
|
120
|
+
hook_names = [x for x in self._options.keys() if x.startswith("hook_")]
|
|
121
|
+
self._hooks = {x[5:]: self._options.pop(x) for x in hook_names}
|
|
122
|
+
self._delayhooks = self._options.pop("delayhooks", False)
|
|
123
|
+
|
|
124
|
+
self._history = History(tag="data-handler")
|
|
125
|
+
self._history.append(self.__class__.__name__, "init", True)
|
|
126
|
+
self._stage = ["load"]
|
|
127
|
+
self._observer.notify_new(self, dict(stage="load"))
|
|
128
|
+
self._localpr_cache = None # To cache the promise dictionary
|
|
129
|
+
self._latest_earlyget_id = None
|
|
130
|
+
self._latest_earlyget_opts = None
|
|
131
|
+
logger.debug("New resource handler %s", self.__dict__)
|
|
132
|
+
|
|
133
|
+
def __str__(self):
|
|
134
|
+
return str(self.__dict__)
|
|
135
|
+
|
|
136
|
+
def _get_resource(self):
|
|
137
|
+
"""Getter for ``resource`` property."""
|
|
138
|
+
return self._resource
|
|
139
|
+
|
|
140
|
+
def _set_resource(self, value):
|
|
141
|
+
"""Setter for ``resource`` property."""
|
|
142
|
+
if isinstance(value, resources.Resource):
|
|
143
|
+
oldhash = self.simplified_hashkey
|
|
144
|
+
self._resource = value
|
|
145
|
+
self._notifyhash(oldhash)
|
|
146
|
+
self.reset_contents()
|
|
147
|
+
else:
|
|
148
|
+
raise ValueError(
|
|
149
|
+
"This value is not a plain Resource <{!s}>".format(value)
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
resource = property(_get_resource, _set_resource)
|
|
153
|
+
|
|
154
|
+
def _get_provider(self):
|
|
155
|
+
"""Getter for ``provider`` property."""
|
|
156
|
+
return self._provider
|
|
157
|
+
|
|
158
|
+
def _set_provider(self, value):
|
|
159
|
+
"""Setter for ``provider`` property."""
|
|
160
|
+
if isinstance(value, providers.Provider):
|
|
161
|
+
oldhash = self.simplified_hashkey
|
|
162
|
+
self._provider = value
|
|
163
|
+
self._notifyhash(oldhash)
|
|
164
|
+
self.reset_contents()
|
|
165
|
+
else:
|
|
166
|
+
raise ValueError(
|
|
167
|
+
"This value is not a plain Provider <{!s}>".format(value)
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
provider = property(_get_provider, _set_provider)
|
|
171
|
+
|
|
172
|
+
def _get_container(self):
|
|
173
|
+
"""Getter for ``container`` property."""
|
|
174
|
+
return self._container
|
|
175
|
+
|
|
176
|
+
def _set_container(self, value):
|
|
177
|
+
"""Setter for ``container`` property."""
|
|
178
|
+
if isinstance(value, containers.Container):
|
|
179
|
+
oldhash = self.simplified_hashkey
|
|
180
|
+
self._container = value
|
|
181
|
+
self._notifyhash(oldhash)
|
|
182
|
+
else:
|
|
183
|
+
raise ValueError(
|
|
184
|
+
"This value is not a plain Container <{!s}>".format(value)
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
container = property(_get_container, _set_container)
|
|
188
|
+
|
|
189
|
+
@property
|
|
190
|
+
def history(self):
|
|
191
|
+
return self._history
|
|
192
|
+
|
|
193
|
+
@property
|
|
194
|
+
def observer(self):
|
|
195
|
+
"""Footprint observer devoted to resource handlers tracking."""
|
|
196
|
+
return self._observer
|
|
197
|
+
|
|
198
|
+
def observers(self):
|
|
199
|
+
"""Remote objects observing the current resource handler... and maybe others."""
|
|
200
|
+
return self._observer.observers()
|
|
201
|
+
|
|
202
|
+
def observed(self):
|
|
203
|
+
"""Other objects observed by the observers of the current resource handler."""
|
|
204
|
+
return [x for x in self._observer.observed() if x is not self]
|
|
205
|
+
|
|
206
|
+
@property
|
|
207
|
+
def complete(self):
|
|
208
|
+
"""Returns whether all the internal components are defined."""
|
|
209
|
+
return bool(self.resource and self.provider and self.container)
|
|
210
|
+
|
|
211
|
+
@property
|
|
212
|
+
def stage(self):
|
|
213
|
+
"""Return current resource handler stage (load, get, put)."""
|
|
214
|
+
return self._stage[-1]
|
|
215
|
+
|
|
216
|
+
@property
|
|
217
|
+
def simplified_hashkey(self):
|
|
218
|
+
"""Returns a tuple that can be used as a hashkey to quickly identify the handler."""
|
|
219
|
+
if self.complete:
|
|
220
|
+
rkind = getattr(self.resource, "kind", None)
|
|
221
|
+
rfile = getattr(self.container, "filename", None)
|
|
222
|
+
return (rkind, rfile)
|
|
223
|
+
else:
|
|
224
|
+
return ("incomplete",)
|
|
225
|
+
|
|
226
|
+
@property
|
|
227
|
+
def _cur_session(self):
|
|
228
|
+
"""Return the current active session."""
|
|
229
|
+
return sessions.current()
|
|
230
|
+
|
|
231
|
+
@property
|
|
232
|
+
def _cur_context(self):
|
|
233
|
+
"""Return the current active context."""
|
|
234
|
+
return contexts.current()
|
|
235
|
+
|
|
236
|
+
def external_stage_update(self, newstage):
|
|
237
|
+
"""This method must not be used directly by users!
|
|
238
|
+
|
|
239
|
+
Update the stage upon request (e.g. the file has been fetched by another process).
|
|
240
|
+
"""
|
|
241
|
+
self._stage.append(newstage)
|
|
242
|
+
if newstage in ("get",):
|
|
243
|
+
self.container.updfill(True)
|
|
244
|
+
|
|
245
|
+
def _updstage(self, newstage, insitu=False):
|
|
246
|
+
"""Notify the new stage to any observing system."""
|
|
247
|
+
self._stage.append(newstage)
|
|
248
|
+
self._observer.notify_upd(self, dict(stage=newstage, insitu=insitu))
|
|
249
|
+
|
|
250
|
+
def _notifyhook(self, stage, hookname):
|
|
251
|
+
"""Notify that a hook function has been executed."""
|
|
252
|
+
self._observer.notify_upd(self, dict(stage=stage, hook=hookname))
|
|
253
|
+
|
|
254
|
+
def _notifyclear(self):
|
|
255
|
+
"""Notify that the hashkey has changed."""
|
|
256
|
+
self._observer.notify_upd(
|
|
257
|
+
self,
|
|
258
|
+
dict(
|
|
259
|
+
clear=True,
|
|
260
|
+
),
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
def _notifyhash(self, oldhash):
|
|
264
|
+
"""Notify that the hashkey has changed."""
|
|
265
|
+
self._observer.notify_upd(
|
|
266
|
+
self,
|
|
267
|
+
dict(
|
|
268
|
+
oldhash=oldhash,
|
|
269
|
+
),
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
def is_expected(self):
|
|
273
|
+
"""Return a boolean value according to the last stage value (expected or not)."""
|
|
274
|
+
return self.stage.startswith("expect")
|
|
275
|
+
|
|
276
|
+
@property
|
|
277
|
+
def contents(self):
|
|
278
|
+
"""
|
|
279
|
+
Returns an valid data layout object as long as the current handler
|
|
280
|
+
is complete and the container filled.
|
|
281
|
+
"""
|
|
282
|
+
if self._empty:
|
|
283
|
+
self.container.write("")
|
|
284
|
+
self._empty = False
|
|
285
|
+
if self.complete:
|
|
286
|
+
if self.container.filled or self.stage == "put":
|
|
287
|
+
if self._contents is None:
|
|
288
|
+
self._contents = self.resource.contents_handler(
|
|
289
|
+
datafmt=self.container.actualfmt
|
|
290
|
+
)
|
|
291
|
+
with self.container.iod_context():
|
|
292
|
+
self._contents.slurp(self.container)
|
|
293
|
+
return self._contents
|
|
294
|
+
else:
|
|
295
|
+
logger.warning(
|
|
296
|
+
"Contents requested on an empty container [%s]",
|
|
297
|
+
self.container,
|
|
298
|
+
)
|
|
299
|
+
else:
|
|
300
|
+
logger.warning(
|
|
301
|
+
"Contents requested for an uncomplete handler [%s]",
|
|
302
|
+
self.container,
|
|
303
|
+
)
|
|
304
|
+
return None
|
|
305
|
+
|
|
306
|
+
def reset_contents(self):
|
|
307
|
+
"""Delete actual internal reference to data contents manager."""
|
|
308
|
+
self._contents = None
|
|
309
|
+
|
|
310
|
+
@property
|
|
311
|
+
def ghost(self):
|
|
312
|
+
return self._ghost
|
|
313
|
+
|
|
314
|
+
@property
|
|
315
|
+
def hooks(self):
|
|
316
|
+
return self._hooks
|
|
317
|
+
|
|
318
|
+
@property
|
|
319
|
+
def options(self):
|
|
320
|
+
return self._options
|
|
321
|
+
|
|
322
|
+
@property
|
|
323
|
+
def delayhooks(self):
|
|
324
|
+
return self._delayhooks
|
|
325
|
+
|
|
326
|
+
def mkopts(self, *dicos, **kw):
|
|
327
|
+
"""Returns options associated to that handler and a system reference."""
|
|
328
|
+
opts = dict(
|
|
329
|
+
intent=dataflow.intent.IN,
|
|
330
|
+
fmt=self.container.actualfmt,
|
|
331
|
+
)
|
|
332
|
+
opts.update(self.options)
|
|
333
|
+
for d in dicos:
|
|
334
|
+
opts.update(d)
|
|
335
|
+
opts.update(kw)
|
|
336
|
+
return opts
|
|
337
|
+
|
|
338
|
+
def location(self, fatal=True):
|
|
339
|
+
"""Returns the URL as defined by the internal provider and resource."""
|
|
340
|
+
self._lasturl = None
|
|
341
|
+
if self.provider and self.resource:
|
|
342
|
+
try:
|
|
343
|
+
self._lasturl = self.provider.uri(self.resource)
|
|
344
|
+
except Exception as e:
|
|
345
|
+
if fatal:
|
|
346
|
+
raise
|
|
347
|
+
else:
|
|
348
|
+
return "OOPS: {!s} (but fatal is False)".format(e)
|
|
349
|
+
return self._lasturl
|
|
350
|
+
else:
|
|
351
|
+
logger.warning(
|
|
352
|
+
"Resource handler %s could not build location", self
|
|
353
|
+
)
|
|
354
|
+
return None
|
|
355
|
+
|
|
356
|
+
def idcard(self, indent=2):
|
|
357
|
+
"""
|
|
358
|
+
Returns a multilines documentation string with a summary
|
|
359
|
+
of the valuable information contained by this handler.
|
|
360
|
+
"""
|
|
361
|
+
tab = " " * indent
|
|
362
|
+
card = "\n".join(
|
|
363
|
+
(
|
|
364
|
+
"{0}Handler {1!r}",
|
|
365
|
+
"{0}{0}Complete : {2}",
|
|
366
|
+
"{0}{0}Options : {3}",
|
|
367
|
+
"{0}{0}Location : {4}",
|
|
368
|
+
)
|
|
369
|
+
).format(tab, self, self.complete, self.options, self.location())
|
|
370
|
+
if self.hooks:
|
|
371
|
+
card += "\n{0}{0}Hooks : {1}".format(
|
|
372
|
+
tab, ",".join(list(self.hooks.keys()))
|
|
373
|
+
)
|
|
374
|
+
d = IdCardAttrDumper(tag="idcarddumper")
|
|
375
|
+
d.reset()
|
|
376
|
+
d.indent_first = 2 * len(tab)
|
|
377
|
+
for subobj in ("resource", "provider", "container"):
|
|
378
|
+
obj = getattr(self, subobj, None)
|
|
379
|
+
if obj:
|
|
380
|
+
thisdoc = "{0}{0}{1:s} {2!r}".format(
|
|
381
|
+
tab, subobj.capitalize(), obj
|
|
382
|
+
)
|
|
383
|
+
thisdoc += d.dump_fpattrs(obj)
|
|
384
|
+
else:
|
|
385
|
+
thisdoc = "{0}{0}{1:s} undefined".format(
|
|
386
|
+
tab, subobj.capitalize()
|
|
387
|
+
)
|
|
388
|
+
card = card + "\n" + thisdoc
|
|
389
|
+
return card
|
|
390
|
+
|
|
391
|
+
def quickview(self, nb=0, indent=0):
|
|
392
|
+
"""Standard glance to objects."""
|
|
393
|
+
tab = " " * indent
|
|
394
|
+
print("{}{:02d}. {:s}".format(tab, nb, repr(self)))
|
|
395
|
+
print("{} Complete : {!s}".format(tab, self.complete))
|
|
396
|
+
for subobj in ("container", "provider", "resource"):
|
|
397
|
+
obj = getattr(self, subobj, None)
|
|
398
|
+
if obj:
|
|
399
|
+
print("{} {:10s}: {!s}".format(tab, subobj.capitalize(), obj))
|
|
400
|
+
|
|
401
|
+
def wide_key_lookup(self, key, exports=False, fatal=True):
|
|
402
|
+
"""Return the *key* attribute if it exists in the provider or resource.
|
|
403
|
+
|
|
404
|
+
If *exports* is True, the footprint_export() or the export_dict() function
|
|
405
|
+
is called upon the return value.
|
|
406
|
+
"""
|
|
407
|
+
try:
|
|
408
|
+
if key == "safeblock":
|
|
409
|
+
# In olive experiments, the block may contain an indication of
|
|
410
|
+
# the member's number. Usually we do not want to get that...
|
|
411
|
+
a_value = getattr(self.provider, "block")
|
|
412
|
+
a_value = re.sub(r"(member|fc)_?\d+/", "", a_value)
|
|
413
|
+
else:
|
|
414
|
+
a_value = getattr(self.provider, key)
|
|
415
|
+
except AttributeError:
|
|
416
|
+
try:
|
|
417
|
+
a_value = getattr(self.resource, key)
|
|
418
|
+
except AttributeError:
|
|
419
|
+
if fatal:
|
|
420
|
+
raise AttributeError(
|
|
421
|
+
"The {:s} attribute could not be found in {!r}".format(
|
|
422
|
+
key, self
|
|
423
|
+
)
|
|
424
|
+
)
|
|
425
|
+
else:
|
|
426
|
+
a_value = None
|
|
427
|
+
if exports:
|
|
428
|
+
if hasattr(a_value, "footprint_export"):
|
|
429
|
+
a_value = a_value.footprint_export()
|
|
430
|
+
elif hasattr(a_value, "export_dict"):
|
|
431
|
+
a_value = a_value.export_dict()
|
|
432
|
+
return a_value
|
|
433
|
+
|
|
434
|
+
def as_dict(self):
|
|
435
|
+
"""Produce a raw json-compatible dictionary."""
|
|
436
|
+
rhd = dict(options=dict())
|
|
437
|
+
for k, v in self.options.items():
|
|
438
|
+
try:
|
|
439
|
+
v = v.export_dict()
|
|
440
|
+
except (AttributeError, TypeError):
|
|
441
|
+
pass
|
|
442
|
+
rhd["options"][k] = v
|
|
443
|
+
for subobj in ("resource", "provider", "container"):
|
|
444
|
+
obj = getattr(self, subobj, None)
|
|
445
|
+
if obj is not None:
|
|
446
|
+
rhd[subobj] = obj.footprint_export()
|
|
447
|
+
return rhd
|
|
448
|
+
|
|
449
|
+
@property
|
|
450
|
+
def lasturl(self):
|
|
451
|
+
"""The last actual URL value evaluated."""
|
|
452
|
+
return self._lasturl
|
|
453
|
+
|
|
454
|
+
@property
|
|
455
|
+
def uridata(self):
|
|
456
|
+
"""Actual extra URI values after store definition."""
|
|
457
|
+
return self._uridata
|
|
458
|
+
|
|
459
|
+
@property
|
|
460
|
+
def store(self):
|
|
461
|
+
if self.resource and self.provider:
|
|
462
|
+
self._uridata = net.uriparse(self.location())
|
|
463
|
+
stopts = {
|
|
464
|
+
k: v for k, v in self.options.items() if k.startswith("stor")
|
|
465
|
+
}
|
|
466
|
+
return footprints.proxy.store(
|
|
467
|
+
scheme=self._uridata.pop("scheme"),
|
|
468
|
+
netloc=self._uridata.pop("netloc"),
|
|
469
|
+
**stopts,
|
|
470
|
+
)
|
|
471
|
+
else:
|
|
472
|
+
return None
|
|
473
|
+
|
|
474
|
+
def check(self, **extras):
|
|
475
|
+
"""Returns a stat-like information to the remote resource."""
|
|
476
|
+
rst = None
|
|
477
|
+
if self.resource and self.provider:
|
|
478
|
+
store = self.store
|
|
479
|
+
if store:
|
|
480
|
+
logger.debug(
|
|
481
|
+
"Check resource %s at %s from %s",
|
|
482
|
+
self,
|
|
483
|
+
self.lasturl,
|
|
484
|
+
store,
|
|
485
|
+
)
|
|
486
|
+
rst = store.check(self.uridata, self.mkopts(extras))
|
|
487
|
+
if rst and self._mdcheck:
|
|
488
|
+
logger.info(
|
|
489
|
+
"metadatacheck is on: we are forcing a real get()..."
|
|
490
|
+
)
|
|
491
|
+
# We are using a temporary fake container
|
|
492
|
+
mycontainer = footprints.proxy.container(
|
|
493
|
+
shouldfly=True, actualfmt=self.container.actualfmt
|
|
494
|
+
)
|
|
495
|
+
try:
|
|
496
|
+
tmp_options = self.mkopts(extras)
|
|
497
|
+
tmp_options["obs_notify"] = False
|
|
498
|
+
rst = store.get(
|
|
499
|
+
self.uridata, mycontainer.iotarget(), tmp_options
|
|
500
|
+
)
|
|
501
|
+
if rst:
|
|
502
|
+
if store.delayed:
|
|
503
|
+
logger.warning(
|
|
504
|
+
"The resource is expected... let's say that's fine."
|
|
505
|
+
)
|
|
506
|
+
else:
|
|
507
|
+
# Create the contents manually and drop it when we are done.
|
|
508
|
+
contents = self.resource.contents_handler(
|
|
509
|
+
datafmt=mycontainer.actualfmt
|
|
510
|
+
)
|
|
511
|
+
contents.slurp(mycontainer)
|
|
512
|
+
rst = contents.metadata_check(
|
|
513
|
+
self.resource, delta=self._mddelta
|
|
514
|
+
)
|
|
515
|
+
finally:
|
|
516
|
+
# Delete the temporary container
|
|
517
|
+
mycontainer.clear()
|
|
518
|
+
self.history.append(store.fullname(), "check", rst)
|
|
519
|
+
if rst and self.stage == "load":
|
|
520
|
+
# Indicate that the resource was checked
|
|
521
|
+
self._updstage("checked")
|
|
522
|
+
if not rst:
|
|
523
|
+
# Always signal failures
|
|
524
|
+
self._updstage("void")
|
|
525
|
+
else:
|
|
526
|
+
logger.error(
|
|
527
|
+
"Could not find any store to check %s", self.lasturl
|
|
528
|
+
)
|
|
529
|
+
else:
|
|
530
|
+
logger.error(
|
|
531
|
+
"Could not check a rh without defined resource and provider %s",
|
|
532
|
+
self,
|
|
533
|
+
)
|
|
534
|
+
return rst
|
|
535
|
+
|
|
536
|
+
def locate(self, **extras):
|
|
537
|
+
"""Try to figure out what would be the physical location of the resource."""
|
|
538
|
+
rst = None
|
|
539
|
+
if self.resource and self.provider:
|
|
540
|
+
store = self.store
|
|
541
|
+
if store:
|
|
542
|
+
logger.debug(
|
|
543
|
+
"Locate resource %s at %s from %s",
|
|
544
|
+
self,
|
|
545
|
+
self.lasturl,
|
|
546
|
+
store,
|
|
547
|
+
)
|
|
548
|
+
rst = store.locate(self.uridata, self.mkopts(extras))
|
|
549
|
+
self.history.append(store.fullname(), "locate", rst)
|
|
550
|
+
else:
|
|
551
|
+
logger.error(
|
|
552
|
+
"Could not find any store to locate %s", self.lasturl
|
|
553
|
+
)
|
|
554
|
+
else:
|
|
555
|
+
logger.error("Could not locate an incomplete rh %s", self)
|
|
556
|
+
return rst
|
|
557
|
+
|
|
558
|
+
def prestage(self, **extras):
|
|
559
|
+
"""Request the pre-staging of the remote resource."""
|
|
560
|
+
rst = None
|
|
561
|
+
if self.resource and self.provider:
|
|
562
|
+
store = self.store
|
|
563
|
+
if store:
|
|
564
|
+
logger.debug(
|
|
565
|
+
"Prestage resource %s at %s from %s",
|
|
566
|
+
self,
|
|
567
|
+
self.lasturl,
|
|
568
|
+
store,
|
|
569
|
+
)
|
|
570
|
+
rst = store.prestage(self.uridata, self.mkopts(extras))
|
|
571
|
+
self.history.append(store.fullname(), "prestage", rst)
|
|
572
|
+
else:
|
|
573
|
+
logger.error(
|
|
574
|
+
"Could not find any store to prestage %s", self.lasturl
|
|
575
|
+
)
|
|
576
|
+
else:
|
|
577
|
+
logger.error("Could not prestage an incomplete rh %s", self)
|
|
578
|
+
return rst
|
|
579
|
+
|
|
580
|
+
def _generic_apply_hooks(self, action, **extras):
|
|
581
|
+
"""Apply the hooks after a get request (or verify that they were done)."""
|
|
582
|
+
if self.hooks:
|
|
583
|
+
mytracker = extras.get("mytracker", None)
|
|
584
|
+
if mytracker is None:
|
|
585
|
+
iotarget = self.container.iotarget()
|
|
586
|
+
mytracker = self._cur_context.localtracker[iotarget]
|
|
587
|
+
for hook_name in sorted(self.hooks.keys()):
|
|
588
|
+
if mytracker.redundant_hook(action, hook_name):
|
|
589
|
+
logger.info(
|
|
590
|
+
"Hook already executed <hook_name:%s>", hook_name
|
|
591
|
+
)
|
|
592
|
+
else:
|
|
593
|
+
logger.info("Executing Hook <hook_name:%s>", hook_name)
|
|
594
|
+
hook_func, hook_args = self.hooks[hook_name]
|
|
595
|
+
hook_func(self._cur_session, self, *hook_args)
|
|
596
|
+
self._notifyhook(action, hook_name)
|
|
597
|
+
|
|
598
|
+
def apply_get_hooks(self, **extras):
|
|
599
|
+
"""Apply the hooks after a get request (or verify that they were done)."""
|
|
600
|
+
self._generic_apply_hooks(action="get", **extras)
|
|
601
|
+
|
|
602
|
+
def apply_put_hooks(self, **extras):
|
|
603
|
+
"""Apply the hooks before a put request (or verify that they were done)."""
|
|
604
|
+
self._generic_apply_hooks(action="put", **extras)
|
|
605
|
+
|
|
606
|
+
def _postproc_get(self, store, rst, extras):
|
|
607
|
+
self.container.updfill(rst)
|
|
608
|
+
# Check metadata if sensible
|
|
609
|
+
if self._mdcheck and rst and not store.delayed:
|
|
610
|
+
rst = self.contents.metadata_check(
|
|
611
|
+
self.resource, delta=self._mddelta
|
|
612
|
+
)
|
|
613
|
+
if not rst:
|
|
614
|
+
logger.info(
|
|
615
|
+
"We are now cleaning up the container and data contents."
|
|
616
|
+
)
|
|
617
|
+
self.reset_contents()
|
|
618
|
+
self.clear()
|
|
619
|
+
# For the record...
|
|
620
|
+
self.history.append(store.fullname(), "get", rst)
|
|
621
|
+
if rst:
|
|
622
|
+
# This is an expected resource
|
|
623
|
+
if store.delayed:
|
|
624
|
+
self._updstage("expected")
|
|
625
|
+
logger.info(
|
|
626
|
+
"Resource <%s> is expected", self.container.iotarget()
|
|
627
|
+
)
|
|
628
|
+
# This is a "real" resource
|
|
629
|
+
else:
|
|
630
|
+
self._updstage("get")
|
|
631
|
+
if self.hooks:
|
|
632
|
+
if not self.delayhooks:
|
|
633
|
+
self.apply_get_hooks(**extras)
|
|
634
|
+
else:
|
|
635
|
+
logger.info("(get-)Hooks were delayed")
|
|
636
|
+
else:
|
|
637
|
+
# Always signal failures
|
|
638
|
+
self._updstage("void")
|
|
639
|
+
return rst
|
|
640
|
+
|
|
641
|
+
def _actual_get(self, **extras):
|
|
642
|
+
"""Internal method in charge of getting the resource.
|
|
643
|
+
|
|
644
|
+
If requested, it will check the metadata of the resource and apply the
|
|
645
|
+
hook functions.
|
|
646
|
+
"""
|
|
647
|
+
rst = False
|
|
648
|
+
store = self.store
|
|
649
|
+
if store:
|
|
650
|
+
logger.debug(
|
|
651
|
+
"Get resource %s at %s from %s", self, self.lasturl, store
|
|
652
|
+
)
|
|
653
|
+
st_options = self.mkopts(dict(rhandler=self.as_dict()), extras)
|
|
654
|
+
# Actual get
|
|
655
|
+
try:
|
|
656
|
+
rst = store.get(
|
|
657
|
+
self.uridata,
|
|
658
|
+
self.container.iotarget(),
|
|
659
|
+
st_options,
|
|
660
|
+
)
|
|
661
|
+
except Exception:
|
|
662
|
+
rst = False
|
|
663
|
+
raise
|
|
664
|
+
finally:
|
|
665
|
+
rst = self._postproc_get(store, rst, extras)
|
|
666
|
+
else:
|
|
667
|
+
logger.error("Could not find any store to get %s", self.lasturl)
|
|
668
|
+
|
|
669
|
+
# Reset the promise dictionary cache
|
|
670
|
+
self._localpr_cache = None # To cache the promise dictionary
|
|
671
|
+
|
|
672
|
+
return rst
|
|
673
|
+
|
|
674
|
+
def _actual_earlyget(self, **extras):
|
|
675
|
+
"""Internal method in charge of requesting an earlyget on the resource.
|
|
676
|
+
|
|
677
|
+
:return: ``None`` if earlyget is unavailable (depending on the store's kind
|
|
678
|
+
and resource it can be perfectly fine). ``True`` if the resource was
|
|
679
|
+
actually fetched (no need to call :meth:`finaliseget`). Some kind of
|
|
680
|
+
non-null identifier that will be used to call :meth:`finaliseget`.
|
|
681
|
+
"""
|
|
682
|
+
try:
|
|
683
|
+
store = self.store
|
|
684
|
+
except Exception as e:
|
|
685
|
+
logger.error(
|
|
686
|
+
"The Resource handler was unable to create a store object (%s).",
|
|
687
|
+
str(e),
|
|
688
|
+
)
|
|
689
|
+
store = None
|
|
690
|
+
if store:
|
|
691
|
+
logger.debug(
|
|
692
|
+
"Early-Get resource %s at %s from %s",
|
|
693
|
+
self,
|
|
694
|
+
self.lasturl,
|
|
695
|
+
store,
|
|
696
|
+
)
|
|
697
|
+
st_options = self.mkopts(dict(rhandler=self.as_dict()), extras)
|
|
698
|
+
# Actual earlyget
|
|
699
|
+
try:
|
|
700
|
+
return store.earlyget(
|
|
701
|
+
self.uridata,
|
|
702
|
+
self.container.iotarget(),
|
|
703
|
+
st_options,
|
|
704
|
+
)
|
|
705
|
+
except Exception as e:
|
|
706
|
+
logger.error(
|
|
707
|
+
"The store's earlyget method did not return (%s): it should never append!",
|
|
708
|
+
str(e),
|
|
709
|
+
)
|
|
710
|
+
return None
|
|
711
|
+
else:
|
|
712
|
+
logger.error("Could not find any store to get %s", self.lasturl)
|
|
713
|
+
return None
|
|
714
|
+
|
|
715
|
+
def _get_proxy(self, callback, alternate=False, **extras):
|
|
716
|
+
"""
|
|
717
|
+
Process the **insitu** and **alternate** option and launch the **callback**
|
|
718
|
+
callable if sensible.
|
|
719
|
+
"""
|
|
720
|
+
rst = False
|
|
721
|
+
if self.complete:
|
|
722
|
+
if self.options.get(
|
|
723
|
+
"insitu", False
|
|
724
|
+
): # This a second pass (or third, forth, ...)
|
|
725
|
+
cur_tracker = self._cur_context.localtracker
|
|
726
|
+
cur_seq = self._cur_context.sequence
|
|
727
|
+
iotarget = self.container.iotarget()
|
|
728
|
+
# The localpath is here and listed in the tracker
|
|
729
|
+
if self.container.exists() and cur_tracker.is_tracked_input(
|
|
730
|
+
iotarget
|
|
731
|
+
):
|
|
732
|
+
# Am I consistent with the ResourceHandler recorded in the tracker ?
|
|
733
|
+
if cur_tracker[iotarget].match_rh("get", self):
|
|
734
|
+
rst = True
|
|
735
|
+
# There is the tricky usecase where we are dealing with an alternate
|
|
736
|
+
# that was already dealt with (yes, sometimes the nominal case and
|
|
737
|
+
# the alternate is the same !)
|
|
738
|
+
if not (
|
|
739
|
+
alternate
|
|
740
|
+
and iotarget
|
|
741
|
+
in [
|
|
742
|
+
s.rh.container.iotarget()
|
|
743
|
+
for s in cur_seq.effective_inputs()
|
|
744
|
+
]
|
|
745
|
+
):
|
|
746
|
+
self.container.updfill(True)
|
|
747
|
+
self._updstage("get", insitu=True)
|
|
748
|
+
logger.info(
|
|
749
|
+
"The <%s> resource is already here and matches the RH description :-)",
|
|
750
|
+
self.container.iotarget(),
|
|
751
|
+
)
|
|
752
|
+
else:
|
|
753
|
+
# This may happen if fatal=False and the local file was fetched
|
|
754
|
+
# by an alternate
|
|
755
|
+
if alternate:
|
|
756
|
+
if not self.container.is_virtual():
|
|
757
|
+
lpath = self.container.localpath()
|
|
758
|
+
for (
|
|
759
|
+
isec
|
|
760
|
+
) in self._cur_context.sequence.rinputs():
|
|
761
|
+
if (
|
|
762
|
+
isec.stage in ("get" or "expected")
|
|
763
|
+
and not isec.rh.container.is_virtual()
|
|
764
|
+
and isec.rh.container.localpath()
|
|
765
|
+
== lpath
|
|
766
|
+
):
|
|
767
|
+
rst = True
|
|
768
|
+
break
|
|
769
|
+
if rst:
|
|
770
|
+
logger.info(
|
|
771
|
+
"Alternate is on and the local file exists."
|
|
772
|
+
)
|
|
773
|
+
else:
|
|
774
|
+
logger.info(
|
|
775
|
+
"Alternate is on but the local file is not yet matched."
|
|
776
|
+
)
|
|
777
|
+
self._updstage("void", insitu=True)
|
|
778
|
+
else:
|
|
779
|
+
logger.info(
|
|
780
|
+
"Alternate is on. The local file exists. The container is virtual."
|
|
781
|
+
)
|
|
782
|
+
rst = True
|
|
783
|
+
else:
|
|
784
|
+
logger.info(
|
|
785
|
+
"The resource is already here but doesn't match the RH description :-("
|
|
786
|
+
)
|
|
787
|
+
cur_tracker[iotarget].match_rh(
|
|
788
|
+
"get", self, verbose=True
|
|
789
|
+
)
|
|
790
|
+
self._updstage("void", insitu=True)
|
|
791
|
+
# Bloody hell, the localpath doesn't exist
|
|
792
|
+
else:
|
|
793
|
+
rst = callback(
|
|
794
|
+
**extras
|
|
795
|
+
) # This might be an expected resource...
|
|
796
|
+
if rst:
|
|
797
|
+
logger.info(
|
|
798
|
+
"The resource was successfully fetched :-)"
|
|
799
|
+
)
|
|
800
|
+
else:
|
|
801
|
+
logger.info("Could not get the resource :-(")
|
|
802
|
+
else:
|
|
803
|
+
if alternate and self.container.exists():
|
|
804
|
+
logger.info("Alternate <%s> exists", alternate)
|
|
805
|
+
rst = True
|
|
806
|
+
else:
|
|
807
|
+
if self.container.exists():
|
|
808
|
+
logger.warning(
|
|
809
|
+
"The resource is already here: that should not happen at this stage !"
|
|
810
|
+
)
|
|
811
|
+
rst = callback(**extras)
|
|
812
|
+
else:
|
|
813
|
+
logger.error("Could not get an incomplete rh %s", self)
|
|
814
|
+
return rst
|
|
815
|
+
|
|
816
|
+
def get(self, alternate=False, **extras):
|
|
817
|
+
"""Method to retrieve the resource through the provider and feed the current container.
|
|
818
|
+
|
|
819
|
+
The behaviour of this method depends on the **insitu** and **alternate** options:
|
|
820
|
+
|
|
821
|
+
* When **insitu** is True, the :class:`~vortex.layout.dataflow.LocalTracker`
|
|
822
|
+
object associated with the active context is checked to determine
|
|
823
|
+
whether the resource has already been fetched or not. If not, another
|
|
824
|
+
try is made (but without using any non-cache store).
|
|
825
|
+
* When **insitu** is False, an attempt to get the resource is systematically
|
|
826
|
+
made except if **alternate** is defined and the local container already
|
|
827
|
+
exists.
|
|
828
|
+
"""
|
|
829
|
+
return self._get_proxy(self._actual_get, alternate=alternate, **extras)
|
|
830
|
+
|
|
831
|
+
def earlyget(self, alternate=False, **extras):
|
|
832
|
+
"""The earlyget feature is somehow a declaration of intent.
|
|
833
|
+
|
|
834
|
+
It records in the current context that, at some point in the future, we will
|
|
835
|
+
retrieve the present resource. It can be useful for some kind of stores
|
|
836
|
+
(and useless to others). For example, when using a store that targets a mass
|
|
837
|
+
archive system, this information can be used to ask for several files at
|
|
838
|
+
once, which accelerates the overall process and optimises the tape's drivers
|
|
839
|
+
usage. On the other hand, for a cache based store, it does not make much sense
|
|
840
|
+
since the data is readily available on disk.
|
|
841
|
+
|
|
842
|
+
Return values can be:
|
|
843
|
+
|
|
844
|
+
* ``None`` if earlyget is unavailable (depending on the store's kind
|
|
845
|
+
and resource it can be perfectly fine).
|
|
846
|
+
* Some kind of non-null identifier that will be used later on to actually
|
|
847
|
+
retrieve the resource. It is returned to the user as a diagnostic but is
|
|
848
|
+
also stored internally within the :class:`Handler` object.
|
|
849
|
+
* ``True`` if the resource has actually been retrieved through the provider
|
|
850
|
+
and fed into the current container.
|
|
851
|
+
|
|
852
|
+
In any case, the :meth:`finaliseget` method should be called later on
|
|
853
|
+
to actually retrieve the resource and feed the container. When ``True``
|
|
854
|
+
is returned by the :meth:`earlyget` method, the :meth:`finaliseget` call
|
|
855
|
+
can be made although it is useless.
|
|
856
|
+
|
|
857
|
+
Like with the :meth:`get` method, the behaviour of this method depends
|
|
858
|
+
on the **insitu** and **alternate** options:
|
|
859
|
+
|
|
860
|
+
* When **insitu** is True, the :class:`~vortex.layout.dataflow.LocalTracker`
|
|
861
|
+
object associated with the active context is checked to determine
|
|
862
|
+
whether the resource has already been fetched or not. If not, another
|
|
863
|
+
try is made (but without using any non-cache store).
|
|
864
|
+
* When **insitu** is False, an attempt to get the resource is systematically
|
|
865
|
+
made except if **alternate** is defined and the local container already
|
|
866
|
+
exists.
|
|
867
|
+
"""
|
|
868
|
+
r_opts = extras.copy()
|
|
869
|
+
self._latest_earlyget_opts = r_opts
|
|
870
|
+
self._latest_earlyget_opts["alternate"] = alternate
|
|
871
|
+
self._latest_earlyget_id = self._get_proxy(
|
|
872
|
+
self._actual_earlyget, alternate=alternate, **extras
|
|
873
|
+
)
|
|
874
|
+
return self._latest_earlyget_id
|
|
875
|
+
|
|
876
|
+
def finaliseget(self):
|
|
877
|
+
"""
|
|
878
|
+
When the :meth:`earlyget` method had previously been called, the
|
|
879
|
+
:meth:`finaliseget` can be called to finalise the ``get`` sequence.
|
|
880
|
+
|
|
881
|
+
When :meth:`finaliseget` returns, if the return code is non-zero, the resource
|
|
882
|
+
has been retrieved and fed into the container.
|
|
883
|
+
|
|
884
|
+
:raises HandlerError: if :meth:`earlyget` is not called prior to this
|
|
885
|
+
method.
|
|
886
|
+
"""
|
|
887
|
+
if (
|
|
888
|
+
self._latest_earlyget_id is None
|
|
889
|
+
and self._latest_earlyget_opts is None
|
|
890
|
+
):
|
|
891
|
+
raise HandlerError(
|
|
892
|
+
"earlyget was not called yet. Calling finaliseget is not Allowed !"
|
|
893
|
+
)
|
|
894
|
+
try:
|
|
895
|
+
if self._latest_earlyget_id is True:
|
|
896
|
+
# Nothing to be done...
|
|
897
|
+
return True
|
|
898
|
+
elif self._latest_earlyget_id is None:
|
|
899
|
+
# Delayed get not available... do the usual get !
|
|
900
|
+
e_opts = self._latest_earlyget_opts.copy()
|
|
901
|
+
e_opts["insitu"] = False
|
|
902
|
+
return self._get_proxy(self._actual_get, **e_opts)
|
|
903
|
+
else:
|
|
904
|
+
alternate = self._latest_earlyget_opts.get("alternate", False)
|
|
905
|
+
if alternate and self.container.exists():
|
|
906
|
+
# The container may have been filled be another finaliseget
|
|
907
|
+
logger.info("Alternate <%s> exists", alternate)
|
|
908
|
+
rst = True
|
|
909
|
+
else:
|
|
910
|
+
rst = False
|
|
911
|
+
store = self.store
|
|
912
|
+
if store:
|
|
913
|
+
logger.debug(
|
|
914
|
+
"Finalise-Get resource %s at %s from %s",
|
|
915
|
+
self,
|
|
916
|
+
self.lasturl,
|
|
917
|
+
store,
|
|
918
|
+
)
|
|
919
|
+
st_options = self.mkopts(
|
|
920
|
+
dict(rhandler=self.as_dict()),
|
|
921
|
+
self._latest_earlyget_opts,
|
|
922
|
+
)
|
|
923
|
+
# Actual get
|
|
924
|
+
rst = store.finaliseget(
|
|
925
|
+
self._latest_earlyget_id,
|
|
926
|
+
self.uridata,
|
|
927
|
+
self.container.iotarget(),
|
|
928
|
+
st_options,
|
|
929
|
+
)
|
|
930
|
+
if rst is None:
|
|
931
|
+
# Delayed get failed... attempt the usual get
|
|
932
|
+
logger.warning(
|
|
933
|
+
"Delayed get result was unclear ! Reverting to the usual get."
|
|
934
|
+
)
|
|
935
|
+
e_opts = self._latest_earlyget_opts.copy()
|
|
936
|
+
e_opts["insitu"] = False
|
|
937
|
+
return self._get_proxy(self._actual_get, **e_opts)
|
|
938
|
+
else:
|
|
939
|
+
rst = self._postproc_get(
|
|
940
|
+
store, rst, self._latest_earlyget_opts
|
|
941
|
+
)
|
|
942
|
+
else:
|
|
943
|
+
logger.error(
|
|
944
|
+
"Could not find any store to get %s", self.lasturl
|
|
945
|
+
)
|
|
946
|
+
|
|
947
|
+
# Reset the promise dictionary cache
|
|
948
|
+
self._localpr_cache = (
|
|
949
|
+
None # To cache the promise dictionary
|
|
950
|
+
)
|
|
951
|
+
|
|
952
|
+
return rst
|
|
953
|
+
finally:
|
|
954
|
+
self._latest_earlyget_id = None
|
|
955
|
+
self._latest_earlyget_opts = None
|
|
956
|
+
|
|
957
|
+
def insitu_quickget(self, alternate=False, **extras):
|
|
958
|
+
"""This method attempts a straightforward insitu get.
|
|
959
|
+
|
|
960
|
+
It is designed to minimise the amount of outputs when everything goes smoothly.
|
|
961
|
+
"""
|
|
962
|
+
rst = False
|
|
963
|
+
if self.complete:
|
|
964
|
+
if self.options.get(
|
|
965
|
+
"insitu", False
|
|
966
|
+
): # This a second pass (or third, forth, ...)
|
|
967
|
+
cur_tracker = self._cur_context.localtracker
|
|
968
|
+
cur_seq = self._cur_context.sequence
|
|
969
|
+
iotarget = self.container.iotarget()
|
|
970
|
+
# The localpath is here and listed in the tracker
|
|
971
|
+
if self.container.exists() and cur_tracker.is_tracked_input(
|
|
972
|
+
iotarget
|
|
973
|
+
):
|
|
974
|
+
if cur_tracker[iotarget].match_rh("get", self):
|
|
975
|
+
rst = True
|
|
976
|
+
# There is the tricky usecase where we are dealing with an alternate
|
|
977
|
+
# that was already dealt with (yes, sometimes the nominal case and
|
|
978
|
+
# the alternate is the same !)
|
|
979
|
+
if not (
|
|
980
|
+
alternate
|
|
981
|
+
and iotarget
|
|
982
|
+
in [
|
|
983
|
+
s.rh.container.iotarget()
|
|
984
|
+
for s in cur_seq.effective_inputs()
|
|
985
|
+
]
|
|
986
|
+
):
|
|
987
|
+
self.container.updfill(True)
|
|
988
|
+
self._updstage("get", insitu=True)
|
|
989
|
+
elif alternate:
|
|
990
|
+
# Alternate is on and the local file exists: check if
|
|
991
|
+
# the file has already been fetch previously in the sequence
|
|
992
|
+
if iotarget in [
|
|
993
|
+
s.rh.container.iotarget()
|
|
994
|
+
for s in cur_seq.effective_inputs()
|
|
995
|
+
]:
|
|
996
|
+
rst = True
|
|
997
|
+
else:
|
|
998
|
+
logger.error(
|
|
999
|
+
"This method should not be called with insitu=False (rh %s)",
|
|
1000
|
+
self,
|
|
1001
|
+
)
|
|
1002
|
+
return rst
|
|
1003
|
+
|
|
1004
|
+
def put(self, **extras):
|
|
1005
|
+
"""Method to store data from the current container through the provider.
|
|
1006
|
+
|
|
1007
|
+
Hook functions may be applied before the put in the designated store. We
|
|
1008
|
+
will ensure that a given hook function (identified by its name) is not
|
|
1009
|
+
applied more than once to the local container.
|
|
1010
|
+
|
|
1011
|
+
Conversely, the low-level stores are made aware of the previous successful
|
|
1012
|
+
put. That way, a local container is not put twice to the same destination.
|
|
1013
|
+
"""
|
|
1014
|
+
rst = False
|
|
1015
|
+
if self.complete:
|
|
1016
|
+
store = self.store
|
|
1017
|
+
if store:
|
|
1018
|
+
iotarget = self.container.iotarget()
|
|
1019
|
+
logger.debug(
|
|
1020
|
+
"Put resource %s as io %s at store %s",
|
|
1021
|
+
self,
|
|
1022
|
+
iotarget,
|
|
1023
|
+
store,
|
|
1024
|
+
)
|
|
1025
|
+
if iotarget is not None and (
|
|
1026
|
+
self.container.exists() or self.provider.expected
|
|
1027
|
+
):
|
|
1028
|
+
mytracker = self._cur_context.localtracker[iotarget]
|
|
1029
|
+
# Execute the hooks only if the local file exists
|
|
1030
|
+
if self.container.exists():
|
|
1031
|
+
self.container.updfill(True)
|
|
1032
|
+
if self.hooks:
|
|
1033
|
+
if not self.delayhooks:
|
|
1034
|
+
self.apply_put_hooks(
|
|
1035
|
+
mytracker=mytracker, **extras
|
|
1036
|
+
)
|
|
1037
|
+
else:
|
|
1038
|
+
logger.info("(put-)Hooks were delayed")
|
|
1039
|
+
# Add a filter function to remove duplicated PUTs to the same uri
|
|
1040
|
+
extras_ext = dict(extras)
|
|
1041
|
+
extras_ext["urifilter"] = functools.partial(
|
|
1042
|
+
mytracker.redundant_uri, "put"
|
|
1043
|
+
)
|
|
1044
|
+
# Actual put
|
|
1045
|
+
logger.debug(
|
|
1046
|
+
"Put resource %s at %s from %s",
|
|
1047
|
+
self,
|
|
1048
|
+
self.lasturl,
|
|
1049
|
+
store,
|
|
1050
|
+
)
|
|
1051
|
+
rst = store.put(
|
|
1052
|
+
iotarget,
|
|
1053
|
+
self.uridata,
|
|
1054
|
+
self.mkopts(dict(rhandler=self.as_dict()), extras_ext),
|
|
1055
|
+
)
|
|
1056
|
+
# For the record...
|
|
1057
|
+
self.history.append(store.fullname(), "put", rst)
|
|
1058
|
+
self._updstage("put")
|
|
1059
|
+
elif self.ghost:
|
|
1060
|
+
self.history.append(store.fullname(), "put", False)
|
|
1061
|
+
self._updstage("ghost")
|
|
1062
|
+
rst = True
|
|
1063
|
+
else:
|
|
1064
|
+
logger.error(
|
|
1065
|
+
"Could not find any source to put [%s]", iotarget
|
|
1066
|
+
)
|
|
1067
|
+
else:
|
|
1068
|
+
logger.error(
|
|
1069
|
+
"Could not find any store to put [%s]", self.lasturl
|
|
1070
|
+
)
|
|
1071
|
+
else:
|
|
1072
|
+
logger.error("Could not put an incomplete rh [%s]", self)
|
|
1073
|
+
return rst
|
|
1074
|
+
|
|
1075
|
+
def delete(self, **extras):
|
|
1076
|
+
"""Delete the remote resource from store."""
|
|
1077
|
+
rst = None
|
|
1078
|
+
if self.resource and self.provider:
|
|
1079
|
+
store = self.store
|
|
1080
|
+
if store:
|
|
1081
|
+
logger.debug(
|
|
1082
|
+
"Delete resource %s at %s from %s",
|
|
1083
|
+
self,
|
|
1084
|
+
self.lasturl,
|
|
1085
|
+
store,
|
|
1086
|
+
)
|
|
1087
|
+
rst = store.delete(
|
|
1088
|
+
self.uridata,
|
|
1089
|
+
self.mkopts(dict(rhandler=self.as_dict()), extras),
|
|
1090
|
+
)
|
|
1091
|
+
self.history.append(store.fullname(), "delete", rst)
|
|
1092
|
+
else:
|
|
1093
|
+
logger.error(
|
|
1094
|
+
"Could not find any store to delete %s", self.lasturl
|
|
1095
|
+
)
|
|
1096
|
+
else:
|
|
1097
|
+
logger.error(
|
|
1098
|
+
"Could not delete a rh without defined resource and provider %s",
|
|
1099
|
+
self,
|
|
1100
|
+
)
|
|
1101
|
+
return rst
|
|
1102
|
+
|
|
1103
|
+
def clear(self):
|
|
1104
|
+
"""Clear the local container contents."""
|
|
1105
|
+
rst = False
|
|
1106
|
+
if self.container:
|
|
1107
|
+
logger.debug("Remove resource container %s", self.container)
|
|
1108
|
+
rst = self.container.clear()
|
|
1109
|
+
self.history.append(self.container.actualpath(), "clear", rst)
|
|
1110
|
+
self._notifyclear()
|
|
1111
|
+
stage_clear_mapping = dict(expected="checked", get="checked")
|
|
1112
|
+
if self.stage in stage_clear_mapping:
|
|
1113
|
+
self._updstage(stage_clear_mapping[self.stage])
|
|
1114
|
+
return rst
|
|
1115
|
+
|
|
1116
|
+
def mkgetpr(
|
|
1117
|
+
self,
|
|
1118
|
+
pr_getter=None,
|
|
1119
|
+
tplfile=None,
|
|
1120
|
+
py_exec=sys.executable,
|
|
1121
|
+
py_opts="",
|
|
1122
|
+
):
|
|
1123
|
+
"""Build a getter for the expected resource."""
|
|
1124
|
+
if tplfile is None:
|
|
1125
|
+
tplfile = (
|
|
1126
|
+
"sync-" + ("fetch" if self.is_expected() else "skip") + ".tpl"
|
|
1127
|
+
)
|
|
1128
|
+
with importlib.resources.path(
|
|
1129
|
+
"vortex.data.sync_templates",
|
|
1130
|
+
tplfile,
|
|
1131
|
+
) as tplpath:
|
|
1132
|
+
tpl = config.load_template(tplpath)
|
|
1133
|
+
if pr_getter is None:
|
|
1134
|
+
pr_getter = self.container.localpath() + ".getpr"
|
|
1135
|
+
t = self._cur_session
|
|
1136
|
+
|
|
1137
|
+
with open(pr_getter, "w", encoding="utf-8") as fd:
|
|
1138
|
+
fd.write(
|
|
1139
|
+
tpl.substitute(
|
|
1140
|
+
python=py_exec,
|
|
1141
|
+
pyopts=py_opts,
|
|
1142
|
+
promise=self.container.localpath(),
|
|
1143
|
+
)
|
|
1144
|
+
)
|
|
1145
|
+
t.sh.chmod(pr_getter, 0o555)
|
|
1146
|
+
return pr_getter
|
|
1147
|
+
|
|
1148
|
+
@property
|
|
1149
|
+
def _localpr_json(self):
|
|
1150
|
+
if self.is_expected():
|
|
1151
|
+
if self._localpr_cache is None:
|
|
1152
|
+
self._localpr_cache = self._cur_session.sh.json_load(
|
|
1153
|
+
self.container.localpath()
|
|
1154
|
+
)
|
|
1155
|
+
return self._localpr_cache
|
|
1156
|
+
else:
|
|
1157
|
+
return None
|
|
1158
|
+
|
|
1159
|
+
def is_grabable(self, check_exists=False):
|
|
1160
|
+
"""Return if an expected resource is available or not.
|
|
1161
|
+
|
|
1162
|
+
Note: If it returns True, the user still needs to :meth:`get` the resource.
|
|
1163
|
+
"""
|
|
1164
|
+
rc = True
|
|
1165
|
+
if self.is_expected():
|
|
1166
|
+
pr = self._localpr_json
|
|
1167
|
+
itself = pr.get("itself")
|
|
1168
|
+
rc = not self._cur_session.sh.path.exists(itself)
|
|
1169
|
+
if rc and check_exists:
|
|
1170
|
+
remote = pr.get("locate").split(";")[0]
|
|
1171
|
+
rc = self._cur_session.sh.path.exists(remote)
|
|
1172
|
+
return rc
|
|
1173
|
+
|
|
1174
|
+
def wait(self, sleep=10, timeout=300, fatal=False):
|
|
1175
|
+
"""Wait for an expected resource or return immediately."""
|
|
1176
|
+
rc = True
|
|
1177
|
+
local = self.container.localpath()
|
|
1178
|
+
if self.is_expected():
|
|
1179
|
+
nb = 0
|
|
1180
|
+
sh = self._cur_session.sh
|
|
1181
|
+
pr = self._localpr_json
|
|
1182
|
+
itself = pr.get("itself")
|
|
1183
|
+
nbtries = int(timeout / sleep)
|
|
1184
|
+
logger.info(
|
|
1185
|
+
"Waiting %d x %d s. for expected resource <%s>",
|
|
1186
|
+
nbtries,
|
|
1187
|
+
sleep,
|
|
1188
|
+
local,
|
|
1189
|
+
)
|
|
1190
|
+
while sh.path.exists(itself):
|
|
1191
|
+
sh.sleep(sleep)
|
|
1192
|
+
nb += 1
|
|
1193
|
+
if nb > nbtries:
|
|
1194
|
+
logger.error("Could not wait anymore <%d>", nb)
|
|
1195
|
+
rc = False
|
|
1196
|
+
if fatal:
|
|
1197
|
+
logger.critical(
|
|
1198
|
+
"Missing expected resource is fatal <%s>", local
|
|
1199
|
+
)
|
|
1200
|
+
raise HandlerError("Expected resource missing")
|
|
1201
|
+
break
|
|
1202
|
+
else:
|
|
1203
|
+
remote = pr.get("locate").split(";")[0]
|
|
1204
|
+
if sh.path.exists(remote):
|
|
1205
|
+
logger.info(
|
|
1206
|
+
"Keeping promise for remote resource <%s>", remote
|
|
1207
|
+
)
|
|
1208
|
+
else:
|
|
1209
|
+
logger.warning(
|
|
1210
|
+
"Empty promise for remote resource <%s>", remote
|
|
1211
|
+
)
|
|
1212
|
+
rc = False
|
|
1213
|
+
else:
|
|
1214
|
+
logger.info("Resource <%s> not expected", local)
|
|
1215
|
+
return rc
|
|
1216
|
+
|
|
1217
|
+
def save(self):
|
|
1218
|
+
"""Rewrite data if contents have been updated."""
|
|
1219
|
+
rst = False
|
|
1220
|
+
if self.contents:
|
|
1221
|
+
rst = self.contents.rewrite(self.container)
|
|
1222
|
+
if not self.container.is_virtual():
|
|
1223
|
+
self.container.close()
|
|
1224
|
+
else:
|
|
1225
|
+
logger.warning("Try to save undefined contents %s", self)
|
|
1226
|
+
return rst
|
|
1227
|
+
|
|
1228
|
+
def strlast(self):
|
|
1229
|
+
"""String formatted log of the last action."""
|
|
1230
|
+
return " ".join([str(x) for x in self.history.last])
|