Repository: bokeh/bokeh

Issue:
When embeding bokeh server to flask, AttributeError: 'NoneType' object has no attribute '_id' shows up
I just want to embed the bokeh server into flask

I tried the following simple example but just cannot understand what's going on...

(python version = 2.7.12)
(bokeh version = 0.12.10)
(flask version = 0.12.2)

app.py
```
from flask import Flask, render_template
from bokeh.embed import autoload_server
from bokeh.client import pull_session

app = Flask(__name__)

@app.route("/")

def index():
	session = pull_session(url="http://localhost:5006/test_for_animation")
	bokeh_script = autoload_server(None, url="http://localhost:5006/test_for_animation", session_id=session.id)
	return render_template("index.html", bokeh_script=bokeh_script)

if __name__ == "__main__":
	app.run(debug=True)
```

test_for_animation.py
```
from bokeh.models import HoverTool, CrosshairTool, ColumnDataSource
from bokeh.layouts import widgetbox, row
from bokeh.plotting import figure, curdoc
from bokeh.models.widgets import TextInput, Button
from random import randrange

f=figure(x_range=(0,11), y_range=(0,11))
source = ColumnDataSource(data=dict(x=[], y=[]))
f.circle(x="x", y="y", size=8, color="olive", source=source)

def update():
	new_data = dict(x=[randrange(1,10)], y=[randrange(1,10)])
	source.stream(new_data, rollover=15)
	print(source.data)

curdoc().add_root(f)
curdoc().add_periodic_callback(update, 1000)
```

index.html
```
<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>Document</title>
<body>
	<h3>I am a heading</h3>
	<div>
		{{bokeh_script|safe}}
	</div>
	<h3>I am a footer</h3>
</body>
</html>
```

The error is

 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 291-452-623
/usr/local/lib/python2.7/dist-packages/bokeh/util/deprecation.py:34: BokehDeprecationWarning: bokeh.embed.autoload_server was deprecated in Bokeh 0.12.7 and will be removed, use bokeh.embed.server_document or bokeh.embed.server_session instead.
  warn(message)
127.0.0.1 - - [30/Oct/2017 12:57:09] "GET / HTTP/1.1" 500 -
INFO:werkzeug:127.0.0.1 - - [30/Oct/2017 12:57:09] "GET / HTTP/1.1" 500 -
Traceback (most recent call last):
  File "/usr/local/lib/python2.7/dist-packages/flask/app.py", line 1997, in __call__
    return self.wsgi_app(environ, start_response)
  File "/usr/local/lib/python2.7/dist-packages/flask/app.py", line 1985, in wsgi_app
    response = self.handle_exception(e)
  File "/usr/local/lib/python2.7/dist-packages/flask/app.py", line 1540, in handle_exception
    reraise(exc_type, exc_value, tb)
  File "/usr/local/lib/python2.7/dist-packages/flask/app.py", line 1982, in wsgi_app
    response = self.full_dispatch_request()
  File "/usr/local/lib/python2.7/dist-packages/flask/app.py", line 1614, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "/usr/local/lib/python2.7/dist-packages/flask/app.py", line 1517, in handle_user_exception
    reraise(exc_type, exc_value, tb)
  File "/usr/local/lib/python2.7/dist-packages/flask/app.py", line 1612, in full_dispatch_request
    rv = self.dispatch_request()
  File "/usr/local/lib/python2.7/dist-packages/flask/app.py", line 1598, in dispatch_request
    return self.view_functions[rule.endpoint](**req.view_args)
  File "/home/bigmoumou/Desktop/python/bokeh/tutorial/animation/app.py", line 10, in index
    bokeh_script = autoload_server(None, url="http://localhost:5006/test_for_animation", session_id=session.id)
  File "/usr/local/lib/python2.7/dist-packages/bokeh/embed/__init__.py", line 62, in autoload_server
    return server_session(model, session_id, url, relative_urls, "default", arguments)
  File "/usr/local/lib/python2.7/dist-packages/bokeh/util/api.py", line 190, in wrapper
    return obj(*args, **kw)
  File "/usr/local/lib/python2.7/dist-packages/bokeh/embed/server.py", line 188, in server_session
    modelid   = model._id,
AttributeError: 'NoneType' object has no attribute '_id'


Discussion:
This is a regression from `0.12.9` that happened during the refactoring that split up `embed.py`. The case where `model` is `None` is no longer correctly handled, it should pass the empty string to the template if `model` is `None`. 

Thanks or the report @bigmoumou 

Resolving patch:
diff --git a/bokeh/embed/server.py b/bokeh/embed/server.py
--- a/bokeh/embed/server.py
+++ b/bokeh/embed/server.py
@@ -121,8 +121,10 @@ def server_session(model, session_id, url="default", relative_urls=False, resour
         function for different or multiple page loads.
 
     Args:
-        model (Model) :
-            The object to render from the session
+        model (Model or None) :
+            The object to render from the session, or None.
+
+            If None, the entire document will be rendered.
 
         session_id (str) :
             A server session ID (default: None)
@@ -173,6 +175,7 @@ def server_session(model, session_id, url="default", relative_urls=False, resour
     app_path = _get_app_path(url)
 
     elementid = make_id()
+    modelid = "" if model is None else model._id
     src_path = _src_path(url, elementid)
 
     src_path += _process_app_path(app_path)
@@ -185,7 +188,7 @@ def server_session(model, session_id, url="default", relative_urls=False, resour
         src_path  = src_path,
         app_path  = app_path,
         elementid = elementid,
-        modelid   = model._id,
+        modelid   = modelid,
     )
 
     return encode_utf8(tag)


Test patch:
diff --git a/bokeh/embed/tests/test_server.py b/bokeh/embed/tests/test_server.py
--- a/bokeh/embed/tests/test_server.py
+++ b/bokeh/embed/tests/test_server.py
@@ -209,6 +209,26 @@ def test_resources_none(self, test_plot):
         r = bes.server_session(test_plot, session_id='fakesession', resources=None)
         assert 'resources=none' in r
 
+    def test_model_none(self):
+        r = bes.server_session(None, session_id='fakesession')
+        html = bs4.BeautifulSoup(r, "lxml")
+        scripts = html.findAll(name='script')
+        assert len(scripts) == 1
+        attrs = scripts[0].attrs
+        assert set(attrs), set([
+            'src',
+            'data-bokeh-doc-id',
+            'data-bokeh-model-id',
+            'id'
+        ])
+        divid = attrs['id']
+        src = "%s/autoload.js?bokeh-autoload-element=%s&bokeh-absolute-url=%s&bokeh-session-id=fakesession" % \
+              ("http://localhost:5006", divid, "http://localhost:5006")
+        assert attrs == { 'data-bokeh-doc-id' : '',
+                          'data-bokeh-model-id' : '',
+                          'id' : divid,
+                          'src' : src }
+
     def test_general(self, test_plot):
         r = bes.server_session(test_plot, session_id='fakesession')
         assert 'bokeh-session-id=fakesession' in r