##// END OF EJS Templates
finish plumbing config to Session objects...
MinRK -
Show More
@@ -1,402 +1,404 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
3
4 #-----------------------------------------------------------------------------
4 #-----------------------------------------------------------------------------
5 # Imports
5 # Imports
6 #-----------------------------------------------------------------------------
6 #-----------------------------------------------------------------------------
7
7
8 # stdlib imports
8 # stdlib imports
9 import os
9 import os
10 import signal
10 import signal
11 import sys
11 import sys
12
12
13 # System library imports
13 # System library imports
14 from IPython.external.qt import QtGui
14 from IPython.external.qt import QtGui
15 from pygments.styles import get_all_styles
15 from pygments.styles import get_all_styles
16
16
17 # Local imports
17 # Local imports
18 from IPython.config.application import boolean_flag
18 from IPython.config.application import boolean_flag
19 from IPython.core.newapplication import ProfileDir, BaseIPythonApplication
19 from IPython.core.newapplication import ProfileDir, BaseIPythonApplication
20 from IPython.frontend.qt.console.frontend_widget import FrontendWidget
20 from IPython.frontend.qt.console.frontend_widget import FrontendWidget
21 from IPython.frontend.qt.console.ipython_widget import IPythonWidget
21 from IPython.frontend.qt.console.ipython_widget import IPythonWidget
22 from IPython.frontend.qt.console.rich_ipython_widget import RichIPythonWidget
22 from IPython.frontend.qt.console.rich_ipython_widget import RichIPythonWidget
23 from IPython.frontend.qt.console import styles
23 from IPython.frontend.qt.console import styles
24 from IPython.frontend.qt.kernelmanager import QtKernelManager
24 from IPython.frontend.qt.kernelmanager import QtKernelManager
25 from IPython.utils.traitlets import (
25 from IPython.utils.traitlets import (
26 Dict, List, Unicode, Int, CaselessStrEnum, CBool, Any
26 Dict, List, Unicode, Int, CaselessStrEnum, CBool, Any
27 )
27 )
28 from IPython.zmq.ipkernel import (
28 from IPython.zmq.ipkernel import (
29 flags as ipkernel_flags,
29 flags as ipkernel_flags,
30 aliases as ipkernel_aliases,
30 aliases as ipkernel_aliases,
31 IPKernelApp
31 IPKernelApp
32 )
32 )
33 from IPython.zmq.session import Session
33 from IPython.zmq.zmqshell import ZMQInteractiveShell
34 from IPython.zmq.zmqshell import ZMQInteractiveShell
34
35
35
36
36 #-----------------------------------------------------------------------------
37 #-----------------------------------------------------------------------------
37 # Network Constants
38 # Network Constants
38 #-----------------------------------------------------------------------------
39 #-----------------------------------------------------------------------------
39
40
40 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
41 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
41
42
42 #-----------------------------------------------------------------------------
43 #-----------------------------------------------------------------------------
43 # Classes
44 # Classes
44 #-----------------------------------------------------------------------------
45 #-----------------------------------------------------------------------------
45
46
46 class MainWindow(QtGui.QMainWindow):
47 class MainWindow(QtGui.QMainWindow):
47
48
48 #---------------------------------------------------------------------------
49 #---------------------------------------------------------------------------
49 # 'object' interface
50 # 'object' interface
50 #---------------------------------------------------------------------------
51 #---------------------------------------------------------------------------
51
52
52 def __init__(self, app, frontend, existing=False, may_close=True,
53 def __init__(self, app, frontend, existing=False, may_close=True,
53 confirm_exit=True):
54 confirm_exit=True):
54 """ Create a MainWindow for the specified FrontendWidget.
55 """ Create a MainWindow for the specified FrontendWidget.
55
56
56 The app is passed as an argument to allow for different
57 The app is passed as an argument to allow for different
57 closing behavior depending on whether we are the Kernel's parent.
58 closing behavior depending on whether we are the Kernel's parent.
58
59
59 If existing is True, then this Console does not own the Kernel.
60 If existing is True, then this Console does not own the Kernel.
60
61
61 If may_close is True, then this Console is permitted to close the kernel
62 If may_close is True, then this Console is permitted to close the kernel
62 """
63 """
63 super(MainWindow, self).__init__()
64 super(MainWindow, self).__init__()
64 self._app = app
65 self._app = app
65 self._frontend = frontend
66 self._frontend = frontend
66 self._existing = existing
67 self._existing = existing
67 if existing:
68 if existing:
68 self._may_close = may_close
69 self._may_close = may_close
69 else:
70 else:
70 self._may_close = True
71 self._may_close = True
71 self._frontend.exit_requested.connect(self.close)
72 self._frontend.exit_requested.connect(self.close)
72 self._confirm_exit = confirm_exit
73 self._confirm_exit = confirm_exit
73 self.setCentralWidget(frontend)
74 self.setCentralWidget(frontend)
74
75
75 #---------------------------------------------------------------------------
76 #---------------------------------------------------------------------------
76 # QWidget interface
77 # QWidget interface
77 #---------------------------------------------------------------------------
78 #---------------------------------------------------------------------------
78
79
79 def closeEvent(self, event):
80 def closeEvent(self, event):
80 """ Close the window and the kernel (if necessary).
81 """ Close the window and the kernel (if necessary).
81
82
82 This will prompt the user if they are finished with the kernel, and if
83 This will prompt the user if they are finished with the kernel, and if
83 so, closes the kernel cleanly. Alternatively, if the exit magic is used,
84 so, closes the kernel cleanly. Alternatively, if the exit magic is used,
84 it closes without prompt.
85 it closes without prompt.
85 """
86 """
86 keepkernel = None #Use the prompt by default
87 keepkernel = None #Use the prompt by default
87 if hasattr(self._frontend,'_keep_kernel_on_exit'): #set by exit magic
88 if hasattr(self._frontend,'_keep_kernel_on_exit'): #set by exit magic
88 keepkernel = self._frontend._keep_kernel_on_exit
89 keepkernel = self._frontend._keep_kernel_on_exit
89
90
90 kernel_manager = self._frontend.kernel_manager
91 kernel_manager = self._frontend.kernel_manager
91
92
92 if keepkernel is None and not self._confirm_exit:
93 if keepkernel is None and not self._confirm_exit:
93 # don't prompt, just terminate the kernel if we own it
94 # don't prompt, just terminate the kernel if we own it
94 # or leave it alone if we don't
95 # or leave it alone if we don't
95 keepkernel = not self._existing
96 keepkernel = not self._existing
96
97
97 if keepkernel is None: #show prompt
98 if keepkernel is None: #show prompt
98 if kernel_manager and kernel_manager.channels_running:
99 if kernel_manager and kernel_manager.channels_running:
99 title = self.window().windowTitle()
100 title = self.window().windowTitle()
100 cancel = QtGui.QMessageBox.Cancel
101 cancel = QtGui.QMessageBox.Cancel
101 okay = QtGui.QMessageBox.Ok
102 okay = QtGui.QMessageBox.Ok
102 if self._may_close:
103 if self._may_close:
103 msg = "You are closing this Console window."
104 msg = "You are closing this Console window."
104 info = "Would you like to quit the Kernel and all attached Consoles as well?"
105 info = "Would you like to quit the Kernel and all attached Consoles as well?"
105 justthis = QtGui.QPushButton("&No, just this Console", self)
106 justthis = QtGui.QPushButton("&No, just this Console", self)
106 justthis.setShortcut('N')
107 justthis.setShortcut('N')
107 closeall = QtGui.QPushButton("&Yes, quit everything", self)
108 closeall = QtGui.QPushButton("&Yes, quit everything", self)
108 closeall.setShortcut('Y')
109 closeall.setShortcut('Y')
109 box = QtGui.QMessageBox(QtGui.QMessageBox.Question,
110 box = QtGui.QMessageBox(QtGui.QMessageBox.Question,
110 title, msg)
111 title, msg)
111 box.setInformativeText(info)
112 box.setInformativeText(info)
112 box.addButton(cancel)
113 box.addButton(cancel)
113 box.addButton(justthis, QtGui.QMessageBox.NoRole)
114 box.addButton(justthis, QtGui.QMessageBox.NoRole)
114 box.addButton(closeall, QtGui.QMessageBox.YesRole)
115 box.addButton(closeall, QtGui.QMessageBox.YesRole)
115 box.setDefaultButton(closeall)
116 box.setDefaultButton(closeall)
116 box.setEscapeButton(cancel)
117 box.setEscapeButton(cancel)
117 reply = box.exec_()
118 reply = box.exec_()
118 if reply == 1: # close All
119 if reply == 1: # close All
119 kernel_manager.shutdown_kernel()
120 kernel_manager.shutdown_kernel()
120 #kernel_manager.stop_channels()
121 #kernel_manager.stop_channels()
121 event.accept()
122 event.accept()
122 elif reply == 0: # close Console
123 elif reply == 0: # close Console
123 if not self._existing:
124 if not self._existing:
124 # Have kernel: don't quit, just close the window
125 # Have kernel: don't quit, just close the window
125 self._app.setQuitOnLastWindowClosed(False)
126 self._app.setQuitOnLastWindowClosed(False)
126 self.deleteLater()
127 self.deleteLater()
127 event.accept()
128 event.accept()
128 else:
129 else:
129 event.ignore()
130 event.ignore()
130 else:
131 else:
131 reply = QtGui.QMessageBox.question(self, title,
132 reply = QtGui.QMessageBox.question(self, title,
132 "Are you sure you want to close this Console?"+
133 "Are you sure you want to close this Console?"+
133 "\nThe Kernel and other Consoles will remain active.",
134 "\nThe Kernel and other Consoles will remain active.",
134 okay|cancel,
135 okay|cancel,
135 defaultButton=okay
136 defaultButton=okay
136 )
137 )
137 if reply == okay:
138 if reply == okay:
138 event.accept()
139 event.accept()
139 else:
140 else:
140 event.ignore()
141 event.ignore()
141 elif keepkernel: #close console but leave kernel running (no prompt)
142 elif keepkernel: #close console but leave kernel running (no prompt)
142 if kernel_manager and kernel_manager.channels_running:
143 if kernel_manager and kernel_manager.channels_running:
143 if not self._existing:
144 if not self._existing:
144 # I have the kernel: don't quit, just close the window
145 # I have the kernel: don't quit, just close the window
145 self._app.setQuitOnLastWindowClosed(False)
146 self._app.setQuitOnLastWindowClosed(False)
146 event.accept()
147 event.accept()
147 else: #close console and kernel (no prompt)
148 else: #close console and kernel (no prompt)
148 if kernel_manager and kernel_manager.channels_running:
149 if kernel_manager and kernel_manager.channels_running:
149 kernel_manager.shutdown_kernel()
150 kernel_manager.shutdown_kernel()
150 event.accept()
151 event.accept()
151
152
152 #-----------------------------------------------------------------------------
153 #-----------------------------------------------------------------------------
153 # Aliases and Flags
154 # Aliases and Flags
154 #-----------------------------------------------------------------------------
155 #-----------------------------------------------------------------------------
155
156
156 flags = dict(ipkernel_flags)
157 flags = dict(ipkernel_flags)
157
158
158 flags.update({
159 flags.update({
159 'existing' : ({'IPythonQtConsoleApp' : {'existing' : True}},
160 'existing' : ({'IPythonQtConsoleApp' : {'existing' : True}},
160 "Connect to an existing kernel."),
161 "Connect to an existing kernel."),
161 'pure' : ({'IPythonQtConsoleApp' : {'pure' : True}},
162 'pure' : ({'IPythonQtConsoleApp' : {'pure' : True}},
162 "Use a pure Python kernel instead of an IPython kernel."),
163 "Use a pure Python kernel instead of an IPython kernel."),
163 'plain' : ({'ConsoleWidget' : {'kind' : 'plain'}},
164 'plain' : ({'ConsoleWidget' : {'kind' : 'plain'}},
164 "Disable rich text support."),
165 "Disable rich text support."),
165 })
166 })
166 flags.update(boolean_flag(
167 flags.update(boolean_flag(
167 'gui-completion', 'ConsoleWidget.gui_completion',
168 'gui-completion', 'ConsoleWidget.gui_completion',
168 "use a GUI widget for tab completion",
169 "use a GUI widget for tab completion",
169 "use plaintext output for completion"
170 "use plaintext output for completion"
170 ))
171 ))
171 flags.update(boolean_flag(
172 flags.update(boolean_flag(
172 'confirm-exit', 'IPythonQtConsoleApp.confirm_exit',
173 'confirm-exit', 'IPythonQtConsoleApp.confirm_exit',
173 """Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
174 """Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
174 to force a direct exit without any confirmation.
175 to force a direct exit without any confirmation.
175 """,
176 """,
176 """Don't prompt the user when exiting. This will terminate the kernel
177 """Don't prompt the user when exiting. This will terminate the kernel
177 if it is owned by the frontend, and leave it alive if it is external.
178 if it is owned by the frontend, and leave it alive if it is external.
178 """
179 """
179 ))
180 ))
180 # the flags that are specific to the frontend
181 # the flags that are specific to the frontend
181 # these must be scrubbed before being passed to the kernel,
182 # these must be scrubbed before being passed to the kernel,
182 # or it will raise an error on unrecognized flags
183 # or it will raise an error on unrecognized flags
183 qt_flags = ['existing', 'pure', 'plain', 'gui-completion', 'no-gui-completion',
184 qt_flags = ['existing', 'pure', 'plain', 'gui-completion', 'no-gui-completion',
184 'confirm-exit', 'no-confirm-exit']
185 'confirm-exit', 'no-confirm-exit']
185
186
186 aliases = dict(ipkernel_aliases)
187 aliases = dict(ipkernel_aliases)
187
188
188 aliases.update(dict(
189 aliases.update(dict(
189 hb = 'IPythonQtConsoleApp.hb_port',
190 hb = 'IPythonQtConsoleApp.hb_port',
190 shell = 'IPythonQtConsoleApp.shell_port',
191 shell = 'IPythonQtConsoleApp.shell_port',
191 iopub = 'IPythonQtConsoleApp.iopub_port',
192 iopub = 'IPythonQtConsoleApp.iopub_port',
192 stdin = 'IPythonQtConsoleApp.stdin_port',
193 stdin = 'IPythonQtConsoleApp.stdin_port',
193 ip = 'IPythonQtConsoleApp.ip',
194 ip = 'IPythonQtConsoleApp.ip',
194
195
195 plain = 'IPythonQtConsoleApp.plain',
196 plain = 'IPythonQtConsoleApp.plain',
196 pure = 'IPythonQtConsoleApp.pure',
197 pure = 'IPythonQtConsoleApp.pure',
197 gui_completion = 'ConsoleWidget.gui_completion',
198 gui_completion = 'ConsoleWidget.gui_completion',
198 style = 'IPythonWidget.syntax_style',
199 style = 'IPythonWidget.syntax_style',
199 stylesheet = 'IPythonQtConsoleApp.stylesheet',
200 stylesheet = 'IPythonQtConsoleApp.stylesheet',
200 colors = 'ZMQInteractiveShell.colors',
201 colors = 'ZMQInteractiveShell.colors',
201
202
202 editor = 'IPythonWidget.editor',
203 editor = 'IPythonWidget.editor',
203 pi = 'IPythonWidget.in_prompt',
204 pi = 'IPythonWidget.in_prompt',
204 po = 'IPythonWidget.out_prompt',
205 po = 'IPythonWidget.out_prompt',
205 si = 'IPythonWidget.input_sep',
206 si = 'IPythonWidget.input_sep',
206 so = 'IPythonWidget.output_sep',
207 so = 'IPythonWidget.output_sep',
207 so2 = 'IPythonWidget.output_sep2',
208 so2 = 'IPythonWidget.output_sep2',
208 ))
209 ))
209
210
210 #-----------------------------------------------------------------------------
211 #-----------------------------------------------------------------------------
211 # IPythonQtConsole
212 # IPythonQtConsole
212 #-----------------------------------------------------------------------------
213 #-----------------------------------------------------------------------------
213
214
214 class IPythonQtConsoleApp(BaseIPythonApplication):
215 class IPythonQtConsoleApp(BaseIPythonApplication):
215 name = 'ipython-qtconsole'
216 name = 'ipython-qtconsole'
216 default_config_file_name='ipython_config.py'
217 default_config_file_name='ipython_config.py'
217 classes = [IPKernelApp, IPythonWidget, ZMQInteractiveShell, ProfileDir]
218 classes = [IPKernelApp, IPythonWidget, ZMQInteractiveShell, ProfileDir, Session]
218 flags = Dict(flags)
219 flags = Dict(flags)
219 aliases = Dict(aliases)
220 aliases = Dict(aliases)
220
221
221 kernel_argv = List(Unicode)
222 kernel_argv = List(Unicode)
222
223
223 # connection info:
224 # connection info:
224 ip = Unicode(LOCALHOST, config=True,
225 ip = Unicode(LOCALHOST, config=True,
225 help="""Set the kernel\'s IP address [default localhost].
226 help="""Set the kernel\'s IP address [default localhost].
226 If the IP address is something other than localhost, then
227 If the IP address is something other than localhost, then
227 Consoles on other machines will be able to connect
228 Consoles on other machines will be able to connect
228 to the Kernel, so be careful!"""
229 to the Kernel, so be careful!"""
229 )
230 )
230 hb_port = Int(0, config=True,
231 hb_port = Int(0, config=True,
231 help="set the heartbeat port [default: random]")
232 help="set the heartbeat port [default: random]")
232 shell_port = Int(0, config=True,
233 shell_port = Int(0, config=True,
233 help="set the shell (XREP) port [default: random]")
234 help="set the shell (XREP) port [default: random]")
234 iopub_port = Int(0, config=True,
235 iopub_port = Int(0, config=True,
235 help="set the iopub (PUB) port [default: random]")
236 help="set the iopub (PUB) port [default: random]")
236 stdin_port = Int(0, config=True,
237 stdin_port = Int(0, config=True,
237 help="set the stdin (XREQ) port [default: random]")
238 help="set the stdin (XREQ) port [default: random]")
238
239
239 existing = CBool(False, config=True,
240 existing = CBool(False, config=True,
240 help="Whether to connect to an already running Kernel.")
241 help="Whether to connect to an already running Kernel.")
241
242
242 stylesheet = Unicode('', config=True,
243 stylesheet = Unicode('', config=True,
243 help="path to a custom CSS stylesheet")
244 help="path to a custom CSS stylesheet")
244
245
245 pure = CBool(False, config=True,
246 pure = CBool(False, config=True,
246 help="Use a pure Python kernel instead of an IPython kernel.")
247 help="Use a pure Python kernel instead of an IPython kernel.")
247 plain = CBool(False, config=True,
248 plain = CBool(False, config=True,
248 help="Use a plaintext widget instead of rich text (plain can't print/save).")
249 help="Use a plaintext widget instead of rich text (plain can't print/save).")
249
250
250 def _pure_changed(self, name, old, new):
251 def _pure_changed(self, name, old, new):
251 kind = 'plain' if self.plain else 'rich'
252 kind = 'plain' if self.plain else 'rich'
252 self.config.ConsoleWidget.kind = kind
253 self.config.ConsoleWidget.kind = kind
253 if self.pure:
254 if self.pure:
254 self.widget_factory = FrontendWidget
255 self.widget_factory = FrontendWidget
255 elif self.plain:
256 elif self.plain:
256 self.widget_factory = IPythonWidget
257 self.widget_factory = IPythonWidget
257 else:
258 else:
258 self.widget_factory = RichIPythonWidget
259 self.widget_factory = RichIPythonWidget
259
260
260 _plain_changed = _pure_changed
261 _plain_changed = _pure_changed
261
262
262 confirm_exit = CBool(True, config=True,
263 confirm_exit = CBool(True, config=True,
263 help="""
264 help="""
264 Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
265 Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
265 to force a direct exit without any confirmation.""",
266 to force a direct exit without any confirmation.""",
266 )
267 )
267
268
268 # the factory for creating a widget
269 # the factory for creating a widget
269 widget_factory = Any(RichIPythonWidget)
270 widget_factory = Any(RichIPythonWidget)
270
271
271 def parse_command_line(self, argv=None):
272 def parse_command_line(self, argv=None):
272 super(IPythonQtConsoleApp, self).parse_command_line(argv)
273 super(IPythonQtConsoleApp, self).parse_command_line(argv)
273 if argv is None:
274 if argv is None:
274 argv = sys.argv[1:]
275 argv = sys.argv[1:]
275
276
276 self.kernel_argv = list(argv) # copy
277 self.kernel_argv = list(argv) # copy
277
278
278 # scrub frontend-specific flags
279 # scrub frontend-specific flags
279 for a in argv:
280 for a in argv:
280 if a.startswith('--') and a[2:] in qt_flags:
281 if a.startswith('--') and a[2:] in qt_flags:
281 self.kernel_argv.remove(a)
282 self.kernel_argv.remove(a)
282
283
283 def init_kernel_manager(self):
284 def init_kernel_manager(self):
284 # Don't let Qt or ZMQ swallow KeyboardInterupts.
285 # Don't let Qt or ZMQ swallow KeyboardInterupts.
285 signal.signal(signal.SIGINT, signal.SIG_DFL)
286 signal.signal(signal.SIGINT, signal.SIG_DFL)
286
287
287 # Create a KernelManager and start a kernel.
288 # Create a KernelManager and start a kernel.
288 self.kernel_manager = QtKernelManager(
289 self.kernel_manager = QtKernelManager(
289 shell_address=(self.ip, self.shell_port),
290 shell_address=(self.ip, self.shell_port),
290 sub_address=(self.ip, self.iopub_port),
291 sub_address=(self.ip, self.iopub_port),
291 stdin_address=(self.ip, self.stdin_port),
292 stdin_address=(self.ip, self.stdin_port),
292 hb_address=(self.ip, self.hb_port)
293 hb_address=(self.ip, self.hb_port),
294 config=self.config
293 )
295 )
294 # start the kernel
296 # start the kernel
295 if not self.existing:
297 if not self.existing:
296 kwargs = dict(ip=self.ip, ipython=not self.pure)
298 kwargs = dict(ip=self.ip, ipython=not self.pure)
297 kwargs['extra_arguments'] = self.kernel_argv
299 kwargs['extra_arguments'] = self.kernel_argv
298 self.kernel_manager.start_kernel(**kwargs)
300 self.kernel_manager.start_kernel(**kwargs)
299 self.kernel_manager.start_channels()
301 self.kernel_manager.start_channels()
300
302
301
303
302 def init_qt_elements(self):
304 def init_qt_elements(self):
303 # Create the widget.
305 # Create the widget.
304 self.app = QtGui.QApplication([])
306 self.app = QtGui.QApplication([])
305 local_kernel = (not self.existing) or self.ip in LOCAL_IPS
307 local_kernel = (not self.existing) or self.ip in LOCAL_IPS
306 self.widget = self.widget_factory(config=self.config,
308 self.widget = self.widget_factory(config=self.config,
307 local_kernel=local_kernel)
309 local_kernel=local_kernel)
308 self.widget.kernel_manager = self.kernel_manager
310 self.widget.kernel_manager = self.kernel_manager
309 self.window = MainWindow(self.app, self.widget, self.existing,
311 self.window = MainWindow(self.app, self.widget, self.existing,
310 may_close=local_kernel,
312 may_close=local_kernel,
311 confirm_exit=self.confirm_exit)
313 confirm_exit=self.confirm_exit)
312 self.window.setWindowTitle('Python' if self.pure else 'IPython')
314 self.window.setWindowTitle('Python' if self.pure else 'IPython')
313
315
314 def init_colors(self):
316 def init_colors(self):
315 """Configure the coloring of the widget"""
317 """Configure the coloring of the widget"""
316 # Note: This will be dramatically simplified when colors
318 # Note: This will be dramatically simplified when colors
317 # are removed from the backend.
319 # are removed from the backend.
318
320
319 if self.pure:
321 if self.pure:
320 # only IPythonWidget supports styling
322 # only IPythonWidget supports styling
321 return
323 return
322
324
323 # parse the colors arg down to current known labels
325 # parse the colors arg down to current known labels
324 try:
326 try:
325 colors = self.config.ZMQInteractiveShell.colors
327 colors = self.config.ZMQInteractiveShell.colors
326 except AttributeError:
328 except AttributeError:
327 colors = None
329 colors = None
328 try:
330 try:
329 style = self.config.IPythonWidget.colors
331 style = self.config.IPythonWidget.colors
330 except AttributeError:
332 except AttributeError:
331 style = None
333 style = None
332
334
333 # find the value for colors:
335 # find the value for colors:
334 if colors:
336 if colors:
335 colors=colors.lower()
337 colors=colors.lower()
336 if colors in ('lightbg', 'light'):
338 if colors in ('lightbg', 'light'):
337 colors='lightbg'
339 colors='lightbg'
338 elif colors in ('dark', 'linux'):
340 elif colors in ('dark', 'linux'):
339 colors='linux'
341 colors='linux'
340 else:
342 else:
341 colors='nocolor'
343 colors='nocolor'
342 elif style:
344 elif style:
343 if style=='bw':
345 if style=='bw':
344 colors='nocolor'
346 colors='nocolor'
345 elif styles.dark_style(style):
347 elif styles.dark_style(style):
346 colors='linux'
348 colors='linux'
347 else:
349 else:
348 colors='lightbg'
350 colors='lightbg'
349 else:
351 else:
350 colors=None
352 colors=None
351
353
352 # Configure the style.
354 # Configure the style.
353 widget = self.widget
355 widget = self.widget
354 if style:
356 if style:
355 widget.style_sheet = styles.sheet_from_template(style, colors)
357 widget.style_sheet = styles.sheet_from_template(style, colors)
356 widget.syntax_style = style
358 widget.syntax_style = style
357 widget._syntax_style_changed()
359 widget._syntax_style_changed()
358 widget._style_sheet_changed()
360 widget._style_sheet_changed()
359 elif colors:
361 elif colors:
360 # use a default style
362 # use a default style
361 widget.set_default_style(colors=colors)
363 widget.set_default_style(colors=colors)
362 else:
364 else:
363 # this is redundant for now, but allows the widget's
365 # this is redundant for now, but allows the widget's
364 # defaults to change
366 # defaults to change
365 widget.set_default_style()
367 widget.set_default_style()
366
368
367 if self.stylesheet:
369 if self.stylesheet:
368 # we got an expicit stylesheet
370 # we got an expicit stylesheet
369 if os.path.isfile(self.stylesheet):
371 if os.path.isfile(self.stylesheet):
370 with open(self.stylesheet) as f:
372 with open(self.stylesheet) as f:
371 sheet = f.read()
373 sheet = f.read()
372 widget.style_sheet = sheet
374 widget.style_sheet = sheet
373 widget._style_sheet_changed()
375 widget._style_sheet_changed()
374 else:
376 else:
375 raise IOError("Stylesheet %r not found."%self.stylesheet)
377 raise IOError("Stylesheet %r not found."%self.stylesheet)
376
378
377 def initialize(self, argv=None):
379 def initialize(self, argv=None):
378 super(IPythonQtConsoleApp, self).initialize(argv)
380 super(IPythonQtConsoleApp, self).initialize(argv)
379 self.init_kernel_manager()
381 self.init_kernel_manager()
380 self.init_qt_elements()
382 self.init_qt_elements()
381 self.init_colors()
383 self.init_colors()
382
384
383 def start(self):
385 def start(self):
384
386
385 # draw the window
387 # draw the window
386 self.window.show()
388 self.window.show()
387
389
388 # Start the application main loop.
390 # Start the application main loop.
389 self.app.exec_()
391 self.app.exec_()
390
392
391 #-----------------------------------------------------------------------------
393 #-----------------------------------------------------------------------------
392 # Main entry point
394 # Main entry point
393 #-----------------------------------------------------------------------------
395 #-----------------------------------------------------------------------------
394
396
395 def main():
397 def main():
396 app = IPythonQtConsoleApp()
398 app = IPythonQtConsoleApp()
397 app.initialize()
399 app.initialize()
398 app.start()
400 app.start()
399
401
400
402
401 if __name__ == '__main__':
403 if __name__ == '__main__':
402 main()
404 main()
@@ -1,673 +1,673 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 """A simple interactive kernel that talks to a frontend over 0MQ.
2 """A simple interactive kernel that talks to a frontend over 0MQ.
3
3
4 Things to do:
4 Things to do:
5
5
6 * Implement `set_parent` logic. Right before doing exec, the Kernel should
6 * Implement `set_parent` logic. Right before doing exec, the Kernel should
7 call set_parent on all the PUB objects with the message about to be executed.
7 call set_parent on all the PUB objects with the message about to be executed.
8 * Implement random port and security key logic.
8 * Implement random port and security key logic.
9 * Implement control messages.
9 * Implement control messages.
10 * Implement event loop and poll version.
10 * Implement event loop and poll version.
11 """
11 """
12
12
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14 # Imports
14 # Imports
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 from __future__ import print_function
16 from __future__ import print_function
17
17
18 # Standard library imports.
18 # Standard library imports.
19 import __builtin__
19 import __builtin__
20 import atexit
20 import atexit
21 import sys
21 import sys
22 import time
22 import time
23 import traceback
23 import traceback
24 import logging
24 import logging
25 # System library imports.
25 # System library imports.
26 import zmq
26 import zmq
27
27
28 # Local imports.
28 # Local imports.
29 from IPython.config.configurable import Configurable
29 from IPython.config.configurable import Configurable
30 from IPython.config.application import boolean_flag
30 from IPython.config.application import boolean_flag
31 from IPython.core.newapplication import ProfileDir
31 from IPython.core.newapplication import ProfileDir
32 from IPython.core.shellapp import (
32 from IPython.core.shellapp import (
33 InteractiveShellApp, shell_flags, shell_aliases
33 InteractiveShellApp, shell_flags, shell_aliases
34 )
34 )
35 from IPython.utils import io
35 from IPython.utils import io
36 from IPython.utils.jsonutil import json_clean
36 from IPython.utils.jsonutil import json_clean
37 from IPython.lib import pylabtools
37 from IPython.lib import pylabtools
38 from IPython.utils.traitlets import (
38 from IPython.utils.traitlets import (
39 List, Instance, Float, Dict, Bool, Int, Unicode, CaselessStrEnum
39 List, Instance, Float, Dict, Bool, Int, Unicode, CaselessStrEnum
40 )
40 )
41
41
42 from entry_point import base_launch_kernel
42 from entry_point import base_launch_kernel
43 from kernelapp import KernelApp, kernel_flags, kernel_aliases
43 from kernelapp import KernelApp, kernel_flags, kernel_aliases
44 from iostream import OutStream
44 from iostream import OutStream
45 from session import Session, Message
45 from session import Session, Message
46 from zmqshell import ZMQInteractiveShell
46 from zmqshell import ZMQInteractiveShell
47
47
48
48
49
49
50 #-----------------------------------------------------------------------------
50 #-----------------------------------------------------------------------------
51 # Main kernel class
51 # Main kernel class
52 #-----------------------------------------------------------------------------
52 #-----------------------------------------------------------------------------
53
53
54 class Kernel(Configurable):
54 class Kernel(Configurable):
55
55
56 #---------------------------------------------------------------------------
56 #---------------------------------------------------------------------------
57 # Kernel interface
57 # Kernel interface
58 #---------------------------------------------------------------------------
58 #---------------------------------------------------------------------------
59
59
60 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
60 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
61 session = Instance(Session)
61 session = Instance(Session)
62 shell_socket = Instance('zmq.Socket')
62 shell_socket = Instance('zmq.Socket')
63 iopub_socket = Instance('zmq.Socket')
63 iopub_socket = Instance('zmq.Socket')
64 stdin_socket = Instance('zmq.Socket')
64 stdin_socket = Instance('zmq.Socket')
65 log = Instance(logging.Logger)
65 log = Instance(logging.Logger)
66
66
67 # Private interface
67 # Private interface
68
68
69 # Time to sleep after flushing the stdout/err buffers in each execute
69 # Time to sleep after flushing the stdout/err buffers in each execute
70 # cycle. While this introduces a hard limit on the minimal latency of the
70 # cycle. While this introduces a hard limit on the minimal latency of the
71 # execute cycle, it helps prevent output synchronization problems for
71 # execute cycle, it helps prevent output synchronization problems for
72 # clients.
72 # clients.
73 # Units are in seconds. The minimum zmq latency on local host is probably
73 # Units are in seconds. The minimum zmq latency on local host is probably
74 # ~150 microseconds, set this to 500us for now. We may need to increase it
74 # ~150 microseconds, set this to 500us for now. We may need to increase it
75 # a little if it's not enough after more interactive testing.
75 # a little if it's not enough after more interactive testing.
76 _execute_sleep = Float(0.0005, config=True)
76 _execute_sleep = Float(0.0005, config=True)
77
77
78 # Frequency of the kernel's event loop.
78 # Frequency of the kernel's event loop.
79 # Units are in seconds, kernel subclasses for GUI toolkits may need to
79 # Units are in seconds, kernel subclasses for GUI toolkits may need to
80 # adapt to milliseconds.
80 # adapt to milliseconds.
81 _poll_interval = Float(0.05, config=True)
81 _poll_interval = Float(0.05, config=True)
82
82
83 # If the shutdown was requested over the network, we leave here the
83 # If the shutdown was requested over the network, we leave here the
84 # necessary reply message so it can be sent by our registered atexit
84 # necessary reply message so it can be sent by our registered atexit
85 # handler. This ensures that the reply is only sent to clients truly at
85 # handler. This ensures that the reply is only sent to clients truly at
86 # the end of our shutdown process (which happens after the underlying
86 # the end of our shutdown process (which happens after the underlying
87 # IPython shell's own shutdown).
87 # IPython shell's own shutdown).
88 _shutdown_message = None
88 _shutdown_message = None
89
89
90 # This is a dict of port number that the kernel is listening on. It is set
90 # This is a dict of port number that the kernel is listening on. It is set
91 # by record_ports and used by connect_request.
91 # by record_ports and used by connect_request.
92 _recorded_ports = Dict()
92 _recorded_ports = Dict()
93
93
94
94
95
95
96 def __init__(self, **kwargs):
96 def __init__(self, **kwargs):
97 super(Kernel, self).__init__(**kwargs)
97 super(Kernel, self).__init__(**kwargs)
98
98
99 # Before we even start up the shell, register *first* our exit handlers
99 # Before we even start up the shell, register *first* our exit handlers
100 # so they come before the shell's
100 # so they come before the shell's
101 atexit.register(self._at_shutdown)
101 atexit.register(self._at_shutdown)
102
102
103 # Initialize the InteractiveShell subclass
103 # Initialize the InteractiveShell subclass
104 self.shell = ZMQInteractiveShell.instance(config=self.config)
104 self.shell = ZMQInteractiveShell.instance(config=self.config)
105 self.shell.displayhook.session = self.session
105 self.shell.displayhook.session = self.session
106 self.shell.displayhook.pub_socket = self.iopub_socket
106 self.shell.displayhook.pub_socket = self.iopub_socket
107 self.shell.display_pub.session = self.session
107 self.shell.display_pub.session = self.session
108 self.shell.display_pub.pub_socket = self.iopub_socket
108 self.shell.display_pub.pub_socket = self.iopub_socket
109
109
110 # TMP - hack while developing
110 # TMP - hack while developing
111 self.shell._reply_content = None
111 self.shell._reply_content = None
112
112
113 # Build dict of handlers for message types
113 # Build dict of handlers for message types
114 msg_types = [ 'execute_request', 'complete_request',
114 msg_types = [ 'execute_request', 'complete_request',
115 'object_info_request', 'history_request',
115 'object_info_request', 'history_request',
116 'connect_request', 'shutdown_request']
116 'connect_request', 'shutdown_request']
117 self.handlers = {}
117 self.handlers = {}
118 for msg_type in msg_types:
118 for msg_type in msg_types:
119 self.handlers[msg_type] = getattr(self, msg_type)
119 self.handlers[msg_type] = getattr(self, msg_type)
120
120
121 def do_one_iteration(self):
121 def do_one_iteration(self):
122 """Do one iteration of the kernel's evaluation loop.
122 """Do one iteration of the kernel's evaluation loop.
123 """
123 """
124 ident,msg = self.session.recv(self.shell_socket, zmq.NOBLOCK)
124 ident,msg = self.session.recv(self.shell_socket, zmq.NOBLOCK)
125 if msg is None:
125 if msg is None:
126 return
126 return
127
127
128 # This assert will raise in versions of zeromq 2.0.7 and lesser.
128 # This assert will raise in versions of zeromq 2.0.7 and lesser.
129 # We now require 2.0.8 or above, so we can uncomment for safety.
129 # We now require 2.0.8 or above, so we can uncomment for safety.
130 # print(ident,msg, file=sys.__stdout__)
130 # print(ident,msg, file=sys.__stdout__)
131 assert ident is not None, "Missing message part."
131 assert ident is not None, "Missing message part."
132
132
133 # Print some info about this message and leave a '--->' marker, so it's
133 # Print some info about this message and leave a '--->' marker, so it's
134 # easier to trace visually the message chain when debugging. Each
134 # easier to trace visually the message chain when debugging. Each
135 # handler prints its message at the end.
135 # handler prints its message at the end.
136 self.log.debug('\n*** MESSAGE TYPE:'+str(msg['msg_type'])+'***')
136 self.log.debug('\n*** MESSAGE TYPE:'+str(msg['msg_type'])+'***')
137 self.log.debug(' Content: '+str(msg['content'])+'\n --->\n ')
137 self.log.debug(' Content: '+str(msg['content'])+'\n --->\n ')
138
138
139 # Find and call actual handler for message
139 # Find and call actual handler for message
140 handler = self.handlers.get(msg['msg_type'], None)
140 handler = self.handlers.get(msg['msg_type'], None)
141 if handler is None:
141 if handler is None:
142 self.log.error("UNKNOWN MESSAGE TYPE:" +str(msg))
142 self.log.error("UNKNOWN MESSAGE TYPE:" +str(msg))
143 else:
143 else:
144 handler(ident, msg)
144 handler(ident, msg)
145
145
146 # Check whether we should exit, in case the incoming message set the
146 # Check whether we should exit, in case the incoming message set the
147 # exit flag on
147 # exit flag on
148 if self.shell.exit_now:
148 if self.shell.exit_now:
149 self.log.debug('\nExiting IPython kernel...')
149 self.log.debug('\nExiting IPython kernel...')
150 # We do a normal, clean exit, which allows any actions registered
150 # We do a normal, clean exit, which allows any actions registered
151 # via atexit (such as history saving) to take place.
151 # via atexit (such as history saving) to take place.
152 sys.exit(0)
152 sys.exit(0)
153
153
154
154
155 def start(self):
155 def start(self):
156 """ Start the kernel main loop.
156 """ Start the kernel main loop.
157 """
157 """
158 poller = zmq.Poller()
158 poller = zmq.Poller()
159 poller.register(self.shell_socket, zmq.POLLIN)
159 poller.register(self.shell_socket, zmq.POLLIN)
160 while True:
160 while True:
161 try:
161 try:
162 # scale by extra factor of 10, because there is no
162 # scale by extra factor of 10, because there is no
163 # reason for this to be anything less than ~ 0.1s
163 # reason for this to be anything less than ~ 0.1s
164 # since it is a real poller and will respond
164 # since it is a real poller and will respond
165 # to events immediately
165 # to events immediately
166 poller.poll(10*1000*self._poll_interval)
166 poller.poll(10*1000*self._poll_interval)
167 self.do_one_iteration()
167 self.do_one_iteration()
168 except KeyboardInterrupt:
168 except KeyboardInterrupt:
169 # Ctrl-C shouldn't crash the kernel
169 # Ctrl-C shouldn't crash the kernel
170 io.raw_print("KeyboardInterrupt caught in kernel")
170 io.raw_print("KeyboardInterrupt caught in kernel")
171
171
172 def record_ports(self, ports):
172 def record_ports(self, ports):
173 """Record the ports that this kernel is using.
173 """Record the ports that this kernel is using.
174
174
175 The creator of the Kernel instance must call this methods if they
175 The creator of the Kernel instance must call this methods if they
176 want the :meth:`connect_request` method to return the port numbers.
176 want the :meth:`connect_request` method to return the port numbers.
177 """
177 """
178 self._recorded_ports = ports
178 self._recorded_ports = ports
179
179
180 #---------------------------------------------------------------------------
180 #---------------------------------------------------------------------------
181 # Kernel request handlers
181 # Kernel request handlers
182 #---------------------------------------------------------------------------
182 #---------------------------------------------------------------------------
183
183
184 def _publish_pyin(self, code, parent):
184 def _publish_pyin(self, code, parent):
185 """Publish the code request on the pyin stream."""
185 """Publish the code request on the pyin stream."""
186
186
187 pyin_msg = self.session.send(self.iopub_socket, u'pyin',{u'code':code}, parent=parent)
187 pyin_msg = self.session.send(self.iopub_socket, u'pyin',{u'code':code}, parent=parent)
188
188
189 def execute_request(self, ident, parent):
189 def execute_request(self, ident, parent):
190
190
191 status_msg = self.session.send(self.iopub_socket,
191 status_msg = self.session.send(self.iopub_socket,
192 u'status',
192 u'status',
193 {u'execution_state':u'busy'},
193 {u'execution_state':u'busy'},
194 parent=parent
194 parent=parent
195 )
195 )
196
196
197 try:
197 try:
198 content = parent[u'content']
198 content = parent[u'content']
199 code = content[u'code']
199 code = content[u'code']
200 silent = content[u'silent']
200 silent = content[u'silent']
201 except:
201 except:
202 self.log.error("Got bad msg: ")
202 self.log.error("Got bad msg: ")
203 self.log.error(str(Message(parent)))
203 self.log.error(str(Message(parent)))
204 return
204 return
205
205
206 shell = self.shell # we'll need this a lot here
206 shell = self.shell # we'll need this a lot here
207
207
208 # Replace raw_input. Note that is not sufficient to replace
208 # Replace raw_input. Note that is not sufficient to replace
209 # raw_input in the user namespace.
209 # raw_input in the user namespace.
210 raw_input = lambda prompt='': self._raw_input(prompt, ident, parent)
210 raw_input = lambda prompt='': self._raw_input(prompt, ident, parent)
211 __builtin__.raw_input = raw_input
211 __builtin__.raw_input = raw_input
212
212
213 # Set the parent message of the display hook and out streams.
213 # Set the parent message of the display hook and out streams.
214 shell.displayhook.set_parent(parent)
214 shell.displayhook.set_parent(parent)
215 shell.display_pub.set_parent(parent)
215 shell.display_pub.set_parent(parent)
216 sys.stdout.set_parent(parent)
216 sys.stdout.set_parent(parent)
217 sys.stderr.set_parent(parent)
217 sys.stderr.set_parent(parent)
218
218
219 # Re-broadcast our input for the benefit of listening clients, and
219 # Re-broadcast our input for the benefit of listening clients, and
220 # start computing output
220 # start computing output
221 if not silent:
221 if not silent:
222 self._publish_pyin(code, parent)
222 self._publish_pyin(code, parent)
223
223
224 reply_content = {}
224 reply_content = {}
225 try:
225 try:
226 if silent:
226 if silent:
227 # run_code uses 'exec' mode, so no displayhook will fire, and it
227 # run_code uses 'exec' mode, so no displayhook will fire, and it
228 # doesn't call logging or history manipulations. Print
228 # doesn't call logging or history manipulations. Print
229 # statements in that code will obviously still execute.
229 # statements in that code will obviously still execute.
230 shell.run_code(code)
230 shell.run_code(code)
231 else:
231 else:
232 # FIXME: the shell calls the exception handler itself.
232 # FIXME: the shell calls the exception handler itself.
233 shell.run_cell(code)
233 shell.run_cell(code)
234 except:
234 except:
235 status = u'error'
235 status = u'error'
236 # FIXME: this code right now isn't being used yet by default,
236 # FIXME: this code right now isn't being used yet by default,
237 # because the run_cell() call above directly fires off exception
237 # because the run_cell() call above directly fires off exception
238 # reporting. This code, therefore, is only active in the scenario
238 # reporting. This code, therefore, is only active in the scenario
239 # where runlines itself has an unhandled exception. We need to
239 # where runlines itself has an unhandled exception. We need to
240 # uniformize this, for all exception construction to come from a
240 # uniformize this, for all exception construction to come from a
241 # single location in the codbase.
241 # single location in the codbase.
242 etype, evalue, tb = sys.exc_info()
242 etype, evalue, tb = sys.exc_info()
243 tb_list = traceback.format_exception(etype, evalue, tb)
243 tb_list = traceback.format_exception(etype, evalue, tb)
244 reply_content.update(shell._showtraceback(etype, evalue, tb_list))
244 reply_content.update(shell._showtraceback(etype, evalue, tb_list))
245 else:
245 else:
246 status = u'ok'
246 status = u'ok'
247
247
248 reply_content[u'status'] = status
248 reply_content[u'status'] = status
249
249
250 # Return the execution counter so clients can display prompts
250 # Return the execution counter so clients can display prompts
251 reply_content['execution_count'] = shell.execution_count -1
251 reply_content['execution_count'] = shell.execution_count -1
252
252
253 # FIXME - fish exception info out of shell, possibly left there by
253 # FIXME - fish exception info out of shell, possibly left there by
254 # runlines. We'll need to clean up this logic later.
254 # runlines. We'll need to clean up this logic later.
255 if shell._reply_content is not None:
255 if shell._reply_content is not None:
256 reply_content.update(shell._reply_content)
256 reply_content.update(shell._reply_content)
257 # reset after use
257 # reset after use
258 shell._reply_content = None
258 shell._reply_content = None
259
259
260 # At this point, we can tell whether the main code execution succeeded
260 # At this point, we can tell whether the main code execution succeeded
261 # or not. If it did, we proceed to evaluate user_variables/expressions
261 # or not. If it did, we proceed to evaluate user_variables/expressions
262 if reply_content['status'] == 'ok':
262 if reply_content['status'] == 'ok':
263 reply_content[u'user_variables'] = \
263 reply_content[u'user_variables'] = \
264 shell.user_variables(content[u'user_variables'])
264 shell.user_variables(content[u'user_variables'])
265 reply_content[u'user_expressions'] = \
265 reply_content[u'user_expressions'] = \
266 shell.user_expressions(content[u'user_expressions'])
266 shell.user_expressions(content[u'user_expressions'])
267 else:
267 else:
268 # If there was an error, don't even try to compute variables or
268 # If there was an error, don't even try to compute variables or
269 # expressions
269 # expressions
270 reply_content[u'user_variables'] = {}
270 reply_content[u'user_variables'] = {}
271 reply_content[u'user_expressions'] = {}
271 reply_content[u'user_expressions'] = {}
272
272
273 # Payloads should be retrieved regardless of outcome, so we can both
273 # Payloads should be retrieved regardless of outcome, so we can both
274 # recover partial output (that could have been generated early in a
274 # recover partial output (that could have been generated early in a
275 # block, before an error) and clear the payload system always.
275 # block, before an error) and clear the payload system always.
276 reply_content[u'payload'] = shell.payload_manager.read_payload()
276 reply_content[u'payload'] = shell.payload_manager.read_payload()
277 # Be agressive about clearing the payload because we don't want
277 # Be agressive about clearing the payload because we don't want
278 # it to sit in memory until the next execute_request comes in.
278 # it to sit in memory until the next execute_request comes in.
279 shell.payload_manager.clear_payload()
279 shell.payload_manager.clear_payload()
280
280
281 # Flush output before sending the reply.
281 # Flush output before sending the reply.
282 sys.stdout.flush()
282 sys.stdout.flush()
283 sys.stderr.flush()
283 sys.stderr.flush()
284 # FIXME: on rare occasions, the flush doesn't seem to make it to the
284 # FIXME: on rare occasions, the flush doesn't seem to make it to the
285 # clients... This seems to mitigate the problem, but we definitely need
285 # clients... This seems to mitigate the problem, but we definitely need
286 # to better understand what's going on.
286 # to better understand what's going on.
287 if self._execute_sleep:
287 if self._execute_sleep:
288 time.sleep(self._execute_sleep)
288 time.sleep(self._execute_sleep)
289
289
290 # Send the reply.
290 # Send the reply.
291 reply_msg = self.session.send(self.shell_socket, u'execute_reply',
291 reply_msg = self.session.send(self.shell_socket, u'execute_reply',
292 reply_content, parent, ident=ident)
292 reply_content, parent, ident=ident)
293 self.log.debug(str(reply_msg))
293 self.log.debug(str(reply_msg))
294
294
295 if reply_msg['content']['status'] == u'error':
295 if reply_msg['content']['status'] == u'error':
296 self._abort_queue()
296 self._abort_queue()
297
297
298 status_msg = self.session.send(self.iopub_socket,
298 status_msg = self.session.send(self.iopub_socket,
299 u'status',
299 u'status',
300 {u'execution_state':u'idle'},
300 {u'execution_state':u'idle'},
301 parent=parent
301 parent=parent
302 )
302 )
303
303
304 def complete_request(self, ident, parent):
304 def complete_request(self, ident, parent):
305 txt, matches = self._complete(parent)
305 txt, matches = self._complete(parent)
306 matches = {'matches' : matches,
306 matches = {'matches' : matches,
307 'matched_text' : txt,
307 'matched_text' : txt,
308 'status' : 'ok'}
308 'status' : 'ok'}
309 completion_msg = self.session.send(self.shell_socket, 'complete_reply',
309 completion_msg = self.session.send(self.shell_socket, 'complete_reply',
310 matches, parent, ident)
310 matches, parent, ident)
311 self.log.debug(str(completion_msg))
311 self.log.debug(str(completion_msg))
312
312
313 def object_info_request(self, ident, parent):
313 def object_info_request(self, ident, parent):
314 object_info = self.shell.object_inspect(parent['content']['oname'])
314 object_info = self.shell.object_inspect(parent['content']['oname'])
315 # Before we send this object over, we scrub it for JSON usage
315 # Before we send this object over, we scrub it for JSON usage
316 oinfo = json_clean(object_info)
316 oinfo = json_clean(object_info)
317 msg = self.session.send(self.shell_socket, 'object_info_reply',
317 msg = self.session.send(self.shell_socket, 'object_info_reply',
318 oinfo, parent, ident)
318 oinfo, parent, ident)
319 self.log.debug(msg)
319 self.log.debug(msg)
320
320
321 def history_request(self, ident, parent):
321 def history_request(self, ident, parent):
322 # We need to pull these out, as passing **kwargs doesn't work with
322 # We need to pull these out, as passing **kwargs doesn't work with
323 # unicode keys before Python 2.6.5.
323 # unicode keys before Python 2.6.5.
324 hist_access_type = parent['content']['hist_access_type']
324 hist_access_type = parent['content']['hist_access_type']
325 raw = parent['content']['raw']
325 raw = parent['content']['raw']
326 output = parent['content']['output']
326 output = parent['content']['output']
327 if hist_access_type == 'tail':
327 if hist_access_type == 'tail':
328 n = parent['content']['n']
328 n = parent['content']['n']
329 hist = self.shell.history_manager.get_tail(n, raw=raw, output=output,
329 hist = self.shell.history_manager.get_tail(n, raw=raw, output=output,
330 include_latest=True)
330 include_latest=True)
331
331
332 elif hist_access_type == 'range':
332 elif hist_access_type == 'range':
333 session = parent['content']['session']
333 session = parent['content']['session']
334 start = parent['content']['start']
334 start = parent['content']['start']
335 stop = parent['content']['stop']
335 stop = parent['content']['stop']
336 hist = self.shell.history_manager.get_range(session, start, stop,
336 hist = self.shell.history_manager.get_range(session, start, stop,
337 raw=raw, output=output)
337 raw=raw, output=output)
338
338
339 elif hist_access_type == 'search':
339 elif hist_access_type == 'search':
340 pattern = parent['content']['pattern']
340 pattern = parent['content']['pattern']
341 hist = self.shell.history_manager.search(pattern, raw=raw, output=output)
341 hist = self.shell.history_manager.search(pattern, raw=raw, output=output)
342
342
343 else:
343 else:
344 hist = []
344 hist = []
345 content = {'history' : list(hist)}
345 content = {'history' : list(hist)}
346 msg = self.session.send(self.shell_socket, 'history_reply',
346 msg = self.session.send(self.shell_socket, 'history_reply',
347 content, parent, ident)
347 content, parent, ident)
348 self.log.debug(str(msg))
348 self.log.debug(str(msg))
349
349
350 def connect_request(self, ident, parent):
350 def connect_request(self, ident, parent):
351 if self._recorded_ports is not None:
351 if self._recorded_ports is not None:
352 content = self._recorded_ports.copy()
352 content = self._recorded_ports.copy()
353 else:
353 else:
354 content = {}
354 content = {}
355 msg = self.session.send(self.shell_socket, 'connect_reply',
355 msg = self.session.send(self.shell_socket, 'connect_reply',
356 content, parent, ident)
356 content, parent, ident)
357 self.log.debug(msg)
357 self.log.debug(msg)
358
358
359 def shutdown_request(self, ident, parent):
359 def shutdown_request(self, ident, parent):
360 self.shell.exit_now = True
360 self.shell.exit_now = True
361 self._shutdown_message = self.session.msg(u'shutdown_reply', parent['content'], parent)
361 self._shutdown_message = self.session.msg(u'shutdown_reply', parent['content'], parent)
362 sys.exit(0)
362 sys.exit(0)
363
363
364 #---------------------------------------------------------------------------
364 #---------------------------------------------------------------------------
365 # Protected interface
365 # Protected interface
366 #---------------------------------------------------------------------------
366 #---------------------------------------------------------------------------
367
367
368 def _abort_queue(self):
368 def _abort_queue(self):
369 while True:
369 while True:
370 ident,msg = self.session.recv(self.shell_socket, zmq.NOBLOCK)
370 ident,msg = self.session.recv(self.shell_socket, zmq.NOBLOCK)
371 if msg is None:
371 if msg is None:
372 break
372 break
373 else:
373 else:
374 assert ident is not None, \
374 assert ident is not None, \
375 "Unexpected missing message part."
375 "Unexpected missing message part."
376
376
377 self.log.debug("Aborting:\n"+str(Message(msg)))
377 self.log.debug("Aborting:\n"+str(Message(msg)))
378 msg_type = msg['msg_type']
378 msg_type = msg['msg_type']
379 reply_type = msg_type.split('_')[0] + '_reply'
379 reply_type = msg_type.split('_')[0] + '_reply'
380 reply_msg = self.session.send(self.shell_socket, reply_type,
380 reply_msg = self.session.send(self.shell_socket, reply_type,
381 {'status' : 'aborted'}, msg, ident=ident)
381 {'status' : 'aborted'}, msg, ident=ident)
382 self.log.debug(reply_msg)
382 self.log.debug(reply_msg)
383 # We need to wait a bit for requests to come in. This can probably
383 # We need to wait a bit for requests to come in. This can probably
384 # be set shorter for true asynchronous clients.
384 # be set shorter for true asynchronous clients.
385 time.sleep(0.1)
385 time.sleep(0.1)
386
386
387 def _raw_input(self, prompt, ident, parent):
387 def _raw_input(self, prompt, ident, parent):
388 # Flush output before making the request.
388 # Flush output before making the request.
389 sys.stderr.flush()
389 sys.stderr.flush()
390 sys.stdout.flush()
390 sys.stdout.flush()
391
391
392 # Send the input request.
392 # Send the input request.
393 content = dict(prompt=prompt)
393 content = dict(prompt=prompt)
394 msg = self.session.send(self.stdin_socket, u'input_request', content, parent)
394 msg = self.session.send(self.stdin_socket, u'input_request', content, parent)
395
395
396 # Await a response.
396 # Await a response.
397 ident, reply = self.session.recv(self.stdin_socket, 0)
397 ident, reply = self.session.recv(self.stdin_socket, 0)
398 try:
398 try:
399 value = reply['content']['value']
399 value = reply['content']['value']
400 except:
400 except:
401 self.log.error("Got bad raw_input reply: ")
401 self.log.error("Got bad raw_input reply: ")
402 self.log.error(str(Message(parent)))
402 self.log.error(str(Message(parent)))
403 value = ''
403 value = ''
404 return value
404 return value
405
405
406 def _complete(self, msg):
406 def _complete(self, msg):
407 c = msg['content']
407 c = msg['content']
408 try:
408 try:
409 cpos = int(c['cursor_pos'])
409 cpos = int(c['cursor_pos'])
410 except:
410 except:
411 # If we don't get something that we can convert to an integer, at
411 # If we don't get something that we can convert to an integer, at
412 # least attempt the completion guessing the cursor is at the end of
412 # least attempt the completion guessing the cursor is at the end of
413 # the text, if there's any, and otherwise of the line
413 # the text, if there's any, and otherwise of the line
414 cpos = len(c['text'])
414 cpos = len(c['text'])
415 if cpos==0:
415 if cpos==0:
416 cpos = len(c['line'])
416 cpos = len(c['line'])
417 return self.shell.complete(c['text'], c['line'], cpos)
417 return self.shell.complete(c['text'], c['line'], cpos)
418
418
419 def _object_info(self, context):
419 def _object_info(self, context):
420 symbol, leftover = self._symbol_from_context(context)
420 symbol, leftover = self._symbol_from_context(context)
421 if symbol is not None and not leftover:
421 if symbol is not None and not leftover:
422 doc = getattr(symbol, '__doc__', '')
422 doc = getattr(symbol, '__doc__', '')
423 else:
423 else:
424 doc = ''
424 doc = ''
425 object_info = dict(docstring = doc)
425 object_info = dict(docstring = doc)
426 return object_info
426 return object_info
427
427
428 def _symbol_from_context(self, context):
428 def _symbol_from_context(self, context):
429 if not context:
429 if not context:
430 return None, context
430 return None, context
431
431
432 base_symbol_string = context[0]
432 base_symbol_string = context[0]
433 symbol = self.shell.user_ns.get(base_symbol_string, None)
433 symbol = self.shell.user_ns.get(base_symbol_string, None)
434 if symbol is None:
434 if symbol is None:
435 symbol = __builtin__.__dict__.get(base_symbol_string, None)
435 symbol = __builtin__.__dict__.get(base_symbol_string, None)
436 if symbol is None:
436 if symbol is None:
437 return None, context
437 return None, context
438
438
439 context = context[1:]
439 context = context[1:]
440 for i, name in enumerate(context):
440 for i, name in enumerate(context):
441 new_symbol = getattr(symbol, name, None)
441 new_symbol = getattr(symbol, name, None)
442 if new_symbol is None:
442 if new_symbol is None:
443 return symbol, context[i:]
443 return symbol, context[i:]
444 else:
444 else:
445 symbol = new_symbol
445 symbol = new_symbol
446
446
447 return symbol, []
447 return symbol, []
448
448
449 def _at_shutdown(self):
449 def _at_shutdown(self):
450 """Actions taken at shutdown by the kernel, called by python's atexit.
450 """Actions taken at shutdown by the kernel, called by python's atexit.
451 """
451 """
452 # io.rprint("Kernel at_shutdown") # dbg
452 # io.rprint("Kernel at_shutdown") # dbg
453 if self._shutdown_message is not None:
453 if self._shutdown_message is not None:
454 self.session.send(self.shell_socket, self._shutdown_message)
454 self.session.send(self.shell_socket, self._shutdown_message)
455 self.session.send(self.iopub_socket, self._shutdown_message)
455 self.session.send(self.iopub_socket, self._shutdown_message)
456 self.log.debug(str(self._shutdown_message))
456 self.log.debug(str(self._shutdown_message))
457 # A very short sleep to give zmq time to flush its message buffers
457 # A very short sleep to give zmq time to flush its message buffers
458 # before Python truly shuts down.
458 # before Python truly shuts down.
459 time.sleep(0.01)
459 time.sleep(0.01)
460
460
461
461
462 class QtKernel(Kernel):
462 class QtKernel(Kernel):
463 """A Kernel subclass with Qt support."""
463 """A Kernel subclass with Qt support."""
464
464
465 def start(self):
465 def start(self):
466 """Start a kernel with QtPy4 event loop integration."""
466 """Start a kernel with QtPy4 event loop integration."""
467
467
468 from PyQt4 import QtCore
468 from PyQt4 import QtCore
469 from IPython.lib.guisupport import get_app_qt4, start_event_loop_qt4
469 from IPython.lib.guisupport import get_app_qt4, start_event_loop_qt4
470
470
471 self.app = get_app_qt4([" "])
471 self.app = get_app_qt4([" "])
472 self.app.setQuitOnLastWindowClosed(False)
472 self.app.setQuitOnLastWindowClosed(False)
473 self.timer = QtCore.QTimer()
473 self.timer = QtCore.QTimer()
474 self.timer.timeout.connect(self.do_one_iteration)
474 self.timer.timeout.connect(self.do_one_iteration)
475 # Units for the timer are in milliseconds
475 # Units for the timer are in milliseconds
476 self.timer.start(1000*self._poll_interval)
476 self.timer.start(1000*self._poll_interval)
477 start_event_loop_qt4(self.app)
477 start_event_loop_qt4(self.app)
478
478
479
479
480 class WxKernel(Kernel):
480 class WxKernel(Kernel):
481 """A Kernel subclass with Wx support."""
481 """A Kernel subclass with Wx support."""
482
482
483 def start(self):
483 def start(self):
484 """Start a kernel with wx event loop support."""
484 """Start a kernel with wx event loop support."""
485
485
486 import wx
486 import wx
487 from IPython.lib.guisupport import start_event_loop_wx
487 from IPython.lib.guisupport import start_event_loop_wx
488
488
489 doi = self.do_one_iteration
489 doi = self.do_one_iteration
490 # Wx uses milliseconds
490 # Wx uses milliseconds
491 poll_interval = int(1000*self._poll_interval)
491 poll_interval = int(1000*self._poll_interval)
492
492
493 # We have to put the wx.Timer in a wx.Frame for it to fire properly.
493 # We have to put the wx.Timer in a wx.Frame for it to fire properly.
494 # We make the Frame hidden when we create it in the main app below.
494 # We make the Frame hidden when we create it in the main app below.
495 class TimerFrame(wx.Frame):
495 class TimerFrame(wx.Frame):
496 def __init__(self, func):
496 def __init__(self, func):
497 wx.Frame.__init__(self, None, -1)
497 wx.Frame.__init__(self, None, -1)
498 self.timer = wx.Timer(self)
498 self.timer = wx.Timer(self)
499 # Units for the timer are in milliseconds
499 # Units for the timer are in milliseconds
500 self.timer.Start(poll_interval)
500 self.timer.Start(poll_interval)
501 self.Bind(wx.EVT_TIMER, self.on_timer)
501 self.Bind(wx.EVT_TIMER, self.on_timer)
502 self.func = func
502 self.func = func
503
503
504 def on_timer(self, event):
504 def on_timer(self, event):
505 self.func()
505 self.func()
506
506
507 # We need a custom wx.App to create our Frame subclass that has the
507 # We need a custom wx.App to create our Frame subclass that has the
508 # wx.Timer to drive the ZMQ event loop.
508 # wx.Timer to drive the ZMQ event loop.
509 class IPWxApp(wx.App):
509 class IPWxApp(wx.App):
510 def OnInit(self):
510 def OnInit(self):
511 self.frame = TimerFrame(doi)
511 self.frame = TimerFrame(doi)
512 self.frame.Show(False)
512 self.frame.Show(False)
513 return True
513 return True
514
514
515 # The redirect=False here makes sure that wx doesn't replace
515 # The redirect=False here makes sure that wx doesn't replace
516 # sys.stdout/stderr with its own classes.
516 # sys.stdout/stderr with its own classes.
517 self.app = IPWxApp(redirect=False)
517 self.app = IPWxApp(redirect=False)
518 start_event_loop_wx(self.app)
518 start_event_loop_wx(self.app)
519
519
520
520
521 class TkKernel(Kernel):
521 class TkKernel(Kernel):
522 """A Kernel subclass with Tk support."""
522 """A Kernel subclass with Tk support."""
523
523
524 def start(self):
524 def start(self):
525 """Start a Tk enabled event loop."""
525 """Start a Tk enabled event loop."""
526
526
527 import Tkinter
527 import Tkinter
528 doi = self.do_one_iteration
528 doi = self.do_one_iteration
529 # Tk uses milliseconds
529 # Tk uses milliseconds
530 poll_interval = int(1000*self._poll_interval)
530 poll_interval = int(1000*self._poll_interval)
531 # For Tkinter, we create a Tk object and call its withdraw method.
531 # For Tkinter, we create a Tk object and call its withdraw method.
532 class Timer(object):
532 class Timer(object):
533 def __init__(self, func):
533 def __init__(self, func):
534 self.app = Tkinter.Tk()
534 self.app = Tkinter.Tk()
535 self.app.withdraw()
535 self.app.withdraw()
536 self.func = func
536 self.func = func
537
537
538 def on_timer(self):
538 def on_timer(self):
539 self.func()
539 self.func()
540 self.app.after(poll_interval, self.on_timer)
540 self.app.after(poll_interval, self.on_timer)
541
541
542 def start(self):
542 def start(self):
543 self.on_timer() # Call it once to get things going.
543 self.on_timer() # Call it once to get things going.
544 self.app.mainloop()
544 self.app.mainloop()
545
545
546 self.timer = Timer(doi)
546 self.timer = Timer(doi)
547 self.timer.start()
547 self.timer.start()
548
548
549
549
550 class GTKKernel(Kernel):
550 class GTKKernel(Kernel):
551 """A Kernel subclass with GTK support."""
551 """A Kernel subclass with GTK support."""
552
552
553 def start(self):
553 def start(self):
554 """Start the kernel, coordinating with the GTK event loop"""
554 """Start the kernel, coordinating with the GTK event loop"""
555 from .gui.gtkembed import GTKEmbed
555 from .gui.gtkembed import GTKEmbed
556
556
557 gtk_kernel = GTKEmbed(self)
557 gtk_kernel = GTKEmbed(self)
558 gtk_kernel.start()
558 gtk_kernel.start()
559
559
560
560
561 #-----------------------------------------------------------------------------
561 #-----------------------------------------------------------------------------
562 # Aliases and Flags for the IPKernelApp
562 # Aliases and Flags for the IPKernelApp
563 #-----------------------------------------------------------------------------
563 #-----------------------------------------------------------------------------
564
564
565 flags = dict(kernel_flags)
565 flags = dict(kernel_flags)
566 flags.update(shell_flags)
566 flags.update(shell_flags)
567
567
568 addflag = lambda *args: flags.update(boolean_flag(*args))
568 addflag = lambda *args: flags.update(boolean_flag(*args))
569
569
570 flags['pylab'] = (
570 flags['pylab'] = (
571 {'IPKernelApp' : {'pylab' : 'auto'}},
571 {'IPKernelApp' : {'pylab' : 'auto'}},
572 """Pre-load matplotlib and numpy for interactive use with
572 """Pre-load matplotlib and numpy for interactive use with
573 the default matplotlib backend."""
573 the default matplotlib backend."""
574 )
574 )
575
575
576 aliases = dict(kernel_aliases)
576 aliases = dict(kernel_aliases)
577 aliases.update(shell_aliases)
577 aliases.update(shell_aliases)
578
578
579 # it's possible we don't want short aliases for *all* of these:
579 # it's possible we don't want short aliases for *all* of these:
580 aliases.update(dict(
580 aliases.update(dict(
581 pylab='IPKernelApp.pylab',
581 pylab='IPKernelApp.pylab',
582 ))
582 ))
583
583
584 #-----------------------------------------------------------------------------
584 #-----------------------------------------------------------------------------
585 # The IPKernelApp class
585 # The IPKernelApp class
586 #-----------------------------------------------------------------------------
586 #-----------------------------------------------------------------------------
587
587
588 class IPKernelApp(KernelApp, InteractiveShellApp):
588 class IPKernelApp(KernelApp, InteractiveShellApp):
589 name = 'ipkernel'
589 name = 'ipkernel'
590
590
591 aliases = Dict(aliases)
591 aliases = Dict(aliases)
592 flags = Dict(flags)
592 flags = Dict(flags)
593 classes = [Kernel, ZMQInteractiveShell, ProfileDir]
593 classes = [Kernel, ZMQInteractiveShell, ProfileDir, Session]
594 # configurables
594 # configurables
595 pylab = CaselessStrEnum(['tk', 'qt', 'wx', 'gtk', 'osx', 'inline', 'auto'],
595 pylab = CaselessStrEnum(['tk', 'qt', 'wx', 'gtk', 'osx', 'inline', 'auto'],
596 config=True,
596 config=True,
597 help="""Pre-load matplotlib and numpy for interactive use,
597 help="""Pre-load matplotlib and numpy for interactive use,
598 selecting a particular matplotlib backend and loop integration.
598 selecting a particular matplotlib backend and loop integration.
599 """
599 """
600 )
600 )
601 def initialize(self, argv=None):
601 def initialize(self, argv=None):
602 super(IPKernelApp, self).initialize(argv)
602 super(IPKernelApp, self).initialize(argv)
603 self.init_shell()
603 self.init_shell()
604 self.init_extensions()
604 self.init_extensions()
605 self.init_code()
605 self.init_code()
606
606
607 def init_kernel(self):
607 def init_kernel(self):
608 kernel_factory = Kernel
608 kernel_factory = Kernel
609
609
610 kernel_map = {
610 kernel_map = {
611 'qt' : QtKernel,
611 'qt' : QtKernel,
612 'qt4': QtKernel,
612 'qt4': QtKernel,
613 'inline': Kernel,
613 'inline': Kernel,
614 'osx': TkKernel,
614 'osx': TkKernel,
615 'wx' : WxKernel,
615 'wx' : WxKernel,
616 'tk' : TkKernel,
616 'tk' : TkKernel,
617 'gtk': GTKKernel,
617 'gtk': GTKKernel,
618 }
618 }
619
619
620 if self.pylab:
620 if self.pylab:
621 key = None if self.pylab == 'auto' else self.pylab
621 key = None if self.pylab == 'auto' else self.pylab
622 gui, backend = pylabtools.find_gui_and_backend(key)
622 gui, backend = pylabtools.find_gui_and_backend(key)
623 kernel_factory = kernel_map.get(gui)
623 kernel_factory = kernel_map.get(gui)
624 if kernel_factory is None:
624 if kernel_factory is None:
625 raise ValueError('GUI is not supported: %r' % gui)
625 raise ValueError('GUI is not supported: %r' % gui)
626 pylabtools.activate_matplotlib(backend)
626 pylabtools.activate_matplotlib(backend)
627
627
628 kernel = kernel_factory(config=self.config, session=self.session,
628 kernel = kernel_factory(config=self.config, session=self.session,
629 shell_socket=self.shell_socket,
629 shell_socket=self.shell_socket,
630 iopub_socket=self.iopub_socket,
630 iopub_socket=self.iopub_socket,
631 stdin_socket=self.stdin_socket,
631 stdin_socket=self.stdin_socket,
632 log=self.log
632 log=self.log
633 )
633 )
634 self.kernel = kernel
634 self.kernel = kernel
635 kernel.record_ports(self.ports)
635 kernel.record_ports(self.ports)
636
636
637 if self.pylab:
637 if self.pylab:
638 pylabtools.import_pylab(kernel.shell.user_ns, backend,
638 pylabtools.import_pylab(kernel.shell.user_ns, backend,
639 shell=kernel.shell)
639 shell=kernel.shell)
640
640
641 def init_shell(self):
641 def init_shell(self):
642 self.shell = self.kernel.shell
642 self.shell = self.kernel.shell
643
643
644
644
645 #-----------------------------------------------------------------------------
645 #-----------------------------------------------------------------------------
646 # Kernel main and launch functions
646 # Kernel main and launch functions
647 #-----------------------------------------------------------------------------
647 #-----------------------------------------------------------------------------
648
648
649 def launch_kernel(*args, **kwargs):
649 def launch_kernel(*args, **kwargs):
650 """Launches a localhost IPython kernel, binding to the specified ports.
650 """Launches a localhost IPython kernel, binding to the specified ports.
651
651
652 This function simply calls entry_point.base_launch_kernel with the right first
652 This function simply calls entry_point.base_launch_kernel with the right first
653 command to start an ipkernel. See base_launch_kernel for arguments.
653 command to start an ipkernel. See base_launch_kernel for arguments.
654
654
655 Returns
655 Returns
656 -------
656 -------
657 A tuple of form:
657 A tuple of form:
658 (kernel_process, shell_port, iopub_port, stdin_port, hb_port)
658 (kernel_process, shell_port, iopub_port, stdin_port, hb_port)
659 where kernel_process is a Popen object and the ports are integers.
659 where kernel_process is a Popen object and the ports are integers.
660 """
660 """
661 return base_launch_kernel('from IPython.zmq.ipkernel import main; main()',
661 return base_launch_kernel('from IPython.zmq.ipkernel import main; main()',
662 *args, **kwargs)
662 *args, **kwargs)
663
663
664
664
665 def main():
665 def main():
666 """Run an IPKernel as an application"""
666 """Run an IPKernel as an application"""
667 app = IPKernelApp.instance()
667 app = IPKernelApp.instance()
668 app.initialize()
668 app.initialize()
669 app.start()
669 app.start()
670
670
671
671
672 if __name__ == '__main__':
672 if __name__ == '__main__':
673 main()
673 main()
@@ -1,214 +1,214 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 """An Application for launching a kernel
2 """An Application for launching a kernel
3
3
4 Authors
4 Authors
5 -------
5 -------
6 * MinRK
6 * MinRK
7 """
7 """
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2011 The IPython Development Team
9 # Copyright (C) 2011 The IPython Development Team
10 #
10 #
11 # Distributed under the terms of the BSD License. The full license is in
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING.txt, distributed as part of this software.
12 # the file COPYING.txt, distributed as part of this software.
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 # Imports
16 # Imports
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18
18
19 # Standard library imports.
19 # Standard library imports.
20 import os
20 import os
21 import sys
21 import sys
22
22
23 # System library imports.
23 # System library imports.
24 import zmq
24 import zmq
25
25
26 # IPython imports.
26 # IPython imports.
27 from IPython.core.ultratb import FormattedTB
27 from IPython.core.ultratb import FormattedTB
28 from IPython.core.newapplication import (
28 from IPython.core.newapplication import (
29 BaseIPythonApplication, base_flags, base_aliases
29 BaseIPythonApplication, base_flags, base_aliases
30 )
30 )
31 from IPython.utils import io
31 from IPython.utils import io
32 from IPython.utils.localinterfaces import LOCALHOST
32 from IPython.utils.localinterfaces import LOCALHOST
33 from IPython.utils.traitlets import Any, Instance, Dict, Unicode, Int, Bool
33 from IPython.utils.traitlets import Any, Instance, Dict, Unicode, Int, Bool
34 from IPython.utils.importstring import import_item
34 from IPython.utils.importstring import import_item
35 # local imports
35 # local imports
36 from IPython.zmq.heartbeat import Heartbeat
36 from IPython.zmq.heartbeat import Heartbeat
37 from IPython.zmq.parentpoller import ParentPollerUnix, ParentPollerWindows
37 from IPython.zmq.parentpoller import ParentPollerUnix, ParentPollerWindows
38 from IPython.zmq.session import Session
38 from IPython.zmq.session import Session
39
39
40
40
41 #-----------------------------------------------------------------------------
41 #-----------------------------------------------------------------------------
42 # Flags and Aliases
42 # Flags and Aliases
43 #-----------------------------------------------------------------------------
43 #-----------------------------------------------------------------------------
44
44
45 kernel_aliases = dict(base_aliases)
45 kernel_aliases = dict(base_aliases)
46 kernel_aliases.update({
46 kernel_aliases.update({
47 'ip' : 'KernelApp.ip',
47 'ip' : 'KernelApp.ip',
48 'hb' : 'KernelApp.hb_port',
48 'hb' : 'KernelApp.hb_port',
49 'shell' : 'KernelApp.shell_port',
49 'shell' : 'KernelApp.shell_port',
50 'iopub' : 'KernelApp.iopub_port',
50 'iopub' : 'KernelApp.iopub_port',
51 'stdin' : 'KernelApp.stdin_port',
51 'stdin' : 'KernelApp.stdin_port',
52 'parent': 'KernelApp.parent',
52 'parent': 'KernelApp.parent',
53 })
53 })
54 if sys.platform.startswith('win'):
54 if sys.platform.startswith('win'):
55 kernel_aliases['interrupt'] = 'KernelApp.interrupt'
55 kernel_aliases['interrupt'] = 'KernelApp.interrupt'
56
56
57 kernel_flags = dict(base_flags)
57 kernel_flags = dict(base_flags)
58 kernel_flags.update({
58 kernel_flags.update({
59 'no-stdout' : (
59 'no-stdout' : (
60 {'KernelApp' : {'no_stdout' : True}},
60 {'KernelApp' : {'no_stdout' : True}},
61 "redirect stdout to the null device"),
61 "redirect stdout to the null device"),
62 'no-stderr' : (
62 'no-stderr' : (
63 {'KernelApp' : {'no_stderr' : True}},
63 {'KernelApp' : {'no_stderr' : True}},
64 "redirect stderr to the null device"),
64 "redirect stderr to the null device"),
65 })
65 })
66
66
67
67
68 #-----------------------------------------------------------------------------
68 #-----------------------------------------------------------------------------
69 # Application class for starting a Kernel
69 # Application class for starting a Kernel
70 #-----------------------------------------------------------------------------
70 #-----------------------------------------------------------------------------
71
71
72 class KernelApp(BaseIPythonApplication):
72 class KernelApp(BaseIPythonApplication):
73 name='pykernel'
73 name='pykernel'
74 aliases = Dict(kernel_aliases)
74 aliases = Dict(kernel_aliases)
75 flags = Dict(kernel_flags)
75 flags = Dict(kernel_flags)
76
76 classes = [Session]
77 # the kernel class, as an importstring
77 # the kernel class, as an importstring
78 kernel_class = Unicode('IPython.zmq.pykernel.Kernel')
78 kernel_class = Unicode('IPython.zmq.pykernel.Kernel')
79 kernel = Any()
79 kernel = Any()
80 poller = Any() # don't restrict this even though current pollers are all Threads
80 poller = Any() # don't restrict this even though current pollers are all Threads
81 heartbeat = Instance(Heartbeat)
81 heartbeat = Instance(Heartbeat)
82 session = Instance('IPython.zmq.session.Session')
82 session = Instance('IPython.zmq.session.Session')
83 ports = Dict()
83 ports = Dict()
84
84
85 # connection info:
85 # connection info:
86 ip = Unicode(LOCALHOST, config=True,
86 ip = Unicode(LOCALHOST, config=True,
87 help="Set the IP or interface on which the kernel will listen.")
87 help="Set the IP or interface on which the kernel will listen.")
88 hb_port = Int(0, config=True, help="set the heartbeat port [default: random]")
88 hb_port = Int(0, config=True, help="set the heartbeat port [default: random]")
89 shell_port = Int(0, config=True, help="set the shell (XREP) port [default: random]")
89 shell_port = Int(0, config=True, help="set the shell (XREP) port [default: random]")
90 iopub_port = Int(0, config=True, help="set the iopub (PUB) port [default: random]")
90 iopub_port = Int(0, config=True, help="set the iopub (PUB) port [default: random]")
91 stdin_port = Int(0, config=True, help="set the stdin (XREQ) port [default: random]")
91 stdin_port = Int(0, config=True, help="set the stdin (XREQ) port [default: random]")
92
92
93 # streams, etc.
93 # streams, etc.
94 no_stdout = Bool(False, config=True, help="redirect stdout to the null device")
94 no_stdout = Bool(False, config=True, help="redirect stdout to the null device")
95 no_stderr = Bool(False, config=True, help="redirect stderr to the null device")
95 no_stderr = Bool(False, config=True, help="redirect stderr to the null device")
96 outstream_class = Unicode('IPython.zmq.iostream.OutStream', config=True,
96 outstream_class = Unicode('IPython.zmq.iostream.OutStream', config=True,
97 help="The importstring for the OutStream factory")
97 help="The importstring for the OutStream factory")
98 displayhook_class = Unicode('IPython.zmq.displayhook.DisplayHook', config=True,
98 displayhook_class = Unicode('IPython.zmq.displayhook.DisplayHook', config=True,
99 help="The importstring for the DisplayHook factory")
99 help="The importstring for the DisplayHook factory")
100
100
101 # polling
101 # polling
102 parent = Int(0, config=True,
102 parent = Int(0, config=True,
103 help="""kill this process if its parent dies. On Windows, the argument
103 help="""kill this process if its parent dies. On Windows, the argument
104 specifies the HANDLE of the parent process, otherwise it is simply boolean.
104 specifies the HANDLE of the parent process, otherwise it is simply boolean.
105 """)
105 """)
106 interrupt = Int(0, config=True,
106 interrupt = Int(0, config=True,
107 help="""ONLY USED ON WINDOWS
107 help="""ONLY USED ON WINDOWS
108 Interrupt this process when the parent is signalled.
108 Interrupt this process when the parent is signalled.
109 """)
109 """)
110
110
111 def init_crash_handler(self):
111 def init_crash_handler(self):
112 # Install minimal exception handling
112 # Install minimal exception handling
113 sys.excepthook = FormattedTB(mode='Verbose', color_scheme='NoColor',
113 sys.excepthook = FormattedTB(mode='Verbose', color_scheme='NoColor',
114 ostream=sys.__stdout__)
114 ostream=sys.__stdout__)
115
115
116 def init_poller(self):
116 def init_poller(self):
117 if sys.platform == 'win32':
117 if sys.platform == 'win32':
118 if self.interrupt or self.parent:
118 if self.interrupt or self.parent:
119 self.poller = ParentPollerWindows(self.interrupt, self.parent)
119 self.poller = ParentPollerWindows(self.interrupt, self.parent)
120 elif self.parent:
120 elif self.parent:
121 self.poller = ParentPollerUnix()
121 self.poller = ParentPollerUnix()
122
122
123 def _bind_socket(self, s, port):
123 def _bind_socket(self, s, port):
124 iface = 'tcp://%s' % self.ip
124 iface = 'tcp://%s' % self.ip
125 if port <= 0:
125 if port <= 0:
126 port = s.bind_to_random_port(iface)
126 port = s.bind_to_random_port(iface)
127 else:
127 else:
128 s.bind(iface + ':%i'%port)
128 s.bind(iface + ':%i'%port)
129 return port
129 return port
130
130
131 def init_sockets(self):
131 def init_sockets(self):
132 # Create a context, a session, and the kernel sockets.
132 # Create a context, a session, and the kernel sockets.
133 io.raw_print("Starting the kernel at pid:", os.getpid())
133 io.raw_print("Starting the kernel at pid:", os.getpid())
134 context = zmq.Context.instance()
134 context = zmq.Context.instance()
135 # Uncomment this to try closing the context.
135 # Uncomment this to try closing the context.
136 # atexit.register(context.term)
136 # atexit.register(context.term)
137
137
138 self.shell_socket = context.socket(zmq.XREP)
138 self.shell_socket = context.socket(zmq.XREP)
139 self.shell_port = self._bind_socket(self.shell_socket, self.shell_port)
139 self.shell_port = self._bind_socket(self.shell_socket, self.shell_port)
140 self.log.debug("shell XREP Channel on port: %i"%self.shell_port)
140 self.log.debug("shell XREP Channel on port: %i"%self.shell_port)
141
141
142 self.iopub_socket = context.socket(zmq.PUB)
142 self.iopub_socket = context.socket(zmq.PUB)
143 self.iopub_port = self._bind_socket(self.iopub_socket, self.iopub_port)
143 self.iopub_port = self._bind_socket(self.iopub_socket, self.iopub_port)
144 self.log.debug("iopub PUB Channel on port: %i"%self.iopub_port)
144 self.log.debug("iopub PUB Channel on port: %i"%self.iopub_port)
145
145
146 self.stdin_socket = context.socket(zmq.XREQ)
146 self.stdin_socket = context.socket(zmq.XREQ)
147 self.stdin_port = self._bind_socket(self.stdin_socket, self.stdin_port)
147 self.stdin_port = self._bind_socket(self.stdin_socket, self.stdin_port)
148 self.log.debug("stdin XREQ Channel on port: %i"%self.stdin_port)
148 self.log.debug("stdin XREQ Channel on port: %i"%self.stdin_port)
149
149
150 self.heartbeat = Heartbeat(context, (self.ip, self.hb_port))
150 self.heartbeat = Heartbeat(context, (self.ip, self.hb_port))
151 self.hb_port = self.heartbeat.port
151 self.hb_port = self.heartbeat.port
152 self.log.debug("Heartbeat REP Channel on port: %i"%self.hb_port)
152 self.log.debug("Heartbeat REP Channel on port: %i"%self.hb_port)
153
153
154 # Helper to make it easier to connect to an existing kernel, until we have
154 # Helper to make it easier to connect to an existing kernel, until we have
155 # single-port connection negotiation fully implemented.
155 # single-port connection negotiation fully implemented.
156 self.log.info("To connect another client to this kernel, use:")
156 self.log.info("To connect another client to this kernel, use:")
157 self.log.info("--external shell={0} iopub={1} stdin={2} hb={3}".format(
157 self.log.info("--external shell={0} iopub={1} stdin={2} hb={3}".format(
158 self.shell_port, self.iopub_port, self.stdin_port, self.hb_port))
158 self.shell_port, self.iopub_port, self.stdin_port, self.hb_port))
159
159
160
160
161 self.ports = dict(shell=self.shell_port, iopub=self.iopub_port,
161 self.ports = dict(shell=self.shell_port, iopub=self.iopub_port,
162 stdin=self.stdin_port, hb=self.hb_port)
162 stdin=self.stdin_port, hb=self.hb_port)
163
163
164 def init_session(self):
164 def init_session(self):
165 """create our session object"""
165 """create our session object"""
166 self.session = Session(username=u'kernel')
166 self.session = Session(config=self.config, username=u'kernel')
167
167
168 def init_io(self):
168 def init_io(self):
169 """redirects stdout/stderr, and installs a display hook"""
169 """redirects stdout/stderr, and installs a display hook"""
170 # Re-direct stdout/stderr, if necessary.
170 # Re-direct stdout/stderr, if necessary.
171 if self.no_stdout or self.no_stderr:
171 if self.no_stdout or self.no_stderr:
172 blackhole = file(os.devnull, 'w')
172 blackhole = file(os.devnull, 'w')
173 if self.no_stdout:
173 if self.no_stdout:
174 sys.stdout = sys.__stdout__ = blackhole
174 sys.stdout = sys.__stdout__ = blackhole
175 if self.no_stderr:
175 if self.no_stderr:
176 sys.stderr = sys.__stderr__ = blackhole
176 sys.stderr = sys.__stderr__ = blackhole
177
177
178 # Redirect input streams and set a display hook.
178 # Redirect input streams and set a display hook.
179
179
180 if self.outstream_class:
180 if self.outstream_class:
181 outstream_factory = import_item(str(self.outstream_class))
181 outstream_factory = import_item(str(self.outstream_class))
182 sys.stdout = outstream_factory(self.session, self.iopub_socket, u'stdout')
182 sys.stdout = outstream_factory(self.session, self.iopub_socket, u'stdout')
183 sys.stderr = outstream_factory(self.session, self.iopub_socket, u'stderr')
183 sys.stderr = outstream_factory(self.session, self.iopub_socket, u'stderr')
184 if self.displayhook_class:
184 if self.displayhook_class:
185 displayhook_factory = import_item(str(self.displayhook_class))
185 displayhook_factory = import_item(str(self.displayhook_class))
186 sys.displayhook = displayhook_factory(self.session, self.iopub_socket)
186 sys.displayhook = displayhook_factory(self.session, self.iopub_socket)
187
187
188 def init_kernel(self):
188 def init_kernel(self):
189 """Create the Kernel object itself"""
189 """Create the Kernel object itself"""
190 kernel_factory = import_item(str(self.kernel_class))
190 kernel_factory = import_item(str(self.kernel_class))
191 self.kernel = kernel_factory(config=self.config, session=self.session,
191 self.kernel = kernel_factory(config=self.config, session=self.session,
192 shell_socket=self.shell_socket,
192 shell_socket=self.shell_socket,
193 iopub_socket=self.iopub_socket,
193 iopub_socket=self.iopub_socket,
194 stdin_socket=self.stdin_socket,
194 stdin_socket=self.stdin_socket,
195 log=self.log
195 log=self.log
196 )
196 )
197 self.kernel.record_ports(self.ports)
197 self.kernel.record_ports(self.ports)
198
198
199 def initialize(self, argv=None):
199 def initialize(self, argv=None):
200 super(KernelApp, self).initialize(argv)
200 super(KernelApp, self).initialize(argv)
201 self.init_session()
201 self.init_session()
202 self.init_poller()
202 self.init_poller()
203 self.init_sockets()
203 self.init_sockets()
204 self.init_io()
204 self.init_io()
205 self.init_kernel()
205 self.init_kernel()
206
206
207 def start(self):
207 def start(self):
208 self.heartbeat.start()
208 self.heartbeat.start()
209 if self.poller is not None:
209 if self.poller is not None:
210 self.poller.start()
210 self.poller.start()
211 try:
211 try:
212 self.kernel.start()
212 self.kernel.start()
213 except KeyboardInterrupt:
213 except KeyboardInterrupt:
214 pass
214 pass
@@ -1,968 +1,976 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
19 import atexit
20 import errno
20 import errno
21 from Queue import Queue, Empty
21 from Queue import Queue, Empty
22 from subprocess import Popen
22 from subprocess import Popen
23 import signal
23 import signal
24 import sys
24 import sys
25 from threading import Thread
25 from threading import Thread
26 import time
26 import time
27 import logging
27 import logging
28
28
29 # System library imports.
29 # System library imports.
30 import zmq
30 import zmq
31 from zmq import POLLIN, POLLOUT, POLLERR
31 from zmq import POLLIN, POLLOUT, POLLERR
32 from zmq.eventloop import ioloop
32 from zmq.eventloop import ioloop
33
33
34 # Local imports.
34 # Local imports.
35 from IPython.config.loader import Config
35 from IPython.utils import io
36 from IPython.utils import io
36 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
37 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
37 from IPython.utils.traitlets import HasTraits, Any, Instance, Type, TCPAddress
38 from IPython.utils.traitlets import HasTraits, Any, Instance, Type, TCPAddress
38 from session import Session, Message
39 from session import Session, Message
39
40
40 #-----------------------------------------------------------------------------
41 #-----------------------------------------------------------------------------
41 # Constants and exceptions
42 # Constants and exceptions
42 #-----------------------------------------------------------------------------
43 #-----------------------------------------------------------------------------
43
44
44 class InvalidPortNumber(Exception):
45 class InvalidPortNumber(Exception):
45 pass
46 pass
46
47
47 #-----------------------------------------------------------------------------
48 #-----------------------------------------------------------------------------
48 # Utility functions
49 # Utility functions
49 #-----------------------------------------------------------------------------
50 #-----------------------------------------------------------------------------
50
51
51 # some utilities to validate message structure, these might get moved elsewhere
52 # some utilities to validate message structure, these might get moved elsewhere
52 # if they prove to have more generic utility
53 # if they prove to have more generic utility
53
54
54 def validate_string_list(lst):
55 def validate_string_list(lst):
55 """Validate that the input is a list of strings.
56 """Validate that the input is a list of strings.
56
57
57 Raises ValueError if not."""
58 Raises ValueError if not."""
58 if not isinstance(lst, list):
59 if not isinstance(lst, list):
59 raise ValueError('input %r must be a list' % lst)
60 raise ValueError('input %r must be a list' % lst)
60 for x in lst:
61 for x in lst:
61 if not isinstance(x, basestring):
62 if not isinstance(x, basestring):
62 raise ValueError('element %r in list must be a string' % x)
63 raise ValueError('element %r in list must be a string' % x)
63
64
64
65
65 def validate_string_dict(dct):
66 def validate_string_dict(dct):
66 """Validate that the input is a dict with string keys and values.
67 """Validate that the input is a dict with string keys and values.
67
68
68 Raises ValueError if not."""
69 Raises ValueError if not."""
69 for k,v in dct.iteritems():
70 for k,v in dct.iteritems():
70 if not isinstance(k, basestring):
71 if not isinstance(k, basestring):
71 raise ValueError('key %r in dict must be a string' % k)
72 raise ValueError('key %r in dict must be a string' % k)
72 if not isinstance(v, basestring):
73 if not isinstance(v, basestring):
73 raise ValueError('value %r in dict must be a string' % v)
74 raise ValueError('value %r in dict must be a string' % v)
74
75
75
76
76 #-----------------------------------------------------------------------------
77 #-----------------------------------------------------------------------------
77 # ZMQ Socket Channel classes
78 # ZMQ Socket Channel classes
78 #-----------------------------------------------------------------------------
79 #-----------------------------------------------------------------------------
79
80
80 class ZMQSocketChannel(Thread):
81 class ZMQSocketChannel(Thread):
81 """The base class for the channels that use ZMQ sockets.
82 """The base class for the channels that use ZMQ sockets.
82 """
83 """
83 context = None
84 context = None
84 session = None
85 session = None
85 socket = None
86 socket = None
86 ioloop = None
87 ioloop = None
87 iostate = None
88 iostate = None
88 _address = None
89 _address = None
89
90
90 def __init__(self, context, session, address):
91 def __init__(self, context, session, address):
91 """Create a channel
92 """Create a channel
92
93
93 Parameters
94 Parameters
94 ----------
95 ----------
95 context : :class:`zmq.Context`
96 context : :class:`zmq.Context`
96 The ZMQ context to use.
97 The ZMQ context to use.
97 session : :class:`session.Session`
98 session : :class:`session.Session`
98 The session to use.
99 The session to use.
99 address : tuple
100 address : tuple
100 Standard (ip, port) tuple that the kernel is listening on.
101 Standard (ip, port) tuple that the kernel is listening on.
101 """
102 """
102 super(ZMQSocketChannel, self).__init__()
103 super(ZMQSocketChannel, self).__init__()
103 self.daemon = True
104 self.daemon = True
104
105
105 self.context = context
106 self.context = context
106 self.session = session
107 self.session = session
107 if address[1] == 0:
108 if address[1] == 0:
108 message = 'The port number for a channel cannot be 0.'
109 message = 'The port number for a channel cannot be 0.'
109 raise InvalidPortNumber(message)
110 raise InvalidPortNumber(message)
110 self._address = address
111 self._address = address
111
112
112 def _run_loop(self):
113 def _run_loop(self):
113 """Run my loop, ignoring EINTR events in the poller"""
114 """Run my loop, ignoring EINTR events in the poller"""
114 while True:
115 while True:
115 try:
116 try:
116 self.ioloop.start()
117 self.ioloop.start()
117 except zmq.ZMQError as e:
118 except zmq.ZMQError as e:
118 if e.errno == errno.EINTR:
119 if e.errno == errno.EINTR:
119 continue
120 continue
120 else:
121 else:
121 raise
122 raise
122 else:
123 else:
123 break
124 break
124
125
125 def stop(self):
126 def stop(self):
126 """Stop the channel's activity.
127 """Stop the channel's activity.
127
128
128 This calls :method:`Thread.join` and returns when the thread
129 This calls :method:`Thread.join` and returns when the thread
129 terminates. :class:`RuntimeError` will be raised if
130 terminates. :class:`RuntimeError` will be raised if
130 :method:`self.start` is called again.
131 :method:`self.start` is called again.
131 """
132 """
132 self.join()
133 self.join()
133
134
134 @property
135 @property
135 def address(self):
136 def address(self):
136 """Get the channel's address as an (ip, port) tuple.
137 """Get the channel's address as an (ip, port) tuple.
137
138
138 By the default, the address is (localhost, 0), where 0 means a random
139 By the default, the address is (localhost, 0), where 0 means a random
139 port.
140 port.
140 """
141 """
141 return self._address
142 return self._address
142
143
143 def add_io_state(self, state):
144 def add_io_state(self, state):
144 """Add IO state to the eventloop.
145 """Add IO state to the eventloop.
145
146
146 Parameters
147 Parameters
147 ----------
148 ----------
148 state : zmq.POLLIN|zmq.POLLOUT|zmq.POLLERR
149 state : zmq.POLLIN|zmq.POLLOUT|zmq.POLLERR
149 The IO state flag to set.
150 The IO state flag to set.
150
151
151 This is thread safe as it uses the thread safe IOLoop.add_callback.
152 This is thread safe as it uses the thread safe IOLoop.add_callback.
152 """
153 """
153 def add_io_state_callback():
154 def add_io_state_callback():
154 if not self.iostate & state:
155 if not self.iostate & state:
155 self.iostate = self.iostate | state
156 self.iostate = self.iostate | state
156 self.ioloop.update_handler(self.socket, self.iostate)
157 self.ioloop.update_handler(self.socket, self.iostate)
157 self.ioloop.add_callback(add_io_state_callback)
158 self.ioloop.add_callback(add_io_state_callback)
158
159
159 def drop_io_state(self, state):
160 def drop_io_state(self, state):
160 """Drop IO state from the eventloop.
161 """Drop IO state from the eventloop.
161
162
162 Parameters
163 Parameters
163 ----------
164 ----------
164 state : zmq.POLLIN|zmq.POLLOUT|zmq.POLLERR
165 state : zmq.POLLIN|zmq.POLLOUT|zmq.POLLERR
165 The IO state flag to set.
166 The IO state flag to set.
166
167
167 This is thread safe as it uses the thread safe IOLoop.add_callback.
168 This is thread safe as it uses the thread safe IOLoop.add_callback.
168 """
169 """
169 def drop_io_state_callback():
170 def drop_io_state_callback():
170 if self.iostate & state:
171 if self.iostate & state:
171 self.iostate = self.iostate & (~state)
172 self.iostate = self.iostate & (~state)
172 self.ioloop.update_handler(self.socket, self.iostate)
173 self.ioloop.update_handler(self.socket, self.iostate)
173 self.ioloop.add_callback(drop_io_state_callback)
174 self.ioloop.add_callback(drop_io_state_callback)
174
175
175
176
176 class ShellSocketChannel(ZMQSocketChannel):
177 class ShellSocketChannel(ZMQSocketChannel):
177 """The XREQ channel for issues request/replies to the kernel.
178 """The XREQ channel for issues request/replies to the kernel.
178 """
179 """
179
180
180 command_queue = None
181 command_queue = None
181
182
182 def __init__(self, context, session, address):
183 def __init__(self, context, session, address):
183 super(ShellSocketChannel, self).__init__(context, session, address)
184 super(ShellSocketChannel, self).__init__(context, session, address)
184 self.command_queue = Queue()
185 self.command_queue = Queue()
185 self.ioloop = ioloop.IOLoop()
186 self.ioloop = ioloop.IOLoop()
186
187
187 def run(self):
188 def run(self):
188 """The thread's main activity. Call start() instead."""
189 """The thread's main activity. Call start() instead."""
189 self.socket = self.context.socket(zmq.XREQ)
190 self.socket = self.context.socket(zmq.XREQ)
190 self.socket.setsockopt(zmq.IDENTITY, self.session.session)
191 self.socket.setsockopt(zmq.IDENTITY, self.session.session)
191 self.socket.connect('tcp://%s:%i' % self.address)
192 self.socket.connect('tcp://%s:%i' % self.address)
192 self.iostate = POLLERR|POLLIN
193 self.iostate = POLLERR|POLLIN
193 self.ioloop.add_handler(self.socket, self._handle_events,
194 self.ioloop.add_handler(self.socket, self._handle_events,
194 self.iostate)
195 self.iostate)
195 self._run_loop()
196 self._run_loop()
196
197
197 def stop(self):
198 def stop(self):
198 self.ioloop.stop()
199 self.ioloop.stop()
199 super(ShellSocketChannel, self).stop()
200 super(ShellSocketChannel, self).stop()
200
201
201 def call_handlers(self, msg):
202 def call_handlers(self, msg):
202 """This method is called in the ioloop thread when a message arrives.
203 """This method is called in the ioloop thread when a message arrives.
203
204
204 Subclasses should override this method to handle incoming messages.
205 Subclasses should override this method to handle incoming messages.
205 It is important to remember that this method is called in the thread
206 It is important to remember that this method is called in the thread
206 so that some logic must be done to ensure that the application leve
207 so that some logic must be done to ensure that the application leve
207 handlers are called in the application thread.
208 handlers are called in the application thread.
208 """
209 """
209 raise NotImplementedError('call_handlers must be defined in a subclass.')
210 raise NotImplementedError('call_handlers must be defined in a subclass.')
210
211
211 def execute(self, code, silent=False,
212 def execute(self, code, silent=False,
212 user_variables=None, user_expressions=None):
213 user_variables=None, user_expressions=None):
213 """Execute code in the kernel.
214 """Execute code in the kernel.
214
215
215 Parameters
216 Parameters
216 ----------
217 ----------
217 code : str
218 code : str
218 A string of Python code.
219 A string of Python code.
219
220
220 silent : bool, optional (default False)
221 silent : bool, optional (default False)
221 If set, the kernel will execute the code as quietly possible.
222 If set, the kernel will execute the code as quietly possible.
222
223
223 user_variables : list, optional
224 user_variables : list, optional
224 A list of variable names to pull from the user's namespace. They
225 A list of variable names to pull from the user's namespace. They
225 will come back as a dict with these names as keys and their
226 will come back as a dict with these names as keys and their
226 :func:`repr` as values.
227 :func:`repr` as values.
227
228
228 user_expressions : dict, optional
229 user_expressions : dict, optional
229 A dict with string keys and to pull from the user's
230 A dict with string keys and to pull from the user's
230 namespace. They will come back as a dict with these names as keys
231 namespace. They will come back as a dict with these names as keys
231 and their :func:`repr` as values.
232 and their :func:`repr` as values.
232
233
233 Returns
234 Returns
234 -------
235 -------
235 The msg_id of the message sent.
236 The msg_id of the message sent.
236 """
237 """
237 if user_variables is None:
238 if user_variables is None:
238 user_variables = []
239 user_variables = []
239 if user_expressions is None:
240 if user_expressions is None:
240 user_expressions = {}
241 user_expressions = {}
241
242
242 # Don't waste network traffic if inputs are invalid
243 # Don't waste network traffic if inputs are invalid
243 if not isinstance(code, basestring):
244 if not isinstance(code, basestring):
244 raise ValueError('code %r must be a string' % code)
245 raise ValueError('code %r must be a string' % code)
245 validate_string_list(user_variables)
246 validate_string_list(user_variables)
246 validate_string_dict(user_expressions)
247 validate_string_dict(user_expressions)
247
248
248 # Create class for content/msg creation. Related to, but possibly
249 # Create class for content/msg creation. Related to, but possibly
249 # not in Session.
250 # not in Session.
250 content = dict(code=code, silent=silent,
251 content = dict(code=code, silent=silent,
251 user_variables=user_variables,
252 user_variables=user_variables,
252 user_expressions=user_expressions)
253 user_expressions=user_expressions)
253 msg = self.session.msg('execute_request', content)
254 msg = self.session.msg('execute_request', content)
254 self._queue_request(msg)
255 self._queue_request(msg)
255 return msg['header']['msg_id']
256 return msg['header']['msg_id']
256
257
257 def complete(self, text, line, cursor_pos, block=None):
258 def complete(self, text, line, cursor_pos, block=None):
258 """Tab complete text in the kernel's namespace.
259 """Tab complete text in the kernel's namespace.
259
260
260 Parameters
261 Parameters
261 ----------
262 ----------
262 text : str
263 text : str
263 The text to complete.
264 The text to complete.
264 line : str
265 line : str
265 The full line of text that is the surrounding context for the
266 The full line of text that is the surrounding context for the
266 text to complete.
267 text to complete.
267 cursor_pos : int
268 cursor_pos : int
268 The position of the cursor in the line where the completion was
269 The position of the cursor in the line where the completion was
269 requested.
270 requested.
270 block : str, optional
271 block : str, optional
271 The full block of code in which the completion is being requested.
272 The full block of code in which the completion is being requested.
272
273
273 Returns
274 Returns
274 -------
275 -------
275 The msg_id of the message sent.
276 The msg_id of the message sent.
276 """
277 """
277 content = dict(text=text, line=line, block=block, cursor_pos=cursor_pos)
278 content = dict(text=text, line=line, block=block, cursor_pos=cursor_pos)
278 msg = self.session.msg('complete_request', content)
279 msg = self.session.msg('complete_request', content)
279 self._queue_request(msg)
280 self._queue_request(msg)
280 return msg['header']['msg_id']
281 return msg['header']['msg_id']
281
282
282 def object_info(self, oname):
283 def object_info(self, oname):
283 """Get metadata information about an object.
284 """Get metadata information about an object.
284
285
285 Parameters
286 Parameters
286 ----------
287 ----------
287 oname : str
288 oname : str
288 A string specifying the object name.
289 A string specifying the object name.
289
290
290 Returns
291 Returns
291 -------
292 -------
292 The msg_id of the message sent.
293 The msg_id of the message sent.
293 """
294 """
294 content = dict(oname=oname)
295 content = dict(oname=oname)
295 msg = self.session.msg('object_info_request', content)
296 msg = self.session.msg('object_info_request', content)
296 self._queue_request(msg)
297 self._queue_request(msg)
297 return msg['header']['msg_id']
298 return msg['header']['msg_id']
298
299
299 def history(self, raw=True, output=False, hist_access_type='range', **kwargs):
300 def history(self, raw=True, output=False, hist_access_type='range', **kwargs):
300 """Get entries from the history list.
301 """Get entries from the history list.
301
302
302 Parameters
303 Parameters
303 ----------
304 ----------
304 raw : bool
305 raw : bool
305 If True, return the raw input.
306 If True, return the raw input.
306 output : bool
307 output : bool
307 If True, then return the output as well.
308 If True, then return the output as well.
308 hist_access_type : str
309 hist_access_type : str
309 'range' (fill in session, start and stop params), 'tail' (fill in n)
310 'range' (fill in session, start and stop params), 'tail' (fill in n)
310 or 'search' (fill in pattern param).
311 or 'search' (fill in pattern param).
311
312
312 session : int
313 session : int
313 For a range request, the session from which to get lines. Session
314 For a range request, the session from which to get lines. Session
314 numbers are positive integers; negative ones count back from the
315 numbers are positive integers; negative ones count back from the
315 current session.
316 current session.
316 start : int
317 start : int
317 The first line number of a history range.
318 The first line number of a history range.
318 stop : int
319 stop : int
319 The final (excluded) line number of a history range.
320 The final (excluded) line number of a history range.
320
321
321 n : int
322 n : int
322 The number of lines of history to get for a tail request.
323 The number of lines of history to get for a tail request.
323
324
324 pattern : str
325 pattern : str
325 The glob-syntax pattern for a search request.
326 The glob-syntax pattern for a search request.
326
327
327 Returns
328 Returns
328 -------
329 -------
329 The msg_id of the message sent.
330 The msg_id of the message sent.
330 """
331 """
331 content = dict(raw=raw, output=output, hist_access_type=hist_access_type,
332 content = dict(raw=raw, output=output, hist_access_type=hist_access_type,
332 **kwargs)
333 **kwargs)
333 msg = self.session.msg('history_request', content)
334 msg = self.session.msg('history_request', content)
334 self._queue_request(msg)
335 self._queue_request(msg)
335 return msg['header']['msg_id']
336 return msg['header']['msg_id']
336
337
337 def shutdown(self, restart=False):
338 def shutdown(self, restart=False):
338 """Request an immediate kernel shutdown.
339 """Request an immediate kernel shutdown.
339
340
340 Upon receipt of the (empty) reply, client code can safely assume that
341 Upon receipt of the (empty) reply, client code can safely assume that
341 the kernel has shut down and it's safe to forcefully terminate it if
342 the kernel has shut down and it's safe to forcefully terminate it if
342 it's still alive.
343 it's still alive.
343
344
344 The kernel will send the reply via a function registered with Python's
345 The kernel will send the reply via a function registered with Python's
345 atexit module, ensuring it's truly done as the kernel is done with all
346 atexit module, ensuring it's truly done as the kernel is done with all
346 normal operation.
347 normal operation.
347 """
348 """
348 # Send quit message to kernel. Once we implement kernel-side setattr,
349 # Send quit message to kernel. Once we implement kernel-side setattr,
349 # this should probably be done that way, but for now this will do.
350 # this should probably be done that way, but for now this will do.
350 msg = self.session.msg('shutdown_request', {'restart':restart})
351 msg = self.session.msg('shutdown_request', {'restart':restart})
351 self._queue_request(msg)
352 self._queue_request(msg)
352 return msg['header']['msg_id']
353 return msg['header']['msg_id']
353
354
354 def _handle_events(self, socket, events):
355 def _handle_events(self, socket, events):
355 if events & POLLERR:
356 if events & POLLERR:
356 self._handle_err()
357 self._handle_err()
357 if events & POLLOUT:
358 if events & POLLOUT:
358 self._handle_send()
359 self._handle_send()
359 if events & POLLIN:
360 if events & POLLIN:
360 self._handle_recv()
361 self._handle_recv()
361
362
362 def _handle_recv(self):
363 def _handle_recv(self):
363 ident,msg = self.session.recv(self.socket, 0)
364 ident,msg = self.session.recv(self.socket, 0)
364 self.call_handlers(msg)
365 self.call_handlers(msg)
365
366
366 def _handle_send(self):
367 def _handle_send(self):
367 try:
368 try:
368 msg = self.command_queue.get(False)
369 msg = self.command_queue.get(False)
369 except Empty:
370 except Empty:
370 pass
371 pass
371 else:
372 else:
372 self.session.send(self.socket,msg)
373 self.session.send(self.socket,msg)
373 if self.command_queue.empty():
374 if self.command_queue.empty():
374 self.drop_io_state(POLLOUT)
375 self.drop_io_state(POLLOUT)
375
376
376 def _handle_err(self):
377 def _handle_err(self):
377 # We don't want to let this go silently, so eventually we should log.
378 # We don't want to let this go silently, so eventually we should log.
378 raise zmq.ZMQError()
379 raise zmq.ZMQError()
379
380
380 def _queue_request(self, msg):
381 def _queue_request(self, msg):
381 self.command_queue.put(msg)
382 self.command_queue.put(msg)
382 self.add_io_state(POLLOUT)
383 self.add_io_state(POLLOUT)
383
384
384
385
385 class SubSocketChannel(ZMQSocketChannel):
386 class SubSocketChannel(ZMQSocketChannel):
386 """The SUB channel which listens for messages that the kernel publishes.
387 """The SUB channel which listens for messages that the kernel publishes.
387 """
388 """
388
389
389 def __init__(self, context, session, address):
390 def __init__(self, context, session, address):
390 super(SubSocketChannel, self).__init__(context, session, address)
391 super(SubSocketChannel, self).__init__(context, session, address)
391 self.ioloop = ioloop.IOLoop()
392 self.ioloop = ioloop.IOLoop()
392
393
393 def run(self):
394 def run(self):
394 """The thread's main activity. Call start() instead."""
395 """The thread's main activity. Call start() instead."""
395 self.socket = self.context.socket(zmq.SUB)
396 self.socket = self.context.socket(zmq.SUB)
396 self.socket.setsockopt(zmq.SUBSCRIBE,'')
397 self.socket.setsockopt(zmq.SUBSCRIBE,'')
397 self.socket.setsockopt(zmq.IDENTITY, self.session.session)
398 self.socket.setsockopt(zmq.IDENTITY, self.session.session)
398 self.socket.connect('tcp://%s:%i' % self.address)
399 self.socket.connect('tcp://%s:%i' % self.address)
399 self.iostate = POLLIN|POLLERR
400 self.iostate = POLLIN|POLLERR
400 self.ioloop.add_handler(self.socket, self._handle_events,
401 self.ioloop.add_handler(self.socket, self._handle_events,
401 self.iostate)
402 self.iostate)
402 self._run_loop()
403 self._run_loop()
403
404
404 def stop(self):
405 def stop(self):
405 self.ioloop.stop()
406 self.ioloop.stop()
406 super(SubSocketChannel, self).stop()
407 super(SubSocketChannel, self).stop()
407
408
408 def call_handlers(self, msg):
409 def call_handlers(self, msg):
409 """This method is called in the ioloop thread when a message arrives.
410 """This method is called in the ioloop thread when a message arrives.
410
411
411 Subclasses should override this method to handle incoming messages.
412 Subclasses should override this method to handle incoming messages.
412 It is important to remember that this method is called in the thread
413 It is important to remember that this method is called in the thread
413 so that some logic must be done to ensure that the application leve
414 so that some logic must be done to ensure that the application leve
414 handlers are called in the application thread.
415 handlers are called in the application thread.
415 """
416 """
416 raise NotImplementedError('call_handlers must be defined in a subclass.')
417 raise NotImplementedError('call_handlers must be defined in a subclass.')
417
418
418 def flush(self, timeout=1.0):
419 def flush(self, timeout=1.0):
419 """Immediately processes all pending messages on the SUB channel.
420 """Immediately processes all pending messages on the SUB channel.
420
421
421 Callers should use this method to ensure that :method:`call_handlers`
422 Callers should use this method to ensure that :method:`call_handlers`
422 has been called for all messages that have been received on the
423 has been called for all messages that have been received on the
423 0MQ SUB socket of this channel.
424 0MQ SUB socket of this channel.
424
425
425 This method is thread safe.
426 This method is thread safe.
426
427
427 Parameters
428 Parameters
428 ----------
429 ----------
429 timeout : float, optional
430 timeout : float, optional
430 The maximum amount of time to spend flushing, in seconds. The
431 The maximum amount of time to spend flushing, in seconds. The
431 default is one second.
432 default is one second.
432 """
433 """
433 # We do the IOLoop callback process twice to ensure that the IOLoop
434 # We do the IOLoop callback process twice to ensure that the IOLoop
434 # gets to perform at least one full poll.
435 # gets to perform at least one full poll.
435 stop_time = time.time() + timeout
436 stop_time = time.time() + timeout
436 for i in xrange(2):
437 for i in xrange(2):
437 self._flushed = False
438 self._flushed = False
438 self.ioloop.add_callback(self._flush)
439 self.ioloop.add_callback(self._flush)
439 while not self._flushed and time.time() < stop_time:
440 while not self._flushed and time.time() < stop_time:
440 time.sleep(0.01)
441 time.sleep(0.01)
441
442
442 def _handle_events(self, socket, events):
443 def _handle_events(self, socket, events):
443 # Turn on and off POLLOUT depending on if we have made a request
444 # Turn on and off POLLOUT depending on if we have made a request
444 if events & POLLERR:
445 if events & POLLERR:
445 self._handle_err()
446 self._handle_err()
446 if events & POLLIN:
447 if events & POLLIN:
447 self._handle_recv()
448 self._handle_recv()
448
449
449 def _handle_err(self):
450 def _handle_err(self):
450 # We don't want to let this go silently, so eventually we should log.
451 # We don't want to let this go silently, so eventually we should log.
451 raise zmq.ZMQError()
452 raise zmq.ZMQError()
452
453
453 def _handle_recv(self):
454 def _handle_recv(self):
454 # Get all of the messages we can
455 # Get all of the messages we can
455 while True:
456 while True:
456 try:
457 try:
457 ident,msg = self.session.recv(self.socket)
458 ident,msg = self.session.recv(self.socket)
458 except zmq.ZMQError:
459 except zmq.ZMQError:
459 # Check the errno?
460 # Check the errno?
460 # Will this trigger POLLERR?
461 # Will this trigger POLLERR?
461 break
462 break
462 else:
463 else:
463 if msg is None:
464 if msg is None:
464 break
465 break
465 self.call_handlers(msg)
466 self.call_handlers(msg)
466
467
467 def _flush(self):
468 def _flush(self):
468 """Callback for :method:`self.flush`."""
469 """Callback for :method:`self.flush`."""
469 self._flushed = True
470 self._flushed = True
470
471
471
472
472 class StdInSocketChannel(ZMQSocketChannel):
473 class StdInSocketChannel(ZMQSocketChannel):
473 """A reply channel to handle raw_input requests that the kernel makes."""
474 """A reply channel to handle raw_input requests that the kernel makes."""
474
475
475 msg_queue = None
476 msg_queue = None
476
477
477 def __init__(self, context, session, address):
478 def __init__(self, context, session, address):
478 super(StdInSocketChannel, self).__init__(context, session, address)
479 super(StdInSocketChannel, self).__init__(context, session, address)
479 self.ioloop = ioloop.IOLoop()
480 self.ioloop = ioloop.IOLoop()
480 self.msg_queue = Queue()
481 self.msg_queue = Queue()
481
482
482 def run(self):
483 def run(self):
483 """The thread's main activity. Call start() instead."""
484 """The thread's main activity. Call start() instead."""
484 self.socket = self.context.socket(zmq.XREQ)
485 self.socket = self.context.socket(zmq.XREQ)
485 self.socket.setsockopt(zmq.IDENTITY, self.session.session)
486 self.socket.setsockopt(zmq.IDENTITY, self.session.session)
486 self.socket.connect('tcp://%s:%i' % self.address)
487 self.socket.connect('tcp://%s:%i' % self.address)
487 self.iostate = POLLERR|POLLIN
488 self.iostate = POLLERR|POLLIN
488 self.ioloop.add_handler(self.socket, self._handle_events,
489 self.ioloop.add_handler(self.socket, self._handle_events,
489 self.iostate)
490 self.iostate)
490 self._run_loop()
491 self._run_loop()
491
492
492 def stop(self):
493 def stop(self):
493 self.ioloop.stop()
494 self.ioloop.stop()
494 super(StdInSocketChannel, self).stop()
495 super(StdInSocketChannel, self).stop()
495
496
496 def call_handlers(self, msg):
497 def call_handlers(self, msg):
497 """This method is called in the ioloop thread when a message arrives.
498 """This method is called in the ioloop thread when a message arrives.
498
499
499 Subclasses should override this method to handle incoming messages.
500 Subclasses should override this method to handle incoming messages.
500 It is important to remember that this method is called in the thread
501 It is important to remember that this method is called in the thread
501 so that some logic must be done to ensure that the application leve
502 so that some logic must be done to ensure that the application leve
502 handlers are called in the application thread.
503 handlers are called in the application thread.
503 """
504 """
504 raise NotImplementedError('call_handlers must be defined in a subclass.')
505 raise NotImplementedError('call_handlers must be defined in a subclass.')
505
506
506 def input(self, string):
507 def input(self, string):
507 """Send a string of raw input to the kernel."""
508 """Send a string of raw input to the kernel."""
508 content = dict(value=string)
509 content = dict(value=string)
509 msg = self.session.msg('input_reply', content)
510 msg = self.session.msg('input_reply', content)
510 self._queue_reply(msg)
511 self._queue_reply(msg)
511
512
512 def _handle_events(self, socket, events):
513 def _handle_events(self, socket, events):
513 if events & POLLERR:
514 if events & POLLERR:
514 self._handle_err()
515 self._handle_err()
515 if events & POLLOUT:
516 if events & POLLOUT:
516 self._handle_send()
517 self._handle_send()
517 if events & POLLIN:
518 if events & POLLIN:
518 self._handle_recv()
519 self._handle_recv()
519
520
520 def _handle_recv(self):
521 def _handle_recv(self):
521 ident,msg = self.session.recv(self.socket, 0)
522 ident,msg = self.session.recv(self.socket, 0)
522 self.call_handlers(msg)
523 self.call_handlers(msg)
523
524
524 def _handle_send(self):
525 def _handle_send(self):
525 try:
526 try:
526 msg = self.msg_queue.get(False)
527 msg = self.msg_queue.get(False)
527 except Empty:
528 except Empty:
528 pass
529 pass
529 else:
530 else:
530 self.session.send(self.socket,msg)
531 self.session.send(self.socket,msg)
531 if self.msg_queue.empty():
532 if self.msg_queue.empty():
532 self.drop_io_state(POLLOUT)
533 self.drop_io_state(POLLOUT)
533
534
534 def _handle_err(self):
535 def _handle_err(self):
535 # We don't want to let this go silently, so eventually we should log.
536 # We don't want to let this go silently, so eventually we should log.
536 raise zmq.ZMQError()
537 raise zmq.ZMQError()
537
538
538 def _queue_reply(self, msg):
539 def _queue_reply(self, msg):
539 self.msg_queue.put(msg)
540 self.msg_queue.put(msg)
540 self.add_io_state(POLLOUT)
541 self.add_io_state(POLLOUT)
541
542
542
543
543 class HBSocketChannel(ZMQSocketChannel):
544 class HBSocketChannel(ZMQSocketChannel):
544 """The heartbeat channel which monitors the kernel heartbeat.
545 """The heartbeat channel which monitors the kernel heartbeat.
545
546
546 Note that the heartbeat channel is paused by default. As long as you start
547 Note that the heartbeat channel is paused by default. As long as you start
547 this channel, the kernel manager will ensure that it is paused and un-paused
548 this channel, the kernel manager will ensure that it is paused and un-paused
548 as appropriate.
549 as appropriate.
549 """
550 """
550
551
551 time_to_dead = 3.0
552 time_to_dead = 3.0
552 socket = None
553 socket = None
553 poller = None
554 poller = None
554 _running = None
555 _running = None
555 _pause = None
556 _pause = None
556
557
557 def __init__(self, context, session, address):
558 def __init__(self, context, session, address):
558 super(HBSocketChannel, self).__init__(context, session, address)
559 super(HBSocketChannel, self).__init__(context, session, address)
559 self._running = False
560 self._running = False
560 self._pause = True
561 self._pause = True
561
562
562 def _create_socket(self):
563 def _create_socket(self):
563 self.socket = self.context.socket(zmq.REQ)
564 self.socket = self.context.socket(zmq.REQ)
564 self.socket.setsockopt(zmq.IDENTITY, self.session.session)
565 self.socket.setsockopt(zmq.IDENTITY, self.session.session)
565 self.socket.connect('tcp://%s:%i' % self.address)
566 self.socket.connect('tcp://%s:%i' % self.address)
566 self.poller = zmq.Poller()
567 self.poller = zmq.Poller()
567 self.poller.register(self.socket, zmq.POLLIN)
568 self.poller.register(self.socket, zmq.POLLIN)
568
569
569 def run(self):
570 def run(self):
570 """The thread's main activity. Call start() instead."""
571 """The thread's main activity. Call start() instead."""
571 self._create_socket()
572 self._create_socket()
572 self._running = True
573 self._running = True
573 while self._running:
574 while self._running:
574 if self._pause:
575 if self._pause:
575 time.sleep(self.time_to_dead)
576 time.sleep(self.time_to_dead)
576 else:
577 else:
577 since_last_heartbeat = 0.0
578 since_last_heartbeat = 0.0
578 request_time = time.time()
579 request_time = time.time()
579 try:
580 try:
580 #io.rprint('Ping from HB channel') # dbg
581 #io.rprint('Ping from HB channel') # dbg
581 self.socket.send(b'ping')
582 self.socket.send(b'ping')
582 except zmq.ZMQError, e:
583 except zmq.ZMQError, e:
583 #io.rprint('*** HB Error:', e) # dbg
584 #io.rprint('*** HB Error:', e) # dbg
584 if e.errno == zmq.EFSM:
585 if e.errno == zmq.EFSM:
585 #io.rprint('sleep...', self.time_to_dead) # dbg
586 #io.rprint('sleep...', self.time_to_dead) # dbg
586 time.sleep(self.time_to_dead)
587 time.sleep(self.time_to_dead)
587 self._create_socket()
588 self._create_socket()
588 else:
589 else:
589 raise
590 raise
590 else:
591 else:
591 while True:
592 while True:
592 try:
593 try:
593 self.socket.recv(zmq.NOBLOCK)
594 self.socket.recv(zmq.NOBLOCK)
594 except zmq.ZMQError, e:
595 except zmq.ZMQError, e:
595 #io.rprint('*** HB Error 2:', e) # dbg
596 #io.rprint('*** HB Error 2:', e) # dbg
596 if e.errno == zmq.EAGAIN:
597 if e.errno == zmq.EAGAIN:
597 before_poll = time.time()
598 before_poll = time.time()
598 until_dead = self.time_to_dead - (before_poll -
599 until_dead = self.time_to_dead - (before_poll -
599 request_time)
600 request_time)
600
601
601 # When the return value of poll() is an empty
602 # When the return value of poll() is an empty
602 # list, that is when things have gone wrong
603 # list, that is when things have gone wrong
603 # (zeromq bug). As long as it is not an empty
604 # (zeromq bug). As long as it is not an empty
604 # list, poll is working correctly even if it
605 # list, poll is working correctly even if it
605 # returns quickly. Note: poll timeout is in
606 # returns quickly. Note: poll timeout is in
606 # milliseconds.
607 # milliseconds.
607 if until_dead > 0.0:
608 if until_dead > 0.0:
608 while True:
609 while True:
609 try:
610 try:
610 self.poller.poll(1000 * until_dead)
611 self.poller.poll(1000 * until_dead)
611 except zmq.ZMQError as e:
612 except zmq.ZMQError as e:
612 if e.errno == errno.EINTR:
613 if e.errno == errno.EINTR:
613 continue
614 continue
614 else:
615 else:
615 raise
616 raise
616 else:
617 else:
617 break
618 break
618
619
619 since_last_heartbeat = time.time()-request_time
620 since_last_heartbeat = time.time()-request_time
620 if since_last_heartbeat > self.time_to_dead:
621 if since_last_heartbeat > self.time_to_dead:
621 self.call_handlers(since_last_heartbeat)
622 self.call_handlers(since_last_heartbeat)
622 break
623 break
623 else:
624 else:
624 # FIXME: We should probably log this instead.
625 # FIXME: We should probably log this instead.
625 raise
626 raise
626 else:
627 else:
627 until_dead = self.time_to_dead - (time.time() -
628 until_dead = self.time_to_dead - (time.time() -
628 request_time)
629 request_time)
629 if until_dead > 0.0:
630 if until_dead > 0.0:
630 #io.rprint('sleep...', self.time_to_dead) # dbg
631 #io.rprint('sleep...', self.time_to_dead) # dbg
631 time.sleep(until_dead)
632 time.sleep(until_dead)
632 break
633 break
633
634
634 def pause(self):
635 def pause(self):
635 """Pause the heartbeat."""
636 """Pause the heartbeat."""
636 self._pause = True
637 self._pause = True
637
638
638 def unpause(self):
639 def unpause(self):
639 """Unpause the heartbeat."""
640 """Unpause the heartbeat."""
640 self._pause = False
641 self._pause = False
641
642
642 def is_beating(self):
643 def is_beating(self):
643 """Is the heartbeat running and not paused."""
644 """Is the heartbeat running and not paused."""
644 if self.is_alive() and not self._pause:
645 if self.is_alive() and not self._pause:
645 return True
646 return True
646 else:
647 else:
647 return False
648 return False
648
649
649 def stop(self):
650 def stop(self):
650 self._running = False
651 self._running = False
651 super(HBSocketChannel, self).stop()
652 super(HBSocketChannel, self).stop()
652
653
653 def call_handlers(self, since_last_heartbeat):
654 def call_handlers(self, since_last_heartbeat):
654 """This method is called in the ioloop thread when a message arrives.
655 """This method is called in the ioloop thread when a message arrives.
655
656
656 Subclasses should override this method to handle incoming messages.
657 Subclasses should override this method to handle incoming messages.
657 It is important to remember that this method is called in the thread
658 It is important to remember that this method is called in the thread
658 so that some logic must be done to ensure that the application leve
659 so that some logic must be done to ensure that the application leve
659 handlers are called in the application thread.
660 handlers are called in the application thread.
660 """
661 """
661 raise NotImplementedError('call_handlers must be defined in a subclass.')
662 raise NotImplementedError('call_handlers must be defined in a subclass.')
662
663
663
664
664 #-----------------------------------------------------------------------------
665 #-----------------------------------------------------------------------------
665 # Main kernel manager class
666 # Main kernel manager class
666 #-----------------------------------------------------------------------------
667 #-----------------------------------------------------------------------------
667
668
668 class KernelManager(HasTraits):
669 class KernelManager(HasTraits):
669 """ Manages a kernel for a frontend.
670 """ Manages a kernel for a frontend.
670
671
671 The SUB channel is for the frontend to receive messages published by the
672 The SUB channel is for the frontend to receive messages published by the
672 kernel.
673 kernel.
673
674
674 The REQ channel is for the frontend to make requests of the kernel.
675 The REQ channel is for the frontend to make requests of the kernel.
675
676
676 The REP channel is for the kernel to request stdin (raw_input) from the
677 The REP channel is for the kernel to request stdin (raw_input) from the
677 frontend.
678 frontend.
678 """
679 """
680 # config object for passing to child configurables
681 config = Instance(Config)
682
679 # The PyZMQ Context to use for communication with the kernel.
683 # The PyZMQ Context to use for communication with the kernel.
680 context = Instance(zmq.Context,(),{})
684 context = Instance(zmq.Context)
685 def _context_default(self):
686 return zmq.Context.instance()
681
687
682 # The Session to use for communication with the kernel.
688 # The Session to use for communication with the kernel.
683 session = Instance(Session,(),{})
689 session = Instance(Session)
684
690
685 # The kernel process with which the KernelManager is communicating.
691 # The kernel process with which the KernelManager is communicating.
686 kernel = Instance(Popen)
692 kernel = Instance(Popen)
687
693
688 # The addresses for the communication channels.
694 # The addresses for the communication channels.
689 shell_address = TCPAddress((LOCALHOST, 0))
695 shell_address = TCPAddress((LOCALHOST, 0))
690 sub_address = TCPAddress((LOCALHOST, 0))
696 sub_address = TCPAddress((LOCALHOST, 0))
691 stdin_address = TCPAddress((LOCALHOST, 0))
697 stdin_address = TCPAddress((LOCALHOST, 0))
692 hb_address = TCPAddress((LOCALHOST, 0))
698 hb_address = TCPAddress((LOCALHOST, 0))
693
699
694 # The classes to use for the various channels.
700 # The classes to use for the various channels.
695 shell_channel_class = Type(ShellSocketChannel)
701 shell_channel_class = Type(ShellSocketChannel)
696 sub_channel_class = Type(SubSocketChannel)
702 sub_channel_class = Type(SubSocketChannel)
697 stdin_channel_class = Type(StdInSocketChannel)
703 stdin_channel_class = Type(StdInSocketChannel)
698 hb_channel_class = Type(HBSocketChannel)
704 hb_channel_class = Type(HBSocketChannel)
699
705
700 # Protected traits.
706 # Protected traits.
701 _launch_args = Any
707 _launch_args = Any
702 _shell_channel = Any
708 _shell_channel = Any
703 _sub_channel = Any
709 _sub_channel = Any
704 _stdin_channel = Any
710 _stdin_channel = Any
705 _hb_channel = Any
711 _hb_channel = Any
706
712
707 def __init__(self, **kwargs):
713 def __init__(self, **kwargs):
708 super(KernelManager, self).__init__(**kwargs)
714 super(KernelManager, self).__init__(**kwargs)
715 if self.session is None:
716 self.session = Session(config=self.config)
709 # Uncomment this to try closing the context.
717 # Uncomment this to try closing the context.
710 # atexit.register(self.context.close)
718 # atexit.register(self.context.term)
711
719
712 #--------------------------------------------------------------------------
720 #--------------------------------------------------------------------------
713 # Channel management methods:
721 # Channel management methods:
714 #--------------------------------------------------------------------------
722 #--------------------------------------------------------------------------
715
723
716 def start_channels(self, shell=True, sub=True, stdin=True, hb=True):
724 def start_channels(self, shell=True, sub=True, stdin=True, hb=True):
717 """Starts the channels for this kernel.
725 """Starts the channels for this kernel.
718
726
719 This will create the channels if they do not exist and then start
727 This will create the channels if they do not exist and then start
720 them. If port numbers of 0 are being used (random ports) then you
728 them. If port numbers of 0 are being used (random ports) then you
721 must first call :method:`start_kernel`. If the channels have been
729 must first call :method:`start_kernel`. If the channels have been
722 stopped and you call this, :class:`RuntimeError` will be raised.
730 stopped and you call this, :class:`RuntimeError` will be raised.
723 """
731 """
724 if shell:
732 if shell:
725 self.shell_channel.start()
733 self.shell_channel.start()
726 if sub:
734 if sub:
727 self.sub_channel.start()
735 self.sub_channel.start()
728 if stdin:
736 if stdin:
729 self.stdin_channel.start()
737 self.stdin_channel.start()
730 if hb:
738 if hb:
731 self.hb_channel.start()
739 self.hb_channel.start()
732
740
733 def stop_channels(self):
741 def stop_channels(self):
734 """Stops all the running channels for this kernel.
742 """Stops all the running channels for this kernel.
735 """
743 """
736 if self.shell_channel.is_alive():
744 if self.shell_channel.is_alive():
737 self.shell_channel.stop()
745 self.shell_channel.stop()
738 if self.sub_channel.is_alive():
746 if self.sub_channel.is_alive():
739 self.sub_channel.stop()
747 self.sub_channel.stop()
740 if self.stdin_channel.is_alive():
748 if self.stdin_channel.is_alive():
741 self.stdin_channel.stop()
749 self.stdin_channel.stop()
742 if self.hb_channel.is_alive():
750 if self.hb_channel.is_alive():
743 self.hb_channel.stop()
751 self.hb_channel.stop()
744
752
745 @property
753 @property
746 def channels_running(self):
754 def channels_running(self):
747 """Are any of the channels created and running?"""
755 """Are any of the channels created and running?"""
748 return (self.shell_channel.is_alive() or self.sub_channel.is_alive() or
756 return (self.shell_channel.is_alive() or self.sub_channel.is_alive() or
749 self.stdin_channel.is_alive() or self.hb_channel.is_alive())
757 self.stdin_channel.is_alive() or self.hb_channel.is_alive())
750
758
751 #--------------------------------------------------------------------------
759 #--------------------------------------------------------------------------
752 # Kernel process management methods:
760 # Kernel process management methods:
753 #--------------------------------------------------------------------------
761 #--------------------------------------------------------------------------
754
762
755 def start_kernel(self, **kw):
763 def start_kernel(self, **kw):
756 """Starts a kernel process and configures the manager to use it.
764 """Starts a kernel process and configures the manager to use it.
757
765
758 If random ports (port=0) are being used, this method must be called
766 If random ports (port=0) are being used, this method must be called
759 before the channels are created.
767 before the channels are created.
760
768
761 Parameters:
769 Parameters:
762 -----------
770 -----------
763 ipython : bool, optional (default True)
771 ipython : bool, optional (default True)
764 Whether to use an IPython kernel instead of a plain Python kernel.
772 Whether to use an IPython kernel instead of a plain Python kernel.
765
773
766 **kw : optional
774 **kw : optional
767 See respective options for IPython and Python kernels.
775 See respective options for IPython and Python kernels.
768 """
776 """
769 shell, sub, stdin, hb = self.shell_address, self.sub_address, \
777 shell, sub, stdin, hb = self.shell_address, self.sub_address, \
770 self.stdin_address, self.hb_address
778 self.stdin_address, self.hb_address
771 if shell[0] not in LOCAL_IPS or sub[0] not in LOCAL_IPS or \
779 if shell[0] not in LOCAL_IPS or sub[0] not in LOCAL_IPS or \
772 stdin[0] not in LOCAL_IPS or hb[0] not in LOCAL_IPS:
780 stdin[0] not in LOCAL_IPS or hb[0] not in LOCAL_IPS:
773 raise RuntimeError("Can only launch a kernel on a local interface. "
781 raise RuntimeError("Can only launch a kernel on a local interface. "
774 "Make sure that the '*_address' attributes are "
782 "Make sure that the '*_address' attributes are "
775 "configured properly. "
783 "configured properly. "
776 "Currently valid addresses are: %s"%LOCAL_IPS
784 "Currently valid addresses are: %s"%LOCAL_IPS
777 )
785 )
778
786
779 self._launch_args = kw.copy()
787 self._launch_args = kw.copy()
780 if kw.pop('ipython', True):
788 if kw.pop('ipython', True):
781 from ipkernel import launch_kernel
789 from ipkernel import launch_kernel
782 else:
790 else:
783 from pykernel import launch_kernel
791 from pykernel import launch_kernel
784 self.kernel, xrep, pub, req, _hb = launch_kernel(
792 self.kernel, xrep, pub, req, _hb = launch_kernel(
785 shell_port=shell[1], iopub_port=sub[1],
793 shell_port=shell[1], iopub_port=sub[1],
786 stdin_port=stdin[1], hb_port=hb[1], **kw)
794 stdin_port=stdin[1], hb_port=hb[1], **kw)
787 self.shell_address = (shell[0], xrep)
795 self.shell_address = (shell[0], xrep)
788 self.sub_address = (sub[0], pub)
796 self.sub_address = (sub[0], pub)
789 self.stdin_address = (stdin[0], req)
797 self.stdin_address = (stdin[0], req)
790 self.hb_address = (hb[0], _hb)
798 self.hb_address = (hb[0], _hb)
791
799
792 def shutdown_kernel(self, restart=False):
800 def shutdown_kernel(self, restart=False):
793 """ Attempts to the stop the kernel process cleanly. If the kernel
801 """ Attempts to the stop the kernel process cleanly. If the kernel
794 cannot be stopped, it is killed, if possible.
802 cannot be stopped, it is killed, if possible.
795 """
803 """
796 # FIXME: Shutdown does not work on Windows due to ZMQ errors!
804 # FIXME: Shutdown does not work on Windows due to ZMQ errors!
797 if sys.platform == 'win32':
805 if sys.platform == 'win32':
798 self.kill_kernel()
806 self.kill_kernel()
799 return
807 return
800
808
801 # Pause the heart beat channel if it exists.
809 # Pause the heart beat channel if it exists.
802 if self._hb_channel is not None:
810 if self._hb_channel is not None:
803 self._hb_channel.pause()
811 self._hb_channel.pause()
804
812
805 # Don't send any additional kernel kill messages immediately, to give
813 # Don't send any additional kernel kill messages immediately, to give
806 # the kernel a chance to properly execute shutdown actions. Wait for at
814 # the kernel a chance to properly execute shutdown actions. Wait for at
807 # most 1s, checking every 0.1s.
815 # most 1s, checking every 0.1s.
808 self.shell_channel.shutdown(restart=restart)
816 self.shell_channel.shutdown(restart=restart)
809 for i in range(10):
817 for i in range(10):
810 if self.is_alive:
818 if self.is_alive:
811 time.sleep(0.1)
819 time.sleep(0.1)
812 else:
820 else:
813 break
821 break
814 else:
822 else:
815 # OK, we've waited long enough.
823 # OK, we've waited long enough.
816 if self.has_kernel:
824 if self.has_kernel:
817 self.kill_kernel()
825 self.kill_kernel()
818
826
819 def restart_kernel(self, now=False, **kw):
827 def restart_kernel(self, now=False, **kw):
820 """Restarts a kernel with the arguments that were used to launch it.
828 """Restarts a kernel with the arguments that were used to launch it.
821
829
822 If the old kernel was launched with random ports, the same ports will be
830 If the old kernel was launched with random ports, the same ports will be
823 used for the new kernel.
831 used for the new kernel.
824
832
825 Parameters
833 Parameters
826 ----------
834 ----------
827 now : bool, optional
835 now : bool, optional
828 If True, the kernel is forcefully restarted *immediately*, without
836 If True, the kernel is forcefully restarted *immediately*, without
829 having a chance to do any cleanup action. Otherwise the kernel is
837 having a chance to do any cleanup action. Otherwise the kernel is
830 given 1s to clean up before a forceful restart is issued.
838 given 1s to clean up before a forceful restart is issued.
831
839
832 In all cases the kernel is restarted, the only difference is whether
840 In all cases the kernel is restarted, the only difference is whether
833 it is given a chance to perform a clean shutdown or not.
841 it is given a chance to perform a clean shutdown or not.
834
842
835 **kw : optional
843 **kw : optional
836 Any options specified here will replace those used to launch the
844 Any options specified here will replace those used to launch the
837 kernel.
845 kernel.
838 """
846 """
839 if self._launch_args is None:
847 if self._launch_args is None:
840 raise RuntimeError("Cannot restart the kernel. "
848 raise RuntimeError("Cannot restart the kernel. "
841 "No previous call to 'start_kernel'.")
849 "No previous call to 'start_kernel'.")
842 else:
850 else:
843 # Stop currently running kernel.
851 # Stop currently running kernel.
844 if self.has_kernel:
852 if self.has_kernel:
845 if now:
853 if now:
846 self.kill_kernel()
854 self.kill_kernel()
847 else:
855 else:
848 self.shutdown_kernel(restart=True)
856 self.shutdown_kernel(restart=True)
849
857
850 # Start new kernel.
858 # Start new kernel.
851 self._launch_args.update(kw)
859 self._launch_args.update(kw)
852 self.start_kernel(**self._launch_args)
860 self.start_kernel(**self._launch_args)
853
861
854 # FIXME: Messages get dropped in Windows due to probable ZMQ bug
862 # FIXME: Messages get dropped in Windows due to probable ZMQ bug
855 # unless there is some delay here.
863 # unless there is some delay here.
856 if sys.platform == 'win32':
864 if sys.platform == 'win32':
857 time.sleep(0.2)
865 time.sleep(0.2)
858
866
859 @property
867 @property
860 def has_kernel(self):
868 def has_kernel(self):
861 """Returns whether a kernel process has been specified for the kernel
869 """Returns whether a kernel process has been specified for the kernel
862 manager.
870 manager.
863 """
871 """
864 return self.kernel is not None
872 return self.kernel is not None
865
873
866 def kill_kernel(self):
874 def kill_kernel(self):
867 """ Kill the running kernel. """
875 """ Kill the running kernel. """
868 if self.has_kernel:
876 if self.has_kernel:
869 # Pause the heart beat channel if it exists.
877 # Pause the heart beat channel if it exists.
870 if self._hb_channel is not None:
878 if self._hb_channel is not None:
871 self._hb_channel.pause()
879 self._hb_channel.pause()
872
880
873 # Attempt to kill the kernel.
881 # Attempt to kill the kernel.
874 try:
882 try:
875 self.kernel.kill()
883 self.kernel.kill()
876 except OSError, e:
884 except OSError, e:
877 # In Windows, we will get an Access Denied error if the process
885 # In Windows, we will get an Access Denied error if the process
878 # has already terminated. Ignore it.
886 # has already terminated. Ignore it.
879 if sys.platform == 'win32':
887 if sys.platform == 'win32':
880 if e.winerror != 5:
888 if e.winerror != 5:
881 raise
889 raise
882 # On Unix, we may get an ESRCH error if the process has already
890 # On Unix, we may get an ESRCH error if the process has already
883 # terminated. Ignore it.
891 # terminated. Ignore it.
884 else:
892 else:
885 from errno import ESRCH
893 from errno import ESRCH
886 if e.errno != ESRCH:
894 if e.errno != ESRCH:
887 raise
895 raise
888 self.kernel = None
896 self.kernel = None
889 else:
897 else:
890 raise RuntimeError("Cannot kill kernel. No kernel is running!")
898 raise RuntimeError("Cannot kill kernel. No kernel is running!")
891
899
892 def interrupt_kernel(self):
900 def interrupt_kernel(self):
893 """ Interrupts the kernel. Unlike ``signal_kernel``, this operation is
901 """ Interrupts the kernel. Unlike ``signal_kernel``, this operation is
894 well supported on all platforms.
902 well supported on all platforms.
895 """
903 """
896 if self.has_kernel:
904 if self.has_kernel:
897 if sys.platform == 'win32':
905 if sys.platform == 'win32':
898 from parentpoller import ParentPollerWindows as Poller
906 from parentpoller import ParentPollerWindows as Poller
899 Poller.send_interrupt(self.kernel.win32_interrupt_event)
907 Poller.send_interrupt(self.kernel.win32_interrupt_event)
900 else:
908 else:
901 self.kernel.send_signal(signal.SIGINT)
909 self.kernel.send_signal(signal.SIGINT)
902 else:
910 else:
903 raise RuntimeError("Cannot interrupt kernel. No kernel is running!")
911 raise RuntimeError("Cannot interrupt kernel. No kernel is running!")
904
912
905 def signal_kernel(self, signum):
913 def signal_kernel(self, signum):
906 """ Sends a signal to the kernel. Note that since only SIGTERM is
914 """ Sends a signal to the kernel. Note that since only SIGTERM is
907 supported on Windows, this function is only useful on Unix systems.
915 supported on Windows, this function is only useful on Unix systems.
908 """
916 """
909 if self.has_kernel:
917 if self.has_kernel:
910 self.kernel.send_signal(signum)
918 self.kernel.send_signal(signum)
911 else:
919 else:
912 raise RuntimeError("Cannot signal kernel. No kernel is running!")
920 raise RuntimeError("Cannot signal kernel. No kernel is running!")
913
921
914 @property
922 @property
915 def is_alive(self):
923 def is_alive(self):
916 """Is the kernel process still running?"""
924 """Is the kernel process still running?"""
917 # FIXME: not using a heartbeat means this method is broken for any
925 # FIXME: not using a heartbeat means this method is broken for any
918 # remote kernel, it's only capable of handling local kernels.
926 # remote kernel, it's only capable of handling local kernels.
919 if self.has_kernel:
927 if self.has_kernel:
920 if self.kernel.poll() is None:
928 if self.kernel.poll() is None:
921 return True
929 return True
922 else:
930 else:
923 return False
931 return False
924 else:
932 else:
925 # We didn't start the kernel with this KernelManager so we don't
933 # We didn't start the kernel with this KernelManager so we don't
926 # know if it is running. We should use a heartbeat for this case.
934 # know if it is running. We should use a heartbeat for this case.
927 return True
935 return True
928
936
929 #--------------------------------------------------------------------------
937 #--------------------------------------------------------------------------
930 # Channels used for communication with the kernel:
938 # Channels used for communication with the kernel:
931 #--------------------------------------------------------------------------
939 #--------------------------------------------------------------------------
932
940
933 @property
941 @property
934 def shell_channel(self):
942 def shell_channel(self):
935 """Get the REQ socket channel object to make requests of the kernel."""
943 """Get the REQ socket channel object to make requests of the kernel."""
936 if self._shell_channel is None:
944 if self._shell_channel is None:
937 self._shell_channel = self.shell_channel_class(self.context,
945 self._shell_channel = self.shell_channel_class(self.context,
938 self.session,
946 self.session,
939 self.shell_address)
947 self.shell_address)
940 return self._shell_channel
948 return self._shell_channel
941
949
942 @property
950 @property
943 def sub_channel(self):
951 def sub_channel(self):
944 """Get the SUB socket channel object."""
952 """Get the SUB socket channel object."""
945 if self._sub_channel is None:
953 if self._sub_channel is None:
946 self._sub_channel = self.sub_channel_class(self.context,
954 self._sub_channel = self.sub_channel_class(self.context,
947 self.session,
955 self.session,
948 self.sub_address)
956 self.sub_address)
949 return self._sub_channel
957 return self._sub_channel
950
958
951 @property
959 @property
952 def stdin_channel(self):
960 def stdin_channel(self):
953 """Get the REP socket channel object to handle stdin (raw_input)."""
961 """Get the REP socket channel object to handle stdin (raw_input)."""
954 if self._stdin_channel is None:
962 if self._stdin_channel is None:
955 self._stdin_channel = self.stdin_channel_class(self.context,
963 self._stdin_channel = self.stdin_channel_class(self.context,
956 self.session,
964 self.session,
957 self.stdin_address)
965 self.stdin_address)
958 return self._stdin_channel
966 return self._stdin_channel
959
967
960 @property
968 @property
961 def hb_channel(self):
969 def hb_channel(self):
962 """Get the heartbeat socket channel object to check that the
970 """Get the heartbeat socket channel object to check that the
963 kernel is alive."""
971 kernel is alive."""
964 if self._hb_channel is None:
972 if self._hb_channel is None:
965 self._hb_channel = self.hb_channel_class(self.context,
973 self._hb_channel = self.hb_channel_class(self.context,
966 self.session,
974 self.session,
967 self.hb_address)
975 self.hb_address)
968 return self._hb_channel
976 return self._hb_channel
General Comments 0
You need to be logged in to leave comments. Login now