Repository: bokeh/bokeh

Issue:
ColumnDataSource.stream() setter argument is not documented
I'm trying to figure out how to store state in my [callback()](https://stackoverflow.com/questions/44841436/how-to-store-and-retrieve-state-in-a-callback) method so that I can maintain a pointer to the current location in my source database.  I've been looking through the bokeh source code and I noticed an argument that looks interesting, but it isn't documented:

```
    def stream(self, new_data, rollover=None, setter=None):
        ''' Efficiently update data source columns with new append-only data.
        In cases where it is necessary to update data columns in, this method
        can efficiently send only the new data, instead of requiring the
        entire data set to be re-sent.
        Args:
            new_data (dict[str, seq]) : a mapping of column names to sequences of
                new data to append to each column.
                All columns of the data source must be present in ``new_data``,
                with identical-length append data.
            rollover (int, optional) : A maximum column size, above which data
                from the start of the column begins to be discarded. If None,
                then columns will continue to grow unbounded (default: None)
        Returns:
            None
        Raises:
            ValueError
```

https://github.com/bokeh/bokeh/blob/master/bokeh/models/sources.py#L211


Discussion:
This argument should never be used by users, it's documented on the very low level property descriptor:

https://github.com/bokeh/bokeh/blob/master/bokeh/core/property/descriptors.py#L141-L150

@philippjfr AFACT this parameter in the public API is only used in `Document.apply_json_patch`. Perhaps we could rename `.stream` to `._stream` and have `Document` use that internally, then the public `.stream` API could remove `setter` entirely (and just call `._stream` with no `setter` set)
Or we could just add a note that says it's not for general use. 
> we could rename .stream to ._stream and have Document use that internally, then the public .stream

I would prefer this approach.
>Perhaps we could rename .stream to ._stream and have Document use that internally, then the public .stream API could remove setter entirely.

That seems sensible to me.
@snowch regarding your actual problem that you are trying to solve, the mailing list is probably the best place:

https://groups.google.com/a/continuum.io/forum/#!forum/bokeh

Since you have already been looking into the bokeh source code, would you be interested in working up PR to remove `setter` from the public API (of `stream` and a few other functions)?
Thanks @bryevdv.  

I'm having a bit of a think in terms of my direction with bokeh.  Originally, I was looking for something like shiny + shiny server, but for python.  I was lured to bokeh by the ease at which I could create some useful visualisations, however, It seems that there is a steep learning curve to go from this to create dashboards programmatically with bokeh server.  For example, my next challenge is to figure out how to add authentication to my dashboard app, and it's proving difficult to work out where I do that.  I've been through the tornado docs and started looking through the bokeh code to try to figure out where the frameworks are integrated and where I need to add login interceptors, however, having to go though code and understand two frameworks is quite challenging.  Also, I'd prefer not to delve into any framework source code.  I've seen a few references to the happiness demo - it sounds like the goal of that demo was to show how you can build a dashboard application with bokeh.  Maybe that is the missing piece to my puzzle?

In parallel, I'm going to start evaluating a different approach by creating a data webservice along with a browser visualisation library like d3.  I'm thinking that this approach may be quicker, but I won't know until I try.

I'm feeding this back because I think it's useful for the bokeh team to understand where people are running into issues.  Apologies if this sort of discussion is more suited to the mailing list.


@snowch Since there are so many different possible routes to authentication and validation, and since we are not experts, the general notion at least for now is that the best approach is a "hands-off" one, i.e. for people to layer whatever authentication system they already use in front of Bokeh apps. For example by putting the app behind a reverse proxy that limits access based on authentication. Unless I am mistaken, this is basically the [same approach taken by Shiny server](https://auth0.com/blog/adding-authentication-to-shiny-server/). That said, Tornado *does* have a hook for authentication, it's just not easily exposed to Bokeh apps at the moment. We are looking at making that available for the next release. This is the relevant issue in case you are interested in following progress: https://github.com/bokeh/bokeh/issues/5876

Resolving patch:
diff --git a/bokeh/document.py b/bokeh/document.py
--- a/bokeh/document.py
+++ b/bokeh/document.py
@@ -372,7 +372,7 @@ def apply_json_patch(self, patch, setter=None):
                 source = self._all_models[source_id]
                 data = event_json['data']
                 rollover = event_json['rollover']
-                source.stream(data, rollover, setter)
+                source._stream(data, rollover, setter)
             elif event_json['kind'] == 'ColumnsPatched':
                 source_id = event_json['column_source']['id']
                 if source_id not in self._all_models:
diff --git a/bokeh/models/sources.py b/bokeh/models/sources.py
--- a/bokeh/models/sources.py
+++ b/bokeh/models/sources.py
@@ -209,7 +209,7 @@ def remove(self, name):
             import warnings
             warnings.warn("Unable to find column '%s' in data source" % name)
 
-    def stream(self, new_data, rollover=None, setter=None):
+    def stream(self, new_data, rollover=None):
         ''' Efficiently update data source columns with new append-only data.
 
         In cases where it is necessary to update data columns in, this method
@@ -235,6 +235,58 @@ def stream(self, new_data, rollover=None, setter=None):
 
         Example:
 
+        .. code-block:: python
+
+            source = ColumnDataSource(data=dict(foo=[], bar=[]))
+
+            # has new, identical-length updates for all columns in source
+            new_data = {
+                'foo' : [10, 20],
+                'bar' : [100, 200],
+            }
+
+            source.stream(new_data)
+
+        '''
+        # calls internal implementation
+        self._stream(new_data, rollover)
+
+    def _stream(self, new_data, rollover=None, setter=None):
+        ''' Internal implementation to efficiently update data source columns
+        with new append-only data.   The interal implementation adds the setter
+        attribute.  [https://github.com/bokeh/bokeh/issues/6577]
+
+        In cases where it is necessary to update data columns in, this method
+        can efficiently send only the new data, instead of requiring the
+        entire data set to be re-sent.
+
+        Args:
+            new_data (dict[str, seq]) : a mapping of column names to sequences of
+                new data to append to each column.
+
+                All columns of the data source must be present in ``new_data``,
+                with identical-length append data.
+
+            rollover (int, optional) : A maximum column size, above which data
+                from the start of the column begins to be discarded. If None,
+                then columns will continue to grow unbounded (default: None)
+            setter (ClientSession or ServerSession or None, optional) :
+                This is used to prevent "boomerang" updates to Bokeh apps.
+                (default: None)
+                In the context of a Bokeh server application, incoming updates
+                to properties will be annotated with the session that is
+                doing the updating. This value is propagated through any
+                subsequent change notifications that the update triggers.
+                The session can compare the event setter to itself, and
+                suppress any updates that originate from itself.
+        Returns:
+            None
+
+        Raises:
+            ValueError
+
+        Example:
+
         .. code-block:: python
 
             source = ColumnDataSource(data=dict(foo=[], bar=[]))


Test patch:
diff --git a/bokeh/models/tests/test_sources.py b/bokeh/models/tests/test_sources.py
--- a/bokeh/models/tests/test_sources.py
+++ b/bokeh/models/tests/test_sources.py
@@ -119,19 +119,35 @@ def test_stream_bad_data(self):
             str(cm.exception).startswith("stream(...) only supports 1d sequences, got ndarray with size (")
         )
 
-    def test_stream_good_data(self):
+    def test__stream_good_data(self):
         ds = ColumnDataSource(data=dict(a=[10], b=[20]))
         ds._document = "doc"
         stuff = {}
         mock_setter = object()
+
         def mock(*args, **kw):
             stuff['args'] = args
             stuff['kw'] = kw
         ds.data._stream = mock
-        ds.stream(dict(a=[11, 12], b=[21, 22]), "foo", mock_setter)
+        # internal implementation of stream
+        ds._stream(dict(a=[11, 12], b=[21, 22]), "foo", mock_setter)
         self.assertEqual(stuff['args'], ("doc", ds, dict(a=[11, 12], b=[21, 22]), "foo", mock_setter))
         self.assertEqual(stuff['kw'], {})
 
+    def test_stream_good_data(self):
+        ds = ColumnDataSource(data=dict(a=[10], b=[20]))
+        ds._document = "doc"
+        stuff = {}
+
+        def mock(*args, **kw):
+            stuff['args'] = args
+            stuff['kw'] = kw
+        ds.data._stream = mock
+        # public implementation of stream
+        ds._stream(dict(a=[11, 12], b=[21, 22]), "foo")
+        self.assertEqual(stuff['args'], ("doc", ds, dict(a=[11, 12], b=[21, 22]), "foo", None))
+        self.assertEqual(stuff['kw'], {})
+
     def test_patch_bad_columns(self):
         ds = ColumnDataSource(data=dict(a=[10, 11], b=[20, 21]))
         with self.assertRaises(ValueError) as cm: