Skip to content

Instantly share code, notes, and snippets.

@gnthibault
Forked from Sklavit/README.md
Created March 16, 2020 23:04
Show Gist options
  • Save gnthibault/eaf1a34a04df5cd43441f2041fd86863 to your computer and use it in GitHub Desktop.
Save gnthibault/eaf1a34a04df5cd43441f2041fd86863 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.

Features

  • 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

References

https://github.com/bokeh/bokeh/blob/0.12.4/examples/howto/server_embed/tornado_embed.py http://bokeh.pydata.org/en/latest/docs/user_guide/embed.html#server-data http://bokeh.pydata.org/en/latest/docs/user_guide/server.html#embedding-bokeh-server-as-a-library http://bokeh.pydata.org/en/latest/docs/reference/embed.html#bokeh.embed.autoload_server

Requirement

conda environment description

name: py34
channels: !!python/tuple
- defaults
dependencies:
- 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):
self.render("second_page_template.html")
@gen.coroutine
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]
@gen.coroutine
def update():
source.data = 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
doc.add_next_tick_callback(update)
self.render('second_page_template.html')
class MainHandler(tornado.web.RequestHandler):
@staticmethod
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.session_context.id
doc_by_user_str[user_str] = doc
source_by_user_str[user_str] = source
doc.add_root(widgetbox(data_table))
_bokeh_app = None
@classmethod
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
@gen.coroutine
def get(self):
user_str = str(self.current_user)
script = autoload_server(model=None, session_id=user_str, # NOTE: MUST be string
app_path='/bokeh/app',
url='http://localhost:5006')
self.render(
'main_page_template.html', active_page='inks_upload',
script=script
)
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<iframe src="/second_page" width="100%"></iframe>
{% raw script %}
</body>
</html>
# 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"),
xsrf_cookies=True,
# NOTE: some random value as secret (i.e. generated by uuid4())
cookie_secret="YOUR SECRET HERE",
login_url="/auth/login",
debug=True,
)
super(Application, self).__init__(handlers, **settings)
if __name__ == '__main__':
http_server = tornado.httpserver.HTTPServer(Application())
http_server.listen(options.port)
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'])
bokeh_server.start(start_loop=False)
io_loop.add_callback(view, "http://localhost:8888/main_page")
io_loop.start()
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<form method="post">
<input type="submit">
{% module xsrf_form_html() %}
</form>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment