Browse Source

closes #408 - allow hiding of widgets in UI

pull/473/head
Chris 5 years ago
parent
commit
fca40dbcd5
5 changed files with 163 additions and 47 deletions
  1. 2
      gooey/gui/components/config.py
  2. 49
      gooey/python_bindings/constraints.py
  3. 20
      gooey/python_bindings/gooey_parser.py
  4. 98
      gooey/tests/test_constraints.py
  5. 41
      gooey/tests/test_language_parity.py

2
gooey/gui/components/config.py

@ -134,6 +134,8 @@ class ConfigPage(ScrolledPanel):
sizer = wx.BoxSizer(wx.HORIZONTAL)
for item in uigroup:
widget = self.reifyWidget(parent, item)
if not getin(item, ['options', 'visible'], True):
widget.Hide()
# !Mutate the reifiedWidgets instance variable in place
self.reifiedWidgets.append(widget)
sizer.Add(widget, 1, wx.ALL | wx.EXPAND, 5)

49
gooey/python_bindings/constraints.py

@ -0,0 +1,49 @@
"""
Basic constraints to ensure GooeyParser is fed all the info it needs
for various widget classes.
TODO: this should all live in the build_config stage here where it is used
within the GooeyParser directly. As is, logic is fragmented across files. Some
assertions happen in argparse_to_json, while others happen in GooeyParser.
Whenever refactoring happens, these should be removed from GooeyParser.
"""
from textwrap import dedent
def is_required(action):
return action.required
def is_hidden(options):
return not options.get('visible', True)
def has_validator(options):
return bool(options.get('validator'))
def has_default(action):
return bool(action.default)
def assert_visibility_requirements(action, options):
if action.required and is_hidden(options) \
and not (has_validator(options) or has_default(action)):
raise ValueError(dedent(
'''
When using Gooey's hidden field functionality, you must either '
(a) provide a default value, or '
(b) provide a custom validator'
Without one of those, your users will be unable to advance past
the configuration screen as they cannot interact with your
hidden field, and the default validator requires something to
be present for fields marked as `required`.
'''
))
def assert_listbox_constraints(widget, **kwargs):
if widget and widget == 'Listbox':
if not 'nargs' in kwargs or kwargs['nargs'] not in ['*', '+']:
raise ValueError(
'Gooey\'s Listbox widget requires that nargs be specified.\n'
'Nargs must be set to either `*` or `+` (e.g. nargs="*")'
)

20
gooey/python_bindings/gooey_parser.py

@ -1,5 +1,6 @@
from argparse import ArgumentParser, _SubParsersAction
from argparse import _MutuallyExclusiveGroup, _ArgumentGroup
from textwrap import dedent
from gooey.gui.lang.i18n import _
@ -84,16 +85,16 @@ class GooeyParser(object):
metavar = kwargs.pop('metavar', None)
options = kwargs.pop('gooey_options', None)
if widget and widget == 'Listbox':
if not 'nargs' in kwargs or kwargs['nargs'] not in ['*', '+']:
raise ValueError(
'Gooey\'s Listbox widget requires that nargs be specified.\n'
'Nargs must be set to either `*` or `+` (e.g. nargs="*")'
)
self.parser.add_argument(*args, **kwargs)
self.parser._actions[-1].metavar = metavar
self.widgets[self.parser._actions[-1].dest] = widget
self.options[self.parser._actions[-1].dest] = options
self._validate_constraints(
self.parser._actions[-1],
widget,
options or {},
**kwargs
)
def add_mutually_exclusive_group(self, *args, **kwargs):
options = kwargs.pop('gooey_options', {})
@ -143,6 +144,13 @@ class GooeyParser(object):
# return the created parsers action
return action
def _validate_constraints(self, parser_action, widget, options, **kwargs):
from gooey.python_bindings import constraints
constraints.assert_listbox_constraints(widget, **kwargs)
constraints.assert_visibility_requirements(parser_action, options)
def __getattr__(self, item):
return getattr(self.parser, item)

98
gooey/tests/test_constraints.py

@ -0,0 +1,98 @@
import unittest
from gooey import GooeyParser
class TestConstraints(unittest.TestCase):
def test_listbox_constraints(self):
"""
Listbox widgets must be provided a nargs option
"""
# Trying to create a listbox widget without specifying nargs
# throws an error
with self.assertRaises(ValueError):
parser = GooeyParser()
parser.add_argument('one', choices=['one', 'two'], widget='Listbox')
# Listbox with an invalid nargs value throws an error
with self.assertRaises(ValueError):
parser = GooeyParser()
parser.add_argument(
'one', choices=['one', 'two'], widget='Listbox', nargs='?')
# Listbox with an invalid nargs value throws an error
with self.assertRaises(ValueError):
parser = GooeyParser()
parser.add_argument(
'one', choices=['one', 'two'], widget='Listbox', nargs=3)
# valid nargs throw no errors
for narg in ['*', '+']:
parser = GooeyParser()
parser.add_argument(
'one', choices=['one', 'two'], widget='Listbox', nargs=narg)
def test_visibility_constraint(self):
"""
When visible=False in Gooey config, the user MUST supply either
a custom validator or a default value.
"""
# added without issue
parser = GooeyParser()
parser.add_argument('one')
# still fine
parser = GooeyParser()
parser.add_argument('one', gooey_options={'visible': True})
# trying to hide an input without a default or custom validator
# results in an error
with self.assertRaises(ValueError):
parser = GooeyParser()
parser.add_argument('one', gooey_options={'visible': False})
# explicit default=None; still error
with self.assertRaises(ValueError):
parser = GooeyParser()
parser.add_argument(
'one',
default=None,
gooey_options={'visible': False})
# default = empty string. Still error
with self.assertRaises(ValueError):
parser = GooeyParser()
parser.add_argument(
'one',
default='',
gooey_options={'visible': False})
# default = valid string. No Error
parser = GooeyParser()
parser.add_argument(
'one',
default='Hello',
gooey_options={'visible': False})
# No default, but custom validator: Success
parser = GooeyParser()
parser.add_argument(
'one',
gooey_options={
'visible': False,
'validator': {'test': 'true'}
})
# default AND validator, still fine
parser = GooeyParser()
parser.add_argument(
'one',
default='Hai',
gooey_options={
'visible': False,
'validator': {'test': 'true'}
})

41
gooey/tests/test_language_parity.py

@ -1,41 +0,0 @@
import os
import unittest
import json
from collections import OrderedDict
from gooey import languages
from gooey.gui.processor import ProcessController
class TestLanguageParity(unittest.TestCase):
"""
Checks that all language files have the same set of keys so that non-english
languages don't silently break as features are added to Gooey.
"""
def test_languageParity(self):
langDir = os.path.dirname(languages.__file__)
englishFile = os.path.join(langDir, 'english.json')
english = self.readFile(englishFile)
jsonFiles = [(path, self.readFile(os.path.join(langDir, path)))
for path in os.listdir(langDir)
if path.endswith('json') and 'poooo' not in path and '2' not in path]
allKeys = set(english.keys())
for name, contents in jsonFiles:
missing = allKeys.difference(set(contents.keys()))
self.assertEqual(
set(),
missing,
"{} language file is missing keys: [{}]".format(name, missing)
)
def readFile(self, path):
with open(path, 'r', encoding='utf-8') as f:
return json.loads(f.read())
if __name__ == '__main__':
unittest.main()
Loading…
Cancel
Save