##// END OF EJS Templates
KernelManager has port traits instead of multiple ip/port pairs...
MinRK -
Show More
@@ -1,503 +1,503 b''
1 """ A minimal application using the Qt console-style IPython frontend.
1 """ A minimal application using the Qt console-style IPython frontend.
2
2
3 This is not a complete console app, as subprocess will not be able to receive
3 This is not a complete console app, as subprocess will not be able to receive
4 input, there is no real readline support, among other limitations.
4 input, there is no real readline support, among other limitations.
5
5
6 Authors:
6 Authors:
7
7
8 * Evan Patterson
8 * Evan Patterson
9 * Min RK
9 * Min RK
10 * Erik Tollerud
10 * Erik Tollerud
11 * Fernando Perez
11 * Fernando Perez
12
12
13 """
13 """
14
14
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 # Imports
16 # Imports
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18
18
19 # stdlib imports
19 # stdlib imports
20 import os
20 import os
21 import signal
21 import signal
22 import sys
22 import sys
23 from getpass import getpass
23 from getpass import getpass
24
24
25 # System library imports
25 # System library imports
26 from IPython.external.qt import QtGui
26 from IPython.external.qt import QtGui
27 from pygments.styles import get_all_styles
27 from pygments.styles import get_all_styles
28
28
29 # external imports
29 # external imports
30 from IPython.external.ssh import tunnel
30 from IPython.external.ssh import tunnel
31
31
32 # Local imports
32 # Local imports
33 from IPython.config.application import boolean_flag
33 from IPython.config.application import boolean_flag
34 from IPython.core.application import BaseIPythonApplication
34 from IPython.core.application import BaseIPythonApplication
35 from IPython.core.profiledir import ProfileDir
35 from IPython.core.profiledir import ProfileDir
36 from IPython.frontend.qt.console.frontend_widget import FrontendWidget
36 from IPython.frontend.qt.console.frontend_widget import FrontendWidget
37 from IPython.frontend.qt.console.ipython_widget import IPythonWidget
37 from IPython.frontend.qt.console.ipython_widget import IPythonWidget
38 from IPython.frontend.qt.console.rich_ipython_widget import RichIPythonWidget
38 from IPython.frontend.qt.console.rich_ipython_widget import RichIPythonWidget
39 from IPython.frontend.qt.console import styles
39 from IPython.frontend.qt.console import styles
40 from IPython.frontend.qt.kernelmanager import QtKernelManager
40 from IPython.frontend.qt.kernelmanager import QtKernelManager
41 from IPython.parallel.util import select_random_ports
41 from IPython.parallel.util import select_random_ports
42 from IPython.utils.traitlets import (
42 from IPython.utils.traitlets import (
43 Dict, List, Unicode, Int, CaselessStrEnum, CBool, Any
43 Dict, List, Unicode, Int, CaselessStrEnum, CBool, Any
44 )
44 )
45 from IPython.zmq.ipkernel import (
45 from IPython.zmq.ipkernel import (
46 flags as ipkernel_flags,
46 flags as ipkernel_flags,
47 aliases as ipkernel_aliases,
47 aliases as ipkernel_aliases,
48 IPKernelApp
48 IPKernelApp
49 )
49 )
50 from IPython.zmq.session import Session
50 from IPython.zmq.session import Session
51 from IPython.zmq.zmqshell import ZMQInteractiveShell
51 from IPython.zmq.zmqshell import ZMQInteractiveShell
52
52
53
53
54 #-----------------------------------------------------------------------------
54 #-----------------------------------------------------------------------------
55 # Network Constants
55 # Network Constants
56 #-----------------------------------------------------------------------------
56 #-----------------------------------------------------------------------------
57
57
58 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
58 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
59
59
60 #-----------------------------------------------------------------------------
60 #-----------------------------------------------------------------------------
61 # Globals
61 # Globals
62 #-----------------------------------------------------------------------------
62 #-----------------------------------------------------------------------------
63
63
64 _examples = """
64 _examples = """
65 ipython qtconsole # start the qtconsole
65 ipython qtconsole # start the qtconsole
66 ipython qtconsole --pylab=inline # start with pylab in inline plotting mode
66 ipython qtconsole --pylab=inline # start with pylab in inline plotting mode
67 """
67 """
68
68
69 #-----------------------------------------------------------------------------
69 #-----------------------------------------------------------------------------
70 # Classes
70 # Classes
71 #-----------------------------------------------------------------------------
71 #-----------------------------------------------------------------------------
72
72
73 class MainWindow(QtGui.QMainWindow):
73 class MainWindow(QtGui.QMainWindow):
74
74
75 #---------------------------------------------------------------------------
75 #---------------------------------------------------------------------------
76 # 'object' interface
76 # 'object' interface
77 #---------------------------------------------------------------------------
77 #---------------------------------------------------------------------------
78
78
79 def __init__(self, app, frontend, existing=False, may_close=True,
79 def __init__(self, app, frontend, existing=False, may_close=True,
80 confirm_exit=True):
80 confirm_exit=True):
81 """ Create a MainWindow for the specified FrontendWidget.
81 """ Create a MainWindow for the specified FrontendWidget.
82
82
83 The app is passed as an argument to allow for different
83 The app is passed as an argument to allow for different
84 closing behavior depending on whether we are the Kernel's parent.
84 closing behavior depending on whether we are the Kernel's parent.
85
85
86 If existing is True, then this Console does not own the Kernel.
86 If existing is True, then this Console does not own the Kernel.
87
87
88 If may_close is True, then this Console is permitted to close the kernel
88 If may_close is True, then this Console is permitted to close the kernel
89 """
89 """
90 super(MainWindow, self).__init__()
90 super(MainWindow, self).__init__()
91 self._app = app
91 self._app = app
92 self._frontend = frontend
92 self._frontend = frontend
93 self._existing = existing
93 self._existing = existing
94 if existing:
94 if existing:
95 self._may_close = may_close
95 self._may_close = may_close
96 else:
96 else:
97 self._may_close = True
97 self._may_close = True
98 self._frontend.exit_requested.connect(self.close)
98 self._frontend.exit_requested.connect(self.close)
99 self._confirm_exit = confirm_exit
99 self._confirm_exit = confirm_exit
100 self.setCentralWidget(frontend)
100 self.setCentralWidget(frontend)
101
101
102 #---------------------------------------------------------------------------
102 #---------------------------------------------------------------------------
103 # QWidget interface
103 # QWidget interface
104 #---------------------------------------------------------------------------
104 #---------------------------------------------------------------------------
105
105
106 def closeEvent(self, event):
106 def closeEvent(self, event):
107 """ Close the window and the kernel (if necessary).
107 """ Close the window and the kernel (if necessary).
108
108
109 This will prompt the user if they are finished with the kernel, and if
109 This will prompt the user if they are finished with the kernel, and if
110 so, closes the kernel cleanly. Alternatively, if the exit magic is used,
110 so, closes the kernel cleanly. Alternatively, if the exit magic is used,
111 it closes without prompt.
111 it closes without prompt.
112 """
112 """
113 keepkernel = None #Use the prompt by default
113 keepkernel = None #Use the prompt by default
114 if hasattr(self._frontend,'_keep_kernel_on_exit'): #set by exit magic
114 if hasattr(self._frontend,'_keep_kernel_on_exit'): #set by exit magic
115 keepkernel = self._frontend._keep_kernel_on_exit
115 keepkernel = self._frontend._keep_kernel_on_exit
116
116
117 kernel_manager = self._frontend.kernel_manager
117 kernel_manager = self._frontend.kernel_manager
118
118
119 if keepkernel is None and not self._confirm_exit:
119 if keepkernel is None and not self._confirm_exit:
120 # don't prompt, just terminate the kernel if we own it
120 # don't prompt, just terminate the kernel if we own it
121 # or leave it alone if we don't
121 # or leave it alone if we don't
122 keepkernel = not self._existing
122 keepkernel = not self._existing
123
123
124 if keepkernel is None: #show prompt
124 if keepkernel is None: #show prompt
125 if kernel_manager and kernel_manager.channels_running:
125 if kernel_manager and kernel_manager.channels_running:
126 title = self.window().windowTitle()
126 title = self.window().windowTitle()
127 cancel = QtGui.QMessageBox.Cancel
127 cancel = QtGui.QMessageBox.Cancel
128 okay = QtGui.QMessageBox.Ok
128 okay = QtGui.QMessageBox.Ok
129 if self._may_close:
129 if self._may_close:
130 msg = "You are closing this Console window."
130 msg = "You are closing this Console window."
131 info = "Would you like to quit the Kernel and all attached Consoles as well?"
131 info = "Would you like to quit the Kernel and all attached Consoles as well?"
132 justthis = QtGui.QPushButton("&No, just this Console", self)
132 justthis = QtGui.QPushButton("&No, just this Console", self)
133 justthis.setShortcut('N')
133 justthis.setShortcut('N')
134 closeall = QtGui.QPushButton("&Yes, quit everything", self)
134 closeall = QtGui.QPushButton("&Yes, quit everything", self)
135 closeall.setShortcut('Y')
135 closeall.setShortcut('Y')
136 box = QtGui.QMessageBox(QtGui.QMessageBox.Question,
136 box = QtGui.QMessageBox(QtGui.QMessageBox.Question,
137 title, msg)
137 title, msg)
138 box.setInformativeText(info)
138 box.setInformativeText(info)
139 box.addButton(cancel)
139 box.addButton(cancel)
140 box.addButton(justthis, QtGui.QMessageBox.NoRole)
140 box.addButton(justthis, QtGui.QMessageBox.NoRole)
141 box.addButton(closeall, QtGui.QMessageBox.YesRole)
141 box.addButton(closeall, QtGui.QMessageBox.YesRole)
142 box.setDefaultButton(closeall)
142 box.setDefaultButton(closeall)
143 box.setEscapeButton(cancel)
143 box.setEscapeButton(cancel)
144 reply = box.exec_()
144 reply = box.exec_()
145 if reply == 1: # close All
145 if reply == 1: # close All
146 kernel_manager.shutdown_kernel()
146 kernel_manager.shutdown_kernel()
147 #kernel_manager.stop_channels()
147 #kernel_manager.stop_channels()
148 event.accept()
148 event.accept()
149 elif reply == 0: # close Console
149 elif reply == 0: # close Console
150 if not self._existing:
150 if not self._existing:
151 # Have kernel: don't quit, just close the window
151 # Have kernel: don't quit, just close the window
152 self._app.setQuitOnLastWindowClosed(False)
152 self._app.setQuitOnLastWindowClosed(False)
153 self.deleteLater()
153 self.deleteLater()
154 event.accept()
154 event.accept()
155 else:
155 else:
156 event.ignore()
156 event.ignore()
157 else:
157 else:
158 reply = QtGui.QMessageBox.question(self, title,
158 reply = QtGui.QMessageBox.question(self, title,
159 "Are you sure you want to close this Console?"+
159 "Are you sure you want to close this Console?"+
160 "\nThe Kernel and other Consoles will remain active.",
160 "\nThe Kernel and other Consoles will remain active.",
161 okay|cancel,
161 okay|cancel,
162 defaultButton=okay
162 defaultButton=okay
163 )
163 )
164 if reply == okay:
164 if reply == okay:
165 event.accept()
165 event.accept()
166 else:
166 else:
167 event.ignore()
167 event.ignore()
168 elif keepkernel: #close console but leave kernel running (no prompt)
168 elif keepkernel: #close console but leave kernel running (no prompt)
169 if kernel_manager and kernel_manager.channels_running:
169 if kernel_manager and kernel_manager.channels_running:
170 if not self._existing:
170 if not self._existing:
171 # I have the kernel: don't quit, just close the window
171 # I have the kernel: don't quit, just close the window
172 self._app.setQuitOnLastWindowClosed(False)
172 self._app.setQuitOnLastWindowClosed(False)
173 event.accept()
173 event.accept()
174 else: #close console and kernel (no prompt)
174 else: #close console and kernel (no prompt)
175 if kernel_manager and kernel_manager.channels_running:
175 if kernel_manager and kernel_manager.channels_running:
176 kernel_manager.shutdown_kernel()
176 kernel_manager.shutdown_kernel()
177 event.accept()
177 event.accept()
178
178
179 #-----------------------------------------------------------------------------
179 #-----------------------------------------------------------------------------
180 # Aliases and Flags
180 # Aliases and Flags
181 #-----------------------------------------------------------------------------
181 #-----------------------------------------------------------------------------
182
182
183 flags = dict(ipkernel_flags)
183 flags = dict(ipkernel_flags)
184 qt_flags = {
184 qt_flags = {
185 'existing' : ({'IPythonQtConsoleApp' : {'existing' : True}},
185 'existing' : ({'IPythonQtConsoleApp' : {'existing' : True}},
186 "Connect to an existing kernel."),
186 "Connect to an existing kernel."),
187 'pure' : ({'IPythonQtConsoleApp' : {'pure' : True}},
187 'pure' : ({'IPythonQtConsoleApp' : {'pure' : True}},
188 "Use a pure Python kernel instead of an IPython kernel."),
188 "Use a pure Python kernel instead of an IPython kernel."),
189 'plain' : ({'ConsoleWidget' : {'kind' : 'plain'}},
189 'plain' : ({'ConsoleWidget' : {'kind' : 'plain'}},
190 "Disable rich text support."),
190 "Disable rich text support."),
191 }
191 }
192 qt_flags.update(boolean_flag(
192 qt_flags.update(boolean_flag(
193 'gui-completion', 'ConsoleWidget.gui_completion',
193 'gui-completion', 'ConsoleWidget.gui_completion',
194 "use a GUI widget for tab completion",
194 "use a GUI widget for tab completion",
195 "use plaintext output for completion"
195 "use plaintext output for completion"
196 ))
196 ))
197 qt_flags.update(boolean_flag(
197 qt_flags.update(boolean_flag(
198 'confirm-exit', 'IPythonQtConsoleApp.confirm_exit',
198 'confirm-exit', 'IPythonQtConsoleApp.confirm_exit',
199 """Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
199 """Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
200 to force a direct exit without any confirmation.
200 to force a direct exit without any confirmation.
201 """,
201 """,
202 """Don't prompt the user when exiting. This will terminate the kernel
202 """Don't prompt the user when exiting. This will terminate the kernel
203 if it is owned by the frontend, and leave it alive if it is external.
203 if it is owned by the frontend, and leave it alive if it is external.
204 """
204 """
205 ))
205 ))
206 flags.update(qt_flags)
206 flags.update(qt_flags)
207 # the flags that are specific to the frontend
207 # the flags that are specific to the frontend
208 # these must be scrubbed before being passed to the kernel,
208 # these must be scrubbed before being passed to the kernel,
209 # or it will raise an error on unrecognized flags
209 # or it will raise an error on unrecognized flags
210 qt_flags = qt_flags.keys()
210 qt_flags = qt_flags.keys()
211
211
212 aliases = dict(ipkernel_aliases)
212 aliases = dict(ipkernel_aliases)
213
213
214 qt_aliases = dict(
214 qt_aliases = dict(
215 hb = 'IPythonQtConsoleApp.hb_port',
215 hb = 'IPythonQtConsoleApp.hb_port',
216 shell = 'IPythonQtConsoleApp.shell_port',
216 shell = 'IPythonQtConsoleApp.shell_port',
217 iopub = 'IPythonQtConsoleApp.iopub_port',
217 iopub = 'IPythonQtConsoleApp.iopub_port',
218 stdin = 'IPythonQtConsoleApp.stdin_port',
218 stdin = 'IPythonQtConsoleApp.stdin_port',
219 ip = 'IPythonQtConsoleApp.ip',
219 ip = 'IPythonQtConsoleApp.ip',
220
220
221 style = 'IPythonWidget.syntax_style',
221 style = 'IPythonWidget.syntax_style',
222 stylesheet = 'IPythonQtConsoleApp.stylesheet',
222 stylesheet = 'IPythonQtConsoleApp.stylesheet',
223 colors = 'ZMQInteractiveShell.colors',
223 colors = 'ZMQInteractiveShell.colors',
224
224
225 editor = 'IPythonWidget.editor',
225 editor = 'IPythonWidget.editor',
226 paging = 'ConsoleWidget.paging',
226 paging = 'ConsoleWidget.paging',
227 ssh = 'IPythonQtConsoleApp.sshserver',
227 ssh = 'IPythonQtConsoleApp.sshserver',
228 )
228 )
229 aliases.update(qt_aliases)
229 aliases.update(qt_aliases)
230 # also scrub aliases from the frontend
230 # also scrub aliases from the frontend
231 qt_flags.extend(qt_aliases.keys())
231 qt_flags.extend(qt_aliases.keys())
232
232
233
233
234 #-----------------------------------------------------------------------------
234 #-----------------------------------------------------------------------------
235 # IPythonQtConsole
235 # IPythonQtConsole
236 #-----------------------------------------------------------------------------
236 #-----------------------------------------------------------------------------
237
237
238
238
239 class IPythonQtConsoleApp(BaseIPythonApplication):
239 class IPythonQtConsoleApp(BaseIPythonApplication):
240 name = 'ipython-qtconsole'
240 name = 'ipython-qtconsole'
241 default_config_file_name='ipython_config.py'
241 default_config_file_name='ipython_config.py'
242
242
243 description = """
243 description = """
244 The IPython QtConsole.
244 The IPython QtConsole.
245
245
246 This launches a Console-style application using Qt. It is not a full
246 This launches a Console-style application using Qt. It is not a full
247 console, in that launched terminal subprocesses will not be able to accept
247 console, in that launched terminal subprocesses will not be able to accept
248 input.
248 input.
249
249
250 The QtConsole supports various extra features beyond the Terminal IPython
250 The QtConsole supports various extra features beyond the Terminal IPython
251 shell, such as inline plotting with matplotlib, via:
251 shell, such as inline plotting with matplotlib, via:
252
252
253 ipython qtconsole --pylab=inline
253 ipython qtconsole --pylab=inline
254
254
255 as well as saving your session as HTML, and printing the output.
255 as well as saving your session as HTML, and printing the output.
256
256
257 """
257 """
258 examples = _examples
258 examples = _examples
259
259
260 classes = [IPKernelApp, IPythonWidget, ZMQInteractiveShell, ProfileDir, Session]
260 classes = [IPKernelApp, IPythonWidget, ZMQInteractiveShell, ProfileDir, Session]
261 flags = Dict(flags)
261 flags = Dict(flags)
262 aliases = Dict(aliases)
262 aliases = Dict(aliases)
263
263
264 kernel_argv = List(Unicode)
264 kernel_argv = List(Unicode)
265
265
266 # create requested profiles by default, if they don't exist:
266 # create requested profiles by default, if they don't exist:
267 auto_create = CBool(True)
267 auto_create = CBool(True)
268 # connection info:
268 # connection info:
269 ip = Unicode(LOCALHOST, config=True,
269 ip = Unicode(LOCALHOST, config=True,
270 help="""Set the kernel\'s IP address [default localhost].
270 help="""Set the kernel\'s IP address [default localhost].
271 If the IP address is something other than localhost, then
271 If the IP address is something other than localhost, then
272 Consoles on other machines will be able to connect
272 Consoles on other machines will be able to connect
273 to the Kernel, so be careful!"""
273 to the Kernel, so be careful!"""
274 )
274 )
275
275
276 sshserver = Unicode('', config=True,
276 sshserver = Unicode('', config=True,
277 help="""The SSH server to use to connect to the kernel.""")
277 help="""The SSH server to use to connect to the kernel.""")
278 sshkey = Unicode('', config=True,
278 sshkey = Unicode('', config=True,
279 help="""Path to the ssh key to use for logging in to the ssh server.""")
279 help="""Path to the ssh key to use for logging in to the ssh server.""")
280
280
281 hb_port = Int(0, config=True,
281 hb_port = Int(0, config=True,
282 help="set the heartbeat port [default: random]")
282 help="set the heartbeat port [default: random]")
283 shell_port = Int(0, config=True,
283 shell_port = Int(0, config=True,
284 help="set the shell (XREP) port [default: random]")
284 help="set the shell (XREP) port [default: random]")
285 iopub_port = Int(0, config=True,
285 iopub_port = Int(0, config=True,
286 help="set the iopub (PUB) port [default: random]")
286 help="set the iopub (PUB) port [default: random]")
287 stdin_port = Int(0, config=True,
287 stdin_port = Int(0, config=True,
288 help="set the stdin (XREQ) port [default: random]")
288 help="set the stdin (XREQ) port [default: random]")
289
289
290 existing = CBool(False, config=True,
290 existing = CBool(False, config=True,
291 help="Whether to connect to an already running Kernel.")
291 help="Whether to connect to an already running Kernel.")
292
292
293 stylesheet = Unicode('', config=True,
293 stylesheet = Unicode('', config=True,
294 help="path to a custom CSS stylesheet")
294 help="path to a custom CSS stylesheet")
295
295
296 pure = CBool(False, config=True,
296 pure = CBool(False, config=True,
297 help="Use a pure Python kernel instead of an IPython kernel.")
297 help="Use a pure Python kernel instead of an IPython kernel.")
298 plain = CBool(False, config=True,
298 plain = CBool(False, config=True,
299 help="Use a plaintext widget instead of rich text (plain can't print/save).")
299 help="Use a plaintext widget instead of rich text (plain can't print/save).")
300
300
301 def _pure_changed(self, name, old, new):
301 def _pure_changed(self, name, old, new):
302 kind = 'plain' if self.plain else 'rich'
302 kind = 'plain' if self.plain else 'rich'
303 self.config.ConsoleWidget.kind = kind
303 self.config.ConsoleWidget.kind = kind
304 if self.pure:
304 if self.pure:
305 self.widget_factory = FrontendWidget
305 self.widget_factory = FrontendWidget
306 elif self.plain:
306 elif self.plain:
307 self.widget_factory = IPythonWidget
307 self.widget_factory = IPythonWidget
308 else:
308 else:
309 self.widget_factory = RichIPythonWidget
309 self.widget_factory = RichIPythonWidget
310
310
311 _plain_changed = _pure_changed
311 _plain_changed = _pure_changed
312
312
313 confirm_exit = CBool(True, config=True,
313 confirm_exit = CBool(True, config=True,
314 help="""
314 help="""
315 Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
315 Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
316 to force a direct exit without any confirmation.""",
316 to force a direct exit without any confirmation.""",
317 )
317 )
318
318
319 # the factory for creating a widget
319 # the factory for creating a widget
320 widget_factory = Any(RichIPythonWidget)
320 widget_factory = Any(RichIPythonWidget)
321
321
322 def parse_command_line(self, argv=None):
322 def parse_command_line(self, argv=None):
323 super(IPythonQtConsoleApp, self).parse_command_line(argv)
323 super(IPythonQtConsoleApp, self).parse_command_line(argv)
324 if argv is None:
324 if argv is None:
325 argv = sys.argv[1:]
325 argv = sys.argv[1:]
326
326
327 self.kernel_argv = list(argv) # copy
327 self.kernel_argv = list(argv) # copy
328 # kernel should inherit default config file from frontend
328 # kernel should inherit default config file from frontend
329 self.kernel_argv.append("--KernelApp.parent_appname='%s'"%self.name)
329 self.kernel_argv.append("--KernelApp.parent_appname='%s'"%self.name)
330 # scrub frontend-specific flags
330 # scrub frontend-specific flags
331 for a in argv:
331 for a in argv:
332
332
333 if a.startswith('-'):
333 if a.startswith('-'):
334 key = a.lstrip('-').split('=')[0]
334 key = a.lstrip('-').split('=')[0]
335 if key in qt_flags:
335 if key in qt_flags:
336 self.kernel_argv.remove(a)
336 self.kernel_argv.remove(a)
337
337
338 def init_ssh(self):
338 def init_ssh(self):
339 """set up ssh tunnels, if needed."""
339 """set up ssh tunnels, if needed."""
340 if not self.sshserver and not self.sshkey:
340 if not self.sshserver and not self.sshkey:
341 return
341 return
342
342
343 if self.sshkey and not self.sshserver:
343 if self.sshkey and not self.sshserver:
344 self.sshserver = self.ip
344 self.sshserver = self.ip
345 self.ip=LOCALHOST
345 self.ip=LOCALHOST
346
346
347 lports = select_random_ports(4)
347 lports = select_random_ports(4)
348 rports = self.shell_port, self.iopub_port, self.stdin_port, self.hb_port
348 rports = self.shell_port, self.iopub_port, self.stdin_port, self.hb_port
349 self.shell_port, self.iopub_port, self.stdin_port, self.hb_port = lports
349 self.shell_port, self.iopub_port, self.stdin_port, self.hb_port = lports
350
350
351 remote_ip = self.ip
351 remote_ip = self.ip
352 self.ip = LOCALHOST
352 self.ip = LOCALHOST
353 self.log.info("Forwarding connections to %s via %s"%(remote_ip, self.sshserver))
353 self.log.info("Forwarding connections to %s via %s"%(remote_ip, self.sshserver))
354
354
355 if tunnel.try_passwordless_ssh(self.sshserver, self.sshkey):
355 if tunnel.try_passwordless_ssh(self.sshserver, self.sshkey):
356 password=False
356 password=False
357 else:
357 else:
358 password = getpass("SSH Password for %s: "%self.sshserver)
358 password = getpass("SSH Password for %s: "%self.sshserver)
359
359
360 for lp,rp in zip(lports, rports):
360 for lp,rp in zip(lports, rports):
361 tunnel.ssh_tunnel(lp, rp, self.sshserver, remote_ip, self.sshkey, password)
361 tunnel.ssh_tunnel(lp, rp, self.sshserver, remote_ip, self.sshkey, password)
362
362
363 self.log.critical("To connect another client to this tunnel, use:")
363 self.log.critical("To connect another client to this tunnel, use:")
364 self.log.critical(
364 self.log.critical(
365 "--existing --shell={0} --iopub={1} --stdin={2} --hb={3}".format(
365 "--existing --shell={0} --iopub={1} --stdin={2} --hb={3}".format(
366 self.shell_port, self.iopub_port, self.stdin_port,
366 self.shell_port, self.iopub_port, self.stdin_port,
367 self.hb_port))
367 self.hb_port))
368
368
369 def init_kernel_manager(self):
369 def init_kernel_manager(self):
370 # Don't let Qt or ZMQ swallow KeyboardInterupts.
370 # Don't let Qt or ZMQ swallow KeyboardInterupts.
371 signal.signal(signal.SIGINT, signal.SIG_DFL)
371 signal.signal(signal.SIGINT, signal.SIG_DFL)
372
372
373 # Create a KernelManager and start a kernel.
373 # Create a KernelManager and start a kernel.
374 self.kernel_manager = QtKernelManager(
374 self.kernel_manager = QtKernelManager(ip=self.ip,
375 shell_address=(self.ip, self.shell_port),
375 shell_port=self.shell_port,
376 sub_address=(self.ip, self.iopub_port),
376 sub_port=self.iopub_port,
377 stdin_address=(self.ip, self.stdin_port),
377 stdin_port=self.stdin_port,
378 hb_address=(self.ip, self.hb_port),
378 hb_port=self.hb_port,
379 config=self.config
379 config=self.config,
380 )
380 )
381 # start the kernel
381 # start the kernel
382 if not self.existing:
382 if not self.existing:
383 kwargs = dict(ip=self.ip, ipython=not self.pure)
383 kwargs = dict(ip=self.ip, ipython=not self.pure)
384 kwargs['extra_arguments'] = self.kernel_argv
384 kwargs['extra_arguments'] = self.kernel_argv
385 self.kernel_manager.start_kernel(**kwargs)
385 self.kernel_manager.start_kernel(**kwargs)
386 self.kernel_manager.start_channels()
386 self.kernel_manager.start_channels()
387
387
388
388
389 def init_qt_elements(self):
389 def init_qt_elements(self):
390 # Create the widget.
390 # Create the widget.
391 self.app = QtGui.QApplication([])
391 self.app = QtGui.QApplication([])
392 local_kernel = (not self.existing) or self.ip in LOCAL_IPS
392 local_kernel = (not self.existing) or self.ip in LOCAL_IPS
393 self.widget = self.widget_factory(config=self.config,
393 self.widget = self.widget_factory(config=self.config,
394 local_kernel=local_kernel)
394 local_kernel=local_kernel)
395 self.widget.kernel_manager = self.kernel_manager
395 self.widget.kernel_manager = self.kernel_manager
396 self.window = MainWindow(self.app, self.widget, self.existing,
396 self.window = MainWindow(self.app, self.widget, self.existing,
397 may_close=local_kernel,
397 may_close=local_kernel,
398 confirm_exit=self.confirm_exit)
398 confirm_exit=self.confirm_exit)
399 self.window.setWindowTitle('Python' if self.pure else 'IPython')
399 self.window.setWindowTitle('Python' if self.pure else 'IPython')
400
400
401 def init_colors(self):
401 def init_colors(self):
402 """Configure the coloring of the widget"""
402 """Configure the coloring of the widget"""
403 # Note: This will be dramatically simplified when colors
403 # Note: This will be dramatically simplified when colors
404 # are removed from the backend.
404 # are removed from the backend.
405
405
406 if self.pure:
406 if self.pure:
407 # only IPythonWidget supports styling
407 # only IPythonWidget supports styling
408 return
408 return
409
409
410 # parse the colors arg down to current known labels
410 # parse the colors arg down to current known labels
411 try:
411 try:
412 colors = self.config.ZMQInteractiveShell.colors
412 colors = self.config.ZMQInteractiveShell.colors
413 except AttributeError:
413 except AttributeError:
414 colors = None
414 colors = None
415 try:
415 try:
416 style = self.config.IPythonWidget.colors
416 style = self.config.IPythonWidget.colors
417 except AttributeError:
417 except AttributeError:
418 style = None
418 style = None
419
419
420 # find the value for colors:
420 # find the value for colors:
421 if colors:
421 if colors:
422 colors=colors.lower()
422 colors=colors.lower()
423 if colors in ('lightbg', 'light'):
423 if colors in ('lightbg', 'light'):
424 colors='lightbg'
424 colors='lightbg'
425 elif colors in ('dark', 'linux'):
425 elif colors in ('dark', 'linux'):
426 colors='linux'
426 colors='linux'
427 else:
427 else:
428 colors='nocolor'
428 colors='nocolor'
429 elif style:
429 elif style:
430 if style=='bw':
430 if style=='bw':
431 colors='nocolor'
431 colors='nocolor'
432 elif styles.dark_style(style):
432 elif styles.dark_style(style):
433 colors='linux'
433 colors='linux'
434 else:
434 else:
435 colors='lightbg'
435 colors='lightbg'
436 else:
436 else:
437 colors=None
437 colors=None
438
438
439 # Configure the style.
439 # Configure the style.
440 widget = self.widget
440 widget = self.widget
441 if style:
441 if style:
442 widget.style_sheet = styles.sheet_from_template(style, colors)
442 widget.style_sheet = styles.sheet_from_template(style, colors)
443 widget.syntax_style = style
443 widget.syntax_style = style
444 widget._syntax_style_changed()
444 widget._syntax_style_changed()
445 widget._style_sheet_changed()
445 widget._style_sheet_changed()
446 elif colors:
446 elif colors:
447 # use a default style
447 # use a default style
448 widget.set_default_style(colors=colors)
448 widget.set_default_style(colors=colors)
449 else:
449 else:
450 # this is redundant for now, but allows the widget's
450 # this is redundant for now, but allows the widget's
451 # defaults to change
451 # defaults to change
452 widget.set_default_style()
452 widget.set_default_style()
453
453
454 if self.stylesheet:
454 if self.stylesheet:
455 # we got an expicit stylesheet
455 # we got an expicit stylesheet
456 if os.path.isfile(self.stylesheet):
456 if os.path.isfile(self.stylesheet):
457 with open(self.stylesheet) as f:
457 with open(self.stylesheet) as f:
458 sheet = f.read()
458 sheet = f.read()
459 widget.style_sheet = sheet
459 widget.style_sheet = sheet
460 widget._style_sheet_changed()
460 widget._style_sheet_changed()
461 else:
461 else:
462 raise IOError("Stylesheet %r not found."%self.stylesheet)
462 raise IOError("Stylesheet %r not found."%self.stylesheet)
463
463
464 def initialize(self, argv=None):
464 def initialize(self, argv=None):
465 super(IPythonQtConsoleApp, self).initialize(argv)
465 super(IPythonQtConsoleApp, self).initialize(argv)
466 self.init_ssh()
466 self.init_ssh()
467 self.init_kernel_manager()
467 self.init_kernel_manager()
468 self.init_qt_elements()
468 self.init_qt_elements()
469 self.init_colors()
469 self.init_colors()
470 self.init_window_shortcut()
470 self.init_window_shortcut()
471
471
472 def init_window_shortcut(self):
472 def init_window_shortcut(self):
473 fullScreenAction = QtGui.QAction('Toggle Full Screen', self.window)
473 fullScreenAction = QtGui.QAction('Toggle Full Screen', self.window)
474 fullScreenAction.setShortcut('Ctrl+Meta+Space')
474 fullScreenAction.setShortcut('Ctrl+Meta+Space')
475 fullScreenAction.triggered.connect(self.toggleFullScreen)
475 fullScreenAction.triggered.connect(self.toggleFullScreen)
476 self.window.addAction(fullScreenAction)
476 self.window.addAction(fullScreenAction)
477
477
478 def toggleFullScreen(self):
478 def toggleFullScreen(self):
479 if not self.window.isFullScreen():
479 if not self.window.isFullScreen():
480 self.window.showFullScreen()
480 self.window.showFullScreen()
481 else:
481 else:
482 self.window.showNormal()
482 self.window.showNormal()
483
483
484 def start(self):
484 def start(self):
485
485
486 # draw the window
486 # draw the window
487 self.window.show()
487 self.window.show()
488
488
489 # Start the application main loop.
489 # Start the application main loop.
490 self.app.exec_()
490 self.app.exec_()
491
491
492 #-----------------------------------------------------------------------------
492 #-----------------------------------------------------------------------------
493 # Main entry point
493 # Main entry point
494 #-----------------------------------------------------------------------------
494 #-----------------------------------------------------------------------------
495
495
496 def main():
496 def main():
497 app = IPythonQtConsoleApp()
497 app = IPythonQtConsoleApp()
498 app.initialize()
498 app.initialize()
499 app.start()
499 app.start()
500
500
501
501
502 if __name__ == '__main__':
502 if __name__ == '__main__':
503 main()
503 main()
@@ -1,999 +1,994 b''
1 """Base classes to manage the interaction with a running kernel.
1 """Base classes to manage the interaction with a running kernel.
2
2
3 TODO
3 TODO
4 * Create logger to handle debugging and console messages.
4 * Create logger to handle debugging and console messages.
5 """
5 """
6
6
7 #-----------------------------------------------------------------------------
7 #-----------------------------------------------------------------------------
8 # Copyright (C) 2008-2010 The IPython Development Team
8 # Copyright (C) 2008-2010 The IPython Development Team
9 #
9 #
10 # Distributed under the terms of the BSD License. The full license is in
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
11 # the file COPYING, distributed as part of this software.
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13
13
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15 # Imports
15 # Imports
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17
17
18 # Standard library imports.
18 # Standard library imports.
19 import atexit
20 import errno
19 import errno
21 from Queue import Queue, Empty
20 from Queue import Queue, Empty
22 from subprocess import Popen
21 from subprocess import Popen
23 import signal
22 import signal
24 import sys
23 import sys
25 from threading import Thread
24 from threading import Thread
26 import time
25 import time
27 import logging
28
26
29 # System library imports.
27 # System library imports.
30 import zmq
28 import zmq
31 from zmq import POLLIN, POLLOUT, POLLERR
29 from zmq import POLLIN, POLLOUT, POLLERR
32 from zmq.eventloop import ioloop
30 from zmq.eventloop import ioloop
33
31
34 # Local imports.
32 # Local imports.
35 from IPython.config.loader import Config
33 from IPython.config.loader import Config
36 from IPython.utils import io
37 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
34 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
38 from IPython.utils.traitlets import HasTraits, Any, Instance, Type, TCPAddress
35 from IPython.utils.traitlets import HasTraits, Any, Instance, Type, Unicode, Int
39 from session import Session, Message
36 from session import Session
40
37
41 #-----------------------------------------------------------------------------
38 #-----------------------------------------------------------------------------
42 # Constants and exceptions
39 # Constants and exceptions
43 #-----------------------------------------------------------------------------
40 #-----------------------------------------------------------------------------
44
41
45 class InvalidPortNumber(Exception):
42 class InvalidPortNumber(Exception):
46 pass
43 pass
47
44
48 #-----------------------------------------------------------------------------
45 #-----------------------------------------------------------------------------
49 # Utility functions
46 # Utility functions
50 #-----------------------------------------------------------------------------
47 #-----------------------------------------------------------------------------
51
48
52 # some utilities to validate message structure, these might get moved elsewhere
49 # some utilities to validate message structure, these might get moved elsewhere
53 # if they prove to have more generic utility
50 # if they prove to have more generic utility
54
51
55 def validate_string_list(lst):
52 def validate_string_list(lst):
56 """Validate that the input is a list of strings.
53 """Validate that the input is a list of strings.
57
54
58 Raises ValueError if not."""
55 Raises ValueError if not."""
59 if not isinstance(lst, list):
56 if not isinstance(lst, list):
60 raise ValueError('input %r must be a list' % lst)
57 raise ValueError('input %r must be a list' % lst)
61 for x in lst:
58 for x in lst:
62 if not isinstance(x, basestring):
59 if not isinstance(x, basestring):
63 raise ValueError('element %r in list must be a string' % x)
60 raise ValueError('element %r in list must be a string' % x)
64
61
65
62
66 def validate_string_dict(dct):
63 def validate_string_dict(dct):
67 """Validate that the input is a dict with string keys and values.
64 """Validate that the input is a dict with string keys and values.
68
65
69 Raises ValueError if not."""
66 Raises ValueError if not."""
70 for k,v in dct.iteritems():
67 for k,v in dct.iteritems():
71 if not isinstance(k, basestring):
68 if not isinstance(k, basestring):
72 raise ValueError('key %r in dict must be a string' % k)
69 raise ValueError('key %r in dict must be a string' % k)
73 if not isinstance(v, basestring):
70 if not isinstance(v, basestring):
74 raise ValueError('value %r in dict must be a string' % v)
71 raise ValueError('value %r in dict must be a string' % v)
75
72
76
73
77 #-----------------------------------------------------------------------------
74 #-----------------------------------------------------------------------------
78 # ZMQ Socket Channel classes
75 # ZMQ Socket Channel classes
79 #-----------------------------------------------------------------------------
76 #-----------------------------------------------------------------------------
80
77
81 class ZMQSocketChannel(Thread):
78 class ZMQSocketChannel(Thread):
82 """The base class for the channels that use ZMQ sockets.
79 """The base class for the channels that use ZMQ sockets.
83 """
80 """
84 context = None
81 context = None
85 session = None
82 session = None
86 socket = None
83 socket = None
87 ioloop = None
84 ioloop = None
88 iostate = None
85 iostate = None
89 _address = None
86 _address = None
90
87
91 def __init__(self, context, session, address):
88 def __init__(self, context, session, address):
92 """Create a channel
89 """Create a channel
93
90
94 Parameters
91 Parameters
95 ----------
92 ----------
96 context : :class:`zmq.Context`
93 context : :class:`zmq.Context`
97 The ZMQ context to use.
94 The ZMQ context to use.
98 session : :class:`session.Session`
95 session : :class:`session.Session`
99 The session to use.
96 The session to use.
100 address : tuple
97 address : tuple
101 Standard (ip, port) tuple that the kernel is listening on.
98 Standard (ip, port) tuple that the kernel is listening on.
102 """
99 """
103 super(ZMQSocketChannel, self).__init__()
100 super(ZMQSocketChannel, self).__init__()
104 self.daemon = True
101 self.daemon = True
105
102
106 self.context = context
103 self.context = context
107 self.session = session
104 self.session = session
108 if address[1] == 0:
105 if address[1] == 0:
109 message = 'The port number for a channel cannot be 0.'
106 message = 'The port number for a channel cannot be 0.'
110 raise InvalidPortNumber(message)
107 raise InvalidPortNumber(message)
111 self._address = address
108 self._address = address
112
109
113 def _run_loop(self):
110 def _run_loop(self):
114 """Run my loop, ignoring EINTR events in the poller"""
111 """Run my loop, ignoring EINTR events in the poller"""
115 while True:
112 while True:
116 try:
113 try:
117 self.ioloop.start()
114 self.ioloop.start()
118 except zmq.ZMQError as e:
115 except zmq.ZMQError as e:
119 if e.errno == errno.EINTR:
116 if e.errno == errno.EINTR:
120 continue
117 continue
121 else:
118 else:
122 raise
119 raise
123 else:
120 else:
124 break
121 break
125
122
126 def stop(self):
123 def stop(self):
127 """Stop the channel's activity.
124 """Stop the channel's activity.
128
125
129 This calls :method:`Thread.join` and returns when the thread
126 This calls :method:`Thread.join` and returns when the thread
130 terminates. :class:`RuntimeError` will be raised if
127 terminates. :class:`RuntimeError` will be raised if
131 :method:`self.start` is called again.
128 :method:`self.start` is called again.
132 """
129 """
133 self.join()
130 self.join()
134
131
135 @property
132 @property
136 def address(self):
133 def address(self):
137 """Get the channel's address as an (ip, port) tuple.
134 """Get the channel's address as an (ip, port) tuple.
138
135
139 By the default, the address is (localhost, 0), where 0 means a random
136 By the default, the address is (localhost, 0), where 0 means a random
140 port.
137 port.
141 """
138 """
142 return self._address
139 return self._address
143
140
144 def add_io_state(self, state):
141 def add_io_state(self, state):
145 """Add IO state to the eventloop.
142 """Add IO state to the eventloop.
146
143
147 Parameters
144 Parameters
148 ----------
145 ----------
149 state : zmq.POLLIN|zmq.POLLOUT|zmq.POLLERR
146 state : zmq.POLLIN|zmq.POLLOUT|zmq.POLLERR
150 The IO state flag to set.
147 The IO state flag to set.
151
148
152 This is thread safe as it uses the thread safe IOLoop.add_callback.
149 This is thread safe as it uses the thread safe IOLoop.add_callback.
153 """
150 """
154 def add_io_state_callback():
151 def add_io_state_callback():
155 if not self.iostate & state:
152 if not self.iostate & state:
156 self.iostate = self.iostate | state
153 self.iostate = self.iostate | state
157 self.ioloop.update_handler(self.socket, self.iostate)
154 self.ioloop.update_handler(self.socket, self.iostate)
158 self.ioloop.add_callback(add_io_state_callback)
155 self.ioloop.add_callback(add_io_state_callback)
159
156
160 def drop_io_state(self, state):
157 def drop_io_state(self, state):
161 """Drop IO state from the eventloop.
158 """Drop IO state from the eventloop.
162
159
163 Parameters
160 Parameters
164 ----------
161 ----------
165 state : zmq.POLLIN|zmq.POLLOUT|zmq.POLLERR
162 state : zmq.POLLIN|zmq.POLLOUT|zmq.POLLERR
166 The IO state flag to set.
163 The IO state flag to set.
167
164
168 This is thread safe as it uses the thread safe IOLoop.add_callback.
165 This is thread safe as it uses the thread safe IOLoop.add_callback.
169 """
166 """
170 def drop_io_state_callback():
167 def drop_io_state_callback():
171 if self.iostate & state:
168 if self.iostate & state:
172 self.iostate = self.iostate & (~state)
169 self.iostate = self.iostate & (~state)
173 self.ioloop.update_handler(self.socket, self.iostate)
170 self.ioloop.update_handler(self.socket, self.iostate)
174 self.ioloop.add_callback(drop_io_state_callback)
171 self.ioloop.add_callback(drop_io_state_callback)
175
172
176
173
177 class ShellSocketChannel(ZMQSocketChannel):
174 class ShellSocketChannel(ZMQSocketChannel):
178 """The XREQ channel for issues request/replies to the kernel.
175 """The XREQ channel for issues request/replies to the kernel.
179 """
176 """
180
177
181 command_queue = None
178 command_queue = None
182 # flag for whether execute requests should be allowed to call raw_input:
179 # flag for whether execute requests should be allowed to call raw_input:
183 allow_stdin = True
180 allow_stdin = True
184
181
185 def __init__(self, context, session, address):
182 def __init__(self, context, session, address):
186 super(ShellSocketChannel, self).__init__(context, session, address)
183 super(ShellSocketChannel, self).__init__(context, session, address)
187 self.command_queue = Queue()
184 self.command_queue = Queue()
188 self.ioloop = ioloop.IOLoop()
185 self.ioloop = ioloop.IOLoop()
189
186
190 def run(self):
187 def run(self):
191 """The thread's main activity. Call start() instead."""
188 """The thread's main activity. Call start() instead."""
192 self.socket = self.context.socket(zmq.DEALER)
189 self.socket = self.context.socket(zmq.DEALER)
193 self.socket.setsockopt(zmq.IDENTITY, self.session.bsession)
190 self.socket.setsockopt(zmq.IDENTITY, self.session.bsession)
194 self.socket.connect('tcp://%s:%i' % self.address)
191 self.socket.connect('tcp://%s:%i' % self.address)
195 self.iostate = POLLERR|POLLIN
192 self.iostate = POLLERR|POLLIN
196 self.ioloop.add_handler(self.socket, self._handle_events,
193 self.ioloop.add_handler(self.socket, self._handle_events,
197 self.iostate)
194 self.iostate)
198 self._run_loop()
195 self._run_loop()
199
196
200 def stop(self):
197 def stop(self):
201 self.ioloop.stop()
198 self.ioloop.stop()
202 super(ShellSocketChannel, self).stop()
199 super(ShellSocketChannel, self).stop()
203
200
204 def call_handlers(self, msg):
201 def call_handlers(self, msg):
205 """This method is called in the ioloop thread when a message arrives.
202 """This method is called in the ioloop thread when a message arrives.
206
203
207 Subclasses should override this method to handle incoming messages.
204 Subclasses should override this method to handle incoming messages.
208 It is important to remember that this method is called in the thread
205 It is important to remember that this method is called in the thread
209 so that some logic must be done to ensure that the application leve
206 so that some logic must be done to ensure that the application leve
210 handlers are called in the application thread.
207 handlers are called in the application thread.
211 """
208 """
212 raise NotImplementedError('call_handlers must be defined in a subclass.')
209 raise NotImplementedError('call_handlers must be defined in a subclass.')
213
210
214 def execute(self, code, silent=False,
211 def execute(self, code, silent=False,
215 user_variables=None, user_expressions=None, allow_stdin=None):
212 user_variables=None, user_expressions=None, allow_stdin=None):
216 """Execute code in the kernel.
213 """Execute code in the kernel.
217
214
218 Parameters
215 Parameters
219 ----------
216 ----------
220 code : str
217 code : str
221 A string of Python code.
218 A string of Python code.
222
219
223 silent : bool, optional (default False)
220 silent : bool, optional (default False)
224 If set, the kernel will execute the code as quietly possible.
221 If set, the kernel will execute the code as quietly possible.
225
222
226 user_variables : list, optional
223 user_variables : list, optional
227 A list of variable names to pull from the user's namespace. They
224 A list of variable names to pull from the user's namespace. They
228 will come back as a dict with these names as keys and their
225 will come back as a dict with these names as keys and their
229 :func:`repr` as values.
226 :func:`repr` as values.
230
227
231 user_expressions : dict, optional
228 user_expressions : dict, optional
232 A dict with string keys and to pull from the user's
229 A dict with string keys and to pull from the user's
233 namespace. They will come back as a dict with these names as keys
230 namespace. They will come back as a dict with these names as keys
234 and their :func:`repr` as values.
231 and their :func:`repr` as values.
235
232
236 allow_stdin : bool, optional
233 allow_stdin : bool, optional
237 Flag for
234 Flag for
238 A dict with string keys and to pull from the user's
235 A dict with string keys and to pull from the user's
239 namespace. They will come back as a dict with these names as keys
236 namespace. They will come back as a dict with these names as keys
240 and their :func:`repr` as values.
237 and their :func:`repr` as values.
241
238
242 Returns
239 Returns
243 -------
240 -------
244 The msg_id of the message sent.
241 The msg_id of the message sent.
245 """
242 """
246 if user_variables is None:
243 if user_variables is None:
247 user_variables = []
244 user_variables = []
248 if user_expressions is None:
245 if user_expressions is None:
249 user_expressions = {}
246 user_expressions = {}
250 if allow_stdin is None:
247 if allow_stdin is None:
251 allow_stdin = self.allow_stdin
248 allow_stdin = self.allow_stdin
252
249
253
250
254 # Don't waste network traffic if inputs are invalid
251 # Don't waste network traffic if inputs are invalid
255 if not isinstance(code, basestring):
252 if not isinstance(code, basestring):
256 raise ValueError('code %r must be a string' % code)
253 raise ValueError('code %r must be a string' % code)
257 validate_string_list(user_variables)
254 validate_string_list(user_variables)
258 validate_string_dict(user_expressions)
255 validate_string_dict(user_expressions)
259
256
260 # Create class for content/msg creation. Related to, but possibly
257 # Create class for content/msg creation. Related to, but possibly
261 # not in Session.
258 # not in Session.
262 content = dict(code=code, silent=silent,
259 content = dict(code=code, silent=silent,
263 user_variables=user_variables,
260 user_variables=user_variables,
264 user_expressions=user_expressions,
261 user_expressions=user_expressions,
265 allow_stdin=allow_stdin,
262 allow_stdin=allow_stdin,
266 )
263 )
267 msg = self.session.msg('execute_request', content)
264 msg = self.session.msg('execute_request', content)
268 self._queue_request(msg)
265 self._queue_request(msg)
269 return msg['header']['msg_id']
266 return msg['header']['msg_id']
270
267
271 def complete(self, text, line, cursor_pos, block=None):
268 def complete(self, text, line, cursor_pos, block=None):
272 """Tab complete text in the kernel's namespace.
269 """Tab complete text in the kernel's namespace.
273
270
274 Parameters
271 Parameters
275 ----------
272 ----------
276 text : str
273 text : str
277 The text to complete.
274 The text to complete.
278 line : str
275 line : str
279 The full line of text that is the surrounding context for the
276 The full line of text that is the surrounding context for the
280 text to complete.
277 text to complete.
281 cursor_pos : int
278 cursor_pos : int
282 The position of the cursor in the line where the completion was
279 The position of the cursor in the line where the completion was
283 requested.
280 requested.
284 block : str, optional
281 block : str, optional
285 The full block of code in which the completion is being requested.
282 The full block of code in which the completion is being requested.
286
283
287 Returns
284 Returns
288 -------
285 -------
289 The msg_id of the message sent.
286 The msg_id of the message sent.
290 """
287 """
291 content = dict(text=text, line=line, block=block, cursor_pos=cursor_pos)
288 content = dict(text=text, line=line, block=block, cursor_pos=cursor_pos)
292 msg = self.session.msg('complete_request', content)
289 msg = self.session.msg('complete_request', content)
293 self._queue_request(msg)
290 self._queue_request(msg)
294 return msg['header']['msg_id']
291 return msg['header']['msg_id']
295
292
296 def object_info(self, oname):
293 def object_info(self, oname):
297 """Get metadata information about an object.
294 """Get metadata information about an object.
298
295
299 Parameters
296 Parameters
300 ----------
297 ----------
301 oname : str
298 oname : str
302 A string specifying the object name.
299 A string specifying the object name.
303
300
304 Returns
301 Returns
305 -------
302 -------
306 The msg_id of the message sent.
303 The msg_id of the message sent.
307 """
304 """
308 content = dict(oname=oname)
305 content = dict(oname=oname)
309 msg = self.session.msg('object_info_request', content)
306 msg = self.session.msg('object_info_request', content)
310 self._queue_request(msg)
307 self._queue_request(msg)
311 return msg['header']['msg_id']
308 return msg['header']['msg_id']
312
309
313 def history(self, raw=True, output=False, hist_access_type='range', **kwargs):
310 def history(self, raw=True, output=False, hist_access_type='range', **kwargs):
314 """Get entries from the history list.
311 """Get entries from the history list.
315
312
316 Parameters
313 Parameters
317 ----------
314 ----------
318 raw : bool
315 raw : bool
319 If True, return the raw input.
316 If True, return the raw input.
320 output : bool
317 output : bool
321 If True, then return the output as well.
318 If True, then return the output as well.
322 hist_access_type : str
319 hist_access_type : str
323 'range' (fill in session, start and stop params), 'tail' (fill in n)
320 'range' (fill in session, start and stop params), 'tail' (fill in n)
324 or 'search' (fill in pattern param).
321 or 'search' (fill in pattern param).
325
322
326 session : int
323 session : int
327 For a range request, the session from which to get lines. Session
324 For a range request, the session from which to get lines. Session
328 numbers are positive integers; negative ones count back from the
325 numbers are positive integers; negative ones count back from the
329 current session.
326 current session.
330 start : int
327 start : int
331 The first line number of a history range.
328 The first line number of a history range.
332 stop : int
329 stop : int
333 The final (excluded) line number of a history range.
330 The final (excluded) line number of a history range.
334
331
335 n : int
332 n : int
336 The number of lines of history to get for a tail request.
333 The number of lines of history to get for a tail request.
337
334
338 pattern : str
335 pattern : str
339 The glob-syntax pattern for a search request.
336 The glob-syntax pattern for a search request.
340
337
341 Returns
338 Returns
342 -------
339 -------
343 The msg_id of the message sent.
340 The msg_id of the message sent.
344 """
341 """
345 content = dict(raw=raw, output=output, hist_access_type=hist_access_type,
342 content = dict(raw=raw, output=output, hist_access_type=hist_access_type,
346 **kwargs)
343 **kwargs)
347 msg = self.session.msg('history_request', content)
344 msg = self.session.msg('history_request', content)
348 self._queue_request(msg)
345 self._queue_request(msg)
349 return msg['header']['msg_id']
346 return msg['header']['msg_id']
350
347
351 def shutdown(self, restart=False):
348 def shutdown(self, restart=False):
352 """Request an immediate kernel shutdown.
349 """Request an immediate kernel shutdown.
353
350
354 Upon receipt of the (empty) reply, client code can safely assume that
351 Upon receipt of the (empty) reply, client code can safely assume that
355 the kernel has shut down and it's safe to forcefully terminate it if
352 the kernel has shut down and it's safe to forcefully terminate it if
356 it's still alive.
353 it's still alive.
357
354
358 The kernel will send the reply via a function registered with Python's
355 The kernel will send the reply via a function registered with Python's
359 atexit module, ensuring it's truly done as the kernel is done with all
356 atexit module, ensuring it's truly done as the kernel is done with all
360 normal operation.
357 normal operation.
361 """
358 """
362 # Send quit message to kernel. Once we implement kernel-side setattr,
359 # Send quit message to kernel. Once we implement kernel-side setattr,
363 # this should probably be done that way, but for now this will do.
360 # this should probably be done that way, but for now this will do.
364 msg = self.session.msg('shutdown_request', {'restart':restart})
361 msg = self.session.msg('shutdown_request', {'restart':restart})
365 self._queue_request(msg)
362 self._queue_request(msg)
366 return msg['header']['msg_id']
363 return msg['header']['msg_id']
367
364
368 def _handle_events(self, socket, events):
365 def _handle_events(self, socket, events):
369 if events & POLLERR:
366 if events & POLLERR:
370 self._handle_err()
367 self._handle_err()
371 if events & POLLOUT:
368 if events & POLLOUT:
372 self._handle_send()
369 self._handle_send()
373 if events & POLLIN:
370 if events & POLLIN:
374 self._handle_recv()
371 self._handle_recv()
375
372
376 def _handle_recv(self):
373 def _handle_recv(self):
377 ident,msg = self.session.recv(self.socket, 0)
374 ident,msg = self.session.recv(self.socket, 0)
378 self.call_handlers(msg)
375 self.call_handlers(msg)
379
376
380 def _handle_send(self):
377 def _handle_send(self):
381 try:
378 try:
382 msg = self.command_queue.get(False)
379 msg = self.command_queue.get(False)
383 except Empty:
380 except Empty:
384 pass
381 pass
385 else:
382 else:
386 self.session.send(self.socket,msg)
383 self.session.send(self.socket,msg)
387 if self.command_queue.empty():
384 if self.command_queue.empty():
388 self.drop_io_state(POLLOUT)
385 self.drop_io_state(POLLOUT)
389
386
390 def _handle_err(self):
387 def _handle_err(self):
391 # We don't want to let this go silently, so eventually we should log.
388 # We don't want to let this go silently, so eventually we should log.
392 raise zmq.ZMQError()
389 raise zmq.ZMQError()
393
390
394 def _queue_request(self, msg):
391 def _queue_request(self, msg):
395 self.command_queue.put(msg)
392 self.command_queue.put(msg)
396 self.add_io_state(POLLOUT)
393 self.add_io_state(POLLOUT)
397
394
398
395
399 class SubSocketChannel(ZMQSocketChannel):
396 class SubSocketChannel(ZMQSocketChannel):
400 """The SUB channel which listens for messages that the kernel publishes.
397 """The SUB channel which listens for messages that the kernel publishes.
401 """
398 """
402
399
403 def __init__(self, context, session, address):
400 def __init__(self, context, session, address):
404 super(SubSocketChannel, self).__init__(context, session, address)
401 super(SubSocketChannel, self).__init__(context, session, address)
405 self.ioloop = ioloop.IOLoop()
402 self.ioloop = ioloop.IOLoop()
406
403
407 def run(self):
404 def run(self):
408 """The thread's main activity. Call start() instead."""
405 """The thread's main activity. Call start() instead."""
409 self.socket = self.context.socket(zmq.SUB)
406 self.socket = self.context.socket(zmq.SUB)
410 self.socket.setsockopt(zmq.SUBSCRIBE,b'')
407 self.socket.setsockopt(zmq.SUBSCRIBE,b'')
411 self.socket.setsockopt(zmq.IDENTITY, self.session.bsession)
408 self.socket.setsockopt(zmq.IDENTITY, self.session.bsession)
412 self.socket.connect('tcp://%s:%i' % self.address)
409 self.socket.connect('tcp://%s:%i' % self.address)
413 self.iostate = POLLIN|POLLERR
410 self.iostate = POLLIN|POLLERR
414 self.ioloop.add_handler(self.socket, self._handle_events,
411 self.ioloop.add_handler(self.socket, self._handle_events,
415 self.iostate)
412 self.iostate)
416 self._run_loop()
413 self._run_loop()
417
414
418 def stop(self):
415 def stop(self):
419 self.ioloop.stop()
416 self.ioloop.stop()
420 super(SubSocketChannel, self).stop()
417 super(SubSocketChannel, self).stop()
421
418
422 def call_handlers(self, msg):
419 def call_handlers(self, msg):
423 """This method is called in the ioloop thread when a message arrives.
420 """This method is called in the ioloop thread when a message arrives.
424
421
425 Subclasses should override this method to handle incoming messages.
422 Subclasses should override this method to handle incoming messages.
426 It is important to remember that this method is called in the thread
423 It is important to remember that this method is called in the thread
427 so that some logic must be done to ensure that the application leve
424 so that some logic must be done to ensure that the application leve
428 handlers are called in the application thread.
425 handlers are called in the application thread.
429 """
426 """
430 raise NotImplementedError('call_handlers must be defined in a subclass.')
427 raise NotImplementedError('call_handlers must be defined in a subclass.')
431
428
432 def flush(self, timeout=1.0):
429 def flush(self, timeout=1.0):
433 """Immediately processes all pending messages on the SUB channel.
430 """Immediately processes all pending messages on the SUB channel.
434
431
435 Callers should use this method to ensure that :method:`call_handlers`
432 Callers should use this method to ensure that :method:`call_handlers`
436 has been called for all messages that have been received on the
433 has been called for all messages that have been received on the
437 0MQ SUB socket of this channel.
434 0MQ SUB socket of this channel.
438
435
439 This method is thread safe.
436 This method is thread safe.
440
437
441 Parameters
438 Parameters
442 ----------
439 ----------
443 timeout : float, optional
440 timeout : float, optional
444 The maximum amount of time to spend flushing, in seconds. The
441 The maximum amount of time to spend flushing, in seconds. The
445 default is one second.
442 default is one second.
446 """
443 """
447 # We do the IOLoop callback process twice to ensure that the IOLoop
444 # We do the IOLoop callback process twice to ensure that the IOLoop
448 # gets to perform at least one full poll.
445 # gets to perform at least one full poll.
449 stop_time = time.time() + timeout
446 stop_time = time.time() + timeout
450 for i in xrange(2):
447 for i in xrange(2):
451 self._flushed = False
448 self._flushed = False
452 self.ioloop.add_callback(self._flush)
449 self.ioloop.add_callback(self._flush)
453 while not self._flushed and time.time() < stop_time:
450 while not self._flushed and time.time() < stop_time:
454 time.sleep(0.01)
451 time.sleep(0.01)
455
452
456 def _handle_events(self, socket, events):
453 def _handle_events(self, socket, events):
457 # Turn on and off POLLOUT depending on if we have made a request
454 # Turn on and off POLLOUT depending on if we have made a request
458 if events & POLLERR:
455 if events & POLLERR:
459 self._handle_err()
456 self._handle_err()
460 if events & POLLIN:
457 if events & POLLIN:
461 self._handle_recv()
458 self._handle_recv()
462
459
463 def _handle_err(self):
460 def _handle_err(self):
464 # We don't want to let this go silently, so eventually we should log.
461 # We don't want to let this go silently, so eventually we should log.
465 raise zmq.ZMQError()
462 raise zmq.ZMQError()
466
463
467 def _handle_recv(self):
464 def _handle_recv(self):
468 # Get all of the messages we can
465 # Get all of the messages we can
469 while True:
466 while True:
470 try:
467 try:
471 ident,msg = self.session.recv(self.socket)
468 ident,msg = self.session.recv(self.socket)
472 except zmq.ZMQError:
469 except zmq.ZMQError:
473 # Check the errno?
470 # Check the errno?
474 # Will this trigger POLLERR?
471 # Will this trigger POLLERR?
475 break
472 break
476 else:
473 else:
477 if msg is None:
474 if msg is None:
478 break
475 break
479 self.call_handlers(msg)
476 self.call_handlers(msg)
480
477
481 def _flush(self):
478 def _flush(self):
482 """Callback for :method:`self.flush`."""
479 """Callback for :method:`self.flush`."""
483 self._flushed = True
480 self._flushed = True
484
481
485
482
486 class StdInSocketChannel(ZMQSocketChannel):
483 class StdInSocketChannel(ZMQSocketChannel):
487 """A reply channel to handle raw_input requests that the kernel makes."""
484 """A reply channel to handle raw_input requests that the kernel makes."""
488
485
489 msg_queue = None
486 msg_queue = None
490
487
491 def __init__(self, context, session, address):
488 def __init__(self, context, session, address):
492 super(StdInSocketChannel, self).__init__(context, session, address)
489 super(StdInSocketChannel, self).__init__(context, session, address)
493 self.ioloop = ioloop.IOLoop()
490 self.ioloop = ioloop.IOLoop()
494 self.msg_queue = Queue()
491 self.msg_queue = Queue()
495
492
496 def run(self):
493 def run(self):
497 """The thread's main activity. Call start() instead."""
494 """The thread's main activity. Call start() instead."""
498 self.socket = self.context.socket(zmq.DEALER)
495 self.socket = self.context.socket(zmq.DEALER)
499 self.socket.setsockopt(zmq.IDENTITY, self.session.bsession)
496 self.socket.setsockopt(zmq.IDENTITY, self.session.bsession)
500 self.socket.connect('tcp://%s:%i' % self.address)
497 self.socket.connect('tcp://%s:%i' % self.address)
501 self.iostate = POLLERR|POLLIN
498 self.iostate = POLLERR|POLLIN
502 self.ioloop.add_handler(self.socket, self._handle_events,
499 self.ioloop.add_handler(self.socket, self._handle_events,
503 self.iostate)
500 self.iostate)
504 self._run_loop()
501 self._run_loop()
505
502
506 def stop(self):
503 def stop(self):
507 self.ioloop.stop()
504 self.ioloop.stop()
508 super(StdInSocketChannel, self).stop()
505 super(StdInSocketChannel, self).stop()
509
506
510 def call_handlers(self, msg):
507 def call_handlers(self, msg):
511 """This method is called in the ioloop thread when a message arrives.
508 """This method is called in the ioloop thread when a message arrives.
512
509
513 Subclasses should override this method to handle incoming messages.
510 Subclasses should override this method to handle incoming messages.
514 It is important to remember that this method is called in the thread
511 It is important to remember that this method is called in the thread
515 so that some logic must be done to ensure that the application leve
512 so that some logic must be done to ensure that the application leve
516 handlers are called in the application thread.
513 handlers are called in the application thread.
517 """
514 """
518 raise NotImplementedError('call_handlers must be defined in a subclass.')
515 raise NotImplementedError('call_handlers must be defined in a subclass.')
519
516
520 def input(self, string):
517 def input(self, string):
521 """Send a string of raw input to the kernel."""
518 """Send a string of raw input to the kernel."""
522 content = dict(value=string)
519 content = dict(value=string)
523 msg = self.session.msg('input_reply', content)
520 msg = self.session.msg('input_reply', content)
524 self._queue_reply(msg)
521 self._queue_reply(msg)
525
522
526 def _handle_events(self, socket, events):
523 def _handle_events(self, socket, events):
527 if events & POLLERR:
524 if events & POLLERR:
528 self._handle_err()
525 self._handle_err()
529 if events & POLLOUT:
526 if events & POLLOUT:
530 self._handle_send()
527 self._handle_send()
531 if events & POLLIN:
528 if events & POLLIN:
532 self._handle_recv()
529 self._handle_recv()
533
530
534 def _handle_recv(self):
531 def _handle_recv(self):
535 ident,msg = self.session.recv(self.socket, 0)
532 ident,msg = self.session.recv(self.socket, 0)
536 self.call_handlers(msg)
533 self.call_handlers(msg)
537
534
538 def _handle_send(self):
535 def _handle_send(self):
539 try:
536 try:
540 msg = self.msg_queue.get(False)
537 msg = self.msg_queue.get(False)
541 except Empty:
538 except Empty:
542 pass
539 pass
543 else:
540 else:
544 self.session.send(self.socket,msg)
541 self.session.send(self.socket,msg)
545 if self.msg_queue.empty():
542 if self.msg_queue.empty():
546 self.drop_io_state(POLLOUT)
543 self.drop_io_state(POLLOUT)
547
544
548 def _handle_err(self):
545 def _handle_err(self):
549 # We don't want to let this go silently, so eventually we should log.
546 # We don't want to let this go silently, so eventually we should log.
550 raise zmq.ZMQError()
547 raise zmq.ZMQError()
551
548
552 def _queue_reply(self, msg):
549 def _queue_reply(self, msg):
553 self.msg_queue.put(msg)
550 self.msg_queue.put(msg)
554 self.add_io_state(POLLOUT)
551 self.add_io_state(POLLOUT)
555
552
556
553
557 class HBSocketChannel(ZMQSocketChannel):
554 class HBSocketChannel(ZMQSocketChannel):
558 """The heartbeat channel which monitors the kernel heartbeat.
555 """The heartbeat channel which monitors the kernel heartbeat.
559
556
560 Note that the heartbeat channel is paused by default. As long as you start
557 Note that the heartbeat channel is paused by default. As long as you start
561 this channel, the kernel manager will ensure that it is paused and un-paused
558 this channel, the kernel manager will ensure that it is paused and un-paused
562 as appropriate.
559 as appropriate.
563 """
560 """
564
561
565 time_to_dead = 3.0
562 time_to_dead = 3.0
566 socket = None
563 socket = None
567 poller = None
564 poller = None
568 _running = None
565 _running = None
569 _pause = None
566 _pause = None
570
567
571 def __init__(self, context, session, address):
568 def __init__(self, context, session, address):
572 super(HBSocketChannel, self).__init__(context, session, address)
569 super(HBSocketChannel, self).__init__(context, session, address)
573 self._running = False
570 self._running = False
574 self._pause = True
571 self._pause = True
575
572
576 def _create_socket(self):
573 def _create_socket(self):
577 self.socket = self.context.socket(zmq.REQ)
574 self.socket = self.context.socket(zmq.REQ)
578 self.socket.setsockopt(zmq.IDENTITY, self.session.bsession)
575 self.socket.setsockopt(zmq.IDENTITY, self.session.bsession)
579 self.socket.connect('tcp://%s:%i' % self.address)
576 self.socket.connect('tcp://%s:%i' % self.address)
580 self.poller = zmq.Poller()
577 self.poller = zmq.Poller()
581 self.poller.register(self.socket, zmq.POLLIN)
578 self.poller.register(self.socket, zmq.POLLIN)
582
579
583 def run(self):
580 def run(self):
584 """The thread's main activity. Call start() instead."""
581 """The thread's main activity. Call start() instead."""
585 self._create_socket()
582 self._create_socket()
586 self._running = True
583 self._running = True
587 while self._running:
584 while self._running:
588 if self._pause:
585 if self._pause:
589 time.sleep(self.time_to_dead)
586 time.sleep(self.time_to_dead)
590 else:
587 else:
591 since_last_heartbeat = 0.0
588 since_last_heartbeat = 0.0
592 request_time = time.time()
589 request_time = time.time()
593 try:
590 try:
594 #io.rprint('Ping from HB channel') # dbg
591 #io.rprint('Ping from HB channel') # dbg
595 self.socket.send(b'ping')
592 self.socket.send(b'ping')
596 except zmq.ZMQError, e:
593 except zmq.ZMQError, e:
597 #io.rprint('*** HB Error:', e) # dbg
594 #io.rprint('*** HB Error:', e) # dbg
598 if e.errno == zmq.EFSM:
595 if e.errno == zmq.EFSM:
599 #io.rprint('sleep...', self.time_to_dead) # dbg
596 #io.rprint('sleep...', self.time_to_dead) # dbg
600 time.sleep(self.time_to_dead)
597 time.sleep(self.time_to_dead)
601 self._create_socket()
598 self._create_socket()
602 else:
599 else:
603 raise
600 raise
604 else:
601 else:
605 while True:
602 while True:
606 try:
603 try:
607 self.socket.recv(zmq.NOBLOCK)
604 self.socket.recv(zmq.NOBLOCK)
608 except zmq.ZMQError, e:
605 except zmq.ZMQError, e:
609 #io.rprint('*** HB Error 2:', e) # dbg
606 #io.rprint('*** HB Error 2:', e) # dbg
610 if e.errno == zmq.EAGAIN:
607 if e.errno == zmq.EAGAIN:
611 before_poll = time.time()
608 before_poll = time.time()
612 until_dead = self.time_to_dead - (before_poll -
609 until_dead = self.time_to_dead - (before_poll -
613 request_time)
610 request_time)
614
611
615 # When the return value of poll() is an empty
612 # When the return value of poll() is an empty
616 # list, that is when things have gone wrong
613 # list, that is when things have gone wrong
617 # (zeromq bug). As long as it is not an empty
614 # (zeromq bug). As long as it is not an empty
618 # list, poll is working correctly even if it
615 # list, poll is working correctly even if it
619 # returns quickly. Note: poll timeout is in
616 # returns quickly. Note: poll timeout is in
620 # milliseconds.
617 # milliseconds.
621 if until_dead > 0.0:
618 if until_dead > 0.0:
622 while True:
619 while True:
623 try:
620 try:
624 self.poller.poll(1000 * until_dead)
621 self.poller.poll(1000 * until_dead)
625 except zmq.ZMQError as e:
622 except zmq.ZMQError as e:
626 if e.errno == errno.EINTR:
623 if e.errno == errno.EINTR:
627 continue
624 continue
628 else:
625 else:
629 raise
626 raise
630 else:
627 else:
631 break
628 break
632
629
633 since_last_heartbeat = time.time()-request_time
630 since_last_heartbeat = time.time()-request_time
634 if since_last_heartbeat > self.time_to_dead:
631 if since_last_heartbeat > self.time_to_dead:
635 self.call_handlers(since_last_heartbeat)
632 self.call_handlers(since_last_heartbeat)
636 break
633 break
637 else:
634 else:
638 # FIXME: We should probably log this instead.
635 # FIXME: We should probably log this instead.
639 raise
636 raise
640 else:
637 else:
641 until_dead = self.time_to_dead - (time.time() -
638 until_dead = self.time_to_dead - (time.time() -
642 request_time)
639 request_time)
643 if until_dead > 0.0:
640 if until_dead > 0.0:
644 #io.rprint('sleep...', self.time_to_dead) # dbg
641 #io.rprint('sleep...', self.time_to_dead) # dbg
645 time.sleep(until_dead)
642 time.sleep(until_dead)
646 break
643 break
647
644
648 def pause(self):
645 def pause(self):
649 """Pause the heartbeat."""
646 """Pause the heartbeat."""
650 self._pause = True
647 self._pause = True
651
648
652 def unpause(self):
649 def unpause(self):
653 """Unpause the heartbeat."""
650 """Unpause the heartbeat."""
654 self._pause = False
651 self._pause = False
655
652
656 def is_beating(self):
653 def is_beating(self):
657 """Is the heartbeat running and not paused."""
654 """Is the heartbeat running and not paused."""
658 if self.is_alive() and not self._pause:
655 if self.is_alive() and not self._pause:
659 return True
656 return True
660 else:
657 else:
661 return False
658 return False
662
659
663 def stop(self):
660 def stop(self):
664 self._running = False
661 self._running = False
665 super(HBSocketChannel, self).stop()
662 super(HBSocketChannel, self).stop()
666
663
667 def call_handlers(self, since_last_heartbeat):
664 def call_handlers(self, since_last_heartbeat):
668 """This method is called in the ioloop thread when a message arrives.
665 """This method is called in the ioloop thread when a message arrives.
669
666
670 Subclasses should override this method to handle incoming messages.
667 Subclasses should override this method to handle incoming messages.
671 It is important to remember that this method is called in the thread
668 It is important to remember that this method is called in the thread
672 so that some logic must be done to ensure that the application leve
669 so that some logic must be done to ensure that the application leve
673 handlers are called in the application thread.
670 handlers are called in the application thread.
674 """
671 """
675 raise NotImplementedError('call_handlers must be defined in a subclass.')
672 raise NotImplementedError('call_handlers must be defined in a subclass.')
676
673
677
674
678 #-----------------------------------------------------------------------------
675 #-----------------------------------------------------------------------------
679 # Main kernel manager class
676 # Main kernel manager class
680 #-----------------------------------------------------------------------------
677 #-----------------------------------------------------------------------------
681
678
682 class KernelManager(HasTraits):
679 class KernelManager(HasTraits):
683 """ Manages a kernel for a frontend.
680 """ Manages a kernel for a frontend.
684
681
685 The SUB channel is for the frontend to receive messages published by the
682 The SUB channel is for the frontend to receive messages published by the
686 kernel.
683 kernel.
687
684
688 The REQ channel is for the frontend to make requests of the kernel.
685 The REQ channel is for the frontend to make requests of the kernel.
689
686
690 The REP channel is for the kernel to request stdin (raw_input) from the
687 The REP channel is for the kernel to request stdin (raw_input) from the
691 frontend.
688 frontend.
692 """
689 """
693 # config object for passing to child configurables
690 # config object for passing to child configurables
694 config = Instance(Config)
691 config = Instance(Config)
695
692
696 # The PyZMQ Context to use for communication with the kernel.
693 # The PyZMQ Context to use for communication with the kernel.
697 context = Instance(zmq.Context)
694 context = Instance(zmq.Context)
698 def _context_default(self):
695 def _context_default(self):
699 return zmq.Context.instance()
696 return zmq.Context.instance()
700
697
701 # The Session to use for communication with the kernel.
698 # The Session to use for communication with the kernel.
702 session = Instance(Session)
699 session = Instance(Session)
703
700
704 # The kernel process with which the KernelManager is communicating.
701 # The kernel process with which the KernelManager is communicating.
705 kernel = Instance(Popen)
702 kernel = Instance(Popen)
706
703
707 # The addresses for the communication channels.
704 # The addresses for the communication channels.
708 shell_address = TCPAddress((LOCALHOST, 0))
705 ip = Unicode(LOCALHOST)
709 sub_address = TCPAddress((LOCALHOST, 0))
706 shell_port = Int(0)
710 stdin_address = TCPAddress((LOCALHOST, 0))
707 sub_port = Int(0)
711 hb_address = TCPAddress((LOCALHOST, 0))
708 stdin_port = Int(0)
709 hb_port = Int(0)
712
710
713 # The classes to use for the various channels.
711 # The classes to use for the various channels.
714 shell_channel_class = Type(ShellSocketChannel)
712 shell_channel_class = Type(ShellSocketChannel)
715 sub_channel_class = Type(SubSocketChannel)
713 sub_channel_class = Type(SubSocketChannel)
716 stdin_channel_class = Type(StdInSocketChannel)
714 stdin_channel_class = Type(StdInSocketChannel)
717 hb_channel_class = Type(HBSocketChannel)
715 hb_channel_class = Type(HBSocketChannel)
718
716
719 # Protected traits.
717 # Protected traits.
720 _launch_args = Any
718 _launch_args = Any
721 _shell_channel = Any
719 _shell_channel = Any
722 _sub_channel = Any
720 _sub_channel = Any
723 _stdin_channel = Any
721 _stdin_channel = Any
724 _hb_channel = Any
722 _hb_channel = Any
725
723
726 def __init__(self, **kwargs):
724 def __init__(self, **kwargs):
727 super(KernelManager, self).__init__(**kwargs)
725 super(KernelManager, self).__init__(**kwargs)
728 if self.session is None:
726 if self.session is None:
729 self.session = Session(config=self.config)
727 self.session = Session(config=self.config)
730 # Uncomment this to try closing the context.
728 # Uncomment this to try closing the context.
731 # atexit.register(self.context.term)
729 # atexit.register(self.context.term)
732
730
733 #--------------------------------------------------------------------------
731 #--------------------------------------------------------------------------
734 # Channel management methods:
732 # Channel management methods:
735 #--------------------------------------------------------------------------
733 #--------------------------------------------------------------------------
736
734
737 def start_channels(self, shell=True, sub=True, stdin=True, hb=True):
735 def start_channels(self, shell=True, sub=True, stdin=True, hb=True):
738 """Starts the channels for this kernel.
736 """Starts the channels for this kernel.
739
737
740 This will create the channels if they do not exist and then start
738 This will create the channels if they do not exist and then start
741 them. If port numbers of 0 are being used (random ports) then you
739 them. If port numbers of 0 are being used (random ports) then you
742 must first call :method:`start_kernel`. If the channels have been
740 must first call :method:`start_kernel`. If the channels have been
743 stopped and you call this, :class:`RuntimeError` will be raised.
741 stopped and you call this, :class:`RuntimeError` will be raised.
744 """
742 """
745 if shell:
743 if shell:
746 self.shell_channel.start()
744 self.shell_channel.start()
747 if sub:
745 if sub:
748 self.sub_channel.start()
746 self.sub_channel.start()
749 if stdin:
747 if stdin:
750 self.stdin_channel.start()
748 self.stdin_channel.start()
751 self.shell_channel.allow_stdin = True
749 self.shell_channel.allow_stdin = True
752 else:
750 else:
753 self.shell_channel.allow_stdin = False
751 self.shell_channel.allow_stdin = False
754 if hb:
752 if hb:
755 self.hb_channel.start()
753 self.hb_channel.start()
756
754
757 def stop_channels(self):
755 def stop_channels(self):
758 """Stops all the running channels for this kernel.
756 """Stops all the running channels for this kernel.
759 """
757 """
760 if self.shell_channel.is_alive():
758 if self.shell_channel.is_alive():
761 self.shell_channel.stop()
759 self.shell_channel.stop()
762 if self.sub_channel.is_alive():
760 if self.sub_channel.is_alive():
763 self.sub_channel.stop()
761 self.sub_channel.stop()
764 if self.stdin_channel.is_alive():
762 if self.stdin_channel.is_alive():
765 self.stdin_channel.stop()
763 self.stdin_channel.stop()
766 if self.hb_channel.is_alive():
764 if self.hb_channel.is_alive():
767 self.hb_channel.stop()
765 self.hb_channel.stop()
768
766
769 @property
767 @property
770 def channels_running(self):
768 def channels_running(self):
771 """Are any of the channels created and running?"""
769 """Are any of the channels created and running?"""
772 return (self.shell_channel.is_alive() or self.sub_channel.is_alive() or
770 return (self.shell_channel.is_alive() or self.sub_channel.is_alive() or
773 self.stdin_channel.is_alive() or self.hb_channel.is_alive())
771 self.stdin_channel.is_alive() or self.hb_channel.is_alive())
774
772
775 #--------------------------------------------------------------------------
773 #--------------------------------------------------------------------------
776 # Kernel process management methods:
774 # Kernel process management methods:
777 #--------------------------------------------------------------------------
775 #--------------------------------------------------------------------------
778
776
779 def start_kernel(self, **kw):
777 def start_kernel(self, **kw):
780 """Starts a kernel process and configures the manager to use it.
778 """Starts a kernel process and configures the manager to use it.
781
779
782 If random ports (port=0) are being used, this method must be called
780 If random ports (port=0) are being used, this method must be called
783 before the channels are created.
781 before the channels are created.
784
782
785 Parameters:
783 Parameters:
786 -----------
784 -----------
787 ipython : bool, optional (default True)
785 ipython : bool, optional (default True)
788 Whether to use an IPython kernel instead of a plain Python kernel.
786 Whether to use an IPython kernel instead of a plain Python kernel.
789
787
790 launcher : callable, optional (default None)
788 launcher : callable, optional (default None)
791 A custom function for launching the kernel process (generally a
789 A custom function for launching the kernel process (generally a
792 wrapper around ``entry_point.base_launch_kernel``). In most cases,
790 wrapper around ``entry_point.base_launch_kernel``). In most cases,
793 it should not be necessary to use this parameter.
791 it should not be necessary to use this parameter.
794
792
795 **kw : optional
793 **kw : optional
796 See respective options for IPython and Python kernels.
794 See respective options for IPython and Python kernels.
797 """
795 """
798 shell, sub, stdin, hb = self.shell_address, self.sub_address, \
796 if self.ip not in LOCAL_IPS:
799 self.stdin_address, self.hb_address
800 if shell[0] not in LOCAL_IPS or sub[0] not in LOCAL_IPS or \
801 stdin[0] not in LOCAL_IPS or hb[0] not in LOCAL_IPS:
802 raise RuntimeError("Can only launch a kernel on a local interface. "
797 raise RuntimeError("Can only launch a kernel on a local interface. "
803 "Make sure that the '*_address' attributes are "
798 "Make sure that the '*_address' attributes are "
804 "configured properly. "
799 "configured properly. "
805 "Currently valid addresses are: %s"%LOCAL_IPS
800 "Currently valid addresses are: %s"%LOCAL_IPS
806 )
801 )
807
802
808 self._launch_args = kw.copy()
803 self._launch_args = kw.copy()
809 launch_kernel = kw.pop('launcher', None)
804 launch_kernel = kw.pop('launcher', None)
810 if launch_kernel is None:
805 if launch_kernel is None:
811 if kw.pop('ipython', True):
806 if kw.pop('ipython', True):
812 from ipkernel import launch_kernel
807 from ipkernel import launch_kernel
813 else:
808 else:
814 from pykernel import launch_kernel
809 from pykernel import launch_kernel
815 self.kernel, xrep, pub, req, _hb = launch_kernel(
810 self.kernel, shell, sub, stdin, hb = launch_kernel(
816 shell_port=shell[1], iopub_port=sub[1],
811 shell_port=self.shell_port, iopub_port=self.sub_port,
817 stdin_port=stdin[1], hb_port=hb[1], **kw)
812 stdin_port=self.stdin_port, hb_port=self.hb_port, **kw)
818 self.shell_address = (shell[0], xrep)
813 self.shell_port = shell
819 self.sub_address = (sub[0], pub)
814 self.sub_port = sub
820 self.stdin_address = (stdin[0], req)
815 self.stdin_port = stdin
821 self.hb_address = (hb[0], _hb)
816 self.hb_port = hb
822
817
823 def shutdown_kernel(self, restart=False):
818 def shutdown_kernel(self, restart=False):
824 """ Attempts to the stop the kernel process cleanly. If the kernel
819 """ Attempts to the stop the kernel process cleanly. If the kernel
825 cannot be stopped, it is killed, if possible.
820 cannot be stopped, it is killed, if possible.
826 """
821 """
827 # FIXME: Shutdown does not work on Windows due to ZMQ errors!
822 # FIXME: Shutdown does not work on Windows due to ZMQ errors!
828 if sys.platform == 'win32':
823 if sys.platform == 'win32':
829 self.kill_kernel()
824 self.kill_kernel()
830 return
825 return
831
826
832 # Pause the heart beat channel if it exists.
827 # Pause the heart beat channel if it exists.
833 if self._hb_channel is not None:
828 if self._hb_channel is not None:
834 self._hb_channel.pause()
829 self._hb_channel.pause()
835
830
836 # Don't send any additional kernel kill messages immediately, to give
831 # Don't send any additional kernel kill messages immediately, to give
837 # the kernel a chance to properly execute shutdown actions. Wait for at
832 # the kernel a chance to properly execute shutdown actions. Wait for at
838 # most 1s, checking every 0.1s.
833 # most 1s, checking every 0.1s.
839 self.shell_channel.shutdown(restart=restart)
834 self.shell_channel.shutdown(restart=restart)
840 for i in range(10):
835 for i in range(10):
841 if self.is_alive:
836 if self.is_alive:
842 time.sleep(0.1)
837 time.sleep(0.1)
843 else:
838 else:
844 break
839 break
845 else:
840 else:
846 # OK, we've waited long enough.
841 # OK, we've waited long enough.
847 if self.has_kernel:
842 if self.has_kernel:
848 self.kill_kernel()
843 self.kill_kernel()
849
844
850 def restart_kernel(self, now=False, **kw):
845 def restart_kernel(self, now=False, **kw):
851 """Restarts a kernel with the arguments that were used to launch it.
846 """Restarts a kernel with the arguments that were used to launch it.
852
847
853 If the old kernel was launched with random ports, the same ports will be
848 If the old kernel was launched with random ports, the same ports will be
854 used for the new kernel.
849 used for the new kernel.
855
850
856 Parameters
851 Parameters
857 ----------
852 ----------
858 now : bool, optional
853 now : bool, optional
859 If True, the kernel is forcefully restarted *immediately*, without
854 If True, the kernel is forcefully restarted *immediately*, without
860 having a chance to do any cleanup action. Otherwise the kernel is
855 having a chance to do any cleanup action. Otherwise the kernel is
861 given 1s to clean up before a forceful restart is issued.
856 given 1s to clean up before a forceful restart is issued.
862
857
863 In all cases the kernel is restarted, the only difference is whether
858 In all cases the kernel is restarted, the only difference is whether
864 it is given a chance to perform a clean shutdown or not.
859 it is given a chance to perform a clean shutdown or not.
865
860
866 **kw : optional
861 **kw : optional
867 Any options specified here will replace those used to launch the
862 Any options specified here will replace those used to launch the
868 kernel.
863 kernel.
869 """
864 """
870 if self._launch_args is None:
865 if self._launch_args is None:
871 raise RuntimeError("Cannot restart the kernel. "
866 raise RuntimeError("Cannot restart the kernel. "
872 "No previous call to 'start_kernel'.")
867 "No previous call to 'start_kernel'.")
873 else:
868 else:
874 # Stop currently running kernel.
869 # Stop currently running kernel.
875 if self.has_kernel:
870 if self.has_kernel:
876 if now:
871 if now:
877 self.kill_kernel()
872 self.kill_kernel()
878 else:
873 else:
879 self.shutdown_kernel(restart=True)
874 self.shutdown_kernel(restart=True)
880
875
881 # Start new kernel.
876 # Start new kernel.
882 self._launch_args.update(kw)
877 self._launch_args.update(kw)
883 self.start_kernel(**self._launch_args)
878 self.start_kernel(**self._launch_args)
884
879
885 # FIXME: Messages get dropped in Windows due to probable ZMQ bug
880 # FIXME: Messages get dropped in Windows due to probable ZMQ bug
886 # unless there is some delay here.
881 # unless there is some delay here.
887 if sys.platform == 'win32':
882 if sys.platform == 'win32':
888 time.sleep(0.2)
883 time.sleep(0.2)
889
884
890 @property
885 @property
891 def has_kernel(self):
886 def has_kernel(self):
892 """Returns whether a kernel process has been specified for the kernel
887 """Returns whether a kernel process has been specified for the kernel
893 manager.
888 manager.
894 """
889 """
895 return self.kernel is not None
890 return self.kernel is not None
896
891
897 def kill_kernel(self):
892 def kill_kernel(self):
898 """ Kill the running kernel. """
893 """ Kill the running kernel. """
899 if self.has_kernel:
894 if self.has_kernel:
900 # Pause the heart beat channel if it exists.
895 # Pause the heart beat channel if it exists.
901 if self._hb_channel is not None:
896 if self._hb_channel is not None:
902 self._hb_channel.pause()
897 self._hb_channel.pause()
903
898
904 # Attempt to kill the kernel.
899 # Attempt to kill the kernel.
905 try:
900 try:
906 self.kernel.kill()
901 self.kernel.kill()
907 except OSError, e:
902 except OSError, e:
908 # In Windows, we will get an Access Denied error if the process
903 # In Windows, we will get an Access Denied error if the process
909 # has already terminated. Ignore it.
904 # has already terminated. Ignore it.
910 if sys.platform == 'win32':
905 if sys.platform == 'win32':
911 if e.winerror != 5:
906 if e.winerror != 5:
912 raise
907 raise
913 # On Unix, we may get an ESRCH error if the process has already
908 # On Unix, we may get an ESRCH error if the process has already
914 # terminated. Ignore it.
909 # terminated. Ignore it.
915 else:
910 else:
916 from errno import ESRCH
911 from errno import ESRCH
917 if e.errno != ESRCH:
912 if e.errno != ESRCH:
918 raise
913 raise
919 self.kernel = None
914 self.kernel = None
920 else:
915 else:
921 raise RuntimeError("Cannot kill kernel. No kernel is running!")
916 raise RuntimeError("Cannot kill kernel. No kernel is running!")
922
917
923 def interrupt_kernel(self):
918 def interrupt_kernel(self):
924 """ Interrupts the kernel. Unlike ``signal_kernel``, this operation is
919 """ Interrupts the kernel. Unlike ``signal_kernel``, this operation is
925 well supported on all platforms.
920 well supported on all platforms.
926 """
921 """
927 if self.has_kernel:
922 if self.has_kernel:
928 if sys.platform == 'win32':
923 if sys.platform == 'win32':
929 from parentpoller import ParentPollerWindows as Poller
924 from parentpoller import ParentPollerWindows as Poller
930 Poller.send_interrupt(self.kernel.win32_interrupt_event)
925 Poller.send_interrupt(self.kernel.win32_interrupt_event)
931 else:
926 else:
932 self.kernel.send_signal(signal.SIGINT)
927 self.kernel.send_signal(signal.SIGINT)
933 else:
928 else:
934 raise RuntimeError("Cannot interrupt kernel. No kernel is running!")
929 raise RuntimeError("Cannot interrupt kernel. No kernel is running!")
935
930
936 def signal_kernel(self, signum):
931 def signal_kernel(self, signum):
937 """ Sends a signal to the kernel. Note that since only SIGTERM is
932 """ Sends a signal to the kernel. Note that since only SIGTERM is
938 supported on Windows, this function is only useful on Unix systems.
933 supported on Windows, this function is only useful on Unix systems.
939 """
934 """
940 if self.has_kernel:
935 if self.has_kernel:
941 self.kernel.send_signal(signum)
936 self.kernel.send_signal(signum)
942 else:
937 else:
943 raise RuntimeError("Cannot signal kernel. No kernel is running!")
938 raise RuntimeError("Cannot signal kernel. No kernel is running!")
944
939
945 @property
940 @property
946 def is_alive(self):
941 def is_alive(self):
947 """Is the kernel process still running?"""
942 """Is the kernel process still running?"""
948 # FIXME: not using a heartbeat means this method is broken for any
943 # FIXME: not using a heartbeat means this method is broken for any
949 # remote kernel, it's only capable of handling local kernels.
944 # remote kernel, it's only capable of handling local kernels.
950 if self.has_kernel:
945 if self.has_kernel:
951 if self.kernel.poll() is None:
946 if self.kernel.poll() is None:
952 return True
947 return True
953 else:
948 else:
954 return False
949 return False
955 else:
950 else:
956 # We didn't start the kernel with this KernelManager so we don't
951 # We didn't start the kernel with this KernelManager so we don't
957 # know if it is running. We should use a heartbeat for this case.
952 # know if it is running. We should use a heartbeat for this case.
958 return True
953 return True
959
954
960 #--------------------------------------------------------------------------
955 #--------------------------------------------------------------------------
961 # Channels used for communication with the kernel:
956 # Channels used for communication with the kernel:
962 #--------------------------------------------------------------------------
957 #--------------------------------------------------------------------------
963
958
964 @property
959 @property
965 def shell_channel(self):
960 def shell_channel(self):
966 """Get the REQ socket channel object to make requests of the kernel."""
961 """Get the REQ socket channel object to make requests of the kernel."""
967 if self._shell_channel is None:
962 if self._shell_channel is None:
968 self._shell_channel = self.shell_channel_class(self.context,
963 self._shell_channel = self.shell_channel_class(self.context,
969 self.session,
964 self.session,
970 self.shell_address)
965 (self.ip, self.shell_port))
971 return self._shell_channel
966 return self._shell_channel
972
967
973 @property
968 @property
974 def sub_channel(self):
969 def sub_channel(self):
975 """Get the SUB socket channel object."""
970 """Get the SUB socket channel object."""
976 if self._sub_channel is None:
971 if self._sub_channel is None:
977 self._sub_channel = self.sub_channel_class(self.context,
972 self._sub_channel = self.sub_channel_class(self.context,
978 self.session,
973 self.session,
979 self.sub_address)
974 (self.ip, self.sub_port))
980 return self._sub_channel
975 return self._sub_channel
981
976
982 @property
977 @property
983 def stdin_channel(self):
978 def stdin_channel(self):
984 """Get the REP socket channel object to handle stdin (raw_input)."""
979 """Get the REP socket channel object to handle stdin (raw_input)."""
985 if self._stdin_channel is None:
980 if self._stdin_channel is None:
986 self._stdin_channel = self.stdin_channel_class(self.context,
981 self._stdin_channel = self.stdin_channel_class(self.context,
987 self.session,
982 self.session,
988 self.stdin_address)
983 (self.ip, self.stdin_port))
989 return self._stdin_channel
984 return self._stdin_channel
990
985
991 @property
986 @property
992 def hb_channel(self):
987 def hb_channel(self):
993 """Get the heartbeat socket channel object to check that the
988 """Get the heartbeat socket channel object to check that the
994 kernel is alive."""
989 kernel is alive."""
995 if self._hb_channel is None:
990 if self._hb_channel is None:
996 self._hb_channel = self.hb_channel_class(self.context,
991 self._hb_channel = self.hb_channel_class(self.context,
997 self.session,
992 self.session,
998 self.hb_address)
993 (self.ip, self.hb_port))
999 return self._hb_channel
994 return self._hb_channel
General Comments 0
You need to be logged in to leave comments. Login now