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