314
class ActionsBasedSubCommand(BaseSubCommand):
315
"""A sub command that uses "actions" to handle its execution.
314
class StepsBasedSubCommand(BaseSubCommand):
315
"""A sub command that uses "steps" to handle its execution.
317
Actions are callables stored in the `actions` attribute, together
317
Steps are callables stored in the `steps` attribute, together
318
318
with the arguments they expect. Those arguments are strings
319
319
representing attributes of the argparse namespace::
323
>>> def action1(foo):
324
... trace.append('action1 received ' + foo)
326
>>> def action2(foo, bar):
327
... trace.append('action2 received {0} and {1}'.format(foo, bar))
329
>>> class SubCommand(ActionsBasedSubCommand):
331
... (action1, 'foo'),
332
... (action2, 'foo', 'bar'),
324
... trace.append('step1 received ' + foo)
326
>>> def step2(foo, bar):
327
... trace.append('step2 received {0} and {1}'.format(foo, bar))
329
>>> class SubCommand(StepsBasedSubCommand):
332
... (step2, 'foo', 'bar'),
335
335
... def add_arguments(self, parser):
345
345
>>> namespace = parser.parse_args('sub --foo eggs --bar spam'.split())
346
346
>>> namespace.main(namespace)
348
['action1 received eggs', 'action2 received eggs and spam']
348
['step1 received eggs', 'step2 received eggs and spam']
350
A special argument `-a` or `--actions` is automatically added to the
351
parser. It can be used to execute only one or a subset of actions::
350
A special argument `-s` or `--steps` is automatically added to the
351
parser. It can be used to execute only one or a subset of steps::
355
>>> namespace = parser.parse_args('sub --foo eggs -a action1'.split())
355
>>> namespace = parser.parse_args('sub --foo eggs -s step1'.split())
356
356
>>> namespace.main(namespace)
358
['action1 received eggs']
358
['step1 received eggs']
360
A special argument `--skip-actions` is automatically added to the
361
parser. It can be used to skip one or more actions::
360
A special argument `--skip-steps` is automatically added to the
361
parser. It can be used to skip one or more steps::
365
365
>>> namespace = parser.parse_args(
366
... 'sub --foo eggs --skip-actions action1'.split())
366
... 'sub --foo eggs --skip-steps step1'.split())
367
367
>>> namespace.main(namespace)
369
['action2 received eggs and None']
369
['step2 received eggs and None']
371
The actions execution is stopped if an action raises
372
`subprocess.CalledProcessError`.
371
The steps execution is stopped if a step raises `CalledProcessError`.
373
372
In that case, the error is returned by the handler.
377
>>> def erroneous_action(foo):
376
>>> def erroneous_step(foo):
378
377
... raise subprocess.CalledProcessError(1, 'command')
380
379
>>> class SubCommandWithErrors(SubCommand):
382
... (action1, 'foo'),
383
... (erroneous_action, 'foo'),
384
... (action2, 'foo', 'bar'),
382
... (erroneous_step, 'foo'),
383
... (step2, 'foo', 'bar'),
387
386
>>> parser = ArgumentParser()
392
391
"Command 'command' returned non-zero exit status 1"
394
The action `action2` is not executed::
393
The step `step2` is not executed::
397
['action1 received eggs']
396
['step1 received eggs']
402
401
def __init__(self, *args, **kwargs):
403
super(ActionsBasedSubCommand, self).__init__(*args, **kwargs)
404
self._action_names = []
406
for action_args in self.actions:
407
action, args = action_args[0], action_args[1:]
408
action_name = self._get_action_name(action)
409
self._action_names.append(action_name)
410
self._actions[action_name] = (action, args)
412
def _get_action_name(self, action):
413
"""Return the string representation of an action callable.
415
The name is retrieved using attributes lookup for `action_name`
402
super(StepsBasedSubCommand, self).__init__(*args, **kwargs)
403
self._step_names = []
405
for step_args in self.steps:
406
step, args = step_args[0], step_args[1:]
407
step_name = self._get_step_name(step)
408
self._step_names.append(step_name)
409
self._steps[step_name] = (step, args)
411
def _get_step_name(self, step):
412
"""Return the string representation of a step callable.
414
The name is retrieved using attributes lookup for `step_name`
416
415
and then `__name__`::
420
>>> action1.action_name = 'myaction'
425
>>> sub_command = ActionsBasedSubCommand('foo')
426
>>> sub_command._get_action_name(action1)
428
>>> sub_command._get_action_name(action2)
419
>>> step1.step_name = 'mystep'
424
>>> sub_command = StepsBasedSubCommand('foo')
425
>>> sub_command._get_step_name(step1)
427
>>> sub_command._get_step_name(step2)
432
return action.action_name
431
return step.step_name
433
432
except AttributeError:
434
return action.__name__
436
435
def add_arguments(self, parser):
437
super(ActionsBasedSubCommand, self).add_arguments(parser)
436
super(StepsBasedSubCommand, self).add_arguments(parser)
438
437
parser.add_argument(
439
'-a', '--actions', nargs='+', choices=self._action_names,
438
'-s', '--steps', nargs='+', choices=self._step_names,
440
439
help='Call one or more internal functions.')
441
440
parser.add_argument(
442
'--skip-actions', nargs='+', choices=self._action_names,
441
'--skip-steps', nargs='+', choices=self._step_names,
443
442
help='Skip one or more internal functions.')
445
444
def handle(self, namespace):
446
skip_actions = namespace.skip_actions or []
447
action_names = filter(
448
lambda action_name: action_name not in skip_actions,
449
namespace.actions or self._action_names)
450
for action_name in action_names:
451
action, arg_names = self._actions[action_name]
445
skip_steps = namespace.skip_steps or []
447
lambda step_name: step_name not in skip_steps,
448
namespace.steps or self._step_names)
449
for step_name in step_names:
450
step, arg_names = self._steps[step_name]
452
451
args = [getattr(namespace, i) for i in arg_names]
455
454
except subprocess.CalledProcessError as err: