Skip to content

Commit a74a4a6

Browse files
committed
better error handling
1 parent d6e773b commit a74a4a6

File tree

1 file changed

+34
-16
lines changed

1 file changed

+34
-16
lines changed

mathics/builtin/manipulate.py

Lines changed: 34 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
try:
2121
from ipywidgets import (IntSlider, FloatSlider, ToggleButtons, Box, DOMWidget)
22+
from IPython.core.formatters import IPythonDisplayFormatter
2223
_ipywidgets = True
2324
except ImportError:
2425
# fallback to non-Manipulate-enabled build if we don't have ipywidgets installed.
@@ -57,6 +58,12 @@ def __init__(self, var):
5758
self.var = var
5859

5960

61+
class JupyterWidgetError(Exception):
62+
def __init__(self, err):
63+
super(JupyterWidgetError, self).__init__()
64+
self.err = err
65+
66+
6067
class ManipulateParameter(Builtin): # parses one Manipulate[] parameter spec, e.g. {x, 1, 2}, see _WidgetInstantiator
6168
context = 'System`Private`'
6269

@@ -88,6 +95,13 @@ def _manipulate_label(x): # gets the label that is displayed for a symbol or na
8895
return str(x)
8996

9097

98+
def _create_widget(widget, **kwargs):
99+
try:
100+
return widget(**kwargs)
101+
except Exception as e:
102+
raise JupyterWidgetError(str(e))
103+
104+
91105
class _WidgetInstantiator():
92106
# we do not want to have widget instances (like FloatSlider) get into the evaluation pipeline (e.g. via Expression
93107
# or Atom), since there might be all kinds of problems with serialization of these widget classes. therefore, the
@@ -132,7 +146,7 @@ def _add_continuous_widget(self, symbol, label, default, minimum, maximum, evalu
132146
raise IllegalWidgetArguments(symbol)
133147
else:
134148
defval = min(max(default.to_python(), minimum_value), maximum_value)
135-
widget = FloatSlider(value=defval, min=minimum_value, max=maximum_value)
149+
widget = _create_widget(FloatSlider, value=defval, min=minimum_value, max=maximum_value)
136150
self._add_widget(widget, symbol.get_name(), lambda x: from_python(x), label)
137151

138152
def _add_discrete_widget(self, symbol, label, default, minimum, maximum, step, evaluation):
@@ -144,10 +158,10 @@ def _add_discrete_widget(self, symbol, label, default, minimum, maximum, step, e
144158
else:
145159
default_value = min(max(default.to_python(), minimum_value), maximum_value)
146160
if all(isinstance(x, Integer) for x in [minimum, maximum, default, step]):
147-
widget = IntSlider(value=default_value, min=minimum_value, max=maximum_value,
161+
widget = _create_widget(IntSlider, value=default_value, min=minimum_value, max=maximum_value,
148162
step=step_value)
149163
else:
150-
widget = FloatSlider(value=default_value, min=minimum_value, max=maximum_value,
164+
widget = _create_widget(FloatSlider, value=default_value, min=minimum_value, max=maximum_value,
151165
step=step_value)
152166
self._add_widget(widget, symbol.get_name(), lambda x: from_python(x), label)
153167

@@ -162,7 +176,7 @@ def _add_options_widget(self, symbol, options, default, label, evaluation):
162176
if option.same(default):
163177
default_index = i
164178

165-
widget = ToggleButtons(options=formatted_options, value=default_index)
179+
widget = _create_widget(ToggleButtons, options=formatted_options, value=default_index)
166180
self._add_widget(widget, symbol.get_name(), lambda j: options.leaves[j], label)
167181

168182
def _add_widget(self, widget, name, parse, label):
@@ -226,8 +240,11 @@ class Manipulate(Builtin):
226240
messages = {
227241
'jupyter': 'Manipulate[] only works inside a Jupyter notebook.',
228242
'noipywidget': 'Manipulate[] needs the ipywidgets module to work.',
229-
'generr': 'Widget creation failed: ``',
230-
'widgetargs': 'Illegal variable range or step parameters for ``.'
243+
'imathics': 'Your IMathics kernel does not seem to support all necessary operations. ' +
244+
'Please check that you have the latest version installed.',
245+
'widgetmake': 'Jupyter widget construction failed with "``".',
246+
'widgetargs': 'Illegal variable range or step parameters for ``.',
247+
'widgetdisp': 'Jupyter failed to display the widget.',
231248
}
232249

233250
def apply(self, expr, args, evaluation):
@@ -245,14 +262,18 @@ def apply(self, expr, args, evaluation):
245262
if not instantiator.add(arg, evaluation): # not a valid argument pattern?
246263
return
247264
except IllegalWidgetArguments as e:
248-
evaluation.message('Manipulate', 'widgetargs', strip_context(str(e.var)))
249-
except Exception as e:
250-
evaluation.message('Manipulate', 'generr', str(e)) # usually some Jupyter error
265+
evaluation.error('Manipulate', 'widgetargs', strip_context(str(e.var)))
266+
except JupyterWidgetError as e:
267+
evaluation.error('Manipulate', 'widgetmake', e.err)
251268
return Symbol('$Aborted')
252269

253270
clear_output_callback = evaluation.clear_output_callback
254271
display_data_callback = evaluation.display_data_callback # for pushing updates
255272

273+
if clear_output_callback is None or display_data_callback is None:
274+
evaluation.error('Manipulate', 'imathics')
275+
return Symbol('$Aborted')
276+
256277
def callback(**kwargs):
257278
clear_output_callback(wait=True)
258279

@@ -270,12 +291,9 @@ def callback(**kwargs):
270291
widgets = instantiator.get_widgets()
271292
if len(widgets) > 0:
272293
box = _interactive(instantiator.build_callback(callback), widgets) # create the widget
273-
274-
# the following code is a boiled down version from IPython.core.formatters.IPythonDisplayFormatter.
275-
# note that '_ipython_display_' is a magic constant defined in print_method of IPythonDisplayFormatter.
276-
277-
method = getattr(box, '_ipython_display_')
278-
if method is not None:
279-
method() # make the widget appear on the Jupyter notebook
294+
formatter = IPythonDisplayFormatter()
295+
if not formatter(box): # make the widget appear on the Jupyter notebook
296+
evaluation.error('Manipulate', 'widgetdisp')
297+
return Symbol('$Aborted')
280298

281299
return Symbol('Null') # the interactive output is pushed via kernel.display_data_callback (see above)

0 commit comments

Comments
 (0)