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,11 @@
1
+ """
2
+ This is a pure package containing several modules that could be used
3
+ as standalone tools.
4
+ """
5
+
6
+ from . import storage, schedulers, services, systems, targets, date, env, names
7
+
8
+ #: No automatic export
9
+ __all__ = []
10
+
11
+ __tocinfoline__ = 'VORTEX generic tools (system interfaces, format handling, ...)'
@@ -0,0 +1,457 @@
1
+ """
2
+ Module managing the sending of messages.
3
+ Default action classes must provide four methods: on, off, status, execute.
4
+ The on, off and status functions must return a boolean value reflecting the
5
+ status of the action. As far as the execute function is concerned,
6
+ it must deal with the data (given to realize the action) and the action
7
+ to be processed: e.g. mail, routing, alarm.
8
+ """
9
+
10
+ import bronx.stdtypes.catalog
11
+ import footprints
12
+ from bronx.fancies import loggers
13
+ from bronx.fancies.display import dict_as_str
14
+ from vortex import sessions
15
+ from vortex.util.config import GenericConfigParser
16
+
17
+ #: Export nothing
18
+ __all__ = []
19
+
20
+ logger = loggers.getLogger(__name__)
21
+
22
+
23
+ class Action:
24
+ """
25
+ An ``Action`` object is intended to produce a dedicated service through a simple command
26
+ which internally refers to the :meth:`execute` method.
27
+ Such an action could be activated or not, and is basically driven by permissions settings.
28
+ """
29
+
30
+ def __init__(self, kind='foo', service=None, active=False, permanent=False):
31
+ if service is None:
32
+ service = 'send' + kind
33
+ self._service = service
34
+ self._kind = kind
35
+ self._active = active
36
+ self._permanent = permanent
37
+ self._frozen = None
38
+
39
+ @property
40
+ def kind(self):
41
+ """Kind name of this action."""
42
+ return self._kind
43
+
44
+ @property
45
+ def service(self):
46
+ """Standard service associated to this action."""
47
+ return self._service
48
+
49
+ @property
50
+ def active(self):
51
+ """Current status of the action as a boolean property."""
52
+ return self._active
53
+
54
+ def permanent(self, update=None):
55
+ """Return or update the permanent status of this action."""
56
+ if update is not None:
57
+ self._permanent = bool(update)
58
+ if not self._permanent:
59
+ self._frozen = None
60
+ return self._permanent
61
+
62
+ def clear_service(self):
63
+ """Clear the possibly defined permanent service."""
64
+ self._frozen = None
65
+
66
+ def status(self, update=None):
67
+ """Return or update current active status."""
68
+ if update is not None:
69
+ self._active = bool(update)
70
+ return self._active
71
+
72
+ def on(self):
73
+ """Switch on this action."""
74
+ self._active = True
75
+ return self._active
76
+
77
+ def off(self):
78
+ """Switch off this action."""
79
+ self._active = False
80
+ return self._active
81
+
82
+ def info(self):
83
+ """Informative string (may serve debugging purposes)."""
84
+ return '{} Action {} (kind={})'.format(
85
+ 'ON ' if self.status() else 'OFF',
86
+ self.__class__.__name__,
87
+ self.kind,
88
+ )
89
+
90
+ def service_kind(self, **kw):
91
+ """Actual service kind name to be used for footprint evaluation."""
92
+ return self.service
93
+
94
+ def service_info(self, **kw):
95
+ """On the fly remapping of the expected footprint."""
96
+ info = dict(kw)
97
+ info.setdefault('kind', self.service_kind(**kw))
98
+ return info
99
+
100
+ def get_actual_service(self, **kw):
101
+ """Return the service instance determined by the actual description."""
102
+ info = self.service_info(**kw)
103
+ if self.permanent():
104
+ if self._frozen is None:
105
+ self._frozen = footprints.proxy.services.default(**info)
106
+ a_service = self._frozen
107
+ else:
108
+ a_service = footprints.proxy.service(**info)
109
+ return a_service
110
+
111
+ def get_active_service(self, **kw):
112
+ """Return the actual service according to active status and user authorizations."""
113
+ a_service = None
114
+ if self.active:
115
+ a_service = self.get_actual_service(**kw)
116
+ if a_service is None:
117
+ logger.warning('Could not find any service for action %s', self.kind)
118
+ else:
119
+ logger.warning('Action %s is not active', self.kind)
120
+ return a_service
121
+
122
+ def execute(self, *args, **kw):
123
+ """Generic method to perform the action through a service."""
124
+ rc = None
125
+ service = self.get_active_service(**kw)
126
+ if service:
127
+ rc = service(*args)
128
+ return rc
129
+
130
+
131
+ class TunableAction(Action):
132
+ """An Action that may be tuned
133
+
134
+ - may have it's own section in the target configuration files
135
+ - accepts the syntax `ad.action_tune(key=value)` (which has priority)
136
+ """
137
+
138
+ def __init__(self, configuration=None, **kwargs):
139
+ super().__init__(**kwargs)
140
+ self._tuning = dict()
141
+ self._conf_section = configuration
142
+ self._conf_dict = None
143
+
144
+ @property
145
+ def _shtarget(self):
146
+ """Warning: this may be a `vortex.syntax.stdattrs.DelayedInit` object
147
+ during Vortex initialization and may not have a `sections()` method
148
+ nor a `config` property.
149
+ """
150
+ return sessions.current().sh.default_target
151
+
152
+ @property
153
+ def _conf_items(self):
154
+ """Check and return the configuration: a section in the target-xxx.ini file.
155
+
156
+ If the configuration is None, an attempt is made to use the Action's kind.
157
+ Don't use before Vortex initialization is done (see `_shtarget`).
158
+ """
159
+ if self._conf_dict is None:
160
+ if self._conf_section is None:
161
+ if self.kind in self._shtarget.sections():
162
+ self._conf_section = self.kind
163
+ else:
164
+ if self._conf_section not in self._shtarget.sections():
165
+ raise KeyError('No section "{}" in "{}"'.format(self._conf_section,
166
+ self._shtarget.config.file))
167
+ if self._conf_section is None:
168
+ self._conf_dict = dict()
169
+ else:
170
+ self._conf_dict = self._shtarget.items(self._conf_section)
171
+ return self._conf_dict
172
+
173
+ def service_info(self, **kw):
174
+ for k, v in self._get_config_dict().items():
175
+ kw.setdefault(k, v)
176
+ return super().service_info(**kw)
177
+
178
+ def tune(self, section=None, **kw):
179
+ """Add options to override the .ini file configuration.
180
+
181
+ ``section`` is a specific section name, or ``None`` for all.
182
+ """
183
+ if section is None or section == self._conf_section:
184
+ self._tuning.update(kw)
185
+
186
+ def _get_config_dict(self):
187
+ final_dict = dict()
188
+ final_dict.update(self._conf_items)
189
+ final_dict.update(self._tuning)
190
+ return final_dict
191
+
192
+ def info(self):
193
+ """Informative string (may serve debugging purposes)."""
194
+ s = super().info() + ' - tunable\n'
195
+ mix = dict()
196
+ mix.update(self._conf_items)
197
+ mix.update(self._tuning)
198
+ prt = dict()
199
+ for k, v in mix.items():
200
+ if k in self._tuning:
201
+ prt['++ ' + k] = '{} (was: {})'.format(v, str(
202
+ self._conf_items[k]) if k in self._conf_items else '<not set>')
203
+ else:
204
+ prt[' ' + k] = v
205
+ if self._conf_section is not None:
206
+ s += ' ' * 4 + 'configuration: ' + self._conf_section + '\n'
207
+ s += dict_as_str(prt, prefix=4)
208
+ return s.strip()
209
+
210
+ def getx(self, key, *args, **kw):
211
+ """Shortcut to access the configuration overridden by the tuning."""
212
+ if key in self._tuning:
213
+ return self._tuning[key]
214
+
215
+ if self._conf_section is not None:
216
+ return self._shtarget.getx(key=self._conf_section + ':' + key, *args, **kw)
217
+
218
+ if 'default' in kw:
219
+ return kw['default']
220
+
221
+ raise KeyError('The "{:s}" entry was not found in any configuration'.format(key))
222
+
223
+
224
+ class SendMail(Action):
225
+ """
226
+ Class responsible for sending emails.
227
+ """
228
+
229
+ def __init__(self, kind='mail', service='sendmail', active=True, quoteprintable=True):
230
+ super().__init__(kind=kind, active=active, service=service)
231
+ if quoteprintable:
232
+ from email import charset
233
+ charset.add_charset('utf-8', charset.QP, charset.QP, 'utf-8')
234
+
235
+
236
+ class TemplatedMail(TunableAction):
237
+ """Abstract class to end email from a given template.
238
+
239
+ Do not use directly !
240
+ """
241
+ def __init__(self, kind='templatedmail', service='templatedmail', active=True,
242
+ catalog=None, inputs_charset=None):
243
+ super().__init__(configuration=None, kind=kind, active=active,
244
+ service=service)
245
+ self.catalog = catalog or GenericConfigParser('@{:s}-inventory.ini'.format(kind),
246
+ encoding=inputs_charset)
247
+ self.inputs_charset = inputs_charset
248
+
249
+ def service_info(self, **kw):
250
+ """Kindly propose the permanent directory and catalog to the final service"""
251
+ kw.setdefault('catalog', self.catalog)
252
+ kw.setdefault('inputs_charset', self.inputs_charset)
253
+ return super().service_info(**kw)
254
+
255
+ def execute(self, *args, **kw):
256
+ """
257
+ Perform the action through a service. Extraneous arguments (not included
258
+ in the footprint) are collected and explicitely transmitted to the service
259
+ in a dictionary.
260
+ """
261
+ rc = None
262
+ service = self.get_active_service(**kw)
263
+ if service:
264
+ options = {k: v for k, v in kw.items() if k not in service.footprint_attributes}
265
+ rc = service(options)
266
+ return rc
267
+
268
+
269
+ class Report(TunableAction):
270
+ """
271
+ Class responsible for sending reports.
272
+ """
273
+
274
+ def __init__(self, kind='report', service='sendreport', active=True):
275
+ super().__init__(kind=kind, active=active, service=service)
276
+
277
+
278
+ class SSH(Action):
279
+ """
280
+ Class responsible for sending commands to an SSH proxy.
281
+ """
282
+
283
+ def __init__(self, kind='ssh', service='ssh', active=True):
284
+ super().__init__(kind=kind, active=active, service=service)
285
+
286
+
287
+ class AskJeeves(TunableAction):
288
+ """
289
+ Class responsible for posting requests to Jeeves daemon.
290
+ """
291
+
292
+ def __init__(self, kind='jeeves', service='askjeeves', active=True):
293
+ super().__init__(configuration=None, kind=kind, active=active, service=service)
294
+
295
+ def execute(self, *args, **kw):
296
+ """Generic method to perform the action through a service."""
297
+ rc = None
298
+ if 'kind' in kw:
299
+ kw['fwd_kind'] = kw.pop('kind')
300
+ service = self.get_active_service(**kw)
301
+ if service:
302
+ talk = {k: v for k, v in kw.items() if k not in service.footprint_attributes}
303
+ rc = service(talk)
304
+ return rc
305
+
306
+
307
+ class Prompt(Action):
308
+ """
309
+ Fake action that could be used for any real action.
310
+ """
311
+
312
+ def __init__(self, kind='prompt', service='prompt', active=True):
313
+ super().__init__(kind=kind, active=active, service=service)
314
+
315
+ def execute(self, *args, **kw):
316
+ """Do nothing but prompt the actual arguments."""
317
+ # kind could be unintentionally given, force it back
318
+ kw['kind'] = self.kind
319
+ service = self.get_active_service(**kw)
320
+ rc = False
321
+ if service:
322
+ options = {k: v for k, v in kw.items() if k not in service.footprint_attributes}
323
+ rc = service(options)
324
+ return rc
325
+
326
+ def foo(self, *args, **kw):
327
+ """Yet an other foo method."""
328
+ print('#FOO', self.kind, '/ args:', args, '/ kw:', kw)
329
+ return True
330
+
331
+
332
+ class FlowSchedulerGateway(Action):
333
+ """
334
+ Send a child command to any ECMWF's workfow scheduler.
335
+ """
336
+
337
+ _KNOWN_CMD = dict(sms=['abort', 'complete', 'event', 'init', 'label', 'meter', 'msg', 'variable', 'fix'],
338
+ ecflow=['abort', 'complete', 'event', 'init', 'label', 'meter', 'msg'])
339
+
340
+ def __init__(self, kind='flow', service=None, active=True, permanent=True):
341
+ """
342
+ The `service` attribute must be specified (it can be either sms or ecflow).
343
+ """
344
+ if service is None:
345
+ raise ValueError('The service name must be provided')
346
+ super().__init__(kind=kind, active=active, service=service, permanent=permanent)
347
+
348
+ def gateway(self, *args, **kw):
349
+ """Ask the Scheduler to run any (but known) command."""
350
+ rc = None
351
+ service = self.get_active_service(**kw)
352
+ if service and self._schedcmd is not None:
353
+ kwbis = {k: v for k, v in kw.items() if k in ('critical',)}
354
+ rc = getattr(service, self._schedcmd)(*args, **kwbis)
355
+ self._schedcmd = None
356
+ return rc
357
+
358
+ def __getattr__(self, attr):
359
+ if attr.startswith('_'):
360
+ raise AttributeError
361
+ if attr in (['conf', 'info', 'clear', 'mute', 'play', 'path', ] +
362
+ self._KNOWN_CMD[self.service]):
363
+ self._schedcmd = attr
364
+ return self.gateway
365
+ else:
366
+ self._schedcmd = None
367
+ return None
368
+
369
+
370
+ class SmsGateway(FlowSchedulerGateway):
371
+ """Send a child command to an SMS server."""
372
+
373
+ def __init__(self, kind='sms', service='sms', active=True, permanent=True):
374
+ super().__init__(kind=kind, active=active, service=service, permanent=permanent)
375
+
376
+
377
+ class EcflowGateway(FlowSchedulerGateway):
378
+ """Send a child command to an Ecflow server."""
379
+
380
+ def __init__(self, kind='ecflow', service='ecflow', active=True, permanent=True):
381
+ super().__init__(kind=kind, active=active, service=service, permanent=permanent)
382
+
383
+
384
+ class SpooledActions:
385
+ """
386
+ Delayed action to be processed.
387
+ """
388
+
389
+ def __init__(self, kind=None, method=None, actions=None):
390
+ """Store effective action and method to be processed."""
391
+ self._kind = kind
392
+ self._method = method
393
+ self._actions = actions
394
+
395
+ @property
396
+ def kind(self):
397
+ return self._kind
398
+
399
+ @property
400
+ def method(self):
401
+ return self._method
402
+
403
+ @property
404
+ def actions(self):
405
+ return self._actions[:]
406
+
407
+ def __call__(self, *args, **kw):
408
+ return self.process(*args, **kw)
409
+
410
+ def process(self, *args, **kw):
411
+ """Process the actual method for all action candidates of a given kind."""
412
+ rc = list()
413
+ for item in self.actions:
414
+ xx = getattr(item, self.method, None)
415
+ if xx is not None:
416
+ rc.append(xx(*args, **kw))
417
+ else:
418
+ rc.append(None)
419
+ return rc
420
+
421
+
422
+ class Dispatcher(bronx.stdtypes.catalog.Catalog):
423
+ """
424
+ Central office for dispatching actions.
425
+ """
426
+
427
+ def __init__(self, **kw):
428
+ logger.debug('Action dispatcher init %s', self)
429
+ super().__init__(**kw)
430
+
431
+ @property
432
+ def actions(self):
433
+ """A set of kind names of actual actions registered in that Dispatcher."""
434
+ return {x.kind for x in self.items()}
435
+
436
+ def candidates(self, kind):
437
+ """Return a selection of the dispatcher's items with the specified ``kind``."""
438
+ return [x for x in self.items() if x.kind == kind]
439
+
440
+ def discard_kind(self, kind):
441
+ """A shortcut to discard from the dispatcher any item with the specified ``kind``."""
442
+ for item in self:
443
+ if item.kind == kind:
444
+ self.discard(item)
445
+
446
+ def __getattr__(self, attr):
447
+ if attr.startswith('_'):
448
+ raise AttributeError
449
+ a_kind, u_sep, a_method = attr.partition('_')
450
+ if not a_method:
451
+ a_method = 'execute'
452
+ return SpooledActions(a_kind, a_method, self.candidates(a_kind))
453
+
454
+
455
+ #: Default action dispatcher... containing an anonymous SendMail action
456
+ actiond = Dispatcher()
457
+ actiond.add(SendMail(), Report(), AskJeeves(), SSH(), Prompt())