Repository: bokeh/bokeh

Issue:
Line renderer raises the "without value specification" when a ColumnDataSource is used
bokeh 0.12.3

Line renderer raises the JS error, while Circle renderer works as expected. There's probably a workaround to define a separate source column for each value of width (highly inefficient).

```python
from bokeh.models import ColumnDataSource
from bokeh.plotting import figure, show
from bokeh.io import output_notebook, push_notebook

output_notebook(hide_banner=True)

source = ColumnDataSource(dict(x=[1.2, 3.4], y=[3.2, 4.5], sz=[20.5, 40.1], lw=[1.1, 2.2]))

plot = figure()
plot.circle('x', 'y', source=source, size='sz') # This line works fine
plot.line('x', 'y', source=source, line_alpha2='sz') # This line raises 
# Uncaught Error: attempted to retrieve property value for property without value specification
show(plot)
```


Discussion:
Currently `Line` glyph only supports scalar values for `line_*` properties. This seems like some sort of optimization or what not (@bryevdv?). The offending line is https://github.com/bokeh/bokeh/blob/master/bokehjs/src/coffee/models/glyphs/line.coffee#L13.
`line` only draws one single line, it's. It is not meaningful for it to have vectorized line properties. Perhaps you want `multi_line`?
@bryevdv, so we have to make the API reflect this limitation.
Agreed, I thought it did already. 

Resolving patch:
diff --git a/bokeh/core/properties.py b/bokeh/core/properties.py
--- a/bokeh/core/properties.py
+++ b/bokeh/core/properties.py
@@ -312,6 +312,19 @@ class String(PrimitiveProperty):
     '''
     _underlying_type = string_types
 
+class FontSize(String):
+
+    _font_size_re = re.compile(r"^[0-9]+(.[0-9]+)?(%|em|ex|ch|ic|rem|vw|vh|vi|vb|vmin|vmax|cm|mm|q|in|pc|pt|px)$", re.I)
+
+    def validate(self, value):
+        super(FontSize, self).validate(value)
+
+        if isinstance(value, string_types):
+            if len(value) == 0:
+                raise ValueError("empty string is not a valid font size value")
+            elif self._font_size_re.match(value) is None:
+                raise ValueError("%r is not a valid font size value" % value)
+
 class Regex(String):
     ''' Accept strings that match a given regular expression.
 
@@ -1630,26 +1643,16 @@ class FontSizeSpec(DataSpec):
     https://drafts.csswg.org/css-values/#lengths
 
     '''
-    _font_size_re = re.compile(r"^[0-9]+(.[0-9]+)?(%|em|ex|ch|ic|rem|vw|vh|vi|vb|vmin|vmax|cm|mm|q|in|pc|pt|px)$", re.I)
 
     def __init__(self, default, help=None, key_type=_ExprFieldValueTransform):
-        super(FontSizeSpec, self).__init__(key_type, List(String), default=default, help=help)
-
-    def prepare_value(self, cls, name, value):
-        if isinstance(value, string_types) and self._font_size_re.match(value) is not None:
-            value = dict(value=value)
-        return super(FontSizeSpec, self).prepare_value(cls, name, value)
+        super(FontSizeSpec, self).__init__(key_type, FontSize, default=default, help=help)
 
     def validate(self, value):
+        # We want to preserve existing semantics and be a little more restrictive. This
+        # validations makes m.font_size = "" or m.font_size = "6" an error
         super(FontSizeSpec, self).validate(value)
-
-        if isinstance(value, dict) and 'value' in value:
-            value = value['value']
-
         if isinstance(value, string_types):
-            if len(value) == 0:
-                raise ValueError("empty string is not a valid font size value")
-            elif value[0].isdigit() and self._font_size_re.match(value) is None:
+            if len(value) == 0 or value[0].isdigit() and FontSize._font_size_re.match(value) is None:
                 raise ValueError("%r is not a valid font size value" % value)
 
 _ExprFieldValueTransformUnits = Enum("expr", "field", "value", "transform", "units")
diff --git a/bokeh/core/property_mixins.py b/bokeh/core/property_mixins.py
--- a/bokeh/core/property_mixins.py
+++ b/bokeh/core/property_mixins.py
@@ -43,70 +43,61 @@ class SomeGlyph(Glyph):
 
 from .enums import LineJoin, LineCap, FontStyle, TextAlign, TextBaseline
 from .has_props import HasProps
-from .properties import ColorSpec, DashPattern, Enum, FontSizeSpec, Int, Float, NumberSpec, String, value
+from .properties import Color, ColorSpec, DashPattern, Enum, FontSize, FontSizeSpec, Include, Int, Float, NumberSpec, Percent, String, value
 
-class FillProps(HasProps):
-    ''' Properties relevant to rendering fill regions.
 
-    Mirrors the BokehJS ``properties.Fill`` class.
+_color_help = """
+A color to use to %s with.
 
-    '''
+Acceptable values are:
 
-    fill_color = ColorSpec(default="gray", help="""
-    A color to use to fill paths with.
+- any of the 147 named `CSS colors`_, e.g ``'green'``, ``'indigo'``
+- an RGB(A) hex value, e.g., ``'#FF0000'``, ``'#44444444'``
+- a 3-tuple of integers (r,g,b) between 0 and 255
+- a 4-tuple of (r,g,b,a) where r,g,b are integers between 0..255 and a is between 0..1
 
-    Acceptable values are:
+.. _CSS colors: http://www.w3schools.com/cssref/css_colornames.asp
 
-    - any of the 147 named `CSS colors`_, e.g ``'green'``, ``'indigo'``
-    - an RGB(A) hex value, e.g., ``'#FF0000'``, ``'#44444444'``
-    - a 3-tuple of integers (r,g,b) between 0 and 255
-    - a 4-tuple of (r,g,b,a) where r,g,b are integers between 0..255 and a is between 0..1
+"""
 
-    .. _CSS colors: http://www.w3schools.com/cssref/css_colornames.asp
+_alpha_help = """
+An alpha value to use to %s with.
 
-    """)
+Acceptable values are floating point numbers between 0 (transparent)
+and 1 (opaque).
 
-    fill_alpha = NumberSpec(default=1.0, accept_datetime=False, help="""
-    An alpha value to use to fill paths with.
+"""
 
-    Acceptable values are floating point numbers between 0 (transparent)
-    and 1 (opaque).
+class FillProps(HasProps):
+    ''' Properties relevant to rendering fill regions.
 
-    """)
+    Mirrors the BokehJS ``properties.Fill`` class.
 
-class LineProps(HasProps):
-    ''' Properties relevant to rendering path operations.
+    '''
 
-    Mirrors the BokehJS ``properties.Line`` class.
+    fill_color = ColorSpec(default="gray", help=_color_help % "fill paths")
+    fill_alpha = NumberSpec(default=1.0, accept_datetime=False, help=_alpha_help % "fill paths")
 
-    '''
+class ScalarFillProps(HasProps):
+    ''' Properties relevant to rendering fill regions.
 
-    line_color = ColorSpec(default="black", help="""
-    A color to use to stroke paths with.
+    Mirrors the BokehJS ``properties.Fill`` class.
 
-    Acceptable values are:
+    '''
 
-    - any of the 147 named `CSS colors`_, e.g ``'green'``, ``'indigo'``
-    - an RGB(A) hex value, e.g., ``'#FF0000'``, ``'#44444444'``
-    - a 3-tuple of integers (r,g,b) between 0 and 255
-    - a 4-tuple of (r,g,b,a) where r,g,b are integers between 0..255 and a is between 0..1
+    fill_color = Color(default="gray", help=_color_help)
+    fill_alpha = Percent(default=1.0, help=_alpha_help)
 
-    .. _CSS colors: http://www.w3schools.com/cssref/css_colornames.asp
 
-    """)
 
-    line_width = NumberSpec(default=1, accept_datetime=False, help="""
-    Stroke width in units of pixels.
-    """)
 
-    line_alpha = NumberSpec(default=1.0, accept_datetime=False, help="""
-    An alpha value to use to stroke paths with.
 
-    Acceptable values are floating point numbers between 0 (transparent)
-    and 1 (opaque).
 
-    """)
+_line_width_help = """
+Stroke width in units of pixels.
+"""
 
+class BaseLineProps(HasProps):
     line_join = Enum(LineJoin, help="""
     How path segments should be joined together.
 
@@ -152,16 +143,36 @@ class LineProps(HasProps):
     start from.
     """)
 
-class TextProps(HasProps):
-    ''' Properties relevant to rendering text.
+class LineProps(HasProps):
+    ''' Properties relevant to rendering path operations.
 
-    Mirrors the BokehJS ``properties.Text`` class.
+    Mirrors the BokehJS ``properties.Line`` class.
 
-    .. note::
-        There is currently only support for filling text. An interface
-        to stroke the outlines of text has not yet been exposed.
+    '''
+
+    base_line_props = Include(BaseLineProps, use_prefix=False)
+
+    line_color = ColorSpec(default="black", help=_color_help % "stroke paths")
+    line_width = NumberSpec(default=1, accept_datetime=False, help=_line_width_help)
+    line_alpha = NumberSpec(default=1.0, accept_datetime=False, help=_alpha_help % "stroke paths")
+
+
+class ScalarLineProps(HasProps):
+    ''' Properties relevant to rendering path operations.
+
+    Mirrors the BokehJS ``properties.Line`` class.
 
     '''
+    base_line_props = Include(BaseLineProps, use_prefix=False)
+
+    line_color = Color(default="black", help=_color_help % "stroke paths")
+    line_width = Float(default=1, help=_line_width_help)
+    line_alpha = Percent(default=1.0, help=_alpha_help % "stroke paths")
+
+
+
+
+class BaseTextProps(HasProps):
 
     text_font = String("helvetica", help="""
     Name of a font to use for rendering text, e.g., ``'times'``,
@@ -169,8 +180,6 @@ class TextProps(HasProps):
 
     """)
 
-    text_font_size = FontSizeSpec(value("12pt"))
-
     text_font_style = Enum(FontStyle, help="""
     A style to use for rendering text.
 
@@ -182,28 +191,6 @@ class TextProps(HasProps):
 
     """)
 
-    text_color = ColorSpec(default="#444444", help="""
-    A color to use to fill text with.
-
-    Acceptable values are:
-
-    - any of the 147 named `CSS colors`_, e.g ``'green'``, ``'indigo'``
-    - an RGB(A) hex value, e.g., ``'#FF0000'``, ``'#44444444'``
-    - a 3-tuple of integers (r,g,b) between 0 and 255
-    - a 4-tuple of (r,g,b,a) where r,g,b are integers between 0..255 and a is between 0..1
-
-    .. _CSS colors: http://www.w3schools.com/cssref/css_colornames.asp
-
-    """)
-
-    text_alpha = NumberSpec(default=1.0, accept_datetime=False, help="""
-    An alpha value to use to fill text with.
-
-    Acceptable values are floating point numbers between 0 (transparent)
-    and 1 (opaque).
-
-    """)
-
     text_align = Enum(TextAlign, help="""
     Horizontal anchor point to use when rendering text.
 
@@ -235,3 +222,42 @@ class TextProps(HasProps):
     a percentage of font size. The default is 120%. Setting it to 1.0, so
     100%, means no additional space will be used.
     """)
+
+
+class TextProps(HasProps):
+    ''' Properties relevant to rendering text.
+
+    Mirrors the BokehJS ``properties.Text`` class.
+
+    .. note::
+        There is currently only support for filling text. An interface
+        to stroke the outlines of text has not yet been exposed.
+
+    '''
+    base_text_props = Include(BaseTextProps, use_prefix=False)
+
+    text_font_size = FontSizeSpec(value("12pt"))
+
+    text_color = ColorSpec(default="#444444", help=_color_help % "fill text")
+
+    text_alpha = NumberSpec(default=1.0, accept_datetime=False, help=_alpha_help % "fill text")
+
+class ScalarTextProps(HasProps):
+    ''' Properties relevant to rendering text.
+
+    Mirrors the BokehJS ``properties.Text`` class.
+
+    .. note::
+        There is currently only support for filling text. An interface
+        to stroke the outlines of text has not yet been exposed.
+
+    '''
+
+    base_text_props = Include(BaseTextProps, use_prefix=False)
+
+    # XXX not great
+    text_font_size = FontSize("12pt")
+
+    text_color = Color(default="#444444", help=_color_help % "fill text")
+
+    text_alpha = Percent(default=1.0, help=_alpha_help % "fill text")
diff --git a/bokeh/models/glyphs.py b/bokeh/models/glyphs.py
--- a/bokeh/models/glyphs.py
+++ b/bokeh/models/glyphs.py
@@ -41,7 +41,7 @@
 from ..core.has_props import abstract
 from ..core.properties import (AngleSpec, Bool, DistanceSpec, Enum, Float,
                                Include, Instance, Int, NumberSpec, StringSpec)
-from ..core.property_mixins import FillProps, LineProps, TextProps
+from ..core.property_mixins import FillProps, LineProps, ScalarFillProps, ScalarLineProps, TextProps
 from ..model import Model
 
 from .mappers import ColorMapper, LinearColorMapper
@@ -533,7 +533,7 @@ class Line(XYGlyph):
     The y-coordinates for the points of the line.
     """)
 
-    line_props = Include(LineProps, use_prefix=False, help="""
+    line_props = Include(ScalarLineProps, use_prefix=False, help="""
     The %s values for the line.
     """)
 
@@ -638,11 +638,11 @@ class Patch(XYGlyph):
         values in the sequence.
     """)
 
-    line_props = Include(LineProps, use_prefix=False, help="""
+    line_props = Include(ScalarLineProps, use_prefix=False, help="""
     The %s values for the patch.
     """)
 
-    fill_props = Include(FillProps, use_prefix=False, help="""
+    fill_props = Include(ScalarFillProps, use_prefix=False, help="""
     The %s values for the patch.
     """)
 


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
@@ -12,7 +12,7 @@
     NumberSpec, ColorSpec, Bool, Int, Float, Complex, String,
     Regex, Seq, List, Dict, Tuple, Instance, Any, Interval, Either,
     Enum, Color, DashPattern, Size, Percent, Angle, AngleSpec, StringSpec,
-    DistanceSpec, FontSizeSpec, Override, Include, MinMaxBounds,
+    DistanceSpec, FontSize, FontSizeSpec, Override, Include, MinMaxBounds,
     DataDistanceSpec, ScreenDistanceSpec, ColumnData, UnitsSpec)
 
 from bokeh.core.property.containers import PropertyValueColumnData, PropertyValueDict, PropertyValueList
@@ -591,12 +591,12 @@ class Foo(HasProps):
 
             v = '10%s' % unit
             a.x = v
-            self.assertEqual(a.x, dict(value=v))
+            self.assertEqual(a.x, v)
             self.assertEqual(a.lookup('x').serializable_value(a), dict(value=v))
 
             v = '10.2%s' % unit
             a.x = v
-            self.assertEqual(a.x, dict(value=v))
+            self.assertEqual(a.x, v)
             self.assertEqual(a.lookup('x').serializable_value(a), dict(value=v))
 
             f = '_10%s' % unit
@@ -612,12 +612,12 @@ class Foo(HasProps):
         for unit in css_units.upper().split("|"):
             v = '10%s' % unit
             a.x = v
-            self.assertEqual(a.x, dict(value=v))
+            self.assertEqual(a.x, v)
             self.assertEqual(a.lookup('x').serializable_value(a), dict(value=v))
 
             v = '10.2%s' % unit
             a.x = v
-            self.assertEqual(a.x, dict(value=v))
+            self.assertEqual(a.x, v)
             self.assertEqual(a.lookup('x').serializable_value(a), dict(value=v))
 
             f = '_10%s' % unit
@@ -1192,6 +1192,7 @@ def test_String(self):
         prop = String()
 
         self.assertTrue(prop.is_valid(None))
+
         self.assertFalse(prop.is_valid(False))
         self.assertFalse(prop.is_valid(True))
         self.assertFalse(prop.is_valid(0))
@@ -1200,11 +1201,60 @@ def test_String(self):
         self.assertFalse(prop.is_valid(1.0))
         self.assertFalse(prop.is_valid(1.0+1.0j))
         self.assertTrue(prop.is_valid(""))
+        self.assertTrue(prop.is_valid("6"))
         self.assertFalse(prop.is_valid(()))
         self.assertFalse(prop.is_valid([]))
         self.assertFalse(prop.is_valid({}))
         self.assertFalse(prop.is_valid(Foo()))
 
+    def test_FontSize(self):
+
+        prop = FontSize()
+
+        self.assertTrue(prop.is_valid(None))
+
+        css_units = "%|em|ex|ch|ic|rem|vw|vh|vi|vb|vmin|vmax|cm|mm|q|in|pc|pt|px"
+
+        for unit in css_units.split("|"):
+            v = '10%s' % unit
+            self.assertTrue(prop.is_valid(v))
+
+            v = '10.2%s' % unit
+            self.assertTrue(prop.is_valid(v))
+
+            v = '_10%s' % unit
+            self.assertFalse(prop.is_valid(v))
+
+            v = '_10.2%s' % unit
+            self.assertFalse(prop.is_valid(v))
+
+        for unit in css_units.upper().split("|"):
+            v = '10%s' % unit
+            self.assertTrue(prop.is_valid(v))
+
+            v = '10.2%s' % unit
+            self.assertTrue(prop.is_valid(v))
+
+            v = '_10%s' % unit
+            self.assertFalse(prop.is_valid(v))
+
+            v = '_10.2%s' % unit
+            self.assertFalse(prop.is_valid(v))
+
+        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.assertFalse(prop.is_valid(()))
+        self.assertFalse(prop.is_valid([]))
+        self.assertFalse(prop.is_valid({}))
+        self.assertFalse(prop.is_valid(Foo()))
+
+
     def test_Regex(self):
         with self.assertRaises(TypeError):
             prop = Regex()