##// END OF EJS Templates
Refactoring the notebook app to support the new config system.
Brian E. Granger -
Show More
@@ -1,179 +1,186 b''
1 1 """A kernel manager for multiple kernels."""
2 2
3 #-----------------------------------------------------------------------------
4 # Imports
5 #-----------------------------------------------------------------------------
6
3 7 import logging
4 8 import signal
5 9 import sys
6 10 import uuid
7 11
8 12 import zmq
9 13
10 14 from IPython.config.configurable import Configurable
11 15 from IPython.zmq.ipkernel import launch_kernel
12 16 from IPython.utils.traitlets import Instance, Dict, Unicode
13 17
18 #-----------------------------------------------------------------------------
19 # Classes
20 #-----------------------------------------------------------------------------
14 21
15 22 class DuplicateKernelError(Exception):
16 23 pass
17 24
18 25
19 26 class KernelManager(Configurable):
20 27 """A class for managing multiple kernels."""
21 28
22 29 context = Instance('zmq.Context')
23 30 def _context_default(self):
24 31 return zmq.Context.instance()
25 32
26 33 logname = Unicode('')
27 34 def _logname_changed(self, name, old, new):
28 35 self.log = logging.getLogger(new)
29 36
30 37 _kernels = Dict()
31 38
32 39 @property
33 40 def kernel_ids(self):
34 41 """Return a list of the kernel ids of the active kernels."""
35 42 return self._kernels.keys()
36 43
37 44 def __len__(self):
38 45 """Return the number of running kernels."""
39 46 return len(self.kernel_ids)
40 47
41 48 def __contains__(self, kernel_id):
42 49 if kernel_id in self.kernel_ids:
43 50 return True
44 51 else:
45 52 return False
46 53
47 54 def start_kernel(self, **kwargs):
48 55 """Start a new kernel."""
49 56 kernel_id = str(uuid.uuid4())
50 57 (process, shell_port, iopub_port, stdin_port, hb_port) = launch_kernel(**kwargs)
51 58 # Store the information for contacting the kernel. This assumes the kernel is
52 59 # running on localhost.
53 60 d = dict(
54 61 process = process,
55 62 stdin_port = stdin_port,
56 63 iopub_port = iopub_port,
57 64 shell_port = shell_port,
58 65 hb_port = hb_port,
59 66 ip = '127.0.0.1'
60 67 )
61 68 self._kernels[kernel_id] = d
62 69 return kernel_id
63 70
64 71 def kill_kernel(self, kernel_id):
65 72 """Kill a kernel by its kernel uuid.
66 73
67 74 Parameters
68 75 ==========
69 76 kernel_id : uuid
70 77 The id of the kernel to kill.
71 78 """
72 79 kernel_process = self.get_kernel_process(kernel_id)
73 80 if kernel_process is not None:
74 81 # Attempt to kill the kernel.
75 82 try:
76 83 kernel_process.kill()
77 84 except OSError, e:
78 85 # In Windows, we will get an Access Denied error if the process
79 86 # has already terminated. Ignore it.
80 87 if not (sys.platform == 'win32' and e.winerror == 5):
81 88 raise
82 89 del self._kernels[kernel_id]
83 90
84 91 def interrupt_kernel(self, kernel_id):
85 92 """Interrupt (SIGINT) the kernel by its uuid.
86 93
87 94 Parameters
88 95 ==========
89 96 kernel_id : uuid
90 97 The id of the kernel to interrupt.
91 98 """
92 99 kernel_process = self.get_kernel_process(kernel_id)
93 100 if kernel_process is not None:
94 101 if sys.platform == 'win32':
95 102 from parentpoller import ParentPollerWindows as Poller
96 103 Poller.send_interrupt(kernel_process.win32_interrupt_event)
97 104 else:
98 105 kernel_process.send_signal(signal.SIGINT)
99 106
100 107 def signal_kernel(self, kernel_id, signum):
101 108 """ Sends a signal to the kernel by its uuid.
102 109
103 110 Note that since only SIGTERM is supported on Windows, this function
104 111 is only useful on Unix systems.
105 112
106 113 Parameters
107 114 ==========
108 115 kernel_id : uuid
109 116 The id of the kernel to signal.
110 117 """
111 118 kernel_process = self.get_kernel_process(kernel_id)
112 119 if kernel_process is not None:
113 120 kernel_process.send_signal(signum)
114 121
115 122 def get_kernel_process(self, kernel_id):
116 123 """Get the process object for a kernel by its uuid.
117 124
118 125 Parameters
119 126 ==========
120 127 kernel_id : uuid
121 128 The id of the kernel.
122 129 """
123 130 d = self._kernels.get(kernel_id)
124 131 if d is not None:
125 132 return d['process']
126 133 else:
127 134 raise KeyError("Kernel with id not found: %s" % kernel_id)
128 135
129 136 def get_kernel_ports(self, kernel_id):
130 137 """Return a dictionary of ports for a kernel.
131 138
132 139 Parameters
133 140 ==========
134 141 kernel_id : uuid
135 142 The id of the kernel.
136 143
137 144 Returns
138 145 =======
139 146 port_dict : dict
140 147 A dict of key, value pairs where the keys are the names
141 148 (stdin_port,iopub_port,shell_port) and the values are the
142 149 integer port numbers for those channels.
143 150 """
144 151 d = self._kernels.get(kernel_id)
145 152 if d is not None:
146 153 dcopy = d.copy()
147 154 dcopy.pop('process')
148 155 dcopy.pop('ip')
149 156 return dcopy
150 157 else:
151 158 raise KeyError("Kernel with id not found: %s" % kernel_id)
152 159
153 160 def get_kernel_ip(self, kernel_id):
154 161 """Return ip address for a kernel.
155 162
156 163 Parameters
157 164 ==========
158 165 kernel_id : uuid
159 166 The id of the kernel.
160 167
161 168 Returns
162 169 =======
163 170 ip : str
164 171 The ip address of the kernel.
165 172 """
166 173 d = self._kernels.get(kernel_id)
167 174 if d is not None:
168 175 return d['ip']
169 176 else:
170 177 raise KeyError("Kernel with id not found: %s" % kernel_id)
171 178
172 179 def create_session_manager(self, kernel_id):
173 180 """Create a new session manager for a kernel by its uuid."""
174 181 from sessionmanager import SessionManager
175 182 return SessionManager(
176 183 kernel_id=kernel_id, kernel_manager=self,
177 184 config=self.config, context=self.context, logname=self.logname
178 185 )
179 186
@@ -1,131 +1,220 b''
1 """A tornado based IPython notebook server."""
2
3 #-----------------------------------------------------------------------------
4 # Imports
5 #-----------------------------------------------------------------------------
6
1 7 import logging
2 8 import os
3 9
4 10 import zmq
5 11
6 12 # Install the pyzmq ioloop. This has to be done before anything else from
7 13 # tornado is imported.
8 14 from zmq.eventloop import ioloop
9 15 import tornado.ioloop
10 16 tornado.ioloop = ioloop
11 17
12 18 from tornado import httpserver
13 19 from tornado import options
14 20 from tornado import web
15 21
16 22 from kernelmanager import KernelManager
23 from sessionmanager import SessionManager
17 24 from handlers import (
18 25 MainHandler, KernelHandler, KernelActionHandler, ZMQStreamHandler,
19 26 NotebookRootHandler, NotebookHandler
20 27 )
21 28 from routers import IOPubStreamRouter, ShellStreamRouter
22 29
23 options.define("port", default=8888, help="run on the given port", type=int)
30 from IPython.core.application import BaseIPythonApplication
31 from IPython.zmq.session import Session
32
33 #-----------------------------------------------------------------------------
34 # Module globals
35 #-----------------------------------------------------------------------------
24 36
25 37 _kernel_id_regex = r"(?P<kernel_id>\w+-\w+-\w+-\w+-\w+)"
26 38 _kernel_action_regex = r"(?P<action>restart|interrupt)"
27 39
28
40 #-----------------------------------------------------------------------------
41 # The Tornado web application
42 #-----------------------------------------------------------------------------
29 43
30 44 class NotebookWebApplication(web.Application):
31 45
32 def __init__(self):
46 def __init__(self, kernel_manager, log):
33 47 handlers = [
34 48 (r"/", MainHandler),
35 49 (r"/kernels", KernelHandler),
36 50 (r"/kernels/%s/%s" % (_kernel_id_regex, _kernel_action_regex), KernelActionHandler),
37 51 (r"/kernels/%s/iopub" % _kernel_id_regex, ZMQStreamHandler, dict(stream_name='iopub')),
38 52 (r"/kernels/%s/shell" % _kernel_id_regex, ZMQStreamHandler, dict(stream_name='shell')),
39 53 (r"/notebooks", NotebookRootHandler),
40 54 (r"/notebooks/([^/]+)", NotebookHandler)
41 55 ]
42 56 settings = dict(
43 57 template_path=os.path.join(os.path.dirname(__file__), "templates"),
44 58 static_path=os.path.join(os.path.dirname(__file__), "static"),
45 59 )
46 60 web.Application.__init__(self, handlers, **settings)
47 61
48 self.context = zmq.Context()
49 self.kernel_manager = KernelManager(self.context)
50 self._session_dict = {}
62 self.kernel_manager = kernel_manager
63 self.log = log
51 64 self._routers = {}
52 65
53 66 #-------------------------------------------------------------------------
54 67 # Methods for managing kernels and sessions
55 68 #-------------------------------------------------------------------------
56 69
57 70 @property
58 71 def kernel_ids(self):
59 72 return self.kernel_manager.kernel_ids
60 73
61 74 def start_kernel(self):
75 # TODO: pass command line options to the kernel in start_kernel()
62 76 kernel_id = self.kernel_manager.start_kernel()
63 logging.info("Kernel started: %s" % kernel_id)
64 self.start_session(kernel_id)
77 self.log.info("Kernel started: %s" % kernel_id)
78 self.start_session_manager(kernel_id)
65 79 return kernel_id
66 80
81 def start_session_manager(self, kernel_id):
82 sm = self.kernel_manager.create_session_manager(kernel_id)
83 self._session_dict[kernel_id] = sm
84 iopub_stream = sm.get_iopub_stream()
85 shell_stream = sm.get_shell_stream()
86 iopub_router = IOPubStreamRouter(iopub_stream)
87 shell_router = ShellStreamRouter(shell_stream)
88 self._routers[(kernel_id, 'iopub')] = iopub_router
89 self._routers[(kernel_id, 'shell')] = shell_router
90 self.log.debug("Session manager started for kernel: %s" % kernel_id)
91
92 def kill_kernel(self, kernel_id):
93 sm = self._session_dict.pop(kernel_id)
94 sm.stop()
95 self.kernel_manager.kill_kernel(kernel_id)
96 self.log.info("Kernel killed: %s" % kernel_id)
97
67 98 def interrupt_kernel(self, kernel_id):
68 99 self.kernel_manager.interrupt_kernel(kernel_id)
69 logging.info("Kernel interrupted: %s" % kernel_id)
100 self.log.debug("Kernel interrupted: %s" % kernel_id)
70 101
71 102 def restart_kernel(self, kernel_id):
72 103 # Create the new kernel first so we can move the clients over.
73 104 new_kernel_id = self.start_kernel()
74 105
75 106 # Copy the clients over to the new routers.
76 107 old_iopub_router = self.get_router(kernel_id, 'iopub')
77 108 old_shell_router = self.get_router(kernel_id, 'shell')
78 109 new_iopub_router = self.get_router(new_kernel_id, 'iopub')
79 110 new_shell_router = self.get_router(new_kernel_id, 'shell')
80 111 new_iopub_router.copy_clients(old_iopub_router)
81 112 new_shell_router.copy_clients(old_shell_router)
82 113
83 114 # Now shutdown the old session and the kernel.
84 115 # TODO: This causes a hard crash in ZMQStream.close, which sets
85 116 # self.socket to None to hastily. We will need to fix this in PyZMQ
86 117 # itself. For now, we just leave the old kernel running :(
87 # sm = self.kernel_manager.get_session_manager(kernel_id)
88 # session_id = self._session_dict[kernel_id]
89 # sm.stop_session(session_id)
90 # self.kernel_manager.kill_kernel(kernel_id)
118 # self.kill_kernel(kernel_id)
91 119
92 logging.info("Kernel restarted")
120 self.log.debug("Kernel restarted: %s -> %s" % (kernel_id, new_kernel_id))
93 121 return new_kernel_id
94 122
95 def start_session(self, kernel_id):
96 sm = self.kernel_manager.get_session_manager(kernel_id)
97 session_id = sm.start_session()
98 self._session_dict[kernel_id] = session_id
99 iopub_stream = sm.get_iopub_stream(session_id)
100 shell_stream = sm.get_shell_stream(session_id)
101 iopub_router = IOPubStreamRouter(iopub_stream)
102 shell_router = ShellStreamRouter(shell_stream)
103 self._routers[(kernel_id, session_id, 'iopub')] = iopub_router
104 self._routers[(kernel_id, session_id, 'shell')] = shell_router
105 logging.info("Session started: %s, %s" % (kernel_id, session_id))
106
107 def stop_session(self, kernel_id):
108 # TODO: finish this!
109 sm = self.kernel_manager.get_session_manager(kernel_id)
110 session_id = self._session_dict[kernel_id]
111
112 123 def get_router(self, kernel_id, stream_name):
113 session_id = self._session_dict[kernel_id]
114 router = self._routers[(kernel_id, session_id, stream_name)]
124 router = self._routers[(kernel_id, stream_name)]
115 125 return router
116 126
127 #-----------------------------------------------------------------------------
128 # Aliases and Flags
129 #-----------------------------------------------------------------------------
130
131 flags = dict(ipkernel_flags)
132
133 # the flags that are specific to the frontend
134 # these must be scrubbed before being passed to the kernel,
135 # or it will raise an error on unrecognized flags
136 notebook_flags = []
137
138 aliases = dict(ipkernel_aliases)
139
140 aliases.update(dict(
141 ip = 'IPythonNotebookApp.ip',
142 port = 'IPythonNotebookApp.port'
143 colors = 'ZMQInteractiveShell.colors',
144 editor = 'IPythonWidget.editor',
145 ))
146
147 #-----------------------------------------------------------------------------
148 # IPythonNotebookApp
149 #-----------------------------------------------------------------------------
150
151 class IPythonNotebookApp(BaseIPythonApplication):
152 name = 'ipython-notebook'
153 default_config_file_name='ipython_notebook_config.py'
154
155 description = """
156 The IPython HTML Notebook.
157
158 This launches a Tornado based HTML Notebook Server that serves up an
159 HTML5/Javascript Notebook client.
160 """
161
162 classes = [IPKernelApp, ZMQInteractiveShell, ProfileDir, Session,
163 KernelManager, SessionManager]
164 flags = Dict(flags)
165 aliases = Dict(aliases)
166
167 kernel_argv = List(Unicode)
168
169 # connection info:
170 ip = Unicode(LOCALHOST, config=True,
171 help="The IP address the notebook server will listen on."
172 )
173
174 port = Int(8888, config=True,
175 help="The port the notebook server will listen on."
176 )
177
178 # the factory for creating a widget
179 widget_factory = Any(RichIPythonWidget)
180
181 def parse_command_line(self, argv=None):
182 super(IPythonNotebookApp, self).parse_command_line(argv)
183 if argv is None:
184 argv = sys.argv[1:]
185
186 self.kernel_argv = list(argv) # copy
187 # kernel should inherit default config file from frontend
188 self.kernel_argv.append("KernelApp.parent_appname='%s'"%self.name)
189 # scrub frontend-specific flags
190 for a in argv:
191 if a.startswith('--') and a[2:] in qt_flags:
192 self.kernel_argv.remove(a)
193
194 def init_kernel_manager(self):
195 # Don't let Qt or ZMQ swallow KeyboardInterupts.
196 signal.signal(signal.SIGINT, signal.SIG_DFL)
197
198 # Create a KernelManager and start a kernel.
199 self.kernel_manager = KernelManager(config=self.config, log=self.log)
200
201 def initialize(self, argv=None):
202 super(IPythonNotebookApp, self).initialize(argv)
203 self.init_kernel_mananger()
204 self.web_app = NotebookWebApplication()
205 self.http_server = httpserver.HTTPServer(self.web_app)
206 self.http_server.listen(self.port)
207
208 def start(self):
209 self.log.info("The IPython Notebook is running at: http://%s:%i" % (self.ip, self.port))
210 ioloop.IOLoop.instance().start()
211
212 #-----------------------------------------------------------------------------
213 # Main entry point
214 #-----------------------------------------------------------------------------
117 215
118 216 def launch_new_instance():
119 options.parse_command_line()
120 application = NotebookWebApplication()
121 http_server = httpserver.HTTPServer(application)
122 http_server.listen(options.options.port)
123 print "IPython Notebook running at: http://127.0.0.1:8888"
124 print "The github master of tornado is required to run this server:"
125 print " https://github.com/facebook/tornado/tree/master/tornado"
126 ioloop.IOLoop.instance().start()
127
128
129 if __name__ == "__main__":
130 main()
217 app = IPythonNotebookApp()
218 app.initialize()
219 app.start()
131 220
@@ -1,75 +1,83 b''
1 1 """A manager for session and channels for a single kernel."""
2 2
3 #-----------------------------------------------------------------------------
4 # Imports
5 #-----------------------------------------------------------------------------
6
3 7 import zmq
4 8 from zmq.eventloop.zmqstream import ZMQStream
5 9
6 10 from IPython.utils.traitlets import Instance, Dict, CBytes, Bool
7 11 from IPython.zmq.session import SessionFactory
8 12
9 13
10 14 class SessionManagerRunningError(Exception):
11 15 pass
12 16
17 #-----------------------------------------------------------------------------
18 # Classes
19 #-----------------------------------------------------------------------------
20
13 21
14 22 class SessionManager(SessionFactory):
15 23 """Manages a session for a kernel.
16 24
17 25 The object manages a variety of things for a connection session to
18 26 a running kernel:
19 27
20 28 * The set of channels or connected ZMQ streams to the kernel.
21 29 * An IPython.zmq.session.Session object that manages send/recv logic
22 30 for those channels.
23 31 """
24 32
25 33 kernel_manager = Instance('IPython.frontend.html.notebook.kernelmanager.KernelManager')
26 34 kernel_id = CBytes(b'')
27 35 _session_streams = Dict()
28 36 _running = Bool(False)
29 37
30 38 def __init__(self, **kwargs):
31 39 kernel_id = kwargs.pop('kernel_id')
32 40 super(SessionManager, self).__init__(**kwargs)
33 41 self.kernel_id = kernel_id
34 42 self.start()
35 43
36 44 def __del__(self):
37 45 self.stop()
38 46
39 47 def start(self):
40 48 if not self._running:
41 49 ports = self.kernel_manager.get_kernel_ports(self.kernel_id)
42 50 iopub_stream = self.create_connected_stream(ports['iopub_port'], zmq.SUB)
43 51 iopub_stream.socket.setsockopt(zmq.SUBSCRIBE, b'')
44 52 shell_stream = self.create_connected_stream(ports['shell_port'], zmq.XREQ)
45 53 self._session_streams = dict(
46 54 iopub_stream = iopub_stream,
47 55 shell_stream = shell_stream
48 56 )
49 57 self._running = True
50 58 else:
51 59 raise SessionManagerRunningError(
52 60 'Session manager is already running, call stop() before start()'
53 61 )
54 62
55 63 def stop(self):
56 64 if self._running:
57 65 for name, stream in self._session_streams.items():
58 66 stream.close()
59 67 self._session_streams = {}
60 68 self._running = False
61 69
62 70 def create_connected_stream(self, port, socket_type):
63 71 sock = self.context.socket(socket_type)
64 72 addr = "tcp://%s:%i" % (self.kernel_manager.get_kernel_ip(self.kernel_id), port)
65 73 self.log.info("Connecting to: %s, %r" % (addr, socket_type))
66 74 sock.connect(addr)
67 75 return ZMQStream(sock)
68 76
69 77 def get_iopub_stream(self):
70 78 return self._session_streams['iopub_stream']
71 79
72 80 def get_shell_stream(self):
73 81 return self._session_streams['shell_stream']
74 82
75 83
@@ -1,38 +1,39 b''
1 """Tests for the notebook kernel and session manager."""
1 2
2 3 from unittest import TestCase
3 4
4 5 from IPython.frontend.html.notebook.kernelmanager import KernelManager
5 6 from IPython.frontend.html.notebook.sessionmanager import SessionManagerRunningError
6 7
7 8 class TestKernelManager(TestCase):
8 9
9 10 def test_km_lifecycle(self):
10 11 km = KernelManager()
11 12 kid = km.start_kernel()
12 13 self.assert_(kid in km)
13 14 self.assertEquals(len(km),1)
14 15 km.kill_kernel(kid)
15 16 self.assert_(not kid in km)
16 17
17 18 kid = km.start_kernel()
18 19 self.assertEquals('127.0.0.1',km.get_kernel_ip(kid))
19 20 port_dict = km.get_kernel_ports(kid)
20 21 self.assert_('stdin_port' in port_dict)
21 22 self.assert_('iopub_port' in port_dict)
22 23 self.assert_('shell_port' in port_dict)
23 24 self.assert_('hb_port' in port_dict)
24 25 km.get_kernel_process(kid)
25 26
26 27 def test_session_manager(self):
27 28 km = KernelManager()
28 29 kid = km.start_kernel()
29 30 sm = km.create_session_manager(kid)
30 31 self.assert_(sm._running)
31 32 sm.stop()
32 33 self.assert_(not sm._running)
33 34 sm.start()
34 35 self.assertRaises(SessionManagerRunningError, sm.start)
35 36 sm.get_iopub_stream()
36 37 sm.get_shell_stream()
37 38 sm.session
38 39
@@ -1,374 +1,377 b''
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 """
4 4 The :class:`~IPython.core.application.Application` object for the command
5 5 line :command:`ipython` program.
6 6
7 7 Authors
8 8 -------
9 9
10 10 * Brian Granger
11 11 * Fernando Perez
12 12 * Min Ragan-Kelley
13 13 """
14 14
15 15 #-----------------------------------------------------------------------------
16 16 # Copyright (C) 2008-2010 The IPython Development Team
17 17 #
18 18 # Distributed under the terms of the BSD License. The full license is in
19 19 # the file COPYING, distributed as part of this software.
20 20 #-----------------------------------------------------------------------------
21 21
22 22 #-----------------------------------------------------------------------------
23 23 # Imports
24 24 #-----------------------------------------------------------------------------
25 25
26 26 from __future__ import absolute_import
27 27
28 28 import logging
29 29 import os
30 30 import sys
31 31
32 32 from IPython.config.loader import (
33 33 Config, PyFileConfigLoader
34 34 )
35 35 from IPython.config.application import boolean_flag
36 36 from IPython.core import release
37 37 from IPython.core import usage
38 38 from IPython.core.crashhandler import CrashHandler
39 39 from IPython.core.formatters import PlainTextFormatter
40 40 from IPython.core.application import (
41 41 ProfileDir, BaseIPythonApplication, base_flags, base_aliases
42 42 )
43 43 from IPython.core.shellapp import (
44 44 InteractiveShellApp, shell_flags, shell_aliases
45 45 )
46 46 from IPython.frontend.terminal.interactiveshell import TerminalInteractiveShell
47 47 from IPython.lib import inputhook
48 48 from IPython.utils import warn
49 49 from IPython.utils.path import get_ipython_dir, check_for_old_config
50 50 from IPython.utils.traitlets import (
51 51 Bool, Dict, CaselessStrEnum
52 52 )
53 53
54 54 #-----------------------------------------------------------------------------
55 55 # Globals, utilities and helpers
56 56 #-----------------------------------------------------------------------------
57 57
58 58 #: The default config file name for this application.
59 59 default_config_file_name = u'ipython_config.py'
60 60
61 61 _examples = """
62 62 ipython --pylab # start in pylab mode
63 63 ipython --pylab=qt # start in pylab mode with the qt4 backend
64 64 ipython --log-level=DEBUG # set logging to DEBUG
65 65 ipython --profile=foo # start with profile foo
66 66
67 67 ipython qtconsole # start the qtconsole GUI application
68 68 ipython qtconsole -h # show the help string for the qtconsole subcmd
69 69
70 70 ipython profile create foo # create profile foo w/ default config files
71 71 ipython profile -h # show the help string for the profile subcmd
72 72 """
73 73
74 74 #-----------------------------------------------------------------------------
75 75 # Crash handler for this application
76 76 #-----------------------------------------------------------------------------
77 77
78 78 class IPAppCrashHandler(CrashHandler):
79 79 """sys.excepthook for IPython itself, leaves a detailed report on disk."""
80 80
81 81 def __init__(self, app):
82 82 contact_name = release.authors['Fernando'][0]
83 83 contact_email = release.authors['Fernando'][1]
84 84 bug_tracker = 'http://github.com/ipython/ipython/issues'
85 85 super(IPAppCrashHandler,self).__init__(
86 86 app, contact_name, contact_email, bug_tracker
87 87 )
88 88
89 89 def make_report(self,traceback):
90 90 """Return a string containing a crash report."""
91 91
92 92 sec_sep = self.section_sep
93 93 # Start with parent report
94 94 report = [super(IPAppCrashHandler, self).make_report(traceback)]
95 95 # Add interactive-specific info we may have
96 96 rpt_add = report.append
97 97 try:
98 98 rpt_add(sec_sep+"History of session input:")
99 99 for line in self.app.shell.user_ns['_ih']:
100 100 rpt_add(line)
101 101 rpt_add('\n*** Last line of input (may not be in above history):\n')
102 102 rpt_add(self.app.shell._last_input_line+'\n')
103 103 except:
104 104 pass
105 105
106 106 return ''.join(report)
107 107
108 108 #-----------------------------------------------------------------------------
109 109 # Aliases and Flags
110 110 #-----------------------------------------------------------------------------
111 111 flags = dict(base_flags)
112 112 flags.update(shell_flags)
113 113 addflag = lambda *args: flags.update(boolean_flag(*args))
114 114 addflag('autoedit-syntax', 'TerminalInteractiveShell.autoedit_syntax',
115 115 'Turn on auto editing of files with syntax errors.',
116 116 'Turn off auto editing of files with syntax errors.'
117 117 )
118 118 addflag('banner', 'TerminalIPythonApp.display_banner',
119 119 "Display a banner upon starting IPython.",
120 120 "Don't display a banner upon starting IPython."
121 121 )
122 122 addflag('confirm-exit', 'TerminalInteractiveShell.confirm_exit',
123 123 """Set to confirm when you try to exit IPython with an EOF (Control-D
124 124 in Unix, Control-Z/Enter in Windows). By typing 'exit' or 'quit',
125 125 you can force a direct exit without any confirmation.""",
126 126 "Don't prompt the user when exiting."
127 127 )
128 128 addflag('term-title', 'TerminalInteractiveShell.term_title',
129 129 "Enable auto setting the terminal title.",
130 130 "Disable auto setting the terminal title."
131 131 )
132 132 classic_config = Config()
133 133 classic_config.InteractiveShell.cache_size = 0
134 134 classic_config.PlainTextFormatter.pprint = False
135 135 classic_config.InteractiveShell.prompt_in1 = '>>> '
136 136 classic_config.InteractiveShell.prompt_in2 = '... '
137 137 classic_config.InteractiveShell.prompt_out = ''
138 138 classic_config.InteractiveShell.separate_in = ''
139 139 classic_config.InteractiveShell.separate_out = ''
140 140 classic_config.InteractiveShell.separate_out2 = ''
141 141 classic_config.InteractiveShell.colors = 'NoColor'
142 142 classic_config.InteractiveShell.xmode = 'Plain'
143 143
144 144 flags['classic']=(
145 145 classic_config,
146 146 "Gives IPython a similar feel to the classic Python prompt."
147 147 )
148 148 # # log doesn't make so much sense this way anymore
149 149 # paa('--log','-l',
150 150 # action='store_true', dest='InteractiveShell.logstart',
151 151 # help="Start logging to the default log file (./ipython_log.py).")
152 152 #
153 153 # # quick is harder to implement
154 154 flags['quick']=(
155 155 {'TerminalIPythonApp' : {'quick' : True}},
156 156 "Enable quick startup with no config files."
157 157 )
158 158
159 159 flags['i'] = (
160 160 {'TerminalIPythonApp' : {'force_interact' : True}},
161 161 """If running code from the command line, become interactive afterwards.
162 162 Note: can also be given simply as '-i.'"""
163 163 )
164 164 flags['pylab'] = (
165 165 {'TerminalIPythonApp' : {'pylab' : 'auto'}},
166 166 """Pre-load matplotlib and numpy for interactive use with
167 167 the default matplotlib backend."""
168 168 )
169 169
170 170 aliases = dict(base_aliases)
171 171 aliases.update(shell_aliases)
172 172
173 173 # it's possible we don't want short aliases for *all* of these:
174 174 aliases.update(dict(
175 175 gui='TerminalIPythonApp.gui',
176 176 pylab='TerminalIPythonApp.pylab',
177 177 ))
178 178
179 179 #-----------------------------------------------------------------------------
180 180 # Main classes and functions
181 181 #-----------------------------------------------------------------------------
182 182
183 183 class TerminalIPythonApp(BaseIPythonApplication, InteractiveShellApp):
184 184 name = u'ipython'
185 185 description = usage.cl_usage
186 186 default_config_file_name = default_config_file_name
187 187 crash_handler_class = IPAppCrashHandler
188 188 examples = _examples
189 189
190 190 flags = Dict(flags)
191 191 aliases = Dict(aliases)
192 192 classes = [InteractiveShellApp, TerminalInteractiveShell, ProfileDir,
193 193 PlainTextFormatter]
194 194 subcommands = Dict(dict(
195 195 qtconsole=('IPython.frontend.qt.console.qtconsoleapp.IPythonQtConsoleApp',
196 196 """Launch the IPython Qt Console."""
197 197 ),
198 hotebook=('IPython.frontend.html.notebook.notebookapp.IPythonNotebookApp',
199 """Launch the IPython HTML Notebook Server"""
200 ),
198 201 profile = ("IPython.core.profileapp.ProfileApp",
199 202 "Create and manage IPython profiles.")
200 203 ))
201 204
202 205 # *do* autocreate requested profile, but don't create the config file.
203 206 auto_create=Bool(True)
204 207 # configurables
205 208 ignore_old_config=Bool(False, config=True,
206 209 help="Suppress warning messages about legacy config files"
207 210 )
208 211 quick = Bool(False, config=True,
209 212 help="""Start IPython quickly by skipping the loading of config files."""
210 213 )
211 214 def _quick_changed(self, name, old, new):
212 215 if new:
213 216 self.load_config_file = lambda *a, **kw: None
214 217 self.ignore_old_config=True
215 218
216 219 gui = CaselessStrEnum(('qt','wx','gtk'), config=True,
217 220 help="Enable GUI event loop integration ('qt', 'wx', 'gtk')."
218 221 )
219 222 pylab = CaselessStrEnum(['tk', 'qt', 'wx', 'gtk', 'osx', 'auto'],
220 223 config=True,
221 224 help="""Pre-load matplotlib and numpy for interactive use,
222 225 selecting a particular matplotlib backend and loop integration.
223 226 """
224 227 )
225 228 display_banner = Bool(True, config=True,
226 229 help="Whether to display a banner upon starting IPython."
227 230 )
228 231
229 232 # if there is code of files to run from the cmd line, don't interact
230 233 # unless the --i flag (App.force_interact) is true.
231 234 force_interact = Bool(False, config=True,
232 235 help="""If a command or file is given via the command-line,
233 236 e.g. 'ipython foo.py"""
234 237 )
235 238 def _force_interact_changed(self, name, old, new):
236 239 if new:
237 240 self.interact = True
238 241
239 242 def _file_to_run_changed(self, name, old, new):
240 243 if new and not self.force_interact:
241 244 self.interact = False
242 245 _code_to_run_changed = _file_to_run_changed
243 246
244 247 # internal, not-configurable
245 248 interact=Bool(True)
246 249
247 250
248 251 def parse_command_line(self, argv=None):
249 252 """override to allow old '-pylab' flag with deprecation warning"""
250 253
251 254 argv = sys.argv[1:] if argv is None else argv
252 255
253 256 if '-pylab' in argv:
254 257 # deprecated `-pylab` given,
255 258 # warn and transform into current syntax
256 259 argv = argv[:] # copy, don't clobber
257 260 idx = argv.index('-pylab')
258 261 warn.warn("`-pylab` flag has been deprecated.\n"
259 262 " Use `--pylab` instead, or `--pylab=foo` to specify a backend.")
260 263 sub = '--pylab'
261 264 if len(argv) > idx+1:
262 265 # check for gui arg, as in '-pylab qt'
263 266 gui = argv[idx+1]
264 267 if gui in ('wx', 'qt', 'qt4', 'gtk', 'auto'):
265 268 sub = '--pylab='+gui
266 269 argv.pop(idx+1)
267 270 argv[idx] = sub
268 271
269 272 return super(TerminalIPythonApp, self).parse_command_line(argv)
270 273
271 274 def initialize(self, argv=None):
272 275 """Do actions after construct, but before starting the app."""
273 276 super(TerminalIPythonApp, self).initialize(argv)
274 277 if self.subapp is not None:
275 278 # don't bother initializing further, starting subapp
276 279 return
277 280 if not self.ignore_old_config:
278 281 check_for_old_config(self.ipython_dir)
279 282 # print self.extra_args
280 283 if self.extra_args:
281 284 self.file_to_run = self.extra_args[0]
282 285 # create the shell
283 286 self.init_shell()
284 287 # and draw the banner
285 288 self.init_banner()
286 289 # Now a variety of things that happen after the banner is printed.
287 290 self.init_gui_pylab()
288 291 self.init_extensions()
289 292 self.init_code()
290 293
291 294 def init_shell(self):
292 295 """initialize the InteractiveShell instance"""
293 296 # I am a little hesitant to put these into InteractiveShell itself.
294 297 # But that might be the place for them
295 298 sys.path.insert(0, '')
296 299
297 300 # Create an InteractiveShell instance.
298 301 # shell.display_banner should always be False for the terminal
299 302 # based app, because we call shell.show_banner() by hand below
300 303 # so the banner shows *before* all extension loading stuff.
301 304 self.shell = TerminalInteractiveShell.instance(config=self.config,
302 305 display_banner=False, profile_dir=self.profile_dir,
303 306 ipython_dir=self.ipython_dir)
304 307
305 308 def init_banner(self):
306 309 """optionally display the banner"""
307 310 if self.display_banner and self.interact:
308 311 self.shell.show_banner()
309 312 # Make sure there is a space below the banner.
310 313 if self.log_level <= logging.INFO: print
311 314
312 315
313 316 def init_gui_pylab(self):
314 317 """Enable GUI event loop integration, taking pylab into account."""
315 318 gui = self.gui
316 319
317 320 # Using `pylab` will also require gui activation, though which toolkit
318 321 # to use may be chosen automatically based on mpl configuration.
319 322 if self.pylab:
320 323 activate = self.shell.enable_pylab
321 324 if self.pylab == 'auto':
322 325 gui = None
323 326 else:
324 327 gui = self.pylab
325 328 else:
326 329 # Enable only GUI integration, no pylab
327 330 activate = inputhook.enable_gui
328 331
329 332 if gui or self.pylab:
330 333 try:
331 334 self.log.info("Enabling GUI event loop integration, "
332 335 "toolkit=%s, pylab=%s" % (gui, self.pylab) )
333 336 activate(gui)
334 337 except:
335 338 self.log.warn("Error in enabling GUI event loop integration:")
336 339 self.shell.showtraceback()
337 340
338 341 def start(self):
339 342 if self.subapp is not None:
340 343 return self.subapp.start()
341 344 # perform any prexec steps:
342 345 if self.interact:
343 346 self.log.debug("Starting IPython's mainloop...")
344 347 self.shell.mainloop()
345 348 else:
346 349 self.log.debug("IPython not interactive...")
347 350
348 351
349 352 def load_default_config(ipython_dir=None):
350 353 """Load the default config file from the default ipython_dir.
351 354
352 355 This is useful for embedded shells.
353 356 """
354 357 if ipython_dir is None:
355 358 ipython_dir = get_ipython_dir()
356 359 profile_dir = os.path.join(ipython_dir, 'profile_default')
357 360 cl = PyFileConfigLoader(default_config_file_name, profile_dir)
358 361 try:
359 362 config = cl.load_config()
360 363 except IOError:
361 364 # no config found
362 365 config = Config()
363 366 return config
364 367
365 368
366 369 def launch_new_instance():
367 370 """Create and run a full blown IPython instance"""
368 371 app = TerminalIPythonApp.instance()
369 372 app.initialize()
370 373 app.start()
371 374
372 375
373 376 if __name__ == '__main__':
374 377 launch_new_instance()
General Comments 0
You need to be logged in to leave comments. Login now