Merge pull request #5136 from minrk/interact-default

set default value from signature defaults in interact
This commit is contained in:
Brian E. Granger 2014-02-21 16:28:52 -08:00
commit 708c30c912
3 changed files with 86 additions and 30 deletions

View File

@ -28,6 +28,8 @@ from IPython.display import display, clear_output
from IPython.utils.py3compat import string_types, unicode_type
from IPython.utils.traitlets import HasTraits, Any, Unicode
empty = Parameter.empty
#-----------------------------------------------------------------------------
# Classes and Functions
#-----------------------------------------------------------------------------
@ -108,12 +110,20 @@ def _widget_abbrev(o):
else:
return _widget_abbrev_single_value(o)
def _widget_from_abbrev(abbrev):
"""Build a Widget intstance given an abbreviation or Widget."""
def _widget_from_abbrev(abbrev, default=empty):
"""Build a Widget instance given an abbreviation or Widget."""
if isinstance(abbrev, Widget) or isinstance(abbrev, fixed):
return abbrev
widget = _widget_abbrev(abbrev)
if default is not empty and isinstance(abbrev, (list, tuple, dict)):
# if it's not a single-value abbreviation,
# set the initial value from the default
try:
widget.value = default
except Exception:
# ignore failure to set default
pass
if widget is None:
raise ValueError("%r cannot be transformed to a Widget" % (abbrev,))
return widget
@ -124,50 +134,38 @@ def _yield_abbreviations_for_parameter(param, kwargs):
kind = param.kind
ann = param.annotation
default = param.default
empty = Parameter.empty
not_found = (None, None)
if kind == Parameter.POSITIONAL_OR_KEYWORD:
not_found = (name, empty, empty)
if kind in (Parameter.POSITIONAL_OR_KEYWORD, Parameter.KEYWORD_ONLY):
if name in kwargs:
yield name, kwargs.pop(name)
value = kwargs.pop(name)
elif ann is not empty:
if default is empty:
yield name, ann
else:
yield name, ann
value = ann
elif default is not empty:
yield name, default
else:
yield not_found
elif kind == Parameter.KEYWORD_ONLY:
if name in kwargs:
yield name, kwargs.pop(name)
elif ann is not empty:
yield name, ann
elif default is not empty:
yield name, default
value = default
else:
yield not_found
yield (name, value, default)
elif kind == Parameter.VAR_KEYWORD:
# In this case name=kwargs and we yield the items in kwargs with their keys.
for k, v in kwargs.copy().items():
kwargs.pop(k)
yield k, v
yield k, v, empty
def _find_abbreviations(f, kwargs):
"""Find the abbreviations for a function and kwargs passed to interact."""
new_kwargs = []
for param in signature(f).parameters.values():
for name, value in _yield_abbreviations_for_parameter(param, kwargs):
if value is None:
for name, value, default in _yield_abbreviations_for_parameter(param, kwargs):
if value is empty:
raise ValueError('cannot find widget or abbreviation for argument: {!r}'.format(name))
new_kwargs.append((name, value))
new_kwargs.append((name, value, default))
return new_kwargs
def _widgets_from_abbreviations(seq):
"""Given a sequence of (name, abbrev) tuples, return a sequence of Widgets."""
result = []
for name, abbrev in seq:
widget = _widget_from_abbrev(abbrev)
for name, abbrev, default in seq:
widget = _widget_from_abbrev(abbrev, default)
widget.description = name
result.append(widget)
return result
@ -187,10 +185,9 @@ def interactive(__interact_f, **kwargs):
# Before we proceed, let's make sure that the user has passed a set of args+kwargs
# that will lead to a valid call of the function. This protects against unspecified
# and doubly-specified arguments.
getcallargs(f, **{n:v for n,v in new_kwargs})
getcallargs(f, **{n:v for n,v,_ in new_kwargs})
# Now build the widgets from the abbreviations.
kwargs_widgets.extend(_widgets_from_abbreviations(new_kwargs))
kwargs_widgets.extend(_widgets_from_abbreviations(sorted(kwargs.items(), key = lambda x: x[0])))
# This has to be done as an assignment, not using container.children.append,
# so that traitlets notices the update. We skip any objects (such as fixed) that

View File

@ -22,7 +22,6 @@ import IPython.testing.tools as tt
from IPython.html import widgets
from IPython.html.widgets import interact, interactive, Widget, interaction
from IPython.utils.py3compat import annotate
# from IPython.utils.capture import capture_output
#-----------------------------------------------------------------------------
# Utility stuff
@ -248,7 +247,7 @@ def test_list_tuple_invalid():
def test_defaults():
@annotate(n=10)
def f(n, f=4.5):
def f(n, f=4.5, g=1):
pass
c = interactive(f)
@ -261,6 +260,64 @@ def test_defaults():
cls=widgets.FloatSliderWidget,
value=4.5,
),
g=dict(
cls=widgets.IntSliderWidget,
value=1,
),
)
def test_default_values():
@annotate(n=10, f=(0, 10.), g=5, h={'a': 1, 'b': 2}, j=['hi', 'there'])
def f(n, f=4.5, g=1, h=2, j='there'):
pass
c = interactive(f)
check_widgets(c,
n=dict(
cls=widgets.IntSliderWidget,
value=10,
),
f=dict(
cls=widgets.FloatSliderWidget,
value=4.5,
),
g=dict(
cls=widgets.IntSliderWidget,
value=5,
),
h=dict(
cls=widgets.DropdownWidget,
values={'a': 1, 'b': 2},
value=2
),
j=dict(
cls=widgets.DropdownWidget,
values={'hi':'hi', 'there':'there'},
value='there'
),
)
def test_default_out_of_bounds():
@annotate(f=(0, 10.), h={'a': 1}, j=['hi', 'there'])
def f(f='hi', h=5, j='other'):
pass
c = interactive(f)
check_widgets(c,
f=dict(
cls=widgets.FloatSliderWidget,
value=5.,
),
h=dict(
cls=widgets.DropdownWidget,
values={'a': 1},
value=1,
),
j=dict(
cls=widgets.DropdownWidget,
values={'hi':'hi', 'there':'there'},
value='hi',
),
)
def test_annotations():

View File

@ -90,6 +90,8 @@ class _SelectionWidget(DOMWidget):
# set the selected value name
self.value_name = k
return
# undo the change, and raise KeyError
self.value = old
raise KeyError(new)
finally:
self.value_lock.release()