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/layout/dataflow.py
CHANGED
|
@@ -23,28 +23,29 @@ __all__ = []
|
|
|
23
23
|
|
|
24
24
|
logger = loggers.getLogger(__name__)
|
|
25
25
|
|
|
26
|
-
_RHANDLERS_OBSBOARD =
|
|
26
|
+
_RHANDLERS_OBSBOARD = "Resources-Handlers"
|
|
27
27
|
|
|
28
28
|
|
|
29
29
|
class SectionFatalError(Exception):
|
|
30
30
|
"""Exception when fatal mode is activated."""
|
|
31
|
+
|
|
31
32
|
pass
|
|
32
33
|
|
|
33
34
|
|
|
34
35
|
#: Definition of a named tuple INTENT
|
|
35
|
-
IntentTuple = namedtuple(
|
|
36
|
+
IntentTuple = namedtuple("IntentTuple", ["IN", "OUT", "INOUT"])
|
|
36
37
|
|
|
37
38
|
#: Predefined INTENT values IN, OUT and INOUT.
|
|
38
|
-
intent = IntentTuple(IN=
|
|
39
|
+
intent = IntentTuple(IN="in", OUT="out", INOUT="inout")
|
|
39
40
|
|
|
40
41
|
#: Definition of a named tuple IXO sequence
|
|
41
|
-
IXOTuple = namedtuple(
|
|
42
|
+
IXOTuple = namedtuple("IXOTuple", ["INPUT", "OUTPUT", "EXEC"])
|
|
42
43
|
|
|
43
44
|
#: Predefined IXO sequence values INPUT, OUTPUT and EXEC.
|
|
44
45
|
ixo = IXOTuple(INPUT=1, OUTPUT=2, EXEC=3)
|
|
45
46
|
|
|
46
47
|
#: Arguments specific to a section (to be striped away from a resource handler description)
|
|
47
|
-
section_args = [
|
|
48
|
+
section_args = ["role", "alternate", "intent", "fatal", "coherentgroup"]
|
|
48
49
|
|
|
49
50
|
|
|
50
51
|
def stripargs_section(**kw):
|
|
@@ -64,22 +65,28 @@ class _ReplaceSectionArgs:
|
|
|
64
65
|
Trigger the footprint's replacement mechanism on some of the section arguments.
|
|
65
66
|
"""
|
|
66
67
|
|
|
67
|
-
_REPL_TODO = (
|
|
68
|
+
_REPL_TODO = ("coherentgroup",)
|
|
68
69
|
|
|
69
70
|
def __init__(self):
|
|
70
|
-
self._fptmp = footprints.Footprint(
|
|
71
|
-
|
|
71
|
+
self._fptmp = footprints.Footprint(
|
|
72
|
+
attr={k: dict(optional=True) for k in self._REPL_TODO}
|
|
73
|
+
)
|
|
72
74
|
|
|
73
75
|
def __call__(self, rh, opts):
|
|
74
|
-
if any(
|
|
75
|
-
|
|
76
|
+
if any(
|
|
77
|
+
{
|
|
78
|
+
footprints.replattr.search(opts[k])
|
|
79
|
+
for k in self._REPL_TODO
|
|
80
|
+
if k in opts
|
|
81
|
+
}
|
|
82
|
+
):
|
|
76
83
|
# The "description"
|
|
77
84
|
desc = opts.copy()
|
|
78
85
|
if rh is not None:
|
|
79
86
|
desc.update(rh.options)
|
|
80
|
-
desc[
|
|
81
|
-
desc[
|
|
82
|
-
desc[
|
|
87
|
+
desc["container"] = rh.container
|
|
88
|
+
desc["provider"] = rh.provider
|
|
89
|
+
desc["resource"] = rh.resource
|
|
83
90
|
# Resolve
|
|
84
91
|
resolved, _, _ = self._fptmp.resolve(desc, fatal=False, fast=False)
|
|
85
92
|
# ok, let's use the resolved values
|
|
@@ -95,25 +102,28 @@ class Section:
|
|
|
95
102
|
"""Low level unit to handle a resource."""
|
|
96
103
|
|
|
97
104
|
def __init__(self, **kw):
|
|
98
|
-
logger.debug(
|
|
105
|
+
logger.debug("Section initialisation %s", self)
|
|
99
106
|
self.kind = ixo.INPUT
|
|
100
107
|
self.intent = intent.INOUT
|
|
101
108
|
self.fatal = True
|
|
102
109
|
# Fetch the ResourceHandler
|
|
103
|
-
self._rh = kw.pop(
|
|
110
|
+
self._rh = kw.pop("rh", None)
|
|
104
111
|
# We realy need a ResourceHandler...
|
|
105
112
|
if self.rh is None:
|
|
106
113
|
raise AttributeError("A proper rh attribute have to be provided")
|
|
107
114
|
# Call the footprint's replacement mechanism if needed
|
|
108
115
|
_default_replace_section_args(self._rh, kw)
|
|
109
116
|
# Process the remaining options
|
|
110
|
-
self._role = setrole(kw.pop(
|
|
111
|
-
self._alternate = setrole(kw.pop(
|
|
112
|
-
self._coherentgroups = kw.pop(
|
|
113
|
-
self._coherentgroups = set(
|
|
114
|
-
|
|
117
|
+
self._role = setrole(kw.pop("role", "anonymous"))
|
|
118
|
+
self._alternate = setrole(kw.pop("alternate", None))
|
|
119
|
+
self._coherentgroups = kw.pop("coherentgroup", None)
|
|
120
|
+
self._coherentgroups = set(
|
|
121
|
+
self._coherentgroups.split(",") if self._coherentgroups else []
|
|
122
|
+
)
|
|
115
123
|
self._coherentgroups_opened = {g: True for g in self._coherentgroups}
|
|
116
|
-
self.stages = [
|
|
124
|
+
self.stages = [
|
|
125
|
+
kw.pop("stage", "load"),
|
|
126
|
+
]
|
|
117
127
|
self.__dict__.update(kw)
|
|
118
128
|
# If alternate is specified role have to be removed
|
|
119
129
|
if self._alternate:
|
|
@@ -135,20 +145,28 @@ class Section:
|
|
|
135
145
|
@property
|
|
136
146
|
def any_coherentgroup_opened(self):
|
|
137
147
|
"""Is, at least, one belonging coherent group opened ?"""
|
|
138
|
-
return not self.coherentgroups or any(
|
|
148
|
+
return not self.coherentgroups or any(
|
|
149
|
+
self._coherentgroups_opened.values()
|
|
150
|
+
)
|
|
139
151
|
|
|
140
152
|
def coherent_group_close(self, group):
|
|
141
153
|
"""Close the coherent group (get and put will fail from now and on)."""
|
|
142
154
|
if group in self._coherentgroups_opened:
|
|
143
155
|
self._coherentgroups_opened[group] = False
|
|
144
156
|
# Another group's resource failed, re-checking and possibly deleting myself !
|
|
145
|
-
if
|
|
146
|
-
|
|
157
|
+
if (
|
|
158
|
+
self.stage in ("expected", "get")
|
|
159
|
+
and not self.any_coherentgroup_opened
|
|
160
|
+
):
|
|
161
|
+
logger.info(
|
|
162
|
+
"Clearing %s because of the coherent group failure.",
|
|
163
|
+
str(self.rh.container),
|
|
164
|
+
)
|
|
147
165
|
self.rh.clear()
|
|
148
166
|
|
|
149
167
|
def check_groupstatus(self, info):
|
|
150
168
|
"""Given the updstage's info dict, check that a coherent group still holds"""
|
|
151
|
-
return info.get(
|
|
169
|
+
return info.get("stage") != "void"
|
|
152
170
|
|
|
153
171
|
@property
|
|
154
172
|
def rh(self):
|
|
@@ -161,41 +179,49 @@ class Section:
|
|
|
161
179
|
|
|
162
180
|
def _updignore(self, info):
|
|
163
181
|
"""Fake function for undefined information driven updates."""
|
|
164
|
-
logger.warning(
|
|
182
|
+
logger.warning("Unable to update %s with info %s", self, info)
|
|
165
183
|
|
|
166
184
|
def _updstage_void(self, info):
|
|
167
185
|
"""Upgrade current section to 'checked' level."""
|
|
168
|
-
if info.get(
|
|
169
|
-
self.stages.append(
|
|
186
|
+
if info.get("stage") == "void" and self.kind in (ixo.INPUT, ixo.EXEC):
|
|
187
|
+
self.stages.append("void")
|
|
170
188
|
|
|
171
189
|
def _updstage_checked(self, info):
|
|
172
190
|
"""Upgrade current section to 'checked' level."""
|
|
173
|
-
if info.get(
|
|
174
|
-
|
|
191
|
+
if info.get("stage") == "checked" and self.kind in (
|
|
192
|
+
ixo.INPUT,
|
|
193
|
+
ixo.EXEC,
|
|
194
|
+
):
|
|
195
|
+
self.stages.append("checked")
|
|
175
196
|
|
|
176
197
|
def _updstage_get(self, info):
|
|
177
198
|
"""Upgrade current section to 'get' level."""
|
|
178
|
-
if info.get(
|
|
179
|
-
self.stages.append(
|
|
199
|
+
if info.get("stage") == "get" and self.kind in (ixo.INPUT, ixo.EXEC):
|
|
200
|
+
self.stages.append("get")
|
|
180
201
|
|
|
181
202
|
def _updstage_expected(self, info):
|
|
182
203
|
"""Upgrade current section to 'expected' level."""
|
|
183
|
-
if info.get(
|
|
184
|
-
|
|
204
|
+
if info.get("stage") == "expected" and self.kind in (
|
|
205
|
+
ixo.INPUT,
|
|
206
|
+
ixo.EXEC,
|
|
207
|
+
):
|
|
208
|
+
self.stages.append("expected")
|
|
185
209
|
|
|
186
210
|
def _updstage_put(self, info):
|
|
187
211
|
"""Upgrade current section to 'put' level."""
|
|
188
|
-
if info.get(
|
|
189
|
-
self.stages.append(
|
|
212
|
+
if info.get("stage") == "put" and self.kind == ixo.OUTPUT:
|
|
213
|
+
self.stages.append("put")
|
|
190
214
|
|
|
191
215
|
def _updstage_ghost(self, info):
|
|
192
216
|
"""Upgrade current section to 'ghost' level."""
|
|
193
|
-
if info.get(
|
|
194
|
-
self.stages.append(
|
|
217
|
+
if info.get("stage") == "ghost" and self.kind == ixo.OUTPUT:
|
|
218
|
+
self.stages.append("ghost")
|
|
195
219
|
|
|
196
220
|
def updstage(self, info):
|
|
197
221
|
"""Upgrade current section level according to information given in dict ``info``."""
|
|
198
|
-
updmethod = getattr(
|
|
222
|
+
updmethod = getattr(
|
|
223
|
+
self, "_updstage_" + info.get("stage"), self._updignore
|
|
224
|
+
)
|
|
199
225
|
updmethod(info)
|
|
200
226
|
|
|
201
227
|
def _stronglocate(self, **kw):
|
|
@@ -203,60 +229,76 @@ class Section:
|
|
|
203
229
|
try:
|
|
204
230
|
loc = self.rh.locate(**kw)
|
|
205
231
|
except Exception:
|
|
206
|
-
loc =
|
|
232
|
+
loc = "???"
|
|
207
233
|
return loc
|
|
208
234
|
|
|
209
235
|
def _fatal_wrap(self, sectiontype, callback, **kw):
|
|
210
236
|
"""Launch **callback** and process the returncode/exceptions according to **fatal**."""
|
|
211
|
-
action = {
|
|
237
|
+
action = {"input": "get", "output": "put"}[sectiontype]
|
|
212
238
|
rc = False
|
|
213
239
|
try:
|
|
214
240
|
rc = callback(**kw)
|
|
215
241
|
except Exception as e:
|
|
216
|
-
logger.error(
|
|
217
|
-
|
|
218
|
-
|
|
242
|
+
logger.error(
|
|
243
|
+
"Something wrong (%s section): %s. %s",
|
|
244
|
+
sectiontype,
|
|
245
|
+
str(e),
|
|
246
|
+
traceback.format_exc(),
|
|
247
|
+
)
|
|
248
|
+
logger.error("Resource %s", self._stronglocate())
|
|
219
249
|
if not rc and self.fatal:
|
|
220
|
-
logger.critical(
|
|
221
|
-
|
|
250
|
+
logger.critical(
|
|
251
|
+
"Fatal error with action %s on %s",
|
|
252
|
+
action,
|
|
253
|
+
self._stronglocate(),
|
|
254
|
+
)
|
|
255
|
+
raise SectionFatalError(
|
|
256
|
+
"Could not {:s} resource {!s}".format(action, rc)
|
|
257
|
+
)
|
|
222
258
|
return rc
|
|
223
259
|
|
|
224
260
|
def _just_fail(self, sectiontype, **kw): # @UnusedVariable
|
|
225
261
|
"""Check if a resource exists but fails anyway."""
|
|
226
|
-
action = {
|
|
262
|
+
action = {"input": "get", "output": "put"}[sectiontype]
|
|
227
263
|
rc = False
|
|
228
264
|
if self.fatal:
|
|
229
|
-
logger.critical(
|
|
230
|
-
|
|
265
|
+
logger.critical(
|
|
266
|
+
"Fatal error with action %s on %s",
|
|
267
|
+
action,
|
|
268
|
+
self._stronglocate(),
|
|
269
|
+
)
|
|
270
|
+
raise SectionFatalError(
|
|
271
|
+
"Could not {:s} resource {!s}".format(action, rc)
|
|
272
|
+
)
|
|
231
273
|
return rc
|
|
232
274
|
|
|
233
275
|
def get(self, **kw):
|
|
234
276
|
"""Shortcut to resource handler :meth:`~vortex.data.handlers.get`."""
|
|
235
277
|
if self.kind == ixo.INPUT or self.kind == ixo.EXEC:
|
|
236
278
|
if self.any_coherentgroup_opened:
|
|
237
|
-
kw[
|
|
279
|
+
kw["intent"] = self.intent
|
|
238
280
|
if self.alternate:
|
|
239
|
-
kw[
|
|
240
|
-
rc = self._fatal_wrap(
|
|
281
|
+
kw["alternate"] = self.alternate
|
|
282
|
+
rc = self._fatal_wrap("input", self.rh.get, **kw)
|
|
241
283
|
else:
|
|
242
284
|
logger.info("The coherent group is closed... doing nothing.")
|
|
243
|
-
rc = self._just_fail(
|
|
285
|
+
rc = self._just_fail("input")
|
|
244
286
|
else:
|
|
245
287
|
rc = False
|
|
246
|
-
logger.error(
|
|
288
|
+
logger.error("Try to get from an output section")
|
|
247
289
|
return rc
|
|
248
290
|
|
|
249
291
|
def finaliseget(self):
|
|
250
292
|
"""Shortcut to resource handler :meth:`~vortex.data.handlers.finaliseget`."""
|
|
251
293
|
if self.kind == ixo.INPUT or self.kind == ixo.EXEC:
|
|
252
294
|
if self.any_coherentgroup_opened:
|
|
253
|
-
rc = self._fatal_wrap(
|
|
295
|
+
rc = self._fatal_wrap("input", self.rh.finaliseget)
|
|
254
296
|
else:
|
|
255
297
|
logger.info("The coherent group is closed... doing nothing.")
|
|
256
|
-
rc = self._just_fail(
|
|
298
|
+
rc = self._just_fail("input")
|
|
257
299
|
else:
|
|
258
300
|
rc = False
|
|
259
|
-
logger.error(
|
|
301
|
+
logger.error("Try to get from an output section")
|
|
260
302
|
return rc
|
|
261
303
|
|
|
262
304
|
def earlyget(self, **kw):
|
|
@@ -264,10 +306,10 @@ class Section:
|
|
|
264
306
|
rc = False
|
|
265
307
|
if self.kind == ixo.INPUT or self.kind == ixo.EXEC:
|
|
266
308
|
if self.any_coherentgroup_opened:
|
|
267
|
-
kw[
|
|
309
|
+
kw["intent"] = self.intent
|
|
268
310
|
if self.alternate:
|
|
269
|
-
kw[
|
|
270
|
-
rc = self.rh.earlyget(**
|
|
311
|
+
kw["alternate"] = self.alternate
|
|
312
|
+
rc = self.rh.earlyget(**kw)
|
|
271
313
|
else:
|
|
272
314
|
rc = None
|
|
273
315
|
return rc
|
|
@@ -276,24 +318,29 @@ class Section:
|
|
|
276
318
|
"""Shortcut to resource handler :meth:`~vortex.data.handlers.put`."""
|
|
277
319
|
if self.kind == ixo.OUTPUT:
|
|
278
320
|
if self.any_coherentgroup_opened:
|
|
279
|
-
kw[
|
|
280
|
-
rc = self._fatal_wrap(
|
|
321
|
+
kw["intent"] = self.intent
|
|
322
|
+
rc = self._fatal_wrap("output", self.rh.put, **kw)
|
|
281
323
|
else:
|
|
282
324
|
logger.info("The coherent group is closed... failing !.")
|
|
283
325
|
rc = False
|
|
284
326
|
if self.fatal:
|
|
285
|
-
logger.critical(
|
|
286
|
-
|
|
327
|
+
logger.critical(
|
|
328
|
+
"Fatal error with action put on %s",
|
|
329
|
+
self._stronglocate(),
|
|
330
|
+
)
|
|
331
|
+
raise SectionFatalError(
|
|
332
|
+
"Could not get resource {!s}".format(rc)
|
|
333
|
+
)
|
|
287
334
|
else:
|
|
288
335
|
rc = False
|
|
289
|
-
logger.error(
|
|
336
|
+
logger.error("Try to put from an input section.")
|
|
290
337
|
return rc
|
|
291
338
|
|
|
292
339
|
def show(self, **kw):
|
|
293
340
|
"""Nice dump of the section attributes and contents."""
|
|
294
341
|
for k, v in sorted(vars(self).items()):
|
|
295
|
-
if k !=
|
|
296
|
-
print(
|
|
342
|
+
if k != "rh":
|
|
343
|
+
print(" ", k.ljust(16), ":", v)
|
|
297
344
|
self.rh.quickview(indent=1)
|
|
298
345
|
|
|
299
346
|
def as_dict(self):
|
|
@@ -306,12 +353,12 @@ class Section:
|
|
|
306
353
|
outdict["coherentgroup"] = ",".join(sorted(v))
|
|
307
354
|
elif k == "_coherentgroups_opened":
|
|
308
355
|
continue
|
|
309
|
-
elif k.startswith(
|
|
356
|
+
elif k.startswith("_"):
|
|
310
357
|
outdict[k[1:]] = v
|
|
311
358
|
else:
|
|
312
359
|
outdict[k] = v
|
|
313
360
|
# Add the latest stage
|
|
314
|
-
outdict[
|
|
361
|
+
outdict["stage"] = self.stage
|
|
315
362
|
return outdict
|
|
316
363
|
|
|
317
364
|
|
|
@@ -322,7 +369,7 @@ class Sequence(observer.Observer):
|
|
|
322
369
|
"""
|
|
323
370
|
|
|
324
371
|
def __init__(self, *args, **kw):
|
|
325
|
-
logger.debug(
|
|
372
|
+
logger.debug("Sequence initialisation %s", self)
|
|
326
373
|
self.sections = list()
|
|
327
374
|
# This hash table will be used to speedup the searches...
|
|
328
375
|
# If one uses the remove method, a WeakSet is not usefull. However,
|
|
@@ -367,7 +414,11 @@ class Sequence(observer.Observer):
|
|
|
367
414
|
if not self._coherentgroups_openings[cgroup]:
|
|
368
415
|
candidate.coherent_group_close(cgroup)
|
|
369
416
|
else:
|
|
370
|
-
logger.warning(
|
|
417
|
+
logger.warning(
|
|
418
|
+
"Try to add a non-section object %s in sequence %s",
|
|
419
|
+
candidate,
|
|
420
|
+
self,
|
|
421
|
+
)
|
|
371
422
|
|
|
372
423
|
def remove(self, candidate):
|
|
373
424
|
"""
|
|
@@ -376,59 +427,71 @@ class Sequence(observer.Observer):
|
|
|
376
427
|
"""
|
|
377
428
|
if isinstance(candidate, Section):
|
|
378
429
|
self.sections.remove(candidate)
|
|
379
|
-
self._sections_hash[candidate.rh.simplified_hashkey].discard(
|
|
430
|
+
self._sections_hash[candidate.rh.simplified_hashkey].discard(
|
|
431
|
+
candidate
|
|
432
|
+
)
|
|
380
433
|
for cgroup in candidate.coherentgroups:
|
|
381
434
|
self._coherentgroups[cgroup].discard(candidate)
|
|
382
435
|
else:
|
|
383
|
-
logger.warning(
|
|
436
|
+
logger.warning(
|
|
437
|
+
"Try to remove a non-section object %s in sequence %s",
|
|
438
|
+
candidate,
|
|
439
|
+
self,
|
|
440
|
+
)
|
|
384
441
|
|
|
385
442
|
def section(self, **kw):
|
|
386
443
|
"""Section factory wrapping a given ``rh`` (Resource Handler)."""
|
|
387
|
-
rhset = kw.get(
|
|
444
|
+
rhset = kw.get("rh", list())
|
|
388
445
|
if not isinstance(rhset, list):
|
|
389
|
-
rhset = [
|
|
390
|
-
|
|
446
|
+
rhset = [
|
|
447
|
+
rhset,
|
|
448
|
+
]
|
|
449
|
+
ralter = kw.get("alternate", kw.get("role", "anonymous"))
|
|
391
450
|
newsections = list()
|
|
392
451
|
for rh in rhset:
|
|
393
|
-
kw[
|
|
452
|
+
kw["rh"] = rh
|
|
394
453
|
this_section = Section(**kw)
|
|
395
454
|
self.add(this_section)
|
|
396
455
|
newsections.append(this_section)
|
|
397
|
-
kw[
|
|
398
|
-
if
|
|
399
|
-
del kw[
|
|
456
|
+
kw["alternate"] = ralter
|
|
457
|
+
if "role" in kw:
|
|
458
|
+
del kw["role"]
|
|
400
459
|
return newsections
|
|
401
460
|
|
|
402
461
|
def input(self, **kw):
|
|
403
462
|
"""Create a section with default kind equal to ``ixo.INPUT``."""
|
|
404
|
-
if
|
|
405
|
-
del kw[
|
|
406
|
-
kw.setdefault(
|
|
463
|
+
if "kind" in kw:
|
|
464
|
+
del kw["kind"]
|
|
465
|
+
kw.setdefault("intent", intent.IN)
|
|
407
466
|
return self.section(kind=ixo.INPUT, **kw)
|
|
408
467
|
|
|
409
468
|
def output(self, **kw):
|
|
410
469
|
"""Create a section with default kind equal to ``ixo.OUTPUT`` and intent equal to ``intent.OUT``."""
|
|
411
|
-
if
|
|
412
|
-
del kw[
|
|
413
|
-
kw.setdefault(
|
|
470
|
+
if "kind" in kw:
|
|
471
|
+
del kw["kind"]
|
|
472
|
+
kw.setdefault("intent", intent.OUT)
|
|
414
473
|
return self.section(kind=ixo.OUTPUT, **kw)
|
|
415
474
|
|
|
416
475
|
def executable(self, **kw):
|
|
417
476
|
"""Create a section with default kind equal to to ``ixo.EXEC``."""
|
|
418
|
-
if
|
|
419
|
-
del kw[
|
|
420
|
-
kw.setdefault(
|
|
477
|
+
if "kind" in kw:
|
|
478
|
+
del kw["kind"]
|
|
479
|
+
kw.setdefault("intent", intent.IN)
|
|
421
480
|
return self.section(kind=ixo.EXEC, **kw)
|
|
422
481
|
|
|
423
482
|
@staticmethod
|
|
424
483
|
def _fuzzy_match(stuff, allowed):
|
|
425
484
|
"""Check if ``stuff`` is in ``allowed``. ``allowed`` may contain regex."""
|
|
426
|
-
if
|
|
427
|
-
|
|
428
|
-
|
|
485
|
+
if isinstance(allowed, str) or not isinstance(
|
|
486
|
+
allowed, collections.abc.Iterable
|
|
487
|
+
):
|
|
488
|
+
allowed = [
|
|
489
|
+
allowed,
|
|
490
|
+
]
|
|
429
491
|
for pattern in allowed:
|
|
430
|
-
if (
|
|
431
|
-
|
|
492
|
+
if (isinstance(pattern, re.Pattern) and pattern.search(stuff)) or (
|
|
493
|
+
pattern == stuff
|
|
494
|
+
):
|
|
432
495
|
return True
|
|
433
496
|
return False
|
|
434
497
|
|
|
@@ -437,18 +500,31 @@ class Sequence(observer.Observer):
|
|
|
437
500
|
return list(sections)
|
|
438
501
|
inrole = list()
|
|
439
502
|
inkind = list()
|
|
440
|
-
with_alternates = not kw.get(
|
|
441
|
-
if
|
|
442
|
-
selectrole = mktuple(kw[
|
|
443
|
-
inrole = [
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
503
|
+
with_alternates = not kw.get("no_alternates", False)
|
|
504
|
+
if "role" in kw and kw["role"] is not None:
|
|
505
|
+
selectrole = mktuple(kw["role"])
|
|
506
|
+
inrole = [
|
|
507
|
+
x
|
|
508
|
+
for x in sections
|
|
509
|
+
if (
|
|
510
|
+
(
|
|
511
|
+
x.role is not None
|
|
512
|
+
and self._fuzzy_match(x.role, selectrole)
|
|
513
|
+
)
|
|
514
|
+
or (
|
|
515
|
+
with_alternates
|
|
516
|
+
and x.alternate is not None
|
|
517
|
+
and self._fuzzy_match(x.alternate, selectrole)
|
|
518
|
+
)
|
|
519
|
+
)
|
|
520
|
+
]
|
|
521
|
+
if not inrole and "kind" in kw:
|
|
522
|
+
selectkind = mktuple(kw["kind"])
|
|
523
|
+
inkind = [
|
|
524
|
+
x
|
|
525
|
+
for x in sections
|
|
526
|
+
if self._fuzzy_match(x.rh.resource.realkind, selectkind)
|
|
527
|
+
]
|
|
452
528
|
return inrole or inkind
|
|
453
529
|
|
|
454
530
|
def inputs(self):
|
|
@@ -472,8 +548,12 @@ class Sequence(observer.Observer):
|
|
|
472
548
|
Similar to :meth:`filtered_inputs` but only walk through the inputs of
|
|
473
549
|
that reached the 'get' or 'expected' stage.
|
|
474
550
|
"""
|
|
475
|
-
return [
|
|
476
|
-
|
|
551
|
+
return [
|
|
552
|
+
x
|
|
553
|
+
for x in self._section_list_filter(list(self.inputs()), **kw)
|
|
554
|
+
if (x.stage == "get" or x.stage == "expected")
|
|
555
|
+
and x.rh.container.exists()
|
|
556
|
+
]
|
|
477
557
|
|
|
478
558
|
def filtered_inputs(self, **kw):
|
|
479
559
|
"""Walk through the inputs of the current sequence.
|
|
@@ -493,15 +573,23 @@ class Sequence(observer.Observer):
|
|
|
493
573
|
def is_somehow_viable(self, section):
|
|
494
574
|
"""Tells wether *section* is ok or has a viable alternate."""
|
|
495
575
|
if section.role is None:
|
|
496
|
-
raise ValueError(
|
|
497
|
-
|
|
576
|
+
raise ValueError(
|
|
577
|
+
"An alternate section was given ; this is incorrect..."
|
|
578
|
+
)
|
|
579
|
+
if (
|
|
580
|
+
section.stage in ("get", "expected")
|
|
581
|
+
and section.rh.container.exists()
|
|
582
|
+
):
|
|
498
583
|
return section
|
|
499
584
|
else:
|
|
500
585
|
for isec in self.inputs():
|
|
501
|
-
if (
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
586
|
+
if (
|
|
587
|
+
isec.alternate == section.role
|
|
588
|
+
and isec.stage in ("get", "expected")
|
|
589
|
+
and isec.rh.container.localpath()
|
|
590
|
+
== section.rh.container.localpath()
|
|
591
|
+
and isec.rh.container.exists()
|
|
592
|
+
):
|
|
505
593
|
return isec
|
|
506
594
|
return None
|
|
507
595
|
|
|
@@ -541,7 +629,9 @@ class Sequence(observer.Observer):
|
|
|
541
629
|
|
|
542
630
|
for cgroup in a_section.coherentgroups:
|
|
543
631
|
if self._coherentgroups_openings[cgroup]:
|
|
544
|
-
if not all(
|
|
632
|
+
if not all(
|
|
633
|
+
map(_s_group_check, self.coherentgroup_iter(cgroup))
|
|
634
|
+
):
|
|
545
635
|
for c_section in self.coherentgroup_iter(cgroup):
|
|
546
636
|
c_section.coherent_group_close(cgroup)
|
|
547
637
|
self._coherentgroups_openings[cgroup] = False
|
|
@@ -551,13 +641,14 @@ class Sequence(observer.Observer):
|
|
|
551
641
|
Resources-Handlers observing facility.
|
|
552
642
|
Track hashkey alteration for the resource handler ``item``.
|
|
553
643
|
"""
|
|
554
|
-
if
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
oldhash = info['oldhash']
|
|
644
|
+
if info["observerboard"] == _RHANDLERS_OBSBOARD and "oldhash" in info:
|
|
645
|
+
logger.debug("Notified %s upd item %s", self, item)
|
|
646
|
+
oldhash = info["oldhash"]
|
|
558
647
|
# First remove the oldhash
|
|
559
648
|
if oldhash in self._sections_hash:
|
|
560
|
-
for section in [
|
|
649
|
+
for section in [
|
|
650
|
+
s for s in self._sections_hash[oldhash] if s.rh is item
|
|
651
|
+
]:
|
|
561
652
|
self._sections_hash[oldhash].discard(section)
|
|
562
653
|
# Then add the new hash: This is relatively slow so that it should not be used much...
|
|
563
654
|
for section in [s for s in self.sections if s.rh is item]:
|
|
@@ -581,39 +672,55 @@ class Sequence(observer.Observer):
|
|
|
581
672
|
elif trydict and isinstance(skeleton, dict):
|
|
582
673
|
# We assume it is a resource handler dictionary
|
|
583
674
|
try:
|
|
584
|
-
hkey = (
|
|
585
|
-
|
|
675
|
+
hkey = (
|
|
676
|
+
skeleton["resource"].get("kind", None),
|
|
677
|
+
skeleton["container"].get("filename", None),
|
|
678
|
+
)
|
|
586
679
|
except KeyError:
|
|
587
|
-
logger.critical(
|
|
680
|
+
logger.critical(
|
|
681
|
+
"This is probably not a ResourceHandler dictionary."
|
|
682
|
+
)
|
|
588
683
|
raise
|
|
589
684
|
return self._sections_hash[hkey]
|
|
590
|
-
raise ValueError(
|
|
685
|
+
raise ValueError(
|
|
686
|
+
"Cannot process a {!s} type skeleton".format(type(skeleton))
|
|
687
|
+
)
|
|
591
688
|
|
|
592
689
|
|
|
593
690
|
#: Class of a list of statuses
|
|
594
|
-
InputsReportStatusTupple = namedtuple(
|
|
595
|
-
|
|
691
|
+
InputsReportStatusTupple = namedtuple(
|
|
692
|
+
"InputsReportStatusTupple",
|
|
693
|
+
("PRESENT", "EXPECTED", "CHECKED", "MISSING", "UNUSED"),
|
|
694
|
+
)
|
|
596
695
|
|
|
597
696
|
|
|
598
697
|
#: Possible statuses used in :class:`SequenceInputsReport` objects
|
|
599
|
-
InputsReportStatus = InputsReportStatusTupple(
|
|
600
|
-
|
|
601
|
-
|
|
698
|
+
InputsReportStatus = InputsReportStatusTupple(
|
|
699
|
+
PRESENT="present",
|
|
700
|
+
EXPECTED="expected",
|
|
701
|
+
CHECKED="checked",
|
|
702
|
+
MISSING="missing",
|
|
703
|
+
UNUSED="unused",
|
|
704
|
+
)
|
|
602
705
|
|
|
603
706
|
|
|
604
707
|
class SequenceInputsReport:
|
|
605
708
|
"""Summarize data about inputs (missing resources, alternates, ...)."""
|
|
606
709
|
|
|
607
|
-
_TranslateStage = dict(
|
|
608
|
-
|
|
609
|
-
|
|
710
|
+
_TranslateStage = dict(
|
|
711
|
+
get=InputsReportStatus.PRESENT,
|
|
712
|
+
expected=InputsReportStatus.EXPECTED,
|
|
713
|
+
checked=InputsReportStatus.CHECKED,
|
|
714
|
+
void=InputsReportStatus.MISSING,
|
|
715
|
+
load=InputsReportStatus.UNUSED,
|
|
716
|
+
)
|
|
610
717
|
|
|
611
718
|
def __init__(self, inputs):
|
|
612
719
|
self._local_map = defaultdict(lambda: defaultdict(list))
|
|
613
720
|
for insec in inputs:
|
|
614
721
|
local = insec.rh.container.localpath()
|
|
615
722
|
# Determine if the current section is an alternate or not...
|
|
616
|
-
kind =
|
|
723
|
+
kind = "alternate" if insec.alternate is not None else "nominal"
|
|
617
724
|
self._local_map[local][kind].append(insec)
|
|
618
725
|
|
|
619
726
|
def _local_status(self, local):
|
|
@@ -627,20 +734,29 @@ class SequenceInputsReport:
|
|
|
627
734
|
"""
|
|
628
735
|
desc = self._local_map[local]
|
|
629
736
|
# First, check the nominal resource
|
|
630
|
-
if len(desc[
|
|
631
|
-
nominal = desc[
|
|
737
|
+
if len(desc["nominal"]) > 0:
|
|
738
|
+
nominal = desc["nominal"][-1]
|
|
632
739
|
status = self._TranslateStage[nominal.stage]
|
|
633
740
|
true_rh = nominal.rh
|
|
634
741
|
else:
|
|
635
|
-
logger.warning(
|
|
742
|
+
logger.warning(
|
|
743
|
+
"No nominal section for < %s >. This should not happened !",
|
|
744
|
+
local,
|
|
745
|
+
)
|
|
636
746
|
nominal = None
|
|
637
747
|
status = None
|
|
638
748
|
true_rh = None
|
|
639
749
|
# Look for alternates:
|
|
640
|
-
if status not in (
|
|
641
|
-
|
|
750
|
+
if status not in (
|
|
751
|
+
InputsReportStatus.PRESENT,
|
|
752
|
+
InputsReportStatus.EXPECTED,
|
|
753
|
+
):
|
|
754
|
+
for alter in desc["alternate"]:
|
|
642
755
|
alter_status = self._TranslateStage[alter.stage]
|
|
643
|
-
if alter_status in (
|
|
756
|
+
if alter_status in (
|
|
757
|
+
InputsReportStatus.PRESENT,
|
|
758
|
+
InputsReportStatus.EXPECTED,
|
|
759
|
+
):
|
|
644
760
|
status = alter_status
|
|
645
761
|
true_rh = alter.rh
|
|
646
762
|
break
|
|
@@ -662,24 +778,30 @@ class SequenceInputsReport:
|
|
|
662
778
|
else:
|
|
663
779
|
# Convert a single string to a list
|
|
664
780
|
if isinstance(only, str):
|
|
665
|
-
only = [
|
|
781
|
+
only = [
|
|
782
|
+
only,
|
|
783
|
+
]
|
|
666
784
|
# Check that the provided statuses exist
|
|
667
785
|
if not all([f in InputsReportStatus for f in only]):
|
|
668
786
|
return "* The only attribute is wrong ! ({!s})".format(only)
|
|
669
787
|
|
|
670
|
-
outstr =
|
|
788
|
+
outstr = ""
|
|
671
789
|
for local in sorted(self._local_map):
|
|
672
790
|
# For each and every local file, check alternates and find out the status
|
|
673
791
|
status, true_rh, nominal_rh = self._local_status(local)
|
|
674
|
-
extrainfo =
|
|
792
|
+
extrainfo = ""
|
|
675
793
|
# Detect alternates
|
|
676
|
-
is_alternate = status != InputsReportStatus.MISSING and (
|
|
794
|
+
is_alternate = status != InputsReportStatus.MISSING and (
|
|
795
|
+
true_rh is not nominal_rh
|
|
796
|
+
)
|
|
677
797
|
if is_alternate:
|
|
678
|
-
extrainfo =
|
|
798
|
+
extrainfo = "(ALTERNATE USED)"
|
|
679
799
|
# Alternates are always printed. Otherwise rely on **only**
|
|
680
800
|
if is_alternate or status in only:
|
|
681
|
-
outstr += "* {:8s} {:16s} : {:s}\n".format(
|
|
682
|
-
|
|
801
|
+
outstr += "* {:8s} {:16s} : {:s}\n".format(
|
|
802
|
+
status, extrainfo, local
|
|
803
|
+
)
|
|
804
|
+
if detailed and extrainfo != "":
|
|
683
805
|
outstr += " * The following resource is used:\n"
|
|
684
806
|
outstr += true_rh.idcard(indent=4) + "\n"
|
|
685
807
|
if nominal_rh is not None:
|
|
@@ -712,7 +834,9 @@ class SequenceInputsReport:
|
|
|
712
834
|
outstack = dict()
|
|
713
835
|
for local in self._local_map:
|
|
714
836
|
status, true_rh, nominal_rh = self._local_status(local)
|
|
715
|
-
if status != InputsReportStatus.MISSING and (
|
|
837
|
+
if status != InputsReportStatus.MISSING and (
|
|
838
|
+
true_rh is not nominal_rh
|
|
839
|
+
):
|
|
716
840
|
outstack[local] = (true_rh, nominal_rh)
|
|
717
841
|
return outstack
|
|
718
842
|
|
|
@@ -720,8 +844,11 @@ class SequenceInputsReport:
|
|
|
720
844
|
"""List the missing local resources."""
|
|
721
845
|
outstack = dict()
|
|
722
846
|
for local in self._local_map:
|
|
723
|
-
(
|
|
724
|
-
|
|
847
|
+
(
|
|
848
|
+
status,
|
|
849
|
+
true_rh, # @UnusedVariable
|
|
850
|
+
nominal_rh,
|
|
851
|
+
) = self._local_status(local)
|
|
725
852
|
if status == InputsReportStatus.MISSING:
|
|
726
853
|
outstack[local] = nominal_rh
|
|
727
854
|
return outstack
|
|
@@ -729,15 +856,21 @@ class SequenceInputsReport:
|
|
|
729
856
|
|
|
730
857
|
def _fast_clean_uri(store, remote):
|
|
731
858
|
"""Clean a URI so that it can be compared with a JSON load version."""
|
|
732
|
-
qsl = remote[
|
|
733
|
-
qsl.update(
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
859
|
+
qsl = remote["query"].copy()
|
|
860
|
+
qsl.update(
|
|
861
|
+
{
|
|
862
|
+
"storearg_{:s}".format(k): v
|
|
863
|
+
for k, v in store.tracking_extraargs.items()
|
|
864
|
+
}
|
|
865
|
+
)
|
|
866
|
+
return {
|
|
867
|
+
"scheme": str(store.scheme),
|
|
868
|
+
"netloc": str(store.netloc),
|
|
869
|
+
"path": str(remote["path"]),
|
|
870
|
+
"params": str(remote["params"]),
|
|
871
|
+
"query": qsl,
|
|
872
|
+
"fragment": str(remote["fragment"]),
|
|
873
|
+
}
|
|
741
874
|
|
|
742
875
|
|
|
743
876
|
class LocalTrackerEntry:
|
|
@@ -748,8 +881,11 @@ class LocalTrackerEntry:
|
|
|
748
881
|
stores are tracked.
|
|
749
882
|
"""
|
|
750
883
|
|
|
751
|
-
_actions = (
|
|
752
|
-
|
|
884
|
+
_actions = (
|
|
885
|
+
"get",
|
|
886
|
+
"put",
|
|
887
|
+
)
|
|
888
|
+
_internals = ("rhdict", "hook", "uri")
|
|
753
889
|
|
|
754
890
|
def __init__(self, master_tracker=None):
|
|
755
891
|
"""
|
|
@@ -771,8 +907,8 @@ class LocalTrackerEntry:
|
|
|
771
907
|
return json.loads(json.dumps(stuff))
|
|
772
908
|
|
|
773
909
|
def _clean_rhdict(self, rhdict):
|
|
774
|
-
if
|
|
775
|
-
del rhdict[
|
|
910
|
+
if "options" in rhdict:
|
|
911
|
+
del rhdict["options"]
|
|
776
912
|
return self._jsonize(rhdict)
|
|
777
913
|
|
|
778
914
|
def update_rh(self, rh, info):
|
|
@@ -784,13 +920,15 @@ class LocalTrackerEntry:
|
|
|
784
920
|
:param rh: :class:`~vortex.data.handlers.Handler` object that sends the update.
|
|
785
921
|
:param info: Info dictionary sent by the :class:`~vortex.data.handlers.Handler` object
|
|
786
922
|
"""
|
|
787
|
-
stage = info[
|
|
923
|
+
stage = info["stage"]
|
|
788
924
|
if self._check_action(stage):
|
|
789
|
-
if
|
|
790
|
-
self._data[
|
|
791
|
-
elif not info.get(
|
|
925
|
+
if "hook" in info:
|
|
926
|
+
self._data["hook"][stage].append(self._jsonize(info["hook"]))
|
|
927
|
+
elif not info.get("insitu", False):
|
|
792
928
|
# We are using as_dict since this may be written to a JSON file
|
|
793
|
-
self._data[
|
|
929
|
+
self._data["rhdict"][stage].append(
|
|
930
|
+
self._clean_rhdict(rh.as_dict())
|
|
931
|
+
)
|
|
794
932
|
|
|
795
933
|
def _update_store(self, info, uri):
|
|
796
934
|
"""Update the entry based on data received from the observer board.
|
|
@@ -801,10 +939,10 @@ class LocalTrackerEntry:
|
|
|
801
939
|
:param info: Info dictionary sent by the :class:`~vortex.data.stores.Store` object
|
|
802
940
|
:param uri: A cleaned (i.e. compatible with JSON) representation of the URI
|
|
803
941
|
"""
|
|
804
|
-
action = info[
|
|
942
|
+
action = info["action"]
|
|
805
943
|
# Only known action and successfull attempts
|
|
806
|
-
if self._check_action(action) and info[
|
|
807
|
-
self._data[
|
|
944
|
+
if self._check_action(action) and info["status"]:
|
|
945
|
+
self._data["uri"][action].append(uri)
|
|
808
946
|
if self._master_tracker is not None:
|
|
809
947
|
self._master_tracker.uri_map_append(self, action, uri)
|
|
810
948
|
|
|
@@ -820,22 +958,24 @@ class LocalTrackerEntry:
|
|
|
820
958
|
"""
|
|
821
959
|
self._data = dumpeddict
|
|
822
960
|
for action in self._actions:
|
|
823
|
-
for uri in self._data[
|
|
961
|
+
for uri in self._data["uri"][action]:
|
|
824
962
|
self._master_tracker.uri_map_append(self, action, uri)
|
|
825
963
|
|
|
826
964
|
def append(self, anotherentry):
|
|
827
965
|
"""Append the content of another LocalTrackerEntry object into this one."""
|
|
828
966
|
for internal in self._internals:
|
|
829
967
|
for act in self._actions:
|
|
830
|
-
self._data[internal][act].extend(
|
|
968
|
+
self._data[internal][act].extend(
|
|
969
|
+
anotherentry._data[internal][act]
|
|
970
|
+
)
|
|
831
971
|
|
|
832
972
|
def latest_rhdict(self, action):
|
|
833
973
|
"""Return the dictionary that represents the latest :class:`~vortex.data.handlers.Handler` object involved.
|
|
834
974
|
|
|
835
975
|
:param action: Action that is considered.
|
|
836
976
|
"""
|
|
837
|
-
if self._check_action(action) and self._data[
|
|
838
|
-
return self._data[
|
|
977
|
+
if self._check_action(action) and self._data["rhdict"][action]:
|
|
978
|
+
return self._data["rhdict"][action][-1]
|
|
839
979
|
else:
|
|
840
980
|
return dict()
|
|
841
981
|
|
|
@@ -853,9 +993,9 @@ class LocalTrackerEntry:
|
|
|
853
993
|
for key, item in latest.items():
|
|
854
994
|
newitem = cleaned.get(key, None)
|
|
855
995
|
if newitem != item:
|
|
856
|
-
logger.error(
|
|
996
|
+
logger.error("Expected %s:", key)
|
|
857
997
|
logger.error(pprint.pformat(item))
|
|
858
|
-
logger.error(
|
|
998
|
+
logger.error("Got:")
|
|
859
999
|
logger.error(pprint.pformat(newitem))
|
|
860
1000
|
return res
|
|
861
1001
|
else:
|
|
@@ -869,10 +1009,10 @@ class LocalTrackerEntry:
|
|
|
869
1009
|
|
|
870
1010
|
:param uri: A cleaned (i.e. compatible with JSON) representation of the URI
|
|
871
1011
|
"""
|
|
872
|
-
while uri in self._data[
|
|
873
|
-
self._data[
|
|
1012
|
+
while uri in self._data["uri"]["put"]:
|
|
1013
|
+
self._data["uri"]["put"].remove(uri)
|
|
874
1014
|
if self._master_tracker is not None:
|
|
875
|
-
self._master_tracker.uri_map_remove(self,
|
|
1015
|
+
self._master_tracker.uri_map_remove(self, "put", uri)
|
|
876
1016
|
|
|
877
1017
|
def _redundant_stuff(self, internal, action, stuff):
|
|
878
1018
|
if self._check_action(action):
|
|
@@ -886,7 +1026,7 @@ class LocalTrackerEntry:
|
|
|
886
1026
|
:param action: Action that is considered.
|
|
887
1027
|
:param hookname: Name of the Hook function that will be checked.
|
|
888
1028
|
"""
|
|
889
|
-
return self._redundant_stuff(
|
|
1029
|
+
return self._redundant_stuff("hook", action, self._jsonize(hookname))
|
|
890
1030
|
|
|
891
1031
|
def redundant_uri(self, action, store, remote):
|
|
892
1032
|
"""Check if an URI has already been processed.
|
|
@@ -895,7 +1035,9 @@ class LocalTrackerEntry:
|
|
|
895
1035
|
:param store: :class:`~vortex.data.stores.Store` object that will be checked.
|
|
896
1036
|
:param remote: Remote path that will be checked.
|
|
897
1037
|
"""
|
|
898
|
-
return self._redundant_stuff(
|
|
1038
|
+
return self._redundant_stuff(
|
|
1039
|
+
"uri", action, _fast_clean_uri(store, remote)
|
|
1040
|
+
)
|
|
899
1041
|
|
|
900
1042
|
def _grep_stuff(self, internal, action, skeleton=dict()):
|
|
901
1043
|
stack = []
|
|
@@ -903,20 +1045,24 @@ class LocalTrackerEntry:
|
|
|
903
1045
|
if isinstance(element, collections.abc.Mapping):
|
|
904
1046
|
succeed = True
|
|
905
1047
|
for key, val in skeleton.items():
|
|
906
|
-
succeed = succeed and (
|
|
1048
|
+
succeed = succeed and (
|
|
1049
|
+
(key in element) and (element[key] == val)
|
|
1050
|
+
)
|
|
907
1051
|
if succeed:
|
|
908
1052
|
stack.append(element)
|
|
909
1053
|
return stack
|
|
910
1054
|
|
|
911
1055
|
def __str__(self):
|
|
912
|
-
out =
|
|
1056
|
+
out = ""
|
|
913
1057
|
for action in self._actions:
|
|
914
1058
|
for internal in self._internals:
|
|
915
1059
|
if len(self._data[internal][action]) > 0:
|
|
916
1060
|
out += "+ {:4s} / {}\n{}\n".format(
|
|
917
1061
|
action.upper(),
|
|
918
1062
|
internal,
|
|
919
|
-
EncodedPrettyPrinter().pformat(
|
|
1063
|
+
EncodedPrettyPrinter().pformat(
|
|
1064
|
+
self._data[internal][action]
|
|
1065
|
+
),
|
|
920
1066
|
)
|
|
921
1067
|
return out
|
|
922
1068
|
|
|
@@ -929,7 +1075,7 @@ class LocalTracker(defaultdict):
|
|
|
929
1075
|
object.
|
|
930
1076
|
"""
|
|
931
1077
|
|
|
932
|
-
_default_json_filename =
|
|
1078
|
+
_default_json_filename = "local-tracker-state.json"
|
|
933
1079
|
|
|
934
1080
|
def __init__(self):
|
|
935
1081
|
super().__init__()
|
|
@@ -972,13 +1118,14 @@ class LocalTracker(defaultdict):
|
|
|
972
1118
|
"""
|
|
973
1119
|
lpath = rh.container.iotarget()
|
|
974
1120
|
if isinstance(lpath, str):
|
|
975
|
-
if info.get(
|
|
1121
|
+
if info.get("clear", False):
|
|
976
1122
|
self.pop(lpath, None)
|
|
977
1123
|
else:
|
|
978
1124
|
self[lpath].update_rh(rh, info)
|
|
979
1125
|
else:
|
|
980
|
-
logger.debug(
|
|
981
|
-
|
|
1126
|
+
logger.debug(
|
|
1127
|
+
"The iotarget is not a str: skipped in %s", self.__class__
|
|
1128
|
+
)
|
|
982
1129
|
|
|
983
1130
|
def update_store(self, store, info):
|
|
984
1131
|
"""Update the object based on data received from the observer board.
|
|
@@ -989,21 +1136,23 @@ class LocalTracker(defaultdict):
|
|
|
989
1136
|
:param store: :class:`~vortex.data.stores.Store` object that sends the update.
|
|
990
1137
|
:param info: Info dictionary sent by the :class:`~vortex.data.stores.Store` object
|
|
991
1138
|
"""
|
|
992
|
-
lpath = info.get(
|
|
1139
|
+
lpath = info.get("local", None)
|
|
993
1140
|
if lpath is None:
|
|
994
1141
|
# Check for file deleted on the remote side
|
|
995
|
-
if info[
|
|
996
|
-
clean_uri = _fast_clean_uri(store, info[
|
|
1142
|
+
if info["action"] == "del" and info["status"]:
|
|
1143
|
+
clean_uri = _fast_clean_uri(store, info["remote"])
|
|
997
1144
|
huri = self._hashable_uri(clean_uri)
|
|
998
|
-
for atracker in list(self._uri_map[
|
|
1145
|
+
for atracker in list(self._uri_map["put"][huri]):
|
|
999
1146
|
atracker._check_uri_remote_delete(clean_uri)
|
|
1000
1147
|
else:
|
|
1001
1148
|
if isinstance(lpath, str):
|
|
1002
|
-
clean_uri = _fast_clean_uri(store, info[
|
|
1149
|
+
clean_uri = _fast_clean_uri(store, info["remote"])
|
|
1003
1150
|
self[lpath]._update_store(info, clean_uri)
|
|
1004
1151
|
else:
|
|
1005
|
-
logger.debug(
|
|
1006
|
-
|
|
1152
|
+
logger.debug(
|
|
1153
|
+
"The iotarget isn't a str: It will be skipped in %s",
|
|
1154
|
+
self.__class__,
|
|
1155
|
+
)
|
|
1007
1156
|
|
|
1008
1157
|
def is_tracked_input(self, local):
|
|
1009
1158
|
"""Check if the given `local` container is listed as an input and associated with
|
|
@@ -1011,9 +1160,11 @@ class LocalTracker(defaultdict):
|
|
|
1011
1160
|
|
|
1012
1161
|
:param local: Local name of the input that will be checked
|
|
1013
1162
|
"""
|
|
1014
|
-
return (
|
|
1015
|
-
|
|
1016
|
-
|
|
1163
|
+
return (
|
|
1164
|
+
isinstance(local, str)
|
|
1165
|
+
and (local in self)
|
|
1166
|
+
and (self[local].latest_rhdict("get"))
|
|
1167
|
+
)
|
|
1017
1168
|
|
|
1018
1169
|
def _grep_stuff(self, internal, action, skeleton=dict()):
|
|
1019
1170
|
stack = []
|
|
@@ -1027,7 +1178,7 @@ class LocalTracker(defaultdict):
|
|
|
1027
1178
|
:param action: Action that is considered.
|
|
1028
1179
|
:param skeleton: Dictionary that will be used as a search pattern
|
|
1029
1180
|
"""
|
|
1030
|
-
return self._grep_stuff(
|
|
1181
|
+
return self._grep_stuff("uri", action, skeleton)
|
|
1031
1182
|
|
|
1032
1183
|
def json_dump(self, filename=_default_json_filename):
|
|
1033
1184
|
"""Dump the object to a JSON file.
|
|
@@ -1035,7 +1186,7 @@ class LocalTracker(defaultdict):
|
|
|
1035
1186
|
:param filename: Path to the JSON file.
|
|
1036
1187
|
"""
|
|
1037
1188
|
outdict = {loc: entry.dump_as_dict() for loc, entry in self.items()}
|
|
1038
|
-
with open(filename,
|
|
1189
|
+
with open(filename, "w", encoding="utf-8") as fpout:
|
|
1039
1190
|
json.dump(outdict, fpout, indent=2, sort_keys=True)
|
|
1040
1191
|
|
|
1041
1192
|
def json_load(self, filename=_default_json_filename):
|
|
@@ -1043,7 +1194,7 @@ class LocalTracker(defaultdict):
|
|
|
1043
1194
|
|
|
1044
1195
|
:param filename: Path to the JSON file.
|
|
1045
1196
|
"""
|
|
1046
|
-
with open(filename, encoding=
|
|
1197
|
+
with open(filename, encoding="utf-8") as fpin:
|
|
1047
1198
|
indict = json.load(fpin)
|
|
1048
1199
|
# Start from scratch
|
|
1049
1200
|
self.clear()
|
|
@@ -1061,7 +1212,7 @@ class LocalTracker(defaultdict):
|
|
|
1061
1212
|
self.append(other)
|
|
1062
1213
|
|
|
1063
1214
|
def __str__(self):
|
|
1064
|
-
out =
|
|
1215
|
+
out = ""
|
|
1065
1216
|
for loc, entry in self.items():
|
|
1066
1217
|
entryout = str(entry)
|
|
1067
1218
|
if entryout:
|