##// END OF EJS Templates
Merge pull request #7508 from minrk/widget-description-kwarg...
Thomas Kluyver -
r20032:7187eb0d merge
parent child Browse files
Show More
@@ -1,274 +1,275 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 17 Box, Button, DOMWidget)
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 widget._kwarg = name
158 159 result.append(widget)
159 160 return result
160 161
161 162 def interactive(__interact_f, **kwargs):
162 163 """Build a group of widgets to interact with a function."""
163 164 f = __interact_f
164 165 co = kwargs.pop('clear_output', True)
165 166 manual = kwargs.pop('__manual', False)
166 167 kwargs_widgets = []
167 168 container = Box()
168 169 container.result = None
169 170 container.args = []
170 171 container.kwargs = dict()
171 172 kwargs = kwargs.copy()
172 173
173 174 new_kwargs = _find_abbreviations(f, kwargs)
174 175 # Before we proceed, let's make sure that the user has passed a set of args+kwargs
175 176 # that will lead to a valid call of the function. This protects against unspecified
176 177 # and doubly-specified arguments.
177 178 getcallargs(f, **{n:v for n,v,_ in new_kwargs})
178 179 # Now build the widgets from the abbreviations.
179 180 kwargs_widgets.extend(_widgets_from_abbreviations(new_kwargs))
180 181
181 182 # This has to be done as an assignment, not using container.children.append,
182 183 # so that traitlets notices the update. We skip any objects (such as fixed) that
183 184 # are not DOMWidgets.
184 185 c = [w for w in kwargs_widgets if isinstance(w, DOMWidget)]
185 186
186 187 # If we are only to run the function on demand, add a button to request this
187 188 if manual:
188 189 manual_button = Button(description="Run %s" % f.__name__)
189 190 c.append(manual_button)
190 191 container.children = c
191 192
192 193 # Build the callback
193 194 def call_f(name=None, old=None, new=None):
194 195 container.kwargs = {}
195 196 for widget in kwargs_widgets:
196 197 value = widget.value
197 container.kwargs[widget.description] = value
198 container.kwargs[widget._kwarg] = value
198 199 if co:
199 200 clear_output(wait=True)
200 201 if manual:
201 202 manual_button.disabled = True
202 203 try:
203 204 container.result = f(**container.kwargs)
204 205 except Exception as e:
205 206 ip = get_ipython()
206 207 if ip is None:
207 208 container.log.warn("Exception in interact callback: %s", e, exc_info=True)
208 209 else:
209 210 ip.showtraceback()
210 211 finally:
211 212 if manual:
212 213 manual_button.disabled = False
213 214
214 215 # Wire up the widgets
215 216 # If we are doing manual running, the callback is only triggered by the button
216 217 # Otherwise, it is triggered for every trait change received
217 218 # On-demand running also suppresses running the fucntion with the initial parameters
218 219 if manual:
219 220 manual_button.on_click(call_f)
220 221 else:
221 222 for widget in kwargs_widgets:
222 223 widget.on_trait_change(call_f, 'value')
223 224
224 225 container.on_displayed(lambda _: call_f(None, None, None))
225 226
226 227 return container
227 228
228 229 def interact(__interact_f=None, **kwargs):
229 230 """interact(f, **kwargs)
230 231
231 232 Interact with a function using widgets."""
232 233 # positional arg support in: https://gist.github.com/8851331
233 234 if __interact_f is not None:
234 235 # This branch handles the cases:
235 236 # 1. interact(f, **kwargs)
236 237 # 2. @interact
237 238 # def f(*args, **kwargs):
238 239 # ...
239 240 f = __interact_f
240 241 w = interactive(f, **kwargs)
241 242 try:
242 243 f.widget = w
243 244 except AttributeError:
244 245 # some things (instancemethods) can't have attributes attached,
245 246 # so wrap in a lambda
246 247 f = lambda *args, **kwargs: __interact_f(*args, **kwargs)
247 248 f.widget = w
248 249 display(w)
249 250 return f
250 251 else:
251 252 # This branch handles the case:
252 253 # @interact(a=30, b=40)
253 254 # def f(*args, **kwargs):
254 255 # ...
255 256 def dec(f):
256 257 return interact(f, **kwargs)
257 258 return dec
258 259
259 260 def interact_manual(__interact_f=None, **kwargs):
260 261 """interact_manual(f, **kwargs)
261 262
262 263 As `interact()`, generates widgets for each argument, but rather than running
263 264 the function after each widget change, adds a "Run" button and waits for it
264 265 to be clicked. Useful if the function is long-running and has several
265 266 parameters to change.
266 267 """
267 268 return interact(__interact_f, __manual=True, **kwargs)
268 269
269 270 class fixed(HasTraits):
270 271 """A pseudo-widget whose value is fixed and never synced to the client."""
271 272 value = Any(help="Any Python object")
272 273 description = Unicode('', help="Any Python object")
273 274 def __init__(self, value, **kwargs):
274 275 super(fixed, self).__init__(value=value, **kwargs)
@@ -1,628 +1,635 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 83 widgets[w.description] = w
84 84
85 85 for key, d in to_check.items():
86 86 nt.assert_in(key, widgets)
87 87 check_widget(widgets[key], **d)
88 88
89 89
90 90 def test_single_value_string():
91 91 a = u'hello'
92 92 c = interactive(f, a=a)
93 93 w = c.children[0]
94 94 check_widget(w,
95 95 cls=widgets.Text,
96 96 description='a',
97 97 value=a,
98 98 )
99 99
100 100 def test_single_value_bool():
101 101 for a in (True, False):
102 102 c = interactive(f, a=a)
103 103 w = c.children[0]
104 104 check_widget(w,
105 105 cls=widgets.Checkbox,
106 106 description='a',
107 107 value=a,
108 108 )
109 109
110 110 def test_single_value_dict():
111 111 for d in [
112 112 dict(a=5),
113 113 dict(a=5, b='b', c=dict),
114 114 ]:
115 115 c = interactive(f, d=d)
116 116 w = c.children[0]
117 117 check_widget(w,
118 118 cls=widgets.Dropdown,
119 119 description='d',
120 120 values=d,
121 121 value=next(iter(d.values())),
122 122 )
123 123
124 124 def test_single_value_float():
125 125 for a in (2.25, 1.0, -3.5):
126 126 c = interactive(f, a=a)
127 127 w = c.children[0]
128 128 check_widget(w,
129 129 cls=widgets.FloatSlider,
130 130 description='a',
131 131 value=a,
132 132 min= -a if a > 0 else 3*a,
133 133 max= 3*a if a > 0 else -a,
134 134 step=0.1,
135 135 readout=True,
136 136 )
137 137
138 138 def test_single_value_int():
139 139 for a in (1, 5, -3):
140 140 c = interactive(f, a=a)
141 141 nt.assert_equal(len(c.children), 1)
142 142 w = c.children[0]
143 143 check_widget(w,
144 144 cls=widgets.IntSlider,
145 145 description='a',
146 146 value=a,
147 147 min= -a if a > 0 else 3*a,
148 148 max= 3*a if a > 0 else -a,
149 149 step=1,
150 150 readout=True,
151 151 )
152 152
153 153 def test_list_tuple_2_int():
154 154 with nt.assert_raises(ValueError):
155 155 c = interactive(f, tup=(1,1))
156 156 with nt.assert_raises(ValueError):
157 157 c = interactive(f, tup=(1,-1))
158 158 for min, max in [ (0,1), (1,10), (1,2), (-5,5), (-20,-19) ]:
159 159 c = interactive(f, tup=(min, max), lis=[min, max])
160 160 nt.assert_equal(len(c.children), 2)
161 161 d = dict(
162 162 cls=widgets.IntSlider,
163 163 min=min,
164 164 max=max,
165 165 step=1,
166 166 readout=True,
167 167 )
168 168 check_widgets(c, tup=d, lis=d)
169 169
170 170 def test_list_tuple_3_int():
171 171 with nt.assert_raises(ValueError):
172 172 c = interactive(f, tup=(1,2,0))
173 173 with nt.assert_raises(ValueError):
174 174 c = interactive(f, tup=(1,2,-1))
175 175 for min, max, step in [ (0,2,1), (1,10,2), (1,100,2), (-5,5,4), (-100,-20,4) ]:
176 176 c = interactive(f, tup=(min, max, step), lis=[min, max, step])
177 177 nt.assert_equal(len(c.children), 2)
178 178 d = dict(
179 179 cls=widgets.IntSlider,
180 180 min=min,
181 181 max=max,
182 182 step=step,
183 183 readout=True,
184 184 )
185 185 check_widgets(c, tup=d, lis=d)
186 186
187 187 def test_list_tuple_2_float():
188 188 with nt.assert_raises(ValueError):
189 189 c = interactive(f, tup=(1.0,1.0))
190 190 with nt.assert_raises(ValueError):
191 191 c = interactive(f, tup=(0.5,-0.5))
192 192 for min, max in [ (0.5, 1.5), (1.1,10.2), (1,2.2), (-5.,5), (-20,-19.) ]:
193 193 c = interactive(f, tup=(min, max), lis=[min, max])
194 194 nt.assert_equal(len(c.children), 2)
195 195 d = dict(
196 196 cls=widgets.FloatSlider,
197 197 min=min,
198 198 max=max,
199 199 step=.1,
200 200 readout=True,
201 201 )
202 202 check_widgets(c, tup=d, lis=d)
203 203
204 204 def test_list_tuple_3_float():
205 205 with nt.assert_raises(ValueError):
206 206 c = interactive(f, tup=(1,2,0.0))
207 207 with nt.assert_raises(ValueError):
208 208 c = interactive(f, tup=(-1,-2,1.))
209 209 with nt.assert_raises(ValueError):
210 210 c = interactive(f, tup=(1,2.,-1.))
211 211 for min, max, step in [ (0.,2,1), (1,10.,2), (1,100,2.), (-5.,5.,4), (-100,-20.,4.) ]:
212 212 c = interactive(f, tup=(min, max, step), lis=[min, max, step])
213 213 nt.assert_equal(len(c.children), 2)
214 214 d = dict(
215 215 cls=widgets.FloatSlider,
216 216 min=min,
217 217 max=max,
218 218 step=step,
219 219 readout=True,
220 220 )
221 221 check_widgets(c, tup=d, lis=d)
222 222
223 223 def test_list_tuple_str():
224 224 values = ['hello', 'there', 'guy']
225 225 first = values[0]
226 226 c = interactive(f, tup=tuple(values), lis=list(values))
227 227 nt.assert_equal(len(c.children), 2)
228 228 d = dict(
229 229 cls=widgets.Dropdown,
230 230 value=first,
231 231 values=values
232 232 )
233 233 check_widgets(c, tup=d, lis=d)
234 234
235 235 def test_list_tuple_invalid():
236 236 for bad in [
237 237 (),
238 238 (5, 'hi'),
239 239 ('hi', 5),
240 240 ({},),
241 241 (None,),
242 242 ]:
243 243 with nt.assert_raises(ValueError):
244 244 print(bad) # because there is no custom message in assert_raises
245 245 c = interactive(f, tup=bad)
246 246
247 247 def test_defaults():
248 248 @annotate(n=10)
249 249 def f(n, f=4.5, g=1):
250 250 pass
251 251
252 252 c = interactive(f)
253 253 check_widgets(c,
254 254 n=dict(
255 255 cls=widgets.IntSlider,
256 256 value=10,
257 257 ),
258 258 f=dict(
259 259 cls=widgets.FloatSlider,
260 260 value=4.5,
261 261 ),
262 262 g=dict(
263 263 cls=widgets.IntSlider,
264 264 value=1,
265 265 ),
266 266 )
267 267
268 268 def test_default_values():
269 269 @annotate(n=10, f=(0, 10.), g=5, h={'a': 1, 'b': 2}, j=['hi', 'there'])
270 270 def f(n, f=4.5, g=1, h=2, j='there'):
271 271 pass
272 272
273 273 c = interactive(f)
274 274 check_widgets(c,
275 275 n=dict(
276 276 cls=widgets.IntSlider,
277 277 value=10,
278 278 ),
279 279 f=dict(
280 280 cls=widgets.FloatSlider,
281 281 value=4.5,
282 282 ),
283 283 g=dict(
284 284 cls=widgets.IntSlider,
285 285 value=5,
286 286 ),
287 287 h=dict(
288 288 cls=widgets.Dropdown,
289 289 values={'a': 1, 'b': 2},
290 290 value=2
291 291 ),
292 292 j=dict(
293 293 cls=widgets.Dropdown,
294 294 values=['hi', 'there'],
295 295 value='there'
296 296 ),
297 297 )
298 298
299 299 def test_default_out_of_bounds():
300 300 @annotate(f=(0, 10.), h={'a': 1}, j=['hi', 'there'])
301 301 def f(f='hi', h=5, j='other'):
302 302 pass
303 303
304 304 c = interactive(f)
305 305 check_widgets(c,
306 306 f=dict(
307 307 cls=widgets.FloatSlider,
308 308 value=5.,
309 309 ),
310 310 h=dict(
311 311 cls=widgets.Dropdown,
312 312 values={'a': 1},
313 313 value=1,
314 314 ),
315 315 j=dict(
316 316 cls=widgets.Dropdown,
317 317 values=['hi', 'there'],
318 318 value='hi',
319 319 ),
320 320 )
321 321
322 322 def test_annotations():
323 323 @annotate(n=10, f=widgets.FloatText())
324 324 def f(n, f):
325 325 pass
326 326
327 327 c = interactive(f)
328 328 check_widgets(c,
329 329 n=dict(
330 330 cls=widgets.IntSlider,
331 331 value=10,
332 332 ),
333 333 f=dict(
334 334 cls=widgets.FloatText,
335 335 ),
336 336 )
337 337
338 338 def test_priority():
339 339 @annotate(annotate='annotate', kwarg='annotate')
340 340 def f(kwarg='default', annotate='default', default='default'):
341 341 pass
342 342
343 343 c = interactive(f, kwarg='kwarg')
344 344 check_widgets(c,
345 345 kwarg=dict(
346 346 cls=widgets.Text,
347 347 value='kwarg',
348 348 ),
349 349 annotate=dict(
350 350 cls=widgets.Text,
351 351 value='annotate',
352 352 ),
353 353 )
354 354
355 355 @nt.with_setup(clear_display)
356 356 def test_decorator_kwarg():
357 357 with tt.monkeypatch(interaction, 'display', record_display):
358 358 @interact(a=5)
359 359 def foo(a):
360 360 pass
361 361 nt.assert_equal(len(displayed), 1)
362 362 w = displayed[0].children[0]
363 363 check_widget(w,
364 364 cls=widgets.IntSlider,
365 365 value=5,
366 366 )
367 367
368 368 @nt.with_setup(clear_display)
369 369 def test_interact_instancemethod():
370 370 class Foo(object):
371 371 def show(self, x):
372 372 print(x)
373 373
374 374 f = Foo()
375 375
376 376 with tt.monkeypatch(interaction, 'display', record_display):
377 377 g = interact(f.show, x=(1,10))
378 378 nt.assert_equal(len(displayed), 1)
379 379 w = displayed[0].children[0]
380 380 check_widget(w,
381 381 cls=widgets.IntSlider,
382 382 value=5,
383 383 )
384 384
385 385 @nt.with_setup(clear_display)
386 386 def test_decorator_no_call():
387 387 with tt.monkeypatch(interaction, 'display', record_display):
388 388 @interact
389 389 def foo(a='default'):
390 390 pass
391 391 nt.assert_equal(len(displayed), 1)
392 392 w = displayed[0].children[0]
393 393 check_widget(w,
394 394 cls=widgets.Text,
395 395 value='default',
396 396 )
397 397
398 398 @nt.with_setup(clear_display)
399 399 def test_call_interact():
400 400 def foo(a='default'):
401 401 pass
402 402 with tt.monkeypatch(interaction, 'display', record_display):
403 403 ifoo = interact(foo)
404 404 nt.assert_equal(len(displayed), 1)
405 405 w = displayed[0].children[0]
406 406 check_widget(w,
407 407 cls=widgets.Text,
408 408 value='default',
409 409 )
410 410
411 411 @nt.with_setup(clear_display)
412 412 def test_call_interact_kwargs():
413 413 def foo(a='default'):
414 414 pass
415 415 with tt.monkeypatch(interaction, 'display', record_display):
416 416 ifoo = interact(foo, a=10)
417 417 nt.assert_equal(len(displayed), 1)
418 418 w = displayed[0].children[0]
419 419 check_widget(w,
420 420 cls=widgets.IntSlider,
421 421 value=10,
422 422 )
423 423
424 424 @nt.with_setup(clear_display)
425 425 def test_call_decorated_on_trait_change():
426 426 """test calling @interact decorated functions"""
427 427 d = {}
428 428 with tt.monkeypatch(interaction, 'display', record_display):
429 429 @interact
430 430 def foo(a='default'):
431 431 d['a'] = a
432 432 return a
433 433 nt.assert_equal(len(displayed), 1)
434 434 w = displayed[0].children[0]
435 435 check_widget(w,
436 436 cls=widgets.Text,
437 437 value='default',
438 438 )
439 439 # test calling the function directly
440 440 a = foo('hello')
441 441 nt.assert_equal(a, 'hello')
442 442 nt.assert_equal(d['a'], 'hello')
443 443
444 444 # test that setting trait values calls the function
445 445 w.value = 'called'
446 446 nt.assert_equal(d['a'], 'called')
447 447
448 448 @nt.with_setup(clear_display)
449 449 def test_call_decorated_kwargs_on_trait_change():
450 450 """test calling @interact(foo=bar) decorated functions"""
451 451 d = {}
452 452 with tt.monkeypatch(interaction, 'display', record_display):
453 453 @interact(a='kwarg')
454 454 def foo(a='default'):
455 455 d['a'] = a
456 456 return a
457 457 nt.assert_equal(len(displayed), 1)
458 458 w = displayed[0].children[0]
459 459 check_widget(w,
460 460 cls=widgets.Text,
461 461 value='kwarg',
462 462 )
463 463 # test calling the function directly
464 464 a = foo('hello')
465 465 nt.assert_equal(a, 'hello')
466 466 nt.assert_equal(d['a'], 'hello')
467 467
468 468 # test that setting trait values calls the function
469 469 w.value = 'called'
470 470 nt.assert_equal(d['a'], 'called')
471 471
472 472 def test_fixed():
473 473 c = interactive(f, a=widgets.fixed(5), b='text')
474 474 nt.assert_equal(len(c.children), 1)
475 475 w = c.children[0]
476 476 check_widget(w,
477 477 cls=widgets.Text,
478 478 value='text',
479 479 description='b',
480 480 )
481 481
482 482 def test_default_description():
483 483 c = interactive(f, b='text')
484 484 w = c.children[0]
485 485 check_widget(w,
486 486 cls=widgets.Text,
487 487 value='text',
488 488 description='b',
489 489 )
490 490
491 491 def test_custom_description():
492 c = interactive(f, b=widgets.Text(value='text', description='foo'))
492 d = {}
493 def record_kwargs(**kwargs):
494 d.clear()
495 d.update(kwargs)
496
497 c = interactive(record_kwargs, b=widgets.Text(value='text', description='foo'))
493 498 w = c.children[0]
494 499 check_widget(w,
495 500 cls=widgets.Text,
496 501 value='text',
497 502 description='foo',
498 503 )
504 w.value = 'different text'
505 nt.assert_equal(d, {'b': 'different text'})
499 506
500 507 def test_interact_manual_button():
501 508 c = interactive(f, __manual=True)
502 509 w = c.children[0]
503 510 check_widget(w, cls=widgets.Button)
504 511
505 512 def test_interact_manual_nocall():
506 513 callcount = 0
507 514 def calltest(testarg):
508 515 callcount += 1
509 516 c = interactive(calltest, testarg=5, __manual=True)
510 517 c.children[0].value = 10
511 518 nt.assert_equal(callcount, 0)
512 519
513 520 def test_int_range_logic():
514 521 irsw = widgets.IntRangeSlider
515 522 w = irsw(value=(2, 4), min=0, max=6)
516 523 check_widget(w, cls=irsw, value=(2, 4), min=0, max=6)
517 524 w.value = (4, 2)
518 525 check_widget(w, cls=irsw, value=(2, 4), min=0, max=6)
519 526 w.value = (-1, 7)
520 527 check_widget(w, cls=irsw, value=(0, 6), min=0, max=6)
521 528 w.min = 3
522 529 check_widget(w, cls=irsw, value=(3, 6), min=3, max=6)
523 530 w.max = 3
524 531 check_widget(w, cls=irsw, value=(3, 3), min=3, max=3)
525 532
526 533 w.min = 0
527 534 w.max = 6
528 535 w.lower = 2
529 536 w.upper = 4
530 537 check_widget(w, cls=irsw, value=(2, 4), min=0, max=6)
531 538 w.value = (0, 1) #lower non-overlapping range
532 539 check_widget(w, cls=irsw, value=(0, 1), min=0, max=6)
533 540 w.value = (5, 6) #upper non-overlapping range
534 541 check_widget(w, cls=irsw, value=(5, 6), min=0, max=6)
535 542 w.value = (-1, 4) #semi out-of-range
536 543 check_widget(w, cls=irsw, value=(0, 4), min=0, max=6)
537 544 w.lower = 2
538 545 check_widget(w, cls=irsw, value=(2, 4), min=0, max=6)
539 546 w.value = (-2, -1) #wholly out of range
540 547 check_widget(w, cls=irsw, value=(0, 0), min=0, max=6)
541 548 w.value = (7, 8)
542 549 check_widget(w, cls=irsw, value=(6, 6), min=0, max=6)
543 550
544 551 with nt.assert_raises(ValueError):
545 552 w.min = 7
546 553 with nt.assert_raises(ValueError):
547 554 w.max = -1
548 555 with nt.assert_raises(ValueError):
549 556 w.lower = 5
550 557 with nt.assert_raises(ValueError):
551 558 w.upper = 1
552 559
553 560 w = irsw(min=2, max=3)
554 561 check_widget(w, min=2, max=3)
555 562 w = irsw(min=100, max=200)
556 563 check_widget(w, lower=125, upper=175, value=(125, 175))
557 564
558 565 with nt.assert_raises(ValueError):
559 566 irsw(value=(2, 4), lower=3)
560 567 with nt.assert_raises(ValueError):
561 568 irsw(value=(2, 4), upper=3)
562 569 with nt.assert_raises(ValueError):
563 570 irsw(value=(2, 4), lower=3, upper=3)
564 571 with nt.assert_raises(ValueError):
565 572 irsw(min=2, max=1)
566 573 with nt.assert_raises(ValueError):
567 574 irsw(lower=5)
568 575 with nt.assert_raises(ValueError):
569 576 irsw(upper=5)
570 577
571 578
572 579 def test_float_range_logic():
573 580 frsw = widgets.FloatRangeSlider
574 581 w = frsw(value=(.2, .4), min=0., max=.6)
575 582 check_widget(w, cls=frsw, value=(.2, .4), min=0., max=.6)
576 583 w.value = (.4, .2)
577 584 check_widget(w, cls=frsw, value=(.2, .4), min=0., max=.6)
578 585 w.value = (-.1, .7)
579 586 check_widget(w, cls=frsw, value=(0., .6), min=0., max=.6)
580 587 w.min = .3
581 588 check_widget(w, cls=frsw, value=(.3, .6), min=.3, max=.6)
582 589 w.max = .3
583 590 check_widget(w, cls=frsw, value=(.3, .3), min=.3, max=.3)
584 591
585 592 w.min = 0.
586 593 w.max = .6
587 594 w.lower = .2
588 595 w.upper = .4
589 596 check_widget(w, cls=frsw, value=(.2, .4), min=0., max=.6)
590 597 w.value = (0., .1) #lower non-overlapping range
591 598 check_widget(w, cls=frsw, value=(0., .1), min=0., max=.6)
592 599 w.value = (.5, .6) #upper non-overlapping range
593 600 check_widget(w, cls=frsw, value=(.5, .6), min=0., max=.6)
594 601 w.value = (-.1, .4) #semi out-of-range
595 602 check_widget(w, cls=frsw, value=(0., .4), min=0., max=.6)
596 603 w.lower = .2
597 604 check_widget(w, cls=frsw, value=(.2, .4), min=0., max=.6)
598 605 w.value = (-.2, -.1) #wholly out of range
599 606 check_widget(w, cls=frsw, value=(0., 0.), min=0., max=.6)
600 607 w.value = (.7, .8)
601 608 check_widget(w, cls=frsw, value=(.6, .6), min=.0, max=.6)
602 609
603 610 with nt.assert_raises(ValueError):
604 611 w.min = .7
605 612 with nt.assert_raises(ValueError):
606 613 w.max = -.1
607 614 with nt.assert_raises(ValueError):
608 615 w.lower = .5
609 616 with nt.assert_raises(ValueError):
610 617 w.upper = .1
611 618
612 619 w = frsw(min=2, max=3)
613 620 check_widget(w, min=2, max=3)
614 621 w = frsw(min=1., max=2.)
615 622 check_widget(w, lower=1.25, upper=1.75, value=(1.25, 1.75))
616 623
617 624 with nt.assert_raises(ValueError):
618 625 frsw(value=(2, 4), lower=3)
619 626 with nt.assert_raises(ValueError):
620 627 frsw(value=(2, 4), upper=3)
621 628 with nt.assert_raises(ValueError):
622 629 frsw(value=(2, 4), lower=3, upper=3)
623 630 with nt.assert_raises(ValueError):
624 631 frsw(min=.2, max=.1)
625 632 with nt.assert_raises(ValueError):
626 633 frsw(lower=5)
627 634 with nt.assert_raises(ValueError):
628 635 frsw(upper=5)
General Comments 0
You need to be logged in to leave comments. Login now