Merge pull request #44 from mesosphere/refactor-cmd-execution

Refactor cmd execution
This commit is contained in:
José Armando García Sancio
2015-02-24 19:43:20 -08:00
4 changed files with 206 additions and 24 deletions

49
dcos/api/cmds.py Normal file
View File

@@ -0,0 +1,49 @@
import collections
from dcos.api import errors
Command = collections.namedtuple(
'Command',
['hierarchy', 'arg_keys', 'function'])
"""Describe a CLI command.
:param hierarchy: the noun and verbs that need to be set for the command to
execute
:type hierarchy: list of str
:param arg_keys: the arguments that must get passed to the function; the order
of the keys determines the order in which they get passed to
the function
:type arg_keys: list of str
:param function: the function to execute
:type function: func(args) -> int
"""
def execute(cmds, args):
"""Executes one of the commands based on the arguments passed.
:param cmds: commands to try to execute; the order determines the order of
evaluation
:type cmds: list of Command
:param args: command line arguments
:type args: dict
:returns: the process status
:rtype: (int, dcos.errors.Error)
"""
for hierarchy, arg_keys, function in cmds:
# Let's find if the function matches the command
match = True
for positional in hierarchy:
if not args[positional]:
match = False
if match:
params = [args[name] for name in arg_keys]
return (function(*params), None)
return (
None,
errors.DefaultError(
'Could not find a command with the passed arguments')
)

View File

@@ -41,8 +41,8 @@ import sys
import docopt
import pkg_resources
from dcos.api import (config, constants, emitting, errors, jsonitem, marathon,
options, util)
from dcos.api import (cmds, config, constants, emitting, errors, jsonitem,
marathon, options, util)
logger = util.get_logger(__name__)
emitter = emitting.FlatEmitter()
@@ -62,39 +62,63 @@ def main():
emitter.publish(options.make_generic_usage_error(__doc__))
return 1
if args['info']:
return _info()
returncode, err = cmds.execute(_cmds(), args)
if err is not None:
emitter.publish(err)
emitter.publish(options.make_generic_usage_error(__doc__))
return 1
if args['add']:
return _add()
return returncode
if args['version'] and args['list']:
return _version_list(args['<app-id>'], args['--max-count'])
if args['list']:
return _list()
def _cmds():
"""
:returns: all the supported commands
:rtype: dcos.api.cmds.Command
"""
if args['remove']:
return _remove(args['<app-id>'], args['--force'])
return [
cmds.Command(hierarchy=['info'], arg_keys=[], function=_info),
if args['show']:
return _show(args['<app-id>'], args['--app-version'])
cmds.Command(hierarchy=['add'], arg_keys=[], function=_add),
if args['start']:
return _start(args['<app-id>'], args['<instances>'], args['--force'])
cmds.Command(
hierarchy=['version', 'list'],
arg_keys=['<app-id>', '--max-count'],
function=_version_list),
if args['stop']:
return _stop(args['<app-id>'], args['--force'])
cmds.Command(hierarchy=['list'], arg_keys=[], function=_list),
if args['update']:
return _update(args['<app-id>'], args['<properties>'], args['--force'])
cmds.Command(
hierarchy=['remove'],
arg_keys=['<app-id>', '--force'],
function=_remove),
if args['restart']:
return _restart(args['<app-id>'], args['--force'])
cmds.Command(
hierarchy=['show'],
arg_keys=['<app-id>', '--app-version'],
function=_show),
emitter.publish(options.make_generic_usage_error(__doc__))
cmds.Command(
hierarchy=['start'],
arg_keys=['<app-id>', '<instances>', '--force'],
function=_start),
return 1
cmds.Command(
hierarchy=['stop'],
arg_keys=['<app-id>', '--force'],
function=_stop),
cmds.Command(
hierarchy=['update'],
arg_keys=['<app-id>', '<properties>', '--force'],
function=_update),
cmds.Command(
hierarchy=['restart'],
arg_keys=['<app-id>', '--force'],
function=_restart),
]
def _info():

View File

@@ -1,6 +1,15 @@
API Documentation
=================
The :mod:`dcos.api.cmds` Module
---------------------------------
.. automodule:: dcos.api.cmds
:members:
:undoc-members:
:show-inheritance:
:inherited-members:
The :mod:`dcos.api.config` Module
---------------------------------

100
tests/test_cmds.py Normal file
View File

@@ -0,0 +1,100 @@
import pytest
from dcos.api import cmds, errors
@pytest.fixture
def args():
return {
'cmd-a': True,
'cmd-b': True,
'cmd-c': False,
'arg-1': 'arg-1',
'arg-2': 'arg-2',
'arg-0': 'arg-0',
}
def test_single_cmd(args):
commands = [
cmds.Command(
hierarchy=['cmd-a', 'cmd-b'],
arg_keys=['arg-0', 'arg-1', 'arg-2'],
function=function),
]
assert cmds.execute(commands, args) == (1, None)
def test_multiple_cmd(args):
commands = [
cmds.Command(
hierarchy=['cmd-c'],
arg_keys=['arg-0', 'arg-1', 'arg-2'],
function=pytest.fail),
cmds.Command(
hierarchy=['cmd-a', 'cmd-b'],
arg_keys=['arg-0', 'arg-1', 'arg-2'],
function=function),
]
assert cmds.execute(commands, args) == (1, None)
def test_no_matching_cmd(args):
commands = [
cmds.Command(
hierarchy=['cmd-c'],
arg_keys=['arg-0', 'arg-1', 'arg-2'],
function=pytest.fail),
]
returncode, err = cmds.execute(commands, args)
assert returncode is None
assert isinstance(err, errors.Error)
def test_similar_cmds(args):
commands = [
cmds.Command(
hierarchy=['cmd-a', 'cmd-b'],
arg_keys=['arg-0', 'arg-1', 'arg-2'],
function=function),
cmds.Command(
hierarchy=['cmd-a'],
arg_keys=['arg-0', 'arg-1', 'arg-2'],
function=pytest.fail),
]
assert cmds.execute(commands, args) == (1, None)
def test_missing_cmd(args):
commands = [
cmds.Command(
hierarchy=['cmd-d'],
arg_keys=['arg-0', 'arg-1', 'arg-2'],
function=pytest.fail),
]
with pytest.raises(KeyError):
returncode, err = cmds.execute(commands, args)
def test_missing_arg(args):
commands = [
cmds.Command(
hierarchy=['cmd-a'],
arg_keys=['arg-3'],
function=pytest.fail),
]
with pytest.raises(KeyError):
returncode, err = cmds.execute(commands, args)
def function(*args):
for i in range(len(args)):
assert args[i] == 'arg-{}'.format(i)
return 1