##// END OF EJS Templates
move @annotate to py3compat
move @annotate to py3compat

File last commit:

r15146:9bd3a1d4
r15146:9bd3a1d4
Show More
interaction.py
248 lines | 9.3 KiB | text/x-python | PythonLexer
Brian E. Granger
Utter interact insanity....
r15140 """Interact with functions using widgets."""
Brian E. Granger
Adding interact.py.
r15132
#-----------------------------------------------------------------------------
# Copyright (c) 2013, the IPython Development Team.
#
# Distributed under the terms of the Modified BSD License.
#
# The full license is in the file COPYING.txt, distributed with this software.
#-----------------------------------------------------------------------------
#-----------------------------------------------------------------------------
# Imports
#-----------------------------------------------------------------------------
Brian E. Granger
Utter interact insanity....
r15140 from __future__ import print_function
Thomas Kluyver
Get widgets from function annotations and default arguments....
r15137 try: # Python >= 3.3
from inspect import signature, Parameter
except ImportError:
from IPython.utils.signatures import signature, Parameter
Brian E. Granger
Utter interact insanity....
r15140 from inspect import getcallargs
Thomas Kluyver
Get widgets from function annotations and default arguments....
r15137
Brian E. Granger
Updating interact to work with latest state of widgets.
r15135 from IPython.html.widgets import (Widget, TextWidget,
FloatSliderWidget, IntSliderWidget, CheckboxWidget, DropdownWidget,
Brian E. Granger
Adding const pseudo-widget for fixing arguments to interact.
r15142 ContainerWidget, DOMWidget)
Brian E. Granger
Adding interact.py.
r15132 from IPython.display import display, clear_output
from IPython.utils.py3compat import string_types, unicode_type
Brian E. Granger
Adding const pseudo-widget for fixing arguments to interact.
r15142 from IPython.utils.traitlets import HasTraits, Any, Unicode
Brian E. Granger
Adding interact.py.
r15132
#-----------------------------------------------------------------------------
# Classes and Functions
#-----------------------------------------------------------------------------
def _matches(o, pattern):
Brian E. Granger
Utter interact insanity....
r15140 """Match a pattern of types in a sequence."""
Brian E. Granger
Adding interact.py.
r15132 if not len(o) == len(pattern):
return False
comps = zip(o,pattern)
return all(isinstance(obj,kind) for obj,kind in comps)
Brian E. Granger
Updating interact to work with latest state of widgets.
r15135 def _get_min_max_value(min, max, value):
"""Return min, max, value given input values with possible None."""
if value is None:
if not max > min:
raise ValueError('max must be greater than min: (min={0}, max={1})'.format(min, max))
value = min + abs(min-max)/2
value = type(min)(value)
elif min is None and max is None:
if value == 0.0:
min, max, value = 0.0, 1.0, 0.5
elif value == 0:
min, max, value = 0, 1, 0
elif isinstance(value, float):
Brian E. Granger
Utter interact insanity....
r15140 min, max = (-value, 3.0*value) if value > 0 else (3.0*value, -value)
Brian E. Granger
Updating interact to work with latest state of widgets.
r15135 elif isinstance(value, int):
Brian E. Granger
Utter interact insanity....
r15140 min, max = (-value, 3*value) if value > 0 else (3*value, -value)
Brian E. Granger
Updating interact to work with latest state of widgets.
r15135 else:
Thomas Kluyver
Get widgets from function annotations and default arguments....
r15137 raise TypeError('expected a number, got: %r' % value)
Brian E. Granger
Updating interact to work with latest state of widgets.
r15135 else:
raise ValueError('unable to infer range, value from: ({0}, {1}, {2})'.format(min, max, value))
Brian E. Granger
Adding interact.py.
r15132 return min, max, value
Thomas Kluyver
Get widgets from function annotations and default arguments....
r15137 def _widget_abbrev_single_value(o):
"""Make widgets from single values, which can be used written as parameter defaults."""
Brian E. Granger
Adding interact.py.
r15132 if isinstance(o, string_types):
Brian E. Granger
Updating interact to work with latest state of widgets.
r15135 return TextWidget(value=unicode_type(o))
Brian E. Granger
Adding interact.py.
r15132 elif isinstance(o, dict):
MinRK
update interact now that SelectionWidget.values is a dict
r15143 # get a single value in a Python 2+3 way:
value = next(iter(o.values()))
return DropdownWidget(value=value, values=o)
Brian E. Granger
Adding interact.py.
r15132 elif isinstance(o, bool):
Brian E. Granger
Updating interact to work with latest state of widgets.
r15135 return CheckboxWidget(value=o)
Brian E. Granger
Adding interact.py.
r15132 elif isinstance(o, float):
Brian E. Granger
Updating interact to work with latest state of widgets.
r15135 min, max, value = _get_min_max_value(None, None, o)
return FloatSliderWidget(value=o, min=min, max=max)
Brian E. Granger
Adding interact.py.
r15132 elif isinstance(o, int):
Brian E. Granger
Updating interact to work with latest state of widgets.
r15135 min, max, value = _get_min_max_value(None, None, o)
return IntSliderWidget(value=o, min=min, max=max)
Brian E. Granger
Utter interact insanity....
r15140 else:
return None
Thomas Kluyver
Get widgets from function annotations and default arguments....
r15137
def _widget_abbrev(o):
"""Make widgets from abbreviations: single values, lists or tuples."""
Brian E. Granger
Adding interact.py.
r15132 if isinstance(o, (list, tuple)):
if _matches(o, (int, int)):
Brian E. Granger
Updating interact to work with latest state of widgets.
r15135 min, max, value = _get_min_max_value(o[0], o[1], None)
return IntSliderWidget(value=value, min=min, max=max)
Brian E. Granger
Adding interact.py.
r15132 elif _matches(o, (int, int, int)):
Brian E. Granger
Updating interact to work with latest state of widgets.
r15135 min, max, value = _get_min_max_value(o[0], o[1], None)
return IntSliderWidget(value=value, min=min, max=max, step=o[2])
Brian E. Granger
Adding interact.py.
r15132 elif _matches(o, (float, float)):
Brian E. Granger
Updating interact to work with latest state of widgets.
r15135 min, max, value = _get_min_max_value(o[0], o[1], None)
Brian E. Granger
Updating interact to new APIs.
r15134 return FloatSliderWidget(value=value, min=min, max=max)
Brian E. Granger
Adding interact.py.
r15132 elif _matches(o, (float, float, float)):
Brian E. Granger
Updating interact to work with latest state of widgets.
r15135 min, max, value = _get_min_max_value(o[0], o[1], None)
Brian E. Granger
Updating interact to new APIs.
r15134 return FloatSliderWidget(value=value, min=min, max=max, step=o[2])
Brian E. Granger
Adding interact.py.
r15132 elif _matches(o, (float, float, int)):
Brian E. Granger
Updating interact to work with latest state of widgets.
r15135 min, max, value = _get_min_max_value(o[0], o[1], None)
Brian E. Granger
Updating interact to new APIs.
r15134 return FloatSliderWidget(value=value, min=min, max=max, step=float(o[2]))
Brian E. Granger
Adding interact.py.
r15132 elif all(isinstance(x, string_types) for x in o):
Brian E. Granger
Updating interact to new APIs.
r15134 return DropdownWidget(value=unicode_type(o[0]),
Brian E. Granger
Adding interact.py.
r15132 values=[unicode_type(k) for k in o])
Thomas Kluyver
Get widgets from function annotations and default arguments....
r15137 else:
return _widget_abbrev_single_value(o)
Brian E. Granger
Utter interact insanity....
r15140 def _widget_from_abbrev(abbrev):
"""Build a Widget intstance given an abbreviation or Widget."""
Brian E. Granger
Adding const pseudo-widget for fixing arguments to interact.
r15142 if isinstance(abbrev, Widget) or isinstance(abbrev, const):
Brian E. Granger
Utter interact insanity....
r15140 return abbrev
Thomas Kluyver
Get widgets from function annotations and default arguments....
r15137
Brian E. Granger
Utter interact insanity....
r15140 widget = _widget_abbrev(abbrev)
Thomas Kluyver
Get widgets from function annotations and default arguments....
r15137 if widget is None:
Brian E. Granger
Utter interact insanity....
r15140 raise ValueError("%r cannot be transformed to a Widget" % abbrev)
Thomas Kluyver
Get widgets from function annotations and default arguments....
r15137 return widget
MinRK
remove positional arg support from interact
r15145 def _yield_abbreviations_for_parameter(param, kwargs):
Brian E. Granger
Utter interact insanity....
r15140 """Get an abbreviation for a function parameter."""
name = param.name
kind = param.kind
ann = param.annotation
default = param.default
empty = Parameter.empty
MinRK
remove positional arg support from interact
r15145 not_found = (None, None)
if kind == Parameter.POSITIONAL_OR_KEYWORD:
Brian E. Granger
Utter interact insanity....
r15140 if name in kwargs:
MinRK
remove positional arg support from interact
r15145 yield name, kwargs.pop(name)
Brian E. Granger
Utter interact insanity....
r15140 elif ann is not empty:
if default is empty:
MinRK
remove positional arg support from interact
r15145 yield name, ann
Brian E. Granger
Utter interact insanity....
r15140 else:
MinRK
remove positional arg support from interact
r15145 yield name, ann
Brian E. Granger
Utter interact insanity....
r15140 elif default is not empty:
MinRK
remove positional arg support from interact
r15145 yield name, default
Brian E. Granger
Utter interact insanity....
r15140 else:
MinRK
remove positional arg support from interact
r15145 yield not_found
Brian E. Granger
Utter interact insanity....
r15140 elif kind == Parameter.KEYWORD_ONLY:
if name in kwargs:
MinRK
remove positional arg support from interact
r15145 yield name, kwargs.pop(name)
Brian E. Granger
Utter interact insanity....
r15140 elif ann is not empty:
MinRK
remove positional arg support from interact
r15145 yield name, ann
Brian E. Granger
Utter interact insanity....
r15140 elif default is not empty:
MinRK
remove positional arg support from interact
r15145 yield name, default
Brian E. Granger
Utter interact insanity....
r15140 else:
MinRK
remove positional arg support from interact
r15145 yield not_found
Brian E. Granger
Utter interact insanity....
r15140 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)
MinRK
remove positional arg support from interact
r15145 yield k, v
Brian E. Granger
Adding interact.py.
r15132
MinRK
remove positional arg support from interact
r15145 def _find_abbreviations(f, kwargs):
"""Find the abbreviations for a function and kwargs passed to interact."""
Brian E. Granger
Utter interact insanity....
r15140 new_kwargs = []
for param in signature(f).parameters.values():
MinRK
remove positional arg support from interact
r15145 for name, value in _yield_abbreviations_for_parameter(param, kwargs):
Brian E. Granger
Utter interact insanity....
r15140 if value is None:
raise ValueError('cannot find widget or abbreviation for argument: {!r}'.format(name))
MinRK
remove positional arg support from interact
r15145 new_kwargs.append((name, value))
return new_kwargs
Brian E. Granger
Utter interact insanity....
r15140
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)
widget.description = name
result.append(widget)
return result
MinRK
remove positional arg support from interact
r15145 def interactive(__interact_f, **kwargs):
Brian E. Granger
Utter interact insanity....
r15140 """Build a group of widgets to interact with a function."""
MinRK
remove positional arg support from interact
r15145 f = __interact_f
Brian E. Granger
Adding interact.py.
r15132 co = kwargs.pop('clear_output', True)
Brian E. Granger
Utter interact insanity....
r15140 kwargs_widgets = []
Brian E. Granger
Adding interact.py.
r15132 container = ContainerWidget()
container.result = None
Brian E. Granger
Utter interact insanity....
r15140 container.args = []
Brian E. Granger
Updating interact to work with latest state of widgets.
r15135 container.kwargs = dict()
Brian E. Granger
Utter interact insanity....
r15140 kwargs = kwargs.copy()
MinRK
remove positional arg support from interact
r15145 new_kwargs = _find_abbreviations(f, kwargs)
Brian E. Granger
Utter interact insanity....
r15140 # 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.
MinRK
remove positional arg support from interact
r15145 getcallargs(f, **{n:v for n,v in new_kwargs})
Brian E. Granger
Utter interact insanity....
r15140 # 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])))
Thomas Kluyver
Get widgets from function annotations and default arguments....
r15137 # This has to be done as an assignment, not using container.children.append,
Brian E. Granger
Adding const pseudo-widget for fixing arguments to interact.
r15142 # so that traitlets notices the update. We skip any objects (such as const) that
# are not DOMWidgets.
MinRK
remove positional arg support from interact
r15145 c = [w for w in kwargs_widgets if isinstance(w, DOMWidget)]
Brian E. Granger
Adding const pseudo-widget for fixing arguments to interact.
r15142 container.children = c
Brian E. Granger
Updating interact to new APIs.
r15134
Brian E. Granger
Adding interact.py.
r15132 # Build the callback
def call_f(name, old, new):
Brian E. Granger
Utter interact insanity....
r15140 container.args = []
for widget in kwargs_widgets:
Brian E. Granger
Adding interact.py.
r15132 value = widget.value
Thomas Kluyver
Get widgets from function annotations and default arguments....
r15137 container.kwargs[widget.description] = value
Brian E. Granger
Adding interact.py.
r15132 if co:
clear_output(wait=True)
Brian E. Granger
Utter interact insanity....
r15140 container.result = f(*container.args, **container.kwargs)
Brian E. Granger
Adding interact.py.
r15132
# Wire up the widgets
Brian E. Granger
Utter interact insanity....
r15140 for widget in kwargs_widgets:
Brian E. Granger
Adding interact.py.
r15132 widget.on_trait_change(call_f, 'value')
Brian E. Granger
Updating interact to work with latest state of widgets.
r15135 container.on_displayed(lambda _: call_f(None, None, None))
Brian E. Granger
Adding interact.py.
r15132
return container
MinRK
remove positional arg support from interact
r15145 def interact(__interact_f=None, **kwargs):
"""interact(f, **kwargs)
Interact with a function using widgets."""
# positional arg support in: https://gist.github.com/8851331
if __interact_f is not None:
Brian E. Granger
Adding decorator forms of interact. Yeah!
r15141 # This branch handles the cases:
MinRK
remove positional arg support from interact
r15145 # 1. interact(f, **kwargs)
Brian E. Granger
Adding decorator forms of interact. Yeah!
r15141 # 2. @interact
# def f(*args, **kwargs):
# ...
MinRK
remove positional arg support from interact
r15145 f = __interact_f
w = interactive(f, **kwargs)
Brian E. Granger
Adding decorator forms of interact. Yeah!
r15141 f.widget = w
display(w)
else:
# This branch handles the case:
MinRK
remove positional arg support from interact
r15145 # @interact(a=30, b=40)
Brian E. Granger
Adding decorator forms of interact. Yeah!
r15141 # def f(*args, **kwargs):
# ...
def dec(f):
MinRK
remove positional arg support from interact
r15145 w = interactive(f, **kwargs)
Brian E. Granger
Adding decorator forms of interact. Yeah!
r15141 f.widget = w
display(w)
return f
return dec
Brian E. Granger
Utter interact insanity....
r15140
Brian E. Granger
Adding const pseudo-widget for fixing arguments to interact.
r15142 class const(HasTraits):
"""A pseudo-widget whose value is constant and never client synced."""
value = Any(help="Any Python object")
description = Unicode('', help="Any Python object")
def __init__(self, value, **kwargs):
super(const, self).__init__(value=value, **kwargs)