diff --git a/IPython/html/widgets/widget.py b/IPython/html/widgets/widget.py
index 4c576b3..6db6715 100644
--- a/IPython/html/widgets/widget.py
+++ b/IPython/html/widgets/widget.py
@@ -308,7 +308,7 @@ class Widget(LoggingConfigurable):
their model ids. Return value must be JSON-able."""
if isinstance(x, dict):
return {k: self._pack_widgets(v) for k, v in x.items()}
- elif isinstance(x, list):
+ elif isinstance(x, (list, tuple)):
return [self._pack_widgets(v) for v in x]
elif isinstance(x, Widget):
return x.model_id
@@ -322,7 +322,7 @@ class Widget(LoggingConfigurable):
their model ids."""
if isinstance(x, dict):
return {k: self._unpack_widgets(v) for k, v in x.items()}
- elif isinstance(x, list):
+ elif isinstance(x, (list, tuple)):
return [self._unpack_widgets(v) for v in x]
elif isinstance(x, string_types):
return x if x not in Widget.widgets else Widget.widgets[x]
@@ -412,7 +412,7 @@ class DOMWidget(Widget):
be added to.
"""
class_list = class_names
- if isinstance(class_list, list):
+ if isinstance(class_list, (list, tuple)):
class_list = ' '.join(class_list)
self.send({
@@ -433,7 +433,7 @@ class DOMWidget(Widget):
be removed from.
"""
class_list = class_names
- if isinstance(class_list, list):
+ if isinstance(class_list, (list, tuple)):
class_list = ' '.join(class_list)
self.send({
diff --git a/IPython/html/widgets/widget_bool.py b/IPython/html/widgets/widget_bool.py
index cce6b98..211ba31 100644
--- a/IPython/html/widgets/widget_bool.py
+++ b/IPython/html/widgets/widget_bool.py
@@ -14,7 +14,7 @@ Represents a boolean using a widget.
# Imports
#-----------------------------------------------------------------------------
from .widget import DOMWidget
-from IPython.utils.traitlets import Unicode, Bool, List
+from IPython.utils.traitlets import Unicode, Bool
#-----------------------------------------------------------------------------
# Classes
diff --git a/IPython/html/widgets/widget_container.py b/IPython/html/widgets/widget_container.py
index ddfccd5..ccbe4d8 100644
--- a/IPython/html/widgets/widget_container.py
+++ b/IPython/html/widgets/widget_container.py
@@ -14,18 +14,20 @@ Represents a container that can be used to group other widgets.
# Imports
#-----------------------------------------------------------------------------
from .widget import DOMWidget
-from IPython.utils.traitlets import Unicode, Bool, List, Instance
+from IPython.utils.traitlets import Unicode, Tuple, TraitError
#-----------------------------------------------------------------------------
# Classes
#-----------------------------------------------------------------------------
+
class ContainerWidget(DOMWidget):
_view_name = Unicode('ContainerView', sync=True)
- # Keys, all private and managed by helper methods. Flexible box model
- # classes...
- children = List(Instance(DOMWidget))
- _children = List(Instance(DOMWidget), sync=True)
+ # Child widgets in the container.
+ # Using a tuple here to force reassignment to update the list.
+ # When a proper notifying-list trait exists, that is what should be used here.
+ children = Tuple()
+ _children = Tuple(sync=True)
def _children_changed(self, name, old, new):
"""Validate children list.
@@ -36,7 +38,7 @@ class ContainerWidget(DOMWidget):
http://www.peterbe.com/plog/uniqifiers-benchmark
which provides the inspiration for using this implementation. Below
I've implemented the `f5` algorithm using Python comprehensions."""
- if new is not None and isinstance(new, list):
+ if new is not None:
seen = {}
def add_item(i):
seen[i.model_id] = True
diff --git a/IPython/html/widgets/widget_float.py b/IPython/html/widgets/widget_float.py
index ec1868e..cad915e 100644
--- a/IPython/html/widgets/widget_float.py
+++ b/IPython/html/widgets/widget_float.py
@@ -14,7 +14,7 @@ Represents an unbounded float using a widget.
# Imports
#-----------------------------------------------------------------------------
from .widget import DOMWidget
-from IPython.utils.traitlets import Unicode, CFloat, Bool, List, Enum
+from IPython.utils.traitlets import Unicode, CFloat, Bool, Enum
#-----------------------------------------------------------------------------
# Classes
diff --git a/IPython/html/widgets/widget_int.py b/IPython/html/widgets/widget_int.py
index 5a706bf..4c9aa2c 100644
--- a/IPython/html/widgets/widget_int.py
+++ b/IPython/html/widgets/widget_int.py
@@ -14,7 +14,7 @@ Represents an unbounded int using a widget.
# Imports
#-----------------------------------------------------------------------------
from .widget import DOMWidget
-from IPython.utils.traitlets import Unicode, CInt, Bool, List, Enum
+from IPython.utils.traitlets import Unicode, CInt, Bool, Enum
#-----------------------------------------------------------------------------
# Classes
diff --git a/IPython/html/widgets/widget_selectioncontainer.py b/IPython/html/widgets/widget_selectioncontainer.py
index 0f9156a..ef91559 100644
--- a/IPython/html/widgets/widget_selectioncontainer.py
+++ b/IPython/html/widgets/widget_selectioncontainer.py
@@ -15,7 +15,7 @@ pages.
# Imports
#-----------------------------------------------------------------------------
from .widget_container import ContainerWidget
-from IPython.utils.traitlets import Unicode, Dict, CInt, List, Instance
+from IPython.utils.traitlets import Unicode, Dict, CInt
#-----------------------------------------------------------------------------
# Classes
diff --git a/IPython/html/widgets/widget_string.py b/IPython/html/widgets/widget_string.py
index e363336..e3505c5 100644
--- a/IPython/html/widgets/widget_string.py
+++ b/IPython/html/widgets/widget_string.py
@@ -14,7 +14,7 @@ Represents a unicode string using a widget.
# Imports
#-----------------------------------------------------------------------------
from .widget import DOMWidget, CallbackDispatcher
-from IPython.utils.traitlets import Unicode, Bool, List
+from IPython.utils.traitlets import Unicode, Bool
#-----------------------------------------------------------------------------
# Classes
diff --git a/IPython/utils/tests/test_traitlets.py b/IPython/utils/tests/test_traitlets.py
index 87f29fb..83fae6e 100644
--- a/IPython/utils/tests/test_traitlets.py
+++ b/IPython/utils/tests/test_traitlets.py
@@ -886,8 +886,13 @@ class TestList(TraitTestBase):
obj = ListTrait()
_default_value = []
- _good_values = [[], [1], list(range(10))]
- _bad_values = [10, [1,'a'], 'a', (1,2)]
+ _good_values = [[], [1], list(range(10)), (1,2)]
+ _bad_values = [10, [1,'a'], 'a']
+
+ def coerce(self, value):
+ if value is not None:
+ value = list(value)
+ return value
class LenListTrait(HasTraits):
@@ -898,8 +903,13 @@ class TestLenList(TraitTestBase):
obj = LenListTrait()
_default_value = [0]
- _good_values = [[1], list(range(2))]
- _bad_values = [10, [1,'a'], 'a', (1,2), [], list(range(3))]
+ _good_values = [[1], [1,2], (1,2)]
+ _bad_values = [10, [1,'a'], 'a', [], list(range(3))]
+
+ def coerce(self, value):
+ if value is not None:
+ value = list(value)
+ return value
class TupleTrait(HasTraits):
@@ -910,8 +920,13 @@ class TestTupleTrait(TraitTestBase):
obj = TupleTrait()
_default_value = None
- _good_values = [(1,), None,(0,)]
- _bad_values = [10, (1,2), [1],('a'), ()]
+ _good_values = [(1,), None, (0,), [1]]
+ _bad_values = [10, (1,2), ('a'), ()]
+
+ def coerce(self, value):
+ if value is not None:
+ value = tuple(value)
+ return value
def test_invalid_args(self):
self.assertRaises(TypeError, Tuple, 5)
@@ -927,8 +942,13 @@ class TestLooseTupleTrait(TraitTestBase):
obj = LooseTupleTrait()
_default_value = (1,2,3)
- _good_values = [(1,), None, (0,), tuple(range(5)), tuple('hello'), ('a',5), ()]
- _bad_values = [10, 'hello', [1], []]
+ _good_values = [(1,), None, [1], (0,), tuple(range(5)), tuple('hello'), ('a',5), ()]
+ _bad_values = [10, 'hello', {}]
+
+ def coerce(self, value):
+ if value is not None:
+ value = tuple(value)
+ return value
def test_invalid_args(self):
self.assertRaises(TypeError, Tuple, 5)
diff --git a/IPython/utils/traitlets.py b/IPython/utils/traitlets.py
index 86fb103..ddea7b7 100644
--- a/IPython/utils/traitlets.py
+++ b/IPython/utils/traitlets.py
@@ -1210,6 +1210,7 @@ class Container(Instance):
To be subclassed by overriding klass.
"""
klass = None
+ _cast_types = ()
_valid_defaults = SequenceTypes
_trait = None
@@ -1273,6 +1274,8 @@ class Container(Instance):
raise TraitError(e)
def validate(self, obj, value):
+ if isinstance(value, self._cast_types):
+ value = self.klass(value)
value = super(Container, self).validate(obj, value)
if value is None:
return value
@@ -1298,6 +1301,7 @@ class Container(Instance):
class List(Container):
"""An instance of a Python list."""
klass = list
+ _cast_types = (tuple,)
def __init__(self, trait=None, default_value=None, minlen=0, maxlen=sys.maxsize,
allow_none=True, **metadata):
@@ -1354,15 +1358,27 @@ class List(Container):
self.length_error(obj, value)
return super(List, self).validate_elements(obj, value)
+
+ def validate(self, obj, value):
+ value = super(List, self).validate(obj, value)
+ if value is None:
+ return value
+
+ value = self.validate_elements(obj, value)
+
+ return value
+
-class Set(Container):
+class Set(List):
"""An instance of a Python set."""
klass = set
+ _cast_types = (tuple, list)
class Tuple(Container):
"""An instance of a Python tuple."""
klass = tuple
+ _cast_types = (list,)
def __init__(self, *traits, **metadata):
"""Tuple(*traits, default_value=None, allow_none=True, **medatata)