Browse Source

add support for initial_value gooey option

pull/662/head
Chris 3 years ago
parent
commit
055708ec3c
13 changed files with 394 additions and 23 deletions
  1. 16
      docs/Gooey-Options.md
  2. 54
      gooey/gui/components/options/options.py
  3. 6
      gooey/gui/components/widgets/bases.py
  4. 14
      gooey/python_bindings/argparse_to_json.py
  5. 2
      gooey/tests/test_argparse_to_json.py
  6. 58
      gooey/tests/test_checkbox.py
  7. 54
      gooey/tests/test_common.py
  8. 51
      gooey/tests/test_counter.py
  9. 40
      gooey/tests/test_dropdown.py
  10. 58
      gooey/tests/test_listbox.py
  11. 22
      gooey/tests/test_numeric_inputs.py
  12. 13
      gooey/tests/test_slider.py
  13. 29
      gooey/tests/test_textfield.py

16
docs/Gooey-Options.md

@ -22,7 +22,8 @@ and with that, you're ready to rock.
## Overview
* Global Style Options
* Global Style/Layout Options
* Global Config Options
* Custom Widget Options
* Textarea
* BlockCheckbox
@ -31,7 +32,7 @@ and with that, you're ready to rock.
* Argument Group Options
## Global Widget Styles
## Global Style / Layout Options
All widgets in Gooey (with the exception of RadioGroups) are made up of three basic components.
@ -72,6 +73,17 @@ parser.add_argument('-my-arg', gooey_options={
| full_width | bool | This is a layout hint for this widget. When `True` the widget will fill the entire available space within a given row. Otherwise, it will be sized based on the column rules provided elsewhere. |
## Global Config Options
> new in 1.0.8
All widgets in Gooey accept an `initial_value` option to seed the UI.
```python
parser.add_argument('-my-arg', widget='Textarea', gooey_options={
'initial_value': 'Hello world!'
})
```
## Individual Widget Options

54
gooey/gui/components/options/options.py

@ -11,6 +11,16 @@ def _include_layout_docs(f):
return f
def _include_global_option_docs(f):
"""
Combines docstrings for options available to
all widget types.
"""
_doc = """:param initial_value: Sets the initial value in the UI.
"""
f.__doc__ = (f.__doc__ or '') + _doc
return f
def _include_chooser_msg_wildcard_docs(f):
"""
Combines the basic Chooser options (wildard, message) docsstring
@ -74,18 +84,22 @@ def LayoutOptions(label_color=None,
return _clean(locals())
@_include_layout_docs
def TextField(validator=None, **layout_options):
@_include_global_option_docs
def TextField(initial_value=None, validator=None, **layout_options):
return _clean(locals())
@_include_layout_docs
def PasswordField(validator=None, **layout_options):
@_include_global_option_docs
def PasswordField(initial_value=None, validator=None, **layout_options):
return _clean(locals())
@_include_layout_docs
def IntegerField(validator=None, min=0, max=100, increment=1, **layout_options):
@_include_global_option_docs
def IntegerField(initial_value=None, validator=None, min=0, max=100, increment=1, **layout_options):
"""
:param min: The minimum value allowed
:param max: The maximum value allowed
@ -94,7 +108,8 @@ def IntegerField(validator=None, min=0, max=100, increment=1, **layout_options):
return _clean(locals())
@_include_layout_docs
def Slider(validator=None, min=0, max=100, increment=1, **layout_options):
@_include_global_option_docs
def Slider(initial_value=None, validator=None, min=0, max=100, increment=1, **layout_options):
"""
:param min: The minimum value allowed
:param max: The maximum value allowed
@ -104,7 +119,9 @@ def Slider(validator=None, min=0, max=100, increment=1, **layout_options):
@_include_layout_docs
@_include_global_option_docs
def DecimalField(validator=None,
initial_value=None,
min=0.0,
max=1.0,
increment=0.01,
@ -120,7 +137,8 @@ def DecimalField(validator=None,
@_include_layout_docs
def TextArea(height=None, readonly=False, validator=None, **layout_options):
@_include_global_option_docs
def TextArea(initial_value=None, height=None, readonly=False, validator=None, **layout_options):
"""
:param height: The height of the TextArea.
:param readonly: Controls whether or not user's may modify the contents
@ -129,12 +147,14 @@ def TextArea(height=None, readonly=False, validator=None, **layout_options):
@_include_layout_docs
@_include_global_option_docs
def RichTextConsole(**layout_options):
return _clean(locals())
@_include_layout_docs
def ListBox(height=None, **layout_options):
@_include_global_option_docs
def ListBox(initial_value=None, height=None, **layout_options):
"""
:param height: The height of the ListBox
"""
@ -150,30 +170,36 @@ def MutexGroup(initial_selection=None, title=None, **layout_options):
@_include_layout_docs
def Dropdown(**layout_options):
@_include_global_option_docs
def Dropdown(initial_value=None, **layout_options):
return _clean(locals())
@_include_layout_docs
def Counter(**layout_options):
@_include_global_option_docs
def Counter(initial_value=None, **layout_options):
return _clean(locals())
@_include_layout_docs
def CheckBox(**layout_options):
@_include_global_option_docs
def CheckBox(initial_value=None, **layout_options):
return _clean(locals())
@_include_layout_docs
def BlockCheckBox(checkbox_label=None, **layout_options):
@_include_global_option_docs
def BlockCheckBox(initial_value=None, checkbox_label=None, **layout_options):
return _clean(locals())
@_include_layout_docs
@_include_global_option_docs
def FilterableDropdown(placeholder=None,
empty_message=None,
max_size=80,
search_strategy=None,
initial_value=None,
**layout_options):
"""
:param placeholder: Text to display when the user has provided no input
@ -205,21 +231,25 @@ def PrefixSearchStrategy(
@_include_layout_docs
@_include_global_option_docs
@_include_choose_dir_file_docs
@_include_chooser_msg_wildcard_docs
def FileChooser(wildcard=None,
default_dir=None,
default_file=None,
message=None,
initial_value=None,
**layout_options):
return _clean(locals())
@_include_layout_docs
@_include_global_option_docs
@_include_chooser_msg_wildcard_docs
def DirectoryChooser(wildcard=None,
default_path=None,
message=None,
initial_value=None,
**layout_options):
"""
:param default_path: The default path selected when the dialog spawns
@ -228,23 +258,27 @@ def DirectoryChooser(wildcard=None,
@_include_layout_docs
@_include_global_option_docs
@_include_choose_dir_file_docs
@_include_chooser_msg_wildcard_docs
def FileSaver(wildcard=None,
default_dir=None,
default_file=None,
message=None,
initial_value=None,
**layout_options):
return _clean(locals())
@_include_layout_docs
@_include_global_option_docs
@_include_choose_dir_file_docs
@_include_chooser_msg_wildcard_docs
def MultiFileSaver(wildcard=None,
default_dir=None,
default_file=None,
message=None,
initial_value=None,
**layout_options):
return _clean(locals())

6
gooey/gui/components/widgets/bases.py

@ -76,8 +76,12 @@ class TextContainer(BaseWidget):
self.SetSizer(self.layout)
self.bindMouseEvents()
self.Bind(wx.EVT_SIZE, self.onSize)
# 1.0.7 initial_value should supersede default when both are present
if self._options.get('initial_value') is not None:
self.setValue(self._options['initial_value'])
# Checking for None instead of truthiness means False-evaluaded defaults can be used.
if self._meta['default'] is not None:
elif self._meta['default'] is not None:
self.setValue(self._meta['default'])
if self._options.get('placeholder'):

14
gooey/python_bindings/argparse_to_json.py

@ -429,10 +429,15 @@ def action_to_json(action, widget, options):
},
})
default = handle_default(action, widget)
if (options.get(action.dest) or {}).get('initial_value') != None:
value = options[action.dest]['initial_value']
options[action.dest]['initial_value'] = handle_initial_values(action, widget, value)
default = handle_initial_values(action, widget, action.default)
if default == argparse.SUPPRESS:
default = None
final_options = merge(base, options.get(action.dest) or {})
validate_gooey_options(action, widget, final_options)
@ -494,7 +499,6 @@ def coerce_default(default, widget):
'Dropdown': safe_string,
'Counter': safe_string
}
# Issue #321:
# Defaults for choice types must be coerced to strings
# to be able to match the stringified `choices` used by `wx.ComboBox`
@ -505,7 +509,7 @@ def coerce_default(default, widget):
return dispatcher.get(widget, identity)(cleaned)
def handle_default(action, widget):
def handle_initial_values(action, widget, value):
handlers = [
[textinput_with_nargs_and_list_default, coerse_nargs_list],
[is_widget('Listbox'), clean_list_defaults],
@ -514,8 +518,8 @@ def handle_default(action, widget):
]
for matches, apply_coercion in handlers:
if matches(action, widget):
return apply_coercion(action.default)
return clean_default(action.default)
return apply_coercion(value)
return clean_default(value)
def coerse_nargs_list(default):

2
gooey/tests/test_argparse_to_json.py

@ -190,7 +190,7 @@ class TestArgparse(unittest.TestCase):
parser = ArgumentParser(prog='test_program')
parser.add_argument('--foo', nargs=case['nargs'], default=case['default'])
action = parser._actions[-1]
result = argparse_to_json.handle_default(action, case['w'])
result = argparse_to_json.handle_initial_values(action, case['w'], action.default)
self.assertEqual(result, case['gooey_default'])
def test_nargs(self):

58
gooey/tests/test_checkbox.py

@ -0,0 +1,58 @@
import unittest
from tests.harness import instrumentGooey
from gooey import GooeyParser
from gooey.tests import *
class TestCheckbox(unittest.TestCase):
def makeParser(self, **kwargs):
parser = GooeyParser(description='description')
parser.add_argument(
'--widget',
action='store_true',
**kwargs)
return parser
def testInitialValue(self):
cases = [
# `initial` should supersede `default`
{'inputs': {'default': False,
'widget': 'CheckBox',
'gooey_options': {'initial_value': True}},
'expect': True},
{'inputs': {'gooey_options': {'initial_value': True},
'widget': 'CheckBox'},
'expect': True},
{'inputs': {'gooey_options': {'initial_value': False},
'widget': 'CheckBox'},
'expect': False},
{'inputs': {'default': True,
'widget': 'CheckBox',
'gooey_options': {}},
'expect': True},
{'inputs': {'default': True,
'widget': 'CheckBox'},
'expect': True},
{'inputs': {'widget': 'CheckBox'},
'expect': False}
]
for case in cases:
with self.subTest(case):
parser = self.makeParser(**case['inputs'])
with instrumentGooey(parser) as (app, gooeyApp):
widget = gooeyApp.configs[0].reifiedWidgets[0]
self.assertEqual(widget.getValue()['rawValue'], case['expect'])
if __name__ == '__main__':
unittest.main()

54
gooey/tests/test_common.py

@ -0,0 +1,54 @@
import unittest
from collections import namedtuple
from tests.harness import instrumentGooey
from gooey import GooeyParser
from gooey.tests import *
Case = namedtuple('Case', 'inputs initialExpected')
class TestCommonProperties(unittest.TestCase):
"""
Test options and functionality
common across all widgets.
"""
def makeParser(self, **kwargs):
parser = GooeyParser(description='description')
parser.add_argument('--widget', **kwargs)
return parser
def testInitialValue(self):
widgets = ['ColourChooser',
'CommandField',
'DateChooser', 'DirChooser', 'FileChooser', 'FileSaver',
'FilterableDropdown', 'MultiDirChooser', 'MultiFileChooser',
'PasswordField', 'TextField', 'Textarea', 'TimeChooser']
cases = [
# initial_value supersedes, default
Case(
{'default': 'default', 'gooey_options': {'initial_value': 'some val'}},
'some val'),
Case(
{'gooey_options': {'initial_value': 'some val'}},
'some val'),
Case(
{'default': 'default', 'gooey_options': {}},
'default'),
Case({'default': 'default'},
'default')
]
for widgetName in widgets:
with self.subTest(widgetName):
for case in cases:
parser = self.makeParser(widget=widgetName, **case.inputs)
with instrumentGooey(parser) as (app, gooeyApp):
widget = gooeyApp.configs[0].reifiedWidgets[0]
self.assertEqual(widget.getValue()['rawValue'], case.initialExpected)
if __name__ == '__main__':
unittest.main()

51
gooey/tests/test_counter.py

@ -0,0 +1,51 @@
import unittest
from tests.harness import instrumentGooey
from gooey import GooeyParser
from gooey.tests import *
class TestCounter(unittest.TestCase):
def makeParser(self, **kwargs):
parser = GooeyParser(description='description')
parser.add_argument(
'--widget',
action='count',
widget="Counter",
**kwargs)
return parser
def testInitialValue(self):
cases = [
# `initial` should supersede `default`
{'inputs': {'default': 1,
'gooey_options': {'initial_value': 3}},
'expect': '3'},
{'inputs': {'gooey_options': {'initial_value': 1}},
'expect': '1'},
{'inputs': {'default': 2,
'gooey_options': {}},
'expect': '2'},
{'inputs': {'default': 1},
'expect': '1'},
{'inputs': {},
'expect': None}
]
for case in cases:
with self.subTest(case):
parser = self.makeParser(**case['inputs'])
with instrumentGooey(parser) as (app, gooeyApp):
widget = gooeyApp.configs[0].reifiedWidgets[0]
self.assertEqual(widget.getValue()['rawValue'], case['expect'])
if __name__ == '__main__':
unittest.main()

40
gooey/tests/test_dropdown.py

@ -2,13 +2,14 @@ import unittest
from argparse import ArgumentParser
from unittest.mock import patch
from gooey import GooeyParser
from tests.harness import instrumentGooey
from gooey.tests import *
class TestGooeyDropdown(unittest.TestCase):
def make_parser(self, **kwargs):
parser = ArgumentParser(description='description')
def makeParser(self, **kwargs):
parser = GooeyParser(description='description')
parser.add_argument('--dropdown', **kwargs)
return parser
@ -35,11 +36,10 @@ class TestGooeyDropdown(unittest.TestCase):
# TODO: from dynamics just like it does in parser land. It doesn't currently
# TODO: do this, so I'm manually casting it to strings for now.
[[True, False], True, 'True', ['True', 'False'], 'True']
]
for choices, default, initalSelection, dynamicUpdate, expectedFinalSelection in testcases:
parser = self.make_parser(choices=choices, default=default)
parser = self.makeParser(choices=choices, default=default)
with instrumentGooey(parser) as (app, gooeyApp):
dropdown = gooeyApp.configs[0].reifiedWidgets[0]
# ensure that default values (when supplied) are selected in the UI
@ -56,5 +56,37 @@ class TestGooeyDropdown(unittest.TestCase):
self.assertEqual(dropdown.widget.GetValue(), expectedFinalSelection)
def testInitialValue(self):
cases = [
# `initial` should supersede `default`
{'inputs': {'default': 'b',
'choices': ['a', 'b', 'c'],
'gooey_options': {'initial_value': 'a'}},
'expect': 'a'},
{'inputs': {'choices': ['a', 'b', 'c'],
'gooey_options': {'initial_value': 'a'}},
'expect': 'a'},
{'inputs': {'choices': ['a', 'b', 'c'],
'default': 'b',
'gooey_options': {}},
'expect': 'b'},
{'inputs': {'choices': ['a', 'b', 'c'],
'default': 'b'},
'expect': 'b'},
{'inputs': {'choices': ['a', 'b', 'c']},
'expect': None}
]
for case in cases:
with self.subTest(case):
parser = self.makeParser(**case['inputs'])
with instrumentGooey(parser) as (app, gooeyApp):
widget = gooeyApp.configs[0].reifiedWidgets[0]
self.assertEqual(widget.getValue()['rawValue'], case['expect'])
if __name__ == '__main__':
unittest.main()

58
gooey/tests/test_listbox.py

@ -0,0 +1,58 @@
import unittest
from tests.harness import instrumentGooey
from gooey import GooeyParser
from gooey.tests import *
class TestListbox(unittest.TestCase):
def makeParser(self, **kwargs):
parser = GooeyParser(description='description')
parser.add_argument(
'--widget',
widget="Listbox",
nargs="*",
**kwargs)
return parser
def testInitialValue(self):
cases = [
# `initial` should supersede `default`
{'inputs': {'default': 'b',
'choices': ['a', 'b', 'c'],
'gooey_options': {'initial_value': 'a'}},
'expect': ['a']},
{'inputs': {'choices': ['a', 'b', 'c'],
'gooey_options': {'initial_value': 'a'}},
'expect': ['a']},
{'inputs': {'choices': ['a', 'b', 'c'],
'gooey_options': {'initial_value': ['a', 'c']}},
'expect': ['a', 'c']},
{'inputs': {'choices': ['a', 'b', 'c'],
'default': 'b',
'gooey_options': {}},
'expect': ['b']},
{'inputs': {'choices': ['a', 'b', 'c'],
'default': 'b'},
'expect': ['b']},
{'inputs': {'choices': ['a', 'b', 'c']},
'expect': []}
]
for case in cases:
with self.subTest(case):
parser = self.makeParser(**case['inputs'])
with instrumentGooey(parser) as (app, gooeyApp):
widget = gooeyApp.configs[0].reifiedWidgets[0]
self.assertEqual(widget.getValue()['rawValue'], case['expect'])
if __name__ == '__main__':
unittest.main()

22
gooey/tests/test_numeric_inputs.py

@ -27,11 +27,33 @@ class TestNumbericInputs(unittest.TestCase):
# the default and all works as expected.
# this is a TODO for validation
[{'default': 81234, 'widget': 'IntegerField', 'gooey_options': {'max': 99999}}, 81234],
# Initial Value cases
[{'widget': 'IntegerField', 'gooey_options': {'initial_value': 0}}, 0],
[{'widget': 'IntegerField', 'gooey_options': {'initial_value': 10}}, 10],
[{'widget': 'IntegerField', 'gooey_options': {'initial_value': 76}}, 76],
# note that WX caps the value
# unless explicitly widened via gooey_options
[{'widget': 'IntegerField', 'gooey_options': {'initial_value': 81234}}, 100],
# here we set the max to something higher than
# the default and all works as expected.
# this is a TODO for validation
[{'widget': 'IntegerField', 'gooey_options': {'initial_value': 81234, 'max': 99999}}, 81234],
[{'widget': 'DecimalField'}, 0],
[{'default': 0, 'widget': 'DecimalField'}, 0],
[{'default': 81234, 'widget': 'DecimalField'}, 100],
[{'default': 81234, 'widget': 'DecimalField', 'gooey_options': {'max': 99999}}, 81234],
# Initial Value cases
[{'widget': 'DecimalField', 'gooey_options': {'initial_value': 0}}, 0],
[{'widget': 'DecimalField', 'gooey_options': {'initial_value': 10}}, 10],
[{'widget': 'DecimalField', 'gooey_options': {'initial_value': 76}}, 76],
# note that WX caps the value
# unless explicitly widened via gooey_options
[{'widget': 'DecimalField', 'gooey_options': {'initial_value': 81234}}, 100],
# here we set the max to something higher than
# the default and all works as expected.
# this is a TODO for validation
[{'widget': 'DecimalField', 'gooey_options': {'initial_value': 81234, 'max': 99999}}, 81234],
]
for inputs, expected in cases:
with self.subTest(inputs):

13
gooey/tests/test_slider.py

@ -26,6 +26,19 @@ class TestGooeySlider(unittest.TestCase):
# the default and all works as expected.
# this is a TODO for validation
[{'default': 81234, 'gooey_options': {'max': 99999}}, 81234],
# Initial Value cases
[{}, 0],
[{'gooey_options': {'initial_value': 0}}, 0],
[{'gooey_options': {'initial_value': 10}}, 10],
[{'gooey_options': {'initial_value': 76}}, 76],
# note that WX caps the value
# unless explicitly widened via gooey_options
[{'gooey_options': {'initial_value': 81234}}, 100],
# here we set the max to something higher than
# the default and all works as expected.
# this is a TODO for validation
[{'gooey_options': {'initial_value': 81234, 'max': 99999}}, 81234],
]
for inputs, expected in cases:
with self.subTest(inputs):

29
gooey/tests/test_textfield.py

@ -1,9 +1,12 @@
import unittest
from collections import namedtuple
from tests.harness import instrumentGooey
from gooey import GooeyParser
from gooey.tests import *
Case = namedtuple('Case', 'inputs initialExpected expectedAfterClearing')
class TestTextField(unittest.TestCase):
def makeParser(self, **kwargs):
@ -28,5 +31,31 @@ class TestTextField(unittest.TestCase):
def testDefaultAndInitialValue(self):
cases = [
# initial_value takes precedence when both are present
Case(
{'default': 'default_val', 'gooey_options': {'initial_value': 'some val'}},
'some val',
None),
# when no default is present
# Case({'gooey_options': {'initial_value': 'some val'}},
# 'some val',
# ''),
# [{'default': 'default', 'gooey_options': {}},
# 'default'],
# [{'default': 'default'},
# 'default'],
]
for case in cases:
parser = self.makeParser(**case.inputs)
with instrumentGooey(parser) as (app, gooeyApp):
widget = gooeyApp.configs[0].reifiedWidgets[0]
self.assertEqual(widget.getValue()['rawValue'], case.initialExpected)
widget.setValue('')
print(widget.getValue())
self.assertEqual(widget.getValue()['cmd'], case.expectedAfterClearing)
if __name__ == '__main__':
unittest.main()
Loading…
Cancel
Save