Browse Source

added subparser support

subparsing
chriskiehl 9 years ago
parent
commit
81a262f2dd
2 changed files with 141 additions and 35 deletions
  1. 67
      gooey/python_bindings/argparse_to_json.py
  2. 109
      gooey/tests/argparse_to_json_unittest.py

67
gooey/python_bindings/argparse_to_json.py

@ -9,8 +9,10 @@ from argparse import (
_StoreConstAction,
_StoreFalseAction,
_StoreTrueAction,
ArgumentParser)
ArgumentParser, _SubParsersAction)
from collections import OrderedDict
from functools import partial
VALID_WIDGETS = (
'FileChooser',
@ -29,16 +31,35 @@ VALID_WIDGETS = (
class UnknownWidgetType(Exception):
pass
class UnsupportedConfiguration(Exception):
pass
def convert(parser):
widget_dict = getattr(parser, 'widgets', {})
actions = parser._actions
if has_subparsers(actions):
if has_required(actions):
raise UnsupportedConfiguration("Gooey doesn't currently support required arguments when subprocesers are present.")
layout_type = 'column'
layout_data = {name: process(sub_parser, widget_dict) for name, sub_parser in get_subparser(actions).choices.iteritems()}
else:
layout_type = 'standard'
layout_data = process(parser, widget_dict)
return {
'layout_type': layout_type,
'widgets': layout_data
}
def process(parser, widget_dict):
mutually_exclusive_group = [
mutex_action
for group_actions in parser._mutually_exclusive_groups
for mutex_action in group_actions._group_actions]
base_actions = [action for action in parser._actions
if action not in mutually_exclusive_group
and action.dest != 'help']
@ -46,24 +67,24 @@ def convert(parser):
required_actions = filter(is_required, base_actions)
optional_actions = filter(is_optional, base_actions)
return {
'required': list(categorize(required_actions, widget_dict)),
'optional': list(categorize(optional_actions, widget_dict)) + build_radio_group(mutually_exclusive_group)
}
return list(categorize(required_actions, widget_dict, required=True)) + \
list(categorize(optional_actions, widget_dict)) + \
build_radio_group(mutually_exclusive_group)
def categorize(actions, widget_dict):
def categorize(actions, widget_dict, required=False):
_get_widget = partial(get_widget, widgets=widget_dict)
for action in actions:
if is_standard(action):
yield as_json(action, get_widget(action, widget_dict) or 'TextField')
yield as_json(action, _get_widget(action) or 'TextField', required)
elif is_choice(action):
yield as_json(action, get_widget(action, widget_dict) or 'Dropdown')
yield as_json(action, _get_widget(action) or 'Dropdown', required)
elif is_flag(action):
yield as_json(action, _get_widget(action) or 'CheckBox', required)
elif is_counter(action):
_json = as_json(action, get_widget(action, widget_dict) or 'Dropdown')
# prefill the 'counter' dropdown
_json = as_json(action, _get_widget(action) or 'Dropdown', required)
# pre-fill the 'counter' dropdown
_json['choices'] = range(1, 11)
yield _json
elif is_flag(action):
yield as_json(action, get_widget(action, widget_dict) or 'CheckBox')
else:
raise UnknownWidgetType(action)
@ -74,7 +95,19 @@ def get_widget(action, widgets):
def is_required(action):
'''_actions which are positional or possessing the `required` flag '''
return not action.option_strings or action.required == True
return not action.option_strings and not isinstance(action, _SubParsersAction) or action.required == True
def has_required(actions):
return filter(None, filter(is_required, actions))
def is_subparser(action):
return isinstance(action,_SubParsersAction)
def has_subparsers(actions):
return filter(is_subparser, actions)
def get_subparser(actions):
return filter(is_subparser, actions)[0]
def is_optional(action):
'''_actions not positional or possessing the `required` flag'''
@ -124,17 +157,18 @@ def build_radio_group(mutex_group):
return [{
'type': 'RadioGroup',
'group_name': 'Choose Option',
'required': False,
'data': options
}]
def as_json(action, widget):
def as_json(action, widget, required):
if widget not in VALID_WIDGETS:
raise UnknownWidgetType('Widget Type {0} is unrecognized'.format(widget))
option_strings = action.option_strings
return {
'type': widget,
'required': required,
'data': {
'display_name': action.dest,
'help': action.help,
@ -146,4 +180,3 @@ def as_json(action, widget):

109
gooey/tests/argparse_to_json_unittest.py

@ -3,7 +3,7 @@ from gooey.python_bindings.argparse_to_json import *
@pytest.fixture
def parser():
def empty_parser():
return argparse.ArgumentParser(description='description')
@pytest.fixture
@ -26,6 +26,27 @@ def complete_parser():
verbosity.add_argument('-j', '--jj', action="store_true", help="hhh")
return parser
@pytest.fixture
def subparser():
parser = argparse.ArgumentParser(description='qidev')
parser.add_argument('--verbose', help='be verbose', dest='verbose', action='store_true', default=False)
subs = parser.add_subparsers(help='commands', dest='command')
config_parser = subs.add_parser('config', help='configure defaults for qidev')
config_parser.add_argument('field', help='the field to configure', type=str)
config_parser.add_argument('value', help='set field to value', type=str)
# ########################################################
connect_parser = subs.add_parser('connect', help='connect to a robot (ip/hostname)')
connect_parser.add_argument('hostname', help='hostname or IP address of the robot', type=str)
# ########################################################
install_parser = subs.add_parser('install', help='package and install a project directory on a robot')
install_parser.add_argument('path', help='path to the project directory (containing manifest.xml', type=str)
install_parser.add_argument('--ip', nargs='*', type=str, dest='ip', help='specify hostname(es)/IP address(es)')
return parser
@pytest.fixture
def exclusive_group():
parser = argparse.ArgumentParser(description='description')
@ -39,6 +60,50 @@ def exclusive_group():
return mutually_exclusive_group
def test_parser_converts_to_correct_type(empty_parser, complete_parser, subparser):
assert convert(subparser)['layout_type'] == 'column'
assert convert(empty_parser)['layout_type'] == 'standard'
assert convert(complete_parser)['layout_type'] == 'standard'
def test_convert_std_parser(complete_parser):
result = convert(complete_parser)
assert result['layout_type'] == 'standard'
assert result['widgets']
assert isinstance(result['widgets'], list)
entry = result['widgets'][0]
assert 'type' in entry
assert 'required' in entry
assert 'data' in entry
required = filter(lambda x: x['required'], result['widgets'])
optional = filter(lambda x: not x['required'], result['widgets'])
assert len(required) == 4
assert len(optional) == 8
def test_convert_sub_parser(subparser):
result = convert(subparser)
assert result['layout_type'] == 'column'
assert result['widgets']
assert isinstance(result['widgets'], dict)
assert len(result['widgets']) == 3
def test_has_required(empty_parser, complete_parser, subparser):
assert has_required(complete_parser._actions)
assert not has_required(empty_parser._actions)
assert not has_required(subparser._actions)
def test_has_subparsers(subparser, complete_parser):
assert has_subparsers(subparser._actions)
assert not has_subparsers(complete_parser._actions)
def test_is_required(complete_parser):
required = filter(is_required, complete_parser._actions)
assert len(required) == 4
@ -53,31 +118,32 @@ def test_is_optional(complete_parser):
assert 'req' not in action.dest
def test_is_choice(parser):
parser.add_argument('--dropdown', choices=[1,2])
assert is_choice(get_action(parser, 'dropdown'))
def test_is_choice(empty_parser):
empty_parser.add_argument('--dropdown', choices=[1,2])
assert is_choice(get_action(empty_parser, 'dropdown'))
parser.add_argument('--storetrue', action='store_true')
assert not is_choice(get_action(parser, 'storetrue'))
empty_parser.add_argument('--storetrue', action='store_true')
assert not is_choice(get_action(empty_parser, 'storetrue'))
# make sure positionals are caught as well (issue #85)
parser.add_argument('positional', choices=[1, 2])
assert is_choice(get_action(parser, 'positional'))
empty_parser.add_argument('positional', choices=[1, 2])
assert is_choice(get_action(empty_parser, 'positional'))
def test_is_standard(empty_parser):
empty_parser.add_argument('--count', action='count')
assert not is_standard(get_action(empty_parser, 'count'))
def test_is_standard(parser):
parser.add_argument('--count', action='count')
assert not is_standard(get_action(parser, 'count'))
empty_parser.add_argument('--store', action='store')
assert is_standard(get_action(empty_parser, 'store'))
parser.add_argument('--store', action='store')
assert is_standard(get_action(parser, 'store'))
def test_is_counter(parser):
parser.add_argument('--count', action='count')
assert is_counter(get_action(parser, 'count'))
def test_is_counter(empty_parser):
empty_parser.add_argument('--count', action='count')
assert is_counter(get_action(empty_parser, 'count'))
parser.add_argument('--dropdown', choices=[1,2])
assert not is_counter(get_action(parser, 'dropdown'))
empty_parser.add_argument('--dropdown', choices=[1,2])
assert not is_counter(get_action(empty_parser, 'dropdown'))
def test_mutually(exclusive_group):
@ -97,9 +163,16 @@ def get_action(parser, dest):
if action.dest == dest:
return action
def find_arg_by_option(group, option_string):
for arg in group:
if option_string in arg.option_strings:
return arg
Loading…
Cancel
Save