##// END OF EJS Templates
* Created an IPythonWidget subclass of FrontendWidget to contain IPython specific functionality....
epatters -
Show More
@@ -0,0 +1,80 b''
1 # Local imports.
2 from frontend_widget import FrontendWidget
3
4
5 class IPythonWidget(FrontendWidget):
6 """ A FrontendWidget for an IPython kernel.
7 """
8
9 #---------------------------------------------------------------------------
10 # 'FrontendWidget' interface
11 #---------------------------------------------------------------------------
12
13 def __init__(self, kernel_manager, parent=None):
14 super(IPythonWidget, self).__init__(kernel_manager, parent)
15
16 self._magic_overrides = {}
17
18 def execute_source(self, source, hidden=False, interactive=False):
19 """ Reimplemented to override magic commands.
20 """
21 magic_source = source.strip()
22 if magic_source.startswith('%'):
23 magic_source = magic_source[1:]
24 magic, sep, arguments = magic_source.partition(' ')
25 if not magic:
26 magic = magic_source
27
28 callback = self._magic_overrides.get(magic)
29 if callback:
30 output = callback(arguments)
31 if output:
32 self.appendPlainText(output)
33 self._show_prompt('>>> ')
34 return True
35 else:
36 return super(IPythonWidget, self).execute_source(source, hidden,
37 interactive)
38
39 #---------------------------------------------------------------------------
40 # 'IPythonWidget' interface
41 #---------------------------------------------------------------------------
42
43 def set_magic_override(self, magic, callback):
44 """ Overrides an IPython magic command. This magic will be intercepted
45 by the frontend rather than passed on to the kernel and 'callback'
46 will be called with a single argument: a string of argument(s) for
47 the magic. The callback can (optionally) return text to print to the
48 console.
49 """
50 self._magic_overrides[magic] = callback
51
52 def remove_magic_override(self, magic):
53 """ Removes the override for the specified magic, if there is one.
54 """
55 try:
56 del self._magic_overrides[magic]
57 except KeyError:
58 pass
59
60
61 if __name__ == '__main__':
62 import sys
63 from IPython.frontend.qt.kernelmanager import QtKernelManager
64
65 # Create KernelManager
66 xreq_addr = ('127.0.0.1', 5575)
67 sub_addr = ('127.0.0.1', 5576)
68 rep_addr = ('127.0.0.1', 5577)
69 kernel_manager = QtKernelManager(xreq_addr, sub_addr, rep_addr)
70 kernel_manager.sub_channel.start()
71 kernel_manager.xreq_channel.start()
72
73 # Launch application
74 app = QtGui.QApplication(sys.argv)
75 widget = IPythonWidget(kernel_manager)
76 widget.setWindowTitle('Python')
77 widget.resize(640, 480)
78 widget.show()
79 sys.exit(app.exec_())
80
@@ -1,324 +1,303 b''
1 # Standard library imports
1 # Standard library imports
2 from codeop import CommandCompiler
2 from codeop import CommandCompiler
3 from threading import Thread
3 from threading import Thread
4 import time
4 import time
5 import types
5 import types
6
6
7 # System library imports
7 # System library imports
8 from pygments.lexers import PythonLexer
8 from pygments.lexers import PythonLexer
9 from PyQt4 import QtCore, QtGui
9 from PyQt4 import QtCore, QtGui
10 import zmq
10 import zmq
11
11
12 # Local imports
12 # Local imports
13 from call_tip_widget import CallTipWidget
13 from call_tip_widget import CallTipWidget
14 from completion_lexer import CompletionLexer
14 from completion_lexer import CompletionLexer
15 from console_widget import HistoryConsoleWidget
15 from console_widget import HistoryConsoleWidget
16 from pygments_highlighter import PygmentsHighlighter
16 from pygments_highlighter import PygmentsHighlighter
17
17
18
18
19 class FrontendHighlighter(PygmentsHighlighter):
19 class FrontendHighlighter(PygmentsHighlighter):
20 """ A Python PygmentsHighlighter that can be turned on and off and which
20 """ A Python PygmentsHighlighter that can be turned on and off and which
21 knows about continuation prompts.
21 knows about continuation prompts.
22 """
22 """
23
23
24 def __init__(self, frontend):
24 def __init__(self, frontend):
25 PygmentsHighlighter.__init__(self, frontend.document(), PythonLexer())
25 PygmentsHighlighter.__init__(self, frontend.document(), PythonLexer())
26 self._current_offset = 0
26 self._current_offset = 0
27 self._frontend = frontend
27 self._frontend = frontend
28 self.highlighting_on = False
28 self.highlighting_on = False
29
29
30 def highlightBlock(self, qstring):
30 def highlightBlock(self, qstring):
31 """ Highlight a block of text. Reimplemented to highlight selectively.
31 """ Highlight a block of text. Reimplemented to highlight selectively.
32 """
32 """
33 if self.highlighting_on:
33 if self.highlighting_on:
34 for prompt in (self._frontend._prompt,
34 for prompt in (self._frontend._prompt,
35 self._frontend.continuation_prompt):
35 self._frontend.continuation_prompt):
36 if qstring.startsWith(prompt):
36 if qstring.startsWith(prompt):
37 qstring.remove(0, len(prompt))
37 qstring.remove(0, len(prompt))
38 self._current_offset = len(prompt)
38 self._current_offset = len(prompt)
39 break
39 break
40 PygmentsHighlighter.highlightBlock(self, qstring)
40 PygmentsHighlighter.highlightBlock(self, qstring)
41
41
42 def setFormat(self, start, count, format):
42 def setFormat(self, start, count, format):
43 """ Reimplemented to avoid highlighting continuation prompts.
43 """ Reimplemented to avoid highlighting continuation prompts.
44 """
44 """
45 start += self._current_offset
45 start += self._current_offset
46 PygmentsHighlighter.setFormat(self, start, count, format)
46 PygmentsHighlighter.setFormat(self, start, count, format)
47
47
48
48
49 class FrontendWidget(HistoryConsoleWidget):
49 class FrontendWidget(HistoryConsoleWidget):
50 """ A Qt frontend for an IPython kernel.
50 """ A Qt frontend for a generic Python kernel.
51 """
51 """
52
52
53 # Emitted when an 'execute_reply' is received from the kernel.
53 # Emitted when an 'execute_reply' is received from the kernel.
54 executed = QtCore.pyqtSignal(object)
54 executed = QtCore.pyqtSignal(object)
55
55
56 #---------------------------------------------------------------------------
56 #---------------------------------------------------------------------------
57 # 'QWidget' interface
57 # 'QWidget' interface
58 #---------------------------------------------------------------------------
58 #---------------------------------------------------------------------------
59
59
60 def __init__(self, kernel_manager, parent=None):
60 def __init__(self, kernel_manager, parent=None):
61 super(FrontendWidget, self).__init__(parent)
61 super(FrontendWidget, self).__init__(parent)
62
62
63 self._call_tip_widget = CallTipWidget(self)
63 self._call_tip_widget = CallTipWidget(self)
64 self._compile = CommandCompiler()
64 self._compile = CommandCompiler()
65 self._completion_lexer = CompletionLexer(PythonLexer())
65 self._completion_lexer = CompletionLexer(PythonLexer())
66 self._hidden = False
66 self._hidden = True
67 self._highlighter = FrontendHighlighter(self)
67 self._highlighter = FrontendHighlighter(self)
68 self._kernel_manager = None
68 self._kernel_manager = None
69
69
70 self.continuation_prompt = '... '
70 self.continuation_prompt = '... '
71 self.kernel_manager = kernel_manager
71 self.kernel_manager = kernel_manager
72
72
73 self.document().contentsChange.connect(self._document_contents_change)
73 self.document().contentsChange.connect(self._document_contents_change)
74
74
75 def focusOutEvent(self, event):
75 def focusOutEvent(self, event):
76 """ Reimplemented to hide calltips.
76 """ Reimplemented to hide calltips.
77 """
77 """
78 self._call_tip_widget.hide()
78 self._call_tip_widget.hide()
79 return super(FrontendWidget, self).focusOutEvent(event)
79 return super(FrontendWidget, self).focusOutEvent(event)
80
80
81 def keyPressEvent(self, event):
81 def keyPressEvent(self, event):
82 """ Reimplemented to hide calltips.
82 """ Reimplemented to hide calltips.
83 """
83 """
84 if event.key() == QtCore.Qt.Key_Escape:
84 if event.key() == QtCore.Qt.Key_Escape:
85 self._call_tip_widget.hide()
85 self._call_tip_widget.hide()
86 return super(FrontendWidget, self).keyPressEvent(event)
86 return super(FrontendWidget, self).keyPressEvent(event)
87
87
88 #---------------------------------------------------------------------------
88 #---------------------------------------------------------------------------
89 # 'ConsoleWidget' abstract interface
89 # 'ConsoleWidget' abstract interface
90 #---------------------------------------------------------------------------
90 #---------------------------------------------------------------------------
91
91
92 def _execute(self, interactive):
92 def _execute(self, interactive):
93 """ Called to execute the input buffer. When triggered by an the enter
93 """ Called to execute the input buffer. When triggered by an the enter
94 key press, 'interactive' is True; otherwise, it is False. Returns
94 key press, 'interactive' is True; otherwise, it is False. Returns
95 whether the input buffer was completely processed and a new prompt
95 whether the input buffer was completely processed and a new prompt
96 created.
96 created.
97 """
97 """
98 return self.execute_source(self.input_buffer, interactive=interactive)
98 return self.execute_source(self.input_buffer, interactive=interactive)
99
99
100 def _prompt_started_hook(self):
100 def _prompt_started_hook(self):
101 """ Called immediately after a new prompt is displayed.
101 """ Called immediately after a new prompt is displayed.
102 """
102 """
103 self._highlighter.highlighting_on = True
103 self._highlighter.highlighting_on = True
104
104
105 def _prompt_finished_hook(self):
105 def _prompt_finished_hook(self):
106 """ Called immediately after a prompt is finished, i.e. when some input
106 """ Called immediately after a prompt is finished, i.e. when some input
107 will be processed and a new prompt displayed.
107 will be processed and a new prompt displayed.
108 """
108 """
109 self._highlighter.highlighting_on = False
109 self._highlighter.highlighting_on = False
110
110
111 def _tab_pressed(self):
111 def _tab_pressed(self):
112 """ Called when the tab key is pressed. Returns whether to continue
112 """ Called when the tab key is pressed. Returns whether to continue
113 processing the event.
113 processing the event.
114 """
114 """
115 self._keep_cursor_in_buffer()
115 self._keep_cursor_in_buffer()
116 cursor = self.textCursor()
116 cursor = self.textCursor()
117 if not self._complete():
117 if not self._complete():
118 cursor.insertText(' ')
118 cursor.insertText(' ')
119 return False
119 return False
120
120
121 #---------------------------------------------------------------------------
121 #---------------------------------------------------------------------------
122 # 'FrontendWidget' interface
122 # 'FrontendWidget' interface
123 #---------------------------------------------------------------------------
123 #---------------------------------------------------------------------------
124
124
125 def execute_source(self, source, hidden=False, interactive=False):
125 def execute_source(self, source, hidden=False, interactive=False):
126 """ Execute a string containing Python code. If 'hidden', no output is
126 """ Execute a string containing Python code. If 'hidden', no output is
127 shown. Returns whether the source executed (i.e., returns True only
127 shown. Returns whether the source executed (i.e., returns True only
128 if no more input is necessary).
128 if no more input is necessary).
129 """
129 """
130 # Use CommandCompiler to determine if more input is needed.
130 try:
131 try:
131 code = self._compile(source, symbol='single')
132 code = self._compile(source, symbol='single')
132 except (OverflowError, SyntaxError, ValueError):
133 except (OverflowError, SyntaxError, ValueError):
133 # Just let IPython deal with the syntax error.
134 # Just let IPython deal with the syntax error.
134 code = Exception
135 code = Exception
135
136
136 # Only execute interactive multiline input if it ends with a blank line
137 # Only execute interactive multiline input if it ends with a blank line
137 lines = source.splitlines()
138 lines = source.splitlines()
138 if interactive and len(lines) > 1 and lines[-1].strip() != '':
139 if interactive and len(lines) > 1 and lines[-1].strip() != '':
139 code = None
140 code = None
140
141
141 executed = code is not None
142 executed = code is not None
142 if executed:
143 if executed:
143 self.kernel_manager.xreq_channel.execute(source)
144 self.kernel_manager.xreq_channel.execute(source)
144 self._hidden = hidden
145 self._hidden = hidden
145 else:
146 else:
146 space = 0
147 space = 0
147 for char in lines[-1]:
148 for char in lines[-1]:
148 if char == '\t':
149 if char == '\t':
149 space += 4
150 space += 4
150 elif char == ' ':
151 elif char == ' ':
151 space += 1
152 space += 1
152 else:
153 else:
153 break
154 break
154 if source.endswith(':') or source.endswith(':\n'):
155 if source.endswith(':') or source.endswith(':\n'):
155 space += 4
156 space += 4
156 self._show_continuation_prompt()
157 self._show_continuation_prompt()
157 self.appendPlainText(' ' * space)
158 self.appendPlainText(' ' * space)
158
159
159 return executed
160 return executed
160
161
161 def execute_file(self, path, hidden=False):
162 def execute_file(self, path, hidden=False):
162 """ Attempts to execute file with 'path'. If 'hidden', no output is
163 """ Attempts to execute file with 'path'. If 'hidden', no output is
163 shown.
164 shown.
164 """
165 """
165 self.execute_source('run %s' % path, hidden=hidden)
166 self.execute_source('run %s' % path, hidden=hidden)
166
167
167 def _get_kernel_manager(self):
168 def _get_kernel_manager(self):
168 """ Returns the current kernel manager.
169 """ Returns the current kernel manager.
169 """
170 """
170 return self._kernel_manager
171 return self._kernel_manager
171
172
172 def _set_kernel_manager(self, kernel_manager):
173 def _set_kernel_manager(self, kernel_manager):
173 """ Sets a new kernel manager, configuring its channels as necessary.
174 """ Sets a new kernel manager, configuring its channels as necessary.
174 """
175 """
175 # Disconnect the old kernel manager.
176 # Disconnect the old kernel manager.
176 if self._kernel_manager is not None:
177 if self._kernel_manager is not None:
177 sub = self._kernel_manager.sub_channel
178 sub = self._kernel_manager.sub_channel
178 xreq = self._kernel_manager.xreq_channel
179 xreq = self._kernel_manager.xreq_channel
179 sub.message_received.disconnect(self._handle_sub)
180 sub.message_received.disconnect(self._handle_sub)
180 xreq.execute_reply.disconnect(self._handle_execute_reply)
181 xreq.execute_reply.disconnect(self._handle_execute_reply)
181 xreq.complete_reply.disconnect(self._handle_complete_reply)
182 xreq.complete_reply.disconnect(self._handle_complete_reply)
182 xreq.object_info_reply.disconnect(self._handle_object_info_reply)
183 xreq.object_info_reply.disconnect(self._handle_object_info_reply)
183
184
184 # Connect the new kernel manager.
185 # Connect the new kernel manager.
185 self._kernel_manager = kernel_manager
186 self._kernel_manager = kernel_manager
186 sub = kernel_manager.sub_channel
187 sub = kernel_manager.sub_channel
187 xreq = kernel_manager.xreq_channel
188 xreq = kernel_manager.xreq_channel
188 sub.message_received.connect(self._handle_sub)
189 sub.message_received.connect(self._handle_sub)
189 xreq.execute_reply.connect(self._handle_execute_reply)
190 xreq.execute_reply.connect(self._handle_execute_reply)
190 xreq.complete_reply.connect(self._handle_complete_reply)
191 xreq.complete_reply.connect(self._handle_complete_reply)
191 xreq.object_info_reply.connect(self._handle_object_info_reply)
192 xreq.object_info_reply.connect(self._handle_object_info_reply)
192
193
193 self._show_prompt('>>> ')
194 self._show_prompt('>>> ')
194
195
195 kernel_manager = property(_get_kernel_manager, _set_kernel_manager)
196 kernel_manager = property(_get_kernel_manager, _set_kernel_manager)
196
197
197 #---------------------------------------------------------------------------
198 #---------------------------------------------------------------------------
198 # 'FrontendWidget' protected interface
199 # 'FrontendWidget' protected interface
199 #---------------------------------------------------------------------------
200 #---------------------------------------------------------------------------
200
201
201 def _call_tip(self):
202 def _call_tip(self):
202 """ Shows a call tip, if appropriate, at the current cursor location.
203 """ Shows a call tip, if appropriate, at the current cursor location.
203 """
204 """
204 # Decide if it makes sense to show a call tip
205 # Decide if it makes sense to show a call tip
205 cursor = self.textCursor()
206 cursor = self.textCursor()
206 cursor.movePosition(QtGui.QTextCursor.Left)
207 cursor.movePosition(QtGui.QTextCursor.Left)
207 document = self.document()
208 document = self.document()
208 if document.characterAt(cursor.position()).toAscii() != '(':
209 if document.characterAt(cursor.position()).toAscii() != '(':
209 return False
210 return False
210 context = self._get_context(cursor)
211 context = self._get_context(cursor)
211 if not context:
212 if not context:
212 return False
213 return False
213
214
214 # Send the metadata request to the kernel
215 # Send the metadata request to the kernel
215 name = '.'.join(context)
216 name = '.'.join(context)
216 self._calltip_id = self.kernel_manager.xreq_channel.object_info(name)
217 self._calltip_id = self.kernel_manager.xreq_channel.object_info(name)
217 self._calltip_pos = self.textCursor().position()
218 self._calltip_pos = self.textCursor().position()
218 return True
219 return True
219
220
220 def _complete(self):
221 def _complete(self):
221 """ Performs completion at the current cursor location.
222 """ Performs completion at the current cursor location.
222 """
223 """
223 # Decide if it makes sense to do completion
224 # Decide if it makes sense to do completion
224 context = self._get_context()
225 context = self._get_context()
225 if not context:
226 if not context:
226 return False
227 return False
227
228
228 # Send the completion request to the kernel
229 # Send the completion request to the kernel
229 text = '.'.join(context)
230 text = '.'.join(context)
230 self._complete_id = self.kernel_manager.xreq_channel.complete(
231 self._complete_id = self.kernel_manager.xreq_channel.complete(
231 text, self.input_buffer_cursor_line, self.input_buffer)
232 text, self.input_buffer_cursor_line, self.input_buffer)
232 self._complete_pos = self.textCursor().position()
233 self._complete_pos = self.textCursor().position()
233 return True
234 return True
234
235
235 def _get_context(self, cursor=None):
236 def _get_context(self, cursor=None):
236 """ Gets the context at the current cursor location.
237 """ Gets the context at the current cursor location.
237 """
238 """
238 if cursor is None:
239 if cursor is None:
239 cursor = self.textCursor()
240 cursor = self.textCursor()
240 cursor.movePosition(QtGui.QTextCursor.StartOfLine,
241 cursor.movePosition(QtGui.QTextCursor.StartOfLine,
241 QtGui.QTextCursor.KeepAnchor)
242 QtGui.QTextCursor.KeepAnchor)
242 text = unicode(cursor.selectedText())
243 text = unicode(cursor.selectedText())
243 return self._completion_lexer.get_context(text)
244 return self._completion_lexer.get_context(text)
244
245
245 #------ Signal handlers ----------------------------------------------------
246 #------ Signal handlers ----------------------------------------------------
246
247
247 def _document_contents_change(self, position, removed, added):
248 def _document_contents_change(self, position, removed, added):
248 """ Called whenever the document's content changes. Display a calltip
249 """ Called whenever the document's content changes. Display a calltip
249 if appropriate.
250 if appropriate.
250 """
251 """
251 # Calculate where the cursor should be *after* the change:
252 # Calculate where the cursor should be *after* the change:
252 position += added
253 position += added
253
254
254 document = self.document()
255 document = self.document()
255 if position == self.textCursor().position():
256 if position == self.textCursor().position():
256 self._call_tip()
257 self._call_tip()
257
258
258 def _handle_sub(self, omsg):
259 def _handle_sub(self, omsg):
259 if not self._hidden:
260 if not self._hidden:
260 handler = getattr(self, '_handle_%s' % omsg['msg_type'], None)
261 handler = getattr(self, '_handle_%s' % omsg['msg_type'], None)
261 if handler is not None:
262 if handler is not None:
262 handler(omsg)
263 handler(omsg)
263
264
264 def _handle_pyout(self, omsg):
265 def _handle_pyout(self, omsg):
265 session = omsg['parent_header']['session']
266 session = omsg['parent_header']['session']
266 if session == self.kernel_manager.session.session:
267 if session == self.kernel_manager.session.session:
267 self.appendPlainText(omsg['content']['data'] + '\n')
268 self.appendPlainText(omsg['content']['data'] + '\n')
268
269
269 def _handle_stream(self, omsg):
270 def _handle_stream(self, omsg):
270 self.appendPlainText(omsg['content']['data'])
271 self.appendPlainText(omsg['content']['data'])
271
272
272 def _handle_execute_reply(self, rep):
273 def _handle_execute_reply(self, rep):
273 # Make sure that all output from the SUB channel has been processed
274 # Make sure that all output from the SUB channel has been processed
274 # before writing a new prompt.
275 # before writing a new prompt.
275 self.kernel_manager.sub_channel.flush()
276 self.kernel_manager.sub_channel.flush()
276
277
277 content = rep['content']
278 content = rep['content']
278 status = content['status']
279 status = content['status']
279 if status == 'error':
280 if status == 'error':
280 self.appendPlainText(content['traceback'][-1])
281 self.appendPlainText(content['traceback'][-1])
281 elif status == 'aborted':
282 elif status == 'aborted':
282 text = "ERROR: ABORTED\n"
283 text = "ERROR: ABORTED\n"
283 self.appendPlainText(text)
284 self.appendPlainText(text)
284 self._hidden = False
285 self._hidden = True
285 self._show_prompt('>>> ')
286 self._show_prompt('>>> ')
286 self.executed.emit(rep)
287 self.executed.emit(rep)
287
288
288 def _handle_complete_reply(self, rep):
289 def _handle_complete_reply(self, rep):
289 cursor = self.textCursor()
290 cursor = self.textCursor()
290 if rep['parent_header']['msg_id'] == self._complete_id and \
291 if rep['parent_header']['msg_id'] == self._complete_id and \
291 cursor.position() == self._complete_pos:
292 cursor.position() == self._complete_pos:
292 text = '.'.join(self._get_context())
293 text = '.'.join(self._get_context())
293 cursor.movePosition(QtGui.QTextCursor.Left, n=len(text))
294 cursor.movePosition(QtGui.QTextCursor.Left, n=len(text))
294 self._complete_with_items(cursor, rep['content']['matches'])
295 self._complete_with_items(cursor, rep['content']['matches'])
295
296
296 def _handle_object_info_reply(self, rep):
297 def _handle_object_info_reply(self, rep):
297 cursor = self.textCursor()
298 cursor = self.textCursor()
298 if rep['parent_header']['msg_id'] == self._calltip_id and \
299 if rep['parent_header']['msg_id'] == self._calltip_id and \
299 cursor.position() == self._calltip_pos:
300 cursor.position() == self._calltip_pos:
300 doc = rep['content']['docstring']
301 doc = rep['content']['docstring']
301 if doc:
302 if doc:
302 self._call_tip_widget.show_tip(doc)
303 self._call_tip_widget.show_tip(doc)
303
304
305 if __name__ == '__main__':
306 import sys
307 from IPython.frontend.qt.kernelmanager import QtKernelManager
308
309 # Create KernelManager
310 xreq_addr = ('127.0.0.1', 5575)
311 sub_addr = ('127.0.0.1', 5576)
312 rep_addr = ('127.0.0.1', 5577)
313 kernel_manager = QtKernelManager(xreq_addr, sub_addr, rep_addr)
314 kernel_manager.sub_channel.start()
315 kernel_manager.xreq_channel.start()
316
317 # Launch application
318 app = QtGui.QApplication(sys.argv)
319 widget = FrontendWidget(kernel_manager)
320 widget.setWindowTitle('Python')
321 widget.resize(640, 480)
322 widget.show()
323 sys.exit(app.exec_())
324
@@ -1,335 +1,335 b''
1 """Kernel frontend classes.
1 """Kernel frontend classes.
2
2
3 TODO: Create logger to handle debugging and console messages.
3 TODO: Create logger to handle debugging and console messages.
4
4
5 """
5 """
6
6
7 # Standard library imports.
7 # Standard library imports.
8 from Queue import Queue, Empty
8 from Queue import Queue, Empty
9 from threading import Thread
9 from threading import Thread
10 import time
10 import time
11 import traceback
11 import traceback
12
12
13 # System library imports.
13 # System library imports.
14 import zmq
14 import zmq
15 from zmq import POLLIN, POLLOUT, POLLERR
15 from zmq import POLLIN, POLLOUT, POLLERR
16 from zmq.eventloop import ioloop
16 from zmq.eventloop import ioloop
17
17
18 # Local imports.
18 # Local imports.
19 from IPython.utils.traitlets import HasTraits, Any, Int, Instance, Str, Type
19 from IPython.utils.traitlets import HasTraits, Any, Int, Instance, Str, Type
20 from session import Session
20 from session import Session
21
21
22
22
23 class MissingHandlerError(Exception):
23 class MissingHandlerError(Exception):
24 pass
24 pass
25
25
26
26
27 class ZmqSocketChannel(Thread):
27 class ZmqSocketChannel(Thread):
28
28
29 socket = None
29 socket = None
30
30
31 def __init__(self, context, session, addr):
31 def __init__(self, context, session, addr):
32 self.context = context
32 self.context = context
33 self.session = session
33 self.session = session
34 self.addr = addr
34 self.addr = addr
35 super(ZmqSocketChannel, self).__init__()
35 super(ZmqSocketChannel, self).__init__()
36 self.daemon = True
36 self.daemon = True
37
37
38
38
39 class SubSocketChannel(ZmqSocketChannel):
39 class SubSocketChannel(ZmqSocketChannel):
40
40
41 handlers = None
41 handlers = None
42 _overriden_call_handler = None
42 _overriden_call_handler = None
43
43
44 def __init__(self, context, session, addr):
44 def __init__(self, context, session, addr):
45 self.handlers = {}
45 self.handlers = {}
46 super(SubSocketChannel, self).__init__(context, session, addr)
46 super(SubSocketChannel, self).__init__(context, session, addr)
47
47
48 def run(self):
48 def run(self):
49 self.socket = self.context.socket(zmq.SUB)
49 self.socket = self.context.socket(zmq.SUB)
50 self.socket.setsockopt(zmq.SUBSCRIBE,'')
50 self.socket.setsockopt(zmq.SUBSCRIBE,'')
51 self.socket.setsockopt(zmq.IDENTITY, self.session.session)
51 self.socket.setsockopt(zmq.IDENTITY, self.session.session)
52 self.socket.connect('tcp://%s:%i' % self.addr)
52 self.socket.connect('tcp://%s:%i' % self.addr)
53 self.ioloop = ioloop.IOLoop()
53 self.ioloop = ioloop.IOLoop()
54 self.ioloop.add_handler(self.socket, self._handle_events,
54 self.ioloop.add_handler(self.socket, self._handle_events,
55 POLLIN|POLLERR)
55 POLLIN|POLLERR)
56 self.ioloop.start()
56 self.ioloop.start()
57
57
58 def _handle_events(self, socket, events):
58 def _handle_events(self, socket, events):
59 # Turn on and off POLLOUT depending on if we have made a request
59 # Turn on and off POLLOUT depending on if we have made a request
60 if events & POLLERR:
60 if events & POLLERR:
61 self._handle_err()
61 self._handle_err()
62 if events & POLLIN:
62 if events & POLLIN:
63 self._handle_recv()
63 self._handle_recv()
64
64
65 def _handle_err(self):
65 def _handle_err(self):
66 raise zmq.ZmqError()
66 raise zmq.ZmqError()
67
67
68 def _handle_recv(self):
68 def _handle_recv(self):
69 msg = self.socket.recv_json()
69 msg = self.socket.recv_json()
70 self.call_handlers(msg)
70 self.call_handlers(msg)
71
71
72 def override_call_handler(self, func):
72 def override_call_handler(self, func):
73 """Permanently override the call_handler.
73 """Permanently override the call_handler.
74
74
75 The function func will be called as::
75 The function func will be called as::
76
76
77 func(handler, msg)
77 func(handler, msg)
78
78
79 And must call::
79 And must call::
80
80
81 handler(msg)
81 handler(msg)
82
82
83 in the main thread.
83 in the main thread.
84 """
84 """
85 assert callable(func), "not a callable: %r" % func
85 assert callable(func), "not a callable: %r" % func
86 self._overriden_call_handler = func
86 self._overriden_call_handler = func
87
87
88 def call_handlers(self, msg):
88 def call_handlers(self, msg):
89 handler = self.handlers.get(msg['msg_type'], None)
89 handler = self.handlers.get(msg['msg_type'], None)
90 if handler is not None:
90 if handler is not None:
91 try:
91 try:
92 self.call_handler(handler, msg)
92 self.call_handler(handler, msg)
93 except:
93 except:
94 # XXX: This should be logged at least
94 # XXX: This should be logged at least
95 traceback.print_last()
95 traceback.print_last()
96
96
97 def call_handler(self, handler, msg):
97 def call_handler(self, handler, msg):
98 if self._overriden_call_handler is not None:
98 if self._overriden_call_handler is not None:
99 self._overriden_call_handler(handler, msg)
99 self._overriden_call_handler(handler, msg)
100 elif hasattr(self, '_call_handler'):
100 elif hasattr(self, '_call_handler'):
101 call_handler = getattr(self, '_call_handler')
101 call_handler = getattr(self, '_call_handler')
102 call_handler(handler, msg)
102 call_handler(handler, msg)
103 else:
103 else:
104 raise RuntimeError('no handler!')
104 raise RuntimeError('no handler!')
105
105
106 def add_handler(self, callback, msg_type):
106 def add_handler(self, callback, msg_type):
107 """Register a callback for msg type."""
107 """Register a callback for msg type."""
108 self.handlers[msg_type] = callback
108 self.handlers[msg_type] = callback
109
109
110 def remove_handler(self, msg_type):
110 def remove_handler(self, msg_type):
111 """Remove the callback for msg type."""
111 """Remove the callback for msg type."""
112 self.handlers.pop(msg_type, None)
112 self.handlers.pop(msg_type, None)
113
113
114 def flush(self):
114 def flush(self):
115 """Immediately processes all pending messages on the SUB channel. This
115 """Immediately processes all pending messages on the SUB channel. This
116 method is thread safe.
116 method is thread safe.
117 """
117 """
118 self._flushed = False
118 self._flushed = False
119 self.ioloop.add_callback(self._flush)
119 self.ioloop.add_callback(self._flush)
120 while not self._flushed:
120 while not self._flushed:
121 time.sleep(0)
121 time.sleep(0.01)
122
122
123 def _flush(self):
123 def _flush(self):
124 """Called in this thread by the IOLoop to indicate that all events have
124 """Called in this thread by the IOLoop to indicate that all events have
125 been processed.
125 been processed.
126 """
126 """
127 self._flushed = True
127 self._flushed = True
128
128
129
129
130 class XReqSocketChannel(ZmqSocketChannel):
130 class XReqSocketChannel(ZmqSocketChannel):
131
131
132 handler_queue = None
132 handler_queue = None
133 command_queue = None
133 command_queue = None
134 handlers = None
134 handlers = None
135 _overriden_call_handler = None
135 _overriden_call_handler = None
136
136
137 def __init__(self, context, session, addr):
137 def __init__(self, context, session, addr):
138 self.handlers = {}
138 self.handlers = {}
139 self.handler_queue = Queue()
139 self.handler_queue = Queue()
140 self.command_queue = Queue()
140 self.command_queue = Queue()
141 super(XReqSocketChannel, self).__init__(context, session, addr)
141 super(XReqSocketChannel, self).__init__(context, session, addr)
142
142
143 def run(self):
143 def run(self):
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.addr)
146 self.socket.connect('tcp://%s:%i' % self.addr)
147 self.ioloop = ioloop.IOLoop()
147 self.ioloop = ioloop.IOLoop()
148 self.ioloop.add_handler(self.socket, self._handle_events,
148 self.ioloop.add_handler(self.socket, self._handle_events,
149 POLLIN|POLLOUT|POLLERR)
149 POLLIN|POLLOUT|POLLERR)
150 self.ioloop.start()
150 self.ioloop.start()
151
151
152 def _handle_events(self, socket, events):
152 def _handle_events(self, socket, events):
153 # Turn on and off POLLOUT depending on if we have made a request
153 # Turn on and off POLLOUT depending on if we have made a request
154 if events & POLLERR:
154 if events & POLLERR:
155 self._handle_err()
155 self._handle_err()
156 if events & POLLOUT:
156 if events & POLLOUT:
157 self._handle_send()
157 self._handle_send()
158 if events & POLLIN:
158 if events & POLLIN:
159 self._handle_recv()
159 self._handle_recv()
160
160
161 def _handle_recv(self):
161 def _handle_recv(self):
162 msg = self.socket.recv_json()
162 msg = self.socket.recv_json()
163 self.call_handlers(msg)
163 self.call_handlers(msg)
164
164
165 def _handle_send(self):
165 def _handle_send(self):
166 try:
166 try:
167 msg = self.command_queue.get(False)
167 msg = self.command_queue.get(False)
168 except Empty:
168 except Empty:
169 pass
169 pass
170 else:
170 else:
171 self.socket.send_json(msg)
171 self.socket.send_json(msg)
172
172
173 def _handle_err(self):
173 def _handle_err(self):
174 raise zmq.ZmqError()
174 raise zmq.ZmqError()
175
175
176 def _queue_request(self, msg, callback):
176 def _queue_request(self, msg, callback):
177 handler = self._find_handler(msg['msg_type'], callback)
177 handler = self._find_handler(msg['msg_type'], callback)
178 self.handler_queue.put(handler)
178 self.handler_queue.put(handler)
179 self.command_queue.put(msg)
179 self.command_queue.put(msg)
180
180
181 def execute(self, code, callback=None):
181 def execute(self, code, callback=None):
182 # Create class for content/msg creation. Related to, but possibly
182 # Create class for content/msg creation. Related to, but possibly
183 # not in Session.
183 # not in Session.
184 content = dict(code=code)
184 content = dict(code=code)
185 msg = self.session.msg('execute_request', content)
185 msg = self.session.msg('execute_request', content)
186 self._queue_request(msg, callback)
186 self._queue_request(msg, callback)
187 return msg['header']['msg_id']
187 return msg['header']['msg_id']
188
188
189 def complete(self, text, line, block=None, callback=None):
189 def complete(self, text, line, block=None, callback=None):
190 content = dict(text=text, line=line)
190 content = dict(text=text, line=line)
191 msg = self.session.msg('complete_request', content)
191 msg = self.session.msg('complete_request', content)
192 self._queue_request(msg, callback)
192 self._queue_request(msg, callback)
193 return msg['header']['msg_id']
193 return msg['header']['msg_id']
194
194
195 def object_info(self, oname, callback=None):
195 def object_info(self, oname, callback=None):
196 content = dict(oname=oname)
196 content = dict(oname=oname)
197 msg = self.session.msg('object_info_request', content)
197 msg = self.session.msg('object_info_request', content)
198 self._queue_request(msg, callback)
198 self._queue_request(msg, callback)
199 return msg['header']['msg_id']
199 return msg['header']['msg_id']
200
200
201 def _find_handler(self, name, callback):
201 def _find_handler(self, name, callback):
202 if callback is not None:
202 if callback is not None:
203 return callback
203 return callback
204 handler = self.handlers.get(name)
204 handler = self.handlers.get(name)
205 if handler is None:
205 if handler is None:
206 raise MissingHandlerError(
206 raise MissingHandlerError(
207 'No handler defined for method: %s' % name)
207 'No handler defined for method: %s' % name)
208 return handler
208 return handler
209
209
210 def override_call_handler(self, func):
210 def override_call_handler(self, func):
211 """Permanently override the call_handler.
211 """Permanently override the call_handler.
212
212
213 The function func will be called as::
213 The function func will be called as::
214
214
215 func(handler, msg)
215 func(handler, msg)
216
216
217 And must call::
217 And must call::
218
218
219 handler(msg)
219 handler(msg)
220
220
221 in the main thread.
221 in the main thread.
222 """
222 """
223 assert callable(func), "not a callable: %r" % func
223 assert callable(func), "not a callable: %r" % func
224 self._overriden_call_handler = func
224 self._overriden_call_handler = func
225
225
226 def call_handlers(self, msg):
226 def call_handlers(self, msg):
227 try:
227 try:
228 handler = self.handler_queue.get(False)
228 handler = self.handler_queue.get(False)
229 except Empty:
229 except Empty:
230 print "Message received with no handler!!!"
230 print "Message received with no handler!!!"
231 print msg
231 print msg
232 else:
232 else:
233 self.call_handler(handler, msg)
233 self.call_handler(handler, msg)
234
234
235 def call_handler(self, handler, msg):
235 def call_handler(self, handler, msg):
236 if self._overriden_call_handler is not None:
236 if self._overriden_call_handler is not None:
237 self._overriden_call_handler(handler, msg)
237 self._overriden_call_handler(handler, msg)
238 elif hasattr(self, '_call_handler'):
238 elif hasattr(self, '_call_handler'):
239 call_handler = getattr(self, '_call_handler')
239 call_handler = getattr(self, '_call_handler')
240 call_handler(handler, msg)
240 call_handler(handler, msg)
241 else:
241 else:
242 raise RuntimeError('no handler!')
242 raise RuntimeError('no handler!')
243
243
244
244
245 class RepSocketChannel(ZmqSocketChannel):
245 class RepSocketChannel(ZmqSocketChannel):
246
246
247 def on_raw_input():
247 def on_raw_input():
248 pass
248 pass
249
249
250
250
251 class KernelManager(HasTraits):
251 class KernelManager(HasTraits):
252
252
253 # The addresses to use for the various channels. Should be tuples of form
253 # The addresses to use for the various channels. Should be tuples of form
254 # (ip_address, port).
254 # (ip_address, port).
255 sub_address = Any
255 sub_address = Any
256 xreq_address = Any
256 xreq_address = Any
257 rep_address = Any
257 rep_address = Any
258 # FIXME: Add Tuple to Traitlets.
258 # FIXME: Add Tuple to Traitlets.
259 #sub_address = Tuple(Str, Int)
259 #sub_address = Tuple(Str, Int)
260 #xreq_address = Tuple(Str, Int)
260 #xreq_address = Tuple(Str, Int)
261 #rep_address = Tuple(Str, Int)
261 #rep_address = Tuple(Str, Int)
262
262
263 # The PyZMQ Context to use for communication with the kernel.
263 # The PyZMQ Context to use for communication with the kernel.
264 context = Instance(zmq.Context, ())
264 context = Instance(zmq.Context, ())
265
265
266 # The Session to use for communication with the kernel.
266 # The Session to use for communication with the kernel.
267 session = Instance(Session, ())
267 session = Instance(Session, ())
268
268
269 # The classes to use for the various channels.
269 # The classes to use for the various channels.
270 sub_channel_class = Type(SubSocketChannel)
270 sub_channel_class = Type(SubSocketChannel)
271 xreq_channel_class = Type(XReqSocketChannel)
271 xreq_channel_class = Type(XReqSocketChannel)
272 rep_channel_class = Type(RepSocketChannel)
272 rep_channel_class = Type(RepSocketChannel)
273
273
274 # Protected traits.
274 # Protected traits.
275 _sub_channel = Any
275 _sub_channel = Any
276 _xreq_channel = Any
276 _xreq_channel = Any
277 _rep_channel = Any
277 _rep_channel = Any
278
278
279 def __init__(self, xreq_address, sub_address, rep_address, **traits):
279 def __init__(self, xreq_address, sub_address, rep_address, **traits):
280 super(KernelManager, self).__init__()
280 super(KernelManager, self).__init__()
281
281
282 self.xreq_address = xreq_address
282 self.xreq_address = xreq_address
283 self.sub_address = sub_address
283 self.sub_address = sub_address
284 self.rep_address = rep_address
284 self.rep_address = rep_address
285
285
286 # FIXME: This should be the business of HasTraits. The convention is:
286 # FIXME: This should be the business of HasTraits. The convention is:
287 # HasTraits.__init__(self, **traits_to_be_initialized.)
287 # HasTraits.__init__(self, **traits_to_be_initialized.)
288 for trait in traits:
288 for trait in traits:
289 setattr(self, trait, traits[trait])
289 setattr(self, trait, traits[trait])
290
290
291 def start_kernel(self):
291 def start_kernel(self):
292 """Start a localhost kernel on ip and port.
292 """Start a localhost kernel on ip and port.
293
293
294 The SUB channel is for the frontend to receive messages published by
294 The SUB channel is for the frontend to receive messages published by
295 the kernel.
295 the kernel.
296
296
297 The REQ channel is for the frontend to make requests of the kernel.
297 The REQ channel is for the frontend to make requests of the kernel.
298
298
299 The REP channel is for the kernel to request stdin (raw_input) from
299 The REP channel is for the kernel to request stdin (raw_input) from
300 the frontend.
300 the frontend.
301 """
301 """
302
302
303 def kill_kernel(self):
303 def kill_kernel(self):
304 """Kill the running kernel"""
304 """Kill the running kernel"""
305
305
306 def is_alive(self):
306 def is_alive(self):
307 """Is the kernel alive?"""
307 """Is the kernel alive?"""
308 return True
308 return True
309
309
310 def signal_kernel(self, signum):
310 def signal_kernel(self, signum):
311 """Send signum to the kernel."""
311 """Send signum to the kernel."""
312
312
313 @property
313 @property
314 def sub_channel(self):
314 def sub_channel(self):
315 """Get the SUB socket channel object."""
315 """Get the SUB socket channel object."""
316 if self._sub_channel is None:
316 if self._sub_channel is None:
317 self._sub_channel = self.sub_channel_class(
317 self._sub_channel = self.sub_channel_class(
318 self.context, self.session, self.sub_address)
318 self.context, self.session, self.sub_address)
319 return self._sub_channel
319 return self._sub_channel
320
320
321 @property
321 @property
322 def xreq_channel(self):
322 def xreq_channel(self):
323 """Get the REQ socket channel object to make requests of the kernel."""
323 """Get the REQ socket channel object to make requests of the kernel."""
324 if self._xreq_channel is None:
324 if self._xreq_channel is None:
325 self._xreq_channel = self.xreq_channel_class(
325 self._xreq_channel = self.xreq_channel_class(
326 self.context, self.session, self.xreq_address)
326 self.context, self.session, self.xreq_address)
327 return self._xreq_channel
327 return self._xreq_channel
328
328
329 @property
329 @property
330 def rep_channel(self):
330 def rep_channel(self):
331 """Get the REP socket channel object to handle stdin (raw_input)."""
331 """Get the REP socket channel object to handle stdin (raw_input)."""
332 if self._rep_channel is None:
332 if self._rep_channel is None:
333 self._rep_channel = self.rep_channel_class(
333 self._rep_channel = self.rep_channel_class(
334 self.context, self.session, self.rep_address)
334 self.context, self.session, self.rep_address)
335 return self._rep_channel
335 return self._rep_channel
General Comments 0
You need to be logged in to leave comments. Login now