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.") ))