-
Notifications
You must be signed in to change notification settings - Fork 18
/
Copy pathapp.py
130 lines (105 loc) · 3.37 KB
/
app.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
import os
import json
import enaml
import tornado.web
import tornado.websocket
import tornado.ioloop
from tornado.log import enable_pretty_logging
from web.core.app import WebApplication
with enaml.imports():
from viewer import Viewer
log = tornado.web.app_log
# Files in the repo
BASE_URL = 'https://raw.githubusercontent.com/mwaskom/seaborn-data/master'
CSV_FILES = ['{}/{}.csv'.format(BASE_URL, name) for name in (
'anscombe',
'attention',
'brain_networks',
'car_crashes',
'diamonds',
'dots',
'exercise',
'flights',
'fmri',
'gammas',
'iris',
'mpg',
'planets',
'tips',
'titanic'
)]
# Holds the rendered view so a websocket can retrieve it later
CACHE = {}
class ViewerHandler(tornado.web.RequestHandler):
def get(self):
viewer = Viewer(
request=self.request,
response=self,
csv_files=CSV_FILES,
)
# Store the viewer in the cache
CACHE[viewer.id] = viewer
self.write(viewer.render())
class ViewerWebSocket(tornado.websocket.WebSocketHandler):
viewer = None
def open(self):
# Store the viewer in the cache
ref = self.get_argument("ref")
if ref not in CACHE:
log.error(f"Viewer with ref={ref} does not exist!")
self.close()
return
# Get a viewer reference
self.viewer = CACHE[ref]
# Setup an observer to watch changes on the enaml view
self.viewer.observe('modified', self.on_dom_modified)
def on_message(self, message):
""" When we get an event from js, lookup the node and invoke the
action on the enaml node.
"""
change = json.loads(message)
log.debug(f'Update from js: {change}')
# Lookup the node
ref = change.get('id')
if not ref:
return
nodes = self.viewer.xpath('//*[@id=$ref]', ref=ref)
if not nodes:
return # Unknown node
node = nodes[0]
# Trigger the change on the enaml node
if change.get('type') and change.get('name'):
if change['type'] == 'event':
trigger = getattr(node, change['name'])
trigger()
elif change['type'] == 'update':
# Trigger the update
setattr(node, change['name'], change['value'])
else:
log.warning(f"Unhandled event {self} {node}: {change}")
def on_dom_modified(self, change):
""" When an event from enaml occurs, send it out the websocket
so the client's browser can update accordingly.
"""
log.debug(f'Update from enaml: {change}')
self.write_message(json.dumps(change['value']))
def on_close(self):
log.debug(f'WebSocket {self} closed')
if self.viewer is not None:
self.viewer.unobserve('modified', self.on_dom_modified)
def run():
enable_pretty_logging()
# Needed to create enaml components
enaml_app = WebApplication()
# Start the tornado app
app = tornado.web.Application([
(r'/', ViewerHandler),
(r'/websocket/', ViewerWebSocket),
(r'/static/(.*)', tornado.web.StaticFileHandler, {
'path': os.path.dirname(__file__)}),
], debug=True)
app.listen(8888)
print("Listening on 8888")
tornado.ioloop.IOLoop.current().start()
if __name__ == '__main__':
run()