vortex-nwp 2.0.0b1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (146) hide show
  1. vortex/__init__.py +135 -0
  2. vortex/algo/__init__.py +12 -0
  3. vortex/algo/components.py +2136 -0
  4. vortex/algo/mpitools.py +1648 -0
  5. vortex/algo/mpitools_templates/envelope_wrapper_default.tpl +27 -0
  6. vortex/algo/mpitools_templates/envelope_wrapper_mpiauto.tpl +29 -0
  7. vortex/algo/mpitools_templates/wrapstd_wrapper_default.tpl +18 -0
  8. vortex/algo/serversynctools.py +170 -0
  9. vortex/config.py +115 -0
  10. vortex/data/__init__.py +13 -0
  11. vortex/data/abstractstores.py +1572 -0
  12. vortex/data/containers.py +780 -0
  13. vortex/data/contents.py +596 -0
  14. vortex/data/executables.py +284 -0
  15. vortex/data/flow.py +113 -0
  16. vortex/data/geometries.ini +2689 -0
  17. vortex/data/geometries.py +703 -0
  18. vortex/data/handlers.py +1021 -0
  19. vortex/data/outflow.py +67 -0
  20. vortex/data/providers.py +465 -0
  21. vortex/data/resources.py +201 -0
  22. vortex/data/stores.py +1271 -0
  23. vortex/gloves.py +282 -0
  24. vortex/layout/__init__.py +27 -0
  25. vortex/layout/appconf.py +109 -0
  26. vortex/layout/contexts.py +511 -0
  27. vortex/layout/dataflow.py +1069 -0
  28. vortex/layout/jobs.py +1276 -0
  29. vortex/layout/monitor.py +833 -0
  30. vortex/layout/nodes.py +1424 -0
  31. vortex/layout/subjobs.py +464 -0
  32. vortex/nwp/__init__.py +11 -0
  33. vortex/nwp/algo/__init__.py +12 -0
  34. vortex/nwp/algo/assim.py +483 -0
  35. vortex/nwp/algo/clim.py +920 -0
  36. vortex/nwp/algo/coupling.py +609 -0
  37. vortex/nwp/algo/eda.py +632 -0
  38. vortex/nwp/algo/eps.py +613 -0
  39. vortex/nwp/algo/forecasts.py +745 -0
  40. vortex/nwp/algo/fpserver.py +927 -0
  41. vortex/nwp/algo/ifsnaming.py +403 -0
  42. vortex/nwp/algo/ifsroot.py +311 -0
  43. vortex/nwp/algo/monitoring.py +202 -0
  44. vortex/nwp/algo/mpitools.py +554 -0
  45. vortex/nwp/algo/odbtools.py +974 -0
  46. vortex/nwp/algo/oopsroot.py +735 -0
  47. vortex/nwp/algo/oopstests.py +186 -0
  48. vortex/nwp/algo/request.py +579 -0
  49. vortex/nwp/algo/stdpost.py +1285 -0
  50. vortex/nwp/data/__init__.py +12 -0
  51. vortex/nwp/data/assim.py +392 -0
  52. vortex/nwp/data/boundaries.py +261 -0
  53. vortex/nwp/data/climfiles.py +539 -0
  54. vortex/nwp/data/configfiles.py +149 -0
  55. vortex/nwp/data/consts.py +929 -0
  56. vortex/nwp/data/ctpini.py +133 -0
  57. vortex/nwp/data/diagnostics.py +181 -0
  58. vortex/nwp/data/eda.py +148 -0
  59. vortex/nwp/data/eps.py +383 -0
  60. vortex/nwp/data/executables.py +1039 -0
  61. vortex/nwp/data/fields.py +96 -0
  62. vortex/nwp/data/gridfiles.py +308 -0
  63. vortex/nwp/data/logs.py +551 -0
  64. vortex/nwp/data/modelstates.py +334 -0
  65. vortex/nwp/data/monitoring.py +220 -0
  66. vortex/nwp/data/namelists.py +644 -0
  67. vortex/nwp/data/obs.py +748 -0
  68. vortex/nwp/data/oopsexec.py +72 -0
  69. vortex/nwp/data/providers.py +182 -0
  70. vortex/nwp/data/query.py +217 -0
  71. vortex/nwp/data/stores.py +147 -0
  72. vortex/nwp/data/surfex.py +338 -0
  73. vortex/nwp/syntax/__init__.py +9 -0
  74. vortex/nwp/syntax/stdattrs.py +375 -0
  75. vortex/nwp/tools/__init__.py +10 -0
  76. vortex/nwp/tools/addons.py +35 -0
  77. vortex/nwp/tools/agt.py +55 -0
  78. vortex/nwp/tools/bdap.py +48 -0
  79. vortex/nwp/tools/bdcp.py +38 -0
  80. vortex/nwp/tools/bdm.py +21 -0
  81. vortex/nwp/tools/bdmp.py +49 -0
  82. vortex/nwp/tools/conftools.py +1311 -0
  83. vortex/nwp/tools/drhook.py +62 -0
  84. vortex/nwp/tools/grib.py +268 -0
  85. vortex/nwp/tools/gribdiff.py +99 -0
  86. vortex/nwp/tools/ifstools.py +163 -0
  87. vortex/nwp/tools/igastuff.py +249 -0
  88. vortex/nwp/tools/mars.py +56 -0
  89. vortex/nwp/tools/odb.py +548 -0
  90. vortex/nwp/tools/partitioning.py +234 -0
  91. vortex/nwp/tools/satrad.py +56 -0
  92. vortex/nwp/util/__init__.py +6 -0
  93. vortex/nwp/util/async.py +184 -0
  94. vortex/nwp/util/beacon.py +40 -0
  95. vortex/nwp/util/diffpygram.py +359 -0
  96. vortex/nwp/util/ens.py +198 -0
  97. vortex/nwp/util/hooks.py +128 -0
  98. vortex/nwp/util/taskdeco.py +81 -0
  99. vortex/nwp/util/usepygram.py +591 -0
  100. vortex/nwp/util/usetnt.py +87 -0
  101. vortex/proxy.py +6 -0
  102. vortex/sessions.py +341 -0
  103. vortex/syntax/__init__.py +9 -0
  104. vortex/syntax/stdattrs.py +628 -0
  105. vortex/syntax/stddeco.py +176 -0
  106. vortex/toolbox.py +982 -0
  107. vortex/tools/__init__.py +11 -0
  108. vortex/tools/actions.py +457 -0
  109. vortex/tools/addons.py +297 -0
  110. vortex/tools/arm.py +76 -0
  111. vortex/tools/compression.py +322 -0
  112. vortex/tools/date.py +20 -0
  113. vortex/tools/ddhpack.py +10 -0
  114. vortex/tools/delayedactions.py +672 -0
  115. vortex/tools/env.py +513 -0
  116. vortex/tools/folder.py +663 -0
  117. vortex/tools/grib.py +559 -0
  118. vortex/tools/lfi.py +746 -0
  119. vortex/tools/listings.py +354 -0
  120. vortex/tools/names.py +575 -0
  121. vortex/tools/net.py +1790 -0
  122. vortex/tools/odb.py +10 -0
  123. vortex/tools/parallelism.py +336 -0
  124. vortex/tools/prestaging.py +186 -0
  125. vortex/tools/rawfiles.py +10 -0
  126. vortex/tools/schedulers.py +413 -0
  127. vortex/tools/services.py +871 -0
  128. vortex/tools/storage.py +1061 -0
  129. vortex/tools/surfex.py +61 -0
  130. vortex/tools/systems.py +3396 -0
  131. vortex/tools/targets.py +384 -0
  132. vortex/util/__init__.py +9 -0
  133. vortex/util/config.py +1071 -0
  134. vortex/util/empty.py +24 -0
  135. vortex/util/helpers.py +184 -0
  136. vortex/util/introspection.py +63 -0
  137. vortex/util/iosponge.py +76 -0
  138. vortex/util/roles.py +51 -0
  139. vortex/util/storefunctions.py +103 -0
  140. vortex/util/structs.py +26 -0
  141. vortex/util/worker.py +150 -0
  142. vortex_nwp-2.0.0b1.dist-info/LICENSE +517 -0
  143. vortex_nwp-2.0.0b1.dist-info/METADATA +50 -0
  144. vortex_nwp-2.0.0b1.dist-info/RECORD +146 -0
  145. vortex_nwp-2.0.0b1.dist-info/WHEEL +5 -0
  146. vortex_nwp-2.0.0b1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,511 @@
1
+ """
2
+ This modules defines the physical layout.
3
+ """
4
+
5
+ import footprints
6
+ from bronx.fancies import loggers
7
+ from bronx.stdtypes.history import PrivateHistory
8
+ from bronx.patterns import getbytag, observer
9
+ from bronx.stdtypes.tracking import Tracker
10
+
11
+ from vortex.tools.env import Environment
12
+ import vortex.tools.prestaging
13
+ from vortex.tools.delayedactions import PrivateDelayedActionsHub
14
+ from . import dataflow
15
+
16
+ #: No automatic export.
17
+ __all__ = []
18
+
19
+ logger = loggers.getLogger(__name__)
20
+
21
+ _RHANDLERS_OBSBOARD = 'Resources-Handlers'
22
+ _STORES_OBSBOARD = 'Stores-Activity'
23
+
24
+ _PRESTAGE_REQ_ACTION = 'prestage_req'
25
+
26
+
27
+ # Module Interface
28
+ def get(**kw):
29
+ """Return actual context object matching description."""
30
+ return Context(**kw)
31
+
32
+
33
+ def keys():
34
+ """Return the list of current context tags."""
35
+ return Context.tag_keys()
36
+
37
+
38
+ def values():
39
+ """Return the list of current context values."""
40
+ return Context.tag_values()
41
+
42
+
43
+ def items():
44
+ """Return the items of the contexts table."""
45
+ return Context.tag_items()
46
+
47
+
48
+ def current():
49
+ """Return the context with the focus on, if any."""
50
+ tf = Context.tag_focus()
51
+ if tf is not None:
52
+ tf = Context(tag=tf)
53
+ return tf
54
+
55
+
56
+ def switch(tag=None):
57
+ """Set the session associated to the actual ``tag`` as active."""
58
+ return current().switch(tag=tag)
59
+
60
+
61
+ class ContextObserverRecorder(observer.Observer):
62
+ """Record events related to a given Context.
63
+
64
+ In order to start recording, this object should be associated with a
65
+ :obj:`Context` object using the :meth:`register` method. The recording will
66
+ be stopped when the :meth:`unregister` method is called. The recording is
67
+ automatically stopped whenever the object is pickled.
68
+
69
+ At any time, the `record` can be replayed in a given Context using the
70
+ :meth:`replay_in` method.
71
+ """
72
+
73
+ def __init__(self):
74
+ self._binded_context = None
75
+ self._tracker_recorder = None
76
+ self._stages_recorder = None
77
+ self._prestaging_recorder = None
78
+
79
+ def __del__(self):
80
+ self.unregister()
81
+
82
+ def __getstate__(self):
83
+ # Objects have to be unregistered before being pickled
84
+ self.unregister()
85
+ return self.__dict__
86
+
87
+ def register(self, context):
88
+ """Associate a particular :obj:`Context` object and start recording.
89
+
90
+ :param context: The :obj:`Context` object that will be recorded.
91
+ """
92
+ self._binded_context = context
93
+ self._tracker_recorder = dataflow.LocalTracker()
94
+ self._stages_recorder = list()
95
+ self._prestaging_recorder = list()
96
+ observer.get(tag=_RHANDLERS_OBSBOARD).register(self)
97
+ observer.get(tag=_STORES_OBSBOARD).register(self)
98
+
99
+ def unregister(self):
100
+ """Stop recording."""
101
+ if self._binded_context is not None:
102
+ self._binded_context = None
103
+ observer.get(tag=_RHANDLERS_OBSBOARD).unregister(self)
104
+ observer.get(tag=_STORES_OBSBOARD).unregister(self)
105
+
106
+ def updobsitem(self, item, info):
107
+ if (self._binded_context is not None) and self._binded_context.active:
108
+ logger.debug('Recording upd item %s', item)
109
+ if info['observerboard'] == _RHANDLERS_OBSBOARD:
110
+ processed_item = item.as_dict()
111
+ self._stages_recorder.append((processed_item, info))
112
+ self._tracker_recorder.update_rh(item, info)
113
+ elif info['observerboard'] == _STORES_OBSBOARD:
114
+ self._tracker_recorder.update_store(item, info)
115
+ if info['action'] == _PRESTAGE_REQ_ACTION:
116
+ self._prestaging_recorder.append(info)
117
+
118
+ def replay_in(self, context):
119
+ """Replays the observer's record in a given context.
120
+
121
+ :param context: The :obj:`Context` object where the record will be replayed.
122
+ """
123
+ # First the stages of the sequence
124
+ if self._stages_recorder:
125
+ logger.info('The recorder is replaying stages for context <%s>', context.tag)
126
+ for (pr_item, info) in self._stages_recorder:
127
+ rh_stack = set()
128
+ for section in context.sequence.fastsearch(pr_item):
129
+ if section.rh.as_dict() == pr_item:
130
+ context.sequence.section_updstage(section, info)
131
+ rh_stack.add(section.rh)
132
+ for rh in rh_stack:
133
+ rh.external_stage_update(info.get('stage'))
134
+ # Then the localtracker
135
+ if self._tracker_recorder is not None:
136
+ logger.info('The recorder is updating the LocalTracker for context <%s>', context.tag)
137
+ context.localtracker.append(self._tracker_recorder)
138
+ # Finally the prestaging requests
139
+ if self._prestaging_recorder:
140
+ logger.info('The recorder is replaying prestaging requests for context <%s>', context.tag)
141
+ for info in self._prestaging_recorder:
142
+ context.prestaging_hub(** info)
143
+
144
+
145
+ class DiffHistory(PrivateHistory):
146
+ """Keep track of all the toolbox.diff made in this Context."""
147
+
148
+ def append_record(self, rc, localcontainer, remotehandler):
149
+ """Adds a new diff record in the current DiffHistory."""
150
+ rcmap = {True: 'PASS', False: 'FAIL'}
151
+ containerstr = (str(localcontainer) if localcontainer.is_virtual()
152
+ else localcontainer.localpath())
153
+ self.append('{:s}: {:s} (Ref: {!s})'.format(rcmap[bool(rc)], containerstr,
154
+ remotehandler.provider))
155
+
156
+ def datastore_inplace_overwrite(self, other):
157
+ """Used by a DataStore object to refill a DiffHistory."""
158
+ self.reset()
159
+ self._history.extend(other.get())
160
+ self._count = other.count
161
+
162
+
163
+ class Context(getbytag.GetByTag, observer.Observer):
164
+ """Physical layout of a session or task, etc."""
165
+
166
+ _tag_default = 'ctx'
167
+
168
+ def __init__(self, path=None, topenv=None, sequence=None, localtracker=None):
169
+ """Initiate a new execution context."""
170
+ logger.debug('Context initialisation %s', self)
171
+ if path is None:
172
+ logger.critical('Try to define a new context without virtual path')
173
+ raise ValueError('No virtual path given to new context.')
174
+ if topenv is None:
175
+ logger.critical('Try to define a new context without a topenv.')
176
+ raise ValueError('No top environment given to new context.')
177
+ self._env = Environment(env=topenv, verbose=topenv.verbose(),
178
+ contextlock=self)
179
+ self._path = path + '/' + self.tag
180
+ self._session = None
181
+ self._rundir = None
182
+ self._stamp = '-'.join(('vortex', 'stamp', self.tag, str(id(self))))
183
+ self._fstore = dict()
184
+ self._fstamps = set()
185
+ self._wkdir = None
186
+ self._record = False
187
+ self._prestaging_hub = None # Will be initialised on demand
188
+ self._delayedactions_hub = None # Will be initialised on demand
189
+
190
+ if sequence:
191
+ self._sequence = sequence
192
+ else:
193
+ self._sequence = dataflow.Sequence()
194
+
195
+ if localtracker:
196
+ self._localtracker = localtracker
197
+ else:
198
+ # Create the localtracker within the Session's datastore
199
+ if self.session.datastore.check('context_localtracker', dict(path=self.path)):
200
+ self._localtracker = self.session.datastore.get('context_localtracker',
201
+ dict(path=self.path))
202
+ else:
203
+ self._localtracker = self.session.datastore.insert('context_localtracker',
204
+ dict(path=self.path),
205
+ dataflow.LocalTracker())
206
+
207
+ # Create the localtracker within the Session's datastore
208
+ if self.session.datastore.check('context_diffhistory', dict(path=self.path)):
209
+ self._dhistory = self.session.datastore.get('context_diffhistory',
210
+ dict(path=self.path))
211
+ else:
212
+ self._dhistory = self.session.datastore.insert('context_diffhistory',
213
+ dict(path=self.path),
214
+ DiffHistory())
215
+
216
+ observer.get(tag=_RHANDLERS_OBSBOARD).register(self)
217
+ observer.get(tag=_STORES_OBSBOARD).register(self)
218
+
219
+ @property
220
+ def active(self):
221
+ """Returns wether this Context is currently active or not."""
222
+ return self.has_focus()
223
+
224
+ def _enforce_active(self):
225
+ if not self.active:
226
+ raise RuntimeError("It's not allowed to call this method on an inactive Context.")
227
+
228
+ def newobsitem(self, item, info):
229
+ """
230
+ Resources-Handlers / Store-Activity observing facility.
231
+ Register a new section in void active context with the resource handler ``item``.
232
+ """
233
+ if self.active:
234
+ logger.debug('Notified %s new item of class %s and id %s', self, item.__class__, id(item))
235
+ if self._record and info['observerboard'] == _RHANDLERS_OBSBOARD:
236
+ self._sequence.section(rh=item, stage='load')
237
+
238
+ def updobsitem(self, item, info):
239
+ """
240
+ Resources-Handlers / Store-Activity observing facility.
241
+ Track the new stage of the section containing the resource handler ``item``.
242
+ """
243
+ if self.active:
244
+ logger.debug('Notified %s upd item %s', self, item)
245
+ if info['observerboard'] == _RHANDLERS_OBSBOARD:
246
+ if 'stage' in info:
247
+ # Update the sequence
248
+ for section in self._sequence.fastsearch(item):
249
+ if section.rh is item:
250
+ self._sequence.section_updstage(section, info)
251
+ if ('stage' in info) or ('clear' in info):
252
+ # Update the local tracker
253
+ self._localtracker.update_rh(item, info)
254
+ elif info['observerboard'] == _STORES_OBSBOARD:
255
+ # Update the local tracker
256
+ self._localtracker.update_store(item, info)
257
+ if info['action'] == _PRESTAGE_REQ_ACTION:
258
+ self.prestaging_hub.record(** info)
259
+
260
+ def get_recorder(self):
261
+ """Return a :obj:`ContextObserverRecorder` object recording the changes in this Context."""
262
+ rec = ContextObserverRecorder()
263
+ rec.register(self)
264
+ return rec
265
+
266
+ def focus_loose_hook(self):
267
+ """Save the current Environment and working directory."""
268
+ super().focus_loose_hook()
269
+ self._env = self.env
270
+ if self._wkdir is not None:
271
+ self._wkdir = self.system.getcwd()
272
+
273
+ def focus_gain_allow(self):
274
+ super().focus_gain_allow()
275
+ # It's not possible to activate a Context that lies outside the current
276
+ # session
277
+ if not self.session.active:
278
+ raise RuntimeError("It's not allowed to switch to a Context that belongs to an inactive session")
279
+
280
+ def focus_gain_hook(self):
281
+ super().focus_gain_hook()
282
+ # Activate the environment (if necessary)
283
+ if not self._env.active():
284
+ self._env.active(True)
285
+ # Jump to the latest working directory
286
+ if self._wkdir is not None:
287
+ self.system.cd(self._wkdir)
288
+
289
+ @classmethod
290
+ def switch(cls, tag=None):
291
+ """
292
+ Allows the user to switch to another context,
293
+ assuming that the provided tag is already known.
294
+ """
295
+ if tag in cls.tag_keys():
296
+ obj = Context(tag=tag)
297
+ obj.catch_focus()
298
+ return obj
299
+ else:
300
+ logger.error('Try to switch to an undefined context: %s', tag)
301
+ return None
302
+
303
+ def activate(self):
304
+ """Force the current context as active."""
305
+ return self.catch_focus()
306
+
307
+ @property
308
+ def path(self):
309
+ """Return the virtual path of the current context."""
310
+ return self._path
311
+
312
+ @property
313
+ def session(self):
314
+ """Return the session bound to the current virtual context path."""
315
+ if self._session is None:
316
+ from vortex import sessions
317
+ self._session = sessions.get(tag=[x for x in self.path.split('/') if x][0])
318
+ return self._session
319
+
320
+ def _get_rundir(self):
321
+ """Return the path of the directory associated to that context."""
322
+ return self._rundir
323
+
324
+ def _set_rundir(self, path):
325
+ """Set a new rundir."""
326
+ if self._rundir:
327
+ logger.warning('Context <%s> is changing its working directory <%s>', self.tag, self._rundir)
328
+ if self.system.path.isdir(path):
329
+ self._rundir = path
330
+ logger.info('Context <%s> set rundir <%s>', self.tag, self._rundir)
331
+ else:
332
+ logger.error('Try to change context <%s> to invalid path <%s>', self.tag, path)
333
+
334
+ rundir = property(_get_rundir, _set_rundir)
335
+
336
+ def cocoon(self):
337
+ """Change directory to the one associated to that context."""
338
+ self._enforce_active()
339
+ if self.rundir is None:
340
+ subpath = self.path.replace(self.session.path, '', 1)
341
+ self._rundir = self.session.rundir + subpath
342
+ self.system.cd(self.rundir, create=True)
343
+ self._wkdir = self.rundir
344
+
345
+ @property
346
+ def env(self):
347
+ """Return the :class:`~vortex.tools.env.Environment` object associated to that context."""
348
+ if self.active:
349
+ return Environment.current()
350
+ else:
351
+ return self._env
352
+
353
+ @property
354
+ def prestaging_hub(self):
355
+ """Return the prestaging hub associated with this context.
356
+
357
+ see :class:`vortex.tools.prestaging` for more details.
358
+ """
359
+ if self._prestaging_hub is None:
360
+ self._prestaging_hub = vortex.tools.prestaging.get_hub(tag='contextbound_{:s}'.format(self.tag),
361
+ sh=self.system,
362
+ email=self.session.glove.email)
363
+ return self._prestaging_hub
364
+
365
+ @property
366
+ def delayedactions_hub(self):
367
+ """Return the delayed actions hub associated with this context.
368
+
369
+ see :class:`vortex.tools.delayedactions` for more details.
370
+ """
371
+ if self._delayedactions_hub is None:
372
+ self._delayedactions_hub = PrivateDelayedActionsHub(sh=self.system,
373
+ contextrundir=self.rundir)
374
+ return self._delayedactions_hub
375
+
376
+ @property
377
+ def system(self):
378
+ """Return the :class:`~vortex.tools.env.System` object associated to the root session."""
379
+ return self.session.system()
380
+
381
+ @property
382
+ def sequence(self):
383
+ """Return the :class:`~vortex.layout.dataflow.Sequence` object associated to that context."""
384
+ return self._sequence
385
+
386
+ @property
387
+ def localtracker(self):
388
+ """Return the :class:`~vortex.layout.dataflow.LocalTracker` object associated to that context."""
389
+ return self._localtracker
390
+
391
+ @property
392
+ def diff_history(self):
393
+ return self._dhistory
394
+
395
+ @property
396
+ def subcontexts(self):
397
+ """The current contexts virtually included in the current one."""
398
+ rootpath = self.path + '/'
399
+ return [x for x in self.__class__.tag_values() if x.path.startswith(rootpath)]
400
+
401
+ def newcontext(self, name, focus=False):
402
+ """
403
+ Create a new child context, attached to the current one.
404
+ The tagname of the new kid is given through the mandatory ``name`` argument,
405
+ as well as the default ``focus``.
406
+ """
407
+ if name in self.__class__.tag_keys():
408
+ raise RuntimeError("A context with tag={!s} already exists.".format(name))
409
+ newctx = self.__class__(tag=name, topenv=self.env, path=self.path)
410
+ if focus:
411
+ self.__class__.set_focus(newctx)
412
+ return newctx
413
+
414
+ def stamp(self, tag='default'):
415
+ """Return a stamp name that could be used for any generic purpose."""
416
+ return self._stamp + '.' + str(tag)
417
+
418
+ def fstrack_stamp(self, tag='default'):
419
+ """Set a stamp to track changes on the filesystem."""
420
+ self._enforce_active()
421
+ stamp = self.stamp(tag)
422
+ self._fstamps.add(self.system.path.abspath(stamp))
423
+ self.system.touch(stamp)
424
+ self._fstore[stamp] = set(self.system.ffind())
425
+
426
+ def fstrack_check(self, tag='default'):
427
+ """
428
+ Return a anonymous dictionary with for the each key, the list of entries
429
+ in the file system that are concerned since the last associated ``tag`` stamp.
430
+ Keys are: ``deleted``, ``created``, ``updated``.
431
+ """
432
+ self._enforce_active()
433
+ stamp = self.stamp(tag)
434
+ if not self.system.path.exists(stamp):
435
+ logger.warning('Missing stamp %s', stamp)
436
+ return None
437
+ ffinded = set(self.system.ffind())
438
+ bkuptrace = self.system.trace
439
+ self.system.trace = False
440
+ fscheck = Tracker(self._fstore[stamp], ffinded)
441
+ stroot = self.system.stat(stamp)
442
+ fscheck.updated = [f for f in fscheck.unchanged if self.system.stat(f).st_mtime > stroot.st_mtime]
443
+ self.system.trace = bkuptrace
444
+ return fscheck
445
+
446
+ @property
447
+ def record(self):
448
+ """Automatic recording of section while loading resource handlers."""
449
+ return self._record
450
+
451
+ def record_off(self):
452
+ """Avoid automatic recording of section while loading resource handlers."""
453
+ self._record = False
454
+
455
+ def record_on(self):
456
+ """Activate automatic recording of section while loading resource handlers."""
457
+ self._record = True
458
+
459
+ def clear_promises(self, netloc='promise.cache.fr', scheme='vortex', storeoptions=None):
460
+ """Remove all promises that have been made in this context.
461
+
462
+ :param netloc: Netloc of the promise's cache store to clean up
463
+ :param scheme: Scheme of the promise's cache store to clean up
464
+ :param storeoptions: Option dictionary passed to the store (may be None)
465
+ """
466
+ self.system.header('Clear promises for {}://{} in context {}'.format(scheme, netloc, self.path))
467
+ skeleton = dict(scheme=scheme, netloc=netloc)
468
+ promises = self.localtracker.grep_uri('put', skeleton)
469
+ if promises:
470
+ logger.info('Some promises are left pending...')
471
+ if storeoptions is None:
472
+ storeoptions = dict()
473
+ store = footprints.proxy.store(scheme=scheme, netloc=netloc,
474
+ **storeoptions)
475
+ for promise in [pr.copy() for pr in promises]:
476
+ del promise['scheme']
477
+ del promise['netloc']
478
+ store.delete(promise)
479
+ else:
480
+ logger.info('No promises were left pending.')
481
+
482
+ def clear_stamps(self):
483
+ """Remove local context stamps."""
484
+ if self._fstore:
485
+ fstamps = list(self._fstamps)
486
+ self.system.rmall(*fstamps)
487
+ logger.info('Removing context stamps %s', fstamps)
488
+ self._fstore = dict()
489
+ self._fstamps = set()
490
+
491
+ def free_resources(self):
492
+ """Try to free up memory (removing temporary stuff, caches, ...)."""
493
+ self.sequence.free_resources()
494
+ self.clear_stamps()
495
+
496
+ def clear(self):
497
+ """Make a clear place of local cocoon directory."""
498
+ self.sequence.clear()
499
+ self.clear_stamps()
500
+
501
+ def exit(self):
502
+ """Clean exit from the current context."""
503
+ try:
504
+ self.clear()
505
+ except TypeError:
506
+ logger.error('Could not clear local context <%s>', self.tag)
507
+ # Nullify some variable to help during garbage collection
508
+ self._prestaging_hub = None
509
+ if self._delayedactions_hub:
510
+ self._delayedactions_hub.clear()
511
+ self._delayedactions_hub = None