##// END OF EJS Templates
Merge pull request #7703 from ipython/revert-7554-interact-fix...
Min RK -
r20366:9b8fb88a merge
parent child Browse files
Show More
@@ -1,349 +1,344 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, Output)
17 Box, Button, DOMWidget)
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(options=o)
62 return Dropdown(options=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(options=[unicode_type(k) for k in o])
79 return Dropdown(options=[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)
211 container.children = c
207 container.children = c
212
208
213 # Build the callback
209 # Build the callback
214 def call_f(name=None, old=None, new=None):
210 def call_f(name=None, old=None, new=None):
215 with output:
211 container.kwargs = {}
216 container.kwargs = {}
212 for widget in kwargs_widgets:
217 for widget in kwargs_widgets:
213 value = widget.value
218 value = widget.value
214 container.kwargs[widget._kwarg] = value
219 container.kwargs[widget._kwarg] = value
215 if co:
220 if co:
216 clear_output(wait=True)
221 clear_output(wait=True)
217 if manual:
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:
222 if manual:
228 if manual:
223 manual_button.disabled = True
229 manual_button.disabled = False
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
235
230
236 # Wire up the widgets
231 # Wire up the widgets
237 # If we are doing manual running, the callback is only triggered by the button
232 # If we are doing manual running, the callback is only triggered by the button
238 # Otherwise, it is triggered for every trait change received
233 # Otherwise, it is triggered for every trait change received
239 # On-demand running also suppresses running the function with the initial parameters
234 # On-demand running also suppresses running the function with the initial parameters
240 if manual:
235 if manual:
241 manual_button.on_click(call_f)
236 manual_button.on_click(call_f)
242 else:
237 else:
243 for widget in kwargs_widgets:
238 for widget in kwargs_widgets:
244 widget.on_trait_change(call_f, 'value')
239 widget.on_trait_change(call_f, 'value')
245
240
246 container.on_displayed(lambda _: call_f(None, None, None))
241 container.on_displayed(lambda _: call_f(None, None, None))
247
242
248 return container
243 return container
249
244
250 def interact(__interact_f=None, **kwargs):
245 def interact(__interact_f=None, **kwargs):
251 """
246 """
252 Displays interactive widgets which are tied to a function.
247 Displays interactive widgets which are tied to a function.
253 Expects the first argument to be a function. Parameters to this function are
248 Expects the first argument to be a function. Parameters to this function are
254 widget abbreviations passed in as keyword arguments (**kwargs). Can be used
249 widget abbreviations passed in as keyword arguments (**kwargs). Can be used
255 as a decorator (see examples).
250 as a decorator (see examples).
256
251
257 Returns
252 Returns
258 -------
253 -------
259 f : __interact_f with interactive widget attached to it.
254 f : __interact_f with interactive widget attached to it.
260
255
261 Parameters
256 Parameters
262 ----------
257 ----------
263 __interact_f : function
258 __interact_f : function
264 The function to which the interactive widgets are tied. The **kwargs
259 The function to which the interactive widgets are tied. The **kwargs
265 should match the function signature. Passed to :func:`interactive()`
260 should match the function signature. Passed to :func:`interactive()`
266 **kwargs : various, optional
261 **kwargs : various, optional
267 An interactive widget is created for each keyword argument that is a
262 An interactive widget is created for each keyword argument that is a
268 valid widget abbreviation. Passed to :func:`interactive()`
263 valid widget abbreviation. Passed to :func:`interactive()`
269
264
270 Examples
265 Examples
271 --------
266 --------
272 Renders an interactive text field that shows the greeting with the passed in
267 Renders an interactive text field that shows the greeting with the passed in
273 text.
268 text.
274
269
275 1. Invocation of interact as a function
270 1. Invocation of interact as a function
276 def greeting(text="World"):
271 def greeting(text="World"):
277 print "Hello {}".format(text)
272 print "Hello {}".format(text)
278 interact(greeting, text="IPython Widgets")
273 interact(greeting, text="IPython Widgets")
279
274
280 2. Invocation of interact as a decorator
275 2. Invocation of interact as a decorator
281 @interact
276 @interact
282 def greeting(text="World"):
277 def greeting(text="World"):
283 print "Hello {}".format(text)
278 print "Hello {}".format(text)
284
279
285 3. Invocation of interact as a decorator with named parameters
280 3. Invocation of interact as a decorator with named parameters
286 @interact(text="IPython Widgets")
281 @interact(text="IPython Widgets")
287 def greeting(text="World"):
282 def greeting(text="World"):
288 print "Hello {}".format(text)
283 print "Hello {}".format(text)
289
284
290 Renders an interactive slider widget and prints square of number.
285 Renders an interactive slider widget and prints square of number.
291
286
292 1. Invocation of interact as a function
287 1. Invocation of interact as a function
293 def square(num=1):
288 def square(num=1):
294 print "{} squared is {}".format(num, num*num)
289 print "{} squared is {}".format(num, num*num)
295 interact(square, num=5)
290 interact(square, num=5)
296
291
297 2. Invocation of interact as a decorator
292 2. Invocation of interact as a decorator
298 @interact
293 @interact
299 def square(num=2):
294 def square(num=2):
300 print "{} squared is {}".format(num, num*num)
295 print "{} squared is {}".format(num, num*num)
301
296
302 3. Invocation of interact as a decorator with named parameters
297 3. Invocation of interact as a decorator with named parameters
303 @interact(num=5)
298 @interact(num=5)
304 def square(num=2):
299 def square(num=2):
305 print "{} squared is {}".format(num, num*num)
300 print "{} squared is {}".format(num, num*num)
306 """
301 """
307 # positional arg support in: https://gist.github.com/8851331
302 # positional arg support in: https://gist.github.com/8851331
308 if __interact_f is not None:
303 if __interact_f is not None:
309 # This branch handles the cases 1 and 2
304 # This branch handles the cases 1 and 2
310 # 1. interact(f, **kwargs)
305 # 1. interact(f, **kwargs)
311 # 2. @interact
306 # 2. @interact
312 # def f(*args, **kwargs):
307 # def f(*args, **kwargs):
313 # ...
308 # ...
314 f = __interact_f
309 f = __interact_f
315 w = interactive(f, **kwargs)
310 w = interactive(f, **kwargs)
316 try:
311 try:
317 f.widget = w
312 f.widget = w
318 except AttributeError:
313 except AttributeError:
319 # some things (instancemethods) can't have attributes attached,
314 # some things (instancemethods) can't have attributes attached,
320 # so wrap in a lambda
315 # so wrap in a lambda
321 f = lambda *args, **kwargs: __interact_f(*args, **kwargs)
316 f = lambda *args, **kwargs: __interact_f(*args, **kwargs)
322 f.widget = w
317 f.widget = w
323 display(w)
318 display(w)
324 return f
319 return f
325 else:
320 else:
326 # This branch handles the case 3
321 # This branch handles the case 3
327 # @interact(a=30, b=40)
322 # @interact(a=30, b=40)
328 # def f(*args, **kwargs):
323 # def f(*args, **kwargs):
329 # ...
324 # ...
330 def dec(f):
325 def dec(f):
331 return interact(f, **kwargs)
326 return interact(f, **kwargs)
332 return dec
327 return dec
333
328
334 def interact_manual(__interact_f=None, **kwargs):
329 def interact_manual(__interact_f=None, **kwargs):
335 """interact_manual(f, **kwargs)
330 """interact_manual(f, **kwargs)
336
331
337 As `interact()`, generates widgets for each argument, but rather than running
332 As `interact()`, generates widgets for each argument, but rather than running
338 the function after each widget change, adds a "Run" button and waits for it
333 the function after each widget change, adds a "Run" button and waits for it
339 to be clicked. Useful if the function is long-running and has several
334 to be clicked. Useful if the function is long-running and has several
340 parameters to change.
335 parameters to change.
341 """
336 """
342 return interact(__interact_f, __manual=True, **kwargs)
337 return interact(__interact_f, __manual=True, **kwargs)
343
338
344 class fixed(HasTraits):
339 class fixed(HasTraits):
345 """A pseudo-widget whose value is fixed and never synced to the client."""
340 """A pseudo-widget whose value is fixed and never synced to the client."""
346 value = Any(help="Any Python object")
341 value = Any(help="Any Python object")
347 description = Unicode('', help="Any Python object")
342 description = Unicode('', help="Any Python object")
348 def __init__(self, value, **kwargs):
343 def __init__(self, value, **kwargs):
349 super(fixed, self).__init__(value=value, **kwargs)
344 super(fixed, self).__init__(value=value, **kwargs)
@@ -1,692 +1,691 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 if hasattr(w, 'description'):
83 widgets[w.description] = w
84 widgets[w.description] = w
85
84
86 for key, d in to_check.items():
85 for key, d in to_check.items():
87 nt.assert_in(key, widgets)
86 nt.assert_in(key, widgets)
88 check_widget(widgets[key], **d)
87 check_widget(widgets[key], **d)
89
88
90
89
91 def test_single_value_string():
90 def test_single_value_string():
92 a = u'hello'
91 a = u'hello'
93 c = interactive(f, a=a)
92 c = interactive(f, a=a)
94 w = c.children[0]
93 w = c.children[0]
95 check_widget(w,
94 check_widget(w,
96 cls=widgets.Text,
95 cls=widgets.Text,
97 description='a',
96 description='a',
98 value=a,
97 value=a,
99 )
98 )
100
99
101 def test_single_value_bool():
100 def test_single_value_bool():
102 for a in (True, False):
101 for a in (True, False):
103 c = interactive(f, a=a)
102 c = interactive(f, a=a)
104 w = c.children[0]
103 w = c.children[0]
105 check_widget(w,
104 check_widget(w,
106 cls=widgets.Checkbox,
105 cls=widgets.Checkbox,
107 description='a',
106 description='a',
108 value=a,
107 value=a,
109 )
108 )
110
109
111 def test_single_value_dict():
110 def test_single_value_dict():
112 for d in [
111 for d in [
113 dict(a=5),
112 dict(a=5),
114 dict(a=5, b='b', c=dict),
113 dict(a=5, b='b', c=dict),
115 ]:
114 ]:
116 c = interactive(f, d=d)
115 c = interactive(f, d=d)
117 w = c.children[0]
116 w = c.children[0]
118 check_widget(w,
117 check_widget(w,
119 cls=widgets.Dropdown,
118 cls=widgets.Dropdown,
120 description='d',
119 description='d',
121 options=d,
120 options=d,
122 value=next(iter(d.values())),
121 value=next(iter(d.values())),
123 )
122 )
124
123
125 def test_single_value_float():
124 def test_single_value_float():
126 for a in (2.25, 1.0, -3.5):
125 for a in (2.25, 1.0, -3.5):
127 c = interactive(f, a=a)
126 c = interactive(f, a=a)
128 w = c.children[0]
127 w = c.children[0]
129 check_widget(w,
128 check_widget(w,
130 cls=widgets.FloatSlider,
129 cls=widgets.FloatSlider,
131 description='a',
130 description='a',
132 value=a,
131 value=a,
133 min= -a if a > 0 else 3*a,
132 min= -a if a > 0 else 3*a,
134 max= 3*a if a > 0 else -a,
133 max= 3*a if a > 0 else -a,
135 step=0.1,
134 step=0.1,
136 readout=True,
135 readout=True,
137 )
136 )
138
137
139 def test_single_value_int():
138 def test_single_value_int():
140 for a in (1, 5, -3):
139 for a in (1, 5, -3):
141 c = interactive(f, a=a)
140 c = interactive(f, a=a)
142 nt.assert_equal(len(c.children), 2)
141 nt.assert_equal(len(c.children), 1)
143 w = c.children[0]
142 w = c.children[0]
144 check_widget(w,
143 check_widget(w,
145 cls=widgets.IntSlider,
144 cls=widgets.IntSlider,
146 description='a',
145 description='a',
147 value=a,
146 value=a,
148 min= -a if a > 0 else 3*a,
147 min= -a if a > 0 else 3*a,
149 max= 3*a if a > 0 else -a,
148 max= 3*a if a > 0 else -a,
150 step=1,
149 step=1,
151 readout=True,
150 readout=True,
152 )
151 )
153
152
154 def test_list_tuple_2_int():
153 def test_list_tuple_2_int():
155 with nt.assert_raises(ValueError):
154 with nt.assert_raises(ValueError):
156 c = interactive(f, tup=(1,1))
155 c = interactive(f, tup=(1,1))
157 with nt.assert_raises(ValueError):
156 with nt.assert_raises(ValueError):
158 c = interactive(f, tup=(1,-1))
157 c = interactive(f, tup=(1,-1))
159 for min, max in [ (0,1), (1,10), (1,2), (-5,5), (-20,-19) ]:
158 for min, max in [ (0,1), (1,10), (1,2), (-5,5), (-20,-19) ]:
160 c = interactive(f, tup=(min, max), lis=[min, max])
159 c = interactive(f, tup=(min, max), lis=[min, max])
161 nt.assert_equal(len(c.children), 3)
160 nt.assert_equal(len(c.children), 2)
162 d = dict(
161 d = dict(
163 cls=widgets.IntSlider,
162 cls=widgets.IntSlider,
164 min=min,
163 min=min,
165 max=max,
164 max=max,
166 step=1,
165 step=1,
167 readout=True,
166 readout=True,
168 )
167 )
169 check_widgets(c, tup=d, lis=d)
168 check_widgets(c, tup=d, lis=d)
170
169
171 def test_list_tuple_3_int():
170 def test_list_tuple_3_int():
172 with nt.assert_raises(ValueError):
171 with nt.assert_raises(ValueError):
173 c = interactive(f, tup=(1,2,0))
172 c = interactive(f, tup=(1,2,0))
174 with nt.assert_raises(ValueError):
173 with nt.assert_raises(ValueError):
175 c = interactive(f, tup=(1,2,-1))
174 c = interactive(f, tup=(1,2,-1))
176 for min, max, step in [ (0,2,1), (1,10,2), (1,100,2), (-5,5,4), (-100,-20,4) ]:
175 for min, max, step in [ (0,2,1), (1,10,2), (1,100,2), (-5,5,4), (-100,-20,4) ]:
177 c = interactive(f, tup=(min, max, step), lis=[min, max, step])
176 c = interactive(f, tup=(min, max, step), lis=[min, max, step])
178 nt.assert_equal(len(c.children), 3)
177 nt.assert_equal(len(c.children), 2)
179 d = dict(
178 d = dict(
180 cls=widgets.IntSlider,
179 cls=widgets.IntSlider,
181 min=min,
180 min=min,
182 max=max,
181 max=max,
183 step=step,
182 step=step,
184 readout=True,
183 readout=True,
185 )
184 )
186 check_widgets(c, tup=d, lis=d)
185 check_widgets(c, tup=d, lis=d)
187
186
188 def test_list_tuple_2_float():
187 def test_list_tuple_2_float():
189 with nt.assert_raises(ValueError):
188 with nt.assert_raises(ValueError):
190 c = interactive(f, tup=(1.0,1.0))
189 c = interactive(f, tup=(1.0,1.0))
191 with nt.assert_raises(ValueError):
190 with nt.assert_raises(ValueError):
192 c = interactive(f, tup=(0.5,-0.5))
191 c = interactive(f, tup=(0.5,-0.5))
193 for min, max in [ (0.5, 1.5), (1.1,10.2), (1,2.2), (-5.,5), (-20,-19.) ]:
192 for min, max in [ (0.5, 1.5), (1.1,10.2), (1,2.2), (-5.,5), (-20,-19.) ]:
194 c = interactive(f, tup=(min, max), lis=[min, max])
193 c = interactive(f, tup=(min, max), lis=[min, max])
195 nt.assert_equal(len(c.children), 3)
194 nt.assert_equal(len(c.children), 2)
196 d = dict(
195 d = dict(
197 cls=widgets.FloatSlider,
196 cls=widgets.FloatSlider,
198 min=min,
197 min=min,
199 max=max,
198 max=max,
200 step=.1,
199 step=.1,
201 readout=True,
200 readout=True,
202 )
201 )
203 check_widgets(c, tup=d, lis=d)
202 check_widgets(c, tup=d, lis=d)
204
203
205 def test_list_tuple_3_float():
204 def test_list_tuple_3_float():
206 with nt.assert_raises(ValueError):
205 with nt.assert_raises(ValueError):
207 c = interactive(f, tup=(1,2,0.0))
206 c = interactive(f, tup=(1,2,0.0))
208 with nt.assert_raises(ValueError):
207 with nt.assert_raises(ValueError):
209 c = interactive(f, tup=(-1,-2,1.))
208 c = interactive(f, tup=(-1,-2,1.))
210 with nt.assert_raises(ValueError):
209 with nt.assert_raises(ValueError):
211 c = interactive(f, tup=(1,2.,-1.))
210 c = interactive(f, tup=(1,2.,-1.))
212 for min, max, step in [ (0.,2,1), (1,10.,2), (1,100,2.), (-5.,5.,4), (-100,-20.,4.) ]:
211 for min, max, step in [ (0.,2,1), (1,10.,2), (1,100,2.), (-5.,5.,4), (-100,-20.,4.) ]:
213 c = interactive(f, tup=(min, max, step), lis=[min, max, step])
212 c = interactive(f, tup=(min, max, step), lis=[min, max, step])
214 nt.assert_equal(len(c.children), 3)
213 nt.assert_equal(len(c.children), 2)
215 d = dict(
214 d = dict(
216 cls=widgets.FloatSlider,
215 cls=widgets.FloatSlider,
217 min=min,
216 min=min,
218 max=max,
217 max=max,
219 step=step,
218 step=step,
220 readout=True,
219 readout=True,
221 )
220 )
222 check_widgets(c, tup=d, lis=d)
221 check_widgets(c, tup=d, lis=d)
223
222
224 def test_list_tuple_str():
223 def test_list_tuple_str():
225 values = ['hello', 'there', 'guy']
224 values = ['hello', 'there', 'guy']
226 first = values[0]
225 first = values[0]
227 c = interactive(f, tup=tuple(values), lis=list(values))
226 c = interactive(f, tup=tuple(values), lis=list(values))
228 nt.assert_equal(len(c.children), 3)
227 nt.assert_equal(len(c.children), 2)
229 d = dict(
228 d = dict(
230 cls=widgets.Dropdown,
229 cls=widgets.Dropdown,
231 value=first,
230 value=first,
232 options=values
231 options=values
233 )
232 )
234 check_widgets(c, tup=d, lis=d)
233 check_widgets(c, tup=d, lis=d)
235
234
236 def test_list_tuple_invalid():
235 def test_list_tuple_invalid():
237 for bad in [
236 for bad in [
238 (),
237 (),
239 (5, 'hi'),
238 (5, 'hi'),
240 ('hi', 5),
239 ('hi', 5),
241 ({},),
240 ({},),
242 (None,),
241 (None,),
243 ]:
242 ]:
244 with nt.assert_raises(ValueError):
243 with nt.assert_raises(ValueError):
245 print(bad) # because there is no custom message in assert_raises
244 print(bad) # because there is no custom message in assert_raises
246 c = interactive(f, tup=bad)
245 c = interactive(f, tup=bad)
247
246
248 def test_defaults():
247 def test_defaults():
249 @annotate(n=10)
248 @annotate(n=10)
250 def f(n, f=4.5, g=1):
249 def f(n, f=4.5, g=1):
251 pass
250 pass
252
251
253 c = interactive(f)
252 c = interactive(f)
254 check_widgets(c,
253 check_widgets(c,
255 n=dict(
254 n=dict(
256 cls=widgets.IntSlider,
255 cls=widgets.IntSlider,
257 value=10,
256 value=10,
258 ),
257 ),
259 f=dict(
258 f=dict(
260 cls=widgets.FloatSlider,
259 cls=widgets.FloatSlider,
261 value=4.5,
260 value=4.5,
262 ),
261 ),
263 g=dict(
262 g=dict(
264 cls=widgets.IntSlider,
263 cls=widgets.IntSlider,
265 value=1,
264 value=1,
266 ),
265 ),
267 )
266 )
268
267
269 def test_default_values():
268 def test_default_values():
270 @annotate(n=10, f=(0, 10.), g=5, h={'a': 1, 'b': 2}, j=['hi', 'there'])
269 @annotate(n=10, f=(0, 10.), g=5, h={'a': 1, 'b': 2}, j=['hi', 'there'])
271 def f(n, f=4.5, g=1, h=2, j='there'):
270 def f(n, f=4.5, g=1, h=2, j='there'):
272 pass
271 pass
273
272
274 c = interactive(f)
273 c = interactive(f)
275 check_widgets(c,
274 check_widgets(c,
276 n=dict(
275 n=dict(
277 cls=widgets.IntSlider,
276 cls=widgets.IntSlider,
278 value=10,
277 value=10,
279 ),
278 ),
280 f=dict(
279 f=dict(
281 cls=widgets.FloatSlider,
280 cls=widgets.FloatSlider,
282 value=4.5,
281 value=4.5,
283 ),
282 ),
284 g=dict(
283 g=dict(
285 cls=widgets.IntSlider,
284 cls=widgets.IntSlider,
286 value=5,
285 value=5,
287 ),
286 ),
288 h=dict(
287 h=dict(
289 cls=widgets.Dropdown,
288 cls=widgets.Dropdown,
290 options={'a': 1, 'b': 2},
289 options={'a': 1, 'b': 2},
291 value=2
290 value=2
292 ),
291 ),
293 j=dict(
292 j=dict(
294 cls=widgets.Dropdown,
293 cls=widgets.Dropdown,
295 options=['hi', 'there'],
294 options=['hi', 'there'],
296 value='there'
295 value='there'
297 ),
296 ),
298 )
297 )
299
298
300 def test_default_out_of_bounds():
299 def test_default_out_of_bounds():
301 @annotate(f=(0, 10.), h={'a': 1}, j=['hi', 'there'])
300 @annotate(f=(0, 10.), h={'a': 1}, j=['hi', 'there'])
302 def f(f='hi', h=5, j='other'):
301 def f(f='hi', h=5, j='other'):
303 pass
302 pass
304
303
305 c = interactive(f)
304 c = interactive(f)
306 check_widgets(c,
305 check_widgets(c,
307 f=dict(
306 f=dict(
308 cls=widgets.FloatSlider,
307 cls=widgets.FloatSlider,
309 value=5.,
308 value=5.,
310 ),
309 ),
311 h=dict(
310 h=dict(
312 cls=widgets.Dropdown,
311 cls=widgets.Dropdown,
313 options={'a': 1},
312 options={'a': 1},
314 value=1,
313 value=1,
315 ),
314 ),
316 j=dict(
315 j=dict(
317 cls=widgets.Dropdown,
316 cls=widgets.Dropdown,
318 options=['hi', 'there'],
317 options=['hi', 'there'],
319 value='hi',
318 value='hi',
320 ),
319 ),
321 )
320 )
322
321
323 def test_annotations():
322 def test_annotations():
324 @annotate(n=10, f=widgets.FloatText())
323 @annotate(n=10, f=widgets.FloatText())
325 def f(n, f):
324 def f(n, f):
326 pass
325 pass
327
326
328 c = interactive(f)
327 c = interactive(f)
329 check_widgets(c,
328 check_widgets(c,
330 n=dict(
329 n=dict(
331 cls=widgets.IntSlider,
330 cls=widgets.IntSlider,
332 value=10,
331 value=10,
333 ),
332 ),
334 f=dict(
333 f=dict(
335 cls=widgets.FloatText,
334 cls=widgets.FloatText,
336 ),
335 ),
337 )
336 )
338
337
339 def test_priority():
338 def test_priority():
340 @annotate(annotate='annotate', kwarg='annotate')
339 @annotate(annotate='annotate', kwarg='annotate')
341 def f(kwarg='default', annotate='default', default='default'):
340 def f(kwarg='default', annotate='default', default='default'):
342 pass
341 pass
343
342
344 c = interactive(f, kwarg='kwarg')
343 c = interactive(f, kwarg='kwarg')
345 check_widgets(c,
344 check_widgets(c,
346 kwarg=dict(
345 kwarg=dict(
347 cls=widgets.Text,
346 cls=widgets.Text,
348 value='kwarg',
347 value='kwarg',
349 ),
348 ),
350 annotate=dict(
349 annotate=dict(
351 cls=widgets.Text,
350 cls=widgets.Text,
352 value='annotate',
351 value='annotate',
353 ),
352 ),
354 )
353 )
355
354
356 @nt.with_setup(clear_display)
355 @nt.with_setup(clear_display)
357 def test_decorator_kwarg():
356 def test_decorator_kwarg():
358 with tt.monkeypatch(interaction, 'display', record_display):
357 with tt.monkeypatch(interaction, 'display', record_display):
359 @interact(a=5)
358 @interact(a=5)
360 def foo(a):
359 def foo(a):
361 pass
360 pass
362 nt.assert_equal(len(displayed), 1)
361 nt.assert_equal(len(displayed), 1)
363 w = displayed[0].children[0]
362 w = displayed[0].children[0]
364 check_widget(w,
363 check_widget(w,
365 cls=widgets.IntSlider,
364 cls=widgets.IntSlider,
366 value=5,
365 value=5,
367 )
366 )
368
367
369 @nt.with_setup(clear_display)
368 @nt.with_setup(clear_display)
370 def test_interact_instancemethod():
369 def test_interact_instancemethod():
371 class Foo(object):
370 class Foo(object):
372 def show(self, x):
371 def show(self, x):
373 print(x)
372 print(x)
374
373
375 f = Foo()
374 f = Foo()
376
375
377 with tt.monkeypatch(interaction, 'display', record_display):
376 with tt.monkeypatch(interaction, 'display', record_display):
378 g = interact(f.show, x=(1,10))
377 g = interact(f.show, x=(1,10))
379 nt.assert_equal(len(displayed), 1)
378 nt.assert_equal(len(displayed), 1)
380 w = displayed[0].children[0]
379 w = displayed[0].children[0]
381 check_widget(w,
380 check_widget(w,
382 cls=widgets.IntSlider,
381 cls=widgets.IntSlider,
383 value=5,
382 value=5,
384 )
383 )
385
384
386 @nt.with_setup(clear_display)
385 @nt.with_setup(clear_display)
387 def test_decorator_no_call():
386 def test_decorator_no_call():
388 with tt.monkeypatch(interaction, 'display', record_display):
387 with tt.monkeypatch(interaction, 'display', record_display):
389 @interact
388 @interact
390 def foo(a='default'):
389 def foo(a='default'):
391 pass
390 pass
392 nt.assert_equal(len(displayed), 1)
391 nt.assert_equal(len(displayed), 1)
393 w = displayed[0].children[0]
392 w = displayed[0].children[0]
394 check_widget(w,
393 check_widget(w,
395 cls=widgets.Text,
394 cls=widgets.Text,
396 value='default',
395 value='default',
397 )
396 )
398
397
399 @nt.with_setup(clear_display)
398 @nt.with_setup(clear_display)
400 def test_call_interact():
399 def test_call_interact():
401 def foo(a='default'):
400 def foo(a='default'):
402 pass
401 pass
403 with tt.monkeypatch(interaction, 'display', record_display):
402 with tt.monkeypatch(interaction, 'display', record_display):
404 ifoo = interact(foo)
403 ifoo = interact(foo)
405 nt.assert_equal(len(displayed), 1)
404 nt.assert_equal(len(displayed), 1)
406 w = displayed[0].children[0]
405 w = displayed[0].children[0]
407 check_widget(w,
406 check_widget(w,
408 cls=widgets.Text,
407 cls=widgets.Text,
409 value='default',
408 value='default',
410 )
409 )
411
410
412 @nt.with_setup(clear_display)
411 @nt.with_setup(clear_display)
413 def test_call_interact_kwargs():
412 def test_call_interact_kwargs():
414 def foo(a='default'):
413 def foo(a='default'):
415 pass
414 pass
416 with tt.monkeypatch(interaction, 'display', record_display):
415 with tt.monkeypatch(interaction, 'display', record_display):
417 ifoo = interact(foo, a=10)
416 ifoo = interact(foo, a=10)
418 nt.assert_equal(len(displayed), 1)
417 nt.assert_equal(len(displayed), 1)
419 w = displayed[0].children[0]
418 w = displayed[0].children[0]
420 check_widget(w,
419 check_widget(w,
421 cls=widgets.IntSlider,
420 cls=widgets.IntSlider,
422 value=10,
421 value=10,
423 )
422 )
424
423
425 @nt.with_setup(clear_display)
424 @nt.with_setup(clear_display)
426 def test_call_decorated_on_trait_change():
425 def test_call_decorated_on_trait_change():
427 """test calling @interact decorated functions"""
426 """test calling @interact decorated functions"""
428 d = {}
427 d = {}
429 with tt.monkeypatch(interaction, 'display', record_display):
428 with tt.monkeypatch(interaction, 'display', record_display):
430 @interact
429 @interact
431 def foo(a='default'):
430 def foo(a='default'):
432 d['a'] = a
431 d['a'] = a
433 return a
432 return a
434 nt.assert_equal(len(displayed), 1)
433 nt.assert_equal(len(displayed), 1)
435 w = displayed[0].children[0]
434 w = displayed[0].children[0]
436 check_widget(w,
435 check_widget(w,
437 cls=widgets.Text,
436 cls=widgets.Text,
438 value='default',
437 value='default',
439 )
438 )
440 # test calling the function directly
439 # test calling the function directly
441 a = foo('hello')
440 a = foo('hello')
442 nt.assert_equal(a, 'hello')
441 nt.assert_equal(a, 'hello')
443 nt.assert_equal(d['a'], 'hello')
442 nt.assert_equal(d['a'], 'hello')
444
443
445 # test that setting trait values calls the function
444 # test that setting trait values calls the function
446 w.value = 'called'
445 w.value = 'called'
447 nt.assert_equal(d['a'], 'called')
446 nt.assert_equal(d['a'], 'called')
448
447
449 @nt.with_setup(clear_display)
448 @nt.with_setup(clear_display)
450 def test_call_decorated_kwargs_on_trait_change():
449 def test_call_decorated_kwargs_on_trait_change():
451 """test calling @interact(foo=bar) decorated functions"""
450 """test calling @interact(foo=bar) decorated functions"""
452 d = {}
451 d = {}
453 with tt.monkeypatch(interaction, 'display', record_display):
452 with tt.monkeypatch(interaction, 'display', record_display):
454 @interact(a='kwarg')
453 @interact(a='kwarg')
455 def foo(a='default'):
454 def foo(a='default'):
456 d['a'] = a
455 d['a'] = a
457 return a
456 return a
458 nt.assert_equal(len(displayed), 1)
457 nt.assert_equal(len(displayed), 1)
459 w = displayed[0].children[0]
458 w = displayed[0].children[0]
460 check_widget(w,
459 check_widget(w,
461 cls=widgets.Text,
460 cls=widgets.Text,
462 value='kwarg',
461 value='kwarg',
463 )
462 )
464 # test calling the function directly
463 # test calling the function directly
465 a = foo('hello')
464 a = foo('hello')
466 nt.assert_equal(a, 'hello')
465 nt.assert_equal(a, 'hello')
467 nt.assert_equal(d['a'], 'hello')
466 nt.assert_equal(d['a'], 'hello')
468
467
469 # test that setting trait values calls the function
468 # test that setting trait values calls the function
470 w.value = 'called'
469 w.value = 'called'
471 nt.assert_equal(d['a'], 'called')
470 nt.assert_equal(d['a'], 'called')
472
471
473 def test_fixed():
472 def test_fixed():
474 c = interactive(f, a=widgets.fixed(5), b='text')
473 c = interactive(f, a=widgets.fixed(5), b='text')
475 nt.assert_equal(len(c.children), 2)
474 nt.assert_equal(len(c.children), 1)
476 w = c.children[0]
475 w = c.children[0]
477 check_widget(w,
476 check_widget(w,
478 cls=widgets.Text,
477 cls=widgets.Text,
479 value='text',
478 value='text',
480 description='b',
479 description='b',
481 )
480 )
482
481
483 def test_default_description():
482 def test_default_description():
484 c = interactive(f, b='text')
483 c = interactive(f, b='text')
485 w = c.children[0]
484 w = c.children[0]
486 check_widget(w,
485 check_widget(w,
487 cls=widgets.Text,
486 cls=widgets.Text,
488 value='text',
487 value='text',
489 description='b',
488 description='b',
490 )
489 )
491
490
492 def test_custom_description():
491 def test_custom_description():
493 d = {}
492 d = {}
494 def record_kwargs(**kwargs):
493 def record_kwargs(**kwargs):
495 d.clear()
494 d.clear()
496 d.update(kwargs)
495 d.update(kwargs)
497
496
498 c = interactive(record_kwargs, b=widgets.Text(value='text', description='foo'))
497 c = interactive(record_kwargs, b=widgets.Text(value='text', description='foo'))
499 w = c.children[0]
498 w = c.children[0]
500 check_widget(w,
499 check_widget(w,
501 cls=widgets.Text,
500 cls=widgets.Text,
502 value='text',
501 value='text',
503 description='foo',
502 description='foo',
504 )
503 )
505 w.value = 'different text'
504 w.value = 'different text'
506 nt.assert_equal(d, {'b': 'different text'})
505 nt.assert_equal(d, {'b': 'different text'})
507
506
508 def test_interact_manual_button():
507 def test_interact_manual_button():
509 c = interactive(f, __manual=True)
508 c = interactive(f, __manual=True)
510 w = c.children[0]
509 w = c.children[0]
511 check_widget(w, cls=widgets.Button)
510 check_widget(w, cls=widgets.Button)
512
511
513 def test_interact_manual_nocall():
512 def test_interact_manual_nocall():
514 callcount = 0
513 callcount = 0
515 def calltest(testarg):
514 def calltest(testarg):
516 callcount += 1
515 callcount += 1
517 c = interactive(calltest, testarg=5, __manual=True)
516 c = interactive(calltest, testarg=5, __manual=True)
518 c.children[0].value = 10
517 c.children[0].value = 10
519 nt.assert_equal(callcount, 0)
518 nt.assert_equal(callcount, 0)
520
519
521 def test_int_range_logic():
520 def test_int_range_logic():
522 irsw = widgets.IntRangeSlider
521 irsw = widgets.IntRangeSlider
523 w = irsw(value=(2, 4), min=0, max=6)
522 w = irsw(value=(2, 4), min=0, max=6)
524 check_widget(w, cls=irsw, value=(2, 4), min=0, max=6)
523 check_widget(w, cls=irsw, value=(2, 4), min=0, max=6)
525 w.value = (4, 2)
524 w.value = (4, 2)
526 check_widget(w, cls=irsw, value=(2, 4), min=0, max=6)
525 check_widget(w, cls=irsw, value=(2, 4), min=0, max=6)
527 w.value = (-1, 7)
526 w.value = (-1, 7)
528 check_widget(w, cls=irsw, value=(0, 6), min=0, max=6)
527 check_widget(w, cls=irsw, value=(0, 6), min=0, max=6)
529 w.min = 3
528 w.min = 3
530 check_widget(w, cls=irsw, value=(3, 6), min=3, max=6)
529 check_widget(w, cls=irsw, value=(3, 6), min=3, max=6)
531 w.max = 3
530 w.max = 3
532 check_widget(w, cls=irsw, value=(3, 3), min=3, max=3)
531 check_widget(w, cls=irsw, value=(3, 3), min=3, max=3)
533
532
534 w.min = 0
533 w.min = 0
535 w.max = 6
534 w.max = 6
536 w.lower = 2
535 w.lower = 2
537 w.upper = 4
536 w.upper = 4
538 check_widget(w, cls=irsw, value=(2, 4), min=0, max=6)
537 check_widget(w, cls=irsw, value=(2, 4), min=0, max=6)
539 w.value = (0, 1) #lower non-overlapping range
538 w.value = (0, 1) #lower non-overlapping range
540 check_widget(w, cls=irsw, value=(0, 1), min=0, max=6)
539 check_widget(w, cls=irsw, value=(0, 1), min=0, max=6)
541 w.value = (5, 6) #upper non-overlapping range
540 w.value = (5, 6) #upper non-overlapping range
542 check_widget(w, cls=irsw, value=(5, 6), min=0, max=6)
541 check_widget(w, cls=irsw, value=(5, 6), min=0, max=6)
543 w.value = (-1, 4) #semi out-of-range
542 w.value = (-1, 4) #semi out-of-range
544 check_widget(w, cls=irsw, value=(0, 4), min=0, max=6)
543 check_widget(w, cls=irsw, value=(0, 4), min=0, max=6)
545 w.lower = 2
544 w.lower = 2
546 check_widget(w, cls=irsw, value=(2, 4), min=0, max=6)
545 check_widget(w, cls=irsw, value=(2, 4), min=0, max=6)
547 w.value = (-2, -1) #wholly out of range
546 w.value = (-2, -1) #wholly out of range
548 check_widget(w, cls=irsw, value=(0, 0), min=0, max=6)
547 check_widget(w, cls=irsw, value=(0, 0), min=0, max=6)
549 w.value = (7, 8)
548 w.value = (7, 8)
550 check_widget(w, cls=irsw, value=(6, 6), min=0, max=6)
549 check_widget(w, cls=irsw, value=(6, 6), min=0, max=6)
551
550
552 with nt.assert_raises(ValueError):
551 with nt.assert_raises(ValueError):
553 w.min = 7
552 w.min = 7
554 with nt.assert_raises(ValueError):
553 with nt.assert_raises(ValueError):
555 w.max = -1
554 w.max = -1
556 with nt.assert_raises(ValueError):
555 with nt.assert_raises(ValueError):
557 w.lower = 5
556 w.lower = 5
558 with nt.assert_raises(ValueError):
557 with nt.assert_raises(ValueError):
559 w.upper = 1
558 w.upper = 1
560
559
561 w = irsw(min=2, max=3)
560 w = irsw(min=2, max=3)
562 check_widget(w, min=2, max=3)
561 check_widget(w, min=2, max=3)
563 w = irsw(min=100, max=200)
562 w = irsw(min=100, max=200)
564 check_widget(w, lower=125, upper=175, value=(125, 175))
563 check_widget(w, lower=125, upper=175, value=(125, 175))
565
564
566 with nt.assert_raises(ValueError):
565 with nt.assert_raises(ValueError):
567 irsw(value=(2, 4), lower=3)
566 irsw(value=(2, 4), lower=3)
568 with nt.assert_raises(ValueError):
567 with nt.assert_raises(ValueError):
569 irsw(value=(2, 4), upper=3)
568 irsw(value=(2, 4), upper=3)
570 with nt.assert_raises(ValueError):
569 with nt.assert_raises(ValueError):
571 irsw(value=(2, 4), lower=3, upper=3)
570 irsw(value=(2, 4), lower=3, upper=3)
572 with nt.assert_raises(ValueError):
571 with nt.assert_raises(ValueError):
573 irsw(min=2, max=1)
572 irsw(min=2, max=1)
574 with nt.assert_raises(ValueError):
573 with nt.assert_raises(ValueError):
575 irsw(lower=5)
574 irsw(lower=5)
576 with nt.assert_raises(ValueError):
575 with nt.assert_raises(ValueError):
577 irsw(upper=5)
576 irsw(upper=5)
578
577
579
578
580 def test_float_range_logic():
579 def test_float_range_logic():
581 frsw = widgets.FloatRangeSlider
580 frsw = widgets.FloatRangeSlider
582 w = frsw(value=(.2, .4), min=0., max=.6)
581 w = frsw(value=(.2, .4), min=0., max=.6)
583 check_widget(w, cls=frsw, value=(.2, .4), min=0., max=.6)
582 check_widget(w, cls=frsw, value=(.2, .4), min=0., max=.6)
584 w.value = (.4, .2)
583 w.value = (.4, .2)
585 check_widget(w, cls=frsw, value=(.2, .4), min=0., max=.6)
584 check_widget(w, cls=frsw, value=(.2, .4), min=0., max=.6)
586 w.value = (-.1, .7)
585 w.value = (-.1, .7)
587 check_widget(w, cls=frsw, value=(0., .6), min=0., max=.6)
586 check_widget(w, cls=frsw, value=(0., .6), min=0., max=.6)
588 w.min = .3
587 w.min = .3
589 check_widget(w, cls=frsw, value=(.3, .6), min=.3, max=.6)
588 check_widget(w, cls=frsw, value=(.3, .6), min=.3, max=.6)
590 w.max = .3
589 w.max = .3
591 check_widget(w, cls=frsw, value=(.3, .3), min=.3, max=.3)
590 check_widget(w, cls=frsw, value=(.3, .3), min=.3, max=.3)
592
591
593 w.min = 0.
592 w.min = 0.
594 w.max = .6
593 w.max = .6
595 w.lower = .2
594 w.lower = .2
596 w.upper = .4
595 w.upper = .4
597 check_widget(w, cls=frsw, value=(.2, .4), min=0., max=.6)
596 check_widget(w, cls=frsw, value=(.2, .4), min=0., max=.6)
598 w.value = (0., .1) #lower non-overlapping range
597 w.value = (0., .1) #lower non-overlapping range
599 check_widget(w, cls=frsw, value=(0., .1), min=0., max=.6)
598 check_widget(w, cls=frsw, value=(0., .1), min=0., max=.6)
600 w.value = (.5, .6) #upper non-overlapping range
599 w.value = (.5, .6) #upper non-overlapping range
601 check_widget(w, cls=frsw, value=(.5, .6), min=0., max=.6)
600 check_widget(w, cls=frsw, value=(.5, .6), min=0., max=.6)
602 w.value = (-.1, .4) #semi out-of-range
601 w.value = (-.1, .4) #semi out-of-range
603 check_widget(w, cls=frsw, value=(0., .4), min=0., max=.6)
602 check_widget(w, cls=frsw, value=(0., .4), min=0., max=.6)
604 w.lower = .2
603 w.lower = .2
605 check_widget(w, cls=frsw, value=(.2, .4), min=0., max=.6)
604 check_widget(w, cls=frsw, value=(.2, .4), min=0., max=.6)
606 w.value = (-.2, -.1) #wholly out of range
605 w.value = (-.2, -.1) #wholly out of range
607 check_widget(w, cls=frsw, value=(0., 0.), min=0., max=.6)
606 check_widget(w, cls=frsw, value=(0., 0.), min=0., max=.6)
608 w.value = (.7, .8)
607 w.value = (.7, .8)
609 check_widget(w, cls=frsw, value=(.6, .6), min=.0, max=.6)
608 check_widget(w, cls=frsw, value=(.6, .6), min=.0, max=.6)
610
609
611 with nt.assert_raises(ValueError):
610 with nt.assert_raises(ValueError):
612 w.min = .7
611 w.min = .7
613 with nt.assert_raises(ValueError):
612 with nt.assert_raises(ValueError):
614 w.max = -.1
613 w.max = -.1
615 with nt.assert_raises(ValueError):
614 with nt.assert_raises(ValueError):
616 w.lower = .5
615 w.lower = .5
617 with nt.assert_raises(ValueError):
616 with nt.assert_raises(ValueError):
618 w.upper = .1
617 w.upper = .1
619
618
620 w = frsw(min=2, max=3)
619 w = frsw(min=2, max=3)
621 check_widget(w, min=2, max=3)
620 check_widget(w, min=2, max=3)
622 w = frsw(min=1., max=2.)
621 w = frsw(min=1., max=2.)
623 check_widget(w, lower=1.25, upper=1.75, value=(1.25, 1.75))
622 check_widget(w, lower=1.25, upper=1.75, value=(1.25, 1.75))
624
623
625 with nt.assert_raises(ValueError):
624 with nt.assert_raises(ValueError):
626 frsw(value=(2, 4), lower=3)
625 frsw(value=(2, 4), lower=3)
627 with nt.assert_raises(ValueError):
626 with nt.assert_raises(ValueError):
628 frsw(value=(2, 4), upper=3)
627 frsw(value=(2, 4), upper=3)
629 with nt.assert_raises(ValueError):
628 with nt.assert_raises(ValueError):
630 frsw(value=(2, 4), lower=3, upper=3)
629 frsw(value=(2, 4), lower=3, upper=3)
631 with nt.assert_raises(ValueError):
630 with nt.assert_raises(ValueError):
632 frsw(min=.2, max=.1)
631 frsw(min=.2, max=.1)
633 with nt.assert_raises(ValueError):
632 with nt.assert_raises(ValueError):
634 frsw(lower=5)
633 frsw(lower=5)
635 with nt.assert_raises(ValueError):
634 with nt.assert_raises(ValueError):
636 frsw(upper=5)
635 frsw(upper=5)
637
636
638
637
639 def test_multiple_selection():
638 def test_multiple_selection():
640 smw = widgets.SelectMultiple
639 smw = widgets.SelectMultiple
641
640
642 # degenerate multiple select
641 # degenerate multiple select
643 w = smw()
642 w = smw()
644 check_widget(w, value=tuple(), options=None, selected_labels=tuple())
643 check_widget(w, value=tuple(), options=None, selected_labels=tuple())
645
644
646 # don't accept random other value when no options
645 # don't accept random other value when no options
647 with nt.assert_raises(KeyError):
646 with nt.assert_raises(KeyError):
648 w.value = (2,)
647 w.value = (2,)
649 check_widget(w, value=tuple(), selected_labels=tuple())
648 check_widget(w, value=tuple(), selected_labels=tuple())
650
649
651 # basic multiple select
650 # basic multiple select
652 w = smw(options=[(1, 1)], value=[1])
651 w = smw(options=[(1, 1)], value=[1])
653 check_widget(w, cls=smw, value=(1,), options=[(1, 1)])
652 check_widget(w, cls=smw, value=(1,), options=[(1, 1)])
654
653
655 # don't accept random other value
654 # don't accept random other value
656 with nt.assert_raises(KeyError):
655 with nt.assert_raises(KeyError):
657 w.value = w.value + (2,)
656 w.value = w.value + (2,)
658 check_widget(w, value=(1,), selected_labels=(1,))
657 check_widget(w, value=(1,), selected_labels=(1,))
659
658
660 # change options
659 # change options
661 w.options = w.options + [(2, 2)]
660 w.options = w.options + [(2, 2)]
662 check_widget(w, options=[(1, 1), (2,2)])
661 check_widget(w, options=[(1, 1), (2,2)])
663
662
664 # change value
663 # change value
665 w.value = w.value + (2,)
664 w.value = w.value + (2,)
666 check_widget(w, value=(1, 2), selected_labels=(1, 2))
665 check_widget(w, value=(1, 2), selected_labels=(1, 2))
667
666
668 # change value name
667 # change value name
669 w.selected_labels = (1,)
668 w.selected_labels = (1,)
670 check_widget(w, value=(1,))
669 check_widget(w, value=(1,))
671
670
672 # don't accept random other names when no options
671 # don't accept random other names when no options
673 with nt.assert_raises(KeyError):
672 with nt.assert_raises(KeyError):
674 w.selected_labels = (3,)
673 w.selected_labels = (3,)
675 check_widget(w, value=(1,))
674 check_widget(w, value=(1,))
676
675
677 # don't accept selected_label (from superclass)
676 # don't accept selected_label (from superclass)
678 with nt.assert_raises(AttributeError):
677 with nt.assert_raises(AttributeError):
679 w.selected_label = 3
678 w.selected_label = 3
680
679
681 # don't return selected_label (from superclass)
680 # don't return selected_label (from superclass)
682 with nt.assert_raises(AttributeError):
681 with nt.assert_raises(AttributeError):
683 print(w.selected_label)
682 print(w.selected_label)
684
683
685 # dict style
684 # dict style
686 w.options = {1: 1}
685 w.options = {1: 1}
687 check_widget(w, options={1: 1})
686 check_widget(w, options={1: 1})
688
687
689 # updating
688 # updating
690 with nt.assert_raises(KeyError):
689 with nt.assert_raises(KeyError):
691 w.value = (2,)
690 w.value = (2,)
692 check_widget(w, options={1: 1})
691 check_widget(w, options={1: 1})
@@ -1,88 +1,78 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
46 def clear_output(self, *pargs, **kwargs):
37 def clear_output(self, *pargs, **kwargs):
47 with self:
38 with self:
48 clear_output(*pargs, **kwargs)
39 clear_output(*pargs, **kwargs)
49
40
50 def __enter__(self):
41 def __enter__(self):
51 """Called upon entering output widget context manager."""
42 """Called upon entering output widget context manager."""
52 if self._kernel is not None:
43 self._flush()
53 self._flush()
44 kernel = get_ipython().kernel
54 session = self._kernel.session
45 session = kernel.session
55 send = session.send
46 send = session.send
56 self._original_send = send
47 self._original_send = send
57 self._session = session
48 self._session = session
58
49
59 def send_hook(stream, msg_or_type, content=None, parent=None, ident=None,
50 def send_hook(stream, msg_or_type, content=None, parent=None, ident=None,
60 buffers=None, track=False, header=None, metadata=None):
51 buffers=None, track=False, header=None, metadata=None):
61
52
62 # Handle both prebuild messages and unbuilt messages.
53 # Handle both prebuild messages and unbuilt messages.
63 if isinstance(msg_or_type, (Message, dict)):
54 if isinstance(msg_or_type, (Message, dict)):
64 msg_type = msg_or_type['msg_type']
55 msg_type = msg_or_type['msg_type']
65 msg = dict(msg_or_type)
56 msg = dict(msg_or_type)
66 else:
57 else:
67 msg_type = msg_or_type
58 msg_type = msg_or_type
68 msg = session.msg(msg_type, content=content, parent=parent,
59 msg = session.msg(msg_type, content=content, parent=parent,
69 header=header, metadata=metadata)
60 header=header, metadata=metadata)
70
61
71 # If this is a message type that we want to forward, forward it.
62 # If this is a message type that we want to forward, forward it.
72 if stream is self._kernel.iopub_socket and msg_type in ['clear_output', 'stream', 'display_data']:
63 if stream is kernel.iopub_socket and msg_type in ['clear_output', 'stream', 'display_data']:
73 self.send(msg)
64 self.send(msg)
74 else:
65 else:
75 send(stream, msg, ident=ident, buffers=buffers, track=track)
66 send(stream, msg, ident=ident, buffers=buffers, track=track)
76
67
77 session.send = send_hook
68 session.send = send_hook
78
69
79 def __exit__(self, exception_type, exception_value, traceback):
70 def __exit__(self, exception_type, exception_value, traceback):
80 """Called upon exiting output widget context manager."""
71 """Called upon exiting output widget context manager."""
81 if self._kernel is not None:
72 self._flush()
82 self._flush()
73 self._session.send = self._original_send
83 self._session.send = self._original_send
84
74
85 def _flush(self):
75 def _flush(self):
86 """Flush stdout and stderr buffers."""
76 """Flush stdout and stderr buffers."""
87 sys.stdout.flush()
77 sys.stdout.flush()
88 sys.stderr.flush()
78 sys.stderr.flush()
General Comments 0
You need to be logged in to leave comments. Login now