##// END OF EJS Templates
Merge pull request #7554 from jdfreder/interact-fix...
Min RK -
r20261:6bf6e5ac merge
parent child Browse files
Show More
@@ -1,344 +1,349 b''
1 """Interact with functions using widgets."""
1 """Interact with functions using widgets."""
2
2
3 # Copyright (c) IPython Development Team.
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
4 # Distributed under the terms of the Modified BSD License.
5
5
6 from __future__ import print_function
6 from __future__ import print_function
7
7
8 try: # Python >= 3.3
8 try: # Python >= 3.3
9 from inspect import signature, Parameter
9 from inspect import signature, Parameter
10 except ImportError:
10 except ImportError:
11 from IPython.utils.signatures import signature, Parameter
11 from IPython.utils.signatures import signature, Parameter
12 from inspect import getcallargs
12 from inspect import getcallargs
13
13
14 from IPython.core.getipython import get_ipython
14 from IPython.core.getipython import get_ipython
15 from IPython.html.widgets import (Widget, Text,
15 from IPython.html.widgets import (Widget, Text,
16 FloatSlider, IntSlider, Checkbox, Dropdown,
16 FloatSlider, IntSlider, Checkbox, Dropdown,
17 Box, Button, DOMWidget)
17 Box, Button, DOMWidget, Output)
18 from IPython.display import display, clear_output
18 from IPython.display import display, clear_output
19 from IPython.utils.py3compat import string_types, unicode_type
19 from IPython.utils.py3compat import string_types, unicode_type
20 from IPython.utils.traitlets import HasTraits, Any, Unicode
20 from IPython.utils.traitlets import HasTraits, Any, Unicode
21
21
22 empty = Parameter.empty
22 empty = Parameter.empty
23
23
24
24
25 def _matches(o, pattern):
25 def _matches(o, pattern):
26 """Match a pattern of types in a sequence."""
26 """Match a pattern of types in a sequence."""
27 if not len(o) == len(pattern):
27 if not len(o) == len(pattern):
28 return False
28 return False
29 comps = zip(o,pattern)
29 comps = zip(o,pattern)
30 return all(isinstance(obj,kind) for obj,kind in comps)
30 return all(isinstance(obj,kind) for obj,kind in comps)
31
31
32
32
33 def _get_min_max_value(min, max, value=None, step=None):
33 def _get_min_max_value(min, max, value=None, step=None):
34 """Return min, max, value given input values with possible None."""
34 """Return min, max, value given input values with possible None."""
35 if value is None:
35 if value is None:
36 if not max > min:
36 if not max > min:
37 raise ValueError('max must be greater than min: (min={0}, max={1})'.format(min, max))
37 raise ValueError('max must be greater than min: (min={0}, max={1})'.format(min, max))
38 value = min + abs(min-max)/2
38 value = min + abs(min-max)/2
39 value = type(min)(value)
39 value = type(min)(value)
40 elif min is None and max is None:
40 elif min is None and max is None:
41 if value == 0.0:
41 if value == 0.0:
42 min, max, value = 0.0, 1.0, 0.5
42 min, max, value = 0.0, 1.0, 0.5
43 elif value == 0:
43 elif value == 0:
44 min, max, value = 0, 1, 0
44 min, max, value = 0, 1, 0
45 elif isinstance(value, (int, float)):
45 elif isinstance(value, (int, float)):
46 min, max = (-value, 3*value) if value > 0 else (3*value, -value)
46 min, max = (-value, 3*value) if value > 0 else (3*value, -value)
47 else:
47 else:
48 raise TypeError('expected a number, got: %r' % value)
48 raise TypeError('expected a number, got: %r' % value)
49 else:
49 else:
50 raise ValueError('unable to infer range, value from: ({0}, {1}, {2})'.format(min, max, value))
50 raise ValueError('unable to infer range, value from: ({0}, {1}, {2})'.format(min, max, value))
51 if step is not None:
51 if step is not None:
52 # ensure value is on a step
52 # ensure value is on a step
53 r = (value - min) % step
53 r = (value - min) % step
54 value = value - r
54 value = value - r
55 return min, max, value
55 return min, max, value
56
56
57 def _widget_abbrev_single_value(o):
57 def _widget_abbrev_single_value(o):
58 """Make widgets from single values, which can be used as parameter defaults."""
58 """Make widgets from single values, which can be used as parameter defaults."""
59 if isinstance(o, string_types):
59 if isinstance(o, string_types):
60 return Text(value=unicode_type(o))
60 return Text(value=unicode_type(o))
61 elif isinstance(o, dict):
61 elif isinstance(o, dict):
62 return Dropdown(values=o)
62 return Dropdown(values=o)
63 elif isinstance(o, bool):
63 elif isinstance(o, bool):
64 return Checkbox(value=o)
64 return Checkbox(value=o)
65 elif isinstance(o, float):
65 elif isinstance(o, float):
66 min, max, value = _get_min_max_value(None, None, o)
66 min, max, value = _get_min_max_value(None, None, o)
67 return FloatSlider(value=o, min=min, max=max)
67 return FloatSlider(value=o, min=min, max=max)
68 elif isinstance(o, int):
68 elif isinstance(o, int):
69 min, max, value = _get_min_max_value(None, None, o)
69 min, max, value = _get_min_max_value(None, None, o)
70 return IntSlider(value=o, min=min, max=max)
70 return IntSlider(value=o, min=min, max=max)
71 else:
71 else:
72 return None
72 return None
73
73
74 def _widget_abbrev(o):
74 def _widget_abbrev(o):
75 """Make widgets from abbreviations: single values, lists or tuples."""
75 """Make widgets from abbreviations: single values, lists or tuples."""
76 float_or_int = (float, int)
76 float_or_int = (float, int)
77 if isinstance(o, (list, tuple)):
77 if isinstance(o, (list, tuple)):
78 if o and all(isinstance(x, string_types) for x in o):
78 if o and all(isinstance(x, string_types) for x in o):
79 return Dropdown(values=[unicode_type(k) for k in o])
79 return Dropdown(values=[unicode_type(k) for k in o])
80 elif _matches(o, (float_or_int, float_or_int)):
80 elif _matches(o, (float_or_int, float_or_int)):
81 min, max, value = _get_min_max_value(o[0], o[1])
81 min, max, value = _get_min_max_value(o[0], o[1])
82 if all(isinstance(_, int) for _ in o):
82 if all(isinstance(_, int) for _ in o):
83 cls = IntSlider
83 cls = IntSlider
84 else:
84 else:
85 cls = FloatSlider
85 cls = FloatSlider
86 return cls(value=value, min=min, max=max)
86 return cls(value=value, min=min, max=max)
87 elif _matches(o, (float_or_int, float_or_int, float_or_int)):
87 elif _matches(o, (float_or_int, float_or_int, float_or_int)):
88 step = o[2]
88 step = o[2]
89 if step <= 0:
89 if step <= 0:
90 raise ValueError("step must be >= 0, not %r" % step)
90 raise ValueError("step must be >= 0, not %r" % step)
91 min, max, value = _get_min_max_value(o[0], o[1], step=step)
91 min, max, value = _get_min_max_value(o[0], o[1], step=step)
92 if all(isinstance(_, int) for _ in o):
92 if all(isinstance(_, int) for _ in o):
93 cls = IntSlider
93 cls = IntSlider
94 else:
94 else:
95 cls = FloatSlider
95 cls = FloatSlider
96 return cls(value=value, min=min, max=max, step=step)
96 return cls(value=value, min=min, max=max, step=step)
97 else:
97 else:
98 return _widget_abbrev_single_value(o)
98 return _widget_abbrev_single_value(o)
99
99
100 def _widget_from_abbrev(abbrev, default=empty):
100 def _widget_from_abbrev(abbrev, default=empty):
101 """Build a Widget instance given an abbreviation or Widget."""
101 """Build a Widget instance given an abbreviation or Widget."""
102 if isinstance(abbrev, Widget) or isinstance(abbrev, fixed):
102 if isinstance(abbrev, Widget) or isinstance(abbrev, fixed):
103 return abbrev
103 return abbrev
104
104
105 widget = _widget_abbrev(abbrev)
105 widget = _widget_abbrev(abbrev)
106 if default is not empty and isinstance(abbrev, (list, tuple, dict)):
106 if default is not empty and isinstance(abbrev, (list, tuple, dict)):
107 # if it's not a single-value abbreviation,
107 # if it's not a single-value abbreviation,
108 # set the initial value from the default
108 # set the initial value from the default
109 try:
109 try:
110 widget.value = default
110 widget.value = default
111 except Exception:
111 except Exception:
112 # ignore failure to set default
112 # ignore failure to set default
113 pass
113 pass
114 if widget is None:
114 if widget is None:
115 raise ValueError("%r cannot be transformed to a Widget" % (abbrev,))
115 raise ValueError("%r cannot be transformed to a Widget" % (abbrev,))
116 return widget
116 return widget
117
117
118 def _yield_abbreviations_for_parameter(param, kwargs):
118 def _yield_abbreviations_for_parameter(param, kwargs):
119 """Get an abbreviation for a function parameter."""
119 """Get an abbreviation for a function parameter."""
120 name = param.name
120 name = param.name
121 kind = param.kind
121 kind = param.kind
122 ann = param.annotation
122 ann = param.annotation
123 default = param.default
123 default = param.default
124 not_found = (name, empty, empty)
124 not_found = (name, empty, empty)
125 if kind in (Parameter.POSITIONAL_OR_KEYWORD, Parameter.KEYWORD_ONLY):
125 if kind in (Parameter.POSITIONAL_OR_KEYWORD, Parameter.KEYWORD_ONLY):
126 if name in kwargs:
126 if name in kwargs:
127 value = kwargs.pop(name)
127 value = kwargs.pop(name)
128 elif ann is not empty:
128 elif ann is not empty:
129 value = ann
129 value = ann
130 elif default is not empty:
130 elif default is not empty:
131 value = default
131 value = default
132 else:
132 else:
133 yield not_found
133 yield not_found
134 yield (name, value, default)
134 yield (name, value, default)
135 elif kind == Parameter.VAR_KEYWORD:
135 elif kind == Parameter.VAR_KEYWORD:
136 # In this case name=kwargs and we yield the items in kwargs with their keys.
136 # In this case name=kwargs and we yield the items in kwargs with their keys.
137 for k, v in kwargs.copy().items():
137 for k, v in kwargs.copy().items():
138 kwargs.pop(k)
138 kwargs.pop(k)
139 yield k, v, empty
139 yield k, v, empty
140
140
141 def _find_abbreviations(f, kwargs):
141 def _find_abbreviations(f, kwargs):
142 """Find the abbreviations for a function and kwargs passed to interact."""
142 """Find the abbreviations for a function and kwargs passed to interact."""
143 new_kwargs = []
143 new_kwargs = []
144 for param in signature(f).parameters.values():
144 for param in signature(f).parameters.values():
145 for name, value, default in _yield_abbreviations_for_parameter(param, kwargs):
145 for name, value, default in _yield_abbreviations_for_parameter(param, kwargs):
146 if value is empty:
146 if value is empty:
147 raise ValueError('cannot find widget or abbreviation for argument: {!r}'.format(name))
147 raise ValueError('cannot find widget or abbreviation for argument: {!r}'.format(name))
148 new_kwargs.append((name, value, default))
148 new_kwargs.append((name, value, default))
149 return new_kwargs
149 return new_kwargs
150
150
151 def _widgets_from_abbreviations(seq):
151 def _widgets_from_abbreviations(seq):
152 """Given a sequence of (name, abbrev) tuples, return a sequence of Widgets."""
152 """Given a sequence of (name, abbrev) tuples, return a sequence of Widgets."""
153 result = []
153 result = []
154 for name, abbrev, default in seq:
154 for name, abbrev, default in seq:
155 widget = _widget_from_abbrev(abbrev, default)
155 widget = _widget_from_abbrev(abbrev, default)
156 if not widget.description:
156 if not widget.description:
157 widget.description = name
157 widget.description = name
158 widget._kwarg = name
158 widget._kwarg = name
159 result.append(widget)
159 result.append(widget)
160 return result
160 return result
161
161
162 def interactive(__interact_f, **kwargs):
162 def interactive(__interact_f, **kwargs):
163 """
163 """
164 Builds a group of interactive widgets tied to a function and places the
164 Builds a group of interactive widgets tied to a function and places the
165 group into a Box container.
165 group into a Box container.
166
166
167 Returns
167 Returns
168 -------
168 -------
169 container : a Box instance containing multiple widgets
169 container : a Box instance containing multiple widgets
170
170
171 Parameters
171 Parameters
172 ----------
172 ----------
173 __interact_f : function
173 __interact_f : function
174 The function to which the interactive widgets are tied. The **kwargs
174 The function to which the interactive widgets are tied. The **kwargs
175 should match the function signature.
175 should match the function signature.
176 **kwargs : various, optional
176 **kwargs : various, optional
177 An interactive widget is created for each keyword argument that is a
177 An interactive widget is created for each keyword argument that is a
178 valid widget abbreviation.
178 valid widget abbreviation.
179 """
179 """
180 f = __interact_f
180 f = __interact_f
181 co = kwargs.pop('clear_output', True)
181 co = kwargs.pop('clear_output', True)
182 manual = kwargs.pop('__manual', False)
182 manual = kwargs.pop('__manual', False)
183 kwargs_widgets = []
183 kwargs_widgets = []
184 container = Box()
184 container = Box()
185 container.result = None
185 container.result = None
186 container.args = []
186 container.args = []
187 container.kwargs = dict()
187 container.kwargs = dict()
188 kwargs = kwargs.copy()
188 kwargs = kwargs.copy()
189
189
190 new_kwargs = _find_abbreviations(f, kwargs)
190 new_kwargs = _find_abbreviations(f, kwargs)
191 # Before we proceed, let's make sure that the user has passed a set of args+kwargs
191 # Before we proceed, let's make sure that the user has passed a set of args+kwargs
192 # that will lead to a valid call of the function. This protects against unspecified
192 # that will lead to a valid call of the function. This protects against unspecified
193 # and doubly-specified arguments.
193 # and doubly-specified arguments.
194 getcallargs(f, **{n:v for n,v,_ in new_kwargs})
194 getcallargs(f, **{n:v for n,v,_ in new_kwargs})
195 # Now build the widgets from the abbreviations.
195 # Now build the widgets from the abbreviations.
196 kwargs_widgets.extend(_widgets_from_abbreviations(new_kwargs))
196 kwargs_widgets.extend(_widgets_from_abbreviations(new_kwargs))
197
197
198 # This has to be done as an assignment, not using container.children.append,
198 # This has to be done as an assignment, not using container.children.append,
199 # so that traitlets notices the update. We skip any objects (such as fixed) that
199 # so that traitlets notices the update. We skip any objects (such as fixed) that
200 # are not DOMWidgets.
200 # are not DOMWidgets.
201 c = [w for w in kwargs_widgets if isinstance(w, DOMWidget)]
201 c = [w for w in kwargs_widgets if isinstance(w, DOMWidget)]
202
202
203 # If we are only to run the function on demand, add a button to request this
203 # If we are only to run the function on demand, add a button to request this
204 if manual:
204 if manual:
205 manual_button = Button(description="Run %s" % f.__name__)
205 manual_button = Button(description="Run %s" % f.__name__)
206 c.append(manual_button)
206 c.append(manual_button)
207
208 # Use an output widget to capture the output of interact.
209 output = Output()
210 c.append(output)
207 container.children = c
211 container.children = c
208
212
209 # Build the callback
213 # Build the callback
210 def call_f(name=None, old=None, new=None):
214 def call_f(name=None, old=None, new=None):
211 container.kwargs = {}
215 with output:
212 for widget in kwargs_widgets:
216 container.kwargs = {}
213 value = widget.value
217 for widget in kwargs_widgets:
214 container.kwargs[widget._kwarg] = value
218 value = widget.value
215 if co:
219 container.kwargs[widget._kwarg] = value
216 clear_output(wait=True)
220 if co:
217 if manual:
221 clear_output(wait=True)
218 manual_button.disabled = True
219 try:
220 container.result = f(**container.kwargs)
221 except Exception as e:
222 ip = get_ipython()
223 if ip is None:
224 container.log.warn("Exception in interact callback: %s", e, exc_info=True)
225 else:
226 ip.showtraceback()
227 finally:
228 if manual:
222 if manual:
229 manual_button.disabled = False
223 manual_button.disabled = True
224 try:
225 container.result = f(**container.kwargs)
226 except Exception as e:
227 ip = get_ipython()
228 if ip is None:
229 container.log.warn("Exception in interact callback: %s", e, exc_info=True)
230 else:
231 ip.showtraceback()
232 finally:
233 if manual:
234 manual_button.disabled = False
230
235
231 # Wire up the widgets
236 # Wire up the widgets
232 # If we are doing manual running, the callback is only triggered by the button
237 # If we are doing manual running, the callback is only triggered by the button
233 # Otherwise, it is triggered for every trait change received
238 # Otherwise, it is triggered for every trait change received
234 # On-demand running also suppresses running the function with the initial parameters
239 # On-demand running also suppresses running the function with the initial parameters
235 if manual:
240 if manual:
236 manual_button.on_click(call_f)
241 manual_button.on_click(call_f)
237 else:
242 else:
238 for widget in kwargs_widgets:
243 for widget in kwargs_widgets:
239 widget.on_trait_change(call_f, 'value')
244 widget.on_trait_change(call_f, 'value')
240
245
241 container.on_displayed(lambda _: call_f(None, None, None))
246 container.on_displayed(lambda _: call_f(None, None, None))
242
247
243 return container
248 return container
244
249
245 def interact(__interact_f=None, **kwargs):
250 def interact(__interact_f=None, **kwargs):
246 """
251 """
247 Displays interactive widgets which are tied to a function.
252 Displays interactive widgets which are tied to a function.
248 Expects the first argument to be a function. Parameters to this function are
253 Expects the first argument to be a function. Parameters to this function are
249 widget abbreviations passed in as keyword arguments (**kwargs). Can be used
254 widget abbreviations passed in as keyword arguments (**kwargs). Can be used
250 as a decorator (see examples).
255 as a decorator (see examples).
251
256
252 Returns
257 Returns
253 -------
258 -------
254 f : __interact_f with interactive widget attached to it.
259 f : __interact_f with interactive widget attached to it.
255
260
256 Parameters
261 Parameters
257 ----------
262 ----------
258 __interact_f : function
263 __interact_f : function
259 The function to which the interactive widgets are tied. The **kwargs
264 The function to which the interactive widgets are tied. The **kwargs
260 should match the function signature. Passed to :func:`interactive()`
265 should match the function signature. Passed to :func:`interactive()`
261 **kwargs : various, optional
266 **kwargs : various, optional
262 An interactive widget is created for each keyword argument that is a
267 An interactive widget is created for each keyword argument that is a
263 valid widget abbreviation. Passed to :func:`interactive()`
268 valid widget abbreviation. Passed to :func:`interactive()`
264
269
265 Examples
270 Examples
266 --------
271 --------
267 Renders an interactive text field that shows the greeting with the passed in
272 Renders an interactive text field that shows the greeting with the passed in
268 text.
273 text.
269
274
270 1. Invocation of interact as a function
275 1. Invocation of interact as a function
271 def greeting(text="World"):
276 def greeting(text="World"):
272 print "Hello {}".format(text)
277 print "Hello {}".format(text)
273 interact(greeting, text="IPython Widgets")
278 interact(greeting, text="IPython Widgets")
274
279
275 2. Invocation of interact as a decorator
280 2. Invocation of interact as a decorator
276 @interact
281 @interact
277 def greeting(text="World"):
282 def greeting(text="World"):
278 print "Hello {}".format(text)
283 print "Hello {}".format(text)
279
284
280 3. Invocation of interact as a decorator with named parameters
285 3. Invocation of interact as a decorator with named parameters
281 @interact(text="IPython Widgets")
286 @interact(text="IPython Widgets")
282 def greeting(text="World"):
287 def greeting(text="World"):
283 print "Hello {}".format(text)
288 print "Hello {}".format(text)
284
289
285 Renders an interactive slider widget and prints square of number.
290 Renders an interactive slider widget and prints square of number.
286
291
287 1. Invocation of interact as a function
292 1. Invocation of interact as a function
288 def square(num=1):
293 def square(num=1):
289 print "{} squared is {}".format(num, num*num)
294 print "{} squared is {}".format(num, num*num)
290 interact(square, num=5)
295 interact(square, num=5)
291
296
292 2. Invocation of interact as a decorator
297 2. Invocation of interact as a decorator
293 @interact
298 @interact
294 def square(num=2):
299 def square(num=2):
295 print "{} squared is {}".format(num, num*num)
300 print "{} squared is {}".format(num, num*num)
296
301
297 3. Invocation of interact as a decorator with named parameters
302 3. Invocation of interact as a decorator with named parameters
298 @interact(num=5)
303 @interact(num=5)
299 def square(num=2):
304 def square(num=2):
300 print "{} squared is {}".format(num, num*num)
305 print "{} squared is {}".format(num, num*num)
301 """
306 """
302 # positional arg support in: https://gist.github.com/8851331
307 # positional arg support in: https://gist.github.com/8851331
303 if __interact_f is not None:
308 if __interact_f is not None:
304 # This branch handles the cases 1 and 2
309 # This branch handles the cases 1 and 2
305 # 1. interact(f, **kwargs)
310 # 1. interact(f, **kwargs)
306 # 2. @interact
311 # 2. @interact
307 # def f(*args, **kwargs):
312 # def f(*args, **kwargs):
308 # ...
313 # ...
309 f = __interact_f
314 f = __interact_f
310 w = interactive(f, **kwargs)
315 w = interactive(f, **kwargs)
311 try:
316 try:
312 f.widget = w
317 f.widget = w
313 except AttributeError:
318 except AttributeError:
314 # some things (instancemethods) can't have attributes attached,
319 # some things (instancemethods) can't have attributes attached,
315 # so wrap in a lambda
320 # so wrap in a lambda
316 f = lambda *args, **kwargs: __interact_f(*args, **kwargs)
321 f = lambda *args, **kwargs: __interact_f(*args, **kwargs)
317 f.widget = w
322 f.widget = w
318 display(w)
323 display(w)
319 return f
324 return f
320 else:
325 else:
321 # This branch handles the case 3
326 # This branch handles the case 3
322 # @interact(a=30, b=40)
327 # @interact(a=30, b=40)
323 # def f(*args, **kwargs):
328 # def f(*args, **kwargs):
324 # ...
329 # ...
325 def dec(f):
330 def dec(f):
326 return interact(f, **kwargs)
331 return interact(f, **kwargs)
327 return dec
332 return dec
328
333
329 def interact_manual(__interact_f=None, **kwargs):
334 def interact_manual(__interact_f=None, **kwargs):
330 """interact_manual(f, **kwargs)
335 """interact_manual(f, **kwargs)
331
336
332 As `interact()`, generates widgets for each argument, but rather than running
337 As `interact()`, generates widgets for each argument, but rather than running
333 the function after each widget change, adds a "Run" button and waits for it
338 the function after each widget change, adds a "Run" button and waits for it
334 to be clicked. Useful if the function is long-running and has several
339 to be clicked. Useful if the function is long-running and has several
335 parameters to change.
340 parameters to change.
336 """
341 """
337 return interact(__interact_f, __manual=True, **kwargs)
342 return interact(__interact_f, __manual=True, **kwargs)
338
343
339 class fixed(HasTraits):
344 class fixed(HasTraits):
340 """A pseudo-widget whose value is fixed and never synced to the client."""
345 """A pseudo-widget whose value is fixed and never synced to the client."""
341 value = Any(help="Any Python object")
346 value = Any(help="Any Python object")
342 description = Unicode('', help="Any Python object")
347 description = Unicode('', help="Any Python object")
343 def __init__(self, value, **kwargs):
348 def __init__(self, value, **kwargs):
344 super(fixed, self).__init__(value=value, **kwargs)
349 super(fixed, self).__init__(value=value, **kwargs)
@@ -1,635 +1,636 b''
1 """Test interact and interactive."""
1 """Test interact and interactive."""
2
2
3 # Copyright (c) IPython Development Team.
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
4 # Distributed under the terms of the Modified BSD License.
5
5
6 from __future__ import print_function
6 from __future__ import print_function
7
7
8 from collections import OrderedDict
8 from collections import OrderedDict
9
9
10 import nose.tools as nt
10 import nose.tools as nt
11 import IPython.testing.tools as tt
11 import IPython.testing.tools as tt
12
12
13 from IPython.kernel.comm import Comm
13 from IPython.kernel.comm import Comm
14 from IPython.html import widgets
14 from IPython.html import widgets
15 from IPython.html.widgets import interact, interactive, Widget, interaction
15 from IPython.html.widgets import interact, interactive, Widget, interaction
16 from IPython.utils.py3compat import annotate
16 from IPython.utils.py3compat import annotate
17
17
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19 # Utility stuff
19 # Utility stuff
20 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
21
21
22 class DummyComm(Comm):
22 class DummyComm(Comm):
23 comm_id = 'a-b-c-d'
23 comm_id = 'a-b-c-d'
24
24
25 def open(self, *args, **kwargs):
25 def open(self, *args, **kwargs):
26 pass
26 pass
27
27
28 def send(self, *args, **kwargs):
28 def send(self, *args, **kwargs):
29 pass
29 pass
30
30
31 def close(self, *args, **kwargs):
31 def close(self, *args, **kwargs):
32 pass
32 pass
33
33
34 _widget_attrs = {}
34 _widget_attrs = {}
35 displayed = []
35 displayed = []
36 undefined = object()
36 undefined = object()
37
37
38 def setup():
38 def setup():
39 _widget_attrs['_comm_default'] = getattr(Widget, '_comm_default', undefined)
39 _widget_attrs['_comm_default'] = getattr(Widget, '_comm_default', undefined)
40 Widget._comm_default = lambda self: DummyComm()
40 Widget._comm_default = lambda self: DummyComm()
41 _widget_attrs['_ipython_display_'] = Widget._ipython_display_
41 _widget_attrs['_ipython_display_'] = Widget._ipython_display_
42 def raise_not_implemented(*args, **kwargs):
42 def raise_not_implemented(*args, **kwargs):
43 raise NotImplementedError()
43 raise NotImplementedError()
44 Widget._ipython_display_ = raise_not_implemented
44 Widget._ipython_display_ = raise_not_implemented
45
45
46 def teardown():
46 def teardown():
47 for attr, value in _widget_attrs.items():
47 for attr, value in _widget_attrs.items():
48 if value is undefined:
48 if value is undefined:
49 delattr(Widget, attr)
49 delattr(Widget, attr)
50 else:
50 else:
51 setattr(Widget, attr, value)
51 setattr(Widget, attr, value)
52
52
53 def f(**kwargs):
53 def f(**kwargs):
54 pass
54 pass
55
55
56 def clear_display():
56 def clear_display():
57 global displayed
57 global displayed
58 displayed = []
58 displayed = []
59
59
60 def record_display(*args):
60 def record_display(*args):
61 displayed.extend(args)
61 displayed.extend(args)
62
62
63 #-----------------------------------------------------------------------------
63 #-----------------------------------------------------------------------------
64 # Actual tests
64 # Actual tests
65 #-----------------------------------------------------------------------------
65 #-----------------------------------------------------------------------------
66
66
67 def check_widget(w, **d):
67 def check_widget(w, **d):
68 """Check a single widget against a dict"""
68 """Check a single widget against a dict"""
69 for attr, expected in d.items():
69 for attr, expected in d.items():
70 if attr == 'cls':
70 if attr == 'cls':
71 nt.assert_is(w.__class__, expected)
71 nt.assert_is(w.__class__, expected)
72 else:
72 else:
73 value = getattr(w, attr)
73 value = getattr(w, attr)
74 nt.assert_equal(value, expected,
74 nt.assert_equal(value, expected,
75 "%s.%s = %r != %r" % (w.__class__.__name__, attr, value, expected)
75 "%s.%s = %r != %r" % (w.__class__.__name__, attr, value, expected)
76 )
76 )
77
77
78 def check_widgets(container, **to_check):
78 def check_widgets(container, **to_check):
79 """Check that widgets are created as expected"""
79 """Check that widgets are created as expected"""
80 # build a widget dictionary, so it matches
80 # build a widget dictionary, so it matches
81 widgets = {}
81 widgets = {}
82 for w in container.children:
82 for w in container.children:
83 widgets[w.description] = w
83 if hasattr(w, 'description'):
84 widgets[w.description] = w
84
85
85 for key, d in to_check.items():
86 for key, d in to_check.items():
86 nt.assert_in(key, widgets)
87 nt.assert_in(key, widgets)
87 check_widget(widgets[key], **d)
88 check_widget(widgets[key], **d)
88
89
89
90
90 def test_single_value_string():
91 def test_single_value_string():
91 a = u'hello'
92 a = u'hello'
92 c = interactive(f, a=a)
93 c = interactive(f, a=a)
93 w = c.children[0]
94 w = c.children[0]
94 check_widget(w,
95 check_widget(w,
95 cls=widgets.Text,
96 cls=widgets.Text,
96 description='a',
97 description='a',
97 value=a,
98 value=a,
98 )
99 )
99
100
100 def test_single_value_bool():
101 def test_single_value_bool():
101 for a in (True, False):
102 for a in (True, False):
102 c = interactive(f, a=a)
103 c = interactive(f, a=a)
103 w = c.children[0]
104 w = c.children[0]
104 check_widget(w,
105 check_widget(w,
105 cls=widgets.Checkbox,
106 cls=widgets.Checkbox,
106 description='a',
107 description='a',
107 value=a,
108 value=a,
108 )
109 )
109
110
110 def test_single_value_dict():
111 def test_single_value_dict():
111 for d in [
112 for d in [
112 dict(a=5),
113 dict(a=5),
113 dict(a=5, b='b', c=dict),
114 dict(a=5, b='b', c=dict),
114 ]:
115 ]:
115 c = interactive(f, d=d)
116 c = interactive(f, d=d)
116 w = c.children[0]
117 w = c.children[0]
117 check_widget(w,
118 check_widget(w,
118 cls=widgets.Dropdown,
119 cls=widgets.Dropdown,
119 description='d',
120 description='d',
120 values=d,
121 values=d,
121 value=next(iter(d.values())),
122 value=next(iter(d.values())),
122 )
123 )
123
124
124 def test_single_value_float():
125 def test_single_value_float():
125 for a in (2.25, 1.0, -3.5):
126 for a in (2.25, 1.0, -3.5):
126 c = interactive(f, a=a)
127 c = interactive(f, a=a)
127 w = c.children[0]
128 w = c.children[0]
128 check_widget(w,
129 check_widget(w,
129 cls=widgets.FloatSlider,
130 cls=widgets.FloatSlider,
130 description='a',
131 description='a',
131 value=a,
132 value=a,
132 min= -a if a > 0 else 3*a,
133 min= -a if a > 0 else 3*a,
133 max= 3*a if a > 0 else -a,
134 max= 3*a if a > 0 else -a,
134 step=0.1,
135 step=0.1,
135 readout=True,
136 readout=True,
136 )
137 )
137
138
138 def test_single_value_int():
139 def test_single_value_int():
139 for a in (1, 5, -3):
140 for a in (1, 5, -3):
140 c = interactive(f, a=a)
141 c = interactive(f, a=a)
141 nt.assert_equal(len(c.children), 1)
142 nt.assert_equal(len(c.children), 2)
142 w = c.children[0]
143 w = c.children[0]
143 check_widget(w,
144 check_widget(w,
144 cls=widgets.IntSlider,
145 cls=widgets.IntSlider,
145 description='a',
146 description='a',
146 value=a,
147 value=a,
147 min= -a if a > 0 else 3*a,
148 min= -a if a > 0 else 3*a,
148 max= 3*a if a > 0 else -a,
149 max= 3*a if a > 0 else -a,
149 step=1,
150 step=1,
150 readout=True,
151 readout=True,
151 )
152 )
152
153
153 def test_list_tuple_2_int():
154 def test_list_tuple_2_int():
154 with nt.assert_raises(ValueError):
155 with nt.assert_raises(ValueError):
155 c = interactive(f, tup=(1,1))
156 c = interactive(f, tup=(1,1))
156 with nt.assert_raises(ValueError):
157 with nt.assert_raises(ValueError):
157 c = interactive(f, tup=(1,-1))
158 c = interactive(f, tup=(1,-1))
158 for min, max in [ (0,1), (1,10), (1,2), (-5,5), (-20,-19) ]:
159 for min, max in [ (0,1), (1,10), (1,2), (-5,5), (-20,-19) ]:
159 c = interactive(f, tup=(min, max), lis=[min, max])
160 c = interactive(f, tup=(min, max), lis=[min, max])
160 nt.assert_equal(len(c.children), 2)
161 nt.assert_equal(len(c.children), 3)
161 d = dict(
162 d = dict(
162 cls=widgets.IntSlider,
163 cls=widgets.IntSlider,
163 min=min,
164 min=min,
164 max=max,
165 max=max,
165 step=1,
166 step=1,
166 readout=True,
167 readout=True,
167 )
168 )
168 check_widgets(c, tup=d, lis=d)
169 check_widgets(c, tup=d, lis=d)
169
170
170 def test_list_tuple_3_int():
171 def test_list_tuple_3_int():
171 with nt.assert_raises(ValueError):
172 with nt.assert_raises(ValueError):
172 c = interactive(f, tup=(1,2,0))
173 c = interactive(f, tup=(1,2,0))
173 with nt.assert_raises(ValueError):
174 with nt.assert_raises(ValueError):
174 c = interactive(f, tup=(1,2,-1))
175 c = interactive(f, tup=(1,2,-1))
175 for min, max, step in [ (0,2,1), (1,10,2), (1,100,2), (-5,5,4), (-100,-20,4) ]:
176 for min, max, step in [ (0,2,1), (1,10,2), (1,100,2), (-5,5,4), (-100,-20,4) ]:
176 c = interactive(f, tup=(min, max, step), lis=[min, max, step])
177 c = interactive(f, tup=(min, max, step), lis=[min, max, step])
177 nt.assert_equal(len(c.children), 2)
178 nt.assert_equal(len(c.children), 3)
178 d = dict(
179 d = dict(
179 cls=widgets.IntSlider,
180 cls=widgets.IntSlider,
180 min=min,
181 min=min,
181 max=max,
182 max=max,
182 step=step,
183 step=step,
183 readout=True,
184 readout=True,
184 )
185 )
185 check_widgets(c, tup=d, lis=d)
186 check_widgets(c, tup=d, lis=d)
186
187
187 def test_list_tuple_2_float():
188 def test_list_tuple_2_float():
188 with nt.assert_raises(ValueError):
189 with nt.assert_raises(ValueError):
189 c = interactive(f, tup=(1.0,1.0))
190 c = interactive(f, tup=(1.0,1.0))
190 with nt.assert_raises(ValueError):
191 with nt.assert_raises(ValueError):
191 c = interactive(f, tup=(0.5,-0.5))
192 c = interactive(f, tup=(0.5,-0.5))
192 for min, max in [ (0.5, 1.5), (1.1,10.2), (1,2.2), (-5.,5), (-20,-19.) ]:
193 for min, max in [ (0.5, 1.5), (1.1,10.2), (1,2.2), (-5.,5), (-20,-19.) ]:
193 c = interactive(f, tup=(min, max), lis=[min, max])
194 c = interactive(f, tup=(min, max), lis=[min, max])
194 nt.assert_equal(len(c.children), 2)
195 nt.assert_equal(len(c.children), 3)
195 d = dict(
196 d = dict(
196 cls=widgets.FloatSlider,
197 cls=widgets.FloatSlider,
197 min=min,
198 min=min,
198 max=max,
199 max=max,
199 step=.1,
200 step=.1,
200 readout=True,
201 readout=True,
201 )
202 )
202 check_widgets(c, tup=d, lis=d)
203 check_widgets(c, tup=d, lis=d)
203
204
204 def test_list_tuple_3_float():
205 def test_list_tuple_3_float():
205 with nt.assert_raises(ValueError):
206 with nt.assert_raises(ValueError):
206 c = interactive(f, tup=(1,2,0.0))
207 c = interactive(f, tup=(1,2,0.0))
207 with nt.assert_raises(ValueError):
208 with nt.assert_raises(ValueError):
208 c = interactive(f, tup=(-1,-2,1.))
209 c = interactive(f, tup=(-1,-2,1.))
209 with nt.assert_raises(ValueError):
210 with nt.assert_raises(ValueError):
210 c = interactive(f, tup=(1,2.,-1.))
211 c = interactive(f, tup=(1,2.,-1.))
211 for min, max, step in [ (0.,2,1), (1,10.,2), (1,100,2.), (-5.,5.,4), (-100,-20.,4.) ]:
212 for min, max, step in [ (0.,2,1), (1,10.,2), (1,100,2.), (-5.,5.,4), (-100,-20.,4.) ]:
212 c = interactive(f, tup=(min, max, step), lis=[min, max, step])
213 c = interactive(f, tup=(min, max, step), lis=[min, max, step])
213 nt.assert_equal(len(c.children), 2)
214 nt.assert_equal(len(c.children), 3)
214 d = dict(
215 d = dict(
215 cls=widgets.FloatSlider,
216 cls=widgets.FloatSlider,
216 min=min,
217 min=min,
217 max=max,
218 max=max,
218 step=step,
219 step=step,
219 readout=True,
220 readout=True,
220 )
221 )
221 check_widgets(c, tup=d, lis=d)
222 check_widgets(c, tup=d, lis=d)
222
223
223 def test_list_tuple_str():
224 def test_list_tuple_str():
224 values = ['hello', 'there', 'guy']
225 values = ['hello', 'there', 'guy']
225 first = values[0]
226 first = values[0]
226 c = interactive(f, tup=tuple(values), lis=list(values))
227 c = interactive(f, tup=tuple(values), lis=list(values))
227 nt.assert_equal(len(c.children), 2)
228 nt.assert_equal(len(c.children), 3)
228 d = dict(
229 d = dict(
229 cls=widgets.Dropdown,
230 cls=widgets.Dropdown,
230 value=first,
231 value=first,
231 values=values
232 values=values
232 )
233 )
233 check_widgets(c, tup=d, lis=d)
234 check_widgets(c, tup=d, lis=d)
234
235
235 def test_list_tuple_invalid():
236 def test_list_tuple_invalid():
236 for bad in [
237 for bad in [
237 (),
238 (),
238 (5, 'hi'),
239 (5, 'hi'),
239 ('hi', 5),
240 ('hi', 5),
240 ({},),
241 ({},),
241 (None,),
242 (None,),
242 ]:
243 ]:
243 with nt.assert_raises(ValueError):
244 with nt.assert_raises(ValueError):
244 print(bad) # because there is no custom message in assert_raises
245 print(bad) # because there is no custom message in assert_raises
245 c = interactive(f, tup=bad)
246 c = interactive(f, tup=bad)
246
247
247 def test_defaults():
248 def test_defaults():
248 @annotate(n=10)
249 @annotate(n=10)
249 def f(n, f=4.5, g=1):
250 def f(n, f=4.5, g=1):
250 pass
251 pass
251
252
252 c = interactive(f)
253 c = interactive(f)
253 check_widgets(c,
254 check_widgets(c,
254 n=dict(
255 n=dict(
255 cls=widgets.IntSlider,
256 cls=widgets.IntSlider,
256 value=10,
257 value=10,
257 ),
258 ),
258 f=dict(
259 f=dict(
259 cls=widgets.FloatSlider,
260 cls=widgets.FloatSlider,
260 value=4.5,
261 value=4.5,
261 ),
262 ),
262 g=dict(
263 g=dict(
263 cls=widgets.IntSlider,
264 cls=widgets.IntSlider,
264 value=1,
265 value=1,
265 ),
266 ),
266 )
267 )
267
268
268 def test_default_values():
269 def test_default_values():
269 @annotate(n=10, f=(0, 10.), g=5, h={'a': 1, 'b': 2}, j=['hi', 'there'])
270 @annotate(n=10, f=(0, 10.), g=5, h={'a': 1, 'b': 2}, j=['hi', 'there'])
270 def f(n, f=4.5, g=1, h=2, j='there'):
271 def f(n, f=4.5, g=1, h=2, j='there'):
271 pass
272 pass
272
273
273 c = interactive(f)
274 c = interactive(f)
274 check_widgets(c,
275 check_widgets(c,
275 n=dict(
276 n=dict(
276 cls=widgets.IntSlider,
277 cls=widgets.IntSlider,
277 value=10,
278 value=10,
278 ),
279 ),
279 f=dict(
280 f=dict(
280 cls=widgets.FloatSlider,
281 cls=widgets.FloatSlider,
281 value=4.5,
282 value=4.5,
282 ),
283 ),
283 g=dict(
284 g=dict(
284 cls=widgets.IntSlider,
285 cls=widgets.IntSlider,
285 value=5,
286 value=5,
286 ),
287 ),
287 h=dict(
288 h=dict(
288 cls=widgets.Dropdown,
289 cls=widgets.Dropdown,
289 values={'a': 1, 'b': 2},
290 values={'a': 1, 'b': 2},
290 value=2
291 value=2
291 ),
292 ),
292 j=dict(
293 j=dict(
293 cls=widgets.Dropdown,
294 cls=widgets.Dropdown,
294 values=['hi', 'there'],
295 values=['hi', 'there'],
295 value='there'
296 value='there'
296 ),
297 ),
297 )
298 )
298
299
299 def test_default_out_of_bounds():
300 def test_default_out_of_bounds():
300 @annotate(f=(0, 10.), h={'a': 1}, j=['hi', 'there'])
301 @annotate(f=(0, 10.), h={'a': 1}, j=['hi', 'there'])
301 def f(f='hi', h=5, j='other'):
302 def f(f='hi', h=5, j='other'):
302 pass
303 pass
303
304
304 c = interactive(f)
305 c = interactive(f)
305 check_widgets(c,
306 check_widgets(c,
306 f=dict(
307 f=dict(
307 cls=widgets.FloatSlider,
308 cls=widgets.FloatSlider,
308 value=5.,
309 value=5.,
309 ),
310 ),
310 h=dict(
311 h=dict(
311 cls=widgets.Dropdown,
312 cls=widgets.Dropdown,
312 values={'a': 1},
313 values={'a': 1},
313 value=1,
314 value=1,
314 ),
315 ),
315 j=dict(
316 j=dict(
316 cls=widgets.Dropdown,
317 cls=widgets.Dropdown,
317 values=['hi', 'there'],
318 values=['hi', 'there'],
318 value='hi',
319 value='hi',
319 ),
320 ),
320 )
321 )
321
322
322 def test_annotations():
323 def test_annotations():
323 @annotate(n=10, f=widgets.FloatText())
324 @annotate(n=10, f=widgets.FloatText())
324 def f(n, f):
325 def f(n, f):
325 pass
326 pass
326
327
327 c = interactive(f)
328 c = interactive(f)
328 check_widgets(c,
329 check_widgets(c,
329 n=dict(
330 n=dict(
330 cls=widgets.IntSlider,
331 cls=widgets.IntSlider,
331 value=10,
332 value=10,
332 ),
333 ),
333 f=dict(
334 f=dict(
334 cls=widgets.FloatText,
335 cls=widgets.FloatText,
335 ),
336 ),
336 )
337 )
337
338
338 def test_priority():
339 def test_priority():
339 @annotate(annotate='annotate', kwarg='annotate')
340 @annotate(annotate='annotate', kwarg='annotate')
340 def f(kwarg='default', annotate='default', default='default'):
341 def f(kwarg='default', annotate='default', default='default'):
341 pass
342 pass
342
343
343 c = interactive(f, kwarg='kwarg')
344 c = interactive(f, kwarg='kwarg')
344 check_widgets(c,
345 check_widgets(c,
345 kwarg=dict(
346 kwarg=dict(
346 cls=widgets.Text,
347 cls=widgets.Text,
347 value='kwarg',
348 value='kwarg',
348 ),
349 ),
349 annotate=dict(
350 annotate=dict(
350 cls=widgets.Text,
351 cls=widgets.Text,
351 value='annotate',
352 value='annotate',
352 ),
353 ),
353 )
354 )
354
355
355 @nt.with_setup(clear_display)
356 @nt.with_setup(clear_display)
356 def test_decorator_kwarg():
357 def test_decorator_kwarg():
357 with tt.monkeypatch(interaction, 'display', record_display):
358 with tt.monkeypatch(interaction, 'display', record_display):
358 @interact(a=5)
359 @interact(a=5)
359 def foo(a):
360 def foo(a):
360 pass
361 pass
361 nt.assert_equal(len(displayed), 1)
362 nt.assert_equal(len(displayed), 1)
362 w = displayed[0].children[0]
363 w = displayed[0].children[0]
363 check_widget(w,
364 check_widget(w,
364 cls=widgets.IntSlider,
365 cls=widgets.IntSlider,
365 value=5,
366 value=5,
366 )
367 )
367
368
368 @nt.with_setup(clear_display)
369 @nt.with_setup(clear_display)
369 def test_interact_instancemethod():
370 def test_interact_instancemethod():
370 class Foo(object):
371 class Foo(object):
371 def show(self, x):
372 def show(self, x):
372 print(x)
373 print(x)
373
374
374 f = Foo()
375 f = Foo()
375
376
376 with tt.monkeypatch(interaction, 'display', record_display):
377 with tt.monkeypatch(interaction, 'display', record_display):
377 g = interact(f.show, x=(1,10))
378 g = interact(f.show, x=(1,10))
378 nt.assert_equal(len(displayed), 1)
379 nt.assert_equal(len(displayed), 1)
379 w = displayed[0].children[0]
380 w = displayed[0].children[0]
380 check_widget(w,
381 check_widget(w,
381 cls=widgets.IntSlider,
382 cls=widgets.IntSlider,
382 value=5,
383 value=5,
383 )
384 )
384
385
385 @nt.with_setup(clear_display)
386 @nt.with_setup(clear_display)
386 def test_decorator_no_call():
387 def test_decorator_no_call():
387 with tt.monkeypatch(interaction, 'display', record_display):
388 with tt.monkeypatch(interaction, 'display', record_display):
388 @interact
389 @interact
389 def foo(a='default'):
390 def foo(a='default'):
390 pass
391 pass
391 nt.assert_equal(len(displayed), 1)
392 nt.assert_equal(len(displayed), 1)
392 w = displayed[0].children[0]
393 w = displayed[0].children[0]
393 check_widget(w,
394 check_widget(w,
394 cls=widgets.Text,
395 cls=widgets.Text,
395 value='default',
396 value='default',
396 )
397 )
397
398
398 @nt.with_setup(clear_display)
399 @nt.with_setup(clear_display)
399 def test_call_interact():
400 def test_call_interact():
400 def foo(a='default'):
401 def foo(a='default'):
401 pass
402 pass
402 with tt.monkeypatch(interaction, 'display', record_display):
403 with tt.monkeypatch(interaction, 'display', record_display):
403 ifoo = interact(foo)
404 ifoo = interact(foo)
404 nt.assert_equal(len(displayed), 1)
405 nt.assert_equal(len(displayed), 1)
405 w = displayed[0].children[0]
406 w = displayed[0].children[0]
406 check_widget(w,
407 check_widget(w,
407 cls=widgets.Text,
408 cls=widgets.Text,
408 value='default',
409 value='default',
409 )
410 )
410
411
411 @nt.with_setup(clear_display)
412 @nt.with_setup(clear_display)
412 def test_call_interact_kwargs():
413 def test_call_interact_kwargs():
413 def foo(a='default'):
414 def foo(a='default'):
414 pass
415 pass
415 with tt.monkeypatch(interaction, 'display', record_display):
416 with tt.monkeypatch(interaction, 'display', record_display):
416 ifoo = interact(foo, a=10)
417 ifoo = interact(foo, a=10)
417 nt.assert_equal(len(displayed), 1)
418 nt.assert_equal(len(displayed), 1)
418 w = displayed[0].children[0]
419 w = displayed[0].children[0]
419 check_widget(w,
420 check_widget(w,
420 cls=widgets.IntSlider,
421 cls=widgets.IntSlider,
421 value=10,
422 value=10,
422 )
423 )
423
424
424 @nt.with_setup(clear_display)
425 @nt.with_setup(clear_display)
425 def test_call_decorated_on_trait_change():
426 def test_call_decorated_on_trait_change():
426 """test calling @interact decorated functions"""
427 """test calling @interact decorated functions"""
427 d = {}
428 d = {}
428 with tt.monkeypatch(interaction, 'display', record_display):
429 with tt.monkeypatch(interaction, 'display', record_display):
429 @interact
430 @interact
430 def foo(a='default'):
431 def foo(a='default'):
431 d['a'] = a
432 d['a'] = a
432 return a
433 return a
433 nt.assert_equal(len(displayed), 1)
434 nt.assert_equal(len(displayed), 1)
434 w = displayed[0].children[0]
435 w = displayed[0].children[0]
435 check_widget(w,
436 check_widget(w,
436 cls=widgets.Text,
437 cls=widgets.Text,
437 value='default',
438 value='default',
438 )
439 )
439 # test calling the function directly
440 # test calling the function directly
440 a = foo('hello')
441 a = foo('hello')
441 nt.assert_equal(a, 'hello')
442 nt.assert_equal(a, 'hello')
442 nt.assert_equal(d['a'], 'hello')
443 nt.assert_equal(d['a'], 'hello')
443
444
444 # test that setting trait values calls the function
445 # test that setting trait values calls the function
445 w.value = 'called'
446 w.value = 'called'
446 nt.assert_equal(d['a'], 'called')
447 nt.assert_equal(d['a'], 'called')
447
448
448 @nt.with_setup(clear_display)
449 @nt.with_setup(clear_display)
449 def test_call_decorated_kwargs_on_trait_change():
450 def test_call_decorated_kwargs_on_trait_change():
450 """test calling @interact(foo=bar) decorated functions"""
451 """test calling @interact(foo=bar) decorated functions"""
451 d = {}
452 d = {}
452 with tt.monkeypatch(interaction, 'display', record_display):
453 with tt.monkeypatch(interaction, 'display', record_display):
453 @interact(a='kwarg')
454 @interact(a='kwarg')
454 def foo(a='default'):
455 def foo(a='default'):
455 d['a'] = a
456 d['a'] = a
456 return a
457 return a
457 nt.assert_equal(len(displayed), 1)
458 nt.assert_equal(len(displayed), 1)
458 w = displayed[0].children[0]
459 w = displayed[0].children[0]
459 check_widget(w,
460 check_widget(w,
460 cls=widgets.Text,
461 cls=widgets.Text,
461 value='kwarg',
462 value='kwarg',
462 )
463 )
463 # test calling the function directly
464 # test calling the function directly
464 a = foo('hello')
465 a = foo('hello')
465 nt.assert_equal(a, 'hello')
466 nt.assert_equal(a, 'hello')
466 nt.assert_equal(d['a'], 'hello')
467 nt.assert_equal(d['a'], 'hello')
467
468
468 # test that setting trait values calls the function
469 # test that setting trait values calls the function
469 w.value = 'called'
470 w.value = 'called'
470 nt.assert_equal(d['a'], 'called')
471 nt.assert_equal(d['a'], 'called')
471
472
472 def test_fixed():
473 def test_fixed():
473 c = interactive(f, a=widgets.fixed(5), b='text')
474 c = interactive(f, a=widgets.fixed(5), b='text')
474 nt.assert_equal(len(c.children), 1)
475 nt.assert_equal(len(c.children), 2)
475 w = c.children[0]
476 w = c.children[0]
476 check_widget(w,
477 check_widget(w,
477 cls=widgets.Text,
478 cls=widgets.Text,
478 value='text',
479 value='text',
479 description='b',
480 description='b',
480 )
481 )
481
482
482 def test_default_description():
483 def test_default_description():
483 c = interactive(f, b='text')
484 c = interactive(f, b='text')
484 w = c.children[0]
485 w = c.children[0]
485 check_widget(w,
486 check_widget(w,
486 cls=widgets.Text,
487 cls=widgets.Text,
487 value='text',
488 value='text',
488 description='b',
489 description='b',
489 )
490 )
490
491
491 def test_custom_description():
492 def test_custom_description():
492 d = {}
493 d = {}
493 def record_kwargs(**kwargs):
494 def record_kwargs(**kwargs):
494 d.clear()
495 d.clear()
495 d.update(kwargs)
496 d.update(kwargs)
496
497
497 c = interactive(record_kwargs, b=widgets.Text(value='text', description='foo'))
498 c = interactive(record_kwargs, b=widgets.Text(value='text', description='foo'))
498 w = c.children[0]
499 w = c.children[0]
499 check_widget(w,
500 check_widget(w,
500 cls=widgets.Text,
501 cls=widgets.Text,
501 value='text',
502 value='text',
502 description='foo',
503 description='foo',
503 )
504 )
504 w.value = 'different text'
505 w.value = 'different text'
505 nt.assert_equal(d, {'b': 'different text'})
506 nt.assert_equal(d, {'b': 'different text'})
506
507
507 def test_interact_manual_button():
508 def test_interact_manual_button():
508 c = interactive(f, __manual=True)
509 c = interactive(f, __manual=True)
509 w = c.children[0]
510 w = c.children[0]
510 check_widget(w, cls=widgets.Button)
511 check_widget(w, cls=widgets.Button)
511
512
512 def test_interact_manual_nocall():
513 def test_interact_manual_nocall():
513 callcount = 0
514 callcount = 0
514 def calltest(testarg):
515 def calltest(testarg):
515 callcount += 1
516 callcount += 1
516 c = interactive(calltest, testarg=5, __manual=True)
517 c = interactive(calltest, testarg=5, __manual=True)
517 c.children[0].value = 10
518 c.children[0].value = 10
518 nt.assert_equal(callcount, 0)
519 nt.assert_equal(callcount, 0)
519
520
520 def test_int_range_logic():
521 def test_int_range_logic():
521 irsw = widgets.IntRangeSlider
522 irsw = widgets.IntRangeSlider
522 w = irsw(value=(2, 4), min=0, max=6)
523 w = irsw(value=(2, 4), min=0, max=6)
523 check_widget(w, cls=irsw, value=(2, 4), min=0, max=6)
524 check_widget(w, cls=irsw, value=(2, 4), min=0, max=6)
524 w.value = (4, 2)
525 w.value = (4, 2)
525 check_widget(w, cls=irsw, value=(2, 4), min=0, max=6)
526 check_widget(w, cls=irsw, value=(2, 4), min=0, max=6)
526 w.value = (-1, 7)
527 w.value = (-1, 7)
527 check_widget(w, cls=irsw, value=(0, 6), min=0, max=6)
528 check_widget(w, cls=irsw, value=(0, 6), min=0, max=6)
528 w.min = 3
529 w.min = 3
529 check_widget(w, cls=irsw, value=(3, 6), min=3, max=6)
530 check_widget(w, cls=irsw, value=(3, 6), min=3, max=6)
530 w.max = 3
531 w.max = 3
531 check_widget(w, cls=irsw, value=(3, 3), min=3, max=3)
532 check_widget(w, cls=irsw, value=(3, 3), min=3, max=3)
532
533
533 w.min = 0
534 w.min = 0
534 w.max = 6
535 w.max = 6
535 w.lower = 2
536 w.lower = 2
536 w.upper = 4
537 w.upper = 4
537 check_widget(w, cls=irsw, value=(2, 4), min=0, max=6)
538 check_widget(w, cls=irsw, value=(2, 4), min=0, max=6)
538 w.value = (0, 1) #lower non-overlapping range
539 w.value = (0, 1) #lower non-overlapping range
539 check_widget(w, cls=irsw, value=(0, 1), min=0, max=6)
540 check_widget(w, cls=irsw, value=(0, 1), min=0, max=6)
540 w.value = (5, 6) #upper non-overlapping range
541 w.value = (5, 6) #upper non-overlapping range
541 check_widget(w, cls=irsw, value=(5, 6), min=0, max=6)
542 check_widget(w, cls=irsw, value=(5, 6), min=0, max=6)
542 w.value = (-1, 4) #semi out-of-range
543 w.value = (-1, 4) #semi out-of-range
543 check_widget(w, cls=irsw, value=(0, 4), min=0, max=6)
544 check_widget(w, cls=irsw, value=(0, 4), min=0, max=6)
544 w.lower = 2
545 w.lower = 2
545 check_widget(w, cls=irsw, value=(2, 4), min=0, max=6)
546 check_widget(w, cls=irsw, value=(2, 4), min=0, max=6)
546 w.value = (-2, -1) #wholly out of range
547 w.value = (-2, -1) #wholly out of range
547 check_widget(w, cls=irsw, value=(0, 0), min=0, max=6)
548 check_widget(w, cls=irsw, value=(0, 0), min=0, max=6)
548 w.value = (7, 8)
549 w.value = (7, 8)
549 check_widget(w, cls=irsw, value=(6, 6), min=0, max=6)
550 check_widget(w, cls=irsw, value=(6, 6), min=0, max=6)
550
551
551 with nt.assert_raises(ValueError):
552 with nt.assert_raises(ValueError):
552 w.min = 7
553 w.min = 7
553 with nt.assert_raises(ValueError):
554 with nt.assert_raises(ValueError):
554 w.max = -1
555 w.max = -1
555 with nt.assert_raises(ValueError):
556 with nt.assert_raises(ValueError):
556 w.lower = 5
557 w.lower = 5
557 with nt.assert_raises(ValueError):
558 with nt.assert_raises(ValueError):
558 w.upper = 1
559 w.upper = 1
559
560
560 w = irsw(min=2, max=3)
561 w = irsw(min=2, max=3)
561 check_widget(w, min=2, max=3)
562 check_widget(w, min=2, max=3)
562 w = irsw(min=100, max=200)
563 w = irsw(min=100, max=200)
563 check_widget(w, lower=125, upper=175, value=(125, 175))
564 check_widget(w, lower=125, upper=175, value=(125, 175))
564
565
565 with nt.assert_raises(ValueError):
566 with nt.assert_raises(ValueError):
566 irsw(value=(2, 4), lower=3)
567 irsw(value=(2, 4), lower=3)
567 with nt.assert_raises(ValueError):
568 with nt.assert_raises(ValueError):
568 irsw(value=(2, 4), upper=3)
569 irsw(value=(2, 4), upper=3)
569 with nt.assert_raises(ValueError):
570 with nt.assert_raises(ValueError):
570 irsw(value=(2, 4), lower=3, upper=3)
571 irsw(value=(2, 4), lower=3, upper=3)
571 with nt.assert_raises(ValueError):
572 with nt.assert_raises(ValueError):
572 irsw(min=2, max=1)
573 irsw(min=2, max=1)
573 with nt.assert_raises(ValueError):
574 with nt.assert_raises(ValueError):
574 irsw(lower=5)
575 irsw(lower=5)
575 with nt.assert_raises(ValueError):
576 with nt.assert_raises(ValueError):
576 irsw(upper=5)
577 irsw(upper=5)
577
578
578
579
579 def test_float_range_logic():
580 def test_float_range_logic():
580 frsw = widgets.FloatRangeSlider
581 frsw = widgets.FloatRangeSlider
581 w = frsw(value=(.2, .4), min=0., max=.6)
582 w = frsw(value=(.2, .4), min=0., max=.6)
582 check_widget(w, cls=frsw, value=(.2, .4), min=0., max=.6)
583 check_widget(w, cls=frsw, value=(.2, .4), min=0., max=.6)
583 w.value = (.4, .2)
584 w.value = (.4, .2)
584 check_widget(w, cls=frsw, value=(.2, .4), min=0., max=.6)
585 check_widget(w, cls=frsw, value=(.2, .4), min=0., max=.6)
585 w.value = (-.1, .7)
586 w.value = (-.1, .7)
586 check_widget(w, cls=frsw, value=(0., .6), min=0., max=.6)
587 check_widget(w, cls=frsw, value=(0., .6), min=0., max=.6)
587 w.min = .3
588 w.min = .3
588 check_widget(w, cls=frsw, value=(.3, .6), min=.3, max=.6)
589 check_widget(w, cls=frsw, value=(.3, .6), min=.3, max=.6)
589 w.max = .3
590 w.max = .3
590 check_widget(w, cls=frsw, value=(.3, .3), min=.3, max=.3)
591 check_widget(w, cls=frsw, value=(.3, .3), min=.3, max=.3)
591
592
592 w.min = 0.
593 w.min = 0.
593 w.max = .6
594 w.max = .6
594 w.lower = .2
595 w.lower = .2
595 w.upper = .4
596 w.upper = .4
596 check_widget(w, cls=frsw, value=(.2, .4), min=0., max=.6)
597 check_widget(w, cls=frsw, value=(.2, .4), min=0., max=.6)
597 w.value = (0., .1) #lower non-overlapping range
598 w.value = (0., .1) #lower non-overlapping range
598 check_widget(w, cls=frsw, value=(0., .1), min=0., max=.6)
599 check_widget(w, cls=frsw, value=(0., .1), min=0., max=.6)
599 w.value = (.5, .6) #upper non-overlapping range
600 w.value = (.5, .6) #upper non-overlapping range
600 check_widget(w, cls=frsw, value=(.5, .6), min=0., max=.6)
601 check_widget(w, cls=frsw, value=(.5, .6), min=0., max=.6)
601 w.value = (-.1, .4) #semi out-of-range
602 w.value = (-.1, .4) #semi out-of-range
602 check_widget(w, cls=frsw, value=(0., .4), min=0., max=.6)
603 check_widget(w, cls=frsw, value=(0., .4), min=0., max=.6)
603 w.lower = .2
604 w.lower = .2
604 check_widget(w, cls=frsw, value=(.2, .4), min=0., max=.6)
605 check_widget(w, cls=frsw, value=(.2, .4), min=0., max=.6)
605 w.value = (-.2, -.1) #wholly out of range
606 w.value = (-.2, -.1) #wholly out of range
606 check_widget(w, cls=frsw, value=(0., 0.), min=0., max=.6)
607 check_widget(w, cls=frsw, value=(0., 0.), min=0., max=.6)
607 w.value = (.7, .8)
608 w.value = (.7, .8)
608 check_widget(w, cls=frsw, value=(.6, .6), min=.0, max=.6)
609 check_widget(w, cls=frsw, value=(.6, .6), min=.0, max=.6)
609
610
610 with nt.assert_raises(ValueError):
611 with nt.assert_raises(ValueError):
611 w.min = .7
612 w.min = .7
612 with nt.assert_raises(ValueError):
613 with nt.assert_raises(ValueError):
613 w.max = -.1
614 w.max = -.1
614 with nt.assert_raises(ValueError):
615 with nt.assert_raises(ValueError):
615 w.lower = .5
616 w.lower = .5
616 with nt.assert_raises(ValueError):
617 with nt.assert_raises(ValueError):
617 w.upper = .1
618 w.upper = .1
618
619
619 w = frsw(min=2, max=3)
620 w = frsw(min=2, max=3)
620 check_widget(w, min=2, max=3)
621 check_widget(w, min=2, max=3)
621 w = frsw(min=1., max=2.)
622 w = frsw(min=1., max=2.)
622 check_widget(w, lower=1.25, upper=1.75, value=(1.25, 1.75))
623 check_widget(w, lower=1.25, upper=1.75, value=(1.25, 1.75))
623
624
624 with nt.assert_raises(ValueError):
625 with nt.assert_raises(ValueError):
625 frsw(value=(2, 4), lower=3)
626 frsw(value=(2, 4), lower=3)
626 with nt.assert_raises(ValueError):
627 with nt.assert_raises(ValueError):
627 frsw(value=(2, 4), upper=3)
628 frsw(value=(2, 4), upper=3)
628 with nt.assert_raises(ValueError):
629 with nt.assert_raises(ValueError):
629 frsw(value=(2, 4), lower=3, upper=3)
630 frsw(value=(2, 4), lower=3, upper=3)
630 with nt.assert_raises(ValueError):
631 with nt.assert_raises(ValueError):
631 frsw(min=.2, max=.1)
632 frsw(min=.2, max=.1)
632 with nt.assert_raises(ValueError):
633 with nt.assert_raises(ValueError):
633 frsw(lower=5)
634 frsw(lower=5)
634 with nt.assert_raises(ValueError):
635 with nt.assert_raises(ValueError):
635 frsw(upper=5)
636 frsw(upper=5)
@@ -1,78 +1,88 b''
1 """Output class.
1 """Output class.
2
2
3 Represents a widget that can be used to display output within the widget area.
3 Represents a widget that can be used to display output within the widget area.
4 """
4 """
5
5
6 # Copyright (c) IPython Development Team.
6 # Copyright (c) IPython Development Team.
7 # Distributed under the terms of the Modified BSD License.
7 # Distributed under the terms of the Modified BSD License.
8
8
9 from .widget import DOMWidget
9 from .widget import DOMWidget
10 import sys
10 import sys
11 from IPython.utils.traitlets import Unicode, List
11 from IPython.utils.traitlets import Unicode, List
12 from IPython.display import clear_output
12 from IPython.display import clear_output
13 from IPython.testing.skipdoctest import skip_doctest
13 from IPython.testing.skipdoctest import skip_doctest
14 from IPython.kernel.zmq.session import Message
14 from IPython.kernel.zmq.session import Message
15
15
16 @skip_doctest
16 @skip_doctest
17 class Output(DOMWidget):
17 class Output(DOMWidget):
18 """Widget used as a context manager to display output.
18 """Widget used as a context manager to display output.
19
19
20 This widget can capture and display stdout, stderr, and rich output. To use
20 This widget can capture and display stdout, stderr, and rich output. To use
21 it, create an instance of it and display it. Then use it as a context
21 it, create an instance of it and display it. Then use it as a context
22 manager. Any output produced while in it's context will be captured and
22 manager. Any output produced while in it's context will be captured and
23 displayed in it instead of the standard output area.
23 displayed in it instead of the standard output area.
24
24
25 Example
25 Example
26 from IPython.html import widgets
26 from IPython.html import widgets
27 from IPython.display import display
27 from IPython.display import display
28 out = widgets.Output()
28 out = widgets.Output()
29 display(out)
29 display(out)
30
30
31 print('prints to output area')
31 print('prints to output area')
32
32
33 with out:
33 with out:
34 print('prints to output widget')"""
34 print('prints to output widget')"""
35 _view_name = Unicode('OutputView', sync=True)
35 _view_name = Unicode('OutputView', sync=True)
36
36
37 def __init__(self, *args, **kwargs):
38 super(Output, self).__init__(*args, **kwargs)
39 from IPython import get_ipython
40 ip = get_ipython()
41 if ip is not None and hasattr(ip, 'kernel'):
42 self._kernel = ip.kernel
43 else:
44 self._kernel = None
45
37 def clear_output(self, *pargs, **kwargs):
46 def clear_output(self, *pargs, **kwargs):
38 with self:
47 with self:
39 clear_output(*pargs, **kwargs)
48 clear_output(*pargs, **kwargs)
40
49
41 def __enter__(self):
50 def __enter__(self):
42 """Called upon entering output widget context manager."""
51 """Called upon entering output widget context manager."""
43 self._flush()
52 if self._kernel is not None:
44 kernel = get_ipython().kernel
53 self._flush()
45 session = kernel.session
54 session = self._kernel.session
46 send = session.send
55 send = session.send
47 self._original_send = send
56 self._original_send = send
48 self._session = session
57 self._session = session
49
58
50 def send_hook(stream, msg_or_type, content=None, parent=None, ident=None,
59 def send_hook(stream, msg_or_type, content=None, parent=None, ident=None,
51 buffers=None, track=False, header=None, metadata=None):
60 buffers=None, track=False, header=None, metadata=None):
52
61
53 # Handle both prebuild messages and unbuilt messages.
62 # Handle both prebuild messages and unbuilt messages.
54 if isinstance(msg_or_type, (Message, dict)):
63 if isinstance(msg_or_type, (Message, dict)):
55 msg_type = msg_or_type['msg_type']
64 msg_type = msg_or_type['msg_type']
56 msg = dict(msg_or_type)
65 msg = dict(msg_or_type)
57 else:
66 else:
58 msg_type = msg_or_type
67 msg_type = msg_or_type
59 msg = session.msg(msg_type, content=content, parent=parent,
68 msg = session.msg(msg_type, content=content, parent=parent,
60 header=header, metadata=metadata)
69 header=header, metadata=metadata)
61
70
62 # If this is a message type that we want to forward, forward it.
71 # If this is a message type that we want to forward, forward it.
63 if stream is kernel.iopub_socket and msg_type in ['clear_output', 'stream', 'display_data']:
72 if stream is self._kernel.iopub_socket and msg_type in ['clear_output', 'stream', 'display_data']:
64 self.send(msg)
73 self.send(msg)
65 else:
74 else:
66 send(stream, msg, ident=ident, buffers=buffers, track=track)
75 send(stream, msg, ident=ident, buffers=buffers, track=track)
67
76
68 session.send = send_hook
77 session.send = send_hook
69
78
70 def __exit__(self, exception_type, exception_value, traceback):
79 def __exit__(self, exception_type, exception_value, traceback):
71 """Called upon exiting output widget context manager."""
80 """Called upon exiting output widget context manager."""
72 self._flush()
81 if self._kernel is not None:
73 self._session.send = self._original_send
82 self._flush()
83 self._session.send = self._original_send
74
84
75 def _flush(self):
85 def _flush(self):
76 """Flush stdout and stderr buffers."""
86 """Flush stdout and stderr buffers."""
77 sys.stdout.flush()
87 sys.stdout.flush()
78 sys.stderr.flush()
88 sys.stderr.flush()
General Comments 0
You need to be logged in to leave comments. Login now