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