##// END OF EJS Templates
Adding const pseudo-widget for fixing arguments to interact.
Brian E. Granger -
Show More

The requested changes are too big and content was truncated. Show full diff

@@ -1,12 +1,12 b''
1 from .widget import Widget, DOMWidget, CallbackDispatcher
1 from .widget import Widget, DOMWidget, CallbackDispatcher
2
2
3 from .widget_bool import CheckboxWidget, ToggleButtonWidget
3 from .widget_bool import CheckboxWidget, ToggleButtonWidget
4 from .widget_button import ButtonWidget
4 from .widget_button import ButtonWidget
5 from .widget_container import ContainerWidget, PopupWidget
5 from .widget_container import ContainerWidget, PopupWidget
6 from .widget_float import FloatTextWidget, BoundedFloatTextWidget, FloatSliderWidget, FloatProgressWidget
6 from .widget_float import FloatTextWidget, BoundedFloatTextWidget, FloatSliderWidget, FloatProgressWidget
7 from .widget_image import ImageWidget
7 from .widget_image import ImageWidget
8 from .widget_int import IntTextWidget, BoundedIntTextWidget, IntSliderWidget, IntProgressWidget
8 from .widget_int import IntTextWidget, BoundedIntTextWidget, IntSliderWidget, IntProgressWidget
9 from .widget_selection import RadioButtonsWidget, ToggleButtonsWidget, DropdownWidget, SelectWidget
9 from .widget_selection import RadioButtonsWidget, ToggleButtonsWidget, DropdownWidget, SelectWidget
10 from .widget_selectioncontainer import TabWidget, AccordionWidget
10 from .widget_selectioncontainer import TabWidget, AccordionWidget
11 from .widget_string import HTMLWidget, LatexWidget, TextWidget, TextareaWidget
11 from .widget_string import HTMLWidget, LatexWidget, TextWidget, TextareaWidget
12 from .interaction import interact, interactive, annotate
12 from .interaction import interact, interactive, annotate, const
@@ -1,276 +1,286 b''
1 """Interact with functions using widgets."""
1 """Interact with functions using widgets."""
2
2
3 #-----------------------------------------------------------------------------
3 #-----------------------------------------------------------------------------
4 # Copyright (c) 2013, the IPython Development Team.
4 # Copyright (c) 2013, the IPython Development Team.
5 #
5 #
6 # Distributed under the terms of the Modified BSD License.
6 # Distributed under the terms of the Modified BSD License.
7 #
7 #
8 # The full license is in the file COPYING.txt, distributed with this software.
8 # The full license is in the file COPYING.txt, distributed with this software.
9 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10
10
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12 # Imports
12 # Imports
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 from __future__ import print_function
15 from __future__ import print_function
16
16
17 try: # Python >= 3.3
17 try: # Python >= 3.3
18 from inspect import signature, Parameter
18 from inspect import signature, Parameter
19 except ImportError:
19 except ImportError:
20 from IPython.utils.signatures import signature, Parameter
20 from IPython.utils.signatures import signature, Parameter
21 from inspect import getcallargs
21 from inspect import getcallargs
22
22
23 from IPython.html.widgets import (Widget, TextWidget,
23 from IPython.html.widgets import (Widget, TextWidget,
24 FloatSliderWidget, IntSliderWidget, CheckboxWidget, DropdownWidget,
24 FloatSliderWidget, IntSliderWidget, CheckboxWidget, DropdownWidget,
25 ContainerWidget)
25 ContainerWidget, DOMWidget)
26 from IPython.display import display, clear_output
26 from IPython.display import display, clear_output
27 from IPython.utils.py3compat import string_types, unicode_type
27 from IPython.utils.py3compat import string_types, unicode_type
28 from IPython.utils.traitlets import HasTraits, Any, Unicode
28
29
29 #-----------------------------------------------------------------------------
30 #-----------------------------------------------------------------------------
30 # Classes and Functions
31 # Classes and Functions
31 #-----------------------------------------------------------------------------
32 #-----------------------------------------------------------------------------
32
33
33
34
34 def _matches(o, pattern):
35 def _matches(o, pattern):
35 """Match a pattern of types in a sequence."""
36 """Match a pattern of types in a sequence."""
36 if not len(o) == len(pattern):
37 if not len(o) == len(pattern):
37 return False
38 return False
38 comps = zip(o,pattern)
39 comps = zip(o,pattern)
39 return all(isinstance(obj,kind) for obj,kind in comps)
40 return all(isinstance(obj,kind) for obj,kind in comps)
40
41
41
42
42 def _get_min_max_value(min, max, value):
43 def _get_min_max_value(min, max, value):
43 """Return min, max, value given input values with possible None."""
44 """Return min, max, value given input values with possible None."""
44 if value is None:
45 if value is None:
45 if not max > min:
46 if not max > min:
46 raise ValueError('max must be greater than min: (min={0}, max={1})'.format(min, max))
47 raise ValueError('max must be greater than min: (min={0}, max={1})'.format(min, max))
47 value = min + abs(min-max)/2
48 value = min + abs(min-max)/2
48 value = type(min)(value)
49 value = type(min)(value)
49 elif min is None and max is None:
50 elif min is None and max is None:
50 if value == 0.0:
51 if value == 0.0:
51 min, max, value = 0.0, 1.0, 0.5
52 min, max, value = 0.0, 1.0, 0.5
52 elif value == 0:
53 elif value == 0:
53 min, max, value = 0, 1, 0
54 min, max, value = 0, 1, 0
54 elif isinstance(value, float):
55 elif isinstance(value, float):
55 min, max = (-value, 3.0*value) if value > 0 else (3.0*value, -value)
56 min, max = (-value, 3.0*value) if value > 0 else (3.0*value, -value)
56 elif isinstance(value, int):
57 elif isinstance(value, int):
57 min, max = (-value, 3*value) if value > 0 else (3*value, -value)
58 min, max = (-value, 3*value) if value > 0 else (3*value, -value)
58 else:
59 else:
59 raise TypeError('expected a number, got: %r' % value)
60 raise TypeError('expected a number, got: %r' % value)
60 else:
61 else:
61 raise ValueError('unable to infer range, value from: ({0}, {1}, {2})'.format(min, max, value))
62 raise ValueError('unable to infer range, value from: ({0}, {1}, {2})'.format(min, max, value))
62 return min, max, value
63 return min, max, value
63
64
64 def _widget_abbrev_single_value(o):
65 def _widget_abbrev_single_value(o):
65 """Make widgets from single values, which can be used written as parameter defaults."""
66 """Make widgets from single values, which can be used written as parameter defaults."""
66 if isinstance(o, string_types):
67 if isinstance(o, string_types):
67 return TextWidget(value=unicode_type(o))
68 return TextWidget(value=unicode_type(o))
68 elif isinstance(o, dict):
69 elif isinstance(o, dict):
69 labels = [unicode_type(k) for k in o]
70 labels = [unicode_type(k) for k in o]
70 values = o.values()
71 values = o.values()
71 w = DropdownWidget(value=values[0], values=values, labels=labels)
72 w = DropdownWidget(value=values[0], values=values, labels=labels)
72 return w
73 return w
73 elif isinstance(o, bool):
74 elif isinstance(o, bool):
74 return CheckboxWidget(value=o)
75 return CheckboxWidget(value=o)
75 elif isinstance(o, float):
76 elif isinstance(o, float):
76 min, max, value = _get_min_max_value(None, None, o)
77 min, max, value = _get_min_max_value(None, None, o)
77 return FloatSliderWidget(value=o, min=min, max=max)
78 return FloatSliderWidget(value=o, min=min, max=max)
78 elif isinstance(o, int):
79 elif isinstance(o, int):
79 min, max, value = _get_min_max_value(None, None, o)
80 min, max, value = _get_min_max_value(None, None, o)
80 return IntSliderWidget(value=o, min=min, max=max)
81 return IntSliderWidget(value=o, min=min, max=max)
81 else:
82 else:
82 return None
83 return None
83
84
84 def _widget_abbrev(o):
85 def _widget_abbrev(o):
85 """Make widgets from abbreviations: single values, lists or tuples."""
86 """Make widgets from abbreviations: single values, lists or tuples."""
86 if isinstance(o, (list, tuple)):
87 if isinstance(o, (list, tuple)):
87 if _matches(o, (int, int)):
88 if _matches(o, (int, int)):
88 min, max, value = _get_min_max_value(o[0], o[1], None)
89 min, max, value = _get_min_max_value(o[0], o[1], None)
89 return IntSliderWidget(value=value, min=min, max=max)
90 return IntSliderWidget(value=value, min=min, max=max)
90 elif _matches(o, (int, int, int)):
91 elif _matches(o, (int, int, int)):
91 min, max, value = _get_min_max_value(o[0], o[1], None)
92 min, max, value = _get_min_max_value(o[0], o[1], None)
92 return IntSliderWidget(value=value, min=min, max=max, step=o[2])
93 return IntSliderWidget(value=value, min=min, max=max, step=o[2])
93 elif _matches(o, (float, float)):
94 elif _matches(o, (float, float)):
94 min, max, value = _get_min_max_value(o[0], o[1], None)
95 min, max, value = _get_min_max_value(o[0], o[1], None)
95 return FloatSliderWidget(value=value, min=min, max=max)
96 return FloatSliderWidget(value=value, min=min, max=max)
96 elif _matches(o, (float, float, float)):
97 elif _matches(o, (float, float, float)):
97 min, max, value = _get_min_max_value(o[0], o[1], None)
98 min, max, value = _get_min_max_value(o[0], o[1], None)
98 return FloatSliderWidget(value=value, min=min, max=max, step=o[2])
99 return FloatSliderWidget(value=value, min=min, max=max, step=o[2])
99 elif _matches(o, (float, float, int)):
100 elif _matches(o, (float, float, int)):
100 min, max, value = _get_min_max_value(o[0], o[1], None)
101 min, max, value = _get_min_max_value(o[0], o[1], None)
101 return FloatSliderWidget(value=value, min=min, max=max, step=float(o[2]))
102 return FloatSliderWidget(value=value, min=min, max=max, step=float(o[2]))
102 elif all(isinstance(x, string_types) for x in o):
103 elif all(isinstance(x, string_types) for x in o):
103 return DropdownWidget(value=unicode_type(o[0]),
104 return DropdownWidget(value=unicode_type(o[0]),
104 values=[unicode_type(k) for k in o])
105 values=[unicode_type(k) for k in o])
105 else:
106 else:
106 return _widget_abbrev_single_value(o)
107 return _widget_abbrev_single_value(o)
107
108
108 def _widget_from_abbrev(abbrev):
109 def _widget_from_abbrev(abbrev):
109 """Build a Widget intstance given an abbreviation or Widget."""
110 """Build a Widget intstance given an abbreviation or Widget."""
110 if isinstance(abbrev, Widget):
111 if isinstance(abbrev, Widget) or isinstance(abbrev, const):
111 return abbrev
112 return abbrev
112
113
113 widget = _widget_abbrev(abbrev)
114 widget = _widget_abbrev(abbrev)
114 if widget is None:
115 if widget is None:
115 raise ValueError("%r cannot be transformed to a Widget" % abbrev)
116 raise ValueError("%r cannot be transformed to a Widget" % abbrev)
116 return widget
117 return widget
117
118
118 def _yield_abbreviations_for_parameter(param, args, kwargs):
119 def _yield_abbreviations_for_parameter(param, args, kwargs):
119 """Get an abbreviation for a function parameter."""
120 """Get an abbreviation for a function parameter."""
120 # print(param, args, kwargs)
121 # print(param, args, kwargs)
121 name = param.name
122 name = param.name
122 kind = param.kind
123 kind = param.kind
123 ann = param.annotation
124 ann = param.annotation
124 default = param.default
125 default = param.default
125 empty = Parameter.empty
126 empty = Parameter.empty
126 if kind == Parameter.POSITIONAL_ONLY:
127 if kind == Parameter.POSITIONAL_ONLY:
127 if args:
128 if args:
128 yield name, args.pop(0), False
129 yield name, args.pop(0), False
129 elif ann is not empty:
130 elif ann is not empty:
130 yield name, ann, False
131 yield name, ann, False
131 else:
132 else:
132 yield None, None, None
133 yield None, None, None
133 elif kind == Parameter.POSITIONAL_OR_KEYWORD:
134 elif kind == Parameter.POSITIONAL_OR_KEYWORD:
134 if name in kwargs:
135 if name in kwargs:
135 yield name, kwargs.pop(name), True
136 yield name, kwargs.pop(name), True
136 elif args:
137 elif args:
137 yield name, args.pop(0), False
138 yield name, args.pop(0), False
138 elif ann is not empty:
139 elif ann is not empty:
139 if default is empty:
140 if default is empty:
140 yield name, ann, False
141 yield name, ann, False
141 else:
142 else:
142 yield name, ann, True
143 yield name, ann, True
143 elif default is not empty:
144 elif default is not empty:
144 yield name, default, True
145 yield name, default, True
145 else:
146 else:
146 yield None, None, None
147 yield None, None, None
147 elif kind == Parameter.VAR_POSITIONAL:
148 elif kind == Parameter.VAR_POSITIONAL:
148 # In this case name=args or something and we don't actually know the names.
149 # In this case name=args or something and we don't actually know the names.
149 for item in args[::]:
150 for item in args[::]:
150 args.pop(0)
151 args.pop(0)
151 yield '', item, False
152 yield '', item, False
152 elif kind == Parameter.KEYWORD_ONLY:
153 elif kind == Parameter.KEYWORD_ONLY:
153 if name in kwargs:
154 if name in kwargs:
154 yield name, kwargs.pop(name), True
155 yield name, kwargs.pop(name), True
155 elif ann is not empty:
156 elif ann is not empty:
156 yield name, ann, True
157 yield name, ann, True
157 elif default is not empty:
158 elif default is not empty:
158 yield name, default, True
159 yield name, default, True
159 else:
160 else:
160 yield None, None, None
161 yield None, None, None
161 elif kind == Parameter.VAR_KEYWORD:
162 elif kind == Parameter.VAR_KEYWORD:
162 # In this case name=kwargs and we yield the items in kwargs with their keys.
163 # In this case name=kwargs and we yield the items in kwargs with their keys.
163 for k, v in kwargs.copy().items():
164 for k, v in kwargs.copy().items():
164 kwargs.pop(k)
165 kwargs.pop(k)
165 yield k, v, True
166 yield k, v, True
166
167
167 def _find_abbreviations(f, args, kwargs):
168 def _find_abbreviations(f, args, kwargs):
168 """Find the abbreviations for a function and args/kwargs passed to interact."""
169 """Find the abbreviations for a function and args/kwargs passed to interact."""
169 new_args = []
170 new_args = []
170 new_kwargs = []
171 new_kwargs = []
171 for param in signature(f).parameters.values():
172 for param in signature(f).parameters.values():
172 for name, value, kw in _yield_abbreviations_for_parameter(param, args, kwargs):
173 for name, value, kw in _yield_abbreviations_for_parameter(param, args, kwargs):
173 if value is None:
174 if value is None:
174 raise ValueError('cannot find widget or abbreviation for argument: {!r}'.format(name))
175 raise ValueError('cannot find widget or abbreviation for argument: {!r}'.format(name))
175 if kw:
176 if kw:
176 new_kwargs.append((name, value))
177 new_kwargs.append((name, value))
177 else:
178 else:
178 new_args.append((name, value))
179 new_args.append((name, value))
179 return new_args, new_kwargs
180 return new_args, new_kwargs
180
181
181 def _widgets_from_abbreviations(seq):
182 def _widgets_from_abbreviations(seq):
182 """Given a sequence of (name, abbrev) tuples, return a sequence of Widgets."""
183 """Given a sequence of (name, abbrev) tuples, return a sequence of Widgets."""
183 result = []
184 result = []
184 for name, abbrev in seq:
185 for name, abbrev in seq:
185 widget = _widget_from_abbrev(abbrev)
186 widget = _widget_from_abbrev(abbrev)
186 widget.description = name
187 widget.description = name
187 result.append(widget)
188 result.append(widget)
188 return result
189 return result
189
190
190 def interactive(f, *args, **kwargs):
191 def interactive(f, *args, **kwargs):
191 """Build a group of widgets to interact with a function."""
192 """Build a group of widgets to interact with a function."""
192 co = kwargs.pop('clear_output', True)
193 co = kwargs.pop('clear_output', True)
193 args_widgets = []
194 args_widgets = []
194 kwargs_widgets = []
195 kwargs_widgets = []
195 container = ContainerWidget()
196 container = ContainerWidget()
196 container.result = None
197 container.result = None
197 container.args = []
198 container.args = []
198 container.kwargs = dict()
199 container.kwargs = dict()
199 # We need this to be a list as we iteratively pop elements off it
200 # We need this to be a list as we iteratively pop elements off it
200 args = list(args)
201 args = list(args)
201 kwargs = kwargs.copy()
202 kwargs = kwargs.copy()
202
203
203 new_args, new_kwargs = _find_abbreviations(f, args, kwargs)
204 new_args, new_kwargs = _find_abbreviations(f, args, kwargs)
204 # Before we proceed, let's make sure that the user has passed a set of args+kwargs
205 # Before we proceed, let's make sure that the user has passed a set of args+kwargs
205 # that will lead to a valid call of the function. This protects against unspecified
206 # that will lead to a valid call of the function. This protects against unspecified
206 # and doubly-specified arguments.
207 # and doubly-specified arguments.
207 getcallargs(f, *[v for n,v in new_args], **{n:v for n,v in new_kwargs})
208 getcallargs(f, *[v for n,v in new_args], **{n:v for n,v in new_kwargs})
208 # Now build the widgets from the abbreviations.
209 # Now build the widgets from the abbreviations.
209 args_widgets.extend(_widgets_from_abbreviations(new_args))
210 args_widgets.extend(_widgets_from_abbreviations(new_args))
210 kwargs_widgets.extend(_widgets_from_abbreviations(new_kwargs))
211 kwargs_widgets.extend(_widgets_from_abbreviations(new_kwargs))
211 kwargs_widgets.extend(_widgets_from_abbreviations(sorted(kwargs.items(), key = lambda x: x[0])))
212 kwargs_widgets.extend(_widgets_from_abbreviations(sorted(kwargs.items(), key = lambda x: x[0])))
212
213
213 # This has to be done as an assignment, not using container.children.append,
214 # This has to be done as an assignment, not using container.children.append,
214 # so that traitlets notices the update.
215 # so that traitlets notices the update. We skip any objects (such as const) that
215 container.children = args_widgets + kwargs_widgets
216 # are not DOMWidgets.
217 c = [w for w in args_widgets+kwargs_widgets if isinstance(w, DOMWidget)]
218 container.children = c
216
219
217 # Build the callback
220 # Build the callback
218 def call_f(name, old, new):
221 def call_f(name, old, new):
219 container.args = []
222 container.args = []
220 for widget in args_widgets:
223 for widget in args_widgets:
221 value = widget.value
224 value = widget.value
222 container.args.append(value)
225 container.args.append(value)
223 for widget in kwargs_widgets:
226 for widget in kwargs_widgets:
224 value = widget.value
227 value = widget.value
225 container.kwargs[widget.description] = value
228 container.kwargs[widget.description] = value
226 if co:
229 if co:
227 clear_output(wait=True)
230 clear_output(wait=True)
228 container.result = f(*container.args, **container.kwargs)
231 container.result = f(*container.args, **container.kwargs)
229
232
230 # Wire up the widgets
233 # Wire up the widgets
231 for widget in args_widgets:
234 for widget in args_widgets:
232 widget.on_trait_change(call_f, 'value')
235 widget.on_trait_change(call_f, 'value')
233 for widget in kwargs_widgets:
236 for widget in kwargs_widgets:
234 widget.on_trait_change(call_f, 'value')
237 widget.on_trait_change(call_f, 'value')
235
238
236 container.on_displayed(lambda _: call_f(None, None, None))
239 container.on_displayed(lambda _: call_f(None, None, None))
237
240
238 return container
241 return container
239
242
240 def interact(*args, **kwargs):
243 def interact(*args, **kwargs):
241 """Interact with a function using widgets."""
244 """Interact with a function using widgets."""
242 if args and callable(args[0]):
245 if args and callable(args[0]):
243 # This branch handles the cases:
246 # This branch handles the cases:
244 # 1. interact(f, *args, **kwargs)
247 # 1. interact(f, *args, **kwargs)
245 # 2. @interact
248 # 2. @interact
246 # def f(*args, **kwargs):
249 # def f(*args, **kwargs):
247 # ...
250 # ...
248 f = args[0]
251 f = args[0]
249 w = interactive(f, *args[1:], **kwargs)
252 w = interactive(f, *args[1:], **kwargs)
250 f.widget = w
253 f.widget = w
251 display(w)
254 display(w)
252 else:
255 else:
253 # This branch handles the case:
256 # This branch handles the case:
254 # @interact(10, 20, a=30, b=40)
257 # @interact(10, 20, a=30, b=40)
255 # def f(*args, **kwargs):
258 # def f(*args, **kwargs):
256 # ...
259 # ...
257 def dec(f):
260 def dec(f):
258 w = interactive(f, *args, **kwargs)
261 w = interactive(f, *args, **kwargs)
259 f.widget = w
262 f.widget = w
260 display(w)
263 display(w)
261 return f
264 return f
262 return dec
265 return dec
263
266
267 class const(HasTraits):
268 """A pseudo-widget whose value is constant and never client synced."""
269 value = Any(help="Any Python object")
270 description = Unicode('', help="Any Python object")
271 def __init__(self, value, **kwargs):
272 super(const, self).__init__(value=value, **kwargs)
273
264 def annotate(**kwargs):
274 def annotate(**kwargs):
265 """Python 3 compatible function annotation for Python 2."""
275 """Python 3 compatible function annotation for Python 2."""
266 if not kwargs:
276 if not kwargs:
267 raise ValueError('annotations must be provided as keyword arguments')
277 raise ValueError('annotations must be provided as keyword arguments')
268 def dec(f):
278 def dec(f):
269 if hasattr(f, '__annotations__'):
279 if hasattr(f, '__annotations__'):
270 for k, v in kwargs.items():
280 for k, v in kwargs.items():
271 f.__annotations__[k] = v
281 f.__annotations__[k] = v
272 else:
282 else:
273 f.__annotations__ = kwargs
283 f.__annotations__ = kwargs
274 return f
284 return f
275 return dec
285 return dec
276
286
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
General Comments 0
You need to be logged in to leave comments. Login now