123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732 |
- # Copyright (c) Jupyter Development Team.
- # Distributed under the terms of the Modified BSD License.
- """Test interact and interactive."""
- from __future__ import print_function
- try:
- from unittest.mock import patch
- except ImportError:
- from mock import patch
- import os
- from collections import OrderedDict
- import pytest
- import ipywidgets as widgets
- from traitlets import TraitError
- from ipywidgets import (interact, interact_manual, interactive,
- interaction, Output)
- from ipython_genutils.py3compat import annotate
- #-----------------------------------------------------------------------------
- # Utility stuff
- #-----------------------------------------------------------------------------
- from .utils import setup, teardown
- def f(**kwargs):
- pass
- displayed = []
- @pytest.fixture()
- def clear_display():
- global displayed
- displayed = []
- def record_display(*args):
- displayed.extend(args)
- #-----------------------------------------------------------------------------
- # Actual tests
- #-----------------------------------------------------------------------------
- def check_widget(w, **d):
- """Check a single widget against a dict"""
- for attr, expected in d.items():
- if attr == 'cls':
- assert w.__class__ is expected
- else:
- value = getattr(w, attr)
- assert value == expected, "%s.%s = %r != %r" % (w.__class__.__name__, attr, value, expected)
- # For numeric values, the types should match too
- if isinstance(value, (int, float)):
- tv = type(value)
- te = type(expected)
- assert tv is te, "type(%s.%s) = %r != %r" % (w.__class__.__name__, attr, tv, te)
- def check_widgets(container, **to_check):
- """Check that widgets are created as expected"""
- # build a widget dictionary, so it matches
- widgets = {}
- for w in container.children:
- if not isinstance(w, Output):
- widgets[w.description] = w
- for key, d in to_check.items():
- assert key in widgets
- check_widget(widgets[key], **d)
- def test_single_value_string():
- a = u'hello'
- c = interactive(f, a=a)
- w = c.children[0]
- check_widget(w,
- cls=widgets.Text,
- description='a',
- value=a,
- )
- def test_single_value_bool():
- for a in (True, False):
- c = interactive(f, a=a)
- w = c.children[0]
- check_widget(w,
- cls=widgets.Checkbox,
- description='a',
- value=a,
- )
- def test_single_value_float():
- for a in (2.25, 1.0, -3.5, 0.0):
- if not a:
- expected_min = 0.0
- expected_max = 1.0
- elif a > 0:
- expected_min = -a
- expected_max = 3*a
- else:
- expected_min = 3*a
- expected_max = -a
- c = interactive(f, a=a)
- w = c.children[0]
- check_widget(w,
- cls=widgets.FloatSlider,
- description='a',
- value=a,
- min=expected_min,
- max=expected_max,
- step=0.1,
- readout=True,
- )
- def test_single_value_int():
- for a in (1, 5, -3, 0):
- if not a:
- expected_min = 0
- expected_max = 1
- elif a > 0:
- expected_min = -a
- expected_max = 3*a
- else:
- expected_min = 3*a
- expected_max = -a
- c = interactive(f, a=a)
- assert len(c.children) == 2
- w = c.children[0]
- check_widget(w,
- cls=widgets.IntSlider,
- description='a',
- value=a,
- min=expected_min,
- max=expected_max,
- step=1,
- readout=True,
- )
- def test_list_str():
- values = ['hello', 'there', 'guy']
- first = values[0]
- c = interactive(f, lis=values)
- assert len(c.children) == 2
- d = dict(
- cls=widgets.Dropdown,
- value=first,
- options=tuple(values),
- _options_labels=tuple(values),
- _options_values=tuple(values),
- )
- check_widgets(c, lis=d)
- def test_list_int():
- values = [3, 1, 2]
- first = values[0]
- c = interactive(f, lis=values)
- assert len(c.children) == 2
- d = dict(
- cls=widgets.Dropdown,
- value=first,
- options=tuple(values),
- _options_labels=tuple(str(v) for v in values),
- _options_values=tuple(values),
- )
- check_widgets(c, lis=d)
- def test_list_tuple():
- values = [(3, 300), (1, 100), (2, 200)]
- first = values[0][1]
- c = interactive(f, lis=values)
- assert len(c.children) == 2
- d = dict(
- cls=widgets.Dropdown,
- value=first,
- options=tuple(values),
- _options_labels=("3", "1", "2"),
- _options_values=(300, 100, 200),
- )
- check_widgets(c, lis=d)
- def test_list_tuple_invalid():
- for bad in [
- (),
- ]:
- with pytest.raises(ValueError):
- print(bad) # because there is no custom message in assert_raises
- c = interactive(f, tup=bad)
- def test_dict():
- for d in [
- dict(a=5),
- dict(a=5, b='b', c=dict),
- ]:
- c = interactive(f, d=d)
- w = c.children[0]
- check = dict(
- cls=widgets.Dropdown,
- description='d',
- value=next(iter(d.values())),
- options=d,
- _options_labels=tuple(d.keys()),
- _options_values=tuple(d.values()),
- )
- check_widget(w, **check)
- def test_ordereddict():
- from collections import OrderedDict
- items = [(3, 300), (1, 100), (2, 200)]
- first = items[0][1]
- values = OrderedDict(items)
- c = interactive(f, lis=values)
- assert len(c.children) == 2
- d = dict(
- cls=widgets.Dropdown,
- value=first,
- options=values,
- _options_labels=("3", "1", "2"),
- _options_values=(300, 100, 200),
- )
- check_widgets(c, lis=d)
- def test_iterable():
- def yield_values():
- yield 3
- yield 1
- yield 2
- first = next(yield_values())
- c = interactive(f, lis=yield_values())
- assert len(c.children) == 2
- d = dict(
- cls=widgets.Dropdown,
- value=first,
- options=(3, 1, 2),
- _options_labels=("3", "1", "2"),
- _options_values=(3, 1, 2),
- )
- check_widgets(c, lis=d)
- def test_iterable_tuple():
- values = [(3, 300), (1, 100), (2, 200)]
- first = values[0][1]
- c = interactive(f, lis=iter(values))
- assert len(c.children) == 2
- d = dict(
- cls=widgets.Dropdown,
- value=first,
- options=tuple(values),
- _options_labels=("3", "1", "2"),
- _options_values=(300, 100, 200),
- )
- check_widgets(c, lis=d)
- def test_mapping():
- from collections import Mapping, OrderedDict
- class TestMapping(Mapping):
- def __init__(self, values):
- self.values = values
- def __getitem__(self):
- raise NotImplementedError
- def __len__(self):
- raise NotImplementedError
- def __iter__(self):
- raise NotImplementedError
- def items(self):
- return self.values
- items = [(3, 300), (1, 100), (2, 200)]
- first = items[0][1]
- values = TestMapping(items)
- c = interactive(f, lis=values)
- assert len(c.children) == 2
- d = dict(
- cls=widgets.Dropdown,
- value=first,
- options=tuple(items),
- _options_labels=("3", "1", "2"),
- _options_values=(300, 100, 200),
- )
- check_widgets(c, lis=d)
- def test_defaults():
- @annotate(n=10)
- def f(n, f=4.5, g=1):
- pass
- c = interactive(f)
- check_widgets(c,
- n=dict(
- cls=widgets.IntSlider,
- value=10,
- ),
- f=dict(
- cls=widgets.FloatSlider,
- value=4.5,
- ),
- g=dict(
- cls=widgets.IntSlider,
- value=1,
- ),
- )
- def test_default_values():
- @annotate(n=10, f=(0, 10.), g=5, h=OrderedDict([('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.IntSlider,
- value=10,
- ),
- f=dict(
- cls=widgets.FloatSlider,
- value=4.5,
- ),
- g=dict(
- cls=widgets.IntSlider,
- value=5,
- ),
- h=dict(
- cls=widgets.Dropdown,
- options=OrderedDict([('a',1), ('b',2)]),
- value=2
- ),
- j=dict(
- cls=widgets.Dropdown,
- options=('hi', '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.FloatSlider,
- value=5.,
- ),
- h=dict(
- cls=widgets.Dropdown,
- options={'a': 1},
- value=1,
- ),
- j=dict(
- cls=widgets.Dropdown,
- options=('hi', 'there'),
- value='hi',
- ),
- )
- def test_annotations():
- @annotate(n=10, f=widgets.FloatText())
- def f(n, f):
- pass
- c = interactive(f)
- check_widgets(c,
- n=dict(
- cls=widgets.IntSlider,
- value=10,
- ),
- f=dict(
- cls=widgets.FloatText,
- ),
- )
- def test_priority():
- @annotate(annotate='annotate', kwarg='annotate')
- def f(kwarg='default', annotate='default', default='default'):
- pass
- c = interactive(f, kwarg='kwarg')
- check_widgets(c,
- kwarg=dict(
- cls=widgets.Text,
- value='kwarg',
- ),
- annotate=dict(
- cls=widgets.Text,
- value='annotate',
- ),
- )
- def test_decorator_kwarg(clear_display):
- with patch.object(interaction, 'display', record_display):
- @interact(a=5)
- def foo(a):
- pass
- assert len(displayed) == 1
- w = displayed[0].children[0]
- check_widget(w,
- cls=widgets.IntSlider,
- value=5,
- )
- def test_interact_instancemethod(clear_display):
- class Foo(object):
- def show(self, x):
- print(x)
- f = Foo()
- with patch.object(interaction, 'display', record_display):
- g = interact(f.show, x=(1,10))
- assert len(displayed) == 1
- w = displayed[0].children[0]
- check_widget(w,
- cls=widgets.IntSlider,
- value=5,
- )
- def test_decorator_no_call(clear_display):
- with patch.object(interaction, 'display', record_display):
- @interact
- def foo(a='default'):
- pass
- assert len(displayed) == 1
- w = displayed[0].children[0]
- check_widget(w,
- cls=widgets.Text,
- value='default',
- )
- def test_call_interact(clear_display):
- def foo(a='default'):
- pass
- with patch.object(interaction, 'display', record_display):
- ifoo = interact(foo)
- assert len(displayed) == 1
- w = displayed[0].children[0]
- check_widget(w,
- cls=widgets.Text,
- value='default',
- )
- def test_call_interact_on_trait_changed_none_return(clear_display):
- def foo(a='default'):
- pass
- with patch.object(interaction, 'display', record_display):
- ifoo = interact(foo)
- assert len(displayed) == 1
- w = displayed[0].children[0]
- check_widget(w,
- cls=widgets.Text,
- value='default',
- )
- with patch.object(interaction, 'display', record_display):
- w.value = 'called'
- assert len(displayed) == 1
- def test_call_interact_kwargs(clear_display):
- def foo(a='default'):
- pass
- with patch.object(interaction, 'display', record_display):
- ifoo = interact(foo, a=10)
- assert len(displayed) == 1
- w = displayed[0].children[0]
- check_widget(w,
- cls=widgets.IntSlider,
- value=10,
- )
- def test_call_decorated_on_trait_change(clear_display):
- """test calling @interact decorated functions"""
- d = {}
- with patch.object(interaction, 'display', record_display):
- @interact
- def foo(a='default'):
- d['a'] = a
- return a
- assert len(displayed) == 1
- w = displayed[0].children[0]
- check_widget(w,
- cls=widgets.Text,
- value='default',
- )
- # test calling the function directly
- a = foo('hello')
- assert a == 'hello'
- assert d['a'] == 'hello'
- # test that setting trait values calls the function
- with patch.object(interaction, 'display', record_display):
- w.value = 'called'
- assert d['a'] == 'called'
- assert len(displayed) == 2
- assert w.value == displayed[-1]
- def test_call_decorated_kwargs_on_trait_change(clear_display):
- """test calling @interact(foo=bar) decorated functions"""
- d = {}
- with patch.object(interaction, 'display', record_display):
- @interact(a='kwarg')
- def foo(a='default'):
- d['a'] = a
- return a
- assert len(displayed) == 1
- w = displayed[0].children[0]
- check_widget(w,
- cls=widgets.Text,
- value='kwarg',
- )
- # test calling the function directly
- a = foo('hello')
- assert a == 'hello'
- assert d['a'] == 'hello'
- # test that setting trait values calls the function
- with patch.object(interaction, 'display', record_display):
- w.value = 'called'
- assert d['a'] == 'called'
- assert len(displayed) == 2
- assert w.value == displayed[-1]
- def test_fixed():
- c = interactive(f, a=widgets.fixed(5), b='text')
- assert len(c.children) == 2
- w = c.children[0]
- check_widget(w,
- cls=widgets.Text,
- value='text',
- description='b',
- )
- def test_default_description():
- c = interactive(f, b='text')
- w = c.children[0]
- check_widget(w,
- cls=widgets.Text,
- value='text',
- description='b',
- )
- def test_custom_description():
- d = {}
- def record_kwargs(**kwargs):
- d.clear()
- d.update(kwargs)
- c = interactive(record_kwargs, b=widgets.Text(value='text', description='foo'))
- w = c.children[0]
- check_widget(w,
- cls=widgets.Text,
- value='text',
- description='foo',
- )
- w.value = 'different text'
- assert d == {'b': 'different text'}
- def test_interact_manual_button():
- c = interact.options(manual=True).widget(f)
- w = c.children[0]
- check_widget(w, cls=widgets.Button)
- def test_interact_manual_nocall():
- callcount = 0
- def calltest(testarg):
- callcount += 1
- c = interact.options(manual=True)(calltest, testarg=5).widget
- c.children[0].value = 10
- assert callcount == 0
- def test_interact_call():
- w = interact.widget(f)
- w.update()
- w = interact_manual.widget(f)
- w.update()
- def test_interact_options():
- def f(x):
- return x
- w = interact.options(manual=False).options(manual=True)(f, x=21).widget
- assert w.manual == True
- w = interact_manual.options(manual=False).options()(x=21).widget(f)
- assert w.manual == False
- w = interact(x=21)().options(manual=True)(f).widget
- assert w.manual == True
- def test_interact_options_bad():
- with pytest.raises(ValueError):
- interact.options(bad="foo")
- def test_int_range_logic():
- irsw = widgets.IntRangeSlider
- w = irsw(value=(2, 4), min=0, max=6)
- check_widget(w, cls=irsw, value=(2, 4), min=0, max=6)
- w.upper = 3
- w.max = 3
- check_widget(w, cls=irsw, value=(2, 3), min=0, max=3)
- w.min = 0
- w.max = 6
- w.lower = 2
- w.upper = 4
- check_widget(w, cls=irsw, value=(2, 4), min=0, max=6)
- w.value = (0, 1) #lower non-overlapping range
- check_widget(w, cls=irsw, value=(0, 1), min=0, max=6)
- w.value = (5, 6) #upper non-overlapping range
- check_widget(w, cls=irsw, value=(5, 6), min=0, max=6)
- w.lower = 2
- check_widget(w, cls=irsw, value=(2, 6), min=0, max=6)
- with pytest.raises(TraitError):
- w.min = 7
- with pytest.raises(TraitError):
- w.max = -1
- w = irsw(min=2, max=3, value=(2, 3))
- check_widget(w, min=2, max=3, value=(2, 3))
- w = irsw(min=100, max=200, value=(125, 175))
- check_widget(w, value=(125, 175))
- with pytest.raises(TraitError):
- irsw(min=2, max=1)
- def test_float_range_logic():
- frsw = widgets.FloatRangeSlider
- w = frsw(value=(.2, .4), min=0., max=.6)
- check_widget(w, cls=frsw, value=(.2, .4), min=0., max=.6)
- w.min = 0.
- w.max = .6
- w.lower = .2
- w.upper = .4
- check_widget(w, cls=frsw, value=(.2, .4), min=0., max=.6)
- w.value = (0., .1) #lower non-overlapping range
- check_widget(w, cls=frsw, value=(0., .1), min=0., max=.6)
- w.value = (.5, .6) #upper non-overlapping range
- check_widget(w, cls=frsw, value=(.5, .6), min=0., max=.6)
- w.lower = .2
- check_widget(w, cls=frsw, value=(.2, .6), min=0., max=.6)
- with pytest.raises(TraitError):
- w.min = .7
- with pytest.raises(TraitError):
- w.max = -.1
- w = frsw(min=2, max=3, value=(2.2, 2.5))
- check_widget(w, min=2., max=3.)
- with pytest.raises(TraitError):
- frsw(min=.2, max=.1)
- def test_multiple_selection():
- smw = widgets.SelectMultiple
- # degenerate multiple select
- w = smw()
- check_widget(w, value=tuple())
- # don't accept random other value when no options
- with pytest.raises(TraitError):
- w.value = (2,)
- check_widget(w, value=tuple())
- # basic multiple select
- w = smw(options=[(1, 1)], value=[1])
- check_widget(w, cls=smw, value=(1,), options=((1, 1),))
- # don't accept random other value
- with pytest.raises(TraitError):
- w.value = w.value + (2,)
- check_widget(w, value=(1,))
- # change options, which resets value
- w.options = w.options + ((2, 2),)
- check_widget(w, options=((1, 1), (2,2)), value=())
- # change value
- w.value = (1,2)
- check_widget(w, value=(1, 2))
- # dict style
- w.options = {1: 1}
- check_widget(w, options={1:1})
- # updating
- with pytest.raises(TraitError):
- w.value = (2,)
- check_widget(w, options={1:1})
- def test_interact_noinspect():
- a = u'hello'
- c = interactive(print, a=a)
- w = c.children[0]
- check_widget(w,
- cls=widgets.Text,
- description='a',
- value=a,
- )
- def test_get_interact_value():
- from ipywidgets.widgets import ValueWidget
- from traitlets import Unicode
- class TheAnswer(ValueWidget):
- _model_name = Unicode('TheAnswer')
- description = Unicode()
- def get_interact_value(self):
- return 42
- w = TheAnswer()
- c = interactive(lambda v: v, v=w)
- c.update()
- assert c.result == 42
- def test_state_schema():
- from ipywidgets.widgets import IntSlider, Widget
- import json
- import jsonschema
- s = IntSlider()
- state = Widget.get_manager_state(drop_defaults=True)
- with open(os.path.join(os.path.dirname(os.path.realpath(__file__)), '../../', 'state.schema.json')) as f:
- schema = json.load(f)
- jsonschema.validate(state, schema)
|