##// END OF EJS Templates
* Added 'req_port' option to 'launch_kernel' and the kernel entry point....
epatters -
Show More
@@ -1,330 +1,330 b''
1 # Standard library imports
1 # Standard library imports
2 import signal
2 import signal
3
3
4 # System library imports
4 # System library imports
5 from pygments.lexers import PythonLexer
5 from pygments.lexers import PythonLexer
6 from PyQt4 import QtCore, QtGui
6 from PyQt4 import QtCore, QtGui
7 import zmq
7 import zmq
8
8
9 # Local imports
9 # Local imports
10 from IPython.core.inputsplitter import InputSplitter
10 from IPython.core.inputsplitter import InputSplitter
11 from call_tip_widget import CallTipWidget
11 from call_tip_widget import CallTipWidget
12 from completion_lexer import CompletionLexer
12 from completion_lexer import CompletionLexer
13 from console_widget import HistoryConsoleWidget
13 from console_widget import HistoryConsoleWidget
14 from pygments_highlighter import PygmentsHighlighter
14 from pygments_highlighter import PygmentsHighlighter
15
15
16
16
17 class FrontendHighlighter(PygmentsHighlighter):
17 class FrontendHighlighter(PygmentsHighlighter):
18 """ A Python PygmentsHighlighter that can be turned on and off and which
18 """ A Python PygmentsHighlighter that can be turned on and off and which
19 knows about continuation prompts.
19 knows about continuation prompts.
20 """
20 """
21
21
22 def __init__(self, frontend):
22 def __init__(self, frontend):
23 PygmentsHighlighter.__init__(self, frontend.document(), PythonLexer())
23 PygmentsHighlighter.__init__(self, frontend.document(), PythonLexer())
24 self._current_offset = 0
24 self._current_offset = 0
25 self._frontend = frontend
25 self._frontend = frontend
26 self.highlighting_on = False
26 self.highlighting_on = False
27
27
28 def highlightBlock(self, qstring):
28 def highlightBlock(self, qstring):
29 """ Highlight a block of text. Reimplemented to highlight selectively.
29 """ Highlight a block of text. Reimplemented to highlight selectively.
30 """
30 """
31 if self.highlighting_on:
31 if self.highlighting_on:
32 for prompt in (self._frontend._continuation_prompt,
32 for prompt in (self._frontend._continuation_prompt,
33 self._frontend._prompt):
33 self._frontend._prompt):
34 if qstring.startsWith(prompt):
34 if qstring.startsWith(prompt):
35 qstring.remove(0, len(prompt))
35 qstring.remove(0, len(prompt))
36 self._current_offset = len(prompt)
36 self._current_offset = len(prompt)
37 break
37 break
38 PygmentsHighlighter.highlightBlock(self, qstring)
38 PygmentsHighlighter.highlightBlock(self, qstring)
39
39
40 def setFormat(self, start, count, format):
40 def setFormat(self, start, count, format):
41 """ Reimplemented to avoid highlighting continuation prompts.
41 """ Reimplemented to avoid highlighting continuation prompts.
42 """
42 """
43 start += self._current_offset
43 start += self._current_offset
44 PygmentsHighlighter.setFormat(self, start, count, format)
44 PygmentsHighlighter.setFormat(self, start, count, format)
45
45
46
46
47 class FrontendWidget(HistoryConsoleWidget):
47 class FrontendWidget(HistoryConsoleWidget):
48 """ A Qt frontend for a generic Python kernel.
48 """ A Qt frontend for a generic Python kernel.
49 """
49 """
50
50
51 # Emitted when an 'execute_reply' is received from the kernel.
51 # Emitted when an 'execute_reply' is received from the kernel.
52 executed = QtCore.pyqtSignal(object)
52 executed = QtCore.pyqtSignal(object)
53
53
54 #---------------------------------------------------------------------------
54 #---------------------------------------------------------------------------
55 # 'QObject' interface
55 # 'QObject' interface
56 #---------------------------------------------------------------------------
56 #---------------------------------------------------------------------------
57
57
58 def __init__(self, parent=None):
58 def __init__(self, parent=None):
59 super(FrontendWidget, self).__init__(parent)
59 super(FrontendWidget, self).__init__(parent)
60
60
61 # ConsoleWidget protected variables.
61 # ConsoleWidget protected variables.
62 self._continuation_prompt = '... '
62 self._continuation_prompt = '... '
63 self._prompt = '>>> '
63 self._prompt = '>>> '
64
64
65 # FrontendWidget protected variables.
65 # FrontendWidget protected variables.
66 self._call_tip_widget = CallTipWidget(self)
66 self._call_tip_widget = CallTipWidget(self)
67 self._completion_lexer = CompletionLexer(PythonLexer())
67 self._completion_lexer = CompletionLexer(PythonLexer())
68 self._hidden = True
68 self._hidden = True
69 self._highlighter = FrontendHighlighter(self)
69 self._highlighter = FrontendHighlighter(self)
70 self._input_splitter = InputSplitter(input_mode='replace')
70 self._input_splitter = InputSplitter(input_mode='replace')
71 self._kernel_manager = None
71 self._kernel_manager = None
72
72
73 self.document().contentsChange.connect(self._document_contents_change)
73 self.document().contentsChange.connect(self._document_contents_change)
74
74
75 #---------------------------------------------------------------------------
75 #---------------------------------------------------------------------------
76 # 'QWidget' interface
76 # 'QWidget' interface
77 #---------------------------------------------------------------------------
77 #---------------------------------------------------------------------------
78
78
79 def focusOutEvent(self, event):
79 def focusOutEvent(self, event):
80 """ Reimplemented to hide calltips.
80 """ Reimplemented to hide calltips.
81 """
81 """
82 self._call_tip_widget.hide()
82 self._call_tip_widget.hide()
83 super(FrontendWidget, self).focusOutEvent(event)
83 super(FrontendWidget, self).focusOutEvent(event)
84
84
85 def keyPressEvent(self, event):
85 def keyPressEvent(self, event):
86 """ Reimplemented to allow calltips to process events and to send
86 """ Reimplemented to allow calltips to process events and to send
87 signals to the kernel.
87 signals to the kernel.
88 """
88 """
89 if self._executing and event.key() == QtCore.Qt.Key_C and \
89 if self._executing and event.key() == QtCore.Qt.Key_C and \
90 self._control_down(event.modifiers()):
90 self._control_down(event.modifiers()):
91 self._interrupt_kernel()
91 self._interrupt_kernel()
92 else:
92 else:
93 if self._call_tip_widget.isVisible():
93 if self._call_tip_widget.isVisible():
94 self._call_tip_widget.keyPressEvent(event)
94 self._call_tip_widget.keyPressEvent(event)
95 super(FrontendWidget, self).keyPressEvent(event)
95 super(FrontendWidget, self).keyPressEvent(event)
96
96
97 #---------------------------------------------------------------------------
97 #---------------------------------------------------------------------------
98 # 'ConsoleWidget' abstract interface
98 # 'ConsoleWidget' abstract interface
99 #---------------------------------------------------------------------------
99 #---------------------------------------------------------------------------
100
100
101 def _is_complete(self, source, interactive):
101 def _is_complete(self, source, interactive):
102 """ Returns whether 'source' can be completely processed and a new
102 """ Returns whether 'source' can be completely processed and a new
103 prompt created. When triggered by an Enter/Return key press,
103 prompt created. When triggered by an Enter/Return key press,
104 'interactive' is True; otherwise, it is False.
104 'interactive' is True; otherwise, it is False.
105 """
105 """
106 complete = self._input_splitter.push(source)
106 complete = self._input_splitter.push(source)
107 if interactive:
107 if interactive:
108 complete = not self._input_splitter.push_accepts_more()
108 complete = not self._input_splitter.push_accepts_more()
109 return complete
109 return complete
110
110
111 def _execute(self, source, hidden):
111 def _execute(self, source, hidden):
112 """ Execute 'source'. If 'hidden', do not show any output.
112 """ Execute 'source'. If 'hidden', do not show any output.
113 """
113 """
114 self.kernel_manager.xreq_channel.execute(source)
114 self.kernel_manager.xreq_channel.execute(source)
115 self._hidden = hidden
115 self._hidden = hidden
116
116
117 def _prompt_started_hook(self):
117 def _prompt_started_hook(self):
118 """ Called immediately after a new prompt is displayed.
118 """ Called immediately after a new prompt is displayed.
119 """
119 """
120 self._highlighter.highlighting_on = True
120 self._highlighter.highlighting_on = True
121
121
122 # Auto-indent if this is a continuation prompt.
122 # Auto-indent if this is a continuation prompt.
123 if self._get_prompt_cursor().blockNumber() != \
123 if self._get_prompt_cursor().blockNumber() != \
124 self._get_end_cursor().blockNumber():
124 self._get_end_cursor().blockNumber():
125 self.appendPlainText(' ' * self._input_splitter.indent_spaces)
125 self.appendPlainText(' ' * self._input_splitter.indent_spaces)
126
126
127 def _prompt_finished_hook(self):
127 def _prompt_finished_hook(self):
128 """ Called immediately after a prompt is finished, i.e. when some input
128 """ Called immediately after a prompt is finished, i.e. when some input
129 will be processed and a new prompt displayed.
129 will be processed and a new prompt displayed.
130 """
130 """
131 self._highlighter.highlighting_on = False
131 self._highlighter.highlighting_on = False
132
132
133 def _tab_pressed(self):
133 def _tab_pressed(self):
134 """ Called when the tab key is pressed. Returns whether to continue
134 """ Called when the tab key is pressed. Returns whether to continue
135 processing the event.
135 processing the event.
136 """
136 """
137 self._keep_cursor_in_buffer()
137 self._keep_cursor_in_buffer()
138 cursor = self.textCursor()
138 cursor = self.textCursor()
139 if not self._complete():
139 if not self._complete():
140 cursor.insertText(' ')
140 cursor.insertText(' ')
141 return False
141 return False
142
142
143 #---------------------------------------------------------------------------
143 #---------------------------------------------------------------------------
144 # 'FrontendWidget' interface
144 # 'FrontendWidget' interface
145 #---------------------------------------------------------------------------
145 #---------------------------------------------------------------------------
146
146
147 def execute_file(self, path, hidden=False):
147 def execute_file(self, path, hidden=False):
148 """ Attempts to execute file with 'path'. If 'hidden', no output is
148 """ Attempts to execute file with 'path'. If 'hidden', no output is
149 shown.
149 shown.
150 """
150 """
151 self.execute('execfile("%s")' % path, hidden=hidden)
151 self.execute('execfile("%s")' % path, hidden=hidden)
152
152
153 def _get_kernel_manager(self):
153 def _get_kernel_manager(self):
154 """ Returns the current kernel manager.
154 """ Returns the current kernel manager.
155 """
155 """
156 return self._kernel_manager
156 return self._kernel_manager
157
157
158 def _set_kernel_manager(self, kernel_manager):
158 def _set_kernel_manager(self, kernel_manager):
159 """ Disconnect from the current kernel manager (if any) and set a new
159 """ Disconnect from the current kernel manager (if any) and set a new
160 kernel manager.
160 kernel manager.
161 """
161 """
162 # Disconnect the old kernel manager, if necessary.
162 # Disconnect the old kernel manager, if necessary.
163 if self._kernel_manager is not None:
163 if self._kernel_manager is not None:
164 self._kernel_manager.started_channels.disconnect(
164 self._kernel_manager.started_channels.disconnect(
165 self._started_channels)
165 self._started_channels)
166 self._kernel_manager.stopped_channels.disconnect(
166 self._kernel_manager.stopped_channels.disconnect(
167 self._stopped_channels)
167 self._stopped_channels)
168
168
169 # Disconnect the old kernel manager's channels.
169 # Disconnect the old kernel manager's channels.
170 sub = self._kernel_manager.sub_channel
170 sub = self._kernel_manager.sub_channel
171 xreq = self._kernel_manager.xreq_channel
171 xreq = self._kernel_manager.xreq_channel
172 sub.message_received.disconnect(self._handle_sub)
172 sub.message_received.disconnect(self._handle_sub)
173 xreq.execute_reply.disconnect(self._handle_execute_reply)
173 xreq.execute_reply.disconnect(self._handle_execute_reply)
174 xreq.complete_reply.disconnect(self._handle_complete_reply)
174 xreq.complete_reply.disconnect(self._handle_complete_reply)
175 xreq.object_info_reply.disconnect(self._handle_object_info_reply)
175 xreq.object_info_reply.disconnect(self._handle_object_info_reply)
176
176
177 # Handle the case where the old kernel manager is still channels.
177 # Handle the case where the old kernel manager is still listening.
178 if self._kernel_manager.channels_running:
178 if self._kernel_manager.channels_running:
179 self._stopped_channels()
179 self._stopped_channels()
180
180
181 # Set the new kernel manager.
181 # Set the new kernel manager.
182 self._kernel_manager = kernel_manager
182 self._kernel_manager = kernel_manager
183 if kernel_manager is None:
183 if kernel_manager is None:
184 return
184 return
185
185
186 # Connect the new kernel manager.
186 # Connect the new kernel manager.
187 kernel_manager.started_channels.connect(self._started_channels)
187 kernel_manager.started_channels.connect(self._started_channels)
188 kernel_manager.stopped_channels.connect(self._stopped_channels)
188 kernel_manager.stopped_channels.connect(self._stopped_channels)
189
189
190 # Connect the new kernel manager's channels.
190 # Connect the new kernel manager's channels.
191 sub = kernel_manager.sub_channel
191 sub = kernel_manager.sub_channel
192 xreq = kernel_manager.xreq_channel
192 xreq = kernel_manager.xreq_channel
193 sub.message_received.connect(self._handle_sub)
193 sub.message_received.connect(self._handle_sub)
194 xreq.execute_reply.connect(self._handle_execute_reply)
194 xreq.execute_reply.connect(self._handle_execute_reply)
195 xreq.complete_reply.connect(self._handle_complete_reply)
195 xreq.complete_reply.connect(self._handle_complete_reply)
196 xreq.object_info_reply.connect(self._handle_object_info_reply)
196 xreq.object_info_reply.connect(self._handle_object_info_reply)
197
197
198 # Handle the case where the kernel manager started channels before
198 # Handle the case where the kernel manager started channels before
199 # we connected.
199 # we connected.
200 if kernel_manager.channels_running:
200 if kernel_manager.channels_running:
201 self._started_channels()
201 self._started_channels()
202
202
203 kernel_manager = property(_get_kernel_manager, _set_kernel_manager)
203 kernel_manager = property(_get_kernel_manager, _set_kernel_manager)
204
204
205 #---------------------------------------------------------------------------
205 #---------------------------------------------------------------------------
206 # 'FrontendWidget' protected interface
206 # 'FrontendWidget' protected interface
207 #---------------------------------------------------------------------------
207 #---------------------------------------------------------------------------
208
208
209 def _call_tip(self):
209 def _call_tip(self):
210 """ Shows a call tip, if appropriate, at the current cursor location.
210 """ Shows a call tip, if appropriate, at the current cursor location.
211 """
211 """
212 # Decide if it makes sense to show a call tip
212 # Decide if it makes sense to show a call tip
213 cursor = self.textCursor()
213 cursor = self.textCursor()
214 cursor.movePosition(QtGui.QTextCursor.Left)
214 cursor.movePosition(QtGui.QTextCursor.Left)
215 document = self.document()
215 document = self.document()
216 if document.characterAt(cursor.position()).toAscii() != '(':
216 if document.characterAt(cursor.position()).toAscii() != '(':
217 return False
217 return False
218 context = self._get_context(cursor)
218 context = self._get_context(cursor)
219 if not context:
219 if not context:
220 return False
220 return False
221
221
222 # Send the metadata request to the kernel
222 # Send the metadata request to the kernel
223 name = '.'.join(context)
223 name = '.'.join(context)
224 self._calltip_id = self.kernel_manager.xreq_channel.object_info(name)
224 self._calltip_id = self.kernel_manager.xreq_channel.object_info(name)
225 self._calltip_pos = self.textCursor().position()
225 self._calltip_pos = self.textCursor().position()
226 return True
226 return True
227
227
228 def _complete(self):
228 def _complete(self):
229 """ Performs completion at the current cursor location.
229 """ Performs completion at the current cursor location.
230 """
230 """
231 # Decide if it makes sense to do completion
231 # Decide if it makes sense to do completion
232 context = self._get_context()
232 context = self._get_context()
233 if not context:
233 if not context:
234 return False
234 return False
235
235
236 # Send the completion request to the kernel
236 # Send the completion request to the kernel
237 text = '.'.join(context)
237 text = '.'.join(context)
238 self._complete_id = self.kernel_manager.xreq_channel.complete(
238 self._complete_id = self.kernel_manager.xreq_channel.complete(
239 text, self.input_buffer_cursor_line, self.input_buffer)
239 text, self.input_buffer_cursor_line, self.input_buffer)
240 self._complete_pos = self.textCursor().position()
240 self._complete_pos = self.textCursor().position()
241 return True
241 return True
242
242
243 def _get_context(self, cursor=None):
243 def _get_context(self, cursor=None):
244 """ Gets the context at the current cursor location.
244 """ Gets the context at the current cursor location.
245 """
245 """
246 if cursor is None:
246 if cursor is None:
247 cursor = self.textCursor()
247 cursor = self.textCursor()
248 cursor.movePosition(QtGui.QTextCursor.StartOfLine,
248 cursor.movePosition(QtGui.QTextCursor.StartOfLine,
249 QtGui.QTextCursor.KeepAnchor)
249 QtGui.QTextCursor.KeepAnchor)
250 text = unicode(cursor.selectedText())
250 text = unicode(cursor.selectedText())
251 return self._completion_lexer.get_context(text)
251 return self._completion_lexer.get_context(text)
252
252
253 def _interrupt_kernel(self):
253 def _interrupt_kernel(self):
254 """ Attempts to the interrupt the kernel.
254 """ Attempts to the interrupt the kernel.
255 """
255 """
256 if self.kernel_manager.has_kernel:
256 if self.kernel_manager.has_kernel:
257 self.kernel_manager.signal_kernel(signal.SIGINT)
257 self.kernel_manager.signal_kernel(signal.SIGINT)
258 else:
258 else:
259 self.appendPlainText('Kernel process is either remote or '
259 self.appendPlainText('Kernel process is either remote or '
260 'unspecified. Cannot interrupt.\n')
260 'unspecified. Cannot interrupt.\n')
261
261
262 #------ Signal handlers ----------------------------------------------------
262 #------ Signal handlers ----------------------------------------------------
263
263
264 def _document_contents_change(self, position, removed, added):
264 def _document_contents_change(self, position, removed, added):
265 """ Called whenever the document's content changes. Display a calltip
265 """ Called whenever the document's content changes. Display a calltip
266 if appropriate.
266 if appropriate.
267 """
267 """
268 # Calculate where the cursor should be *after* the change:
268 # Calculate where the cursor should be *after* the change:
269 position += added
269 position += added
270
270
271 document = self.document()
271 document = self.document()
272 if position == self.textCursor().position():
272 if position == self.textCursor().position():
273 self._call_tip()
273 self._call_tip()
274
274
275 def _handle_sub(self, omsg):
275 def _handle_sub(self, omsg):
276 if self._hidden:
276 if self._hidden:
277 return
277 return
278 handler = getattr(self, '_handle_%s' % omsg['msg_type'], None)
278 handler = getattr(self, '_handle_%s' % omsg['msg_type'], None)
279 if handler is not None:
279 if handler is not None:
280 handler(omsg)
280 handler(omsg)
281
281
282 def _handle_pyout(self, omsg):
282 def _handle_pyout(self, omsg):
283 session = omsg['parent_header']['session']
283 session = omsg['parent_header']['session']
284 if session == self.kernel_manager.session.session:
284 if session == self.kernel_manager.session.session:
285 self.appendPlainText(omsg['content']['data'] + '\n')
285 self.appendPlainText(omsg['content']['data'] + '\n')
286
286
287 def _handle_stream(self, omsg):
287 def _handle_stream(self, omsg):
288 self.appendPlainText(omsg['content']['data'])
288 self.appendPlainText(omsg['content']['data'])
289 self.moveCursor(QtGui.QTextCursor.End)
289 self.moveCursor(QtGui.QTextCursor.End)
290
290
291 def _handle_execute_reply(self, rep):
291 def _handle_execute_reply(self, rep):
292 if self._hidden:
292 if self._hidden:
293 return
293 return
294
294
295 # Make sure that all output from the SUB channel has been processed
295 # Make sure that all output from the SUB channel has been processed
296 # before writing a new prompt.
296 # before writing a new prompt.
297 self.kernel_manager.sub_channel.flush()
297 self.kernel_manager.sub_channel.flush()
298
298
299 content = rep['content']
299 content = rep['content']
300 status = content['status']
300 status = content['status']
301 if status == 'error':
301 if status == 'error':
302 self.appendPlainText(content['traceback'][-1])
302 self.appendPlainText(content['traceback'][-1])
303 elif status == 'aborted':
303 elif status == 'aborted':
304 text = "ERROR: ABORTED\n"
304 text = "ERROR: ABORTED\n"
305 self.appendPlainText(text)
305 self.appendPlainText(text)
306 self._hidden = True
306 self._hidden = True
307 self._show_prompt()
307 self._show_prompt()
308 self.executed.emit(rep)
308 self.executed.emit(rep)
309
309
310 def _handle_complete_reply(self, rep):
310 def _handle_complete_reply(self, rep):
311 cursor = self.textCursor()
311 cursor = self.textCursor()
312 if rep['parent_header']['msg_id'] == self._complete_id and \
312 if rep['parent_header']['msg_id'] == self._complete_id and \
313 cursor.position() == self._complete_pos:
313 cursor.position() == self._complete_pos:
314 text = '.'.join(self._get_context())
314 text = '.'.join(self._get_context())
315 cursor.movePosition(QtGui.QTextCursor.Left, n=len(text))
315 cursor.movePosition(QtGui.QTextCursor.Left, n=len(text))
316 self._complete_with_items(cursor, rep['content']['matches'])
316 self._complete_with_items(cursor, rep['content']['matches'])
317
317
318 def _handle_object_info_reply(self, rep):
318 def _handle_object_info_reply(self, rep):
319 cursor = self.textCursor()
319 cursor = self.textCursor()
320 if rep['parent_header']['msg_id'] == self._calltip_id and \
320 if rep['parent_header']['msg_id'] == self._calltip_id and \
321 cursor.position() == self._calltip_pos:
321 cursor.position() == self._calltip_pos:
322 doc = rep['content']['docstring']
322 doc = rep['content']['docstring']
323 if doc:
323 if doc:
324 self._call_tip_widget.show_docstring(doc)
324 self._call_tip_widget.show_docstring(doc)
325
325
326 def _started_channels(self):
326 def _started_channels(self):
327 self.clear()
327 self.clear()
328
328
329 def _stopped_channels(self):
329 def _stopped_channels(self):
330 pass
330 pass
@@ -1,151 +1,150 b''
1 """ Defines a KernelManager that provides signals and slots.
1 """ Defines a KernelManager that provides signals and slots.
2 """
2 """
3
3
4 # System library imports.
4 # System library imports.
5 from PyQt4 import QtCore
5 from PyQt4 import QtCore
6 import zmq
6 import zmq
7
7
8 # IPython imports.
8 # IPython imports.
9 from IPython.zmq.kernelmanager import KernelManager, SubSocketChannel, \
9 from IPython.zmq.kernelmanager import KernelManager, SubSocketChannel, \
10 XReqSocketChannel, RepSocketChannel
10 XReqSocketChannel, RepSocketChannel
11 from util import MetaQObjectHasTraits
11 from util import MetaQObjectHasTraits
12
12
13
13
14
15 class QtSubSocketChannel(SubSocketChannel, QtCore.QObject):
14 class QtSubSocketChannel(SubSocketChannel, QtCore.QObject):
16
15
17 # Emitted when any message is received.
16 # Emitted when any message is received.
18 message_received = QtCore.pyqtSignal(object)
17 message_received = QtCore.pyqtSignal(object)
19
18
20 # Emitted when a message of type 'pyout' or 'stdout' is received.
19 # Emitted when a message of type 'pyout' or 'stdout' is received.
21 output_received = QtCore.pyqtSignal(object)
20 output_received = QtCore.pyqtSignal(object)
22
21
23 # Emitted when a message of type 'pyerr' or 'stderr' is received.
22 # Emitted when a message of type 'pyerr' or 'stderr' is received.
24 error_received = QtCore.pyqtSignal(object)
23 error_received = QtCore.pyqtSignal(object)
25
24
26 #---------------------------------------------------------------------------
25 #---------------------------------------------------------------------------
27 # 'object' interface
26 # 'object' interface
28 #---------------------------------------------------------------------------
27 #---------------------------------------------------------------------------
29
28
30 def __init__(self, *args, **kw):
29 def __init__(self, *args, **kw):
31 """ Reimplemented to ensure that QtCore.QObject is initialized first.
30 """ Reimplemented to ensure that QtCore.QObject is initialized first.
32 """
31 """
33 QtCore.QObject.__init__(self)
32 QtCore.QObject.__init__(self)
34 SubSocketChannel.__init__(self, *args, **kw)
33 SubSocketChannel.__init__(self, *args, **kw)
35
34
36 #---------------------------------------------------------------------------
35 #---------------------------------------------------------------------------
37 # 'SubSocketChannel' interface
36 # 'SubSocketChannel' interface
38 #---------------------------------------------------------------------------
37 #---------------------------------------------------------------------------
39
38
40 def call_handlers(self, msg):
39 def call_handlers(self, msg):
41 """ Reimplemented to emit signals instead of making callbacks.
40 """ Reimplemented to emit signals instead of making callbacks.
42 """
41 """
43 # Emit the generic signal.
42 # Emit the generic signal.
44 self.message_received.emit(msg)
43 self.message_received.emit(msg)
45
44
46 # Emit signals for specialized message types.
45 # Emit signals for specialized message types.
47 msg_type = msg['msg_type']
46 msg_type = msg['msg_type']
48 if msg_type in ('pyout', 'stdout'):
47 if msg_type in ('pyout', 'stdout'):
49 self.output_received.emit(msg)
48 self.output_received.emit(msg)
50 elif msg_type in ('pyerr', 'stderr'):
49 elif msg_type in ('pyerr', 'stderr'):
51 self.error_received.emit(msg)
50 self.error_received.emit(msg)
52
51
53 def flush(self):
52 def flush(self):
54 """ Reimplemented to ensure that signals are dispatched immediately.
53 """ Reimplemented to ensure that signals are dispatched immediately.
55 """
54 """
56 super(QtSubSocketChannel, self).flush()
55 super(QtSubSocketChannel, self).flush()
57 QtCore.QCoreApplication.instance().processEvents()
56 QtCore.QCoreApplication.instance().processEvents()
58
57
59
58
60 class QtXReqSocketChannel(XReqSocketChannel, QtCore.QObject):
59 class QtXReqSocketChannel(XReqSocketChannel, QtCore.QObject):
61
60
62 # Emitted when any message is received.
61 # Emitted when any message is received.
63 message_received = QtCore.pyqtSignal(object)
62 message_received = QtCore.pyqtSignal(object)
64
63
65 # Emitted when a reply has been received for the corresponding request type.
64 # Emitted when a reply has been received for the corresponding request type.
66 execute_reply = QtCore.pyqtSignal(object)
65 execute_reply = QtCore.pyqtSignal(object)
67 complete_reply = QtCore.pyqtSignal(object)
66 complete_reply = QtCore.pyqtSignal(object)
68 object_info_reply = QtCore.pyqtSignal(object)
67 object_info_reply = QtCore.pyqtSignal(object)
69
68
70 #---------------------------------------------------------------------------
69 #---------------------------------------------------------------------------
71 # 'object' interface
70 # 'object' interface
72 #---------------------------------------------------------------------------
71 #---------------------------------------------------------------------------
73
72
74 def __init__(self, *args, **kw):
73 def __init__(self, *args, **kw):
75 """ Reimplemented to ensure that QtCore.QObject is initialized first.
74 """ Reimplemented to ensure that QtCore.QObject is initialized first.
76 """
75 """
77 QtCore.QObject.__init__(self)
76 QtCore.QObject.__init__(self)
78 XReqSocketChannel.__init__(self, *args, **kw)
77 XReqSocketChannel.__init__(self, *args, **kw)
79
78
80 #---------------------------------------------------------------------------
79 #---------------------------------------------------------------------------
81 # 'XReqSocketChannel' interface
80 # 'XReqSocketChannel' interface
82 #---------------------------------------------------------------------------
81 #---------------------------------------------------------------------------
83
82
84 def call_handlers(self, msg):
83 def call_handlers(self, msg):
85 """ Reimplemented to emit signals instead of making callbacks.
84 """ Reimplemented to emit signals instead of making callbacks.
86 """
85 """
87 # Emit the generic signal.
86 # Emit the generic signal.
88 self.message_received.emit(msg)
87 self.message_received.emit(msg)
89
88
90 # Emit signals for specialized message types.
89 # Emit signals for specialized message types.
91 msg_type = msg['msg_type']
90 msg_type = msg['msg_type']
92 signal = getattr(self, msg_type, None)
91 signal = getattr(self, msg_type, None)
93 if signal:
92 if signal:
94 signal.emit(msg)
93 signal.emit(msg)
95
94
96
95
97 class QtRepSocketChannel(RepSocketChannel, QtCore.QObject):
96 class QtRepSocketChannel(RepSocketChannel, QtCore.QObject):
98
97
99 #---------------------------------------------------------------------------
98 #---------------------------------------------------------------------------
100 # 'object' interface
99 # 'object' interface
101 #---------------------------------------------------------------------------
100 #---------------------------------------------------------------------------
102
101
103 def __init__(self, *args, **kw):
102 def __init__(self, *args, **kw):
104 """ Reimplemented to ensure that QtCore.QObject is initialized first.
103 """ Reimplemented to ensure that QtCore.QObject is initialized first.
105 """
104 """
106 QtCore.QObject.__init__(self)
105 QtCore.QObject.__init__(self)
107 RepSocketChannel.__init__(self, *args, **kw)
106 RepSocketChannel.__init__(self, *args, **kw)
108
107
109
108
110 class QtKernelManager(KernelManager, QtCore.QObject):
109 class QtKernelManager(KernelManager, QtCore.QObject):
111 """ A KernelManager that provides signals and slots.
110 """ A KernelManager that provides signals and slots.
112 """
111 """
113
112
114 __metaclass__ = MetaQObjectHasTraits
113 __metaclass__ = MetaQObjectHasTraits
115
114
116 # Emitted when the kernel manager has started listening.
115 # Emitted when the kernel manager has started listening.
117 started_channels = QtCore.pyqtSignal()
116 started_channels = QtCore.pyqtSignal()
118
117
119 # Emitted when the kernel manager has stopped listening.
118 # Emitted when the kernel manager has stopped listening.
120 stopped_channels = QtCore.pyqtSignal()
119 stopped_channels = QtCore.pyqtSignal()
121
120
122 # Use Qt-specific channel classes that emit signals.
121 # Use Qt-specific channel classes that emit signals.
123 sub_channel_class = QtSubSocketChannel
122 sub_channel_class = QtSubSocketChannel
124 xreq_channel_class = QtXReqSocketChannel
123 xreq_channel_class = QtXReqSocketChannel
125 rep_channel_class = QtRepSocketChannel
124 rep_channel_class = QtRepSocketChannel
126
125
127 #---------------------------------------------------------------------------
126 #---------------------------------------------------------------------------
128 # 'object' interface
127 # 'object' interface
129 #---------------------------------------------------------------------------
128 #---------------------------------------------------------------------------
130
129
131 def __init__(self, *args, **kw):
130 def __init__(self, *args, **kw):
132 """ Reimplemented to ensure that QtCore.QObject is initialized first.
131 """ Reimplemented to ensure that QtCore.QObject is initialized first.
133 """
132 """
134 QtCore.QObject.__init__(self)
133 QtCore.QObject.__init__(self)
135 KernelManager.__init__(self, *args, **kw)
134 KernelManager.__init__(self, *args, **kw)
136
135
137 #---------------------------------------------------------------------------
136 #---------------------------------------------------------------------------
138 # 'KernelManager' interface
137 # 'KernelManager' interface
139 #---------------------------------------------------------------------------
138 #---------------------------------------------------------------------------
140
139
141 def start_channels(self):
140 def start_channels(self):
142 """ Reimplemented to emit signal.
141 """ Reimplemented to emit signal.
143 """
142 """
144 super(QtKernelManager, self).start_channels()
143 super(QtKernelManager, self).start_channels()
145 self.started_channels.emit()
144 self.started_channels.emit()
146
145
147 def stop_channels(self):
146 def stop_channels(self):
148 """ Reimplemented to emit signal.
147 """ Reimplemented to emit signal.
149 """
148 """
150 super(QtKernelManager, self).stop_channels()
149 super(QtKernelManager, self).stop_channels()
151 self.stopped_channels.emit()
150 self.stopped_channels.emit()
@@ -1,25 +1,22 b''
1 """ Defines miscellaneous Qt-related helper classes and functions.
1 """ Defines miscellaneous Qt-related helper classes and functions.
2 """
2 """
3
3
4 # System library imports.
4 # System library imports.
5 from PyQt4 import QtCore
5 from PyQt4 import QtCore
6
6
7 # IPython imports.
7 # IPython imports.
8 from IPython.utils.traitlets import HasTraits
8 from IPython.utils.traitlets import HasTraits
9
9
10
10
11 MetaHasTraits = type(HasTraits)
11 MetaHasTraits = type(HasTraits)
12 MetaQObject = type(QtCore.QObject)
12 MetaQObject = type(QtCore.QObject)
13
13
14 class MetaQObjectHasTraits(MetaQObject, MetaHasTraits):
14 class MetaQObjectHasTraits(MetaQObject, MetaHasTraits):
15 """ A metaclass that inherits from the metaclasses of both HasTraits and
15 """ A metaclass that inherits from the metaclasses of both HasTraits and
16 QObject.
16 QObject.
17
17
18 Using this metaclass allows a class to inherit from both HasTraits and
18 Using this metaclass allows a class to inherit from both HasTraits and
19 QObject. See QtKernelManager for an example.
19 QObject. See QtKernelManager for an example.
20 """
20 """
21
21 pass
22 def __init__(cls, name, bases, dct):
23 MetaQObject.__init__(cls, name, bases, dct)
24 MetaHasTraits.__init__(cls, name, bases, dct)
25
22
@@ -1,401 +1,409 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 * Finish implementing `raw_input`.
6 * Finish implementing `raw_input`.
7 * Implement `set_parent` logic. Right before doing exec, the Kernel should
7 * Implement `set_parent` logic. Right before doing exec, the Kernel should
8 call set_parent on all the PUB objects with the message about to be executed.
8 call set_parent on all the PUB objects with the message about to be executed.
9 * Implement random port and security key logic.
9 * Implement random port and security key logic.
10 * Implement control messages.
10 * Implement control messages.
11 * Implement event loop and poll version.
11 * Implement event loop and poll version.
12 """
12 """
13
13
14 # Standard library imports.
14 # Standard library imports.
15 import __builtin__
15 import __builtin__
16 import os
16 import os
17 import sys
17 import sys
18 import time
18 import time
19 import traceback
19 import traceback
20 from code import CommandCompiler
20 from code import CommandCompiler
21
21
22 # System library imports.
22 # System library imports.
23 import zmq
23 import zmq
24
24
25 # Local imports.
25 # Local imports.
26 from IPython.external.argparse import ArgumentParser
26 from IPython.external.argparse import ArgumentParser
27 from session import Session, Message, extract_header
27 from session import Session, Message, extract_header
28 from completer import KernelCompleter
28 from completer import KernelCompleter
29
29
30
30
31 class OutStream(object):
31 class OutStream(object):
32 """A file like object that publishes the stream to a 0MQ PUB socket."""
32 """A file like object that publishes the stream to a 0MQ PUB socket."""
33
33
34 def __init__(self, session, pub_socket, name, max_buffer=200):
34 def __init__(self, session, pub_socket, name, max_buffer=200):
35 self.session = session
35 self.session = session
36 self.pub_socket = pub_socket
36 self.pub_socket = pub_socket
37 self.name = name
37 self.name = name
38 self._buffer = []
38 self._buffer = []
39 self._buffer_len = 0
39 self._buffer_len = 0
40 self.max_buffer = max_buffer
40 self.max_buffer = max_buffer
41 self.parent_header = {}
41 self.parent_header = {}
42
42
43 def set_parent(self, parent):
43 def set_parent(self, parent):
44 self.parent_header = extract_header(parent)
44 self.parent_header = extract_header(parent)
45
45
46 def close(self):
46 def close(self):
47 self.pub_socket = None
47 self.pub_socket = None
48
48
49 def flush(self):
49 def flush(self):
50 if self.pub_socket is None:
50 if self.pub_socket is None:
51 raise ValueError(u'I/O operation on closed file')
51 raise ValueError(u'I/O operation on closed file')
52 else:
52 else:
53 if self._buffer:
53 if self._buffer:
54 data = ''.join(self._buffer)
54 data = ''.join(self._buffer)
55 content = {u'name':self.name, u'data':data}
55 content = {u'name':self.name, u'data':data}
56 msg = self.session.msg(u'stream', content=content,
56 msg = self.session.msg(u'stream', content=content,
57 parent=self.parent_header)
57 parent=self.parent_header)
58 print>>sys.__stdout__, Message(msg)
58 print>>sys.__stdout__, Message(msg)
59 self.pub_socket.send_json(msg)
59 self.pub_socket.send_json(msg)
60 self._buffer_len = 0
60 self._buffer_len = 0
61 self._buffer = []
61 self._buffer = []
62
62
63 def isattr(self):
63 def isattr(self):
64 return False
64 return False
65
65
66 def next(self):
66 def next(self):
67 raise IOError('Read not supported on a write only stream.')
67 raise IOError('Read not supported on a write only stream.')
68
68
69 def read(self, size=None):
69 def read(self, size=None):
70 raise IOError('Read not supported on a write only stream.')
70 raise IOError('Read not supported on a write only stream.')
71
71
72 readline=read
72 readline=read
73
73
74 def write(self, s):
74 def write(self, s):
75 if self.pub_socket is None:
75 if self.pub_socket is None:
76 raise ValueError('I/O operation on closed file')
76 raise ValueError('I/O operation on closed file')
77 else:
77 else:
78 self._buffer.append(s)
78 self._buffer.append(s)
79 self._buffer_len += len(s)
79 self._buffer_len += len(s)
80 self._maybe_send()
80 self._maybe_send()
81
81
82 def _maybe_send(self):
82 def _maybe_send(self):
83 if '\n' in self._buffer[-1]:
83 if '\n' in self._buffer[-1]:
84 self.flush()
84 self.flush()
85 if self._buffer_len > self.max_buffer:
85 if self._buffer_len > self.max_buffer:
86 self.flush()
86 self.flush()
87
87
88 def writelines(self, sequence):
88 def writelines(self, sequence):
89 if self.pub_socket is None:
89 if self.pub_socket is None:
90 raise ValueError('I/O operation on closed file')
90 raise ValueError('I/O operation on closed file')
91 else:
91 else:
92 for s in sequence:
92 for s in sequence:
93 self.write(s)
93 self.write(s)
94
94
95
95
96 class DisplayHook(object):
96 class DisplayHook(object):
97
97
98 def __init__(self, session, pub_socket):
98 def __init__(self, session, pub_socket):
99 self.session = session
99 self.session = session
100 self.pub_socket = pub_socket
100 self.pub_socket = pub_socket
101 self.parent_header = {}
101 self.parent_header = {}
102
102
103 def __call__(self, obj):
103 def __call__(self, obj):
104 if obj is None:
104 if obj is None:
105 return
105 return
106
106
107 __builtin__._ = obj
107 __builtin__._ = obj
108 msg = self.session.msg(u'pyout', {u'data':repr(obj)},
108 msg = self.session.msg(u'pyout', {u'data':repr(obj)},
109 parent=self.parent_header)
109 parent=self.parent_header)
110 self.pub_socket.send_json(msg)
110 self.pub_socket.send_json(msg)
111
111
112 def set_parent(self, parent):
112 def set_parent(self, parent):
113 self.parent_header = extract_header(parent)
113 self.parent_header = extract_header(parent)
114
114
115
115
116 class RawInput(object):
116 class RawInput(object):
117
117
118 def __init__(self, session, socket):
118 def __init__(self, session, socket):
119 self.session = session
119 self.session = session
120 self.socket = socket
120 self.socket = socket
121
121
122 def __call__(self, prompt=None):
122 def __call__(self, prompt=None):
123 msg = self.session.msg(u'raw_input')
123 msg = self.session.msg(u'raw_input')
124 self.socket.send_json(msg)
124 self.socket.send_json(msg)
125 while True:
125 while True:
126 try:
126 try:
127 reply = self.socket.recv_json(zmq.NOBLOCK)
127 reply = self.socket.recv_json(zmq.NOBLOCK)
128 except zmq.ZMQError, e:
128 except zmq.ZMQError, e:
129 if e.errno == zmq.EAGAIN:
129 if e.errno == zmq.EAGAIN:
130 pass
130 pass
131 else:
131 else:
132 raise
132 raise
133 else:
133 else:
134 break
134 break
135 return reply[u'content'][u'data']
135 return reply[u'content'][u'data']
136
136
137
137
138 class Kernel(object):
138 class Kernel(object):
139
139
140 def __init__(self, session, reply_socket, pub_socket):
140 def __init__(self, session, reply_socket, pub_socket):
141 self.session = session
141 self.session = session
142 self.reply_socket = reply_socket
142 self.reply_socket = reply_socket
143 self.pub_socket = pub_socket
143 self.pub_socket = pub_socket
144 self.user_ns = {}
144 self.user_ns = {}
145 self.history = []
145 self.history = []
146 self.compiler = CommandCompiler()
146 self.compiler = CommandCompiler()
147 self.completer = KernelCompleter(self.user_ns)
147 self.completer = KernelCompleter(self.user_ns)
148 self.poll_ppid = False
148 self.poll_ppid = False
149
149
150 # Build dict of handlers for message types
150 # Build dict of handlers for message types
151 msg_types = [ 'execute_request', 'complete_request',
151 msg_types = [ 'execute_request', 'complete_request',
152 'object_info_request' ]
152 'object_info_request' ]
153 self.handlers = {}
153 self.handlers = {}
154 for msg_type in msg_types:
154 for msg_type in msg_types:
155 self.handlers[msg_type] = getattr(self, msg_type)
155 self.handlers[msg_type] = getattr(self, msg_type)
156
156
157 def abort_queue(self):
157 def abort_queue(self):
158 while True:
158 while True:
159 try:
159 try:
160 ident = self.reply_socket.recv(zmq.NOBLOCK)
160 ident = self.reply_socket.recv(zmq.NOBLOCK)
161 except zmq.ZMQError, e:
161 except zmq.ZMQError, e:
162 if e.errno == zmq.EAGAIN:
162 if e.errno == zmq.EAGAIN:
163 break
163 break
164 else:
164 else:
165 assert self.reply_socket.rcvmore(), "Unexpected missing message part."
165 assert self.reply_socket.rcvmore(), "Unexpected missing message part."
166 msg = self.reply_socket.recv_json()
166 msg = self.reply_socket.recv_json()
167 print>>sys.__stdout__, "Aborting:"
167 print>>sys.__stdout__, "Aborting:"
168 print>>sys.__stdout__, Message(msg)
168 print>>sys.__stdout__, Message(msg)
169 msg_type = msg['msg_type']
169 msg_type = msg['msg_type']
170 reply_type = msg_type.split('_')[0] + '_reply'
170 reply_type = msg_type.split('_')[0] + '_reply'
171 reply_msg = self.session.msg(reply_type, {'status' : 'aborted'}, msg)
171 reply_msg = self.session.msg(reply_type, {'status' : 'aborted'}, msg)
172 print>>sys.__stdout__, Message(reply_msg)
172 print>>sys.__stdout__, Message(reply_msg)
173 self.reply_socket.send(ident,zmq.SNDMORE)
173 self.reply_socket.send(ident,zmq.SNDMORE)
174 self.reply_socket.send_json(reply_msg)
174 self.reply_socket.send_json(reply_msg)
175 # We need to wait a bit for requests to come in. This can probably
175 # We need to wait a bit for requests to come in. This can probably
176 # be set shorter for true asynchronous clients.
176 # be set shorter for true asynchronous clients.
177 time.sleep(0.1)
177 time.sleep(0.1)
178
178
179 def execute_request(self, ident, parent):
179 def execute_request(self, ident, parent):
180 try:
180 try:
181 code = parent[u'content'][u'code']
181 code = parent[u'content'][u'code']
182 except:
182 except:
183 print>>sys.__stderr__, "Got bad msg: "
183 print>>sys.__stderr__, "Got bad msg: "
184 print>>sys.__stderr__, Message(parent)
184 print>>sys.__stderr__, Message(parent)
185 return
185 return
186 pyin_msg = self.session.msg(u'pyin',{u'code':code}, parent=parent)
186 pyin_msg = self.session.msg(u'pyin',{u'code':code}, parent=parent)
187 self.pub_socket.send_json(pyin_msg)
187 self.pub_socket.send_json(pyin_msg)
188 try:
188 try:
189 comp_code = self.compiler(code, '<zmq-kernel>')
189 comp_code = self.compiler(code, '<zmq-kernel>')
190 sys.displayhook.set_parent(parent)
190 sys.displayhook.set_parent(parent)
191 exec comp_code in self.user_ns, self.user_ns
191 exec comp_code in self.user_ns, self.user_ns
192 except:
192 except:
193 result = u'error'
193 result = u'error'
194 etype, evalue, tb = sys.exc_info()
194 etype, evalue, tb = sys.exc_info()
195 tb = traceback.format_exception(etype, evalue, tb)
195 tb = traceback.format_exception(etype, evalue, tb)
196 exc_content = {
196 exc_content = {
197 u'status' : u'error',
197 u'status' : u'error',
198 u'traceback' : tb,
198 u'traceback' : tb,
199 u'etype' : unicode(etype),
199 u'etype' : unicode(etype),
200 u'evalue' : unicode(evalue)
200 u'evalue' : unicode(evalue)
201 }
201 }
202 exc_msg = self.session.msg(u'pyerr', exc_content, parent)
202 exc_msg = self.session.msg(u'pyerr', exc_content, parent)
203 self.pub_socket.send_json(exc_msg)
203 self.pub_socket.send_json(exc_msg)
204 reply_content = exc_content
204 reply_content = exc_content
205 else:
205 else:
206 reply_content = {'status' : 'ok'}
206 reply_content = {'status' : 'ok'}
207 reply_msg = self.session.msg(u'execute_reply', reply_content, parent)
207 reply_msg = self.session.msg(u'execute_reply', reply_content, parent)
208 print>>sys.__stdout__, Message(reply_msg)
208 print>>sys.__stdout__, Message(reply_msg)
209 self.reply_socket.send(ident, zmq.SNDMORE)
209 self.reply_socket.send(ident, zmq.SNDMORE)
210 self.reply_socket.send_json(reply_msg)
210 self.reply_socket.send_json(reply_msg)
211 if reply_msg['content']['status'] == u'error':
211 if reply_msg['content']['status'] == u'error':
212 self.abort_queue()
212 self.abort_queue()
213
213
214 def complete_request(self, ident, parent):
214 def complete_request(self, ident, parent):
215 matches = {'matches' : self.complete(parent),
215 matches = {'matches' : self.complete(parent),
216 'status' : 'ok'}
216 'status' : 'ok'}
217 completion_msg = self.session.send(self.reply_socket, 'complete_reply',
217 completion_msg = self.session.send(self.reply_socket, 'complete_reply',
218 matches, parent, ident)
218 matches, parent, ident)
219 print >> sys.__stdout__, completion_msg
219 print >> sys.__stdout__, completion_msg
220
220
221 def complete(self, msg):
221 def complete(self, msg):
222 return self.completer.complete(msg.content.line, msg.content.text)
222 return self.completer.complete(msg.content.line, msg.content.text)
223
223
224 def object_info_request(self, ident, parent):
224 def object_info_request(self, ident, parent):
225 context = parent['content']['oname'].split('.')
225 context = parent['content']['oname'].split('.')
226 object_info = self.object_info(context)
226 object_info = self.object_info(context)
227 msg = self.session.send(self.reply_socket, 'object_info_reply',
227 msg = self.session.send(self.reply_socket, 'object_info_reply',
228 object_info, parent, ident)
228 object_info, parent, ident)
229 print >> sys.__stdout__, msg
229 print >> sys.__stdout__, msg
230
230
231 def object_info(self, context):
231 def object_info(self, context):
232 symbol, leftover = self.symbol_from_context(context)
232 symbol, leftover = self.symbol_from_context(context)
233 if symbol is not None and not leftover:
233 if symbol is not None and not leftover:
234 doc = getattr(symbol, '__doc__', '')
234 doc = getattr(symbol, '__doc__', '')
235 else:
235 else:
236 doc = ''
236 doc = ''
237 object_info = dict(docstring = doc)
237 object_info = dict(docstring = doc)
238 return object_info
238 return object_info
239
239
240 def symbol_from_context(self, context):
240 def symbol_from_context(self, context):
241 if not context:
241 if not context:
242 return None, context
242 return None, context
243
243
244 base_symbol_string = context[0]
244 base_symbol_string = context[0]
245 symbol = self.user_ns.get(base_symbol_string, None)
245 symbol = self.user_ns.get(base_symbol_string, None)
246 if symbol is None:
246 if symbol is None:
247 symbol = __builtin__.__dict__.get(base_symbol_string, None)
247 symbol = __builtin__.__dict__.get(base_symbol_string, None)
248 if symbol is None:
248 if symbol is None:
249 return None, context
249 return None, context
250
250
251 context = context[1:]
251 context = context[1:]
252 for i, name in enumerate(context):
252 for i, name in enumerate(context):
253 new_symbol = getattr(symbol, name, None)
253 new_symbol = getattr(symbol, name, None)
254 if new_symbol is None:
254 if new_symbol is None:
255 return symbol, context[i:]
255 return symbol, context[i:]
256 else:
256 else:
257 symbol = new_symbol
257 symbol = new_symbol
258
258
259 return symbol, []
259 return symbol, []
260
260
261 def start(self):
261 def start(self):
262 while True:
262 while True:
263 if self.poll_ppid and os.getppid() == 1:
263 if self.poll_ppid and os.getppid() == 1:
264 print>>sys.__stderr__, "KILLED KERNEL. No parent process."
264 print>>sys.__stderr__, "KILLED KERNEL. No parent process."
265 os._exit(1)
265 os._exit(1)
266
266
267 ident = self.reply_socket.recv()
267 ident = self.reply_socket.recv()
268 assert self.reply_socket.rcvmore(), "Unexpected missing message part."
268 assert self.reply_socket.rcvmore(), "Unexpected missing message part."
269 msg = self.reply_socket.recv_json()
269 msg = self.reply_socket.recv_json()
270 omsg = Message(msg)
270 omsg = Message(msg)
271 print>>sys.__stdout__
271 print>>sys.__stdout__
272 print>>sys.__stdout__, omsg
272 print>>sys.__stdout__, omsg
273 handler = self.handlers.get(omsg.msg_type, None)
273 handler = self.handlers.get(omsg.msg_type, None)
274 if handler is None:
274 if handler is None:
275 print >> sys.__stderr__, "UNKNOWN MESSAGE TYPE:", omsg
275 print >> sys.__stderr__, "UNKNOWN MESSAGE TYPE:", omsg
276 else:
276 else:
277 handler(ident, omsg)
277 handler(ident, omsg)
278
278
279
279
280 def bind_port(socket, ip, port):
280 def bind_port(socket, ip, port):
281 """ Binds the specified ZMQ socket. If the port is less than zero, a random
281 """ Binds the specified ZMQ socket. If the port is less than zero, a random
282 port is chosen. Returns the port that was bound.
282 port is chosen. Returns the port that was bound.
283 """
283 """
284 connection = 'tcp://%s' % ip
284 connection = 'tcp://%s' % ip
285 if port < 0:
285 if port < 0:
286 port = socket.bind_to_random_port(connection)
286 port = socket.bind_to_random_port(connection)
287 else:
287 else:
288 connection += ':%i' % port
288 connection += ':%i' % port
289 socket.bind(connection)
289 socket.bind(connection)
290 return port
290 return port
291
291
292 def main():
292 def main():
293 """ Main entry point for launching a kernel.
293 """ Main entry point for launching a kernel.
294 """
294 """
295 # Parse command line arguments.
295 # Parse command line arguments.
296 parser = ArgumentParser()
296 parser = ArgumentParser()
297 parser.add_argument('--ip', type=str, default='127.0.0.1',
297 parser.add_argument('--ip', type=str, default='127.0.0.1',
298 help='set the kernel\'s IP address [default: local]')
298 help='set the kernel\'s IP address [default: local]')
299 parser.add_argument('--xrep', type=int, metavar='PORT', default=0,
299 parser.add_argument('--xrep', type=int, metavar='PORT', default=0,
300 help='set the XREP Channel port [default: random]')
300 help='set the XREP channel port [default: random]')
301 parser.add_argument('--pub', type=int, metavar='PORT', default=0,
301 parser.add_argument('--pub', type=int, metavar='PORT', default=0,
302 help='set the PUB Channel port [default: random]')
302 help='set the PUB channel port [default: random]')
303 parser.add_argument('--req', type=int, metavar='PORT', default=0,
304 help='set the REQ channel port [default: random]')
303 parser.add_argument('--require-parent', action='store_true',
305 parser.add_argument('--require-parent', action='store_true',
304 help='ensure that this process dies with its parent')
306 help='ensure that this process dies with its parent')
305 namespace = parser.parse_args()
307 namespace = parser.parse_args()
306
308
307 # Create a context, a session, and the kernel sockets.
309 # Create a context, a session, and the kernel sockets.
308 print >>sys.__stdout__, "Starting the kernel..."
310 print >>sys.__stdout__, "Starting the kernel..."
309 context = zmq.Context()
311 context = zmq.Context()
310 session = Session(username=u'kernel')
312 session = Session(username=u'kernel')
311
313
312 reply_socket = context.socket(zmq.XREP)
314 reply_socket = context.socket(zmq.XREP)
313 xrep_port = bind_port(reply_socket, namespace.ip, namespace.xrep)
315 xrep_port = bind_port(reply_socket, namespace.ip, namespace.xrep)
314 print >>sys.__stdout__, "XREP Channel on port", xrep_port
316 print >>sys.__stdout__, "XREP Channel on port", xrep_port
315
317
316 pub_socket = context.socket(zmq.PUB)
318 pub_socket = context.socket(zmq.PUB)
317 pub_port = bind_port(pub_socket, namespace.ip, namespace.pub)
319 pub_port = bind_port(pub_socket, namespace.ip, namespace.pub)
318 print >>sys.__stdout__, "PUB Channel on port", pub_port
320 print >>sys.__stdout__, "PUB Channel on port", pub_port
319
321
320 # Redirect input streams and set a display hook.
322 # Redirect input streams and set a display hook.
321 sys.stdout = OutStream(session, pub_socket, u'stdout')
323 sys.stdout = OutStream(session, pub_socket, u'stdout')
322 sys.stderr = OutStream(session, pub_socket, u'stderr')
324 sys.stderr = OutStream(session, pub_socket, u'stderr')
323 sys.displayhook = DisplayHook(session, pub_socket)
325 sys.displayhook = DisplayHook(session, pub_socket)
324
326
325 # Create the kernel.
327 # Create the kernel.
326 kernel = Kernel(session, reply_socket, pub_socket)
328 kernel = Kernel(session, reply_socket, pub_socket)
327
329
328 # Configure this kernel/process to die on parent termination, if necessary.
330 # Configure this kernel/process to die on parent termination, if necessary.
329 if namespace.require_parent:
331 if namespace.require_parent:
330 if sys.platform == 'linux2':
332 if sys.platform == 'linux2':
331 import ctypes, ctypes.util, signal
333 import ctypes, ctypes.util, signal
332 PR_SET_PDEATHSIG = 1
334 PR_SET_PDEATHSIG = 1
333 libc = ctypes.CDLL(ctypes.util.find_library('c'))
335 libc = ctypes.CDLL(ctypes.util.find_library('c'))
334 libc.prctl(PR_SET_PDEATHSIG, signal.SIGKILL)
336 libc.prctl(PR_SET_PDEATHSIG, signal.SIGKILL)
335
337
336 elif sys.platform != 'win32':
338 elif sys.platform != 'win32':
337 kernel.poll_ppid = True
339 kernel.poll_ppid = True
338
340
339 # Start the kernel mainloop.
341 # Start the kernel mainloop.
340 kernel.start()
342 kernel.start()
341
343
342 def launch_kernel(xrep_port=0, pub_port=0, independent=False):
344 def launch_kernel(xrep_port=0, pub_port=0, req_port=0, independent=False):
343 """ Launches a localhost kernel, binding to the specified ports.
345 """ Launches a localhost kernel, binding to the specified ports.
344
346
345 Parameters
347 Parameters
346 ----------
348 ----------
347 xrep_port : int, optional
349 xrep_port : int, optional
348 The port to use for XREP channel.
350 The port to use for XREP channel.
349
351
350 pub_port : int, optional
352 pub_port : int, optional
351 The port to use for the SUB Channel.
353 The port to use for the SUB channel.
354
355 req_port : int, optional
356 The port to use for the REQ (raw input) channel.
352
357
353 independent : bool, optional (default False)
358 independent : bool, optional (default False)
354 If set, the kernel process is guaranteed to survive if this process
359 If set, the kernel process is guaranteed to survive if this process
355 dies. If not set, an effort is made to ensure that the kernel is killed
360 dies. If not set, an effort is made to ensure that the kernel is killed
356 when this process dies. Note that in this case it is still good practice
361 when this process dies. Note that in this case it is still good practice
357 to attempt to kill kernels manually before exiting.
362 to attempt to kill kernels manually before exiting.
358
363
359 Returns
364 Returns
360 -------
365 -------
361 A tuple of form:
366 A tuple of form:
362 (kernel_process [Popen], rep_port [int], sub_port [int])
367 (kernel_process, xrep_port, pub_port, req_port)
368 where kernel_process is a Popen object and the ports are integers.
363 """
369 """
364 import socket
370 import socket
365 from subprocess import Popen
371 from subprocess import Popen
366
372
367 # Find open ports as necessary.
373 # Find open ports as necessary.
368 ports = []
374 ports = []
369 ports_needed = int(xrep_port == 0) + int(pub_port == 0)
375 ports_needed = int(xrep_port <= 0) + int(pub_port <= 0) + int(req_port <= 0)
370 for i in xrange(ports_needed):
376 for i in xrange(ports_needed):
371 sock = socket.socket()
377 sock = socket.socket()
372 sock.bind(('', 0))
378 sock.bind(('', 0))
373 ports.append(sock)
379 ports.append(sock)
374 for i, sock in enumerate(ports):
380 for i, sock in enumerate(ports):
375 port = sock.getsockname()[1]
381 port = sock.getsockname()[1]
376 sock.close()
382 sock.close()
377 ports[i] = port
383 ports[i] = port
378 if xrep_port <= 0:
384 if xrep_port <= 0:
379 xrep_port = ports.pop(0)
385 xrep_port = ports.pop(0)
380 if pub_port <= 0:
386 if pub_port <= 0:
381 pub_port = ports.pop(0)
387 pub_port = ports.pop(0)
388 if req_port <= 0:
389 req_port = ports.pop(0)
382
390
383 # Spawn a kernel.
391 # Spawn a kernel.
384 command = 'from IPython.zmq.kernel import main; main()'
392 command = 'from IPython.zmq.kernel import main; main()'
385 arguments = [ sys.executable, '-c', command,
393 arguments = [ sys.executable, '-c', command, '--xrep', str(xrep_port),
386 '--xrep', str(xrep_port), '--pub', str(pub_port) ]
394 '--pub', str(pub_port), '--req', str(req_port) ]
387
395
388 if independent:
396 if independent:
389 if sys.platform == 'win32':
397 if sys.platform == 'win32':
390 proc = Popen(['start', '/b'] + arguments, shell=True)
398 proc = Popen(['start', '/b'] + arguments, shell=True)
391 else:
399 else:
392 proc = Popen(arguments, preexec_fn=lambda: os.setsid())
400 proc = Popen(arguments, preexec_fn=lambda: os.setsid())
393
401
394 else:
402 else:
395 proc = Popen(arguments + ['--require-parent'])
403 proc = Popen(arguments + ['--require-parent'])
396
404
397 return proc, xrep_port, pub_port
405 return proc, xrep_port, pub_port, req_port
398
406
399
407
400 if __name__ == '__main__':
408 if __name__ == '__main__':
401 main()
409 main()
@@ -1,536 +1,537 b''
1 """Classes to manage the interaction with a running kernel.
1 """Classes to manage the interaction with a running kernel.
2
2
3 Todo
3 Todo
4 ====
4 ====
5
5
6 * Create logger to handle debugging and console messages.
6 * Create logger to handle debugging and console messages.
7 """
7 """
8
8
9 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10 # Copyright (C) 2008-2010 The IPython Development Team
10 # Copyright (C) 2008-2010 The IPython Development Team
11 #
11 #
12 # Distributed under the terms of the BSD License. The full license is in
12 # Distributed under the terms of the BSD License. The full license is in
13 # the file COPYING, distributed as part of this software.
13 # the file COPYING, distributed as part of this software.
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15
15
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17 # Imports
17 # Imports
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19
19
20 # Standard library imports.
20 # Standard library imports.
21 from Queue import Queue, Empty
21 from Queue import Queue, Empty
22 from subprocess import Popen
22 from subprocess import Popen
23 from threading import Thread
23 from threading import Thread
24 import time
24 import time
25
25
26 # System library imports.
26 # System library imports.
27 import zmq
27 import zmq
28 from zmq import POLLIN, POLLOUT, POLLERR
28 from zmq import POLLIN, POLLOUT, POLLERR
29 from zmq.eventloop import ioloop
29 from zmq.eventloop import ioloop
30
30
31 # Local imports.
31 # Local imports.
32 from IPython.utils.traitlets import HasTraits, Any, Instance, Type
32 from IPython.utils.traitlets import HasTraits, Any, Instance, Type
33 from kernel import launch_kernel
33 from kernel import launch_kernel
34 from session import Session
34 from session import Session
35
35
36 #-----------------------------------------------------------------------------
36 #-----------------------------------------------------------------------------
37 # Constants and exceptions
37 # Constants and exceptions
38 #-----------------------------------------------------------------------------
38 #-----------------------------------------------------------------------------
39
39
40 LOCALHOST = '127.0.0.1'
40 LOCALHOST = '127.0.0.1'
41
41
42 class InvalidPortNumber(Exception):
42 class InvalidPortNumber(Exception):
43 pass
43 pass
44
44
45 #-----------------------------------------------------------------------------
45 #-----------------------------------------------------------------------------
46 # ZMQ Socket Channel classes
46 # ZMQ Socket Channel classes
47 #-----------------------------------------------------------------------------
47 #-----------------------------------------------------------------------------
48
48
49 class ZmqSocketChannel(Thread):
49 class ZmqSocketChannel(Thread):
50 """The base class for the channels that use ZMQ sockets.
50 """The base class for the channels that use ZMQ sockets.
51 """
51 """
52 context = None
52 context = None
53 session = None
53 session = None
54 socket = None
54 socket = None
55 ioloop = None
55 ioloop = None
56 iostate = None
56 iostate = None
57 _address = None
57 _address = None
58
58
59 def __init__(self, context, session, address):
59 def __init__(self, context, session, address):
60 """Create a channel
60 """Create a channel
61
61
62 Parameters
62 Parameters
63 ----------
63 ----------
64 context : zmq.Context
64 context : zmq.Context
65 The ZMQ context to use.
65 The ZMQ context to use.
66 session : session.Session
66 session : session.Session
67 The session to use.
67 The session to use.
68 address : tuple
68 address : tuple
69 Standard (ip, port) tuple that the kernel is listening on.
69 Standard (ip, port) tuple that the kernel is listening on.
70 """
70 """
71 super(ZmqSocketChannel, self).__init__()
71 super(ZmqSocketChannel, self).__init__()
72 self.daemon = True
72 self.daemon = True
73
73
74 self.context = context
74 self.context = context
75 self.session = session
75 self.session = session
76 if address[1] == 0:
76 if address[1] == 0:
77 raise InvalidPortNumber('The port number for a channel cannot be 0.')
77 message = 'The port number for a channel cannot be 0.'
78 raise InvalidPortNumber(message)
78 self._address = address
79 self._address = address
79
80
80 def stop(self):
81 def stop(self):
81 """Stop the channel's activity.
82 """Stop the channel's activity.
82
83
83 This calls :method:`Thread.join` and returns when the thread
84 This calls :method:`Thread.join` and returns when the thread
84 terminates. :class:`RuntimeError` will be raised if
85 terminates. :class:`RuntimeError` will be raised if
85 :method:`self.start` is called again.
86 :method:`self.start` is called again.
86 """
87 """
87 self.join()
88 self.join()
88
89
89 @property
90 @property
90 def address(self):
91 def address(self):
91 """Get the channel's address as an (ip, port) tuple.
92 """Get the channel's address as an (ip, port) tuple.
92
93
93 By the default, the address is (localhost, 0), where 0 means a random
94 By the default, the address is (localhost, 0), where 0 means a random
94 port.
95 port.
95 """
96 """
96 return self._address
97 return self._address
97
98
98 def add_io_state(self, state):
99 def add_io_state(self, state):
99 """Add IO state to the eventloop.
100 """Add IO state to the eventloop.
100
101
101 Parameters
102 Parameters
102 ----------
103 ----------
103 state : zmq.POLLIN|zmq.POLLOUT|zmq.POLLERR
104 state : zmq.POLLIN|zmq.POLLOUT|zmq.POLLERR
104 The IO state flag to set.
105 The IO state flag to set.
105
106
106 This is thread safe as it uses the thread safe IOLoop.add_callback.
107 This is thread safe as it uses the thread safe IOLoop.add_callback.
107 """
108 """
108 def add_io_state_callback():
109 def add_io_state_callback():
109 if not self.iostate & state:
110 if not self.iostate & state:
110 self.iostate = self.iostate | state
111 self.iostate = self.iostate | state
111 self.ioloop.update_handler(self.socket, self.iostate)
112 self.ioloop.update_handler(self.socket, self.iostate)
112 self.ioloop.add_callback(add_io_state_callback)
113 self.ioloop.add_callback(add_io_state_callback)
113
114
114 def drop_io_state(self, state):
115 def drop_io_state(self, state):
115 """Drop IO state from the eventloop.
116 """Drop IO state from the eventloop.
116
117
117 Parameters
118 Parameters
118 ----------
119 ----------
119 state : zmq.POLLIN|zmq.POLLOUT|zmq.POLLERR
120 state : zmq.POLLIN|zmq.POLLOUT|zmq.POLLERR
120 The IO state flag to set.
121 The IO state flag to set.
121
122
122 This is thread safe as it uses the thread safe IOLoop.add_callback.
123 This is thread safe as it uses the thread safe IOLoop.add_callback.
123 """
124 """
124 def drop_io_state_callback():
125 def drop_io_state_callback():
125 if self.iostate & state:
126 if self.iostate & state:
126 self.iostate = self.iostate & (~state)
127 self.iostate = self.iostate & (~state)
127 self.ioloop.update_handler(self.socket, self.iostate)
128 self.ioloop.update_handler(self.socket, self.iostate)
128 self.ioloop.add_callback(drop_io_state_callback)
129 self.ioloop.add_callback(drop_io_state_callback)
129
130
130
131
131 class XReqSocketChannel(ZmqSocketChannel):
132 class XReqSocketChannel(ZmqSocketChannel):
132 """The XREQ channel for issues request/replies to the kernel.
133 """The XREQ channel for issues request/replies to the kernel.
133 """
134 """
134
135
135 command_queue = None
136 command_queue = None
136
137
137 def __init__(self, context, session, address):
138 def __init__(self, context, session, address):
138 self.command_queue = Queue()
139 self.command_queue = Queue()
139 super(XReqSocketChannel, self).__init__(context, session, address)
140 super(XReqSocketChannel, self).__init__(context, session, address)
140
141
141 def run(self):
142 def run(self):
142 """The thread's main activity. Call start() instead."""
143 """The thread's main activity. Call start() instead."""
143 self.socket = self.context.socket(zmq.XREQ)
144 self.socket = self.context.socket(zmq.XREQ)
144 self.socket.setsockopt(zmq.IDENTITY, self.session.session)
145 self.socket.setsockopt(zmq.IDENTITY, self.session.session)
145 self.socket.connect('tcp://%s:%i' % self.address)
146 self.socket.connect('tcp://%s:%i' % self.address)
146 self.ioloop = ioloop.IOLoop()
147 self.ioloop = ioloop.IOLoop()
147 self.iostate = POLLERR|POLLIN
148 self.iostate = POLLERR|POLLIN
148 self.ioloop.add_handler(self.socket, self._handle_events,
149 self.ioloop.add_handler(self.socket, self._handle_events,
149 self.iostate)
150 self.iostate)
150 self.ioloop.start()
151 self.ioloop.start()
151
152
152 def stop(self):
153 def stop(self):
153 self.ioloop.stop()
154 self.ioloop.stop()
154 super(XReqSocketChannel, self).stop()
155 super(XReqSocketChannel, self).stop()
155
156
156 def call_handlers(self, msg):
157 def call_handlers(self, msg):
157 """This method is called in the ioloop thread when a message arrives.
158 """This method is called in the ioloop thread when a message arrives.
158
159
159 Subclasses should override this method to handle incoming messages.
160 Subclasses should override this method to handle incoming messages.
160 It is important to remember that this method is called in the thread
161 It is important to remember that this method is called in the thread
161 so that some logic must be done to ensure that the application leve
162 so that some logic must be done to ensure that the application leve
162 handlers are called in the application thread.
163 handlers are called in the application thread.
163 """
164 """
164 raise NotImplementedError('call_handlers must be defined in a subclass.')
165 raise NotImplementedError('call_handlers must be defined in a subclass.')
165
166
166 def execute(self, code):
167 def execute(self, code):
167 """Execute code in the kernel.
168 """Execute code in the kernel.
168
169
169 Parameters
170 Parameters
170 ----------
171 ----------
171 code : str
172 code : str
172 A string of Python code.
173 A string of Python code.
173
174
174 Returns
175 Returns
175 -------
176 -------
176 The msg_id of the message sent.
177 The msg_id of the message sent.
177 """
178 """
178 # Create class for content/msg creation. Related to, but possibly
179 # Create class for content/msg creation. Related to, but possibly
179 # not in Session.
180 # not in Session.
180 content = dict(code=code)
181 content = dict(code=code)
181 msg = self.session.msg('execute_request', content)
182 msg = self.session.msg('execute_request', content)
182 self._queue_request(msg)
183 self._queue_request(msg)
183 return msg['header']['msg_id']
184 return msg['header']['msg_id']
184
185
185 def complete(self, text, line, block=None):
186 def complete(self, text, line, block=None):
186 """Tab complete text, line, block in the kernel's namespace.
187 """Tab complete text, line, block in the kernel's namespace.
187
188
188 Parameters
189 Parameters
189 ----------
190 ----------
190 text : str
191 text : str
191 The text to complete.
192 The text to complete.
192 line : str
193 line : str
193 The full line of text that is the surrounding context for the
194 The full line of text that is the surrounding context for the
194 text to complete.
195 text to complete.
195 block : str
196 block : str
196 The full block of code in which the completion is being requested.
197 The full block of code in which the completion is being requested.
197
198
198 Returns
199 Returns
199 -------
200 -------
200 The msg_id of the message sent.
201 The msg_id of the message sent.
201
202
202 """
203 """
203 content = dict(text=text, line=line)
204 content = dict(text=text, line=line)
204 msg = self.session.msg('complete_request', content)
205 msg = self.session.msg('complete_request', content)
205 self._queue_request(msg)
206 self._queue_request(msg)
206 return msg['header']['msg_id']
207 return msg['header']['msg_id']
207
208
208 def object_info(self, oname):
209 def object_info(self, oname):
209 """Get metadata information about an object.
210 """Get metadata information about an object.
210
211
211 Parameters
212 Parameters
212 ----------
213 ----------
213 oname : str
214 oname : str
214 A string specifying the object name.
215 A string specifying the object name.
215
216
216 Returns
217 Returns
217 -------
218 -------
218 The msg_id of the message sent.
219 The msg_id of the message sent.
219 """
220 """
220 print oname
221 print oname
221 content = dict(oname=oname)
222 content = dict(oname=oname)
222 msg = self.session.msg('object_info_request', content)
223 msg = self.session.msg('object_info_request', content)
223 self._queue_request(msg)
224 self._queue_request(msg)
224 return msg['header']['msg_id']
225 return msg['header']['msg_id']
225
226
226 def _handle_events(self, socket, events):
227 def _handle_events(self, socket, events):
227 if events & POLLERR:
228 if events & POLLERR:
228 self._handle_err()
229 self._handle_err()
229 if events & POLLOUT:
230 if events & POLLOUT:
230 self._handle_send()
231 self._handle_send()
231 if events & POLLIN:
232 if events & POLLIN:
232 self._handle_recv()
233 self._handle_recv()
233
234
234 def _handle_recv(self):
235 def _handle_recv(self):
235 msg = self.socket.recv_json()
236 msg = self.socket.recv_json()
236 self.call_handlers(msg)
237 self.call_handlers(msg)
237
238
238 def _handle_send(self):
239 def _handle_send(self):
239 try:
240 try:
240 msg = self.command_queue.get(False)
241 msg = self.command_queue.get(False)
241 except Empty:
242 except Empty:
242 pass
243 pass
243 else:
244 else:
244 self.socket.send_json(msg)
245 self.socket.send_json(msg)
245 if self.command_queue.empty():
246 if self.command_queue.empty():
246 self.drop_io_state(POLLOUT)
247 self.drop_io_state(POLLOUT)
247
248
248 def _handle_err(self):
249 def _handle_err(self):
249 # We don't want to let this go silently, so eventually we should log.
250 # We don't want to let this go silently, so eventually we should log.
250 raise zmq.ZMQError()
251 raise zmq.ZMQError()
251
252
252 def _queue_request(self, msg):
253 def _queue_request(self, msg):
253 self.command_queue.put(msg)
254 self.command_queue.put(msg)
254 self.add_io_state(POLLOUT)
255 self.add_io_state(POLLOUT)
255
256
256
257
257 class SubSocketChannel(ZmqSocketChannel):
258 class SubSocketChannel(ZmqSocketChannel):
258 """The SUB channel which listens for messages that the kernel publishes.
259 """The SUB channel which listens for messages that the kernel publishes.
259 """
260 """
260
261
261 def __init__(self, context, session, address):
262 def __init__(self, context, session, address):
262 super(SubSocketChannel, self).__init__(context, session, address)
263 super(SubSocketChannel, self).__init__(context, session, address)
263
264
264 def run(self):
265 def run(self):
265 """The thread's main activity. Call start() instead."""
266 """The thread's main activity. Call start() instead."""
266 self.socket = self.context.socket(zmq.SUB)
267 self.socket = self.context.socket(zmq.SUB)
267 self.socket.setsockopt(zmq.SUBSCRIBE,'')
268 self.socket.setsockopt(zmq.SUBSCRIBE,'')
268 self.socket.setsockopt(zmq.IDENTITY, self.session.session)
269 self.socket.setsockopt(zmq.IDENTITY, self.session.session)
269 self.socket.connect('tcp://%s:%i' % self.address)
270 self.socket.connect('tcp://%s:%i' % self.address)
270 self.ioloop = ioloop.IOLoop()
271 self.ioloop = ioloop.IOLoop()
271 self.iostate = POLLIN|POLLERR
272 self.iostate = POLLIN|POLLERR
272 self.ioloop.add_handler(self.socket, self._handle_events,
273 self.ioloop.add_handler(self.socket, self._handle_events,
273 self.iostate)
274 self.iostate)
274 self.ioloop.start()
275 self.ioloop.start()
275
276
276 def stop(self):
277 def stop(self):
277 self.ioloop.stop()
278 self.ioloop.stop()
278 super(SubSocketChannel, self).stop()
279 super(SubSocketChannel, self).stop()
279
280
280 def call_handlers(self, msg):
281 def call_handlers(self, msg):
281 """This method is called in the ioloop thread when a message arrives.
282 """This method is called in the ioloop thread when a message arrives.
282
283
283 Subclasses should override this method to handle incoming messages.
284 Subclasses should override this method to handle incoming messages.
284 It is important to remember that this method is called in the thread
285 It is important to remember that this method is called in the thread
285 so that some logic must be done to ensure that the application leve
286 so that some logic must be done to ensure that the application leve
286 handlers are called in the application thread.
287 handlers are called in the application thread.
287 """
288 """
288 raise NotImplementedError('call_handlers must be defined in a subclass.')
289 raise NotImplementedError('call_handlers must be defined in a subclass.')
289
290
290 def flush(self, timeout=1.0):
291 def flush(self, timeout=1.0):
291 """Immediately processes all pending messages on the SUB channel.
292 """Immediately processes all pending messages on the SUB channel.
292
293
293 This method is thread safe.
294 This method is thread safe.
294
295
295 Parameters
296 Parameters
296 ----------
297 ----------
297 timeout : float, optional
298 timeout : float, optional
298 The maximum amount of time to spend flushing, in seconds. The
299 The maximum amount of time to spend flushing, in seconds. The
299 default is one second.
300 default is one second.
300 """
301 """
301 # We do the IOLoop callback process twice to ensure that the IOLoop
302 # We do the IOLoop callback process twice to ensure that the IOLoop
302 # gets to perform at least one full poll.
303 # gets to perform at least one full poll.
303 stop_time = time.time() + timeout
304 stop_time = time.time() + timeout
304 for i in xrange(2):
305 for i in xrange(2):
305 self._flushed = False
306 self._flushed = False
306 self.ioloop.add_callback(self._flush)
307 self.ioloop.add_callback(self._flush)
307 while not self._flushed and time.time() < stop_time:
308 while not self._flushed and time.time() < stop_time:
308 time.sleep(0.01)
309 time.sleep(0.01)
309
310
310 def _handle_events(self, socket, events):
311 def _handle_events(self, socket, events):
311 # Turn on and off POLLOUT depending on if we have made a request
312 # Turn on and off POLLOUT depending on if we have made a request
312 if events & POLLERR:
313 if events & POLLERR:
313 self._handle_err()
314 self._handle_err()
314 if events & POLLIN:
315 if events & POLLIN:
315 self._handle_recv()
316 self._handle_recv()
316
317
317 def _handle_err(self):
318 def _handle_err(self):
318 # We don't want to let this go silently, so eventually we should log.
319 # We don't want to let this go silently, so eventually we should log.
319 raise zmq.ZMQError()
320 raise zmq.ZMQError()
320
321
321 def _handle_recv(self):
322 def _handle_recv(self):
322 # Get all of the messages we can
323 # Get all of the messages we can
323 while True:
324 while True:
324 try:
325 try:
325 msg = self.socket.recv_json(zmq.NOBLOCK)
326 msg = self.socket.recv_json(zmq.NOBLOCK)
326 except zmq.ZMQError:
327 except zmq.ZMQError:
327 # Check the errno?
328 # Check the errno?
328 # Will this tigger POLLERR?
329 # Will this tigger POLLERR?
329 break
330 break
330 else:
331 else:
331 self.call_handlers(msg)
332 self.call_handlers(msg)
332
333
333 def _flush(self):
334 def _flush(self):
334 """Callback for :method:`self.flush`."""
335 """Callback for :method:`self.flush`."""
335 self._flushed = True
336 self._flushed = True
336
337
337
338
338 class RepSocketChannel(ZmqSocketChannel):
339 class RepSocketChannel(ZmqSocketChannel):
339 """A reply channel to handle raw_input requests that the kernel makes."""
340 """A reply channel to handle raw_input requests that the kernel makes."""
340
341
341 def run(self):
342 def run(self):
342 """The thread's main activity. Call start() instead."""
343 """The thread's main activity. Call start() instead."""
343 self.ioloop = ioloop.IOLoop()
344 self.ioloop = ioloop.IOLoop()
344 self.ioloop.start()
345 self.ioloop.start()
345
346
346 def stop(self):
347 def stop(self):
347 self.ioloop.stop()
348 self.ioloop.stop()
348 super(SubSocketChannel, self).stop()
349 super(RepSocketChannel, self).stop()
349
350
350 def on_raw_input(self):
351 def on_raw_input(self):
351 pass
352 pass
352
353
353
354
354 #-----------------------------------------------------------------------------
355 #-----------------------------------------------------------------------------
355 # Main kernel manager class
356 # Main kernel manager class
356 #-----------------------------------------------------------------------------
357 #-----------------------------------------------------------------------------
357
358
358
359
359 class KernelManager(HasTraits):
360 class KernelManager(HasTraits):
360 """ Manages a kernel for a frontend.
361 """ Manages a kernel for a frontend.
361
362
362 The SUB channel is for the frontend to receive messages published by the
363 The SUB channel is for the frontend to receive messages published by the
363 kernel.
364 kernel.
364
365
365 The REQ channel is for the frontend to make requests of the kernel.
366 The REQ channel is for the frontend to make requests of the kernel.
366
367
367 The REP channel is for the kernel to request stdin (raw_input) from the
368 The REP channel is for the kernel to request stdin (raw_input) from the
368 frontend.
369 frontend.
369 """
370 """
370 # The PyZMQ Context to use for communication with the kernel.
371 # The PyZMQ Context to use for communication with the kernel.
371 context = Instance(zmq.Context)
372 context = Instance(zmq.Context)
372
373
373 # The Session to use for communication with the kernel.
374 # The Session to use for communication with the kernel.
374 session = Instance(Session)
375 session = Instance(Session)
375
376
376 # The classes to use for the various channels.
377 # The classes to use for the various channels.
377 xreq_channel_class = Type(XReqSocketChannel)
378 xreq_channel_class = Type(XReqSocketChannel)
378 sub_channel_class = Type(SubSocketChannel)
379 sub_channel_class = Type(SubSocketChannel)
379 rep_channel_class = Type(RepSocketChannel)
380 rep_channel_class = Type(RepSocketChannel)
380
381
381 # Protected traits.
382 # Protected traits.
382 _kernel = Instance(Popen)
383 _kernel = Instance(Popen)
383 _xreq_address = Any
384 _xreq_address = Any
384 _sub_address = Any
385 _sub_address = Any
385 _rep_address = Any
386 _rep_address = Any
386 _xreq_channel = Any
387 _xreq_channel = Any
387 _sub_channel = Any
388 _sub_channel = Any
388 _rep_channel = Any
389 _rep_channel = Any
389
390
390 def __init__(self, xreq_address=None, sub_address=None, rep_address=None,
391 def __init__(self, xreq_address=None, sub_address=None, rep_address=None,
391 context=None, session=None):
392 context=None, session=None):
392 self._xreq_address = (LOCALHOST, 0) if xreq_address is None else xreq_address
393 self._xreq_address = (LOCALHOST, 0) if xreq_address is None else xreq_address
393 self._sub_address = (LOCALHOST, 0) if sub_address is None else sub_address
394 self._sub_address = (LOCALHOST, 0) if sub_address is None else sub_address
394 self._rep_address = (LOCALHOST, 0) if rep_address is None else rep_address
395 self._rep_address = (LOCALHOST, 0) if rep_address is None else rep_address
395 self.context = zmq.Context() if context is None else context
396 self.context = zmq.Context() if context is None else context
396 self.session = Session() if session is None else session
397 self.session = Session() if session is None else session
397
398
398 #--------------------------------------------------------------------------
399 #--------------------------------------------------------------------------
399 # Channel management methods:
400 # Channel management methods:
400 #--------------------------------------------------------------------------
401 #--------------------------------------------------------------------------
401
402
402 def start_channels(self):
403 def start_channels(self):
403 """Starts the channels for this kernel.
404 """Starts the channels for this kernel.
404
405
405 This will create the channels if they do not exist and then start
406 This will create the channels if they do not exist and then start
406 them. If port numbers of 0 are being used (random ports) then you
407 them. If port numbers of 0 are being used (random ports) then you
407 must first call :method:`start_kernel`. If the channels have been
408 must first call :method:`start_kernel`. If the channels have been
408 stopped and you call this, :class:`RuntimeError` will be raised.
409 stopped and you call this, :class:`RuntimeError` will be raised.
409 """
410 """
410 self.xreq_channel.start()
411 self.xreq_channel.start()
411 self.sub_channel.start()
412 self.sub_channel.start()
412 self.rep_channel.start()
413 self.rep_channel.start()
413
414
414 def stop_channels(self):
415 def stop_channels(self):
415 """Stops the channels for this kernel.
416 """Stops the channels for this kernel.
416
417
417 This stops the channels by joining their threads. If the channels
418 This stops the channels by joining their threads. If the channels
418 were not started, :class:`RuntimeError` will be raised.
419 were not started, :class:`RuntimeError` will be raised.
419 """
420 """
420 self.xreq_channel.stop()
421 self.xreq_channel.stop()
421 self.sub_channel.stop()
422 self.sub_channel.stop()
422 self.rep_channel.stop()
423 self.rep_channel.stop()
423
424
424 @property
425 @property
425 def channels_running(self):
426 def channels_running(self):
426 """Are all of the channels created and running?"""
427 """Are all of the channels created and running?"""
427 return self.xreq_channel.is_alive() \
428 return self.xreq_channel.is_alive() \
428 and self.sub_channel.is_alive() \
429 and self.sub_channel.is_alive() \
429 and self.rep_channel.is_alive()
430 and self.rep_channel.is_alive()
430
431
431 #--------------------------------------------------------------------------
432 #--------------------------------------------------------------------------
432 # Kernel process management methods:
433 # Kernel process management methods:
433 #--------------------------------------------------------------------------
434 #--------------------------------------------------------------------------
434
435
435 def start_kernel(self):
436 def start_kernel(self):
436 """Starts a kernel process and configures the manager to use it.
437 """Starts a kernel process and configures the manager to use it.
437
438
438 If random ports (port=0) are being used, this method must be called
439 If random ports (port=0) are being used, this method must be called
439 before the channels are created.
440 before the channels are created.
440 """
441 """
441 xreq, sub = self.xreq_address, self.sub_address
442 xreq, sub = self.xreq_address, self.sub_address
442 if xreq[0] != LOCALHOST or sub[0] != LOCALHOST:
443 if xreq[0] != LOCALHOST or sub[0] != LOCALHOST:
443 raise RuntimeError("Can only launch a kernel on localhost."
444 raise RuntimeError("Can only launch a kernel on localhost."
444 "Make sure that the '*_address' attributes are "
445 "Make sure that the '*_address' attributes are "
445 "configured properly.")
446 "configured properly.")
446
447
447 kernel, xrep, pub = launch_kernel(xrep_port=xreq[1], pub_port=sub[1])
448 kernel, xrep, pub = launch_kernel(xrep_port=xreq[1], pub_port=sub[1])
448 self._kernel = kernel
449 self._kernel = kernel
449 self._xreq_address = (LOCALHOST, xrep)
450 self._xreq_address = (LOCALHOST, xrep)
450 self._sub_address = (LOCALHOST, pub)
451 self._sub_address = (LOCALHOST, pub)
451 # The rep channel is not fully working yet, but its base class makes
452 # The rep channel is not fully working yet, but its base class makes
452 # sure the port is not 0. We set to -1 for now until the rep channel
453 # sure the port is not 0. We set to -1 for now until the rep channel
453 # is fully working.
454 # is fully working.
454 self._rep_address = (LOCALHOST, -1)
455 self._rep_address = (LOCALHOST, -1)
455
456
456 @property
457 @property
457 def has_kernel(self):
458 def has_kernel(self):
458 """Returns whether a kernel process has been specified for the kernel
459 """Returns whether a kernel process has been specified for the kernel
459 manager.
460 manager.
460
461
461 A kernel process can be set via 'start_kernel' or 'set_kernel'.
462 A kernel process can be set via 'start_kernel' or 'set_kernel'.
462 """
463 """
463 return self._kernel is not None
464 return self._kernel is not None
464
465
465 def kill_kernel(self):
466 def kill_kernel(self):
466 """ Kill the running kernel. """
467 """ Kill the running kernel. """
467 if self._kernel is not None:
468 if self._kernel is not None:
468 self._kernel.kill()
469 self._kernel.kill()
469 self._kernel = None
470 self._kernel = None
470 else:
471 else:
471 raise RuntimeError("Cannot kill kernel. No kernel is running!")
472 raise RuntimeError("Cannot kill kernel. No kernel is running!")
472
473
473 def signal_kernel(self, signum):
474 def signal_kernel(self, signum):
474 """ Sends a signal to the kernel. """
475 """ Sends a signal to the kernel. """
475 if self._kernel is not None:
476 if self._kernel is not None:
476 self._kernel.send_signal(signum)
477 self._kernel.send_signal(signum)
477 else:
478 else:
478 raise RuntimeError("Cannot signal kernel. No kernel is running!")
479 raise RuntimeError("Cannot signal kernel. No kernel is running!")
479
480
480 @property
481 @property
481 def is_alive(self):
482 def is_alive(self):
482 """Is the kernel process still running?"""
483 """Is the kernel process still running?"""
483 if self._kernel is not None:
484 if self._kernel is not None:
484 if self._kernel.poll() is None:
485 if self._kernel.poll() is None:
485 return True
486 return True
486 else:
487 else:
487 return False
488 return False
488 else:
489 else:
489 # We didn't start the kernel with this KernelManager so we don't
490 # We didn't start the kernel with this KernelManager so we don't
490 # know if it is running. We should use a heartbeat for this case.
491 # know if it is running. We should use a heartbeat for this case.
491 return True
492 return True
492
493
493 #--------------------------------------------------------------------------
494 #--------------------------------------------------------------------------
494 # Channels used for communication with the kernel:
495 # Channels used for communication with the kernel:
495 #--------------------------------------------------------------------------
496 #--------------------------------------------------------------------------
496
497
497 @property
498 @property
498 def xreq_channel(self):
499 def xreq_channel(self):
499 """Get the REQ socket channel object to make requests of the kernel."""
500 """Get the REQ socket channel object to make requests of the kernel."""
500 if self._xreq_channel is None:
501 if self._xreq_channel is None:
501 self._xreq_channel = self.xreq_channel_class(self.context,
502 self._xreq_channel = self.xreq_channel_class(self.context,
502 self.session,
503 self.session,
503 self.xreq_address)
504 self.xreq_address)
504 return self._xreq_channel
505 return self._xreq_channel
505
506
506 @property
507 @property
507 def sub_channel(self):
508 def sub_channel(self):
508 """Get the SUB socket channel object."""
509 """Get the SUB socket channel object."""
509 if self._sub_channel is None:
510 if self._sub_channel is None:
510 self._sub_channel = self.sub_channel_class(self.context,
511 self._sub_channel = self.sub_channel_class(self.context,
511 self.session,
512 self.session,
512 self.sub_address)
513 self.sub_address)
513 return self._sub_channel
514 return self._sub_channel
514
515
515 @property
516 @property
516 def rep_channel(self):
517 def rep_channel(self):
517 """Get the REP socket channel object to handle stdin (raw_input)."""
518 """Get the REP socket channel object to handle stdin (raw_input)."""
518 if self._rep_channel is None:
519 if self._rep_channel is None:
519 self._rep_channel = self.rep_channel_class(self.context,
520 self._rep_channel = self.rep_channel_class(self.context,
520 self.session,
521 self.session,
521 self.rep_address)
522 self.rep_address)
522 return self._rep_channel
523 return self._rep_channel
523
524
524 @property
525 @property
525 def xreq_address(self):
526 def xreq_address(self):
526 return self._xreq_address
527 return self._xreq_address
527
528
528 @property
529 @property
529 def sub_address(self):
530 def sub_address(self):
530 return self._sub_address
531 return self._sub_address
531
532
532 @property
533 @property
533 def rep_address(self):
534 def rep_address(self):
534 return self._rep_address
535 return self._rep_address
535
536
536
537
General Comments 0
You need to be logged in to leave comments. Login now