##// END OF EJS Templates
Utter interact insanity....
Brian E. Granger -
Show More
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
@@ -0,0 +1,22 b''
1 """Test interact and interactive."""
2
3 #-----------------------------------------------------------------------------
4 # Copyright (C) 2014 The IPython Development Team
5 #
6 # Distributed under the terms of the BSD License. The full license is in
7 # the file COPYING, distributed as part of this software.
8 #-----------------------------------------------------------------------------
9
10 #-----------------------------------------------------------------------------
11 # Imports
12 #-----------------------------------------------------------------------------
13
14 import nose.tools as nt
15
16 from IPython.html.widgets import interact, interactive
17 from IPython.html.widgets import interaction
18
19 #-----------------------------------------------------------------------------
20 # Test functions
21 #-----------------------------------------------------------------------------
22
@@ -9,4 +9,4 b' from .widget_int import IntTextWidget, BoundedIntTextWidget, IntSliderWidget, In'
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
12 from .interaction import interact, interactive, annotate
@@ -1,5 +1,4 b''
1 """Interact with functions using widgets.
1 """Interact with functions using widgets."""
2 """
3
2
4 #-----------------------------------------------------------------------------
3 #-----------------------------------------------------------------------------
5 # Copyright (c) 2013, the IPython Development Team.
4 # Copyright (c) 2013, the IPython Development Team.
@@ -13,10 +12,13 b''
13 # Imports
12 # Imports
14 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
15
14
15 from __future__ import print_function
16
16 try: # Python >= 3.3
17 try: # Python >= 3.3
17 from inspect import signature, Parameter
18 from inspect import signature, Parameter
18 except ImportError:
19 except ImportError:
19 from IPython.utils.signatures import signature, Parameter
20 from IPython.utils.signatures import signature, Parameter
21 from inspect import getcallargs
20
22
21 from IPython.html.widgets import (Widget, TextWidget,
23 from IPython.html.widgets import (Widget, TextWidget,
22 FloatSliderWidget, IntSliderWidget, CheckboxWidget, DropdownWidget,
24 FloatSliderWidget, IntSliderWidget, CheckboxWidget, DropdownWidget,
@@ -30,6 +32,7 b' from IPython.utils.py3compat import string_types, unicode_type'
30
32
31
33
32 def _matches(o, pattern):
34 def _matches(o, pattern):
35 """Match a pattern of types in a sequence."""
33 if not len(o) == len(pattern):
36 if not len(o) == len(pattern):
34 return False
37 return False
35 comps = zip(o,pattern)
38 comps = zip(o,pattern)
@@ -49,9 +52,9 b' def _get_min_max_value(min, max, value):'
49 elif value == 0:
52 elif value == 0:
50 min, max, value = 0, 1, 0
53 min, max, value = 0, 1, 0
51 elif isinstance(value, float):
54 elif isinstance(value, float):
52 min, max = -value, 3.0*value
55 min, max = (-value, 3.0*value) if value > 0 else (3.0*value, -value)
53 elif isinstance(value, int):
56 elif isinstance(value, int):
54 min, max = -value, 3*value
57 min, max = (-value, 3*value) if value > 0 else (3*value, -value)
55 else:
58 else:
56 raise TypeError('expected a number, got: %r' % value)
59 raise TypeError('expected a number, got: %r' % value)
57 else:
60 else:
@@ -67,8 +70,6 b' def _widget_abbrev_single_value(o):'
67 values = o.values()
70 values = o.values()
68 w = DropdownWidget(value=values[0], values=values, labels=labels)
71 w = DropdownWidget(value=values[0], values=values, labels=labels)
69 return w
72 return w
70 # Special case float and int == 0.0
71 # get_range(value):
72 elif isinstance(o, bool):
73 elif isinstance(o, bool):
73 return CheckboxWidget(value=o)
74 return CheckboxWidget(value=o)
74 elif isinstance(o, float):
75 elif isinstance(o, float):
@@ -77,6 +78,8 b' def _widget_abbrev_single_value(o):'
77 elif isinstance(o, int):
78 elif isinstance(o, int):
78 min, max, value = _get_min_max_value(None, None, o)
79 min, max, value = _get_min_max_value(None, None, o)
79 return IntSliderWidget(value=o, min=min, max=max)
80 return IntSliderWidget(value=o, min=min, max=max)
81 else:
82 return None
80
83
81 def _widget_abbrev(o):
84 def _widget_abbrev(o):
82 """Make widgets from abbreviations: single values, lists or tuples."""
85 """Make widgets from abbreviations: single values, lists or tuples."""
@@ -99,92 +102,157 b' def _widget_abbrev(o):'
99 elif all(isinstance(x, string_types) for x in o):
102 elif all(isinstance(x, string_types) for x in o):
100 return DropdownWidget(value=unicode_type(o[0]),
103 return DropdownWidget(value=unicode_type(o[0]),
101 values=[unicode_type(k) for k in o])
104 values=[unicode_type(k) for k in o])
102
103 else:
105 else:
104 return _widget_abbrev_single_value(o)
106 return _widget_abbrev_single_value(o)
105
107
106 def _widget_or_abbrev(value):
108 def _widget_from_abbrev(abbrev):
107 if isinstance(value, Widget):
109 """Build a Widget intstance given an abbreviation or Widget."""
108 return value
110 if isinstance(abbrev, Widget):
111 return abbrev
109
112
110 widget = _widget_abbrev(value)
113 widget = _widget_abbrev(abbrev)
111 if widget is None:
114 if widget is None:
112 raise ValueError("%r cannot be transformed to a Widget" % value)
115 raise ValueError("%r cannot be transformed to a Widget" % abbrev)
113 return widget
116 return widget
114
117
115 def _widget_for_param(param, kwargs):
118 def _yield_abbreviations_for_parameter(param, args, kwargs):
116 """Get a widget for a parameter.
119 """Get an abbreviation for a function parameter."""
117
120 # print(param, args, kwargs)
118 We look for, in this order:
121 name = param.name
119 - keyword arguments passed to interact[ive]() that match the parameter name.
122 kind = param.kind
120 - function annotations
123 ann = param.annotation
121 - default values
124 default = param.default
122
125 empty = Parameter.empty
123 Returns an instance of Widget, or None if nothing suitable is found.
126 if kind == Parameter.POSITIONAL_ONLY:
124
127 if args:
125 Raises ValueError if the kwargs or annotation value cannot be made into
128 yield name, args.pop(0), False
126 a widget.
129 elif ann is not empty:
127 """
130 yield name, ann, False
128 if param.name in kwargs:
131 else:
129 return _widget_or_abbrev(kwargs.pop(param.name))
132 yield None, None, None
130
133 elif kind == Parameter.POSITIONAL_OR_KEYWORD:
131 if param.annotation is not Parameter.empty:
134 if name in kwargs:
132 return _widget_or_abbrev(param.annotation)
135 yield name, kwargs.pop(name), True
133
136 elif args:
134 if param.default is not Parameter.empty:
137 yield name, args.pop(0), False
135 # Returns None if it's not suitable
138 elif ann is not empty:
136 return _widget_abbrev_single_value(param.default)
139 if default is empty:
137
140 yield name, ann, False
138 return None
141 else:
142 yield name, ann, True
143 elif default is not empty:
144 yield name, default, True
145 else:
146 yield None, None, None
147 elif kind == Parameter.VAR_POSITIONAL:
148 # In this case name=args or something and we don't actually know the names.
149 for item in args[::]:
150 args.pop(0)
151 yield '', item, False
152 elif kind == Parameter.KEYWORD_ONLY:
153 if name in kwargs:
154 yield name, kwargs.pop(name), True
155 elif ann is not empty:
156 yield name, ann, True
157 elif default is not empty:
158 yield name, default, True
159 else:
160 yield None, None, None
161 elif kind == Parameter.VAR_KEYWORD:
162 # In this case name=kwargs and we yield the items in kwargs with their keys.
163 for k, v in kwargs.copy().items():
164 kwargs.pop(k)
165 yield k, v, True
139
166
140 def interactive(f, **kwargs):
167 def _find_abbreviations(f, args, kwargs):
141 """Build a group of widgets for setting the inputs to a function."""
168 """Find the abbreviations for a function and args/kwargs passed to interact."""
142
169 new_args = []
170 new_kwargs = []
171 for param in signature(f).parameters.values():
172 for name, value, kw in _yield_abbreviations_for_parameter(param, args, kwargs):
173 if value is None:
174 raise ValueError('cannot find widget or abbreviation for argument: {!r}'.format(name))
175 if kw:
176 new_kwargs.append((name, value))
177 else:
178 new_args.append((name, value))
179 return new_args, new_kwargs
180
181 def _widgets_from_abbreviations(seq):
182 """Given a sequence of (name, abbrev) tuples, return a sequence of Widgets."""
183 result = []
184 for name, abbrev in seq:
185 widget = _widget_from_abbrev(abbrev)
186 widget.description = name
187 result.append(widget)
188 return result
189
190 def interactive(f, *args, **kwargs):
191 """Build a group of widgets to interact with a function."""
143 co = kwargs.pop('clear_output', True)
192 co = kwargs.pop('clear_output', True)
144 # First convert all args to Widget instances
193 args_widgets = []
145 widgets = []
194 kwargs_widgets = []
146 container = ContainerWidget()
195 container = ContainerWidget()
147 container.result = None
196 container.result = None
197 container.args = []
148 container.kwargs = dict()
198 container.kwargs = dict()
149
199 # We need this to be a list as we iteratively pop elements off it
150 # Extract parameters from the function signature
200 args = list(args)
151 for param in signature(f).parameters.values():
201 kwargs = kwargs.copy()
152 param_widget = _widget_for_param(param, kwargs)
202
153 if param_widget is not None:
203 new_args, new_kwargs = _find_abbreviations(f, args, kwargs)
154 param_widget.description = param.name
204 # Before we proceed, let's make sure that the user has passed a set of args+kwargs
155 widgets.append(param_widget)
205 # that will lead to a valid call of the function. This protects against unspecified
156
206 # and doubly-specified arguments.
157 # Extra parameters from keyword args - we assume f takes **kwargs
207 getcallargs(f, *[v for n,v in new_args], **{n:v for n,v in new_kwargs})
158 for name, value in sorted(kwargs.items(), key = lambda x: x[0]):
208 # Now build the widgets from the abbreviations.
159 widget = _widget_or_abbrev(value)
209 args_widgets.extend(_widgets_from_abbreviations(new_args))
160 widget.description = name
210 kwargs_widgets.extend(_widgets_from_abbreviations(new_kwargs))
161 widgets.append(widget)
211 kwargs_widgets.extend(_widgets_from_abbreviations(sorted(kwargs.items(), key = lambda x: x[0])))
162
212
163 # This has to be done as an assignment, not using container.children.append,
213 # This has to be done as an assignment, not using container.children.append,
164 # so that traitlets notices the update.
214 # so that traitlets notices the update.
165 container.children = widgets
215 container.children = args_widgets + kwargs_widgets
166
216
167 # Build the callback
217 # Build the callback
168 def call_f(name, old, new):
218 def call_f(name, old, new):
169 actual_kwargs = {}
219 container.args = []
170 for widget in widgets:
220 for widget in args_widgets:
221 value = widget.value
222 container.args.append(value)
223 for widget in kwargs_widgets:
171 value = widget.value
224 value = widget.value
172 container.kwargs[widget.description] = value
225 container.kwargs[widget.description] = value
173 actual_kwargs[widget.description] = value
174 if co:
226 if co:
175 clear_output(wait=True)
227 clear_output(wait=True)
176 container.result = f(**actual_kwargs)
228 container.result = f(*container.args, **container.kwargs)
177
229
178 # Wire up the widgets
230 # Wire up the widgets
179 for widget in widgets:
231 for widget in args_widgets:
232 widget.on_trait_change(call_f, 'value')
233 for widget in kwargs_widgets:
180 widget.on_trait_change(call_f, 'value')
234 widget.on_trait_change(call_f, 'value')
181
235
182 container.on_displayed(lambda _: call_f(None, None, None))
236 container.on_displayed(lambda _: call_f(None, None, None))
183
237
184 return container
238 return container
185
239
186 def interact(f, **kwargs):
240 def interact(f, *args, **kwargs):
187 """Interact with a function using widgets."""
241 """Interact with a function using widgets."""
188 w = interactive(f, **kwargs)
242 w = interactive(f, *args, **kwargs)
189 f.widget = w
243 f.widget = w
190 display(w)
244 display(w)
245
246 def annotate(**kwargs):
247 """Python 3 compatible function annotation for Python 2."""
248 if not kwargs:
249 raise ValueError('annotations must be provided as keyword arguments')
250 def dec(f):
251 if hasattr(f, '__annotations__'):
252 for k, v in kwargs.items():
253 f.__annotations__[k] = v
254 else:
255 f.__annotations__ = kwargs
256 return f
257 return dec
258
General Comments 0
You need to be logged in to leave comments. Login now