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