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