vortex-nwp 2.0.0b1__py3-none-any.whl → 2.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (141) hide show
  1. vortex/__init__.py +75 -47
  2. vortex/algo/__init__.py +3 -2
  3. vortex/algo/components.py +944 -618
  4. vortex/algo/mpitools.py +802 -497
  5. vortex/algo/mpitools_templates/__init__.py +1 -0
  6. vortex/algo/serversynctools.py +34 -33
  7. vortex/config.py +19 -22
  8. vortex/data/__init__.py +9 -3
  9. vortex/data/abstractstores.py +593 -655
  10. vortex/data/containers.py +217 -162
  11. vortex/data/contents.py +65 -39
  12. vortex/data/executables.py +93 -102
  13. vortex/data/flow.py +40 -34
  14. vortex/data/geometries.py +228 -132
  15. vortex/data/handlers.py +436 -227
  16. vortex/data/outflow.py +15 -15
  17. vortex/data/providers.py +185 -163
  18. vortex/data/resources.py +48 -42
  19. vortex/data/stores.py +540 -417
  20. vortex/data/sync_templates/__init__.py +0 -0
  21. vortex/gloves.py +114 -87
  22. vortex/layout/__init__.py +1 -8
  23. vortex/layout/contexts.py +150 -84
  24. vortex/layout/dataflow.py +353 -202
  25. vortex/layout/monitor.py +264 -128
  26. vortex/nwp/__init__.py +5 -2
  27. vortex/nwp/algo/__init__.py +14 -5
  28. vortex/nwp/algo/assim.py +205 -151
  29. vortex/nwp/algo/clim.py +683 -517
  30. vortex/nwp/algo/coupling.py +447 -225
  31. vortex/nwp/algo/eda.py +437 -229
  32. vortex/nwp/algo/eps.py +403 -231
  33. vortex/nwp/algo/forecasts.py +416 -275
  34. vortex/nwp/algo/fpserver.py +683 -307
  35. vortex/nwp/algo/ifsnaming.py +205 -145
  36. vortex/nwp/algo/ifsroot.py +215 -122
  37. vortex/nwp/algo/monitoring.py +137 -76
  38. vortex/nwp/algo/mpitools.py +330 -190
  39. vortex/nwp/algo/odbtools.py +637 -353
  40. vortex/nwp/algo/oopsroot.py +454 -273
  41. vortex/nwp/algo/oopstests.py +90 -56
  42. vortex/nwp/algo/request.py +287 -206
  43. vortex/nwp/algo/stdpost.py +878 -522
  44. vortex/nwp/data/__init__.py +22 -4
  45. vortex/nwp/data/assim.py +125 -137
  46. vortex/nwp/data/boundaries.py +121 -68
  47. vortex/nwp/data/climfiles.py +193 -211
  48. vortex/nwp/data/configfiles.py +73 -69
  49. vortex/nwp/data/consts.py +426 -401
  50. vortex/nwp/data/ctpini.py +59 -43
  51. vortex/nwp/data/diagnostics.py +94 -66
  52. vortex/nwp/data/eda.py +50 -51
  53. vortex/nwp/data/eps.py +195 -146
  54. vortex/nwp/data/executables.py +440 -434
  55. vortex/nwp/data/fields.py +63 -48
  56. vortex/nwp/data/gridfiles.py +183 -111
  57. vortex/nwp/data/logs.py +250 -217
  58. vortex/nwp/data/modelstates.py +180 -151
  59. vortex/nwp/data/monitoring.py +72 -99
  60. vortex/nwp/data/namelists.py +254 -202
  61. vortex/nwp/data/obs.py +400 -308
  62. vortex/nwp/data/oopsexec.py +22 -20
  63. vortex/nwp/data/providers.py +90 -65
  64. vortex/nwp/data/query.py +71 -82
  65. vortex/nwp/data/stores.py +49 -36
  66. vortex/nwp/data/surfex.py +136 -137
  67. vortex/nwp/syntax/__init__.py +1 -1
  68. vortex/nwp/syntax/stdattrs.py +173 -111
  69. vortex/nwp/tools/__init__.py +2 -2
  70. vortex/nwp/tools/addons.py +22 -17
  71. vortex/nwp/tools/agt.py +24 -12
  72. vortex/nwp/tools/bdap.py +16 -5
  73. vortex/nwp/tools/bdcp.py +4 -1
  74. vortex/nwp/tools/bdm.py +3 -0
  75. vortex/nwp/tools/bdmp.py +14 -9
  76. vortex/nwp/tools/conftools.py +728 -378
  77. vortex/nwp/tools/drhook.py +12 -8
  78. vortex/nwp/tools/grib.py +65 -39
  79. vortex/nwp/tools/gribdiff.py +22 -17
  80. vortex/nwp/tools/ifstools.py +82 -42
  81. vortex/nwp/tools/igastuff.py +167 -143
  82. vortex/nwp/tools/mars.py +14 -2
  83. vortex/nwp/tools/odb.py +234 -125
  84. vortex/nwp/tools/partitioning.py +61 -37
  85. vortex/nwp/tools/satrad.py +27 -12
  86. vortex/nwp/util/async.py +83 -55
  87. vortex/nwp/util/beacon.py +10 -10
  88. vortex/nwp/util/diffpygram.py +174 -86
  89. vortex/nwp/util/ens.py +144 -63
  90. vortex/nwp/util/hooks.py +30 -19
  91. vortex/nwp/util/taskdeco.py +28 -24
  92. vortex/nwp/util/usepygram.py +278 -172
  93. vortex/nwp/util/usetnt.py +31 -17
  94. vortex/sessions.py +72 -39
  95. vortex/syntax/__init__.py +1 -1
  96. vortex/syntax/stdattrs.py +410 -171
  97. vortex/syntax/stddeco.py +31 -22
  98. vortex/toolbox.py +327 -192
  99. vortex/tools/__init__.py +11 -2
  100. vortex/tools/actions.py +110 -121
  101. vortex/tools/addons.py +111 -92
  102. vortex/tools/arm.py +42 -22
  103. vortex/tools/compression.py +72 -69
  104. vortex/tools/date.py +11 -4
  105. vortex/tools/delayedactions.py +242 -132
  106. vortex/tools/env.py +75 -47
  107. vortex/tools/folder.py +342 -171
  108. vortex/tools/grib.py +341 -162
  109. vortex/tools/lfi.py +423 -216
  110. vortex/tools/listings.py +109 -40
  111. vortex/tools/names.py +218 -156
  112. vortex/tools/net.py +655 -299
  113. vortex/tools/parallelism.py +93 -61
  114. vortex/tools/prestaging.py +55 -31
  115. vortex/tools/schedulers.py +172 -105
  116. vortex/tools/services.py +403 -334
  117. vortex/tools/storage.py +293 -358
  118. vortex/tools/surfex.py +24 -24
  119. vortex/tools/systems.py +1234 -643
  120. vortex/tools/targets.py +156 -100
  121. vortex/util/__init__.py +1 -1
  122. vortex/util/config.py +378 -327
  123. vortex/util/empty.py +2 -2
  124. vortex/util/helpers.py +56 -24
  125. vortex/util/introspection.py +18 -12
  126. vortex/util/iosponge.py +8 -4
  127. vortex/util/roles.py +4 -6
  128. vortex/util/storefunctions.py +39 -13
  129. vortex/util/structs.py +3 -3
  130. vortex/util/worker.py +29 -17
  131. vortex_nwp-2.1.0.dist-info/METADATA +67 -0
  132. vortex_nwp-2.1.0.dist-info/RECORD +144 -0
  133. {vortex_nwp-2.0.0b1.dist-info → vortex_nwp-2.1.0.dist-info}/WHEEL +1 -1
  134. vortex/layout/appconf.py +0 -109
  135. vortex/layout/jobs.py +0 -1276
  136. vortex/layout/nodes.py +0 -1424
  137. vortex/layout/subjobs.py +0 -464
  138. vortex_nwp-2.0.0b1.dist-info/METADATA +0 -50
  139. vortex_nwp-2.0.0b1.dist-info/RECORD +0 -146
  140. {vortex_nwp-2.0.0b1.dist-info → vortex_nwp-2.1.0.dist-info/licenses}/LICENSE +0 -0
  141. {vortex_nwp-2.0.0b1.dist-info → vortex_nwp-2.1.0.dist-info}/top_level.txt +0 -0
vortex/tools/__init__.py CHANGED
@@ -3,9 +3,18 @@ This is a pure package containing several modules that could be used
3
3
  as standalone tools.
4
4
  """
5
5
 
6
- from . import storage, schedulers, services, systems, targets, date, env, names
6
+ from . import storage as storage
7
+ from . import schedulers as schedulers
8
+ from . import services as services
9
+ from . import systems as systems
10
+ from . import targets as targets
11
+ from . import date as date
12
+ from . import env as env
13
+ from . import names as names
7
14
 
8
15
  #: No automatic export
9
16
  __all__ = []
10
17
 
11
- __tocinfoline__ = 'VORTEX generic tools (system interfaces, format handling, ...)'
18
+ __tocinfoline__ = (
19
+ "VORTEX generic tools (system interfaces, format handling, ...)"
20
+ )
vortex/tools/actions.py CHANGED
@@ -10,9 +10,6 @@ to be processed: e.g. mail, routing, alarm.
10
10
  import bronx.stdtypes.catalog
11
11
  import footprints
12
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
13
 
17
14
  #: Export nothing
18
15
  __all__ = []
@@ -27,9 +24,11 @@ class Action:
27
24
  Such an action could be activated or not, and is basically driven by permissions settings.
28
25
  """
29
26
 
30
- def __init__(self, kind='foo', service=None, active=False, permanent=False):
27
+ def __init__(
28
+ self, kind="foo", service=None, active=False, permanent=False
29
+ ):
31
30
  if service is None:
32
- service = 'send' + kind
31
+ service = "send" + kind
33
32
  self._service = service
34
33
  self._kind = kind
35
34
  self._active = active
@@ -81,8 +80,8 @@ class Action:
81
80
 
82
81
  def info(self):
83
82
  """Informative string (may serve debugging purposes)."""
84
- return '{} Action {} (kind={})'.format(
85
- 'ON ' if self.status() else 'OFF',
83
+ return "{} Action {} (kind={})".format(
84
+ "ON " if self.status() else "OFF",
86
85
  self.__class__.__name__,
87
86
  self.kind,
88
87
  )
@@ -94,7 +93,7 @@ class Action:
94
93
  def service_info(self, **kw):
95
94
  """On the fly remapping of the expected footprint."""
96
95
  info = dict(kw)
97
- info.setdefault('kind', self.service_kind(**kw))
96
+ info.setdefault("kind", self.service_kind(**kw))
98
97
  return info
99
98
 
100
99
  def get_actual_service(self, **kw):
@@ -114,9 +113,11 @@ class Action:
114
113
  if self.active:
115
114
  a_service = self.get_actual_service(**kw)
116
115
  if a_service is None:
117
- logger.warning('Could not find any service for action %s', self.kind)
116
+ logger.warning(
117
+ "Could not find any service for action %s", self.kind
118
+ )
118
119
  else:
119
- logger.warning('Action %s is not active', self.kind)
120
+ logger.warning("Action %s is not active", self.kind)
120
121
  return a_service
121
122
 
122
123
  def execute(self, *args, **kw):
@@ -131,94 +132,30 @@ class Action:
131
132
  class TunableAction(Action):
132
133
  """An Action that may be tuned
133
134
 
134
- - may have it's own section in the target configuration files
135
- - accepts the syntax `ad.action_tune(key=value)` (which has priority)
135
+ accepts the syntax `ad.action_tune(key=value)` (which has priority)
136
136
  """
137
137
 
138
138
  def __init__(self, configuration=None, **kwargs):
139
139
  super().__init__(**kwargs)
140
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
141
 
173
142
  def service_info(self, **kw):
174
- for k, v in self._get_config_dict().items():
143
+ for k, v in self._tuning.items():
175
144
  kw.setdefault(k, v)
176
145
  return super().service_info(**kw)
177
146
 
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()
147
+ def tune(self, **kw):
148
+ self._tuning.update(kw)
209
149
 
210
150
  def getx(self, key, *args, **kw):
211
151
  """Shortcut to access the configuration overridden by the tuning."""
212
152
  if key in self._tuning:
213
153
  return self._tuning[key]
214
154
 
215
- if self._conf_section is not None:
216
- return self._shtarget.getx(key=self._conf_section + ':' + key, *args, **kw)
155
+ if "default" in kw:
156
+ return kw["default"]
217
157
 
218
- if 'default' in kw:
219
- return kw['default']
220
-
221
- raise KeyError('The "{:s}" entry was not found in any configuration'.format(key))
158
+ raise KeyError('The "{:s}" entry was not found'.format(key))
222
159
 
223
160
 
224
161
  class SendMail(Action):
@@ -226,11 +163,14 @@ class SendMail(Action):
226
163
  Class responsible for sending emails.
227
164
  """
228
165
 
229
- def __init__(self, kind='mail', service='sendmail', active=True, quoteprintable=True):
166
+ def __init__(
167
+ self, kind="mail", service="sendmail", active=True, quoteprintable=True
168
+ ):
230
169
  super().__init__(kind=kind, active=active, service=service)
231
170
  if quoteprintable:
232
171
  from email import charset
233
- charset.add_charset('utf-8', charset.QP, charset.QP, 'utf-8')
172
+
173
+ charset.add_charset("utf-8", charset.QP, charset.QP, "utf-8")
234
174
 
235
175
 
236
176
  class TemplatedMail(TunableAction):
@@ -238,18 +178,24 @@ class TemplatedMail(TunableAction):
238
178
 
239
179
  Do not use directly !
240
180
  """
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)
181
+
182
+ def __init__(
183
+ self,
184
+ catalog,
185
+ kind="templatedmail",
186
+ service="templatedmail",
187
+ active=True,
188
+ inputs_charset=None,
189
+ ):
190
+ super().__init__(
191
+ configuration=None, kind=kind, active=active, service=service
192
+ )
247
193
  self.inputs_charset = inputs_charset
248
194
 
249
195
  def service_info(self, **kw):
250
196
  """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)
197
+ kw.setdefault("catalog", self.catalog)
198
+ kw.setdefault("inputs_charset", self.inputs_charset)
253
199
  return super().service_info(**kw)
254
200
 
255
201
  def execute(self, *args, **kw):
@@ -261,7 +207,11 @@ class TemplatedMail(TunableAction):
261
207
  rc = None
262
208
  service = self.get_active_service(**kw)
263
209
  if service:
264
- options = {k: v for k, v in kw.items() if k not in service.footprint_attributes}
210
+ options = {
211
+ k: v
212
+ for k, v in kw.items()
213
+ if k not in service.footprint_attributes
214
+ }
265
215
  rc = service(options)
266
216
  return rc
267
217
 
@@ -271,7 +221,7 @@ class Report(TunableAction):
271
221
  Class responsible for sending reports.
272
222
  """
273
223
 
274
- def __init__(self, kind='report', service='sendreport', active=True):
224
+ def __init__(self, kind="report", service="sendreport", active=True):
275
225
  super().__init__(kind=kind, active=active, service=service)
276
226
 
277
227
 
@@ -280,7 +230,7 @@ class SSH(Action):
280
230
  Class responsible for sending commands to an SSH proxy.
281
231
  """
282
232
 
283
- def __init__(self, kind='ssh', service='ssh', active=True):
233
+ def __init__(self, kind="ssh", service="ssh", active=True):
284
234
  super().__init__(kind=kind, active=active, service=service)
285
235
 
286
236
 
@@ -289,17 +239,23 @@ class AskJeeves(TunableAction):
289
239
  Class responsible for posting requests to Jeeves daemon.
290
240
  """
291
241
 
292
- def __init__(self, kind='jeeves', service='askjeeves', active=True):
293
- super().__init__(configuration=None, kind=kind, active=active, service=service)
242
+ def __init__(self, kind="jeeves", service="askjeeves", active=True):
243
+ super().__init__(
244
+ configuration=None, kind=kind, active=active, service=service
245
+ )
294
246
 
295
247
  def execute(self, *args, **kw):
296
248
  """Generic method to perform the action through a service."""
297
249
  rc = None
298
- if 'kind' in kw:
299
- kw['fwd_kind'] = kw.pop('kind')
250
+ if "kind" in kw:
251
+ kw["fwd_kind"] = kw.pop("kind")
300
252
  service = self.get_active_service(**kw)
301
253
  if service:
302
- talk = {k: v for k, v in kw.items() if k not in service.footprint_attributes}
254
+ talk = {
255
+ k: v
256
+ for k, v in kw.items()
257
+ if k not in service.footprint_attributes
258
+ }
303
259
  rc = service(talk)
304
260
  return rc
305
261
 
@@ -309,23 +265,27 @@ class Prompt(Action):
309
265
  Fake action that could be used for any real action.
310
266
  """
311
267
 
312
- def __init__(self, kind='prompt', service='prompt', active=True):
268
+ def __init__(self, kind="prompt", service="prompt", active=True):
313
269
  super().__init__(kind=kind, active=active, service=service)
314
270
 
315
271
  def execute(self, *args, **kw):
316
272
  """Do nothing but prompt the actual arguments."""
317
273
  # kind could be unintentionally given, force it back
318
- kw['kind'] = self.kind
274
+ kw["kind"] = self.kind
319
275
  service = self.get_active_service(**kw)
320
276
  rc = False
321
277
  if service:
322
- options = {k: v for k, v in kw.items() if k not in service.footprint_attributes}
278
+ options = {
279
+ k: v
280
+ for k, v in kw.items()
281
+ if k not in service.footprint_attributes
282
+ }
323
283
  rc = service(options)
324
284
  return rc
325
285
 
326
286
  def foo(self, *args, **kw):
327
287
  """Yet an other foo method."""
328
- print('#FOO', self.kind, '/ args:', args, '/ kw:', kw)
288
+ print("#FOO", self.kind, "/ args:", args, "/ kw:", kw)
329
289
  return True
330
290
 
331
291
 
@@ -334,32 +294,55 @@ class FlowSchedulerGateway(Action):
334
294
  Send a child command to any ECMWF's workfow scheduler.
335
295
  """
336
296
 
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):
297
+ _KNOWN_CMD = dict(
298
+ sms=[
299
+ "abort",
300
+ "complete",
301
+ "event",
302
+ "init",
303
+ "label",
304
+ "meter",
305
+ "msg",
306
+ "variable",
307
+ "fix",
308
+ ],
309
+ ecflow=["abort", "complete", "event", "init", "label", "meter", "msg"],
310
+ )
311
+
312
+ def __init__(self, kind="flow", service=None, active=True, permanent=True):
341
313
  """
342
314
  The `service` attribute must be specified (it can be either sms or ecflow).
343
315
  """
344
316
  if service is None:
345
- raise ValueError('The service name must be provided')
346
- super().__init__(kind=kind, active=active, service=service, permanent=permanent)
317
+ raise ValueError("The service name must be provided")
318
+ super().__init__(
319
+ kind=kind, active=active, service=service, permanent=permanent
320
+ )
347
321
 
348
322
  def gateway(self, *args, **kw):
349
323
  """Ask the Scheduler to run any (but known) command."""
350
324
  rc = None
351
325
  service = self.get_active_service(**kw)
352
326
  if service and self._schedcmd is not None:
353
- kwbis = {k: v for k, v in kw.items() if k in ('critical',)}
327
+ kwbis = {k: v for k, v in kw.items() if k in ("critical",)}
354
328
  rc = getattr(service, self._schedcmd)(*args, **kwbis)
355
329
  self._schedcmd = None
356
330
  return rc
357
331
 
358
332
  def __getattr__(self, attr):
359
- if attr.startswith('_'):
333
+ if attr.startswith("_"):
360
334
  raise AttributeError
361
- if attr in (['conf', 'info', 'clear', 'mute', 'play', 'path', ] +
362
- self._KNOWN_CMD[self.service]):
335
+ if attr in (
336
+ [
337
+ "conf",
338
+ "info",
339
+ "clear",
340
+ "mute",
341
+ "play",
342
+ "path",
343
+ ]
344
+ + self._KNOWN_CMD[self.service]
345
+ ):
363
346
  self._schedcmd = attr
364
347
  return self.gateway
365
348
  else:
@@ -370,15 +353,21 @@ class FlowSchedulerGateway(Action):
370
353
  class SmsGateway(FlowSchedulerGateway):
371
354
  """Send a child command to an SMS server."""
372
355
 
373
- def __init__(self, kind='sms', service='sms', active=True, permanent=True):
374
- super().__init__(kind=kind, active=active, service=service, permanent=permanent)
356
+ def __init__(self, kind="sms", service="sms", active=True, permanent=True):
357
+ super().__init__(
358
+ kind=kind, active=active, service=service, permanent=permanent
359
+ )
375
360
 
376
361
 
377
362
  class EcflowGateway(FlowSchedulerGateway):
378
363
  """Send a child command to an Ecflow server."""
379
364
 
380
- def __init__(self, kind='ecflow', service='ecflow', active=True, permanent=True):
381
- super().__init__(kind=kind, active=active, service=service, permanent=permanent)
365
+ def __init__(
366
+ self, kind="ecflow", service="ecflow", active=True, permanent=True
367
+ ):
368
+ super().__init__(
369
+ kind=kind, active=active, service=service, permanent=permanent
370
+ )
382
371
 
383
372
 
384
373
  class SpooledActions:
@@ -425,7 +414,7 @@ class Dispatcher(bronx.stdtypes.catalog.Catalog):
425
414
  """
426
415
 
427
416
  def __init__(self, **kw):
428
- logger.debug('Action dispatcher init %s', self)
417
+ logger.debug("Action dispatcher init %s", self)
429
418
  super().__init__(**kw)
430
419
 
431
420
  @property
@@ -444,11 +433,11 @@ class Dispatcher(bronx.stdtypes.catalog.Catalog):
444
433
  self.discard(item)
445
434
 
446
435
  def __getattr__(self, attr):
447
- if attr.startswith('_'):
436
+ if attr.startswith("_"):
448
437
  raise AttributeError
449
- a_kind, u_sep, a_method = attr.partition('_')
438
+ a_kind, u_sep, a_method = attr.partition("_")
450
439
  if not a_method:
451
- a_method = 'execute'
440
+ a_method = "execute"
452
441
  return SpooledActions(a_kind, a_method, self.candidates(a_kind))
453
442
 
454
443