Gooey
Gooey copied to clipboard
wxAssertionError with dynamic values on 1.2.1-release with Python3.9.14/MacOS12.0.1
- [x] OS: MacOS 12.0.1
- [x] Python Version: Python 3.9.14
- [x] Gooey Version:
1.2.1-release
(git branch) /1.2.0-ALPHA
(gooey.__version__
) - [x] Thorough description of problem :
Steps to reproduce:
- run
python minimum_working_example.py
on MacOS - enter a value for the parameter "foo" in the UI
- click "start"
- observe exception:
Traceback (most recent call last):
...
...lib/python3.9/site-packages/gooey/gui/util/time.py", line 38, in start
self.wxTimer.Start()
wx._core.wxAssertionError: C++ assertion ""m_milli > 0"" failed at /Users/robind/projects/bb2/dist-osx-py39/build/ext/wxWidgets/src/osx/core/timer.cpp(69) in Start(): invalid value for timer timeout
- [x] Expected Behavior: No exception is raised on MacOS; monkeypatches are not required
- [x] Actual Behavior: An exception is raised on MacOS; monkeypatches are required
- [x] A minimal code example:
minimum_working_example.py
:
from typing import Mapping, Any, Optional
from gooey import Gooey, GooeyParser, Events, types
def monkeypatch_frame():
# https://github.com/chriskiehl/Gooey/issues/826#issuecomment-1240180894
from rewx import widgets
from rewx.widgets import set_basic_props
from rewx.dispatch import update
import wx
import sys
@update.register(wx.Frame)
def frame(element, instance: wx.Frame):
props = element['props']
set_basic_props(instance, props)
if 'title' in props:
instance.SetTitle(props['title'])
if 'show' in props:
instance.Show(props['show'])
if 'icon_uri' in props:
icon = wx.Icon(props['icon_uri'])
instance.SetIcon(icon)
if sys.platform != 'win32':
# OSX needs to have its taskbar icon explicitly set
# bizarrely, wx requires the TaskBarIcon to be attached to the Frame
# as instance data (self.). Otherwise, it will not render correctly.
try:
frame.taskbarIcon = wx.adv.TaskBarIcon(iconType=wx.adv.TBI_DOCK)
except wx._core.wxAssertionError:
pass
frame.taskbarIcon.SetIcon(icon)
else:
instance.SetIcon(wx.Icon(os.path.join(dirname, 'icon.png')))
if 'on_close' in props:
instance.Bind(wx.EVT_CLOSE, props['on_close'])
return instance
widgets.frame = frame
def monkeypatch_communicate():
# https://github.com/chriskiehl/Gooey/issues/813#issuecomment-1113911923
import sys
if sys.platform != 'win32':
import subprocess
from subprocess import CalledProcessError
from json import JSONDecodeError
from gooey.gui import seeder
from gooey.python_bindings.types import Try, Success, Failure
from gooey.python_bindings.coms import deserialize_inbound
def communicate(cmd, encoding) -> Try:
"""
Invoke the processes specified by `cmd`.
Assumes that the process speaks JSON over stdout. Non-json response
are treated as an error.
Implementation Note: I don't know why, but `Popen` is like ~5-6x faster
than `check_output`. in practice, it means waiting for ~1/10th
of a second rather than ~7/10ths of a second. A
difference which is pretty weighty when there's a
user waiting on the other end.
"""
cmd = cmd.replace("'", "").split()
try:
proc = subprocess.Popen(
cmd,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
out, err = proc.communicate()
if out and proc.poll() == 0:
return Success(deserialize_inbound(out, encoding))
else:
return Failure(CalledProcessError(proc.returncode, cmd, output=out, stderr=err))
except JSONDecodeError as e:
return Failure(e)
seeder.communicate = communicate
monkeypatch_frame()
monkeypatch_communicate()
def dump_state(name, args, state):
import pickle
with open(f"{name}.pkl", "wb") as f:
pickle.dump({"args": args, "state": state}, f)
def on_success(args: Mapping[str, Any], state: types.PublicGooeyState) -> Optional[types.PublicGooeyState]:
"""
You can do anything you want in the handler including
returning an updated UI state for your next run!
"""
dump_state("success", args, state)
"""
{'args': Namespace(foo='asdf'),
'state': {'active_form': [{'enabled': True,
'error': '',
'id': 'foo',
'placeholder': '',
'type': 'TextField',
'value': 'asdf',
'visible': True}]}}
"""
state["active_form"][0]["value"] = "updated in on_success"
return state
def on_error(args: Mapping[str, Any], state: types.PublicGooeyState) -> Optional[types.PublicGooeyState]:
"""
You can do anything you want in the handler including
returning an updated UI state for your next run!
"""
dump_state("failure", args, state)
state["active_form"][0]["value"] = "updated in on_failure"
return state
@Gooey(
use_events=[
Events.ON_SUCCESS,
Events.ON_ERROR,
Events.VALIDATE_FORM,
],
)
def main():
parser = GooeyParser(
on_success=on_success,
on_error=on_error,
)
parser.add_argument("foo")
parser.parse_args()
# comment out for on_success; otherwise on_failure is called
#raise Exception("this is an exception")
if __name__ == "__main__":
main()
To clarify:
-
on_success()
is called if no Exception is raised after the call toparser.parse_args()
, otherwiseon_failure()
is called - no additional Exceptions are raised on Windows
- the following additional Exception is raised on MacOS when a parameter value is supplied and the start button is pressed, regardless of success or failure:
wx._core.wxAssertionError: C++ assertion ""m_milli > 0"" failed at /Users/robind/projects/bb2/dist-osx-py39/build/ext/wxWidgets/src/osx/core/timer.cpp(69) in Start(): invalid value for timer timeout
- the state is successfully modified in both
on_success
andon_failure
- if
Events.VALIDATE_FORM
is not included in theuse_events
parameter toGooey()
and a value for the parameter is not supplied in the UI, an error is printed in the UI on both platforms:
usage: minimum_working_example.py [-h] [--ignore-gooey] foo
ui_min.py: error: the following arguments are required: foo
Command '['/.../bin/python', '-u', 'minimum_working_example.py', '--gooey-state', 'eyJhY3RpdmVfZm9ybSI6IFt7ImlkIjogImZvbyIsICJ0eXBlIjogIlRleHRGaWVsZCIsICJ2YWx1ZSI6ICIiLCAicGxhY2Vob2xkZXIiOiAiIiwgImVycm9yIjogIiIsICJlbmFibGVkIjogdHJ1ZSwgInZpc2libGUiOiB0cnVlfV19', '--gooey-run-is-failure']' returned non-zero exit status 2.b''b'usage: minimum_working_example.py [-h] [--gooey-state GOOEY_STATE] [--gooey-run-is-success]\n [--gooey-run-is-failure]\n foo\minimum_working_example.py: error: the following arguments are required: foo\n'
+=======================+
|Gooey Unexpected Error!|
+=======================+
Gooey encountered an unexpected error while trying to communicate
with your program to process one of the ('VALIDATE_FORM', 'ON_SUCCESS', 'ON_ERROR') events.
These features are new and experimental! You may have encountered a bug!
You can open a ticket with a small reproducible example here
https://github.com/chriskiehl/Gooey/issues
Apologies for the verbosity, but the intended behavior and required configuration is not clear from the documentation. Perhaps this could be improved as well.
And of course ideally the monkeypatches from https://github.com/chriskiehl/Gooey/issues/826#issuecomment-1240180894 and https://github.com/chriskiehl/Gooey/issues/813#issuecomment-1113911923 would not be required