##// END OF EJS Templates
fix ValueError format message
MinRK -
Show More
@@ -1,250 +1,250 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 25 ContainerWidget, DOMWidget)
26 26 from IPython.display import display, clear_output
27 27 from IPython.utils.py3compat import string_types, unicode_type
28 28 from IPython.utils.traitlets import HasTraits, Any, Unicode
29 29
30 30 #-----------------------------------------------------------------------------
31 31 # Classes and Functions
32 32 #-----------------------------------------------------------------------------
33 33
34 34
35 35 def _matches(o, pattern):
36 36 """Match a pattern of types in a sequence."""
37 37 if not len(o) == len(pattern):
38 38 return False
39 39 comps = zip(o,pattern)
40 40 return all(isinstance(obj,kind) for obj,kind in comps)
41 41
42 42
43 43 def _get_min_max_value(min, max, value=None, step=None):
44 44 """Return min, max, value given input values with possible None."""
45 45 if value is None:
46 46 if not max > min:
47 47 raise ValueError('max must be greater than min: (min={0}, max={1})'.format(min, max))
48 48 value = min + abs(min-max)/2
49 49 value = type(min)(value)
50 50 elif min is None and max is None:
51 51 if value == 0.0:
52 52 min, max, value = 0.0, 1.0, 0.5
53 53 elif value == 0:
54 54 min, max, value = 0, 1, 0
55 55 elif isinstance(value, (int, float)):
56 56 min, max = (-value, 3*value) if value > 0 else (3*value, -value)
57 57 else:
58 58 raise TypeError('expected a number, got: %r' % value)
59 59 else:
60 60 raise ValueError('unable to infer range, value from: ({0}, {1}, {2})'.format(min, max, value))
61 61 if step is not None:
62 62 # ensure value is on a step
63 63 r = (value - min) % step
64 64 value = value - r
65 65 return min, max, value
66 66
67 67 def _widget_abbrev_single_value(o):
68 68 """Make widgets from single values, which can be used as parameter defaults."""
69 69 if isinstance(o, string_types):
70 70 return TextWidget(value=unicode_type(o))
71 71 elif isinstance(o, dict):
72 72 return DropdownWidget(values=o)
73 73 elif isinstance(o, bool):
74 74 return CheckboxWidget(value=o)
75 75 elif isinstance(o, float):
76 76 min, max, value = _get_min_max_value(None, None, o)
77 77 return FloatSliderWidget(value=o, min=min, max=max)
78 78 elif isinstance(o, int):
79 79 min, max, value = _get_min_max_value(None, None, o)
80 80 return IntSliderWidget(value=o, min=min, max=max)
81 81 else:
82 82 return None
83 83
84 84 def _widget_abbrev(o):
85 85 """Make widgets from abbreviations: single values, lists or tuples."""
86 86 float_or_int = (float, int)
87 87 if isinstance(o, (list, tuple)):
88 88 if o and all(isinstance(x, string_types) for x in o):
89 89 return DropdownWidget(values=[unicode_type(k) for k in o])
90 90 elif _matches(o, (float_or_int, float_or_int)):
91 91 min, max, value = _get_min_max_value(o[0], o[1])
92 92 if all(isinstance(_, int) for _ in o):
93 93 cls = IntSliderWidget
94 94 else:
95 95 cls = FloatSliderWidget
96 96 return cls(value=value, min=min, max=max)
97 97 elif _matches(o, (float_or_int, float_or_int, float_or_int)):
98 98 step = o[2]
99 99 if step <= 0:
100 100 raise ValueError("step must be >= 0, not %r" % step)
101 101 min, max, value = _get_min_max_value(o[0], o[1], step=step)
102 102 if all(isinstance(_, int) for _ in o):
103 103 cls = IntSliderWidget
104 104 else:
105 105 cls = FloatSliderWidget
106 106 return cls(value=value, min=min, max=max, step=step)
107 107 else:
108 108 return _widget_abbrev_single_value(o)
109 109
110 110 def _widget_from_abbrev(abbrev):
111 111 """Build a Widget intstance given an abbreviation or Widget."""
112 112 if isinstance(abbrev, Widget) or isinstance(abbrev, fixed):
113 113 return abbrev
114 114
115 115 widget = _widget_abbrev(abbrev)
116 116 if widget is None:
117 raise ValueError("%r cannot be transformed to a Widget" % abbrev)
117 raise ValueError("%r cannot be transformed to a Widget" % (abbrev,))
118 118 return widget
119 119
120 120 def _yield_abbreviations_for_parameter(param, kwargs):
121 121 """Get an abbreviation for a function parameter."""
122 122 name = param.name
123 123 kind = param.kind
124 124 ann = param.annotation
125 125 default = param.default
126 126 empty = Parameter.empty
127 127 not_found = (None, None)
128 128 if kind == Parameter.POSITIONAL_OR_KEYWORD:
129 129 if name in kwargs:
130 130 yield name, kwargs.pop(name)
131 131 elif ann is not empty:
132 132 if default is empty:
133 133 yield name, ann
134 134 else:
135 135 yield name, ann
136 136 elif default is not empty:
137 137 yield name, default
138 138 else:
139 139 yield not_found
140 140 elif kind == Parameter.KEYWORD_ONLY:
141 141 if name in kwargs:
142 142 yield name, kwargs.pop(name)
143 143 elif ann is not empty:
144 144 yield name, ann
145 145 elif default is not empty:
146 146 yield name, default
147 147 else:
148 148 yield not_found
149 149 elif kind == Parameter.VAR_KEYWORD:
150 150 # In this case name=kwargs and we yield the items in kwargs with their keys.
151 151 for k, v in kwargs.copy().items():
152 152 kwargs.pop(k)
153 153 yield k, v
154 154
155 155 def _find_abbreviations(f, kwargs):
156 156 """Find the abbreviations for a function and kwargs passed to interact."""
157 157 new_kwargs = []
158 158 for param in signature(f).parameters.values():
159 159 for name, value in _yield_abbreviations_for_parameter(param, kwargs):
160 160 if value is None:
161 161 raise ValueError('cannot find widget or abbreviation for argument: {!r}'.format(name))
162 162 new_kwargs.append((name, value))
163 163 return new_kwargs
164 164
165 165 def _widgets_from_abbreviations(seq):
166 166 """Given a sequence of (name, abbrev) tuples, return a sequence of Widgets."""
167 167 result = []
168 168 for name, abbrev in seq:
169 169 widget = _widget_from_abbrev(abbrev)
170 170 widget.description = name
171 171 result.append(widget)
172 172 return result
173 173
174 174 def interactive(__interact_f, **kwargs):
175 175 """Build a group of widgets to interact with a function."""
176 176 f = __interact_f
177 177 co = kwargs.pop('clear_output', True)
178 178 kwargs_widgets = []
179 179 container = ContainerWidget()
180 180 container.result = None
181 181 container.args = []
182 182 container.kwargs = dict()
183 183 kwargs = kwargs.copy()
184 184
185 185 new_kwargs = _find_abbreviations(f, kwargs)
186 186 # Before we proceed, let's make sure that the user has passed a set of args+kwargs
187 187 # that will lead to a valid call of the function. This protects against unspecified
188 188 # and doubly-specified arguments.
189 189 getcallargs(f, **{n:v for n,v in new_kwargs})
190 190 # Now build the widgets from the abbreviations.
191 191 kwargs_widgets.extend(_widgets_from_abbreviations(new_kwargs))
192 192 kwargs_widgets.extend(_widgets_from_abbreviations(sorted(kwargs.items(), key = lambda x: x[0])))
193 193
194 194 # This has to be done as an assignment, not using container.children.append,
195 195 # so that traitlets notices the update. We skip any objects (such as fixed) that
196 196 # are not DOMWidgets.
197 197 c = [w for w in kwargs_widgets if isinstance(w, DOMWidget)]
198 198 container.children = c
199 199
200 200 # Build the callback
201 201 def call_f(name, old, new):
202 202 container.kwargs = {}
203 203 for widget in kwargs_widgets:
204 204 value = widget.value
205 205 container.kwargs[widget.description] = value
206 206 if co:
207 207 clear_output(wait=True)
208 208 container.result = f(**container.kwargs)
209 209
210 210 # Wire up the widgets
211 211 for widget in kwargs_widgets:
212 212 widget.on_trait_change(call_f, 'value')
213 213
214 214 container.on_displayed(lambda _: call_f(None, None, None))
215 215
216 216 return container
217 217
218 218 def interact(__interact_f=None, **kwargs):
219 219 """interact(f, **kwargs)
220 220
221 221 Interact with a function using widgets."""
222 222 # positional arg support in: https://gist.github.com/8851331
223 223 if __interact_f is not None:
224 224 # This branch handles the cases:
225 225 # 1. interact(f, **kwargs)
226 226 # 2. @interact
227 227 # def f(*args, **kwargs):
228 228 # ...
229 229 f = __interact_f
230 230 w = interactive(f, **kwargs)
231 231 f.widget = w
232 232 display(w)
233 233 else:
234 234 # This branch handles the case:
235 235 # @interact(a=30, b=40)
236 236 # def f(*args, **kwargs):
237 237 # ...
238 238 def dec(f):
239 239 w = interactive(f, **kwargs)
240 240 f.widget = w
241 241 display(w)
242 242 return f
243 243 return dec
244 244
245 245 class fixed(HasTraits):
246 246 """A pseudo-widget whose value is fixed and never synced to the client."""
247 247 value = Any(help="Any Python object")
248 248 description = Unicode('', help="Any Python object")
249 249 def __init__(self, value, **kwargs):
250 250 super(fixed, self).__init__(value=value, **kwargs)
General Comments 0
You need to be logged in to leave comments. Login now