"""Tornado handlers for the notebook.""" #----------------------------------------------------------------------------- # Imports #----------------------------------------------------------------------------- from tornado import web from tornado import websocket from zmq.eventloop import ioloop from zmq.utils import jsonapi from IPython.zmq.session import Session try: from docutils.core import publish_string except ImportError: publish_string = None #----------------------------------------------------------------------------- # Top-level handlers #----------------------------------------------------------------------------- class NBBrowserHandler(web.RequestHandler): def get(self): nbm = self.application.notebook_manager project = nbm.notebook_dir self.render('nbbrowser.html', project=project) class NewHandler(web.RequestHandler): def get(self): notebook_id = self.application.notebook_manager.new_notebook() self.render('notebook.html', notebook_id=notebook_id) class NamedNotebookHandler(web.RequestHandler): def get(self, notebook_id): nbm = self.application.notebook_manager if not nbm.notebook_exists(notebook_id): raise web.HTTPError(404) self.render('notebook.html', notebook_id=notebook_id) #----------------------------------------------------------------------------- # Kernel handlers #----------------------------------------------------------------------------- class MainKernelHandler(web.RequestHandler): def get(self): km = self.application.kernel_manager self.finish(jsonapi.dumps(km.kernel_ids)) def post(self): km = self.application.kernel_manager notebook_id = self.get_argument('notebook', default=None) kernel_id = km.start_kernel(notebook_id) self.set_header('Location', '/'+kernel_id) self.finish(jsonapi.dumps(kernel_id)) class KernelHandler(web.RequestHandler): SUPPORTED_METHODS = ('DELETE') def delete(self, kernel_id): km = self.application.kernel_manager km.kill_kernel(kernel_id) self.set_status(204) self.finish() class KernelActionHandler(web.RequestHandler): def post(self, kernel_id, action): km = self.application.kernel_manager if action == 'interrupt': km.interrupt_kernel(kernel_id) self.set_status(204) if action == 'restart': new_kernel_id = km.restart_kernel(kernel_id) self.write(jsonapi.dumps(new_kernel_id)) self.finish() class ZMQStreamHandler(websocket.WebSocketHandler): def _reserialize_reply(self, msg_list): """Reserialize a reply message using JSON. This takes the msg list from the ZMQ socket, unserializes it using self.session and then serializes the result using JSON. This method should be used by self._on_zmq_reply to build messages that can be sent back to the browser. """ idents, msg_list = self.session.feed_identities(msg_list) msg = self.session.unserialize(msg_list) try: msg['header'].pop('date') except KeyError: pass try: msg['parent_header'].pop('date') except KeyError: pass msg.pop('buffers') return jsonapi.dumps(msg) def _on_zmq_reply(self, msg_list): try: msg = self._reserialize_reply(msg_list) except: self.application.kernel_manager.log.critical("Malformed message: %r" % msg_list) else: self.write_message(msg) class IOPubHandler(ZMQStreamHandler): def initialize(self, *args, **kwargs): self._kernel_alive = True self._beating = False def open(self, kernel_id): km = self.application.kernel_manager self.kernel_id = kernel_id self.session = Session() self.time_to_dead = km.time_to_dead self.iopub_stream = km.create_iopub_stream(kernel_id) self.hb_stream = km.create_hb_stream(kernel_id) self.iopub_stream.on_recv(self._on_zmq_reply) self.start_hb(self.kernel_died) def on_close(self): self.stop_hb() self.iopub_stream.close() self.hb_stream.close() def start_hb(self, callback): """Start the heartbeating and call the callback if the kernel dies.""" if not self._beating: self._kernel_alive = True def ping_or_dead(): if self._kernel_alive: self._kernel_alive = False self.hb_stream.send(b'ping') else: try: callback() except: pass finally: self._hb_periodic_callback.stop() def beat_received(msg): self._kernel_alive = True self.hb_stream.on_recv(beat_received) self._hb_periodic_callback = ioloop.PeriodicCallback(ping_or_dead, self.time_to_dead*1000) self._hb_periodic_callback.start() self._beating= True def stop_hb(self): """Stop the heartbeating and cancel all related callbacks.""" if self._beating: self._hb_periodic_callback.stop() if not self.hb_stream.closed(): self.hb_stream.on_recv(None) def kernel_died(self): self.application.kernel_manager.delete_mapping_for_kernel(self.kernel_id) self.write_message( {'header': {'msg_type': 'status'}, 'parent_header': {}, 'content': {'execution_state':'dead'} } ) self.on_close() class ShellHandler(ZMQStreamHandler): def initialize(self, *args, **kwargs): pass def open(self, kernel_id): km = self.application.kernel_manager self.max_msg_size = km.max_msg_size self.kernel_id = kernel_id self.session = Session() self.shell_stream = self.application.kernel_manager.create_shell_stream(kernel_id) self.shell_stream.on_recv(self._on_zmq_reply) def on_message(self, msg): if len(msg) < self.max_msg_size: msg = jsonapi.loads(msg) self.session.send(self.shell_stream, msg) def on_close(self): self.shell_stream.close() #----------------------------------------------------------------------------- # Notebook web service handlers #----------------------------------------------------------------------------- class NotebookRootHandler(web.RequestHandler): def get(self): nbm = self.application.notebook_manager files = nbm.list_notebooks() self.finish(jsonapi.dumps(files)) def post(self): nbm = self.application.notebook_manager body = self.request.body.strip() format = self.get_argument('format', default='json') name = self.get_argument('name', default=None) if body: notebook_id = nbm.save_new_notebook(body, name=name, format=format) else: notebook_id = nbm.new_notebook() self.set_header('Location', '/'+notebook_id) self.finish(jsonapi.dumps(notebook_id)) class NotebookHandler(web.RequestHandler): SUPPORTED_METHODS = ('GET', 'PUT', 'DELETE') def get(self, notebook_id): nbm = self.application.notebook_manager format = self.get_argument('format', default='json') last_mod, name, data = nbm.get_notebook(notebook_id, format) if format == u'json': self.set_header('Content-Type', 'application/json') self.set_header('Content-Disposition','attachment; filename="%s.json"' % name) elif format == u'xml': self.set_header('Content-Type', 'application/xml') self.set_header('Content-Disposition','attachment; filename="%s.ipynb"' % name) elif format == u'py': self.set_header('Content-Type', 'application/x-python') self.set_header('Content-Disposition','attachment; filename="%s.py"' % name) self.set_header('Last-Modified', last_mod) self.finish(data) def put(self, notebook_id): nbm = self.application.notebook_manager format = self.get_argument('format', default='json') name = self.get_argument('name', default=None) nbm.save_notebook(notebook_id, self.request.body, name=name, format=format) self.set_status(204) self.finish() def delete(self, notebook_id): nbm = self.application.notebook_manager nbm.delete_notebook(notebook_id) self.set_status(204) self.finish() #----------------------------------------------------------------------------- # RST web service handlers #----------------------------------------------------------------------------- class RSTHandler(web.RequestHandler): def post(self): if publish_string is None: raise web.HTTPError(503) body = self.request.body.strip() source = body # template_path=os.path.join(os.path.dirname(__file__), u'templates', u'rst_template.html') defaults = {'file_insertion_enabled': 0, 'raw_enabled': 0, '_disable_config': 1, 'stylesheet_path': 0 # 'template': template_path } try: html = publish_string(source, writer_name='html', settings_overrides=defaults ) except: raise web.HTTPError(400) print html self.set_header('Content-Type', 'text/html') self.finish(html)