Repository: bokeh/bokeh

Issue:
Correctly deprecating ImageRGBA cols and rows properties
As part of #5544 we are changing how 2D arrays are serialized in bokeh and in reworking that code @bryevdv noticed that unlike the ``Image`` glyph,  the ``ImageRGBA`` glyph supports setting  ``rows`` and ``cols`` properties and setting the image data to a flattened 1D-array. This is both inconsistent and a bit weird so it was suggested that this option should be deprecated.

The question now is how best to do this deprecation. Most of the deprecations currently either rename or move an existing property rather than deprecating it entirely so there is no standard approach here. Currently I see two options:

1) My initial suggestion was to add hidden ``_rows`` and ``_cols`` props which matching rows and cols setter and getter properties write into.

2) As an alternative @bryevdv suggested defining a ``DeprecatedNumberSpec`` property
which would emit a warning on validation.

Neither approach is great, however currently when I try to implement the second approach I noticed that the ``validate`` method is fired twice when supplying the property value to the constructor, causing the deprecation warning to be duplicated. Suggestions welcome on how to correctly perform this deprecation, it would also good to figure out why the value is being set and validated twice in the constructor.


Discussion:
Maybe use this:
```
rows = NumberSpec(None, help="""
    The numbers of rows in the images
    """).asserts(lambda _, __: False, "rows is deprecated, bla", soft=True)
```
Though it would be nice to use `bokeh.util.deprecation` for this. We could extend `asserts()` accordingly.

Resolving patch:
diff --git a/bokeh/core/properties.py b/bokeh/core/properties.py
--- a/bokeh/core/properties.py
+++ b/bokeh/core/properties.py
@@ -52,7 +52,6 @@ class Model(HasProps):
 import logging
 logger = logging.getLogger(__name__)
 
-import warnings
 import collections
 from copy import copy
 import datetime
@@ -72,7 +71,6 @@ class Model(HasProps):
 from ..colors import RGB
 from ..util.dependencies import import_optional
 from ..util.deprecation import deprecated
-from ..util.warnings import BokehUserWarning
 from ..util.future import with_metaclass
 from ..util.string import nice_join
 from .property_containers import PropertyValueList, PropertyValueDict, PropertyValueContainer
@@ -287,20 +285,23 @@ def prepare_value(self, obj_or_cls, name, value):
         if isinstance(obj_or_cls, HasProps):
             obj = obj_or_cls
 
-            for fn, msg, soft in self.assertions:
-                result = fn(obj, value)
-
-                def bring_to_attention(msg):
-                    if soft:
-                        warnings.warn(msg, BokehUserWarning)
-                    else:
-                        raise ValueError(msg)
+            for fn, msg_or_fn in self.assertions:
+                if isinstance(fn, bool):
+                    result = fn
+                else:
+                    result = fn(obj, value)
 
                 if isinstance(result, bool):
                     if not result:
-                        bring_to_attention(msg)
+                        if isinstance(msg_or_fn, string_types):
+                            raise ValueError(msg_or_fn)
+                        else:
+                            msg_or_fn()
                 elif result is not None:
-                    bring_to_attention(msg % result)
+                    if isinstance(msg_or_fn, string_types):
+                        raise ValueError(msg_or_fn % result)
+                    else:
+                        msg_or_fn(result)
 
         return self._wrap_container(value)
 
@@ -313,8 +314,8 @@ def accepts(self, tp, converter):
         self.alternatives.append((tp, converter))
         return self
 
-    def asserts(self, fn, msg, soft=False):
-        self.assertions.append((fn, msg, soft))
+    def asserts(self, fn, msg_or_fn):
+        self.assertions.append((fn, msg_or_fn))
         return self
 
     def __or__(self, other):
diff --git a/bokeh/models/glyphs.py b/bokeh/models/glyphs.py
--- a/bokeh/models/glyphs.py
+++ b/bokeh/models/glyphs.py
@@ -10,6 +10,7 @@
 from ..model import Model
 from ..core.properties import (abstract, AngleSpec, Bool, DistanceSpec, Enum, Float,
                           Include, Instance, Int, NumberSpec, StringSpec)
+from ..util.deprecation import deprecated
 
 from .mappers import ColorMapper, LinearColorMapper
 
@@ -342,11 +343,11 @@ class ImageRGBA(Glyph):
 
     rows = NumberSpec(None, help="""
     The numbers of rows in the images
-    """)
+    """).asserts(False, lambda: deprecated((0, 12, 4), "ImageRGBA.rows", "2D array representation"))
 
     cols = NumberSpec(None, help="""
     The numbers of columns in the images
-    """)
+    """).asserts(False, lambda: deprecated((0, 12, 4), "ImageRGBA.cols", "2D array representation"))
 
     dw = DistanceSpec(help="""
     The widths of the plot regions that the images will occupy.
diff --git a/bokeh/models/sources.py b/bokeh/models/sources.py
--- a/bokeh/models/sources.py
+++ b/bokeh/models/sources.py
@@ -1,10 +1,13 @@
 from __future__ import absolute_import
 
+import warnings
+
 from ..core.properties import abstract
 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.deprecation import deprecated
+from ..util.warnings import BokehUserWarning
 from ..util.serialization import transform_column_source_data
 from .callbacks import Callback
 
@@ -65,8 +68,7 @@ class ColumnDataSource(DataSource):
     Mapping of column names to sequences of data. The data can be, e.g,
     Python lists or tuples, NumPy arrays, etc.
     """).asserts(lambda _, data: len(set(len(x) for x in data.values())) <= 1,
-                 "ColumnDataSource's columns must be of the same length",
-                 soft=True)
+                 lambda: warnings.warn("ColumnDataSource's columns must be of the same length", BokehUserWarning))
 
     column_names = List(String, help="""
     An list of names for all the columns in this DataSource.


Test patch: