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
vortex/nwp/util/ens.py ADDED
@@ -0,0 +1,279 @@
1
+ """
2
+ A collection of utility functions used in the context of Ensemble forecasts.
3
+ """
4
+
5
+ import io
6
+ import json
7
+ import re
8
+ import time
9
+
10
+ from bronx.compat import random
11
+ from bronx.fancies import loggers
12
+ from bronx.stdtypes.date import Period
13
+
14
+ from vortex import sessions
15
+ from vortex.data.stores import FunctionStoreCallbackError
16
+ from vortex.util import helpers
17
+
18
+ #: No automatic export
19
+ __all__ = []
20
+
21
+ logger = loggers.getLogger(__name__)
22
+
23
+
24
+ def drawingfunction(options):
25
+ """Draw a random sample from a *set* of values.
26
+
27
+ This function is designed to be executed by a
28
+ :obj:`vortex.data.stores.FunctionStore` object.
29
+
30
+ The *set* of values is computed using the resource's argument:
31
+ *set = [resource.start, resource.start + resource.nbset - 1]*. If
32
+ *resource.start* does not exists, *resource.start=1* is assumed.
33
+
34
+ The size of the sample is given by the *nblot* argument of the resource
35
+
36
+ The random generator is initialised using the resource's date. Consequently,
37
+ for a given date, the drawing is reproducible.
38
+
39
+ :param dict options: All the options passed to the store plus anything from
40
+ the query part of the URI.
41
+
42
+ :return: Content of a :obj:`nwp.data.ens.Sample` resource
43
+
44
+ :rtype: A file like object
45
+ """
46
+ rhdict = options.get("rhandler", None)
47
+ if rhdict:
48
+ date = rhdict["resource"]["date"]
49
+ rgen = random.Random()
50
+ rgen.seed(int(date[:-2]))
51
+ nbsample = rhdict["resource"].get("nbsample", 0)
52
+ if not nbsample:
53
+ raise FunctionStoreCallbackError(
54
+ "The resource must hold a non-null nbsample attribute"
55
+ )
56
+ population = rhdict["resource"].get("population", [])
57
+ if not population:
58
+ raise FunctionStoreCallbackError(
59
+ "The resource must hold a non-empty population attribute"
60
+ )
61
+ nbset = len(population)
62
+
63
+ tirage = rgen.sample(
64
+ population * (nbsample // nbset), (nbsample // nbset) * nbset
65
+ ) + rgen.sample(population, nbsample % nbset)
66
+ logger.info(
67
+ "List of random elements: %s", ", ".join([str(x) for x in tirage])
68
+ )
69
+ else:
70
+ raise FunctionStoreCallbackError("no resource handler here :-(")
71
+ # NB: The result have to be a file like object !
72
+ outdict = dict(
73
+ vapp=rhdict["provider"].get("vapp", None),
74
+ vconf=rhdict["provider"].get("vconf", None),
75
+ cutoff=rhdict["resource"].get("cutoff", None),
76
+ date=rhdict["resource"].get("date", None),
77
+ resource_kind=rhdict["resource"].get("kind", None),
78
+ drawing=tirage,
79
+ population=population,
80
+ )
81
+ if rhdict["provider"].get("experiment", None) is not None:
82
+ outdict["experiment"] = rhdict["provider"]["experiment"]
83
+ return io.BytesIO(json.dumps(outdict, indent=4).encode(encoding="utf_8"))
84
+
85
+
86
+ def _checkingfunction_dict(options):
87
+ """
88
+ Internal function that returns a dictionnary that describes the available
89
+ inputs.
90
+ """
91
+ rhdict = options.get("rhandler", None)
92
+ if rhdict:
93
+ # If no nbsample is provided, easy to achieve...
94
+ nbsample = rhdict["resource"].get("nbsample", None)
95
+ # ...and if no explicit minimum of resources, nbsample is the minimum
96
+ nbmin = int(
97
+ options.get(
98
+ "min",
99
+ [
100
+ (0 if nbsample is None else nbsample),
101
+ ],
102
+ ).pop()
103
+ )
104
+ if nbsample is not None and nbsample < nbmin:
105
+ logger.warning(
106
+ "%d resources needed, %d required: sin of gluttony ?",
107
+ nbsample,
108
+ nbmin,
109
+ )
110
+ # What to look for ?
111
+ checkrole = rhdict["resource"].get("checkrole", None)
112
+ if not checkrole:
113
+ raise FunctionStoreCallbackError(
114
+ "The resource must hold a non-empty checkrole attribute"
115
+ )
116
+ rolematch = re.match(r"(\w+)(?:\+(\w+))?$", checkrole)
117
+ cur_t = sessions.current()
118
+ if rolematch:
119
+ ctx = cur_t.context
120
+ checklist = [
121
+ sec.rh
122
+ for sec in ctx.sequence.filtered_inputs(
123
+ role=rolematch.group(1)
124
+ )
125
+ ]
126
+ mandatorylist = (
127
+ [
128
+ sec.rh
129
+ for sec in ctx.sequence.filtered_inputs(
130
+ role=rolematch.group(2)
131
+ )
132
+ ]
133
+ if rolematch.group(2)
134
+ else []
135
+ )
136
+ else:
137
+ raise FunctionStoreCallbackError(
138
+ "checkrole is not properly formatted"
139
+ )
140
+ # Other options
141
+ nretries = int(
142
+ options.get(
143
+ "nretries",
144
+ [
145
+ 0,
146
+ ],
147
+ ).pop()
148
+ )
149
+ retry_wait = Period(
150
+ options.get(
151
+ "retry_wait",
152
+ [
153
+ "PT5M",
154
+ ],
155
+ ).pop()
156
+ )
157
+ comp_delay = Period(
158
+ options.get(
159
+ "comp_delay",
160
+ [
161
+ 0,
162
+ ],
163
+ ).pop()
164
+ )
165
+ fakecheck = options.get(
166
+ "fakecheck",
167
+ [
168
+ False,
169
+ ],
170
+ ).pop()
171
+
172
+ def _retry_cond(the_ntries, the_acceptable_time):
173
+ return (
174
+ the_acceptable_time is None and the_ntries <= nretries
175
+ ) or (
176
+ the_acceptable_time
177
+ and (time.time() - the_acceptable_time)
178
+ < comp_delay.total_seconds()
179
+ )
180
+
181
+ # Ok let's work...
182
+ ntries = 0
183
+ acceptable_time = None
184
+ found = []
185
+ while _retry_cond(ntries, acceptable_time):
186
+ if ntries:
187
+ logger.info(
188
+ "Let's sleep %d sec. before the next check round...",
189
+ retry_wait.total_seconds(),
190
+ )
191
+ cur_t.sh.sleep(retry_wait.total_seconds())
192
+ ntries += 1
193
+ try:
194
+ logger.info("Starting an input check...")
195
+ found, candidates = helpers.colorfull_input_checker(
196
+ nbmin,
197
+ checklist,
198
+ mandatory=mandatorylist,
199
+ fakecheck=fakecheck,
200
+ )
201
+ if acceptable_time is None and (found or nbmin == 0):
202
+ acceptable_time = time.time()
203
+ if comp_delay.total_seconds() and len(found) != len(
204
+ candidates
205
+ ):
206
+ logger.info(
207
+ "The minimum required size was reached (nbmin=%d). "
208
+ + "That's great but we are waiting a little longer "
209
+ + "(for at most %d sec.)",
210
+ nbmin,
211
+ comp_delay.total_seconds(),
212
+ )
213
+
214
+ if len(found) == len(candidates):
215
+ # No need to wait any longer...
216
+ break
217
+ except helpers.InputCheckerError as e:
218
+ if not _retry_cond(ntries, acceptable_time):
219
+ raise FunctionStoreCallbackError(
220
+ "The input checher failed ({!s})".format(e)
221
+ )
222
+ return found
223
+ else:
224
+ raise FunctionStoreCallbackError("no resource handler here :-(\n")
225
+
226
+
227
+ def checkingfunction(options):
228
+ """Check what are the available resources and returns the list.
229
+
230
+ This function is designed to be executed by a
231
+ :obj:`vortex.data.stores.FunctionStore` object.
232
+
233
+ The *checkrole* resource attribute is used to look into the current context
234
+ in order to establish the list of resources that will checked.
235
+
236
+ :param dict options: All the options passed to the store plus anything from
237
+ the query part of the URI.
238
+
239
+ :return: Content of a :obj:`nwp.data.ens.PopulationList` resource
240
+
241
+ :rtype: A file like object
242
+ """
243
+ rhdict = options.get("rhandler", None)
244
+ avail_list = _checkingfunction_dict(options)
245
+ outdict = dict(
246
+ vapp=rhdict["provider"].get("vapp", None),
247
+ vconf=rhdict["provider"].get("vconf", None),
248
+ cutoff=rhdict["resource"].get("cutoff", None),
249
+ date=rhdict["resource"].get("date", None),
250
+ resource_kind=rhdict["resource"].get("kind", None),
251
+ population=avail_list,
252
+ )
253
+ if rhdict["provider"].get("experiment", None) is not None:
254
+ outdict["experiment"] = rhdict["provider"]["experiment"]
255
+ return io.BytesIO(json.dumps(outdict, indent=4).encode(encoding="utf_8"))
256
+
257
+
258
+ def safedrawingfunction(options):
259
+ """Combined called to :func:`checkingfunction` and :func:`drawingfunction`.
260
+
261
+ See the documentation of these two functions for more details.
262
+ """
263
+ checkedlist = _checkingfunction_dict(options)
264
+ options["rhandler"]["resource"]["population"] = checkedlist
265
+ return drawingfunction(options)
266
+
267
+
268
+ def unsafedrawingfunction(options):
269
+ """Combined called to :func:`checkingfunction` and :func:`drawingfunction`...
270
+ but with a big lie on the checking: no real check, all the resources are assumed ok.
271
+
272
+ See the documentation of these two functions for more details.
273
+ """
274
+ options["fakecheck"] = [
275
+ True,
276
+ ]
277
+ checkedlist = _checkingfunction_dict(options)
278
+ options["rhandler"]["resource"]["population"] = checkedlist
279
+ return drawingfunction(options)
@@ -0,0 +1,139 @@
1
+ """
2
+ Some useful hooks.
3
+ """
4
+
5
+ import collections.abc
6
+ import functools
7
+
8
+ from bronx.fancies import loggers
9
+ from bronx.stdtypes.date import Date, Period, Time
10
+
11
+ from ..data.query import StaticCutoffDispenser
12
+
13
+ #: No automatic export
14
+ __all__ = []
15
+
16
+ logger = loggers.getLogger(__name__)
17
+
18
+
19
+ def update_namelist(t, rh, *completive_rh):
20
+ """Update namelist with resource handler(s) given in **completive_rh**."""
21
+ touched = False
22
+ for crh in completive_rh:
23
+ if not isinstance(crh, (list, tuple)):
24
+ crh = [
25
+ crh,
26
+ ]
27
+ for arh in crh:
28
+ logger.info(
29
+ "Merging: {!r} :\n{:s}".format(
30
+ arh.container, arh.contents.dumps()
31
+ )
32
+ )
33
+ rh.contents.merge(arh.contents)
34
+ touched = True
35
+ if touched:
36
+ rh.save()
37
+
38
+
39
+ def concatenate(t, rh, *rhlist):
40
+ """Concatenate *rhlist* after *rh*."""
41
+ blocksize = 32 * 1024 * 1024 # 32Mb
42
+ rh.container.close()
43
+ with rh.container.iod_context():
44
+ myfh = rh.container.iodesc(mode="ab")
45
+ for crh in rhlist:
46
+ if not isinstance(crh, (list, tuple)):
47
+ crh = [
48
+ crh,
49
+ ]
50
+ for arh in crh:
51
+ logger.info("Appending %s to self.", str(arh.container))
52
+ with arh.container.iod_context():
53
+ afh = arh.container.iodesc(mode="rb")
54
+ stuff = afh.read(blocksize)
55
+ while stuff:
56
+ myfh.write(stuff)
57
+ stuff = afh.read(blocksize)
58
+
59
+
60
+ def insert_cutoffs(t, rh, rh_cutoff_source, fuse_per_obstype=False):
61
+ """Read the cutoff from *rh_cutoff_source* and feed them into *rh*.
62
+
63
+ If *fuse_per_obstype* is ``True``, the latest cutoff of a given obstype
64
+ will be used for all the occurences of this obstype.
65
+ """
66
+ # rh_cutoff_source may be a list
67
+ if isinstance(rh_cutoff_source, list):
68
+ if rh_cutoff_source:
69
+ rh_cutoff_source = rh_cutoff_source[0]
70
+ else:
71
+ ValueError("The resource handler's list is empty.")
72
+ # Get the CutoffDispenser
73
+ import vortex.tools.listings
74
+
75
+ assert vortex.tools.listings
76
+ if rh_cutoff_source.container.actualfmt == "bdmbufr_listing":
77
+ c_disp_callback = functools.partial(
78
+ rh_cutoff_source.contents.data.cutoffs_dispenser,
79
+ fuse_per_obstype=fuse_per_obstype,
80
+ )
81
+ else:
82
+ raise RuntimeError(
83
+ "Incompatible < {!s} > ressource handler".format(rh_cutoff_source)
84
+ )
85
+ # Fill the gaps in the original request
86
+ rh.contents.add_cutoff_info(c_disp_callback())
87
+ # Actually save the result to file
88
+ rh.save()
89
+
90
+
91
+ def _new_static_cutoff_dispencer(base_date, cutoffs_def):
92
+ def x_period(p):
93
+ try:
94
+ return Period(p)
95
+ except ValueError:
96
+ return Period(Time(p))
97
+
98
+ if not isinstance(base_date, Date):
99
+ base_date = Date(base_date)
100
+ if isinstance(cutoffs_def, collections.abc.Mapping):
101
+ cutoffs_def = {
102
+ (k if isinstance(k, Period) else x_period(k)): v
103
+ for k, v in cutoffs_def.items()
104
+ }
105
+ cutoffs = {base_date + k: v for k, v in cutoffs_def.items()}
106
+ c_disp = StaticCutoffDispenser(max(cutoffs.keys()), cutoffs)
107
+ else:
108
+ if not isinstance(cutoffs_def, Period):
109
+ cutoffs_def = x_period(cutoffs_def)
110
+ c_disp = StaticCutoffDispenser(base_date + cutoffs_def)
111
+ return c_disp
112
+
113
+
114
+ def insert_static_cutoffs(t, rh, base_date, cutoffs_def):
115
+ """Compute the cutoff from *cutoffs_def* and feed them into *rh*.
116
+
117
+ :param base_date: The current analysis time
118
+ :param cutoffs_def: The cutoff time represented as time offset with respect
119
+ to *base_date*. *cutoffs_defs* may be a single value or
120
+ a dictionary. If *cutoffs_def* is a dictionary, it
121
+ associates a cutoff with a list of `obstypes`.
122
+ """
123
+ # Fill the gaps in the original request
124
+ rh.contents.add_cutoff_info(
125
+ _new_static_cutoff_dispencer(base_date, cutoffs_def)
126
+ )
127
+ # Actually save the result to files
128
+ rh.save()
129
+
130
+
131
+ def arpifs_obs_error_correl_legacy2oops(t, rh):
132
+ """Convert a constant file that contains observation errors correlations."""
133
+ if rh.resource.realkind != "correlations":
134
+ raise ValueError("Incompatible resource: {!s}".format(rh))
135
+ if rh.contents[0].startswith("SIGMAO"):
136
+ logger.warning("Non conversion is needed...")
137
+ else:
138
+ rh.contents[:0] = ["SIGMAO unused\n", "1 1.2\n", "CORRELATIONS\n"]
139
+ rh.save()
@@ -0,0 +1,85 @@
1
+ """
2
+ A collection of Tasks decorators (to add usual inputs/outputs to existing classes).
3
+ """
4
+
5
+ from bronx.fancies import loggers
6
+
7
+ from vortex import toolbox
8
+
9
+ #: No automatic export
10
+ __all__ = []
11
+
12
+ logger = loggers.getLogger(__name__)
13
+
14
+
15
+ def process_needs_lfi_stuff(*kargs, **kwargs):
16
+ """
17
+ Decorator that update the tasks's ``process`` method in order to retrieve the
18
+ things needed with the FA/LFI file format.
19
+
20
+ Example (the self.conf.cycle Genv/Uenv cycle will be used)::
21
+
22
+ @process_needs_lfi_stuff
23
+ class MyTask(Task):
24
+
25
+ def process(self)
26
+ pass
27
+
28
+ Example (the self.conf.arpege_cycle Genv/Uenv cycle will be used)::
29
+
30
+ @process_needs_lfi_stuff(cyclekey='arpege_cycle')
31
+ class MyOtherTask(Task):
32
+
33
+ def process(self)
34
+ pass
35
+
36
+ """
37
+ cyclekey = kwargs.pop("cyclekey", "cycle")
38
+
39
+ def decorate_process(cls):
40
+ """Decorator for Task: get LFI stuff before calling process."""
41
+ original_process = getattr(cls, "process", None)
42
+ if original_process is not None:
43
+
44
+ def process(self, *args, **kwargs):
45
+ _get_lfi_stuff(self, cyclekey)
46
+ original_process(self, *args, **kwargs)
47
+
48
+ process.__doc__ = original_process.__doc__
49
+ cls.process = process
50
+ return cls
51
+
52
+ if kargs:
53
+ return decorate_process(kargs[0])
54
+ else:
55
+ return decorate_process
56
+
57
+
58
+ def _get_lfi_stuff(self, cyclekey):
59
+ """Get LFI stuff method (called from process)."""
60
+ if "early-fetch" in self.steps or "fetch" in self.steps:
61
+ actualcycle = getattr(self.conf, cyclekey)
62
+
63
+ self.sh.title("Toolbox input tblfiscripts")
64
+ toolbox.input(
65
+ role="LFIScripts",
66
+ genv=actualcycle,
67
+ kind="lfiscripts",
68
+ local="usualtools/tools.lfi.tgz",
69
+ )
70
+ self.sh.title("Toolbox input tbiopoll")
71
+ toolbox.input(
72
+ role="IOPoll",
73
+ format="unknown",
74
+ genv=actualcycle,
75
+ kind="iopoll",
76
+ language="perl",
77
+ local="usualtools/io_poll",
78
+ )
79
+ self.sh.title("Toolbox input tblfitools")
80
+ toolbox.input(
81
+ role="LFITOOLS",
82
+ genv=actualcycle,
83
+ kind="lfitools",
84
+ local="usualtools/lfitools",
85
+ )