Skip to content

Instantly share code, notes, and snippets.

Forked from Sklavit/
Created March 16, 2020 23:04
Show Gist options
  • Save gnthibault/c9b992906dc4bc4b4ea1dae03679bde3 to your computer and use it in GitHub Desktop.
Save gnthibault/c9b992906dc4bc4b4ea1dae03679bde3 to your computer and use it in GitHub Desktop.
Tornado HTTP web page with embedded Bokeh widget which communicates with other page of the same application

Tornado HTTP web page with embedded Bokeh widget which communicates with other page of the same application

Tornado HTTP web page with embedded Bokeh widget which communicates with other page of the same application.


  • Full Tornado server
  • Bokeh server is started from Tornado server and is executed in the same ioloop
  • Embedded Bohek widget by autoload_server
  • 2 web page communication:
    • one page is data source
    • the second one (with bokeh widgte) is data receiver
  • communicated data is bound to user_id



conda environment description

name: py34
channels: !!python/tuple
- defaults
- backports_abc=0.5=py34_0
- bokeh=0.12.4=py34_0
- jinja2=2.9.5=py34_0
- markupsafe=0.23=py34_2
- mkl=2017.0.1=0
- numpy=1.11.3=py34_0
- pip=9.0.1=py34_1
- python=3.4.5=0
- python-dateutil=2.6.0=py34_0
- pyyaml=3.12=py34_0
- requests=2.13.0=py34_0
- setuptools=27.2.0=py34_1
- six=1.10.0=py34_0
- tornado=4.4.2=py34_0
- vs2010_runtime=10.00.40219.1=2
- wheel=0.29.0=py34_0
- pip:
- backports-abc==0.5
prefix: C:\Miniconda35\envs\py34
# coding=utf-8
from collections import defaultdict
from datetime import datetime
from random import randint
import random
import bokeh
import bokeh.application as bokeh_app
import tornado.web
from bokeh.application.handlers import FunctionHandler
from bokeh.document import Document
from bokeh.embed import autoload_server
from bokeh.layouts import column, widgetbox
from bokeh.models import ColumnDataSource, Button, TableColumn, DateFormatter, DataTable
from tornado import gen
data_by_user = defaultdict(lambda: dict(file_names=[], dates=[], downloads=[]))
doc_by_user_str = dict()
source_by_user_str = dict()
data = dict(x=[], y=[])
class SecondHandler(tornado.web.RequestHandler):
def get(self):
def post(self, *args, **kwargs):
user_str = str(self.current_user)
data['x'] = random.sample(range(10), 10)
data['y'] = random.sample(range(10), 10)
data_by_user[user_str] = data
source = source_by_user_str[user_str]
def update(): = data
doc = doc_by_user_str[user_str] # type: Document
# Bryan Van de Ven @bryevdv Feb 27 22:23
# @Sklavit actually I can see why next_tick_callback would be needed from another request handler.
# We had other threads in mind but it's also the case that nothing triggering your other request handler
# would acquire a bokeh document lock, so you need to request one by using next_tick_callback
class MainHandler(tornado.web.RequestHandler):
def modify_doc(doc):
source = ColumnDataSource(dict(x=[], y=[]))
columns = [
TableColumn(field="x", title="X"),
TableColumn(field="y", title="Y"),
data_table = DataTable(source=source, columns=columns)
user_str =
doc_by_user_str[user_str] = doc
source_by_user_str[user_str] = source
_bokeh_app = None
def get_bokeh_app(cls):
if cls._bokeh_app is None:
cls._bokeh_app = bokeh.application.Application(FunctionHandler(MainHandler.modify_doc))
return cls._bokeh_app
def get(self):
user_str = str(self.current_user)
script = autoload_server(model=None, session_id=user_str, # NOTE: MUST be string
'main_page_template.html', active_page='inks_upload',
<!DOCTYPE html>
<meta charset="UTF-8">
<iframe src="/second_page" width="100%"></iframe>
{% raw script %}
# coding=utf-8
import os.path
import bokeh
import tornado
from bokeh.application.handlers import FunctionHandler
from bokeh.server.server import Server
from bokeh.util.browser import view
from tornado.options import define, options
from tornado_bokeh_inside_embedded_widget.handlers import MainHandler, SecondHandler
define("port", default=8888, help="run on the given port", type=int)
class Application(tornado.web.Application):
def __init__(self):
handlers = [
(r"/main_page", MainHandler),
(r"/second_page", SecondHandler),
settings = dict(
template_path=os.path.join(os.path.dirname(__file__), "templates"),
static_path=os.path.join(os.path.dirname(__file__), "static"),
# NOTE: some random value as secret (i.e. generated by uuid4())
cookie_secret="YOUR SECRET HERE",
super(Application, self).__init__(handlers, **settings)
if __name__ == '__main__':
http_server = tornado.httpserver.HTTPServer(Application())
io_loop = tornado.ioloop.IOLoop.current()
bokeh_app = bokeh.application.Application(FunctionHandler(MainHandler.modify_doc))
bokeh_server = Server({'/bokeh/app': bokeh_app},
io_loop=io_loop, allow_websocket_origin=['localhost:8888'])
io_loop.add_callback(view, "http://localhost:8888/main_page")
<!DOCTYPE html>
<meta charset="UTF-8">
<form method="post">
<input type="submit">
{% module xsrf_form_html() %}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment