Repository: bokeh/bokeh

Issue:
Disallow set type in `ColumnDataSource.data`



Discussion:

Resolving patch:
diff --git a/bokeh/core/properties.py b/bokeh/core/properties.py
--- a/bokeh/core/properties.py
+++ b/bokeh/core/properties.py
@@ -1025,12 +1025,18 @@ def _has_stable_default(self):
         return False
 
 class Seq(ContainerProperty):
-    """ Sequence (list, tuple) type property.
+    """ An ordered sequence of values (list, tuple, (nd)array). """
 
-    """
+    @classmethod
+    def _is_seq(cls, value):
+        return ((isinstance(value, collections.Sequence) or cls._is_seq_like(value)) and
+                not isinstance(value, string_types))
 
-    def _is_seq(self, value):
-        return isinstance(value, collections.Container) and not isinstance(value, collections.Mapping)
+    @classmethod
+    def _is_seq_like(cls, value):
+        return (isinstance(value, (collections.Container, collections.Sized, collections.Iterable))
+                and hasattr(value, "__getitem__") # NOTE: this is what makes it disallow set type
+                and not isinstance(value, collections.Mapping))
 
     def _new_instance(self, value):
         return value
@@ -1079,6 +1085,7 @@ def __init__(self, item_type, default=[], help=None):
         # optional values. Also in Dict.
         super(List, self).__init__(item_type, default=default, help=help)
 
+    @classmethod
     def _is_seq(self, value):
         return isinstance(value, list)
 
@@ -1087,6 +1094,7 @@ class Array(Seq):
 
     """
 
+    @classmethod
     def _is_seq(self, value):
         import numpy as np
         return isinstance(value, np.ndarray)
diff --git a/bokeh/models/sources.py b/bokeh/models/sources.py
--- a/bokeh/models/sources.py
+++ b/bokeh/models/sources.py
@@ -3,7 +3,7 @@
 from ..core import validation
 from ..core.validation.errors import COLUMN_LENGTHS
 from ..core.properties import abstract
-from ..core.properties import Any, Int, String, Instance, List, Dict, Bool, Enum, JSON
+from ..core.properties import Any, Int, String, Instance, List, Dict, Bool, Enum, JSON, Seq
 from ..model import Model
 from ..util.dependencies import import_optional
 from ..util.deprecate import deprecated
@@ -63,7 +63,7 @@ class ColumnDataSource(DataSource):
 
     """
 
-    data = Dict(String, Any, help="""
+    data = Dict(String, Seq(Any), help="""
     Mapping of column names to sequences of data. The data can be, e.g,
     Python lists or tuples, NumPy arrays, etc.
     """)


Test patch:
diff --git a/bokeh/core/tests/test_properties.py b/bokeh/core/tests/test_properties.py
--- a/bokeh/core/tests/test_properties.py
+++ b/bokeh/core/tests/test_properties.py
@@ -3,11 +3,12 @@
 import datetime
 import unittest
 import numpy as np
+import pandas as pd
 from copy import copy
 
 from bokeh.core.properties import (
     HasProps, NumberSpec, ColorSpec, Bool, Int, Float, Complex, String,
-    Regex, List, Dict, Tuple, Array, Instance, Any, Interval, Either,
+    Regex, Seq, List, Dict, Tuple, Array, Instance, Any, Interval, Either,
     Enum, Color, Align, DashPattern, Size, Percent, Angle, AngleSpec,
     DistanceSpec, Override, Include, MinMaxBounds, TitleProp)
 
@@ -1037,6 +1038,37 @@ def test_Regex(self):
         self.assertFalse(prop.is_valid({}))
         self.assertFalse(prop.is_valid(Foo()))
 
+    def test_Seq(self):
+        with self.assertRaises(TypeError):
+            prop = Seq()
+
+        prop = Seq(Int)
+
+        self.assertTrue(prop.is_valid(None))
+        self.assertFalse(prop.is_valid(False))
+        self.assertFalse(prop.is_valid(True))
+        self.assertFalse(prop.is_valid(0))
+        self.assertFalse(prop.is_valid(1))
+        self.assertFalse(prop.is_valid(0.0))
+        self.assertFalse(prop.is_valid(1.0))
+        self.assertFalse(prop.is_valid(1.0+1.0j))
+        self.assertFalse(prop.is_valid(""))
+        self.assertTrue(prop.is_valid(()))
+        self.assertTrue(prop.is_valid([]))
+        self.assertTrue(prop.is_valid(np.array([])))
+        self.assertFalse(prop.is_valid(set([])))
+        self.assertFalse(prop.is_valid({}))
+        self.assertTrue(prop.is_valid((1, 2)))
+        self.assertTrue(prop.is_valid([1, 2]))
+        self.assertTrue(prop.is_valid(np.array([1, 2])))
+        self.assertFalse(prop.is_valid({1, 2}))
+        self.assertFalse(prop.is_valid({1: 2}))
+        self.assertFalse(prop.is_valid(Foo()))
+
+        df = pd.DataFrame([1, 2])
+        self.assertTrue(prop.is_valid(df.index))
+        self.assertTrue(prop.is_valid(df.iloc[0]))
+
     def test_List(self):
         with self.assertRaises(TypeError):
             prop = List()