diff --git a/IPython/frontend/html/notebook/kernelmanager.py b/IPython/frontend/html/notebook/kernelmanager.py
index 484cec0..b71ee7a 100644
--- a/IPython/frontend/html/notebook/kernelmanager.py
+++ b/IPython/frontend/html/notebook/kernelmanager.py
@@ -1,5 +1,9 @@
"""A kernel manager for multiple kernels."""
+#-----------------------------------------------------------------------------
+# Imports
+#-----------------------------------------------------------------------------
+
import logging
import signal
import sys
@@ -11,6 +15,9 @@ from IPython.config.configurable import Configurable
from IPython.zmq.ipkernel import launch_kernel
from IPython.utils.traitlets import Instance, Dict, Unicode
+#-----------------------------------------------------------------------------
+# Classes
+#-----------------------------------------------------------------------------
class DuplicateKernelError(Exception):
pass
diff --git a/IPython/frontend/html/notebook/notebookapp.py b/IPython/frontend/html/notebook/notebookapp.py
index fef5782..8ac9856 100644
--- a/IPython/frontend/html/notebook/notebookapp.py
+++ b/IPython/frontend/html/notebook/notebookapp.py
@@ -1,3 +1,9 @@
+"""A tornado based IPython notebook server."""
+
+#-----------------------------------------------------------------------------
+# Imports
+#-----------------------------------------------------------------------------
+
import logging
import os
@@ -14,22 +20,30 @@ from tornado import options
from tornado import web
from kernelmanager import KernelManager
+from sessionmanager import SessionManager
from handlers import (
MainHandler, KernelHandler, KernelActionHandler, ZMQStreamHandler,
NotebookRootHandler, NotebookHandler
)
from routers import IOPubStreamRouter, ShellStreamRouter
-options.define("port", default=8888, help="run on the given port", type=int)
+from IPython.core.application import BaseIPythonApplication
+from IPython.zmq.session import Session
+
+#-----------------------------------------------------------------------------
+# Module globals
+#-----------------------------------------------------------------------------
_kernel_id_regex = r"(?P\w+-\w+-\w+-\w+-\w+)"
_kernel_action_regex = r"(?Prestart|interrupt)"
-
+#-----------------------------------------------------------------------------
+# The Tornado web application
+#-----------------------------------------------------------------------------
class NotebookWebApplication(web.Application):
- def __init__(self):
+ def __init__(self, kernel_manager, log):
handlers = [
(r"/", MainHandler),
(r"/kernels", KernelHandler),
@@ -45,9 +59,8 @@ class NotebookWebApplication(web.Application):
)
web.Application.__init__(self, handlers, **settings)
- self.context = zmq.Context()
- self.kernel_manager = KernelManager(self.context)
- self._session_dict = {}
+ self.kernel_manager = kernel_manager
+ self.log = log
self._routers = {}
#-------------------------------------------------------------------------
@@ -59,14 +72,32 @@ class NotebookWebApplication(web.Application):
return self.kernel_manager.kernel_ids
def start_kernel(self):
+ # TODO: pass command line options to the kernel in start_kernel()
kernel_id = self.kernel_manager.start_kernel()
- logging.info("Kernel started: %s" % kernel_id)
- self.start_session(kernel_id)
+ self.log.info("Kernel started: %s" % kernel_id)
+ self.start_session_manager(kernel_id)
return kernel_id
+ def start_session_manager(self, kernel_id):
+ sm = self.kernel_manager.create_session_manager(kernel_id)
+ self._session_dict[kernel_id] = sm
+ iopub_stream = sm.get_iopub_stream()
+ shell_stream = sm.get_shell_stream()
+ iopub_router = IOPubStreamRouter(iopub_stream)
+ shell_router = ShellStreamRouter(shell_stream)
+ self._routers[(kernel_id, 'iopub')] = iopub_router
+ self._routers[(kernel_id, 'shell')] = shell_router
+ self.log.debug("Session manager started for kernel: %s" % kernel_id)
+
+ def kill_kernel(self, kernel_id):
+ sm = self._session_dict.pop(kernel_id)
+ sm.stop()
+ self.kernel_manager.kill_kernel(kernel_id)
+ self.log.info("Kernel killed: %s" % kernel_id)
+
def interrupt_kernel(self, kernel_id):
self.kernel_manager.interrupt_kernel(kernel_id)
- logging.info("Kernel interrupted: %s" % kernel_id)
+ self.log.debug("Kernel interrupted: %s" % kernel_id)
def restart_kernel(self, kernel_id):
# Create the new kernel first so we can move the clients over.
@@ -84,48 +115,106 @@ class NotebookWebApplication(web.Application):
# TODO: This causes a hard crash in ZMQStream.close, which sets
# self.socket to None to hastily. We will need to fix this in PyZMQ
# itself. For now, we just leave the old kernel running :(
- # sm = self.kernel_manager.get_session_manager(kernel_id)
- # session_id = self._session_dict[kernel_id]
- # sm.stop_session(session_id)
- # self.kernel_manager.kill_kernel(kernel_id)
+ # self.kill_kernel(kernel_id)
- logging.info("Kernel restarted")
+ self.log.debug("Kernel restarted: %s -> %s" % (kernel_id, new_kernel_id))
return new_kernel_id
- def start_session(self, kernel_id):
- sm = self.kernel_manager.get_session_manager(kernel_id)
- session_id = sm.start_session()
- self._session_dict[kernel_id] = session_id
- iopub_stream = sm.get_iopub_stream(session_id)
- shell_stream = sm.get_shell_stream(session_id)
- iopub_router = IOPubStreamRouter(iopub_stream)
- shell_router = ShellStreamRouter(shell_stream)
- self._routers[(kernel_id, session_id, 'iopub')] = iopub_router
- self._routers[(kernel_id, session_id, 'shell')] = shell_router
- logging.info("Session started: %s, %s" % (kernel_id, session_id))
-
- def stop_session(self, kernel_id):
- # TODO: finish this!
- sm = self.kernel_manager.get_session_manager(kernel_id)
- session_id = self._session_dict[kernel_id]
-
def get_router(self, kernel_id, stream_name):
- session_id = self._session_dict[kernel_id]
- router = self._routers[(kernel_id, session_id, stream_name)]
+ router = self._routers[(kernel_id, stream_name)]
return router
+#-----------------------------------------------------------------------------
+# Aliases and Flags
+#-----------------------------------------------------------------------------
+
+flags = dict(ipkernel_flags)
+
+# the flags that are specific to the frontend
+# these must be scrubbed before being passed to the kernel,
+# or it will raise an error on unrecognized flags
+notebook_flags = []
+
+aliases = dict(ipkernel_aliases)
+
+aliases.update(dict(
+ ip = 'IPythonNotebookApp.ip',
+ port = 'IPythonNotebookApp.port'
+ colors = 'ZMQInteractiveShell.colors',
+ editor = 'IPythonWidget.editor',
+))
+
+#-----------------------------------------------------------------------------
+# IPythonNotebookApp
+#-----------------------------------------------------------------------------
+
+class IPythonNotebookApp(BaseIPythonApplication):
+ name = 'ipython-notebook'
+ default_config_file_name='ipython_notebook_config.py'
+
+ description = """
+ The IPython HTML Notebook.
+
+ This launches a Tornado based HTML Notebook Server that serves up an
+ HTML5/Javascript Notebook client.
+ """
+
+ classes = [IPKernelApp, ZMQInteractiveShell, ProfileDir, Session,
+ KernelManager, SessionManager]
+ flags = Dict(flags)
+ aliases = Dict(aliases)
+
+ kernel_argv = List(Unicode)
+
+ # connection info:
+ ip = Unicode(LOCALHOST, config=True,
+ help="The IP address the notebook server will listen on."
+ )
+
+ port = Int(8888, config=True,
+ help="The port the notebook server will listen on."
+ )
+
+ # the factory for creating a widget
+ widget_factory = Any(RichIPythonWidget)
+
+ def parse_command_line(self, argv=None):
+ super(IPythonNotebookApp, self).parse_command_line(argv)
+ if argv is None:
+ argv = sys.argv[1:]
+
+ self.kernel_argv = list(argv) # copy
+ # kernel should inherit default config file from frontend
+ self.kernel_argv.append("KernelApp.parent_appname='%s'"%self.name)
+ # scrub frontend-specific flags
+ for a in argv:
+ if a.startswith('--') and a[2:] in qt_flags:
+ self.kernel_argv.remove(a)
+
+ def init_kernel_manager(self):
+ # Don't let Qt or ZMQ swallow KeyboardInterupts.
+ signal.signal(signal.SIGINT, signal.SIG_DFL)
+
+ # Create a KernelManager and start a kernel.
+ self.kernel_manager = KernelManager(config=self.config, log=self.log)
+
+ def initialize(self, argv=None):
+ super(IPythonNotebookApp, self).initialize(argv)
+ self.init_kernel_mananger()
+ self.web_app = NotebookWebApplication()
+ self.http_server = httpserver.HTTPServer(self.web_app)
+ self.http_server.listen(self.port)
+
+ def start(self):
+ self.log.info("The IPython Notebook is running at: http://%s:%i" % (self.ip, self.port))
+ ioloop.IOLoop.instance().start()
+
+#-----------------------------------------------------------------------------
+# Main entry point
+#-----------------------------------------------------------------------------
def launch_new_instance():
- options.parse_command_line()
- application = NotebookWebApplication()
- http_server = httpserver.HTTPServer(application)
- http_server.listen(options.options.port)
- print "IPython Notebook running at: http://127.0.0.1:8888"
- print "The github master of tornado is required to run this server:"
- print " https://github.com/facebook/tornado/tree/master/tornado"
- ioloop.IOLoop.instance().start()
-
-
-if __name__ == "__main__":
- main()
+ app = IPythonNotebookApp()
+ app.initialize()
+ app.start()
diff --git a/IPython/frontend/html/notebook/sessionmanager.py b/IPython/frontend/html/notebook/sessionmanager.py
index 86e04d4..800fd6e 100644
--- a/IPython/frontend/html/notebook/sessionmanager.py
+++ b/IPython/frontend/html/notebook/sessionmanager.py
@@ -1,5 +1,9 @@
"""A manager for session and channels for a single kernel."""
+#-----------------------------------------------------------------------------
+# Imports
+#-----------------------------------------------------------------------------
+
import zmq
from zmq.eventloop.zmqstream import ZMQStream
@@ -10,6 +14,10 @@ from IPython.zmq.session import SessionFactory
class SessionManagerRunningError(Exception):
pass
+#-----------------------------------------------------------------------------
+# Classes
+#-----------------------------------------------------------------------------
+
class SessionManager(SessionFactory):
"""Manages a session for a kernel.
diff --git a/IPython/frontend/html/notebook/tests/test_kernelsession.py b/IPython/frontend/html/notebook/tests/test_kernelsession.py
index 30f4ca6..96d56af 100644
--- a/IPython/frontend/html/notebook/tests/test_kernelsession.py
+++ b/IPython/frontend/html/notebook/tests/test_kernelsession.py
@@ -1,3 +1,4 @@
+"""Tests for the notebook kernel and session manager."""
from unittest import TestCase
diff --git a/IPython/frontend/terminal/ipapp.py b/IPython/frontend/terminal/ipapp.py
index a10b95e..30011e8 100755
--- a/IPython/frontend/terminal/ipapp.py
+++ b/IPython/frontend/terminal/ipapp.py
@@ -195,6 +195,9 @@ class TerminalIPythonApp(BaseIPythonApplication, InteractiveShellApp):
qtconsole=('IPython.frontend.qt.console.qtconsoleapp.IPythonQtConsoleApp',
"""Launch the IPython Qt Console."""
),
+ hotebook=('IPython.frontend.html.notebook.notebookapp.IPythonNotebookApp',
+ """Launch the IPython HTML Notebook Server"""
+ ),
profile = ("IPython.core.profileapp.ProfileApp",
"Create and manage IPython profiles.")
))