mirror of https://github.com/chriskiehl/Gooey.git
Chris
4 years ago
23 changed files with 1149 additions and 87 deletions
Unified View
Diff Options
-
49docs/Filterable-Dropdown-Guide.md
-
99docs/releases/1.0.7-release-notes.md
-
1gooey/__init__.py
-
16gooey/gui/components/filtering/prefix_filter.py
-
2gooey/gui/components/footer.py
-
0gooey/gui/components/options/__init__.py
-
42gooey/gui/components/options/layout.py
-
291gooey/gui/components/options/options.py
-
127gooey/gui/components/options/validation.py
-
199gooey/gui/components/options/validators.py
-
2gooey/gui/components/widgets/__init__.py
-
21gooey/gui/components/widgets/bases.py
-
5gooey/gui/components/widgets/core/chooser.py
-
3gooey/gui/components/widgets/core/text_input.py
-
50gooey/gui/components/widgets/numeric_fields.py
-
74gooey/gui/components/widgets/options.py
-
28gooey/gui/components/widgets/slider.py
-
6gooey/gui/components/widgets/textfield.py
-
33gooey/python_bindings/argparse_to_json.py
-
89gooey/tests/test_numeric_inputs.py
-
54gooey/tests/test_options.py
-
40gooey/tests/test_slider.py
-
5gooey/util/functional.py
@ -0,0 +1,49 @@ |
|||||
|
# Customizing FilterableDropdown's search behavior |
||||
|
|
||||
|
|
||||
|
Out of the box, FilterableDropdown does a very simple 'startswith' style lookup to find candidates which match the user's input. However, this behavior can be customized using GooeyOptions to support all kinds of filtering strategies. |
||||
|
|
||||
|
For each example, we'll be starting with the following sample program. This uses just 4 choices to keep the different options easy to follow. However, `FilterableDropdown` is fully virtualized and can be used with 10s of thousands of choices. |
||||
|
|
||||
|
|
||||
|
```python |
||||
|
from gooey import Gooey, GooeyParser, PrefixTokenizers |
||||
|
|
||||
|
choices = [ |
||||
|
'Afghanistan Kabul', |
||||
|
'Albania Tirana', |
||||
|
'Japan Kyoto', |
||||
|
'Japan Tokyo' |
||||
|
] |
||||
|
|
||||
|
@Gooey(program_name='FilterableDropdown Demo', poll_external_updates=True) |
||||
|
def main(): |
||||
|
parser = GooeyParser(description="Example of the Filterable Dropdown") |
||||
|
parser.add_argument( |
||||
|
"-a", |
||||
|
"--myargument", |
||||
|
metavar='Country', |
||||
|
help='Search for a country', |
||||
|
choices=choices, |
||||
|
widget='FilterableDropdown', |
||||
|
gooey_options={ |
||||
|
'label_color': (255, 100, 100), |
||||
|
'placeholder': 'Start typing to view suggestions' |
||||
|
}) |
||||
|
args = parser.parse_args() |
||||
|
print(args) |
||||
|
``` |
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
|
## Combining results |
||||
|
|
||||
|
## Suffix Trees |
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
|
|
@ -0,0 +1,99 @@ |
|||||
|
## Gooey 1.0.6 Released! |
||||
|
|
||||
|
|
||||
|
|
||||
|
Quality of Life improvements for Gooey Options. |
||||
|
|
||||
|
Goal is to enable more IDE auto-completion help as well as more REPL driven usefulness via help() and docstrings. |
||||
|
|
||||
|
Gooey now exposes a top-level set of public gooey_option data constructors. |
||||
|
|
||||
|
```python |
||||
|
from gooey import options |
||||
|
|
||||
|
parser.add_argument( |
||||
|
'--foo', |
||||
|
help='Some foo thing', |
||||
|
widget='FilterableDropdown', |
||||
|
gooey_options=options.FilterableDropdown( |
||||
|
placeholder='Search for a Foo', |
||||
|
search_strategy=options.PrefixSearchStrategy( |
||||
|
ignore_case=True |
||||
|
) |
||||
|
)) |
||||
|
``` |
||||
|
|
||||
|
Note that these are _just_ helpers for generating the right data shapes. They're still generating plain data behind the scenes and thus all existing gooey_options code remains 100% compatible. |
||||
|
|
||||
|
**Better Docs:** |
||||
|
|
||||
|
Which is to say, documentation which actually exists rather than _not_ exist. You can inspect the docs live in the REPL or by hopping to the symbol in editors which support such things. |
||||
|
|
||||
|
``` |
||||
|
>>> from gooey import options |
||||
|
>>> help(options.RadioGroup) |
||||
|
Help on function FileChooser in module __main__: |
||||
|
|
||||
|
FileChooser(wildcard=None, default_dir=None, default_file=None, message=None, **layout_options) |
||||
|
:param wildcard: Sets the wildcard, which can contain multiple file types, for |
||||
|
example: "BMP files (.bmp)|.bmp|GIF files (.gif)|.gif" |
||||
|
:param message: Sets the message that will be displayed on the dialog. |
||||
|
:param default_dir: The default directory selected when the dialog spawns |
||||
|
:param default_file: The default filename used in the dialog |
||||
|
|
||||
|
Layout Options: |
||||
|
--------------- |
||||
|
|
||||
|
Color options can be passed either as a hex string ('#ff0000') or as |
||||
|
a collection of RGB values (e.g. `[255, 0, 0]` or `(255, 0, 0)`) |
||||
|
|
||||
|
:param label_color: The foreground color of the label text |
||||
|
:param label_bg_color: The background color of the label text. |
||||
|
:param help_color: The foreground color of the help text. |
||||
|
:param help_bg_color: The background color of the help text. |
||||
|
:param error_color: The foreground color of the error text (when visible). |
||||
|
:param error_bg_color: The background color of the error text (when visible). |
||||
|
:param show_label: Toggles whether or not to display the label text |
||||
|
:param show_help: Toggles whether or not to display the help text |
||||
|
:param visible: Hides the entire widget when False. Note: the widget |
||||
|
is still present in the UI and will still send along any |
||||
|
default values that have been provided in code. This option |
||||
|
is here for when you want to hide certain advanced / dangerous |
||||
|
inputs from your GUI users. |
||||
|
:param full_width: 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. |
||||
|
``` |
||||
|
|
||||
|
|
||||
|
Previously, Gooey Options have been an opaque map. While great of openness / extenisibility, it's pretty terrible from a "what does this actually take..?" perspective. |
||||
|
|
||||
|
|
||||
|
Ideally, and eventually, we'll be able to completely type these options to increase visibility / usability even more. However, for backwards compatibility reasons, Gooey will continue to be sans types for the time being. |
||||
|
|
||||
|
|
||||
|
|
||||
|
### Gooey Options: placeholder |
||||
|
|
||||
|
Widgets with text inputs now all accept a `placeholder` Gooey option. |
||||
|
|
||||
|
```python |
||||
|
add_argument('--foo', widget='TextField', gooey_options=options.TextField( |
||||
|
placeholder='Type some text here!' |
||||
|
) |
||||
|
``` |
||||
|
|
||||
|
### New Widget: IntegerField |
||||
|
|
||||
|
### New Widget: DecimalField |
||||
|
|
||||
|
### New Widget: Slider |
||||
|
|
||||
|
|
||||
|
### New Validator option: RegexValidator |
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
|
|
@ -0,0 +1,42 @@ |
|||||
|
from pyrsistent import pmap |
||||
|
|
||||
|
from gooey.gui.components.options.validation import validate_color, _unit |
||||
|
|
||||
|
layout_validators = { |
||||
|
'label_color': validate_color, |
||||
|
'label_bg_color': validate_color, |
||||
|
'help_color': validate_color, |
||||
|
'help_bg_color': validate_color, |
||||
|
'error_color': validate_color, |
||||
|
'error_bg_color': validate_color, |
||||
|
# 'show_label': validate_bool, |
||||
|
# 'show_help': validate_bool, |
||||
|
# 'visible': validate_bool, |
||||
|
# 'full_width': validate_bool |
||||
|
} |
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
|
def layout_options(label_color=None, |
||||
|
label_bg_color=None, |
||||
|
help_color=None, |
||||
|
help_bg_color=None, |
||||
|
error_color=None, |
||||
|
error_bg_color=None, |
||||
|
show_label=True, |
||||
|
show_help=True, |
||||
|
visible=True, |
||||
|
full_width=False): |
||||
|
|
||||
|
options = pmap({k:v for k,v in locals().items() if v is not None}) |
||||
|
failures = {} |
||||
|
for k,v in options.items(): |
||||
|
validator = layout_validators.get(k, _unit) |
||||
|
err, success = validator(v) |
||||
|
if err: |
||||
|
failures[k] = err |
||||
|
|
||||
|
if failures: |
||||
|
raise ValueError(failures) |
||||
|
return options |
@ -0,0 +1,291 @@ |
|||||
|
from gooey.gui.components.filtering.prefix_filter import PrefixTokenizers |
||||
|
|
||||
|
|
||||
|
|
||||
|
def _include_layout_docs(f): |
||||
|
""" |
||||
|
Combines the layout_options docsstring with the |
||||
|
wrapped function's doc string. |
||||
|
""" |
||||
|
f.__doc__ = (f.__doc__ or '') + LayoutOptions.__doc__ |
||||
|
return f |
||||
|
|
||||
|
|
||||
|
def _include_chooser_msg_wildcard_docs(f): |
||||
|
""" |
||||
|
Combines the basic Chooser options (wildard, message) docsstring |
||||
|
with the wrapped function's doc string. |
||||
|
""" |
||||
|
_doc = """:param wildcard: Sets the wildcard, which can contain multiple file types, for |
||||
|
example: "BMP files (.bmp)|.bmp|GIF files (.gif)|.gif" |
||||
|
:param message: Sets the message that will be displayed on the dialog. |
||||
|
""" |
||||
|
f.__doc__ = (f.__doc__ or '') + _doc |
||||
|
return f |
||||
|
|
||||
|
def _include_choose_dir_file_docs(f): |
||||
|
""" |
||||
|
Combines the basic Chooser options (wildard, message) docsstring |
||||
|
with the wrapped function's doc string. |
||||
|
""" |
||||
|
_doc = """:param default_dir: The default directory selected when the dialog spawns |
||||
|
:param default_file: The default filename used in the dialog |
||||
|
""" |
||||
|
f.__doc__ = (f.__doc__ or '') + _doc |
||||
|
return f |
||||
|
|
||||
|
|
||||
|
|
||||
|
def LayoutOptions(label_color=None, |
||||
|
label_bg_color=None, |
||||
|
help_color=None, |
||||
|
help_bg_color=None, |
||||
|
error_color=None, |
||||
|
error_bg_color=None, |
||||
|
show_label=True, |
||||
|
show_help=True, |
||||
|
visible=True, |
||||
|
full_width=False): |
||||
|
""" |
||||
|
Layout Options: |
||||
|
--------------- |
||||
|
|
||||
|
Color options can be passed either as a hex string ('#ff0000') or as |
||||
|
a collection of RGB values (e.g. `[255, 0, 0]` or `(255, 0, 0)`) |
||||
|
|
||||
|
:param label_color: The foreground color of the label text |
||||
|
:param label_bg_color: The background color of the label text. |
||||
|
:param help_color: The foreground color of the help text. |
||||
|
:param help_bg_color: The background color of the help text. |
||||
|
:param error_color: The foreground color of the error text (when visible). |
||||
|
:param error_bg_color: The background color of the error text (when visible). |
||||
|
:param show_label: Toggles whether or not to display the label text |
||||
|
:param show_help: Toggles whether or not to display the help text |
||||
|
:param visible: Hides the entire widget when False. Note: the widget |
||||
|
is still present in the UI and will still send along any |
||||
|
default values that have been provided in code. This option |
||||
|
is here for when you want to hide certain advanced / dangerous |
||||
|
inputs from your GUI users. |
||||
|
:param full_width: 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. |
||||
|
""" |
||||
|
return _clean(locals()) |
||||
|
|
||||
|
|
||||
|
@_include_layout_docs |
||||
|
def TextField(validator=None, **layout_options): |
||||
|
return _clean(locals()) |
||||
|
|
||||
|
|
||||
|
@_include_layout_docs |
||||
|
def PasswordField(validator=None, **layout_options): |
||||
|
return _clean(locals()) |
||||
|
|
||||
|
|
||||
|
@_include_layout_docs |
||||
|
def IntegerField(validator=None, min=0, max=100, increment=1, **layout_options): |
||||
|
""" |
||||
|
:param min: The minimum value allowed |
||||
|
:param max: The maximum value allowed |
||||
|
:param increment: The step size of the spinner |
||||
|
""" |
||||
|
return _clean(locals()) |
||||
|
|
||||
|
|
||||
|
@_include_layout_docs |
||||
|
def DecimalField(validator=None, |
||||
|
min=0.0, |
||||
|
max=1.0, |
||||
|
increment=0.01, |
||||
|
precision=2, |
||||
|
**layout_options): |
||||
|
""" |
||||
|
:param min: The minimum value allowed |
||||
|
:param max: The maximum value allowed |
||||
|
:param increment: The step size of the spinner |
||||
|
:param precision: The precision of the decimal (0-20) |
||||
|
""" |
||||
|
return _clean(locals()) |
||||
|
|
||||
|
|
||||
|
@_include_layout_docs |
||||
|
def TextArea(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 |
||||
|
""" |
||||
|
return _clean(locals()) |
||||
|
|
||||
|
|
||||
|
@_include_layout_docs |
||||
|
def RichTextConsole(**layout_options): |
||||
|
return _clean(locals()) |
||||
|
|
||||
|
|
||||
|
@_include_layout_docs |
||||
|
def ListBox(height=None, **layout_options): |
||||
|
""" |
||||
|
:param height: The height of the ListBox |
||||
|
""" |
||||
|
return _clean(locals()) |
||||
|
|
||||
|
# TODO: what are this guy's layout options..? |
||||
|
def MutexGroup(initial_selection=None, title=None, **layout_options): |
||||
|
""" |
||||
|
:param initial_selection: The index of the option which should be initially selected. |
||||
|
:param title: Adds the supplied title above the RadioGroup options (when present) |
||||
|
""" |
||||
|
return _clean(locals()) |
||||
|
|
||||
|
|
||||
|
@_include_layout_docs |
||||
|
def Dropdown(**layout_options): |
||||
|
return _clean(locals()) |
||||
|
|
||||
|
|
||||
|
@_include_layout_docs |
||||
|
def Counter(**layout_options): |
||||
|
return _clean(locals()) |
||||
|
|
||||
|
|
||||
|
@_include_layout_docs |
||||
|
def CheckBox(**layout_options): |
||||
|
return _clean(locals()) |
||||
|
|
||||
|
|
||||
|
@_include_layout_docs |
||||
|
def BlockCheckBox(checkbox_label=None, **layout_options): |
||||
|
return _clean(locals()) |
||||
|
|
||||
|
|
||||
|
@_include_layout_docs |
||||
|
def FilterableDropdown(placeholder=None, |
||||
|
empty_message=None, |
||||
|
max_size=80, |
||||
|
search_strategy=None, |
||||
|
**layout_options): |
||||
|
""" |
||||
|
:param placeholder: Text to display when the user has provided no input |
||||
|
:param empty_message: Text to display if the user's query doesn't match anything |
||||
|
:param max_size: maximum height of the dropdown |
||||
|
:param search_strategy: see: PrefixSearchStrategy |
||||
|
""" |
||||
|
return _clean(locals()) |
||||
|
|
||||
|
|
||||
|
def PrefixSearchStrategy( |
||||
|
choice_tokenizer=PrefixTokenizers.WORDS, |
||||
|
input_tokenizer=PrefixTokenizers.REGEX('\s'), |
||||
|
ignore_case=True, |
||||
|
operator='AND', |
||||
|
index_suffix=False): |
||||
|
""" |
||||
|
:param choice_tokenizer: See: PrefixTokenizers - sets the tokenization strategy |
||||
|
for the `choices` |
||||
|
:param input_tokenizer: See: PrefixTokenizers sets how the users's `input` get tokenized. |
||||
|
:param ignore_case: Controls whether or not to honor case while searching |
||||
|
:param operator: see: `OperatorType` - controls whether or not individual |
||||
|
search tokens |
||||
|
get `AND`ed or `OR`d together when evaluating a match. |
||||
|
:param index_suffix: When enabled, generates a suffix-tree to enable efficient |
||||
|
partial-matching against any of the tokens. |
||||
|
""" |
||||
|
return {**_clean(locals()), 'type': 'PrefixFilter'} |
||||
|
|
||||
|
|
||||
|
@_include_layout_docs |
||||
|
@_include_choose_dir_file_docs |
||||
|
@_include_chooser_msg_wildcard_docs |
||||
|
def FileChooser(wildcard=None, |
||||
|
default_dir=None, |
||||
|
default_file=None, |
||||
|
message=None, |
||||
|
**layout_options): |
||||
|
return _clean(locals()) |
||||
|
|
||||
|
|
||||
|
@_include_layout_docs |
||||
|
@_include_chooser_msg_wildcard_docs |
||||
|
def DirectoryChooser(wildcard=None, |
||||
|
default_path=None, |
||||
|
message=None, |
||||
|
**layout_options): |
||||
|
""" |
||||
|
:param default_path: The default path selected when the dialog spawns |
||||
|
""" |
||||
|
return _clean(locals()) |
||||
|
|
||||
|
|
||||
|
@_include_layout_docs |
||||
|
@_include_choose_dir_file_docs |
||||
|
@_include_chooser_msg_wildcard_docs |
||||
|
def FileSaver(wildcard=None, |
||||
|
default_dir=None, |
||||
|
default_file=None, |
||||
|
message=None, |
||||
|
**layout_options): |
||||
|
return _clean(locals()) |
||||
|
|
||||
|
|
||||
|
@_include_layout_docs |
||||
|
@_include_choose_dir_file_docs |
||||
|
@_include_chooser_msg_wildcard_docs |
||||
|
def MultiFileSaver(wildcard=None, |
||||
|
default_dir=None, |
||||
|
default_file=None, |
||||
|
message=None, |
||||
|
**layout_options): |
||||
|
return _clean(locals()) |
||||
|
|
||||
|
|
||||
|
def ExpressionValidator(test=None, message=None): |
||||
|
""" |
||||
|
Creates the data for a basic expression validator. |
||||
|
|
||||
|
Your test function can be made up of any valid Python expression. |
||||
|
It receives the variable user_input as an argument against which to |
||||
|
perform its validation. Note that all values coming from Gooey |
||||
|
are in the form of a string, so you'll have to cast as needed |
||||
|
in order to perform your validation. |
||||
|
""" |
||||
|
return {**_clean(locals()), 'type': 'ExpressionValidator'} |
||||
|
|
||||
|
|
||||
|
def RegexValidator(test=None, message=None): |
||||
|
""" |
||||
|
Creates the data for a basic RegexValidator. |
||||
|
|
||||
|
:param test: the regex expression. This should be the expression |
||||
|
directly (i.e. `test='\d+'`). Gooey will test |
||||
|
that the user's input satisfies this expression. |
||||
|
:param message: The message to display if the input doesn't match |
||||
|
the regex |
||||
|
""" |
||||
|
return {**_clean(locals()), 'type': 'RegexValidator'} |
||||
|
|
||||
|
|
||||
|
def ArgumentGroup(show_border=False, |
||||
|
show_underline=True, |
||||
|
label_color=None, |
||||
|
columns=None, |
||||
|
margin_top=None): |
||||
|
""" |
||||
|
:param show_border: When True a labeled border will surround all widgets added to this group. |
||||
|
:param show_underline: Controls whether or not to display the underline when using the default border style |
||||
|
:param label_color: The foreground color for the group name |
||||
|
:param columns: Controls the number of widgets on each row |
||||
|
:param margin_top: specifies the top margin in pixels for this group |
||||
|
""" |
||||
|
return _clean(locals()) |
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
|
def _clean(options): |
||||
|
cleaned = {k: v for k, v in options.items() |
||||
|
if v is not None and k is not "layout_options"} |
||||
|
return {**options.get('layout_options', {}), **cleaned} |
||||
|
|
@ -0,0 +1,127 @@ |
|||||
|
from textwrap import dedent |
||||
|
|
||||
|
from pyrsistent import m, pmap, v |
||||
|
import re |
||||
|
|
||||
|
|
||||
|
def validate_and_raise(validators, some_map): |
||||
|
err, success = validate(validators, some_map) |
||||
|
if err: |
||||
|
raise ValueError(err) |
||||
|
return success |
||||
|
|
||||
|
def validate(validators, some_map): |
||||
|
failures = {} |
||||
|
for k, v in some_map.items(): |
||||
|
validator = validators.get(k, _unit) |
||||
|
err, success = validator(v) |
||||
|
if err: |
||||
|
failures[k] = err |
||||
|
|
||||
|
if failures: |
||||
|
return [[failures], None] |
||||
|
return [None, some_map] |
||||
|
|
||||
|
def _unit(value): |
||||
|
return [None, value] |
||||
|
|
||||
|
def is_tuple_or_list(value): |
||||
|
return isinstance(value, list) or isinstance(value, tuple) |
||||
|
|
||||
|
def is_str(value): |
||||
|
return isinstance(value, str) |
||||
|
|
||||
|
|
||||
|
def validate_str_or_coll(value): |
||||
|
error_msg = dedent(''' |
||||
|
Colors must be either a hex string or collection of RGB values. |
||||
|
e.g. |
||||
|
Hex string: #fff0ce |
||||
|
RGB Collection: [0, 255, 128] or (0, 255, 128) |
||||
|
''') |
||||
|
return ([None, value] |
||||
|
if is_str(value) or is_tuple_or_list(value) |
||||
|
else [[error_msg], None]) |
||||
|
|
||||
|
def validate_color(value): |
||||
|
def _validate(value): |
||||
|
if isinstance(value, str): |
||||
|
return isfoo(value) |
||||
|
else: |
||||
|
return validate_rgb(value) |
||||
|
return _flatmap(_validate, validate_str_or_coll(value)) |
||||
|
|
||||
|
def validate_rgb_vals(rgb_coll): |
||||
|
failures = [] |
||||
|
for val, channel in zip(rgb_coll, 'RGB'): |
||||
|
err, success = is_uint8(val) |
||||
|
if err: |
||||
|
failures += ['{} value: {}'.format(channel, msg) for msg in err] |
||||
|
return [failures, None] if failures else [None, rgb_coll] |
||||
|
|
||||
|
def is_uint8(value): |
||||
|
return _flatmap(is0to255, isInt(value)) |
||||
|
|
||||
|
def validate_rgb(value): |
||||
|
return _flatmap(validate_rgb_vals, three_channels(value)) |
||||
|
|
||||
|
|
||||
|
def three_channels(value): |
||||
|
return ([None, value] |
||||
|
if len(value) == 3 |
||||
|
else [['Colors in an RGB collection should be of the form [R,G,B] or (R,G,B)'], None]) |
||||
|
|
||||
|
def isfoo(value: str): |
||||
|
return ([None, value] |
||||
|
if re.match('^#[\dABCDEF]{6}$', value, flags=2) |
||||
|
else [['Invalid hexadecimal format. Expected: "#FFFFFF"'], None]) |
||||
|
|
||||
|
def is0to255(value: int): |
||||
|
return ([None, value] |
||||
|
if 0 <= value <= 255 |
||||
|
else [['Colors myst be 0-255'], None]) |
||||
|
|
||||
|
def isInt(value): |
||||
|
return ([None, value] |
||||
|
if isinstance(value, int) |
||||
|
else [['Invalid RGB value. Expected type int'], None]) |
||||
|
|
||||
|
|
||||
|
def _or(f, g): |
||||
|
def inner(value): |
||||
|
err1, val1 = f(value) |
||||
|
err2, val2 = g(value) |
||||
|
if val1 and val2: |
||||
|
return [None, val1] |
||||
|
elif err1 and err2: |
||||
|
return [err1 + err2, None] |
||||
|
elif err1: |
||||
|
return [err1, None] |
||||
|
else: |
||||
|
return err2 |
||||
|
return inner |
||||
|
|
||||
|
|
||||
|
def _and(f, g): |
||||
|
def inner(value): |
||||
|
return _flatmap(f, g(value)) |
||||
|
return inner |
||||
|
|
||||
|
def _flatmap(f, v): |
||||
|
err, value = v |
||||
|
if err: |
||||
|
return v |
||||
|
else: |
||||
|
return f(value) |
||||
|
|
||||
|
if __name__ == '__main__': |
||||
|
print(validate_color((1,'ergerg',1234))) |
||||
|
print(validate_color(1234)) |
||||
|
print(validate_color(123.234)) |
||||
|
print(validate_color('123.234')) |
||||
|
print(validate_color('FFFAAA')) |
||||
|
print(validate_color('#FFFAAA')) |
||||
|
print(validate_color([])) |
||||
|
print(validate_color(())) |
||||
|
print(validate_color((1,2))) |
||||
|
print(validate_color((1, 2, 1234))) |
@ -0,0 +1,199 @@ |
|||||
|
import re |
||||
|
from functools import wraps |
||||
|
|
||||
|
from gooey.gui.components.filtering.prefix_filter import OperatorType |
||||
|
|
||||
|
|
||||
|
class SuperBool(object): |
||||
|
""" |
||||
|
A boolean which keeps with it the rationale |
||||
|
for when it is false. |
||||
|
""" |
||||
|
def __init__(self, value, rationale): |
||||
|
self.value = value |
||||
|
self.rationale = rationale |
||||
|
|
||||
|
def __bool__(self): |
||||
|
return self.value |
||||
|
|
||||
|
__nonzero__ = __bool__ |
||||
|
|
||||
|
def __str__(self): |
||||
|
return str(self.value) |
||||
|
|
||||
|
|
||||
|
def lift(f): |
||||
|
""" |
||||
|
Lifts a basic predicate to the SuperBool type |
||||
|
stealing the docstring as the rationale message. |
||||
|
|
||||
|
This is largely just goofing around and experimenting |
||||
|
since it's a private internal API. |
||||
|
""" |
||||
|
@wraps(f) |
||||
|
def inner(value): |
||||
|
result = f(value) |
||||
|
return SuperBool(result, f.__doc__) if not isinstance(result, SuperBool) else result |
||||
|
return inner |
||||
|
|
||||
|
|
||||
|
@lift |
||||
|
def is_tuple_or_list(value): |
||||
|
"""Must be either a list or tuple""" |
||||
|
return isinstance(value, list) or isinstance(value, tuple) |
||||
|
|
||||
|
|
||||
|
@lift |
||||
|
def is_str(value): |
||||
|
"""Must be of type `str`""" |
||||
|
return isinstance(value, str) |
||||
|
|
||||
|
@lift |
||||
|
def is_str_or_coll(value): |
||||
|
""" |
||||
|
Colors must be either a hex string or collection of RGB values. |
||||
|
e.g. |
||||
|
Hex string: #fff0ce |
||||
|
RGB Collection: [0, 255, 128] or (0, 255, 128) |
||||
|
""" |
||||
|
return bool(is_str(value)) or bool(is_tuple_or_list(value)) |
||||
|
|
||||
|
|
||||
|
@lift |
||||
|
def has_valid_channel_values(rgb_coll): |
||||
|
"""Colors in an RGB collection must all be in the range 0-255""" |
||||
|
return all([is_0to255(c) and is_int(c) for c in rgb_coll]) |
||||
|
|
||||
|
|
||||
|
@lift |
||||
|
def is_three_channeled(value): |
||||
|
"""Missing channels! Colors in an RGB collection should be of the form [R,G,B] or (R,G,B)""" |
||||
|
return len(value) == 3 |
||||
|
|
||||
|
@lift |
||||
|
def is_hex_string(value: str): |
||||
|
"""Invalid hexadecimal format. Expected: "#FFFFFF" """ |
||||
|
return isinstance(value, str) and bool(re.match('^#[\dABCDEF]{6}$', value, flags=2)) |
||||
|
|
||||
|
|
||||
|
@lift |
||||
|
def is_bool(value): |
||||
|
"""Must be of type Boolean""" |
||||
|
return isinstance(value, bool) |
||||
|
|
||||
|
@lift |
||||
|
def non_empty_string(value): |
||||
|
"""Must be a non-empty non-blank string""" |
||||
|
return value and bool(value.strip()) |
||||
|
|
||||
|
@lift |
||||
|
def is_tokenization_operator(value): |
||||
|
"""Operator must be a valid OperatorType i.e. one of: (AND, OR)""" |
||||
|
return value in (OperatorType.AND, OperatorType.OR) |
||||
|
|
||||
|
@lift |
||||
|
def is_tokenizer(value): |
||||
|
"""Tokenizers must be valid Regular expressions. see: options.PrefixTokenizers""" |
||||
|
return bool(non_empty_string(value)) |
||||
|
|
||||
|
|
||||
|
@lift |
||||
|
def is_int(value): |
||||
|
"""Invalid type. Expected `int`""" |
||||
|
return isinstance(value, int) |
||||
|
|
||||
|
@lift |
||||
|
def is_0to255(value): |
||||
|
"""RGB values must be in the range 0 - 255 (inclusive)""" |
||||
|
return 0 <= value <= 255 |
||||
|
|
||||
|
|
||||
|
def is_0to20(value): |
||||
|
"""Precision values must be in the range 0 - 20 (inclusive)""" |
||||
|
return 0 <= value <= 20 |
||||
|
|
||||
|
@lift |
||||
|
def is_valid_color(value): |
||||
|
"""Must be either a valid hex string or RGB list""" |
||||
|
if is_str(value): |
||||
|
return is_hex_string(value) |
||||
|
elif is_tuple_or_list(value): |
||||
|
return (is_tuple_or_list(value) |
||||
|
and is_three_channeled(value) |
||||
|
and has_valid_channel_values(value)) |
||||
|
else: |
||||
|
return is_str_or_coll(value) |
||||
|
|
||||
|
|
||||
|
validators = { |
||||
|
'label_color': is_valid_color, |
||||
|
'label_bg_color': is_valid_color, |
||||
|
'help_color': is_valid_color, |
||||
|
'help_bg_color': is_valid_color, |
||||
|
'error_color': is_valid_color, |
||||
|
'error_bg_color': is_valid_color, |
||||
|
'show_label': is_bool, |
||||
|
'show_help': is_bool, |
||||
|
'visible': is_bool, |
||||
|
'full_width': is_bool, |
||||
|
'height': is_int, |
||||
|
'readonly': is_bool, |
||||
|
'initial_selection': is_int, |
||||
|
'title': non_empty_string, |
||||
|
'checkbox_label': non_empty_string, |
||||
|
'placeholder': non_empty_string, |
||||
|
'empty_message': non_empty_string, |
||||
|
'max_size': is_int, |
||||
|
'choice_tokenizer': is_tokenizer, |
||||
|
'input_tokenizer': is_tokenizer, |
||||
|
'ignore_case': is_bool, |
||||
|
'operator': is_tokenization_operator, |
||||
|
'index_suffix': is_bool, |
||||
|
'wildcard': non_empty_string, |
||||
|
'default_dir': non_empty_string, |
||||
|
'default_file': non_empty_string, |
||||
|
'default_path': non_empty_string, |
||||
|
'message': non_empty_string, |
||||
|
'precision': is_0to20 |
||||
|
} |
||||
|
|
||||
|
|
||||
|
|
||||
|
def collect_errors(predicates, m): |
||||
|
return { |
||||
|
k:predicates[k](v).rationale |
||||
|
for k,v in m.items() |
||||
|
if k in predicates and not predicates[k](v)} |
||||
|
|
||||
|
|
||||
|
def validate(pred, value): |
||||
|
result = pred(value) |
||||
|
if not result: |
||||
|
raise ValueError(result.rationale) |
||||
|
|
||||
|
|
||||
|
|
||||
|
if __name__ == '__main__': |
||||
|
# print(validateColor((1, 'ergerg', 1234))) |
||||
|
# print(validateColor(1234)) |
||||
|
# print(validateColor(123.234)) |
||||
|
# print(validateColor('123.234')) |
||||
|
# print(validateColor('FFFAAA')) |
||||
|
# print(validateColor('#FFFAAA')) |
||||
|
# print(validateColor([])) |
||||
|
# print(validateColor(())) |
||||
|
# print(validateColor((1, 2))) |
||||
|
# print(validateColor((1, 2, 1234))) |
||||
|
# print(is_lifted(lift(is_int))) |
||||
|
# print(is_lifted(is_int)) |
||||
|
# print(OR(is_poop, is_int)('poop')) |
||||
|
# print(AND(is_poop, is_lower, is_lower)('pooP')) |
||||
|
# print(OR(is_poop, is_int)) |
||||
|
# print(is_lifted(OR(is_poop, is_int))) |
||||
|
# print(validate(is_valid_color, [255, 255, 256])) |
||||
|
# print(is_valid_color('#fff000')) |
||||
|
# print(is_valid_color([255, 244, 256])) |
||||
|
print(non_empty_string('asdf') and non_empty_string('asdf')) |
||||
|
# validate(is_valid_color, 1234) |
||||
|
|
||||
|
|
@ -0,0 +1,50 @@ |
|||||
|
import wx |
||||
|
|
||||
|
from gooey.gui import formatters |
||||
|
from gooey.gui.components.widgets.bases import TextContainer |
||||
|
|
||||
|
|
||||
|
class IntegerField(TextContainer): |
||||
|
""" |
||||
|
An integer input field |
||||
|
""" |
||||
|
widget_class = wx.SpinCtrl |
||||
|
def getWidget(self, *args, **options): |
||||
|
widget = self.widget_class(self, |
||||
|
value='', |
||||
|
min=self._options.get('min', 0), |
||||
|
max=self._options.get('max', 100)) |
||||
|
return widget |
||||
|
|
||||
|
def getWidgetValue(self): |
||||
|
return self.widget.GetValue() |
||||
|
|
||||
|
def setValue(self, value): |
||||
|
self.widget.SetValue(int(value)) |
||||
|
|
||||
|
def formatOutput(self, metatdata, value): |
||||
|
# casting to string so that the generic formatter |
||||
|
# doesn't treat 0 as false/None |
||||
|
return formatters.general(metatdata, str(value)) |
||||
|
|
||||
|
|
||||
|
class DecimalField(IntegerField): |
||||
|
""" |
||||
|
A decimal input field |
||||
|
""" |
||||
|
widget_class = wx.SpinCtrlDouble |
||||
|
|
||||
|
def getWidget(self, *args, **options): |
||||
|
widget = self.widget_class(self, |
||||
|
value='', |
||||
|
min=self._options.get('min', 0), |
||||
|
max=self._options.get('max', 100), |
||||
|
inc=self._options.get('increment', 0.01)) |
||||
|
widget.SetDigits(self._options.get('precision', widget.GetDigits())) |
||||
|
return widget |
||||
|
|
||||
|
|
||||
|
def setValue(self, value): |
||||
|
self.widget.SetValue(value) |
||||
|
|
||||
|
|
@ -1,74 +0,0 @@ |
|||||
import pygtrie as trie |
|
||||
from fuzzywuzzy import process |
|
||||
|
|
||||
class BasicDisplayOptions(object): |
|
||||
pass |
|
||||
|
|
||||
countries = ["Abkhazia -> Abkhazia", "Afghanistan", |
|
||||
"Albania", "Algeria", "Andorra", "Angola", "Antigua and Barbuda", "Argentina", |
|
||||
"Armenia", "Artsakh -> Artsakh", "Australia", "Austria", "Azerbaijan", "Bahamas, The", |
|
||||
"Bahrain", "Bangladesh", "Barbados", "Belarus", "Belgium", "Belize", "Benin", "Bhutan", |
|
||||
"Bolivia", "Bosnia and Herzegovina", "Botswana", "Brazil", "Brunei", "Bulgaria", |
|
||||
"Burkina Faso[j]", "Burma -> Myanmar", "Burundi", "Cambodia", "Cameroon", "Canada[k]", |
|
||||
"Cape Verde", "Central African Republic", "Chad", "Chile", "China", |
|
||||
"China, Republic of -> Taiwan", "Colombia", "Comoros", |
|
||||
"Congo, Democratic Republic of the[p]", "Congo, Republic of the", |
|
||||
"Cook Islands -> Cook Islands", "Costa Rica", "Croatia", "Cuba", "Cyprus", |
|
||||
"Czech Republic[r]", "Democratic People's Republic of Korea -> Korea, North", |
|
||||
"Democratic Republic of the Congo -> Congo, Democratic Republic of the", "Denmark", |
|
||||
"Djibouti", "Dominica", "Dominican Republic", "East Timor", "Ecuador", "Egypt", |
|
||||
"El Salvador", "Equatorial Guinea", "Eritrea", "Estonia", "Eswatini -> Swaziland", |
|
||||
"Ethiopia", "Fiji", "Finland", "France", "Gabon", "Gambia, The", "Georgia", "Germany", |
|
||||
"Ghana", "Greece", "Grenada", "Guatemala", "Guinea", "Guinea", "Guyana", "Haiti", |
|
||||
"Holy See -> Vatican City", "Honduras", "Hungary", "Iceland[v]", "India", "Indonesia", |
|
||||
"Iran", "Iraq", "Ireland", "Israel", "Italy", "Ivory Coast", "Jamaica", "Japan", |
|
||||
"Jordan", "Kazakhstan", "Kenya", "Kiribati", "Korea, North", "Korea, South", |
|
||||
"Kosovo -> Kosovo", "Kuwait", "Kyrgyzstan", "Laos", "Latvia", "Lebanon", "Lesotho", |
|
||||
"Liberia", "Libya", "Liechtenstein", "Lithuania", "Luxembourg", "Madagascar", "Malawi", |
|
||||
"Malaysia", "Maldives", "Mali", "Malta", "Marshall Islands", "Mauritania", "Mauritius", |
|
||||
"Mexico", "Micronesia", "Moldova", "Monaco", "Mongolia", "Montenegro", "Morocco", |
|
||||
"Mozambique", "Myanmar", "Nagorno", "Namibia", "Nauru", "Nepal", "Netherlands", |
|
||||
"New Zealand", "Nicaragua", "Niger", "Nigeria", "Niue -> Niue", |
|
||||
"Northern Cyprus -> Northern Cyprus", "North Korea -> Korea, North", "North Macedonia", |
|
||||
"Norway", "Oman", "Pakistan", "Palau", "Palestine", "Panama", "Papua New Guinea", |
|
||||
"Paraguay", "Peru", "Philippines", "Poland", "Portugal", |
|
||||
"Pridnestrovie -> Transnistria", "Qatar", "Republic of Korea -> Korea, South", |
|
||||
"Republic of the Congo -> Congo, Republic of the", "Romania", "Russia", "Rwanda", |
|
||||
"Sahrawi Arab Democratic Republic -> Sahrawi Arab Democratic Republic", |
|
||||
"Saint Kitts and Nevis", "Saint Lucia", "Saint Vincent and the Grenadines", "Samoa", |
|
||||
"San Marino", "São Tomé and Príncipe", "Saudi Arabia", "Senegal", "Serbia", |
|
||||
"Seychelles", "Sierra Leone", "Singapore", "Slovakia", "Slovenia", "Solomon Islands", |
|
||||
"Somalia", "Somaliland -> Somaliland", "South Africa", "South Korea -> Korea, South", |
|
||||
"South Ossetia -> South Ossetia", "South Sudan", "Spain", "Sri Lanka", "Sudan", |
|
||||
"Sudan, South -> South Sudan", "Suriname", "Swaziland", "Sweden", "Switzerland", |
|
||||
"Syria", "Taiwan (Republic of China) -> Taiwan", "Tajikistan", "Tanzania", "Thailand", |
|
||||
"The Bahamas -> Bahamas, The", "The Gambia -> Gambia, The", "Timor", "Togo", "Tonga", |
|
||||
"Transnistria -> Transnistria", "Trinidad and Tobago", "Tunisia", "Turkey", |
|
||||
"Turkmenistan", "Tuvalu", "Uganda", "Ukraine", "United Arab Emirates", |
|
||||
"United Kingdom", "United States", "Uruguay", "Uzbekistan", "Vanuatu", "Vatican City", |
|
||||
"Venezuela", "Vietnam", "Yemen", "Zambia", "Zimbabwe", |
|
||||
"↑ UN member states and observer states ↑", "", "↓ Other states ↓", "Abkhazia", |
|
||||
"Artsakh", "Cook Islands", "Kosovo", "Niue", "Northern Cyprus", |
|
||||
"Sahrawi Arab Democratic Republic", "Somaliland", "South Ossetia", "Taiwan", |
|
||||
"Transnistria", "↑ Other states ↑"] |
|
||||
|
|
||||
|
|
||||
us_states = ["Alabama", "Alaska", "Arizona", "Arkansas", "California", "Colorado", |
|
||||
"Connecticut", "Delaware", "Florida", "Georgia", "Hawaii", "Idaho", "Illinois", |
|
||||
"Indiana", "Iowa", "Kansas", "Kentucky[E]", "Louisiana", "Maine", "Maryland", |
|
||||
"Massachusetts", "Michigan", "Minnesota", "Mississippi", "Missouri", "Montana", |
|
||||
"Nebraska", "Nevada", "New Hampshire", "New Jersey", "New Mexico", "New York", |
|
||||
"North Carolina", "North Dakota", "Ohio", "Oklahoma", "Oregon", "Pennsylvania", |
|
||||
"Rhode Island", "South Carolina", "South Dakota", "Tennessee", "Texas", "Utah", |
|
||||
"Vermont", "Virginia", "Washington", "West Virginia", "Wisconsin", "Wyoming"] |
|
||||
|
|
||||
x = trie.Trie() |
|
||||
output = {} |
|
||||
for country in countries: |
|
||||
for i in country.split(): |
|
||||
if not x.has_key(i): |
|
||||
x[i] = [] |
|
||||
x[i].append(country) |
|
||||
|
|
||||
|
|
||||
a = 10 |
|
@ -0,0 +1,28 @@ |
|||||
|
import wx |
||||
|
|
||||
|
from gooey.gui import formatters |
||||
|
from gooey.gui.components.widgets.bases import TextContainer |
||||
|
|
||||
|
|
||||
|
class Slider(TextContainer): |
||||
|
""" |
||||
|
An integer input field |
||||
|
""" |
||||
|
widget_class = wx.Slider |
||||
|
def getWidget(self, *args, **options): |
||||
|
widget = self.widget_class(self, |
||||
|
minValue=self._options.get('min', 0), |
||||
|
maxValue=self._options.get('max', 100), |
||||
|
style=wx.SL_MIN_MAX_LABELS | wx.SL_VALUE_LABEL) |
||||
|
return widget |
||||
|
|
||||
|
def getWidgetValue(self): |
||||
|
return self.widget.GetValue() |
||||
|
|
||||
|
def setValue(self, value): |
||||
|
self.widget.SetValue(value) |
||||
|
|
||||
|
def formatOutput(self, metatdata, value): |
||||
|
return formatters.general(metatdata, value) |
||||
|
|
||||
|
|
@ -0,0 +1,89 @@ |
|||||
|
import unittest |
||||
|
from random import randint |
||||
|
from unittest.mock import patch |
||||
|
|
||||
|
from tests.harness import instrumentGooey |
||||
|
from gooey import GooeyParser |
||||
|
from gooey.tests import * |
||||
|
|
||||
|
class TestNumbericInputs(unittest.TestCase): |
||||
|
|
||||
|
def makeParser(self, **kwargs): |
||||
|
parser = GooeyParser(description='description') |
||||
|
parser.add_argument('--input', **kwargs) |
||||
|
return parser |
||||
|
|
||||
|
|
||||
|
def testDefault(self): |
||||
|
cases = [ |
||||
|
[{'widget': 'IntegerField'}, 0], |
||||
|
[{'default': 0, 'widget': 'IntegerField'}, 0], |
||||
|
[{'default': 10, 'widget': 'IntegerField'}, 10], |
||||
|
[{'default': 76, 'widget': 'IntegerField'}, 76], |
||||
|
# note that WX caps the value |
||||
|
# unless explicitly widened via gooey_options |
||||
|
[{'default': 81234, 'widget': 'IntegerField'}, 100], |
||||
|
# here we set the max to something higher than |
||||
|
# the default and all works as expected. |
||||
|
# this is a TODO for validation |
||||
|
[{'default': 81234, 'widget': 'IntegerField', 'gooey_options': {'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], |
||||
|
] |
||||
|
for inputs, expected in cases: |
||||
|
with self.subTest(inputs): |
||||
|
parser = self.makeParser(**inputs) |
||||
|
with instrumentGooey(parser) as (app, gooeyApp): |
||||
|
input = gooeyApp.configs[0].reifiedWidgets[0] |
||||
|
self.assertEqual(input.getValue()['rawValue'], expected) |
||||
|
|
||||
|
def testGooeyOptions(self): |
||||
|
cases = [ |
||||
|
{'widget': 'DecimalField', 'gooey_options': {'min': -100, 'max': 1234, 'increment': 1.240}}, |
||||
|
{'widget': 'DecimalField', 'gooey_options': {'min': 1234, 'max': 3456, 'increment': 2.2}}, |
||||
|
{'widget': 'IntegerField', 'gooey_options': {'min': -100, 'max': 1234}}, |
||||
|
{'widget': 'IntegerField', 'gooey_options': {'min': 1234, 'max': 3456}} |
||||
|
]; |
||||
|
using = { |
||||
|
'min': lambda widget: widget.GetMin(), |
||||
|
'max': lambda widget: widget.GetMax(), |
||||
|
'increment': lambda widget: widget.GetIncrement(), |
||||
|
|
||||
|
} |
||||
|
for case in cases: |
||||
|
with self.subTest(case): |
||||
|
parser = self.makeParser(**case) |
||||
|
with instrumentGooey(parser) as (app, gooeyApp): |
||||
|
wxWidget = gooeyApp.configs[0].reifiedWidgets[0].widget |
||||
|
for option, value in case['gooey_options'].items(): |
||||
|
self.assertEqual(using[option](wxWidget), value) |
||||
|
|
||||
|
|
||||
|
def testZerosAreReturned(self): |
||||
|
""" |
||||
|
Originally the formatter was dropping '0' due to |
||||
|
it being interpreted as falsey |
||||
|
""" |
||||
|
parser = self.makeParser(widget='IntegerField') |
||||
|
with instrumentGooey(parser) as (app, gooeyApp): |
||||
|
field = gooeyApp.configs[0].reifiedWidgets[0] |
||||
|
result = field.getValue() |
||||
|
self.assertEqual(result['rawValue'], 0) |
||||
|
self.assertIsNotNone(result['cmd']) |
||||
|
|
||||
|
def testNoLossOfPrecision(self): |
||||
|
parser = self.makeParser(widget='DecimalField', default=12.23534, gooey_options={'precision': 20}) |
||||
|
with instrumentGooey(parser) as (app, gooeyApp): |
||||
|
field = gooeyApp.configs[0].reifiedWidgets[0] |
||||
|
result = field.getValue() |
||||
|
self.assertEqual(result['rawValue'], 12.23534) |
||||
|
self.assertIsNotNone(result['cmd']) |
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
|
if __name__ == '__main__': |
||||
|
unittest.main() |
@ -0,0 +1,54 @@ |
|||||
|
import unittest |
||||
|
|
||||
|
from gooey.gui.components.options import options |
||||
|
|
||||
|
class TestPrefixFilter(unittest.TestCase): |
||||
|
|
||||
|
def test_doc_schenanigans(self): |
||||
|
"""Sanity check that my docstring wrappers all behave as expected""" |
||||
|
@options._include_layout_docs |
||||
|
def no_self_docstring(): |
||||
|
pass |
||||
|
|
||||
|
@options._include_layout_docs |
||||
|
def yes_self_docstring(): |
||||
|
"""sup""" |
||||
|
pass |
||||
|
|
||||
|
# gets attached to functions even if they don't have a docstring |
||||
|
self.assertIn(options.LayoutOptions.__doc__, no_self_docstring.__doc__) |
||||
|
# gets attached to the *end* of existing doc strings |
||||
|
self.assertTrue(yes_self_docstring.__doc__.startswith('sup')) |
||||
|
self.assertIn(options.LayoutOptions.__doc__, yes_self_docstring.__doc__) |
||||
|
|
||||
|
|
||||
|
def test_clean_method(self): |
||||
|
""" |
||||
|
_clean should drop any keys with None values |
||||
|
and flatten the layout_option kwargs to the root level |
||||
|
""" |
||||
|
result = options._clean({'a': None, 'b': 123, 'c': 0}) |
||||
|
self.assertEqual(result, {'b': 123, 'c': 0}) |
||||
|
|
||||
|
result = options._clean({'root_level': 123, 'layout_options': { |
||||
|
'nested': 'hello', |
||||
|
'another': 1234 |
||||
|
}}) |
||||
|
self.assertEqual(result, {'root_level': 123, 'nested': 'hello', 'another': 1234}) |
||||
|
|
||||
|
def test_only_provided_arguments_included(self): |
||||
|
""" |
||||
|
More sanity checking that the internal use of locals() |
||||
|
does the Right Thing |
||||
|
""" |
||||
|
option = options.LayoutOptions(label_color='#ffffff') |
||||
|
self.assertIn('label_color', option) |
||||
|
|
||||
|
option = options.LayoutOptions() |
||||
|
self.assertNotIn('label_color', option) |
||||
|
|
||||
|
option = options.TextField(label_color='#ffffff') |
||||
|
self.assertIn('label_color', option) |
||||
|
|
||||
|
option = options.TextField() |
||||
|
self.assertNotIn('label_color', option) |
@ -0,0 +1,40 @@ |
|||||
|
import unittest |
||||
|
from unittest.mock import patch |
||||
|
|
||||
|
from tests.harness import instrumentGooey |
||||
|
from gooey import GooeyParser |
||||
|
from gooey.tests import * |
||||
|
|
||||
|
class TestGooeySlider(unittest.TestCase): |
||||
|
|
||||
|
def makeParser(self, **kwargs): |
||||
|
parser = GooeyParser(description='description') |
||||
|
parser.add_argument('--slider', widget="Slider", **kwargs) |
||||
|
return parser |
||||
|
|
||||
|
|
||||
|
def testSliderDefault(self): |
||||
|
cases = [ |
||||
|
[{}, 0], |
||||
|
[{'default': 0}, 0], |
||||
|
[{'default': 10}, 10], |
||||
|
[{'default': 76}, 76], |
||||
|
# note that WX caps the value |
||||
|
# unless explicitly widened via gooey_options |
||||
|
[{'default': 81234}, 100], |
||||
|
# here we set the max to something higher than |
||||
|
# the default and all works as expected. |
||||
|
# this is a TODO for validation |
||||
|
[{'default': 81234, 'gooey_options': {'max': 99999}}, 81234], |
||||
|
] |
||||
|
for inputs, expected in cases: |
||||
|
with self.subTest(inputs): |
||||
|
parser = self.makeParser(**inputs) |
||||
|
with instrumentGooey(parser) as (app, gooeyApp): |
||||
|
slider = gooeyApp.configs[0].reifiedWidgets[0] |
||||
|
self.assertEqual(slider.getValue()['rawValue'], expected) |
||||
|
|
||||
|
|
||||
|
|
||||
|
if __name__ == '__main__': |
||||
|
unittest.main() |
Write
Preview
Loading…
Cancel
Save