Repository: bokeh/bokeh

Issue:
AjaxDataSource Data Initialization
Using AjaxDataSource from an INLINE page fails to plot without a workaround to initialize the source.data member.

The issue was identified and diagnosed here:

http://stackoverflow.com/questions/37083998/flask-bokeh-ajaxdatasource

Bokeh version:

~ > bokeh info
Python version      :  2.7.11 |Anaconda 2.4.1 (x86_64)| (default, Dec  6 2015, 18:57:58) 
IPython version     :  4.2.0
Bokeh version       :  0.11.1

Code example with workaround labeled:

```
from flask import Flask, jsonify
from jinja2 import Template
import math

from bokeh.plotting import figure
from bokeh.models import AjaxDataSource
from bokeh.embed import components
from bokeh.resources import INLINE
from bokeh.util.string import encode_utf8


app = Flask(__name__)

x, y = 0, 0

@app.route("/data", methods=['POST'])
def get_x():
    global x, y
    x = x + 0.1
    y = math.sin(x)
    return jsonify(x=[x], y=[y])

template = Template('''<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <title>Streaming Example</title>
        {{ js_resources }}
        {{ css_resources }}
    </head>
    <body>
    {{ plot_div }}
    {{ plot_script }}
    </body>
</html>
''')

@app.route("/")
def simple():
    streaming=True
    source = AjaxDataSource(data_url="http://localhost:5000/data",
                            polling_interval=1000, mode='append')

    # This line is the workaround - if you comment it out, the plot will not render
    source.data = dict(x=[], y=[])

    fig = figure(title="Streaming Example")
    fig.line( 'x', 'y', source=source)

    js_resources = INLINE.render_js()
    css_resources = INLINE.render_css()

    script, div = components(fig, INLINE)

    html = template.render(
        plot_script=script,
        plot_div=div,
        js_resources=js_resources,
        css_resources=css_resources
    )

    return encode_utf8(html)

app.run(debug=True)
```



Discussion:
Thanks for the detailed report @flailingsquirrel 

The error is raised here:

https://github.com/bokeh/bokeh/blob/9491d517a84edc528ac84970cb338b5c24ac1d66/bokehjs/src/coffee/core/properties.coffee#L57-L62

Basically the assumptions are too strict for `AjaxDataSource`. I see two possible solutions:
- Add a type check there, and don't throw the exception if the source is a `RemoteDataSource`
- instead of reaching in and looking at `source.data` directly, implement a protocol, so that sources can define their own levels of "strictness" (this just means add some methods to ask data sources about whether they have a given column or not, instead of looking by hand directly at `data`)


Resolving patch:
diff --git a/bokeh/models/sources.py b/bokeh/models/sources.py
--- a/bokeh/models/sources.py
+++ b/bokeh/models/sources.py
@@ -638,37 +638,61 @@ class GeoJSONDataSource(ColumnarDataSource):
 
 @abstract
 class RemoteSource(ColumnDataSource):
-    '''
+    ''' Base class for remote column data sources that can update from data
+    URLs at prescribed time intervals.
+
+    .. note::
+        This base class is typically not useful to instantiate on its own.
 
     '''
 
     data_url = String(help="""
-    The URL to the endpoint for the data.
+    A URL to to fetch data from.
     """)
 
     polling_interval = Int(help="""
-    polling interval for updating data source in milliseconds
+    A polling interval (in milliseconds) for updating data source.
     """)
 
 class AjaxDataSource(RemoteSource):
-    '''
+    ''' A data source that can populate columns by making Ajax calls to REST
+    enpoints.
+
+    The ``AjaxDataSource`` can be especially useful if you want to make a
+    standalone document (i.e. not backed by the Bokeh server) that can still
+    dynamically update using an existing REST API.
+
+    The response from the REST API should match the ``.data`` proeprty of a
+    standard ``ColumnDataSource``, i.e. a JSON dict that maps names to arrays
+    of values:
+
+    .. code-block:: python
+
+        {
+            'x' : [1, 2, 3, ...],
+            'y' : [9, 3, 2, ...]
+        }
+
+    A full example can be seen at :bokeh-tree:`examples/howto/ajax_source.py`
 
     '''
 
-    method = Enum('POST', 'GET', help="http method - GET or POST")
+    method = Enum('POST', 'GET', help="""
+    Specifiy the the HTTP method to use for the Ajax request (GET or POST)
+    """)
 
     mode = Enum("replace", "append", help="""
-    Whether to append new data to existing data (up to ``max_size``),
-    or to replace existing data entirely.
+    Whether to append new data to existing data (up to ``max_size``), or to
+    replace existing data entirely.
     """)
 
     max_size = Int(help="""
-    Maximum size of the data array being kept after each pull requests.
-    Larger than that size, the data will be right shifted.
+    Maximum size of the data columns. If a new fetch would result in columns
+    larger than ``max_size``, then earlier data is dropped to make room.
     """)
 
     if_modified = Bool(False, help="""
-    Whether to include an ``If-Modified-Since`` header in AJAX requests
+    Whether to include an ``If-Modified-Since`` header in Ajax requests
     to the server. If this header is supported by the server, then only
     new data since the last request will be returned.
     """)
@@ -678,5 +702,12 @@ class AjaxDataSource(RemoteSource):
     """)
 
     http_headers = Dict(String, String, help="""
-    HTTP headers to set for the Ajax request.
+    Specify HTTP headers to set for the Ajax request.
+
+    Example:
+
+    .. code-block:: python
+
+        ajax_source.headers = { 'x-my-custom-header': 'some value' }
+
     """)
diff --git a/examples/howto/ajax_source.py b/examples/howto/ajax_source.py
--- a/examples/howto/ajax_source.py
+++ b/examples/howto/ajax_source.py
@@ -9,10 +9,11 @@
 from bokeh.models.sources import AjaxDataSource
 
 output_file("ajax_source.html", title="ajax_source.py example")
-source = AjaxDataSource(data=dict(x=[], y=[]),
-                        data_url='http://localhost:5050/data',
+
+source = AjaxDataSource(data_url='http://localhost:5050/data',
                         polling_interval=100)
-p = figure()
+p = figure(plot_height=300, plot_width=800, background_fill_color="lightgrey",
+           title="Streaming Noisy sin(x) via Ajax")
 p.circle('x', 'y', source=source)
 
 p.x_range.follow = "end"


Test patch:
diff --git a/bokehjs/test/models/sources/index.coffee b/bokehjs/test/models/sources/index.coffee
--- a/bokehjs/test/models/sources/index.coffee
+++ b/bokehjs/test/models/sources/index.coffee
@@ -2,3 +2,4 @@ require "./ajax_data_source"
 require "./cds_view"
 require "./column_data_source"
 require "./geojson_data_source"
+require "./remote_data_source"
diff --git a/bokehjs/test/models/sources/remote_data_source.coffee b/bokehjs/test/models/sources/remote_data_source.coffee
new file mode 100644
--- /dev/null
+++ b/bokehjs/test/models/sources/remote_data_source.coffee
@@ -0,0 +1,17 @@
+{expect} = require "chai"
+utils = require "../../utils"
+
+sinon = require 'sinon'
+
+{RemoteDataSource} = utils.require("models/sources/remote_data_source")
+
+describe "ajax_data_source module", ->
+
+  describe "RemoteDataSource", ->
+
+    describe "get_column method", ->
+
+      it "should return empty lists for not-yet-existant columns", ->
+        s = new RemoteDataSource({data_url: "http://foo.com"})
+        c = s.get_column("foo")
+        expect(c).to.be.deep.equal []