##// END OF EJS Templates
Merge branch 'mynewkernel' into upstream-newkernel
Brian Granger -
r2911:49041c42 merge
parent child Browse files
Show More
@@ -0,0 +1,43 b''
1 """The client and server for a basic ping-pong style heartbeat.
2 """
3
4 #-----------------------------------------------------------------------------
5 # Copyright (C) 2008-2010 The IPython Development Team
6 #
7 # Distributed under the terms of the BSD License. The full license is in
8 # the file COPYING, distributed as part of this software.
9 #-----------------------------------------------------------------------------
10
11 #-----------------------------------------------------------------------------
12 # Imports
13 #-----------------------------------------------------------------------------
14
15 import sys
16 from threading import Thread
17
18 import zmq
19
20 #-----------------------------------------------------------------------------
21 # Code
22 #-----------------------------------------------------------------------------
23
24
25 class Heartbeat(Thread):
26 "A simple ping-pong style heartbeat that runs in a thread."
27
28 def __init__(self, context, addr=('127.0.0.1', 0)):
29 Thread.__init__(self)
30 self.context = context
31 self.addr = addr
32 self.ip = addr[0]
33 self.port = addr[1]
34 self.daemon = True
35
36 def run(self):
37 self.socket = self.context.socket(zmq.REP)
38 if self.port == 0:
39 self.port = self.socket.bind_to_random_port('tcp://%s' % self.ip)
40 else:
41 self.socket.bind('tcp://%s:%i' % self.addr)
42 zmq.device(zmq.FORWARDER, self.socket, self.socket)
43
@@ -1,93 +1,109 b''
1 """ Defines a convenient mix-in class for implementing Qt frontends.
1 """ Defines a convenient mix-in class for implementing Qt frontends.
2 """
2 """
3
3
4 class BaseFrontendMixin(object):
4 class BaseFrontendMixin(object):
5 """ A mix-in class for implementing Qt frontends.
5 """ A mix-in class for implementing Qt frontends.
6
6
7 To handle messages of a particular type, frontends need only define an
7 To handle messages of a particular type, frontends need only define an
8 appropriate handler method. For example, to handle 'stream' messaged, define
8 appropriate handler method. For example, to handle 'stream' messaged, define
9 a '_handle_stream(msg)' method.
9 a '_handle_stream(msg)' method.
10 """
10 """
11
11
12 #---------------------------------------------------------------------------
12 #---------------------------------------------------------------------------
13 # 'BaseFrontendMixin' concrete interface
13 # 'BaseFrontendMixin' concrete interface
14 #---------------------------------------------------------------------------
14 #---------------------------------------------------------------------------
15
15
16 def _get_kernel_manager(self):
16 def _get_kernel_manager(self):
17 """ Returns the current kernel manager.
17 """ Returns the current kernel manager.
18 """
18 """
19 return self._kernel_manager
19 return self._kernel_manager
20
20
21 def _set_kernel_manager(self, kernel_manager):
21 def _set_kernel_manager(self, kernel_manager):
22 """ Disconnect from the current kernel manager (if any) and set a new
22 """ Disconnect from the current kernel manager (if any) and set a new
23 kernel manager.
23 kernel manager.
24 """
24 """
25 # Disconnect the old kernel manager, if necessary.
25 # Disconnect the old kernel manager, if necessary.
26 old_manager = self._kernel_manager
26 old_manager = self._kernel_manager
27 if old_manager is not None:
27 if old_manager is not None:
28 old_manager.started_channels.disconnect(self._started_channels)
28 old_manager.started_channels.disconnect(self._started_channels)
29 old_manager.stopped_channels.disconnect(self._stopped_channels)
29 old_manager.stopped_channels.disconnect(self._stopped_channels)
30
30
31 # Disconnect the old kernel manager's channels.
31 # Disconnect the old kernel manager's channels.
32 old_manager.sub_channel.message_received.disconnect(self._dispatch)
32 old_manager.sub_channel.message_received.disconnect(self._dispatch)
33 old_manager.xreq_channel.message_received.disconnect(self._dispatch)
33 old_manager.xreq_channel.message_received.disconnect(self._dispatch)
34 old_manager.rep_channel.message_received.connect(self._dispatch)
34 old_manager.rep_channel.message_received.disconnect(self._dispatch)
35
35 old_manager.hb_channel.kernel_died.disconnect(self._handle_kernel_died)
36
36 # Handle the case where the old kernel manager is still listening.
37 # Handle the case where the old kernel manager is still listening.
37 if old_manager.channels_running:
38 if old_manager.channels_running:
38 self._stopped_channels()
39 self._stopped_channels()
39
40
40 # Set the new kernel manager.
41 # Set the new kernel manager.
41 self._kernel_manager = kernel_manager
42 self._kernel_manager = kernel_manager
42 if kernel_manager is None:
43 if kernel_manager is None:
43 return
44 return
44
45
45 # Connect the new kernel manager.
46 # Connect the new kernel manager.
46 kernel_manager.started_channels.connect(self._started_channels)
47 kernel_manager.started_channels.connect(self._started_channels)
47 kernel_manager.stopped_channels.connect(self._stopped_channels)
48 kernel_manager.stopped_channels.connect(self._stopped_channels)
48
49
49 # Connect the new kernel manager's channels.
50 # Connect the new kernel manager's channels.
50 kernel_manager.sub_channel.message_received.connect(self._dispatch)
51 kernel_manager.sub_channel.message_received.connect(self._dispatch)
51 kernel_manager.xreq_channel.message_received.connect(self._dispatch)
52 kernel_manager.xreq_channel.message_received.connect(self._dispatch)
52 kernel_manager.rep_channel.message_received.connect(self._dispatch)
53 kernel_manager.rep_channel.message_received.connect(self._dispatch)
53
54 kernel_manager.hb_channel.kernel_died.connect(self._handle_kernel_died)
55
54 # Handle the case where the kernel manager started channels before
56 # Handle the case where the kernel manager started channels before
55 # we connected.
57 # we connected.
56 if kernel_manager.channels_running:
58 if kernel_manager.channels_running:
57 self._started_channels()
59 self._started_channels()
58
60
59 kernel_manager = property(_get_kernel_manager, _set_kernel_manager)
61 kernel_manager = property(_get_kernel_manager, _set_kernel_manager)
60
62
61 #---------------------------------------------------------------------------
63 #---------------------------------------------------------------------------
62 # 'BaseFrontendMixin' abstract interface
64 # 'BaseFrontendMixin' abstract interface
63 #---------------------------------------------------------------------------
65 #---------------------------------------------------------------------------
64
66
65 def _started_channels(self):
67 def _started_channels(self):
66 """ Called when the KernelManager channels have started listening or
68 """ Called when the KernelManager channels have started listening or
67 when the frontend is assigned an already listening KernelManager.
69 when the frontend is assigned an already listening KernelManager.
68 """
70 """
69
71
70 def _stopped_channels(self):
72 def _stopped_channels(self):
71 """ Called when the KernelManager channels have stopped listening or
73 """ Called when the KernelManager channels have stopped listening or
72 when a listening KernelManager is removed from the frontend.
74 when a listening KernelManager is removed from the frontend.
73 """
75 """
74
76
75 #---------------------------------------------------------------------------
77 #---------------------------------------------------------------------------
76 # 'BaseFrontendMixin' protected interface
78 # 'BaseFrontendMixin' protected interface
77 #---------------------------------------------------------------------------
79 #---------------------------------------------------------------------------
78
80
79 def _dispatch(self, msg):
81 def _dispatch(self, msg):
80 """ Calls the frontend handler associated with the message type of the
82 """ Calls the frontend handler associated with the message type of the
81 given message.
83 given message.
82 """
84 """
83 msg_type = msg['msg_type']
85 msg_type = msg['msg_type']
84 handler = getattr(self, '_handle_' + msg_type, None)
86 handler = getattr(self, '_handle_' + msg_type, None)
85 if handler:
87 if handler:
86 handler(msg)
88 handler(msg)
87
89
88 def _is_from_this_session(self, msg):
90 def _is_from_this_session(self, msg):
89 """ Returns whether a reply from the kernel originated from a request
91 """ Returns whether a reply from the kernel originated from a request
90 from this frontend.
92 from this frontend.
91 """
93 """
92 session = self._kernel_manager.session.session
94 session = self._kernel_manager.session.session
93 return msg['parent_header']['session'] == session
95 return msg['parent_header']['session'] == session
96
97 def _handle_kernel_died(self, since_last_heartbeat):
98 """ This is called when the ``kernel_died`` signal is emitted.
99
100 This method is called when the kernel heartbeat has not been
101 active for a certain amount of time. The typical action will be to
102 give the user the option of restarting the kernel.
103
104 Parameters
105 ----------
106 since_last_heartbeat : float
107 The time since the heartbeat was last received.
108 """
109 pass
@@ -1,425 +1,436 b''
1 # Standard library imports
1 # Standard library imports
2 import signal
2 import signal
3 import sys
3 import sys
4
4
5 # System library imports
5 # System library imports
6 from pygments.lexers import PythonLexer
6 from pygments.lexers import PythonLexer
7 from PyQt4 import QtCore, QtGui
7 from PyQt4 import QtCore, QtGui
8
8
9 # Local imports
9 # Local imports
10 from IPython.core.inputsplitter import InputSplitter
10 from IPython.core.inputsplitter import InputSplitter
11 from IPython.frontend.qt.base_frontend_mixin import BaseFrontendMixin
11 from IPython.frontend.qt.base_frontend_mixin import BaseFrontendMixin
12 from IPython.utils.traitlets import Bool
12 from IPython.utils.traitlets import Bool
13 from bracket_matcher import BracketMatcher
13 from bracket_matcher import BracketMatcher
14 from call_tip_widget import CallTipWidget
14 from call_tip_widget import CallTipWidget
15 from completion_lexer import CompletionLexer
15 from completion_lexer import CompletionLexer
16 from console_widget import HistoryConsoleWidget
16 from console_widget import HistoryConsoleWidget
17 from pygments_highlighter import PygmentsHighlighter
17 from pygments_highlighter import PygmentsHighlighter
18
18
19
19
20 class FrontendHighlighter(PygmentsHighlighter):
20 class FrontendHighlighter(PygmentsHighlighter):
21 """ A PygmentsHighlighter that can be turned on and off and that ignores
21 """ A PygmentsHighlighter that can be turned on and off and that ignores
22 prompts.
22 prompts.
23 """
23 """
24
24
25 def __init__(self, frontend):
25 def __init__(self, frontend):
26 super(FrontendHighlighter, self).__init__(frontend._control.document())
26 super(FrontendHighlighter, self).__init__(frontend._control.document())
27 self._current_offset = 0
27 self._current_offset = 0
28 self._frontend = frontend
28 self._frontend = frontend
29 self.highlighting_on = False
29 self.highlighting_on = False
30
30
31 def highlightBlock(self, qstring):
31 def highlightBlock(self, qstring):
32 """ Highlight a block of text. Reimplemented to highlight selectively.
32 """ Highlight a block of text. Reimplemented to highlight selectively.
33 """
33 """
34 if not self.highlighting_on:
34 if not self.highlighting_on:
35 return
35 return
36
36
37 # The input to this function is unicode string that may contain
37 # The input to this function is unicode string that may contain
38 # paragraph break characters, non-breaking spaces, etc. Here we acquire
38 # paragraph break characters, non-breaking spaces, etc. Here we acquire
39 # the string as plain text so we can compare it.
39 # the string as plain text so we can compare it.
40 current_block = self.currentBlock()
40 current_block = self.currentBlock()
41 string = self._frontend._get_block_plain_text(current_block)
41 string = self._frontend._get_block_plain_text(current_block)
42
42
43 # Decide whether to check for the regular or continuation prompt.
43 # Decide whether to check for the regular or continuation prompt.
44 if current_block.contains(self._frontend._prompt_pos):
44 if current_block.contains(self._frontend._prompt_pos):
45 prompt = self._frontend._prompt
45 prompt = self._frontend._prompt
46 else:
46 else:
47 prompt = self._frontend._continuation_prompt
47 prompt = self._frontend._continuation_prompt
48
48
49 # Don't highlight the part of the string that contains the prompt.
49 # Don't highlight the part of the string that contains the prompt.
50 if string.startswith(prompt):
50 if string.startswith(prompt):
51 self._current_offset = len(prompt)
51 self._current_offset = len(prompt)
52 qstring.remove(0, len(prompt))
52 qstring.remove(0, len(prompt))
53 else:
53 else:
54 self._current_offset = 0
54 self._current_offset = 0
55
55
56 PygmentsHighlighter.highlightBlock(self, qstring)
56 PygmentsHighlighter.highlightBlock(self, qstring)
57
57
58 def rehighlightBlock(self, block):
58 def rehighlightBlock(self, block):
59 """ Reimplemented to temporarily enable highlighting if disabled.
59 """ Reimplemented to temporarily enable highlighting if disabled.
60 """
60 """
61 old = self.highlighting_on
61 old = self.highlighting_on
62 self.highlighting_on = True
62 self.highlighting_on = True
63 super(FrontendHighlighter, self).rehighlightBlock(block)
63 super(FrontendHighlighter, self).rehighlightBlock(block)
64 self.highlighting_on = old
64 self.highlighting_on = old
65
65
66 def setFormat(self, start, count, format):
66 def setFormat(self, start, count, format):
67 """ Reimplemented to highlight selectively.
67 """ Reimplemented to highlight selectively.
68 """
68 """
69 start += self._current_offset
69 start += self._current_offset
70 PygmentsHighlighter.setFormat(self, start, count, format)
70 PygmentsHighlighter.setFormat(self, start, count, format)
71
71
72
72
73 class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):
73 class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):
74 """ A Qt frontend for a generic Python kernel.
74 """ A Qt frontend for a generic Python kernel.
75 """
75 """
76
76
77 # An option and corresponding signal for overriding the default kernel
77 # An option and corresponding signal for overriding the default kernel
78 # interrupt behavior.
78 # interrupt behavior.
79 custom_interrupt = Bool(False)
79 custom_interrupt = Bool(False)
80 custom_interrupt_requested = QtCore.pyqtSignal()
80 custom_interrupt_requested = QtCore.pyqtSignal()
81
81
82 # An option and corresponding signal for overriding the default kernel
82 # An option and corresponding signal for overriding the default kernel
83 # restart behavior.
83 # restart behavior.
84 custom_restart = Bool(False)
84 custom_restart = Bool(False)
85 custom_restart_requested = QtCore.pyqtSignal()
85 custom_restart_requested = QtCore.pyqtSignal()
86
86
87 # Emitted when an 'execute_reply' has been received from the kernel and
87 # Emitted when an 'execute_reply' has been received from the kernel and
88 # processed by the FrontendWidget.
88 # processed by the FrontendWidget.
89 executed = QtCore.pyqtSignal(object)
89 executed = QtCore.pyqtSignal(object)
90
90
91 # Protected class variables.
91 # Protected class variables.
92 _input_splitter_class = InputSplitter
92 _input_splitter_class = InputSplitter
93 _possible_kernel_restart = Bool(False)
93
94
94 #---------------------------------------------------------------------------
95 #---------------------------------------------------------------------------
95 # 'object' interface
96 # 'object' interface
96 #---------------------------------------------------------------------------
97 #---------------------------------------------------------------------------
97
98
98 def __init__(self, *args, **kw):
99 def __init__(self, *args, **kw):
99 super(FrontendWidget, self).__init__(*args, **kw)
100 super(FrontendWidget, self).__init__(*args, **kw)
100
101
101 # FrontendWidget protected variables.
102 # FrontendWidget protected variables.
102 self._bracket_matcher = BracketMatcher(self._control)
103 self._bracket_matcher = BracketMatcher(self._control)
103 self._call_tip_widget = CallTipWidget(self._control)
104 self._call_tip_widget = CallTipWidget(self._control)
104 self._completion_lexer = CompletionLexer(PythonLexer())
105 self._completion_lexer = CompletionLexer(PythonLexer())
105 self._hidden = False
106 self._hidden = False
106 self._highlighter = FrontendHighlighter(self)
107 self._highlighter = FrontendHighlighter(self)
107 self._input_splitter = self._input_splitter_class(input_mode='block')
108 self._input_splitter = self._input_splitter_class(input_mode='block')
108 self._kernel_manager = None
109 self._kernel_manager = None
109
110
110 # Configure the ConsoleWidget.
111 # Configure the ConsoleWidget.
111 self.tab_width = 4
112 self.tab_width = 4
112 self._set_continuation_prompt('... ')
113 self._set_continuation_prompt('... ')
113
114
114 # Connect signal handlers.
115 # Connect signal handlers.
115 document = self._control.document()
116 document = self._control.document()
116 document.contentsChange.connect(self._document_contents_change)
117 document.contentsChange.connect(self._document_contents_change)
117
118
118 #---------------------------------------------------------------------------
119 #---------------------------------------------------------------------------
119 # 'ConsoleWidget' abstract interface
120 # 'ConsoleWidget' abstract interface
120 #---------------------------------------------------------------------------
121 #---------------------------------------------------------------------------
121
122
122 def _is_complete(self, source, interactive):
123 def _is_complete(self, source, interactive):
123 """ Returns whether 'source' can be completely processed and a new
124 """ Returns whether 'source' can be completely processed and a new
124 prompt created. When triggered by an Enter/Return key press,
125 prompt created. When triggered by an Enter/Return key press,
125 'interactive' is True; otherwise, it is False.
126 'interactive' is True; otherwise, it is False.
126 """
127 """
127 complete = self._input_splitter.push(source.expandtabs(4))
128 complete = self._input_splitter.push(source.expandtabs(4))
128 if interactive:
129 if interactive:
129 complete = not self._input_splitter.push_accepts_more()
130 complete = not self._input_splitter.push_accepts_more()
130 return complete
131 return complete
131
132
132 def _execute(self, source, hidden):
133 def _execute(self, source, hidden):
133 """ Execute 'source'. If 'hidden', do not show any output.
134 """ Execute 'source'. If 'hidden', do not show any output.
134 """
135 """
135 self.kernel_manager.xreq_channel.execute(source, hidden)
136 self.kernel_manager.xreq_channel.execute(source, hidden)
136 self._hidden = hidden
137 self._hidden = hidden
137
138
138 def _prompt_started_hook(self):
139 def _prompt_started_hook(self):
139 """ Called immediately after a new prompt is displayed.
140 """ Called immediately after a new prompt is displayed.
140 """
141 """
141 if not self._reading:
142 if not self._reading:
142 self._highlighter.highlighting_on = True
143 self._highlighter.highlighting_on = True
143
144
144 def _prompt_finished_hook(self):
145 def _prompt_finished_hook(self):
145 """ Called immediately after a prompt is finished, i.e. when some input
146 """ Called immediately after a prompt is finished, i.e. when some input
146 will be processed and a new prompt displayed.
147 will be processed and a new prompt displayed.
147 """
148 """
148 if not self._reading:
149 if not self._reading:
149 self._highlighter.highlighting_on = False
150 self._highlighter.highlighting_on = False
150
151
151 def _tab_pressed(self):
152 def _tab_pressed(self):
152 """ Called when the tab key is pressed. Returns whether to continue
153 """ Called when the tab key is pressed. Returns whether to continue
153 processing the event.
154 processing the event.
154 """
155 """
155 # Perform tab completion if:
156 # Perform tab completion if:
156 # 1) The cursor is in the input buffer.
157 # 1) The cursor is in the input buffer.
157 # 2) There is a non-whitespace character before the cursor.
158 # 2) There is a non-whitespace character before the cursor.
158 text = self._get_input_buffer_cursor_line()
159 text = self._get_input_buffer_cursor_line()
159 if text is None:
160 if text is None:
160 return False
161 return False
161 complete = bool(text[:self._get_input_buffer_cursor_column()].strip())
162 complete = bool(text[:self._get_input_buffer_cursor_column()].strip())
162 if complete:
163 if complete:
163 self._complete()
164 self._complete()
164 return not complete
165 return not complete
165
166
166 #---------------------------------------------------------------------------
167 #---------------------------------------------------------------------------
167 # 'ConsoleWidget' protected interface
168 # 'ConsoleWidget' protected interface
168 #---------------------------------------------------------------------------
169 #---------------------------------------------------------------------------
169
170
170 def _event_filter_console_keypress(self, event):
171 def _event_filter_console_keypress(self, event):
171 """ Reimplemented to allow execution interruption.
172 """ Reimplemented to allow execution interruption.
172 """
173 """
173 key = event.key()
174 key = event.key()
174 if self._control_key_down(event.modifiers()):
175 if self._control_key_down(event.modifiers()):
175 if key == QtCore.Qt.Key_C and self._executing:
176 if key == QtCore.Qt.Key_C and self._executing:
176 self._kernel_interrupt()
177 self._kernel_interrupt()
177 return True
178 return True
178 elif key == QtCore.Qt.Key_Period:
179 elif key == QtCore.Qt.Key_Period:
179 self._kernel_restart()
180 message = 'Are you sure you want to restart the kernel?'
181 self._kernel_restart(message)
180 return True
182 return True
181 return super(FrontendWidget, self)._event_filter_console_keypress(event)
183 return super(FrontendWidget, self)._event_filter_console_keypress(event)
182
184
183 def _insert_continuation_prompt(self, cursor):
185 def _insert_continuation_prompt(self, cursor):
184 """ Reimplemented for auto-indentation.
186 """ Reimplemented for auto-indentation.
185 """
187 """
186 super(FrontendWidget, self)._insert_continuation_prompt(cursor)
188 super(FrontendWidget, self)._insert_continuation_prompt(cursor)
187 spaces = self._input_splitter.indent_spaces
189 spaces = self._input_splitter.indent_spaces
188 cursor.insertText('\t' * (spaces / self.tab_width))
190 cursor.insertText('\t' * (spaces / self.tab_width))
189 cursor.insertText(' ' * (spaces % self.tab_width))
191 cursor.insertText(' ' * (spaces % self.tab_width))
190
192
191 #---------------------------------------------------------------------------
193 #---------------------------------------------------------------------------
192 # 'BaseFrontendMixin' abstract interface
194 # 'BaseFrontendMixin' abstract interface
193 #---------------------------------------------------------------------------
195 #---------------------------------------------------------------------------
194
196
195 def _handle_complete_reply(self, rep):
197 def _handle_complete_reply(self, rep):
196 """ Handle replies for tab completion.
198 """ Handle replies for tab completion.
197 """
199 """
198 cursor = self._get_cursor()
200 cursor = self._get_cursor()
199 if rep['parent_header']['msg_id'] == self._complete_id and \
201 if rep['parent_header']['msg_id'] == self._complete_id and \
200 cursor.position() == self._complete_pos:
202 cursor.position() == self._complete_pos:
201 text = '.'.join(self._get_context())
203 text = '.'.join(self._get_context())
202 cursor.movePosition(QtGui.QTextCursor.Left, n=len(text))
204 cursor.movePosition(QtGui.QTextCursor.Left, n=len(text))
203 self._complete_with_items(cursor, rep['content']['matches'])
205 self._complete_with_items(cursor, rep['content']['matches'])
204
206
205 def _handle_execute_reply(self, msg):
207 def _handle_execute_reply(self, msg):
206 """ Handles replies for code execution.
208 """ Handles replies for code execution.
207 """
209 """
208 if not self._hidden:
210 if not self._hidden:
209 # Make sure that all output from the SUB channel has been processed
211 # Make sure that all output from the SUB channel has been processed
210 # before writing a new prompt.
212 # before writing a new prompt.
211 self.kernel_manager.sub_channel.flush()
213 self.kernel_manager.sub_channel.flush()
212
214
213 content = msg['content']
215 content = msg['content']
214 status = content['status']
216 status = content['status']
215 if status == 'ok':
217 if status == 'ok':
216 self._process_execute_ok(msg)
218 self._process_execute_ok(msg)
217 elif status == 'error':
219 elif status == 'error':
218 self._process_execute_error(msg)
220 self._process_execute_error(msg)
219 elif status == 'abort':
221 elif status == 'abort':
220 self._process_execute_abort(msg)
222 self._process_execute_abort(msg)
221
223
222 self._show_interpreter_prompt_for_reply(msg)
224 self._show_interpreter_prompt_for_reply(msg)
223 self.executed.emit(msg)
225 self.executed.emit(msg)
224
226
225 def _handle_input_request(self, msg):
227 def _handle_input_request(self, msg):
226 """ Handle requests for raw_input.
228 """ Handle requests for raw_input.
227 """
229 """
228 if self._hidden:
230 if self._hidden:
229 raise RuntimeError('Request for raw input during hidden execution.')
231 raise RuntimeError('Request for raw input during hidden execution.')
230
232
231 # Make sure that all output from the SUB channel has been processed
233 # Make sure that all output from the SUB channel has been processed
232 # before entering readline mode.
234 # before entering readline mode.
233 self.kernel_manager.sub_channel.flush()
235 self.kernel_manager.sub_channel.flush()
234
236
235 def callback(line):
237 def callback(line):
236 self.kernel_manager.rep_channel.input(line)
238 self.kernel_manager.rep_channel.input(line)
237 self._readline(msg['content']['prompt'], callback=callback)
239 self._readline(msg['content']['prompt'], callback=callback)
238
240
239 def _handle_object_info_reply(self, rep):
241 def _handle_object_info_reply(self, rep):
240 """ Handle replies for call tips.
242 """ Handle replies for call tips.
241 """
243 """
242 cursor = self._get_cursor()
244 cursor = self._get_cursor()
243 if rep['parent_header']['msg_id'] == self._call_tip_id and \
245 if rep['parent_header']['msg_id'] == self._call_tip_id and \
244 cursor.position() == self._call_tip_pos:
246 cursor.position() == self._call_tip_pos:
245 doc = rep['content']['docstring']
247 doc = rep['content']['docstring']
246 if doc:
248 if doc:
247 self._call_tip_widget.show_docstring(doc)
249 self._call_tip_widget.show_docstring(doc)
248
250
249 def _handle_pyout(self, msg):
251 def _handle_pyout(self, msg):
250 """ Handle display hook output.
252 """ Handle display hook output.
251 """
253 """
252 if not self._hidden and self._is_from_this_session(msg):
254 if not self._hidden and self._is_from_this_session(msg):
253 self._append_plain_text(msg['content']['data'] + '\n')
255 self._append_plain_text(msg['content']['data'] + '\n')
254
256
255 def _handle_stream(self, msg):
257 def _handle_stream(self, msg):
256 """ Handle stdout, stderr, and stdin.
258 """ Handle stdout, stderr, and stdin.
257 """
259 """
258 if not self._hidden and self._is_from_this_session(msg):
260 if not self._hidden and self._is_from_this_session(msg):
259 self._append_plain_text(msg['content']['data'])
261 self._append_plain_text(msg['content']['data'])
260 self._control.moveCursor(QtGui.QTextCursor.End)
262 self._control.moveCursor(QtGui.QTextCursor.End)
261
263
262 def _started_channels(self):
264 def _started_channels(self):
263 """ Called when the KernelManager channels have started listening or
265 """ Called when the KernelManager channels have started listening or
264 when the frontend is assigned an already listening KernelManager.
266 when the frontend is assigned an already listening KernelManager.
265 """
267 """
266 self._control.clear()
268 self._control.clear()
267 self._append_plain_text(self._get_banner())
269 self._append_plain_text(self._get_banner())
268 self._show_interpreter_prompt()
270 self._show_interpreter_prompt()
269
271
270 def _stopped_channels(self):
272 def _stopped_channels(self):
271 """ Called when the KernelManager channels have stopped listening or
273 """ Called when the KernelManager channels have stopped listening or
272 when a listening KernelManager is removed from the frontend.
274 when a listening KernelManager is removed from the frontend.
273 """
275 """
274 self._executing = self._reading = False
276 self._executing = self._reading = False
275 self._highlighter.highlighting_on = False
277 self._highlighter.highlighting_on = False
276
278
277 #---------------------------------------------------------------------------
279 #---------------------------------------------------------------------------
278 # 'FrontendWidget' interface
280 # 'FrontendWidget' interface
279 #---------------------------------------------------------------------------
281 #---------------------------------------------------------------------------
280
282
281 def execute_file(self, path, hidden=False):
283 def execute_file(self, path, hidden=False):
282 """ Attempts to execute file with 'path'. If 'hidden', no output is
284 """ Attempts to execute file with 'path'. If 'hidden', no output is
283 shown.
285 shown.
284 """
286 """
285 self.execute('execfile("%s")' % path, hidden=hidden)
287 self.execute('execfile("%s")' % path, hidden=hidden)
286
288
287 #---------------------------------------------------------------------------
289 #---------------------------------------------------------------------------
288 # 'FrontendWidget' protected interface
290 # 'FrontendWidget' protected interface
289 #---------------------------------------------------------------------------
291 #---------------------------------------------------------------------------
290
292
291 def _call_tip(self):
293 def _call_tip(self):
292 """ Shows a call tip, if appropriate, at the current cursor location.
294 """ Shows a call tip, if appropriate, at the current cursor location.
293 """
295 """
294 # Decide if it makes sense to show a call tip
296 # Decide if it makes sense to show a call tip
295 cursor = self._get_cursor()
297 cursor = self._get_cursor()
296 cursor.movePosition(QtGui.QTextCursor.Left)
298 cursor.movePosition(QtGui.QTextCursor.Left)
297 if cursor.document().characterAt(cursor.position()).toAscii() != '(':
299 if cursor.document().characterAt(cursor.position()).toAscii() != '(':
298 return False
300 return False
299 context = self._get_context(cursor)
301 context = self._get_context(cursor)
300 if not context:
302 if not context:
301 return False
303 return False
302
304
303 # Send the metadata request to the kernel
305 # Send the metadata request to the kernel
304 name = '.'.join(context)
306 name = '.'.join(context)
305 self._call_tip_id = self.kernel_manager.xreq_channel.object_info(name)
307 self._call_tip_id = self.kernel_manager.xreq_channel.object_info(name)
306 self._call_tip_pos = self._get_cursor().position()
308 self._call_tip_pos = self._get_cursor().position()
307 return True
309 return True
308
310
309 def _complete(self):
311 def _complete(self):
310 """ Performs completion at the current cursor location.
312 """ Performs completion at the current cursor location.
311 """
313 """
312 context = self._get_context()
314 context = self._get_context()
313 if context:
315 if context:
314 # Send the completion request to the kernel
316 # Send the completion request to the kernel
315 self._complete_id = self.kernel_manager.xreq_channel.complete(
317 self._complete_id = self.kernel_manager.xreq_channel.complete(
316 '.'.join(context), # text
318 '.'.join(context), # text
317 self._get_input_buffer_cursor_line(), # line
319 self._get_input_buffer_cursor_line(), # line
318 self._get_input_buffer_cursor_column(), # cursor_pos
320 self._get_input_buffer_cursor_column(), # cursor_pos
319 self.input_buffer) # block
321 self.input_buffer) # block
320 self._complete_pos = self._get_cursor().position()
322 self._complete_pos = self._get_cursor().position()
321
323
322 def _get_banner(self):
324 def _get_banner(self):
323 """ Gets a banner to display at the beginning of a session.
325 """ Gets a banner to display at the beginning of a session.
324 """
326 """
325 banner = 'Python %s on %s\nType "help", "copyright", "credits" or ' \
327 banner = 'Python %s on %s\nType "help", "copyright", "credits" or ' \
326 '"license" for more information.'
328 '"license" for more information.'
327 return banner % (sys.version, sys.platform)
329 return banner % (sys.version, sys.platform)
328
330
329 def _get_context(self, cursor=None):
331 def _get_context(self, cursor=None):
330 """ Gets the context for the specified cursor (or the current cursor
332 """ Gets the context for the specified cursor (or the current cursor
331 if none is specified).
333 if none is specified).
332 """
334 """
333 if cursor is None:
335 if cursor is None:
334 cursor = self._get_cursor()
336 cursor = self._get_cursor()
335 cursor.movePosition(QtGui.QTextCursor.StartOfBlock,
337 cursor.movePosition(QtGui.QTextCursor.StartOfBlock,
336 QtGui.QTextCursor.KeepAnchor)
338 QtGui.QTextCursor.KeepAnchor)
337 text = str(cursor.selection().toPlainText())
339 text = str(cursor.selection().toPlainText())
338 return self._completion_lexer.get_context(text)
340 return self._completion_lexer.get_context(text)
339
341
340 def _kernel_interrupt(self):
342 def _kernel_interrupt(self):
341 """ Attempts to interrupt the running kernel.
343 """ Attempts to interrupt the running kernel.
342 """
344 """
343 if self.custom_interrupt:
345 if self.custom_interrupt:
344 self.custom_interrupt_requested.emit()
346 self.custom_interrupt_requested.emit()
345 elif self.kernel_manager.has_kernel:
347 elif self.kernel_manager.has_kernel:
346 self.kernel_manager.signal_kernel(signal.SIGINT)
348 self.kernel_manager.signal_kernel(signal.SIGINT)
347 else:
349 else:
348 self._append_plain_text('Kernel process is either remote or '
350 self._append_plain_text('Kernel process is either remote or '
349 'unspecified. Cannot interrupt.\n')
351 'unspecified. Cannot interrupt.\n')
350
352
351 def _kernel_restart(self):
353 def _kernel_restart(self, message):
352 """ Attempts to restart the running kernel.
354 """ Attempts to restart the running kernel.
353 """
355 """
354 if self.custom_restart:
356 # We want to make sure that if this dialog is already happening, that
355 self.custom_restart_requested.emit()
357 # other signals don't trigger it again. This can happen when the
356 elif self.kernel_manager.has_kernel:
358 # kernel_died heartbeat signal is emitted and the user is slow to
357 message = 'Are you sure you want to restart the kernel?'
359 # respond to the dialog.
358 buttons = QtGui.QMessageBox.Yes | QtGui.QMessageBox.No
360 if not self._possible_kernel_restart:
359 result = QtGui.QMessageBox.question(self, 'Restart kernel?',
361 if self.custom_restart:
360 message, buttons)
362 self.custom_restart_requested.emit()
361 if result == QtGui.QMessageBox.Yes:
363 elif self.kernel_manager.has_kernel:
362 try:
364 # Setting this to True will prevent this logic from happening
363 self.kernel_manager.restart_kernel()
365 # again until the current pass is completed.
364 except RuntimeError:
366 self._possible_kernel_restart = True
365 message = 'Kernel started externally. Cannot restart.\n'
367 buttons = QtGui.QMessageBox.Yes | QtGui.QMessageBox.No
366 self._append_plain_text(message)
368 result = QtGui.QMessageBox.question(self, 'Restart kernel?',
367 else:
369 message, buttons)
368 self._stopped_channels()
370 if result == QtGui.QMessageBox.Yes:
369 self._append_plain_text('Kernel restarting...\n')
371 try:
370 self._show_interpreter_prompt()
372 self.kernel_manager.restart_kernel()
371 else:
373 except RuntimeError:
372 self._append_plain_text('Kernel process is either remote or '
374 message = 'Kernel started externally. Cannot restart.\n'
373 'unspecified. Cannot restart.\n')
375 self._append_plain_text(message)
376 else:
377 self._stopped_channels()
378 self._append_plain_text('Kernel restarting...\n')
379 self._show_interpreter_prompt()
380 # This might need to be moved to another location?
381 self._possible_kernel_restart = False
382 else:
383 self._append_plain_text('Kernel process is either remote or '
384 'unspecified. Cannot restart.\n')
374
385
375 def _process_execute_abort(self, msg):
386 def _process_execute_abort(self, msg):
376 """ Process a reply for an aborted execution request.
387 """ Process a reply for an aborted execution request.
377 """
388 """
378 self._append_plain_text("ERROR: execution aborted\n")
389 self._append_plain_text("ERROR: execution aborted\n")
379
390
380 def _process_execute_error(self, msg):
391 def _process_execute_error(self, msg):
381 """ Process a reply for an execution request that resulted in an error.
392 """ Process a reply for an execution request that resulted in an error.
382 """
393 """
383 content = msg['content']
394 content = msg['content']
384 traceback = ''.join(content['traceback'])
395 traceback = ''.join(content['traceback'])
385 self._append_plain_text(traceback)
396 self._append_plain_text(traceback)
386
397
387 def _process_execute_ok(self, msg):
398 def _process_execute_ok(self, msg):
388 """ Process a reply for a successful execution equest.
399 """ Process a reply for a successful execution equest.
389 """
400 """
390 payload = msg['content']['payload']
401 payload = msg['content']['payload']
391 for item in payload:
402 for item in payload:
392 if not self._process_execute_payload(item):
403 if not self._process_execute_payload(item):
393 warning = 'Received unknown payload of type %s\n'
404 warning = 'Received unknown payload of type %s\n'
394 self._append_plain_text(warning % repr(item['source']))
405 self._append_plain_text(warning % repr(item['source']))
395
406
396 def _process_execute_payload(self, item):
407 def _process_execute_payload(self, item):
397 """ Process a single payload item from the list of payload items in an
408 """ Process a single payload item from the list of payload items in an
398 execution reply. Returns whether the payload was handled.
409 execution reply. Returns whether the payload was handled.
399 """
410 """
400 # The basic FrontendWidget doesn't handle payloads, as they are a
411 # The basic FrontendWidget doesn't handle payloads, as they are a
401 # mechanism for going beyond the standard Python interpreter model.
412 # mechanism for going beyond the standard Python interpreter model.
402 return False
413 return False
403
414
404 def _show_interpreter_prompt(self):
415 def _show_interpreter_prompt(self):
405 """ Shows a prompt for the interpreter.
416 """ Shows a prompt for the interpreter.
406 """
417 """
407 self._show_prompt('>>> ')
418 self._show_prompt('>>> ')
408
419
409 def _show_interpreter_prompt_for_reply(self, msg):
420 def _show_interpreter_prompt_for_reply(self, msg):
410 """ Shows a prompt for the interpreter given an 'execute_reply' message.
421 """ Shows a prompt for the interpreter given an 'execute_reply' message.
411 """
422 """
412 self._show_interpreter_prompt()
423 self._show_interpreter_prompt()
413
424
414 #------ Signal handlers ----------------------------------------------------
425 #------ Signal handlers ----------------------------------------------------
415
426
416 def _document_contents_change(self, position, removed, added):
427 def _document_contents_change(self, position, removed, added):
417 """ Called whenever the document's content changes. Display a call tip
428 """ Called whenever the document's content changes. Display a call tip
418 if appropriate.
429 if appropriate.
419 """
430 """
420 # Calculate where the cursor should be *after* the change:
431 # Calculate where the cursor should be *after* the change:
421 position += added
432 position += added
422
433
423 document = self._control.document()
434 document = self._control.document()
424 if position == self._get_cursor().position():
435 if position == self._get_cursor().position():
425 self._call_tip()
436 self._call_tip()
@@ -1,359 +1,367 b''
1 """ A FrontendWidget that emulates the interface of the console IPython and
1 """ A FrontendWidget that emulates the interface of the console IPython and
2 supports the additional functionality provided by the IPython kernel.
2 supports the additional functionality provided by the IPython kernel.
3
3
4 TODO: Add support for retrieving the system default editor. Requires code
4 TODO: Add support for retrieving the system default editor. Requires code
5 paths for Windows (use the registry), Mac OS (use LaunchServices), and
5 paths for Windows (use the registry), Mac OS (use LaunchServices), and
6 Linux (use the xdg system).
6 Linux (use the xdg system).
7 """
7 """
8
8
9 # Standard library imports
9 # Standard library imports
10 from collections import namedtuple
10 from collections import namedtuple
11 from subprocess import Popen
11 from subprocess import Popen
12
12
13 # System library imports
13 # System library imports
14 from PyQt4 import QtCore, QtGui
14 from PyQt4 import QtCore, QtGui
15
15
16 # Local imports
16 # Local imports
17 from IPython.core.inputsplitter import IPythonInputSplitter
17 from IPython.core.inputsplitter import IPythonInputSplitter
18 from IPython.core.usage import default_banner
18 from IPython.core.usage import default_banner
19 from IPython.utils.traitlets import Bool, Str
19 from IPython.utils.traitlets import Bool, Str
20 from frontend_widget import FrontendWidget
20 from frontend_widget import FrontendWidget
21
21
22 # The default style sheet: black text on a white background.
22 # The default style sheet: black text on a white background.
23 default_style_sheet = '''
23 default_style_sheet = '''
24 .error { color: red; }
24 .error { color: red; }
25 .in-prompt { color: navy; }
25 .in-prompt { color: navy; }
26 .in-prompt-number { font-weight: bold; }
26 .in-prompt-number { font-weight: bold; }
27 .out-prompt { color: darkred; }
27 .out-prompt { color: darkred; }
28 .out-prompt-number { font-weight: bold; }
28 .out-prompt-number { font-weight: bold; }
29 '''
29 '''
30 default_syntax_style = 'default'
30 default_syntax_style = 'default'
31
31
32 # A dark style sheet: white text on a black background.
32 # A dark style sheet: white text on a black background.
33 dark_style_sheet = '''
33 dark_style_sheet = '''
34 QPlainTextEdit, QTextEdit { background-color: black; color: white }
34 QPlainTextEdit, QTextEdit { background-color: black; color: white }
35 QFrame { border: 1px solid grey; }
35 QFrame { border: 1px solid grey; }
36 .error { color: red; }
36 .error { color: red; }
37 .in-prompt { color: lime; }
37 .in-prompt { color: lime; }
38 .in-prompt-number { color: lime; font-weight: bold; }
38 .in-prompt-number { color: lime; font-weight: bold; }
39 .out-prompt { color: red; }
39 .out-prompt { color: red; }
40 .out-prompt-number { color: red; font-weight: bold; }
40 .out-prompt-number { color: red; font-weight: bold; }
41 '''
41 '''
42 dark_syntax_style = 'monokai'
42 dark_syntax_style = 'monokai'
43
43
44 # Default prompts.
44 # Default prompts.
45 default_in_prompt = 'In [<span class="in-prompt-number">%i</span>]: '
45 default_in_prompt = 'In [<span class="in-prompt-number">%i</span>]: '
46 default_out_prompt = 'Out[<span class="out-prompt-number">%i</span>]: '
46 default_out_prompt = 'Out[<span class="out-prompt-number">%i</span>]: '
47
47
48
48
49 class IPythonWidget(FrontendWidget):
49 class IPythonWidget(FrontendWidget):
50 """ A FrontendWidget for an IPython kernel.
50 """ A FrontendWidget for an IPython kernel.
51 """
51 """
52
52
53 # If set, the 'custom_edit_requested(str, int)' signal will be emitted when
53 # If set, the 'custom_edit_requested(str, int)' signal will be emitted when
54 # an editor is needed for a file. This overrides 'editor' and 'editor_line'
54 # an editor is needed for a file. This overrides 'editor' and 'editor_line'
55 # settings.
55 # settings.
56 custom_edit = Bool(False)
56 custom_edit = Bool(False)
57 custom_edit_requested = QtCore.pyqtSignal(object, object)
57 custom_edit_requested = QtCore.pyqtSignal(object, object)
58
58
59 # A command for invoking a system text editor. If the string contains a
59 # A command for invoking a system text editor. If the string contains a
60 # {filename} format specifier, it will be used. Otherwise, the filename will
60 # {filename} format specifier, it will be used. Otherwise, the filename will
61 # be appended to the end the command.
61 # be appended to the end the command.
62 editor = Str('default', config=True)
62 editor = Str('default', config=True)
63
63
64 # The editor command to use when a specific line number is requested. The
64 # The editor command to use when a specific line number is requested. The
65 # string should contain two format specifiers: {line} and {filename}. If
65 # string should contain two format specifiers: {line} and {filename}. If
66 # this parameter is not specified, the line number option to the %edit magic
66 # this parameter is not specified, the line number option to the %edit magic
67 # will be ignored.
67 # will be ignored.
68 editor_line = Str(config=True)
68 editor_line = Str(config=True)
69
69
70 # A CSS stylesheet. The stylesheet can contain classes for:
70 # A CSS stylesheet. The stylesheet can contain classes for:
71 # 1. Qt: QPlainTextEdit, QFrame, QWidget, etc
71 # 1. Qt: QPlainTextEdit, QFrame, QWidget, etc
72 # 2. Pygments: .c, .k, .o, etc (see PygmentsHighlighter)
72 # 2. Pygments: .c, .k, .o, etc (see PygmentsHighlighter)
73 # 3. IPython: .error, .in-prompt, .out-prompt, etc
73 # 3. IPython: .error, .in-prompt, .out-prompt, etc
74 style_sheet = Str(default_style_sheet, config=True)
74 style_sheet = Str(default_style_sheet, config=True)
75
75
76 # If not empty, use this Pygments style for syntax highlighting. Otherwise,
76 # If not empty, use this Pygments style for syntax highlighting. Otherwise,
77 # the style sheet is queried for Pygments style information.
77 # the style sheet is queried for Pygments style information.
78 syntax_style = Str(default_syntax_style, config=True)
78 syntax_style = Str(default_syntax_style, config=True)
79
79
80 # Prompts.
80 # Prompts.
81 in_prompt = Str(default_in_prompt, config=True)
81 in_prompt = Str(default_in_prompt, config=True)
82 out_prompt = Str(default_out_prompt, config=True)
82 out_prompt = Str(default_out_prompt, config=True)
83
83
84 # FrontendWidget protected class variables.
84 # FrontendWidget protected class variables.
85 _input_splitter_class = IPythonInputSplitter
85 _input_splitter_class = IPythonInputSplitter
86
86
87 # IPythonWidget protected class variables.
87 # IPythonWidget protected class variables.
88 _PromptBlock = namedtuple('_PromptBlock', ['block', 'length', 'number'])
88 _PromptBlock = namedtuple('_PromptBlock', ['block', 'length', 'number'])
89 _payload_source_edit = 'IPython.zmq.zmqshell.ZMQInteractiveShell.edit_magic'
89 _payload_source_edit = 'IPython.zmq.zmqshell.ZMQInteractiveShell.edit_magic'
90 _payload_source_page = 'IPython.zmq.page.page'
90 _payload_source_page = 'IPython.zmq.page.page'
91
91
92 #---------------------------------------------------------------------------
92 #---------------------------------------------------------------------------
93 # 'object' interface
93 # 'object' interface
94 #---------------------------------------------------------------------------
94 #---------------------------------------------------------------------------
95
95
96 def __init__(self, *args, **kw):
96 def __init__(self, *args, **kw):
97 super(IPythonWidget, self).__init__(*args, **kw)
97 super(IPythonWidget, self).__init__(*args, **kw)
98
98
99 # IPythonWidget protected variables.
99 # IPythonWidget protected variables.
100 self._previous_prompt_obj = None
100 self._previous_prompt_obj = None
101
101
102 # Initialize widget styling.
102 # Initialize widget styling.
103 self._style_sheet_changed()
103 self._style_sheet_changed()
104 self._syntax_style_changed()
104 self._syntax_style_changed()
105
105
106 #---------------------------------------------------------------------------
106 #---------------------------------------------------------------------------
107 # 'BaseFrontendMixin' abstract interface
107 # 'BaseFrontendMixin' abstract interface
108 #---------------------------------------------------------------------------
108 #---------------------------------------------------------------------------
109
109
110 def _handle_complete_reply(self, rep):
110 def _handle_complete_reply(self, rep):
111 """ Reimplemented to support IPython's improved completion machinery.
111 """ Reimplemented to support IPython's improved completion machinery.
112 """
112 """
113 cursor = self._get_cursor()
113 cursor = self._get_cursor()
114 if rep['parent_header']['msg_id'] == self._complete_id and \
114 if rep['parent_header']['msg_id'] == self._complete_id and \
115 cursor.position() == self._complete_pos:
115 cursor.position() == self._complete_pos:
116 # The completer tells us what text was actually used for the
116 # The completer tells us what text was actually used for the
117 # matching, so we must move that many characters left to apply the
117 # matching, so we must move that many characters left to apply the
118 # completions.
118 # completions.
119 text = rep['content']['matched_text']
119 text = rep['content']['matched_text']
120 cursor.movePosition(QtGui.QTextCursor.Left, n=len(text))
120 cursor.movePosition(QtGui.QTextCursor.Left, n=len(text))
121 self._complete_with_items(cursor, rep['content']['matches'])
121 self._complete_with_items(cursor, rep['content']['matches'])
122
122
123 def _handle_history_reply(self, msg):
123 def _handle_history_reply(self, msg):
124 """ Implemented to handle history replies, which are only supported by
124 """ Implemented to handle history replies, which are only supported by
125 the IPython kernel.
125 the IPython kernel.
126 """
126 """
127 history_dict = msg['content']['history']
127 history_dict = msg['content']['history']
128 items = [ history_dict[key] for key in sorted(history_dict.keys()) ]
128 items = [ history_dict[key] for key in sorted(history_dict.keys()) ]
129 self._set_history(items)
129 self._set_history(items)
130
130
131 def _handle_prompt_reply(self, msg):
131 def _handle_prompt_reply(self, msg):
132 """ Implemented to handle prompt number replies, which are only
132 """ Implemented to handle prompt number replies, which are only
133 supported by the IPython kernel.
133 supported by the IPython kernel.
134 """
134 """
135 content = msg['content']
135 content = msg['content']
136 self._show_interpreter_prompt(content['prompt_number'],
136 self._show_interpreter_prompt(content['prompt_number'],
137 content['input_sep'])
137 content['input_sep'])
138
138
139 def _handle_pyout(self, msg):
139 def _handle_pyout(self, msg):
140 """ Reimplemented for IPython-style "display hook".
140 """ Reimplemented for IPython-style "display hook".
141 """
141 """
142 if not self._hidden and self._is_from_this_session(msg):
142 if not self._hidden and self._is_from_this_session(msg):
143 content = msg['content']
143 content = msg['content']
144 prompt_number = content['prompt_number']
144 prompt_number = content['prompt_number']
145 self._append_plain_text(content['output_sep'])
145 self._append_plain_text(content['output_sep'])
146 self._append_html(self._make_out_prompt(prompt_number))
146 self._append_html(self._make_out_prompt(prompt_number))
147 self._append_plain_text(content['data'] + '\n' +
147 self._append_plain_text(content['data'] + '\n' +
148 content['output_sep2'])
148 content['output_sep2'])
149
149
150 def _started_channels(self):
150 def _started_channels(self):
151 """ Reimplemented to make a history request.
151 """ Reimplemented to make a history request.
152 """
152 """
153 super(IPythonWidget, self)._started_channels()
153 super(IPythonWidget, self)._started_channels()
154 # FIXME: Disabled until history requests are properly implemented.
154 # FIXME: Disabled until history requests are properly implemented.
155 #self.kernel_manager.xreq_channel.history(raw=True, output=False)
155 #self.kernel_manager.xreq_channel.history(raw=True, output=False)
156
156
157 def _handle_kernel_died(self, since_last_heartbeat):
158 """ Handle the kernel's death by asking if the user wants to restart.
159 """
160 message = 'The kernel heartbeat has been inactive for %.2f ' \
161 'seconds. Do you want to restart the kernel? You may ' \
162 'first want to check the network connection.' % since_last_heartbeat
163 self._kernel_restart(message)
164
157 #---------------------------------------------------------------------------
165 #---------------------------------------------------------------------------
158 # 'FrontendWidget' interface
166 # 'FrontendWidget' interface
159 #---------------------------------------------------------------------------
167 #---------------------------------------------------------------------------
160
168
161 def execute_file(self, path, hidden=False):
169 def execute_file(self, path, hidden=False):
162 """ Reimplemented to use the 'run' magic.
170 """ Reimplemented to use the 'run' magic.
163 """
171 """
164 self.execute('%%run %s' % path, hidden=hidden)
172 self.execute('%%run %s' % path, hidden=hidden)
165
173
166 #---------------------------------------------------------------------------
174 #---------------------------------------------------------------------------
167 # 'FrontendWidget' protected interface
175 # 'FrontendWidget' protected interface
168 #---------------------------------------------------------------------------
176 #---------------------------------------------------------------------------
169
177
170 def _complete(self):
178 def _complete(self):
171 """ Reimplemented to support IPython's improved completion machinery.
179 """ Reimplemented to support IPython's improved completion machinery.
172 """
180 """
173 # We let the kernel split the input line, so we *always* send an empty
181 # We let the kernel split the input line, so we *always* send an empty
174 # text field. Readline-based frontends do get a real text field which
182 # text field. Readline-based frontends do get a real text field which
175 # they can use.
183 # they can use.
176 text = ''
184 text = ''
177
185
178 # Send the completion request to the kernel
186 # Send the completion request to the kernel
179 self._complete_id = self.kernel_manager.xreq_channel.complete(
187 self._complete_id = self.kernel_manager.xreq_channel.complete(
180 text, # text
188 text, # text
181 self._get_input_buffer_cursor_line(), # line
189 self._get_input_buffer_cursor_line(), # line
182 self._get_input_buffer_cursor_column(), # cursor_pos
190 self._get_input_buffer_cursor_column(), # cursor_pos
183 self.input_buffer) # block
191 self.input_buffer) # block
184 self._complete_pos = self._get_cursor().position()
192 self._complete_pos = self._get_cursor().position()
185
193
186 def _get_banner(self):
194 def _get_banner(self):
187 """ Reimplemented to return IPython's default banner.
195 """ Reimplemented to return IPython's default banner.
188 """
196 """
189 return default_banner + '\n'
197 return default_banner + '\n'
190
198
191 def _process_execute_error(self, msg):
199 def _process_execute_error(self, msg):
192 """ Reimplemented for IPython-style traceback formatting.
200 """ Reimplemented for IPython-style traceback formatting.
193 """
201 """
194 content = msg['content']
202 content = msg['content']
195 traceback = '\n'.join(content['traceback']) + '\n'
203 traceback = '\n'.join(content['traceback']) + '\n'
196 if False:
204 if False:
197 # FIXME: For now, tracebacks come as plain text, so we can't use
205 # FIXME: For now, tracebacks come as plain text, so we can't use
198 # the html renderer yet. Once we refactor ultratb to produce
206 # the html renderer yet. Once we refactor ultratb to produce
199 # properly styled tracebacks, this branch should be the default
207 # properly styled tracebacks, this branch should be the default
200 traceback = traceback.replace(' ', '&nbsp;')
208 traceback = traceback.replace(' ', '&nbsp;')
201 traceback = traceback.replace('\n', '<br/>')
209 traceback = traceback.replace('\n', '<br/>')
202
210
203 ename = content['ename']
211 ename = content['ename']
204 ename_styled = '<span class="error">%s</span>' % ename
212 ename_styled = '<span class="error">%s</span>' % ename
205 traceback = traceback.replace(ename, ename_styled)
213 traceback = traceback.replace(ename, ename_styled)
206
214
207 self._append_html(traceback)
215 self._append_html(traceback)
208 else:
216 else:
209 # This is the fallback for now, using plain text with ansi escapes
217 # This is the fallback for now, using plain text with ansi escapes
210 self._append_plain_text(traceback)
218 self._append_plain_text(traceback)
211
219
212 def _process_execute_payload(self, item):
220 def _process_execute_payload(self, item):
213 """ Reimplemented to handle %edit and paging payloads.
221 """ Reimplemented to handle %edit and paging payloads.
214 """
222 """
215 if item['source'] == self._payload_source_edit:
223 if item['source'] == self._payload_source_edit:
216 self._edit(item['filename'], item['line_number'])
224 self._edit(item['filename'], item['line_number'])
217 return True
225 return True
218 elif item['source'] == self._payload_source_page:
226 elif item['source'] == self._payload_source_page:
219 self._page(item['data'])
227 self._page(item['data'])
220 return True
228 return True
221 else:
229 else:
222 return False
230 return False
223
231
224 def _show_interpreter_prompt(self, number=None, input_sep='\n'):
232 def _show_interpreter_prompt(self, number=None, input_sep='\n'):
225 """ Reimplemented for IPython-style prompts.
233 """ Reimplemented for IPython-style prompts.
226 """
234 """
227 # If a number was not specified, make a prompt number request.
235 # If a number was not specified, make a prompt number request.
228 if number is None:
236 if number is None:
229 self.kernel_manager.xreq_channel.prompt()
237 self.kernel_manager.xreq_channel.prompt()
230 return
238 return
231
239
232 # Show a new prompt and save information about it so that it can be
240 # Show a new prompt and save information about it so that it can be
233 # updated later if the prompt number turns out to be wrong.
241 # updated later if the prompt number turns out to be wrong.
234 self._prompt_sep = input_sep
242 self._prompt_sep = input_sep
235 self._show_prompt(self._make_in_prompt(number), html=True)
243 self._show_prompt(self._make_in_prompt(number), html=True)
236 block = self._control.document().lastBlock()
244 block = self._control.document().lastBlock()
237 length = len(self._prompt)
245 length = len(self._prompt)
238 self._previous_prompt_obj = self._PromptBlock(block, length, number)
246 self._previous_prompt_obj = self._PromptBlock(block, length, number)
239
247
240 # Update continuation prompt to reflect (possibly) new prompt length.
248 # Update continuation prompt to reflect (possibly) new prompt length.
241 self._set_continuation_prompt(
249 self._set_continuation_prompt(
242 self._make_continuation_prompt(self._prompt), html=True)
250 self._make_continuation_prompt(self._prompt), html=True)
243
251
244 def _show_interpreter_prompt_for_reply(self, msg):
252 def _show_interpreter_prompt_for_reply(self, msg):
245 """ Reimplemented for IPython-style prompts.
253 """ Reimplemented for IPython-style prompts.
246 """
254 """
247 # Update the old prompt number if necessary.
255 # Update the old prompt number if necessary.
248 content = msg['content']
256 content = msg['content']
249 previous_prompt_number = content['prompt_number']
257 previous_prompt_number = content['prompt_number']
250 if self._previous_prompt_obj and \
258 if self._previous_prompt_obj and \
251 self._previous_prompt_obj.number != previous_prompt_number:
259 self._previous_prompt_obj.number != previous_prompt_number:
252 block = self._previous_prompt_obj.block
260 block = self._previous_prompt_obj.block
253
261
254 # Make sure the prompt block has not been erased.
262 # Make sure the prompt block has not been erased.
255 if block.isValid() and not block.text().isEmpty():
263 if block.isValid() and not block.text().isEmpty():
256
264
257 # Remove the old prompt and insert a new prompt.
265 # Remove the old prompt and insert a new prompt.
258 cursor = QtGui.QTextCursor(block)
266 cursor = QtGui.QTextCursor(block)
259 cursor.movePosition(QtGui.QTextCursor.Right,
267 cursor.movePosition(QtGui.QTextCursor.Right,
260 QtGui.QTextCursor.KeepAnchor,
268 QtGui.QTextCursor.KeepAnchor,
261 self._previous_prompt_obj.length)
269 self._previous_prompt_obj.length)
262 prompt = self._make_in_prompt(previous_prompt_number)
270 prompt = self._make_in_prompt(previous_prompt_number)
263 self._prompt = self._insert_html_fetching_plain_text(
271 self._prompt = self._insert_html_fetching_plain_text(
264 cursor, prompt)
272 cursor, prompt)
265
273
266 # When the HTML is inserted, Qt blows away the syntax
274 # When the HTML is inserted, Qt blows away the syntax
267 # highlighting for the line, so we need to rehighlight it.
275 # highlighting for the line, so we need to rehighlight it.
268 self._highlighter.rehighlightBlock(cursor.block())
276 self._highlighter.rehighlightBlock(cursor.block())
269
277
270 self._previous_prompt_obj = None
278 self._previous_prompt_obj = None
271
279
272 # Show a new prompt with the kernel's estimated prompt number.
280 # Show a new prompt with the kernel's estimated prompt number.
273 next_prompt = content['next_prompt']
281 next_prompt = content['next_prompt']
274 self._show_interpreter_prompt(next_prompt['prompt_number'],
282 self._show_interpreter_prompt(next_prompt['prompt_number'],
275 next_prompt['input_sep'])
283 next_prompt['input_sep'])
276
284
277 #---------------------------------------------------------------------------
285 #---------------------------------------------------------------------------
278 # 'IPythonWidget' protected interface
286 # 'IPythonWidget' protected interface
279 #---------------------------------------------------------------------------
287 #---------------------------------------------------------------------------
280
288
281 def _edit(self, filename, line=None):
289 def _edit(self, filename, line=None):
282 """ Opens a Python script for editing.
290 """ Opens a Python script for editing.
283
291
284 Parameters:
292 Parameters:
285 -----------
293 -----------
286 filename : str
294 filename : str
287 A path to a local system file.
295 A path to a local system file.
288
296
289 line : int, optional
297 line : int, optional
290 A line of interest in the file.
298 A line of interest in the file.
291 """
299 """
292 if self.custom_edit:
300 if self.custom_edit:
293 self.custom_edit_requested.emit(filename, line)
301 self.custom_edit_requested.emit(filename, line)
294 elif self.editor == 'default':
302 elif self.editor == 'default':
295 self._append_plain_text('No default editor available.\n')
303 self._append_plain_text('No default editor available.\n')
296 else:
304 else:
297 try:
305 try:
298 filename = '"%s"' % filename
306 filename = '"%s"' % filename
299 if line and self.editor_line:
307 if line and self.editor_line:
300 command = self.editor_line.format(filename=filename,
308 command = self.editor_line.format(filename=filename,
301 line=line)
309 line=line)
302 else:
310 else:
303 try:
311 try:
304 command = self.editor.format()
312 command = self.editor.format()
305 except KeyError:
313 except KeyError:
306 command = self.editor.format(filename=filename)
314 command = self.editor.format(filename=filename)
307 else:
315 else:
308 command += ' ' + filename
316 command += ' ' + filename
309 except KeyError:
317 except KeyError:
310 self._append_plain_text('Invalid editor command.\n')
318 self._append_plain_text('Invalid editor command.\n')
311 else:
319 else:
312 try:
320 try:
313 Popen(command, shell=True)
321 Popen(command, shell=True)
314 except OSError:
322 except OSError:
315 msg = 'Opening editor with command "%s" failed.\n'
323 msg = 'Opening editor with command "%s" failed.\n'
316 self._append_plain_text(msg % command)
324 self._append_plain_text(msg % command)
317
325
318 def _make_in_prompt(self, number):
326 def _make_in_prompt(self, number):
319 """ Given a prompt number, returns an HTML In prompt.
327 """ Given a prompt number, returns an HTML In prompt.
320 """
328 """
321 body = self.in_prompt % number
329 body = self.in_prompt % number
322 return '<span class="in-prompt">%s</span>' % body
330 return '<span class="in-prompt">%s</span>' % body
323
331
324 def _make_continuation_prompt(self, prompt):
332 def _make_continuation_prompt(self, prompt):
325 """ Given a plain text version of an In prompt, returns an HTML
333 """ Given a plain text version of an In prompt, returns an HTML
326 continuation prompt.
334 continuation prompt.
327 """
335 """
328 end_chars = '...: '
336 end_chars = '...: '
329 space_count = len(prompt.lstrip('\n')) - len(end_chars)
337 space_count = len(prompt.lstrip('\n')) - len(end_chars)
330 body = '&nbsp;' * space_count + end_chars
338 body = '&nbsp;' * space_count + end_chars
331 return '<span class="in-prompt">%s</span>' % body
339 return '<span class="in-prompt">%s</span>' % body
332
340
333 def _make_out_prompt(self, number):
341 def _make_out_prompt(self, number):
334 """ Given a prompt number, returns an HTML Out prompt.
342 """ Given a prompt number, returns an HTML Out prompt.
335 """
343 """
336 body = self.out_prompt % number
344 body = self.out_prompt % number
337 return '<span class="out-prompt">%s</span>' % body
345 return '<span class="out-prompt">%s</span>' % body
338
346
339 #------ Trait change handlers ---------------------------------------------
347 #------ Trait change handlers ---------------------------------------------
340
348
341 def _style_sheet_changed(self):
349 def _style_sheet_changed(self):
342 """ Set the style sheets of the underlying widgets.
350 """ Set the style sheets of the underlying widgets.
343 """
351 """
344 self.setStyleSheet(self.style_sheet)
352 self.setStyleSheet(self.style_sheet)
345 self._control.document().setDefaultStyleSheet(self.style_sheet)
353 self._control.document().setDefaultStyleSheet(self.style_sheet)
346 if self._page_control:
354 if self._page_control:
347 self._page_control.document().setDefaultStyleSheet(self.style_sheet)
355 self._page_control.document().setDefaultStyleSheet(self.style_sheet)
348
356
349 bg_color = self._control.palette().background().color()
357 bg_color = self._control.palette().background().color()
350 self._ansi_processor.set_background_color(bg_color)
358 self._ansi_processor.set_background_color(bg_color)
351
359
352 def _syntax_style_changed(self):
360 def _syntax_style_changed(self):
353 """ Set the style for the syntax highlighter.
361 """ Set the style for the syntax highlighter.
354 """
362 """
355 if self.syntax_style:
363 if self.syntax_style:
356 self._highlighter.set_style(self.syntax_style)
364 self._highlighter.set_style(self.syntax_style)
357 else:
365 else:
358 self._highlighter.set_style_sheet(self.style_sheet)
366 self._highlighter.set_style_sheet(self.style_sheet)
359
367
@@ -1,99 +1,102 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2
2
3 """ A minimal application using the Qt console-style IPython frontend.
3 """ A minimal application using the Qt console-style IPython frontend.
4 """
4 """
5
5
6 # Systemm library imports
6 # Systemm library imports
7 from PyQt4 import QtGui
7 from PyQt4 import QtGui
8
8
9 # Local imports
9 # Local imports
10 from IPython.external.argparse import ArgumentParser
10 from IPython.external.argparse import ArgumentParser
11 from IPython.frontend.qt.console.frontend_widget import FrontendWidget
11 from IPython.frontend.qt.console.frontend_widget import FrontendWidget
12 from IPython.frontend.qt.console.ipython_widget import IPythonWidget
12 from IPython.frontend.qt.console.ipython_widget import IPythonWidget
13 from IPython.frontend.qt.console.rich_ipython_widget import RichIPythonWidget
13 from IPython.frontend.qt.console.rich_ipython_widget import RichIPythonWidget
14 from IPython.frontend.qt.kernelmanager import QtKernelManager
14 from IPython.frontend.qt.kernelmanager import QtKernelManager
15
15
16 # Constants
16 # Constants
17 LOCALHOST = '127.0.0.1'
17 LOCALHOST = '127.0.0.1'
18
18
19
19
20 def main():
20 def main():
21 """ Entry point for application.
21 """ Entry point for application.
22 """
22 """
23 # Parse command line arguments.
23 # Parse command line arguments.
24 parser = ArgumentParser()
24 parser = ArgumentParser()
25 kgroup = parser.add_argument_group('kernel options')
25 kgroup = parser.add_argument_group('kernel options')
26 kgroup.add_argument('-e', '--existing', action='store_true',
26 kgroup.add_argument('-e', '--existing', action='store_true',
27 help='connect to an existing kernel')
27 help='connect to an existing kernel')
28 kgroup.add_argument('--ip', type=str, default=LOCALHOST,
28 kgroup.add_argument('--ip', type=str, default=LOCALHOST,
29 help='set the kernel\'s IP address [default localhost]')
29 help='set the kernel\'s IP address [default localhost]')
30 kgroup.add_argument('--xreq', type=int, metavar='PORT', default=0,
30 kgroup.add_argument('--xreq', type=int, metavar='PORT', default=0,
31 help='set the XREQ channel port [default random]')
31 help='set the XREQ channel port [default random]')
32 kgroup.add_argument('--sub', type=int, metavar='PORT', default=0,
32 kgroup.add_argument('--sub', type=int, metavar='PORT', default=0,
33 help='set the SUB channel port [default random]')
33 help='set the SUB channel port [default random]')
34 kgroup.add_argument('--rep', type=int, metavar='PORT', default=0,
34 kgroup.add_argument('--rep', type=int, metavar='PORT', default=0,
35 help='set the REP channel port [default random]')
35 help='set the REP channel port [default random]')
36 kgroup.add_argument('--hb', type=int, metavar='PORT', default=0,
37 help='set the heartbeat port [default: random]')
36
38
37 egroup = kgroup.add_mutually_exclusive_group()
39 egroup = kgroup.add_mutually_exclusive_group()
38 egroup.add_argument('--pure', action='store_true', help = \
40 egroup.add_argument('--pure', action='store_true', help = \
39 'use a pure Python kernel instead of an IPython kernel')
41 'use a pure Python kernel instead of an IPython kernel')
40 egroup.add_argument('--pylab', type=str, metavar='GUI', nargs='?',
42 egroup.add_argument('--pylab', type=str, metavar='GUI', nargs='?',
41 const='auto', help = \
43 const='auto', help = \
42 "Pre-load matplotlib and numpy for interactive use. If GUI is not \
44 "Pre-load matplotlib and numpy for interactive use. If GUI is not \
43 given, the GUI backend is matplotlib's, otherwise use one of: \
45 given, the GUI backend is matplotlib's, otherwise use one of: \
44 ['tk', 'gtk', 'qt', 'wx', 'payload-svg'].")
46 ['tk', 'gtk', 'qt', 'wx', 'payload-svg'].")
45
47
46 wgroup = parser.add_argument_group('widget options')
48 wgroup = parser.add_argument_group('widget options')
47 wgroup.add_argument('--paging', type=str, default='inside',
49 wgroup.add_argument('--paging', type=str, default='inside',
48 choices = ['inside', 'hsplit', 'vsplit', 'none'],
50 choices = ['inside', 'hsplit', 'vsplit', 'none'],
49 help='set the paging style [default inside]')
51 help='set the paging style [default inside]')
50 wgroup.add_argument('--rich', action='store_true',
52 wgroup.add_argument('--rich', action='store_true',
51 help='enable rich text support')
53 help='enable rich text support')
52 wgroup.add_argument('--tab-simple', action='store_true',
54 wgroup.add_argument('--tab-simple', action='store_true',
53 help='do tab completion ala a Unix terminal')
55 help='do tab completion ala a Unix terminal')
54
56
55 args = parser.parse_args()
57 args = parser.parse_args()
56
58
57 # Don't let Qt or ZMQ swallow KeyboardInterupts.
59 # Don't let Qt or ZMQ swallow KeyboardInterupts.
58 import signal
60 import signal
59 signal.signal(signal.SIGINT, signal.SIG_DFL)
61 signal.signal(signal.SIGINT, signal.SIG_DFL)
60
62
61 # Create a KernelManager and start a kernel.
63 # Create a KernelManager and start a kernel.
62 kernel_manager = QtKernelManager(xreq_address=(args.ip, args.xreq),
64 kernel_manager = QtKernelManager(xreq_address=(args.ip, args.xreq),
63 sub_address=(args.ip, args.sub),
65 sub_address=(args.ip, args.sub),
64 rep_address=(args.ip, args.rep))
66 rep_address=(args.ip, args.rep),
67 hb_address=(args.ip, args.hb))
65 if args.ip == LOCALHOST and not args.existing:
68 if args.ip == LOCALHOST and not args.existing:
66 if args.pure:
69 if args.pure:
67 kernel_manager.start_kernel(ipython=False)
70 kernel_manager.start_kernel(ipython=False)
68 elif args.pylab:
71 elif args.pylab:
69 if args.rich:
72 if args.rich:
70 kernel_manager.start_kernel(pylab='payload-svg')
73 kernel_manager.start_kernel(pylab='payload-svg')
71 else:
74 else:
72 if args.pylab == 'auto':
75 if args.pylab == 'auto':
73 kernel_manager.start_kernel(pylab='qt4')
76 kernel_manager.start_kernel(pylab='qt4')
74 else:
77 else:
75 kernel_manager.start_kernel(pylab=args.pylab)
78 kernel_manager.start_kernel(pylab=args.pylab)
76 else:
79 else:
77 kernel_manager.start_kernel()
80 kernel_manager.start_kernel()
78 kernel_manager.start_channels()
81 kernel_manager.start_channels()
79
82
80 # Create the widget.
83 # Create the widget.
81 app = QtGui.QApplication([])
84 app = QtGui.QApplication([])
82 if args.pure:
85 if args.pure:
83 kind = 'rich' if args.rich else 'plain'
86 kind = 'rich' if args.rich else 'plain'
84 widget = FrontendWidget(kind=kind, paging=args.paging)
87 widget = FrontendWidget(kind=kind, paging=args.paging)
85 elif args.rich:
88 elif args.rich:
86 widget = RichIPythonWidget(paging=args.paging)
89 widget = RichIPythonWidget(paging=args.paging)
87 else:
90 else:
88 widget = IPythonWidget(paging=args.paging)
91 widget = IPythonWidget(paging=args.paging)
89 widget.gui_completion = not args.tab_simple
92 widget.gui_completion = not args.tab_simple
90 widget.kernel_manager = kernel_manager
93 widget.kernel_manager = kernel_manager
91 widget.setWindowTitle('Python' if args.pure else 'IPython')
94 widget.setWindowTitle('Python' if args.pure else 'IPython')
92 widget.show()
95 widget.show()
93
96
94 # Start the application main loop.
97 # Start the application main loop.
95 app.exec_()
98 app.exec_()
96
99
97
100
98 if __name__ == '__main__':
101 if __name__ == '__main__':
99 main()
102 main()
@@ -1,193 +1,220 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.utils.traitlets import Type
9 from IPython.utils.traitlets import Type
10 from IPython.zmq.kernelmanager import KernelManager, SubSocketChannel, \
10 from IPython.zmq.kernelmanager import KernelManager, SubSocketChannel, \
11 XReqSocketChannel, RepSocketChannel
11 XReqSocketChannel, RepSocketChannel, HBSocketChannel
12 from util import MetaQObjectHasTraits
12 from util import MetaQObjectHasTraits
13
13
14 # When doing multiple inheritance from QtCore.QObject and other classes
14 # When doing multiple inheritance from QtCore.QObject and other classes
15 # the calling of the parent __init__'s is a subtle issue:
15 # the calling of the parent __init__'s is a subtle issue:
16 # * QtCore.QObject does not call super so you can't use super and put
16 # * QtCore.QObject does not call super so you can't use super and put
17 # QObject first in the inheritance list.
17 # QObject first in the inheritance list.
18 # * QtCore.QObject.__init__ takes 1 argument, the parent. So if you are going
18 # * QtCore.QObject.__init__ takes 1 argument, the parent. So if you are going
19 # to use super, any class that comes before QObject must pass it something
19 # to use super, any class that comes before QObject must pass it something
20 # reasonable.
20 # reasonable.
21 # In summary, I don't think using super in these situations will work.
21 # In summary, I don't think using super in these situations will work.
22 # Instead we will need to call the __init__ methods of both parents
22 # Instead we will need to call the __init__ methods of both parents
23 # by hand. Not pretty, but it works.
23 # by hand. Not pretty, but it works.
24
24
25 class QtSubSocketChannel(SubSocketChannel, QtCore.QObject):
25 class QtSubSocketChannel(SubSocketChannel, QtCore.QObject):
26
26
27 # Emitted when any message is received.
27 # Emitted when any message is received.
28 message_received = QtCore.pyqtSignal(object)
28 message_received = QtCore.pyqtSignal(object)
29
29
30 # Emitted when a message of type 'stream' is received.
30 # Emitted when a message of type 'stream' is received.
31 stream_received = QtCore.pyqtSignal(object)
31 stream_received = QtCore.pyqtSignal(object)
32
32
33 # Emitted when a message of type 'pyin' is received.
33 # Emitted when a message of type 'pyin' is received.
34 pyin_received = QtCore.pyqtSignal(object)
34 pyin_received = QtCore.pyqtSignal(object)
35
35
36 # Emitted when a message of type 'pyout' is received.
36 # Emitted when a message of type 'pyout' is received.
37 pyout_received = QtCore.pyqtSignal(object)
37 pyout_received = QtCore.pyqtSignal(object)
38
38
39 # Emitted when a message of type 'pyerr' is received.
39 # Emitted when a message of type 'pyerr' is received.
40 pyerr_received = QtCore.pyqtSignal(object)
40 pyerr_received = QtCore.pyqtSignal(object)
41
41
42 # Emitted when a crash report message is received from the kernel's
42 # Emitted when a crash report message is received from the kernel's
43 # last-resort sys.excepthook.
43 # last-resort sys.excepthook.
44 crash_received = QtCore.pyqtSignal(object)
44 crash_received = QtCore.pyqtSignal(object)
45
45
46 #---------------------------------------------------------------------------
46 #---------------------------------------------------------------------------
47 # 'object' interface
47 # 'object' interface
48 #---------------------------------------------------------------------------
48 #---------------------------------------------------------------------------
49
49
50 def __init__(self, *args, **kw):
50 def __init__(self, *args, **kw):
51 """ Reimplemented to ensure that QtCore.QObject is initialized first.
51 """ Reimplemented to ensure that QtCore.QObject is initialized first.
52 """
52 """
53 QtCore.QObject.__init__(self)
53 QtCore.QObject.__init__(self)
54 SubSocketChannel.__init__(self, *args, **kw)
54 SubSocketChannel.__init__(self, *args, **kw)
55
55
56 #---------------------------------------------------------------------------
56 #---------------------------------------------------------------------------
57 # 'SubSocketChannel' interface
57 # 'SubSocketChannel' interface
58 #---------------------------------------------------------------------------
58 #---------------------------------------------------------------------------
59
59
60 def call_handlers(self, msg):
60 def call_handlers(self, msg):
61 """ Reimplemented to emit signals instead of making callbacks.
61 """ Reimplemented to emit signals instead of making callbacks.
62 """
62 """
63 # Emit the generic signal.
63 # Emit the generic signal.
64 self.message_received.emit(msg)
64 self.message_received.emit(msg)
65
65
66 # Emit signals for specialized message types.
66 # Emit signals for specialized message types.
67 msg_type = msg['msg_type']
67 msg_type = msg['msg_type']
68 signal = getattr(self, msg_type + '_received', None)
68 signal = getattr(self, msg_type + '_received', None)
69 if signal:
69 if signal:
70 signal.emit(msg)
70 signal.emit(msg)
71 elif msg_type in ('stdout', 'stderr'):
71 elif msg_type in ('stdout', 'stderr'):
72 self.stream_received.emit(msg)
72 self.stream_received.emit(msg)
73
73
74 def flush(self):
74 def flush(self):
75 """ Reimplemented to ensure that signals are dispatched immediately.
75 """ Reimplemented to ensure that signals are dispatched immediately.
76 """
76 """
77 super(QtSubSocketChannel, self).flush()
77 super(QtSubSocketChannel, self).flush()
78 QtCore.QCoreApplication.instance().processEvents()
78 QtCore.QCoreApplication.instance().processEvents()
79
79
80
80
81 class QtXReqSocketChannel(XReqSocketChannel, QtCore.QObject):
81 class QtXReqSocketChannel(XReqSocketChannel, QtCore.QObject):
82
82
83 # Emitted when any message is received.
83 # Emitted when any message is received.
84 message_received = QtCore.pyqtSignal(object)
84 message_received = QtCore.pyqtSignal(object)
85
85
86 # Emitted when a reply has been received for the corresponding request type.
86 # Emitted when a reply has been received for the corresponding request type.
87 execute_reply = QtCore.pyqtSignal(object)
87 execute_reply = QtCore.pyqtSignal(object)
88 complete_reply = QtCore.pyqtSignal(object)
88 complete_reply = QtCore.pyqtSignal(object)
89 object_info_reply = QtCore.pyqtSignal(object)
89 object_info_reply = QtCore.pyqtSignal(object)
90
90
91 #---------------------------------------------------------------------------
91 #---------------------------------------------------------------------------
92 # 'object' interface
92 # 'object' interface
93 #---------------------------------------------------------------------------
93 #---------------------------------------------------------------------------
94
94
95 def __init__(self, *args, **kw):
95 def __init__(self, *args, **kw):
96 """ Reimplemented to ensure that QtCore.QObject is initialized first.
96 """ Reimplemented to ensure that QtCore.QObject is initialized first.
97 """
97 """
98 QtCore.QObject.__init__(self)
98 QtCore.QObject.__init__(self)
99 XReqSocketChannel.__init__(self, *args, **kw)
99 XReqSocketChannel.__init__(self, *args, **kw)
100
100
101 #---------------------------------------------------------------------------
101 #---------------------------------------------------------------------------
102 # 'XReqSocketChannel' interface
102 # 'XReqSocketChannel' interface
103 #---------------------------------------------------------------------------
103 #---------------------------------------------------------------------------
104
104
105 def call_handlers(self, msg):
105 def call_handlers(self, msg):
106 """ Reimplemented to emit signals instead of making callbacks.
106 """ Reimplemented to emit signals instead of making callbacks.
107 """
107 """
108 # Emit the generic signal.
108 # Emit the generic signal.
109 self.message_received.emit(msg)
109 self.message_received.emit(msg)
110
110
111 # Emit signals for specialized message types.
111 # Emit signals for specialized message types.
112 msg_type = msg['msg_type']
112 msg_type = msg['msg_type']
113 signal = getattr(self, msg_type, None)
113 signal = getattr(self, msg_type, None)
114 if signal:
114 if signal:
115 signal.emit(msg)
115 signal.emit(msg)
116
116
117
117
118 class QtRepSocketChannel(RepSocketChannel, QtCore.QObject):
118 class QtRepSocketChannel(RepSocketChannel, QtCore.QObject):
119
119
120 # Emitted when any message is received.
120 # Emitted when any message is received.
121 message_received = QtCore.pyqtSignal(object)
121 message_received = QtCore.pyqtSignal(object)
122
122
123 # Emitted when an input request is received.
123 # Emitted when an input request is received.
124 input_requested = QtCore.pyqtSignal(object)
124 input_requested = QtCore.pyqtSignal(object)
125
125
126 #---------------------------------------------------------------------------
126 #---------------------------------------------------------------------------
127 # 'object' interface
127 # 'object' interface
128 #---------------------------------------------------------------------------
128 #---------------------------------------------------------------------------
129
129
130 def __init__(self, *args, **kw):
130 def __init__(self, *args, **kw):
131 """ Reimplemented to ensure that QtCore.QObject is initialized first.
131 """ Reimplemented to ensure that QtCore.QObject is initialized first.
132 """
132 """
133 QtCore.QObject.__init__(self)
133 QtCore.QObject.__init__(self)
134 RepSocketChannel.__init__(self, *args, **kw)
134 RepSocketChannel.__init__(self, *args, **kw)
135
135
136 #---------------------------------------------------------------------------
136 #---------------------------------------------------------------------------
137 # 'RepSocketChannel' interface
137 # 'RepSocketChannel' interface
138 #---------------------------------------------------------------------------
138 #---------------------------------------------------------------------------
139
139
140 def call_handlers(self, msg):
140 def call_handlers(self, msg):
141 """ Reimplemented to emit signals instead of making callbacks.
141 """ Reimplemented to emit signals instead of making callbacks.
142 """
142 """
143 # Emit the generic signal.
143 # Emit the generic signal.
144 self.message_received.emit(msg)
144 self.message_received.emit(msg)
145
145
146 # Emit signals for specialized message types.
146 # Emit signals for specialized message types.
147 msg_type = msg['msg_type']
147 msg_type = msg['msg_type']
148 if msg_type == 'input_request':
148 if msg_type == 'input_request':
149 self.input_requested.emit(msg)
149 self.input_requested.emit(msg)
150
150
151
151
152 class QtHBSocketChannel(HBSocketChannel, QtCore.QObject):
153
154 # Emitted when the kernel has died.
155 kernel_died = QtCore.pyqtSignal(object)
156
157 #---------------------------------------------------------------------------
158 # 'object' interface
159 #---------------------------------------------------------------------------
160
161 def __init__(self, *args, **kw):
162 """ Reimplemented to ensure that QtCore.QObject is initialized first.
163 """
164 QtCore.QObject.__init__(self)
165 HBSocketChannel.__init__(self, *args, **kw)
166
167 #---------------------------------------------------------------------------
168 # 'RepSocketChannel' interface
169 #---------------------------------------------------------------------------
170
171 def call_handlers(self, since_last_heartbeat):
172 """ Reimplemented to emit signals instead of making callbacks.
173 """
174 # Emit the generic signal.
175 self.kernel_died.emit(since_last_heartbeat)
176
177
152 class QtKernelManager(KernelManager, QtCore.QObject):
178 class QtKernelManager(KernelManager, QtCore.QObject):
153 """ A KernelManager that provides signals and slots.
179 """ A KernelManager that provides signals and slots.
154 """
180 """
155
181
156 __metaclass__ = MetaQObjectHasTraits
182 __metaclass__ = MetaQObjectHasTraits
157
183
158 # Emitted when the kernel manager has started listening.
184 # Emitted when the kernel manager has started listening.
159 started_channels = QtCore.pyqtSignal()
185 started_channels = QtCore.pyqtSignal()
160
186
161 # Emitted when the kernel manager has stopped listening.
187 # Emitted when the kernel manager has stopped listening.
162 stopped_channels = QtCore.pyqtSignal()
188 stopped_channels = QtCore.pyqtSignal()
163
189
164 # Use Qt-specific channel classes that emit signals.
190 # Use Qt-specific channel classes that emit signals.
165 sub_channel_class = Type(QtSubSocketChannel)
191 sub_channel_class = Type(QtSubSocketChannel)
166 xreq_channel_class = Type(QtXReqSocketChannel)
192 xreq_channel_class = Type(QtXReqSocketChannel)
167 rep_channel_class = Type(QtRepSocketChannel)
193 rep_channel_class = Type(QtRepSocketChannel)
194 hb_channel_class = Type(QtHBSocketChannel)
168
195
169 #---------------------------------------------------------------------------
196 #---------------------------------------------------------------------------
170 # 'object' interface
197 # 'object' interface
171 #---------------------------------------------------------------------------
198 #---------------------------------------------------------------------------
172
199
173 def __init__(self, *args, **kw):
200 def __init__(self, *args, **kw):
174 """ Reimplemented to ensure that QtCore.QObject is initialized first.
201 """ Reimplemented to ensure that QtCore.QObject is initialized first.
175 """
202 """
176 QtCore.QObject.__init__(self)
203 QtCore.QObject.__init__(self)
177 KernelManager.__init__(self, *args, **kw)
204 KernelManager.__init__(self, *args, **kw)
178
205
179 #---------------------------------------------------------------------------
206 #---------------------------------------------------------------------------
180 # 'KernelManager' interface
207 # 'KernelManager' interface
181 #---------------------------------------------------------------------------
208 #---------------------------------------------------------------------------
182
209
183 def start_channels(self):
210 def start_channels(self):
184 """ Reimplemented to emit signal.
211 """ Reimplemented to emit signal.
185 """
212 """
186 super(QtKernelManager, self).start_channels()
213 super(QtKernelManager, self).start_channels()
187 self.started_channels.emit()
214 self.started_channels.emit()
188
215
189 def stop_channels(self):
216 def stop_channels(self):
190 """ Reimplemented to emit signal.
217 """ Reimplemented to emit signal.
191 """
218 """
192 super(QtKernelManager, self).stop_channels()
219 super(QtKernelManager, self).stop_channels()
193 self.stopped_channels.emit()
220 self.stopped_channels.emit()
@@ -1,199 +1,212 b''
1 """ Defines helper functions for creating kernel entry points and process
1 """ Defines helper functions for creating kernel entry points and process
2 launchers.
2 launchers.
3 """
3 """
4
4
5 # Standard library imports.
5 # Standard library imports.
6 import os
6 import os
7 import socket
7 import socket
8 from subprocess import Popen
8 from subprocess import Popen
9 import sys
9 import sys
10
10
11 # System library imports.
11 # System library imports.
12 import zmq
12 import zmq
13
13
14 # Local imports.
14 # Local imports.
15 from IPython.core.ultratb import FormattedTB
15 from IPython.core.ultratb import FormattedTB
16 from IPython.external.argparse import ArgumentParser
16 from IPython.external.argparse import ArgumentParser
17 from IPython.utils import io
17 from IPython.utils import io
18 from exitpoller import ExitPollerUnix, ExitPollerWindows
18 from exitpoller import ExitPollerUnix, ExitPollerWindows
19 from displayhook import DisplayHook
19 from displayhook import DisplayHook
20 from iostream import OutStream
20 from iostream import OutStream
21 from session import Session
21 from session import Session
22
22 from heartbeat import Heartbeat
23
23
24 def bind_port(socket, ip, port):
24 def bind_port(socket, ip, port):
25 """ Binds the specified ZMQ socket. If the port is zero, a random port is
25 """ Binds the specified ZMQ socket. If the port is zero, a random port is
26 chosen. Returns the port that was bound.
26 chosen. Returns the port that was bound.
27 """
27 """
28 connection = 'tcp://%s' % ip
28 connection = 'tcp://%s' % ip
29 if port <= 0:
29 if port <= 0:
30 port = socket.bind_to_random_port(connection)
30 port = socket.bind_to_random_port(connection)
31 else:
31 else:
32 connection += ':%i' % port
32 connection += ':%i' % port
33 socket.bind(connection)
33 socket.bind(connection)
34 return port
34 return port
35
35
36
36
37 def make_argument_parser():
37 def make_argument_parser():
38 """ Creates an ArgumentParser for the generic arguments supported by all
38 """ Creates an ArgumentParser for the generic arguments supported by all
39 kernel entry points.
39 kernel entry points.
40 """
40 """
41 parser = ArgumentParser()
41 parser = ArgumentParser()
42 parser.add_argument('--ip', type=str, default='127.0.0.1',
42 parser.add_argument('--ip', type=str, default='127.0.0.1',
43 help='set the kernel\'s IP address [default: local]')
43 help='set the kernel\'s IP address [default: local]')
44 parser.add_argument('--xrep', type=int, metavar='PORT', default=0,
44 parser.add_argument('--xrep', type=int, metavar='PORT', default=0,
45 help='set the XREP channel port [default: random]')
45 help='set the XREP channel port [default: random]')
46 parser.add_argument('--pub', type=int, metavar='PORT', default=0,
46 parser.add_argument('--pub', type=int, metavar='PORT', default=0,
47 help='set the PUB channel port [default: random]')
47 help='set the PUB channel port [default: random]')
48 parser.add_argument('--req', type=int, metavar='PORT', default=0,
48 parser.add_argument('--req', type=int, metavar='PORT', default=0,
49 help='set the REQ channel port [default: random]')
49 help='set the REQ channel port [default: random]')
50 parser.add_argument('--hb', type=int, metavar='PORT', default=0,
51 help='set the heartbeat port [default: random]')
50
52
51 if sys.platform == 'win32':
53 if sys.platform == 'win32':
52 parser.add_argument('--parent', type=int, metavar='HANDLE',
54 parser.add_argument('--parent', type=int, metavar='HANDLE',
53 default=0, help='kill this process if the process '
55 default=0, help='kill this process if the process '
54 'with HANDLE dies')
56 'with HANDLE dies')
55 else:
57 else:
56 parser.add_argument('--parent', action='store_true',
58 parser.add_argument('--parent', action='store_true',
57 help='kill this process if its parent dies')
59 help='kill this process if its parent dies')
58
60
59 return parser
61 return parser
60
62
61
63
62 def make_kernel(namespace, kernel_factory,
64 def make_kernel(namespace, kernel_factory,
63 out_stream_factory=None, display_hook_factory=None):
65 out_stream_factory=None, display_hook_factory=None):
64 """ Creates a kernel.
66 """ Creates a kernel.
65 """
67 """
66 # Install minimal exception handling
68 # Install minimal exception handling
67 sys.excepthook = FormattedTB(mode='Verbose', color_scheme='NoColor',
69 sys.excepthook = FormattedTB(mode='Verbose', color_scheme='NoColor',
68 ostream=sys.__stdout__)
70 ostream=sys.__stdout__)
69
71
70 # Create a context, a session, and the kernel sockets.
72 # Create a context, a session, and the kernel sockets.
71 io.raw_print("Starting the kernel...")
73 io.raw_print("Starting the kernel...")
72 context = zmq.Context()
74 context = zmq.Context()
73 session = Session(username=u'kernel')
75 session = Session(username=u'kernel')
74
76
75 reply_socket = context.socket(zmq.XREP)
77 reply_socket = context.socket(zmq.XREP)
76 xrep_port = bind_port(reply_socket, namespace.ip, namespace.xrep)
78 xrep_port = bind_port(reply_socket, namespace.ip, namespace.xrep)
77 io.raw_print("XREP Channel on port", xrep_port)
79 io.raw_print("XREP Channel on port", xrep_port)
78
80
79 pub_socket = context.socket(zmq.PUB)
81 pub_socket = context.socket(zmq.PUB)
80 pub_port = bind_port(pub_socket, namespace.ip, namespace.pub)
82 pub_port = bind_port(pub_socket, namespace.ip, namespace.pub)
81 io.raw_print("PUB Channel on port", pub_port)
83 io.raw_print("PUB Channel on port", pub_port)
82
84
83 req_socket = context.socket(zmq.XREQ)
85 req_socket = context.socket(zmq.XREQ)
84 req_port = bind_port(req_socket, namespace.ip, namespace.req)
86 req_port = bind_port(req_socket, namespace.ip, namespace.req)
85 io.raw_print("REQ Channel on port", req_port)
87 io.raw_print("REQ Channel on port", req_port)
86
88
89 hb = Heartbeat(context, (namespace.ip, namespace.hb))
90 hb.start()
91 io.raw_print("Heartbeat REP Channel on port", hb.port)
92
87 # Redirect input streams and set a display hook.
93 # Redirect input streams and set a display hook.
88 if out_stream_factory:
94 if out_stream_factory:
89 pass
95 pass
90 sys.stdout = out_stream_factory(session, pub_socket, u'stdout')
96 sys.stdout = out_stream_factory(session, pub_socket, u'stdout')
91 sys.stderr = out_stream_factory(session, pub_socket, u'stderr')
97 sys.stderr = out_stream_factory(session, pub_socket, u'stderr')
92 if display_hook_factory:
98 if display_hook_factory:
93 sys.displayhook = display_hook_factory(session, pub_socket)
99 sys.displayhook = display_hook_factory(session, pub_socket)
94
100
95 # Create the kernel.
101 # Create the kernel.
96 return kernel_factory(session=session, reply_socket=reply_socket,
102 return kernel_factory(session=session, reply_socket=reply_socket,
97 pub_socket=pub_socket, req_socket=req_socket)
103 pub_socket=pub_socket, req_socket=req_socket)
98
104
99
105
100 def start_kernel(namespace, kernel):
106 def start_kernel(namespace, kernel):
101 """ Starts a kernel.
107 """ Starts a kernel.
102 """
108 """
103 # Configure this kernel/process to die on parent termination, if necessary.
109 # Configure this kernel/process to die on parent termination, if necessary.
104 if namespace.parent:
110 if namespace.parent:
105 if sys.platform == 'win32':
111 if sys.platform == 'win32':
106 poller = ExitPollerWindows(namespace.parent)
112 poller = ExitPollerWindows(namespace.parent)
107 else:
113 else:
108 poller = ExitPollerUnix()
114 poller = ExitPollerUnix()
109 poller.start()
115 poller.start()
110
116
111 # Start the kernel mainloop.
117 # Start the kernel mainloop.
112 kernel.start()
118 kernel.start()
113
119
114
120
115 def make_default_main(kernel_factory):
121 def make_default_main(kernel_factory):
116 """ Creates the simplest possible kernel entry point.
122 """ Creates the simplest possible kernel entry point.
117 """
123 """
118 def main():
124 def main():
119 namespace = make_argument_parser().parse_args()
125 namespace = make_argument_parser().parse_args()
120 kernel = make_kernel(namespace, kernel_factory, OutStream, DisplayHook)
126 kernel = make_kernel(namespace, kernel_factory, OutStream, DisplayHook)
121 start_kernel(namespace, kernel)
127 start_kernel(namespace, kernel)
122 return main
128 return main
123
129
124
130
125 def base_launch_kernel(code, xrep_port=0, pub_port=0, req_port=0,
131 def base_launch_kernel(code, xrep_port=0, pub_port=0, req_port=0, hb_port=0,
126 independent=False, extra_arguments=[]):
132 independent=False, extra_arguments=[]):
127 """ Launches a localhost kernel, binding to the specified ports.
133 """ Launches a localhost kernel, binding to the specified ports.
128
134
129 Parameters
135 Parameters
130 ----------
136 ----------
131 code : str,
137 code : str,
132 A string of Python code that imports and executes a kernel entry point.
138 A string of Python code that imports and executes a kernel entry point.
133
139
134 xrep_port : int, optional
140 xrep_port : int, optional
135 The port to use for XREP channel.
141 The port to use for XREP channel.
136
142
137 pub_port : int, optional
143 pub_port : int, optional
138 The port to use for the SUB channel.
144 The port to use for the SUB channel.
139
145
140 req_port : int, optional
146 req_port : int, optional
141 The port to use for the REQ (raw input) channel.
147 The port to use for the REQ (raw input) channel.
142
148
149 hb_port : int, optional
150 The port to use for the hearbeat REP channel.
151
143 independent : bool, optional (default False)
152 independent : bool, optional (default False)
144 If set, the kernel process is guaranteed to survive if this process
153 If set, the kernel process is guaranteed to survive if this process
145 dies. If not set, an effort is made to ensure that the kernel is killed
154 dies. If not set, an effort is made to ensure that the kernel is killed
146 when this process dies. Note that in this case it is still good practice
155 when this process dies. Note that in this case it is still good practice
147 to kill kernels manually before exiting.
156 to kill kernels manually before exiting.
148
157
149 extra_arguments = list, optional
158 extra_arguments = list, optional
150 A list of extra arguments to pass when executing the launch code.
159 A list of extra arguments to pass when executing the launch code.
151
160
152 Returns
161 Returns
153 -------
162 -------
154 A tuple of form:
163 A tuple of form:
155 (kernel_process, xrep_port, pub_port, req_port)
164 (kernel_process, xrep_port, pub_port, req_port)
156 where kernel_process is a Popen object and the ports are integers.
165 where kernel_process is a Popen object and the ports are integers.
157 """
166 """
158 # Find open ports as necessary.
167 # Find open ports as necessary.
159 ports = []
168 ports = []
160 ports_needed = int(xrep_port <= 0) + int(pub_port <= 0) + int(req_port <= 0)
169 ports_needed = int(xrep_port <= 0) + int(pub_port <= 0) + \
170 int(req_port <= 0) + int(hb_port <= 0)
161 for i in xrange(ports_needed):
171 for i in xrange(ports_needed):
162 sock = socket.socket()
172 sock = socket.socket()
163 sock.bind(('', 0))
173 sock.bind(('', 0))
164 ports.append(sock)
174 ports.append(sock)
165 for i, sock in enumerate(ports):
175 for i, sock in enumerate(ports):
166 port = sock.getsockname()[1]
176 port = sock.getsockname()[1]
167 sock.close()
177 sock.close()
168 ports[i] = port
178 ports[i] = port
169 if xrep_port <= 0:
179 if xrep_port <= 0:
170 xrep_port = ports.pop(0)
180 xrep_port = ports.pop(0)
171 if pub_port <= 0:
181 if pub_port <= 0:
172 pub_port = ports.pop(0)
182 pub_port = ports.pop(0)
173 if req_port <= 0:
183 if req_port <= 0:
174 req_port = ports.pop(0)
184 req_port = ports.pop(0)
185 if hb_port <= 0:
186 hb_port = ports.pop(0)
175
187
176 # Build the kernel launch command.
188 # Build the kernel launch command.
177 arguments = [ sys.executable, '-c', code, '--xrep', str(xrep_port),
189 arguments = [ sys.executable, '-c', code, '--xrep', str(xrep_port),
178 '--pub', str(pub_port), '--req', str(req_port) ]
190 '--pub', str(pub_port), '--req', str(req_port),
191 '--hb', str(hb_port) ]
179 arguments.extend(extra_arguments)
192 arguments.extend(extra_arguments)
180
193
181 # Spawn a kernel.
194 # Spawn a kernel.
182 if independent:
195 if independent:
183 if sys.platform == 'win32':
196 if sys.platform == 'win32':
184 proc = Popen(['start', '/b'] + arguments, shell=True)
197 proc = Popen(['start', '/b'] + arguments, shell=True)
185 else:
198 else:
186 proc = Popen(arguments, preexec_fn=lambda: os.setsid())
199 proc = Popen(arguments, preexec_fn=lambda: os.setsid())
187 else:
200 else:
188 if sys.platform == 'win32':
201 if sys.platform == 'win32':
189 from _subprocess import DuplicateHandle, GetCurrentProcess, \
202 from _subprocess import DuplicateHandle, GetCurrentProcess, \
190 DUPLICATE_SAME_ACCESS
203 DUPLICATE_SAME_ACCESS
191 pid = GetCurrentProcess()
204 pid = GetCurrentProcess()
192 handle = DuplicateHandle(pid, pid, pid, 0,
205 handle = DuplicateHandle(pid, pid, pid, 0,
193 True, # Inheritable by new processes.
206 True, # Inheritable by new processes.
194 DUPLICATE_SAME_ACCESS)
207 DUPLICATE_SAME_ACCESS)
195 proc = Popen(arguments + ['--parent', str(int(handle))])
208 proc = Popen(arguments + ['--parent', str(int(handle))])
196 else:
209 else:
197 proc = Popen(arguments + ['--parent'])
210 proc = Popen(arguments + ['--parent'])
198
211
199 return proc, xrep_port, pub_port, req_port
212 return proc, xrep_port, pub_port, req_port, hb_port
@@ -1,459 +1,462 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 """A simple interactive kernel that talks to a frontend over 0MQ.
2 """A simple interactive kernel that talks to a frontend over 0MQ.
3
3
4 Things to do:
4 Things to do:
5
5
6 * Implement `set_parent` logic. Right before doing exec, the Kernel should
6 * Implement `set_parent` logic. Right before doing exec, the Kernel should
7 call set_parent on all the PUB objects with the message about to be executed.
7 call set_parent on all the PUB objects with the message about to be executed.
8 * Implement random port and security key logic.
8 * Implement random port and security key logic.
9 * Implement control messages.
9 * Implement control messages.
10 * Implement event loop and poll version.
10 * Implement event loop and poll version.
11 """
11 """
12
12
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14 # Imports
14 # Imports
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 from __future__ import print_function
16 from __future__ import print_function
17
17
18 # Standard library imports.
18 # Standard library imports.
19 import __builtin__
19 import __builtin__
20 import sys
20 import sys
21 import time
21 import time
22 import traceback
22 import traceback
23
23
24 # System library imports.
24 # System library imports.
25 import zmq
25 import zmq
26
26
27 # Local imports.
27 # Local imports.
28 from IPython.config.configurable import Configurable
28 from IPython.config.configurable import Configurable
29 from IPython.utils import io
29 from IPython.utils import io
30 from IPython.lib import pylabtools
30 from IPython.lib import pylabtools
31 from IPython.utils.traitlets import Instance
31 from IPython.utils.traitlets import Instance
32 from entry_point import base_launch_kernel, make_argument_parser, make_kernel, \
32 from entry_point import base_launch_kernel, make_argument_parser, make_kernel, \
33 start_kernel
33 start_kernel
34 from iostream import OutStream
34 from iostream import OutStream
35 from session import Session, Message
35 from session import Session, Message
36 from zmqshell import ZMQInteractiveShell
36 from zmqshell import ZMQInteractiveShell
37
37
38 #-----------------------------------------------------------------------------
38 #-----------------------------------------------------------------------------
39 # Main kernel class
39 # Main kernel class
40 #-----------------------------------------------------------------------------
40 #-----------------------------------------------------------------------------
41
41
42 class Kernel(Configurable):
42 class Kernel(Configurable):
43
43
44 #---------------------------------------------------------------------------
44 #---------------------------------------------------------------------------
45 # Kernel interface
45 # Kernel interface
46 #---------------------------------------------------------------------------
46 #---------------------------------------------------------------------------
47
47
48 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
48 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
49 session = Instance(Session)
49 session = Instance(Session)
50 reply_socket = Instance('zmq.Socket')
50 reply_socket = Instance('zmq.Socket')
51 pub_socket = Instance('zmq.Socket')
51 pub_socket = Instance('zmq.Socket')
52 req_socket = Instance('zmq.Socket')
52 req_socket = Instance('zmq.Socket')
53
53
54 def __init__(self, **kwargs):
54 def __init__(self, **kwargs):
55 super(Kernel, self).__init__(**kwargs)
55 super(Kernel, self).__init__(**kwargs)
56
56
57 # Initialize the InteractiveShell subclass
57 # Initialize the InteractiveShell subclass
58 self.shell = ZMQInteractiveShell.instance()
58 self.shell = ZMQInteractiveShell.instance()
59 self.shell.displayhook.session = self.session
59 self.shell.displayhook.session = self.session
60 self.shell.displayhook.pub_socket = self.pub_socket
60 self.shell.displayhook.pub_socket = self.pub_socket
61
61
62 # TMP - hack while developing
62 # TMP - hack while developing
63 self.shell._reply_content = None
63 self.shell._reply_content = None
64
64
65 # Build dict of handlers for message types
65 # Build dict of handlers for message types
66 msg_types = [ 'execute_request', 'complete_request',
66 msg_types = [ 'execute_request', 'complete_request',
67 'object_info_request', 'prompt_request',
67 'object_info_request', 'prompt_request',
68 'history_request' ]
68 'history_request' ]
69 self.handlers = {}
69 self.handlers = {}
70 for msg_type in msg_types:
70 for msg_type in msg_types:
71 self.handlers[msg_type] = getattr(self, msg_type)
71 self.handlers[msg_type] = getattr(self, msg_type)
72
72
73 def do_one_iteration(self):
73 def do_one_iteration(self):
74 try:
74 try:
75 ident = self.reply_socket.recv(zmq.NOBLOCK)
75 ident = self.reply_socket.recv(zmq.NOBLOCK)
76 except zmq.ZMQError, e:
76 except zmq.ZMQError, e:
77 if e.errno == zmq.EAGAIN:
77 if e.errno == zmq.EAGAIN:
78 return
78 return
79 else:
79 else:
80 raise
80 raise
81 # FIXME: Bug in pyzmq/zmq?
81 # FIXME: Bug in pyzmq/zmq?
82 # assert self.reply_socket.rcvmore(), "Missing message part."
82 # assert self.reply_socket.rcvmore(), "Missing message part."
83 msg = self.reply_socket.recv_json()
83 msg = self.reply_socket.recv_json()
84 omsg = Message(msg)
84 omsg = Message(msg)
85 io.raw_print('\n')
85 io.raw_print('\n')
86 io.raw_print(omsg)
86 io.raw_print(omsg)
87 handler = self.handlers.get(omsg.msg_type, None)
87 handler = self.handlers.get(omsg.msg_type, None)
88 if handler is None:
88 if handler is None:
89 io.raw_print_err("UNKNOWN MESSAGE TYPE:", omsg)
89 io.raw_print_err("UNKNOWN MESSAGE TYPE:", omsg)
90 else:
90 else:
91 handler(ident, omsg)
91 handler(ident, omsg)
92
92
93 def start(self):
93 def start(self):
94 """ Start the kernel main loop.
94 """ Start the kernel main loop.
95 """
95 """
96 while True:
96 while True:
97 time.sleep(0.05)
97 time.sleep(0.05)
98 self.do_one_iteration()
98 self.do_one_iteration()
99
99
100
100
101 #---------------------------------------------------------------------------
101 #---------------------------------------------------------------------------
102 # Kernel request handlers
102 # Kernel request handlers
103 #---------------------------------------------------------------------------
103 #---------------------------------------------------------------------------
104
104
105 def execute_request(self, ident, parent):
105 def execute_request(self, ident, parent):
106 try:
106 try:
107 code = parent[u'content'][u'code']
107 code = parent[u'content'][u'code']
108 except:
108 except:
109 io.raw_print_err("Got bad msg: ")
109 io.raw_print_err("Got bad msg: ")
110 io.raw_print_err(Message(parent))
110 io.raw_print_err(Message(parent))
111 return
111 return
112 pyin_msg = self.session.msg(u'pyin',{u'code':code}, parent=parent)
112 pyin_msg = self.session.msg(u'pyin',{u'code':code}, parent=parent)
113 self.pub_socket.send_json(pyin_msg)
113 self.pub_socket.send_json(pyin_msg)
114
114
115 try:
115 try:
116 # Replace raw_input. Note that is not sufficient to replace
116 # Replace raw_input. Note that is not sufficient to replace
117 # raw_input in the user namespace.
117 # raw_input in the user namespace.
118 raw_input = lambda prompt='': self._raw_input(prompt, ident, parent)
118 raw_input = lambda prompt='': self._raw_input(prompt, ident, parent)
119 __builtin__.raw_input = raw_input
119 __builtin__.raw_input = raw_input
120
120
121 # Set the parent message of the display hook and out streams.
121 # Set the parent message of the display hook and out streams.
122 self.shell.displayhook.set_parent(parent)
122 self.shell.displayhook.set_parent(parent)
123 sys.stdout.set_parent(parent)
123 sys.stdout.set_parent(parent)
124 sys.stderr.set_parent(parent)
124 sys.stderr.set_parent(parent)
125
125
126 # FIXME: runlines calls the exception handler itself. We should
126 # FIXME: runlines calls the exception handler itself. We should
127 # clean this up.
127 # clean this up.
128 self.shell._reply_content = None
128 self.shell._reply_content = None
129 self.shell.runlines(code)
129 self.shell.runlines(code)
130 except:
130 except:
131 # FIXME: this code right now isn't being used yet by default,
131 # FIXME: this code right now isn't being used yet by default,
132 # because the runlines() call above directly fires off exception
132 # because the runlines() call above directly fires off exception
133 # reporting. This code, therefore, is only active in the scenario
133 # reporting. This code, therefore, is only active in the scenario
134 # where runlines itself has an unhandled exception. We need to
134 # where runlines itself has an unhandled exception. We need to
135 # uniformize this, for all exception construction to come from a
135 # uniformize this, for all exception construction to come from a
136 # single location in the codbase.
136 # single location in the codbase.
137 etype, evalue, tb = sys.exc_info()
137 etype, evalue, tb = sys.exc_info()
138 tb_list = traceback.format_exception(etype, evalue, tb)
138 tb_list = traceback.format_exception(etype, evalue, tb)
139 reply_content = self.shell._showtraceback(etype, evalue, tb_list)
139 reply_content = self.shell._showtraceback(etype, evalue, tb_list)
140 else:
140 else:
141 payload = self.shell.payload_manager.read_payload()
141 payload = self.shell.payload_manager.read_payload()
142 # Be agressive about clearing the payload because we don't want
142 # Be agressive about clearing the payload because we don't want
143 # it to sit in memory until the next execute_request comes in.
143 # it to sit in memory until the next execute_request comes in.
144 self.shell.payload_manager.clear_payload()
144 self.shell.payload_manager.clear_payload()
145 reply_content = { 'status' : 'ok', 'payload' : payload }
145 reply_content = { 'status' : 'ok', 'payload' : payload }
146
146
147 # Compute the prompt information
147 # Compute the prompt information
148 prompt_number = self.shell.displayhook.prompt_count
148 prompt_number = self.shell.displayhook.prompt_count
149 reply_content['prompt_number'] = prompt_number
149 reply_content['prompt_number'] = prompt_number
150 prompt_string = self.shell.displayhook.prompt1.peek_next_prompt()
150 prompt_string = self.shell.displayhook.prompt1.peek_next_prompt()
151 next_prompt = {'prompt_string' : prompt_string,
151 next_prompt = {'prompt_string' : prompt_string,
152 'prompt_number' : prompt_number+1,
152 'prompt_number' : prompt_number+1,
153 'input_sep' : self.shell.displayhook.input_sep}
153 'input_sep' : self.shell.displayhook.input_sep}
154 reply_content['next_prompt'] = next_prompt
154 reply_content['next_prompt'] = next_prompt
155
155
156 # TMP - fish exception info out of shell, possibly left there by
156 # TMP - fish exception info out of shell, possibly left there by
157 # runlines
157 # runlines
158 if self.shell._reply_content is not None:
158 if self.shell._reply_content is not None:
159 reply_content.update(self.shell._reply_content)
159 reply_content.update(self.shell._reply_content)
160
160
161 # Flush output before sending the reply.
161 # Flush output before sending the reply.
162 sys.stderr.flush()
162 sys.stderr.flush()
163 sys.stdout.flush()
163 sys.stdout.flush()
164
164
165 # Send the reply.
165 # Send the reply.
166 reply_msg = self.session.msg(u'execute_reply', reply_content, parent)
166 reply_msg = self.session.msg(u'execute_reply', reply_content, parent)
167 io.raw_print(Message(reply_msg))
167 io.raw_print(Message(reply_msg))
168 self.reply_socket.send(ident, zmq.SNDMORE)
168 self.reply_socket.send(ident, zmq.SNDMORE)
169 self.reply_socket.send_json(reply_msg)
169 self.reply_socket.send_json(reply_msg)
170 if reply_msg['content']['status'] == u'error':
170 if reply_msg['content']['status'] == u'error':
171 self._abort_queue()
171 self._abort_queue()
172
172
173 def complete_request(self, ident, parent):
173 def complete_request(self, ident, parent):
174 txt, matches = self._complete(parent)
174 txt, matches = self._complete(parent)
175 matches = {'matches' : matches,
175 matches = {'matches' : matches,
176 'matched_text' : txt,
176 'matched_text' : txt,
177 'status' : 'ok'}
177 'status' : 'ok'}
178 completion_msg = self.session.send(self.reply_socket, 'complete_reply',
178 completion_msg = self.session.send(self.reply_socket, 'complete_reply',
179 matches, parent, ident)
179 matches, parent, ident)
180 io.raw_print(completion_msg)
180 io.raw_print(completion_msg)
181
181
182 def object_info_request(self, ident, parent):
182 def object_info_request(self, ident, parent):
183 context = parent['content']['oname'].split('.')
183 context = parent['content']['oname'].split('.')
184 object_info = self._object_info(context)
184 object_info = self._object_info(context)
185 msg = self.session.send(self.reply_socket, 'object_info_reply',
185 msg = self.session.send(self.reply_socket, 'object_info_reply',
186 object_info, parent, ident)
186 object_info, parent, ident)
187 io.raw_print(msg)
187 io.raw_print(msg)
188
188
189 def prompt_request(self, ident, parent):
189 def prompt_request(self, ident, parent):
190 prompt_number = self.shell.displayhook.prompt_count
190 prompt_number = self.shell.displayhook.prompt_count
191 prompt_string = self.shell.displayhook.prompt1.peek_next_prompt()
191 prompt_string = self.shell.displayhook.prompt1.peek_next_prompt()
192 content = {'prompt_string' : prompt_string,
192 content = {'prompt_string' : prompt_string,
193 'prompt_number' : prompt_number+1,
193 'prompt_number' : prompt_number+1,
194 'input_sep' : self.shell.displayhook.input_sep}
194 'input_sep' : self.shell.displayhook.input_sep}
195 msg = self.session.send(self.reply_socket, 'prompt_reply',
195 msg = self.session.send(self.reply_socket, 'prompt_reply',
196 content, parent, ident)
196 content, parent, ident)
197 io.raw_print(msg)
197 io.raw_print(msg)
198
198
199 def history_request(self, ident, parent):
199 def history_request(self, ident, parent):
200 output = parent['content']['output']
200 output = parent['content']['output']
201 index = parent['content']['index']
201 index = parent['content']['index']
202 raw = parent['content']['raw']
202 raw = parent['content']['raw']
203 hist = self.shell.get_history(index=index, raw=raw, output=output)
203 hist = self.shell.get_history(index=index, raw=raw, output=output)
204 content = {'history' : hist}
204 content = {'history' : hist}
205 msg = self.session.send(self.reply_socket, 'history_reply',
205 msg = self.session.send(self.reply_socket, 'history_reply',
206 content, parent, ident)
206 content, parent, ident)
207 io.raw_print(msg)
207 io.raw_print(msg)
208
208
209 #---------------------------------------------------------------------------
209 #---------------------------------------------------------------------------
210 # Protected interface
210 # Protected interface
211 #---------------------------------------------------------------------------
211 #---------------------------------------------------------------------------
212
212
213 def _abort_queue(self):
213 def _abort_queue(self):
214 while True:
214 while True:
215 try:
215 try:
216 ident = self.reply_socket.recv(zmq.NOBLOCK)
216 ident = self.reply_socket.recv(zmq.NOBLOCK)
217 except zmq.ZMQError, e:
217 except zmq.ZMQError, e:
218 if e.errno == zmq.EAGAIN:
218 if e.errno == zmq.EAGAIN:
219 break
219 break
220 else:
220 else:
221 assert self.reply_socket.rcvmore(), "Unexpected missing message part."
221 assert self.reply_socket.rcvmore(), "Unexpected missing message part."
222 msg = self.reply_socket.recv_json()
222 msg = self.reply_socket.recv_json()
223 io.raw_print("Aborting:\n", Message(msg))
223 io.raw_print("Aborting:\n", Message(msg))
224 msg_type = msg['msg_type']
224 msg_type = msg['msg_type']
225 reply_type = msg_type.split('_')[0] + '_reply'
225 reply_type = msg_type.split('_')[0] + '_reply'
226 reply_msg = self.session.msg(reply_type, {'status' : 'aborted'}, msg)
226 reply_msg = self.session.msg(reply_type, {'status' : 'aborted'}, msg)
227 io.raw_print(Message(reply_msg))
227 io.raw_print(Message(reply_msg))
228 self.reply_socket.send(ident,zmq.SNDMORE)
228 self.reply_socket.send(ident,zmq.SNDMORE)
229 self.reply_socket.send_json(reply_msg)
229 self.reply_socket.send_json(reply_msg)
230 # We need to wait a bit for requests to come in. This can probably
230 # We need to wait a bit for requests to come in. This can probably
231 # be set shorter for true asynchronous clients.
231 # be set shorter for true asynchronous clients.
232 time.sleep(0.1)
232 time.sleep(0.1)
233
233
234 def _raw_input(self, prompt, ident, parent):
234 def _raw_input(self, prompt, ident, parent):
235 # Flush output before making the request.
235 # Flush output before making the request.
236 sys.stderr.flush()
236 sys.stderr.flush()
237 sys.stdout.flush()
237 sys.stdout.flush()
238
238
239 # Send the input request.
239 # Send the input request.
240 content = dict(prompt=prompt)
240 content = dict(prompt=prompt)
241 msg = self.session.msg(u'input_request', content, parent)
241 msg = self.session.msg(u'input_request', content, parent)
242 self.req_socket.send_json(msg)
242 self.req_socket.send_json(msg)
243
243
244 # Await a response.
244 # Await a response.
245 reply = self.req_socket.recv_json()
245 reply = self.req_socket.recv_json()
246 try:
246 try:
247 value = reply['content']['value']
247 value = reply['content']['value']
248 except:
248 except:
249 io.raw_print_err("Got bad raw_input reply: ")
249 io.raw_print_err("Got bad raw_input reply: ")
250 io.raw_print_err(Message(parent))
250 io.raw_print_err(Message(parent))
251 value = ''
251 value = ''
252 return value
252 return value
253
253
254 def _complete(self, msg):
254 def _complete(self, msg):
255 c = msg['content']
255 c = msg['content']
256 try:
256 try:
257 cpos = int(c['cursor_pos'])
257 cpos = int(c['cursor_pos'])
258 except:
258 except:
259 # If we don't get something that we can convert to an integer, at
259 # If we don't get something that we can convert to an integer, at
260 # least attempt the completion guessing the cursor is at the end of
260 # least attempt the completion guessing the cursor is at the end of
261 # the text, if there's any, and otherwise of the line
261 # the text, if there's any, and otherwise of the line
262 cpos = len(c['text'])
262 cpos = len(c['text'])
263 if cpos==0:
263 if cpos==0:
264 cpos = len(c['line'])
264 cpos = len(c['line'])
265 return self.shell.complete(c['text'], c['line'], cpos)
265 return self.shell.complete(c['text'], c['line'], cpos)
266
266
267 def _object_info(self, context):
267 def _object_info(self, context):
268 symbol, leftover = self._symbol_from_context(context)
268 symbol, leftover = self._symbol_from_context(context)
269 if symbol is not None and not leftover:
269 if symbol is not None and not leftover:
270 doc = getattr(symbol, '__doc__', '')
270 doc = getattr(symbol, '__doc__', '')
271 else:
271 else:
272 doc = ''
272 doc = ''
273 object_info = dict(docstring = doc)
273 object_info = dict(docstring = doc)
274 return object_info
274 return object_info
275
275
276 def _symbol_from_context(self, context):
276 def _symbol_from_context(self, context):
277 if not context:
277 if not context:
278 return None, context
278 return None, context
279
279
280 base_symbol_string = context[0]
280 base_symbol_string = context[0]
281 symbol = self.shell.user_ns.get(base_symbol_string, None)
281 symbol = self.shell.user_ns.get(base_symbol_string, None)
282 if symbol is None:
282 if symbol is None:
283 symbol = __builtin__.__dict__.get(base_symbol_string, None)
283 symbol = __builtin__.__dict__.get(base_symbol_string, None)
284 if symbol is None:
284 if symbol is None:
285 return None, context
285 return None, context
286
286
287 context = context[1:]
287 context = context[1:]
288 for i, name in enumerate(context):
288 for i, name in enumerate(context):
289 new_symbol = getattr(symbol, name, None)
289 new_symbol = getattr(symbol, name, None)
290 if new_symbol is None:
290 if new_symbol is None:
291 return symbol, context[i:]
291 return symbol, context[i:]
292 else:
292 else:
293 symbol = new_symbol
293 symbol = new_symbol
294
294
295 return symbol, []
295 return symbol, []
296
296
297
297
298 class QtKernel(Kernel):
298 class QtKernel(Kernel):
299 """A Kernel subclass with Qt support."""
299 """A Kernel subclass with Qt support."""
300
300
301 def start(self):
301 def start(self):
302 """Start a kernel with QtPy4 event loop integration."""
302 """Start a kernel with QtPy4 event loop integration."""
303
303
304 from PyQt4 import QtGui, QtCore
304 from PyQt4 import QtGui, QtCore
305 from IPython.lib.guisupport import (
305 from IPython.lib.guisupport import (
306 get_app_qt4, start_event_loop_qt4
306 get_app_qt4, start_event_loop_qt4
307 )
307 )
308 self.app = get_app_qt4([" "])
308 self.app = get_app_qt4([" "])
309 self.app.setQuitOnLastWindowClosed(False)
309 self.app.setQuitOnLastWindowClosed(False)
310 self.timer = QtCore.QTimer()
310 self.timer = QtCore.QTimer()
311 self.timer.timeout.connect(self.do_one_iteration)
311 self.timer.timeout.connect(self.do_one_iteration)
312 self.timer.start(50)
312 self.timer.start(50)
313 start_event_loop_qt4(self.app)
313 start_event_loop_qt4(self.app)
314
314
315 class WxKernel(Kernel):
315 class WxKernel(Kernel):
316 """A Kernel subclass with Wx support."""
316 """A Kernel subclass with Wx support."""
317
317
318 def start(self):
318 def start(self):
319 """Start a kernel with wx event loop support."""
319 """Start a kernel with wx event loop support."""
320
320
321 import wx
321 import wx
322 from IPython.lib.guisupport import start_event_loop_wx
322 from IPython.lib.guisupport import start_event_loop_wx
323 doi = self.do_one_iteration
323 doi = self.do_one_iteration
324
324
325 # We have to put the wx.Timer in a wx.Frame for it to fire properly.
325 # We have to put the wx.Timer in a wx.Frame for it to fire properly.
326 # We make the Frame hidden when we create it in the main app below.
326 # We make the Frame hidden when we create it in the main app below.
327 class TimerFrame(wx.Frame):
327 class TimerFrame(wx.Frame):
328 def __init__(self, func):
328 def __init__(self, func):
329 wx.Frame.__init__(self, None, -1)
329 wx.Frame.__init__(self, None, -1)
330 self.timer = wx.Timer(self)
330 self.timer = wx.Timer(self)
331 self.timer.Start(50)
331 self.timer.Start(50)
332 self.Bind(wx.EVT_TIMER, self.on_timer)
332 self.Bind(wx.EVT_TIMER, self.on_timer)
333 self.func = func
333 self.func = func
334 def on_timer(self, event):
334 def on_timer(self, event):
335 self.func()
335 self.func()
336
336
337 # We need a custom wx.App to create our Frame subclass that has the
337 # We need a custom wx.App to create our Frame subclass that has the
338 # wx.Timer to drive the ZMQ event loop.
338 # wx.Timer to drive the ZMQ event loop.
339 class IPWxApp(wx.App):
339 class IPWxApp(wx.App):
340 def OnInit(self):
340 def OnInit(self):
341 self.frame = TimerFrame(doi)
341 self.frame = TimerFrame(doi)
342 self.frame.Show(False)
342 self.frame.Show(False)
343 return True
343 return True
344
344
345 # The redirect=False here makes sure that wx doesn't replace
345 # The redirect=False here makes sure that wx doesn't replace
346 # sys.stdout/stderr with its own classes.
346 # sys.stdout/stderr with its own classes.
347 self.app = IPWxApp(redirect=False)
347 self.app = IPWxApp(redirect=False)
348 start_event_loop_wx(self.app)
348 start_event_loop_wx(self.app)
349
349
350
350
351 class TkKernel(Kernel):
351 class TkKernel(Kernel):
352 """A Kernel subclass with Tk support."""
352 """A Kernel subclass with Tk support."""
353
353
354 def start(self):
354 def start(self):
355 """Start a Tk enabled event loop."""
355 """Start a Tk enabled event loop."""
356
356
357 import Tkinter
357 import Tkinter
358 doi = self.do_one_iteration
358 doi = self.do_one_iteration
359
359
360 # For Tkinter, we create a Tk object and call its withdraw method.
360 # For Tkinter, we create a Tk object and call its withdraw method.
361 class Timer(object):
361 class Timer(object):
362 def __init__(self, func):
362 def __init__(self, func):
363 self.app = Tkinter.Tk()
363 self.app = Tkinter.Tk()
364 self.app.withdraw()
364 self.app.withdraw()
365 self.func = func
365 self.func = func
366 def on_timer(self):
366 def on_timer(self):
367 self.func()
367 self.func()
368 self.app.after(50, self.on_timer)
368 self.app.after(50, self.on_timer)
369 def start(self):
369 def start(self):
370 self.on_timer() # Call it once to get things going.
370 self.on_timer() # Call it once to get things going.
371 self.app.mainloop()
371 self.app.mainloop()
372
372
373 self.timer = Timer(doi)
373 self.timer = Timer(doi)
374 self.timer.start()
374 self.timer.start()
375
375
376 #-----------------------------------------------------------------------------
376 #-----------------------------------------------------------------------------
377 # Kernel main and launch functions
377 # Kernel main and launch functions
378 #-----------------------------------------------------------------------------
378 #-----------------------------------------------------------------------------
379
379
380 def launch_kernel(xrep_port=0, pub_port=0, req_port=0, independent=False,
380 def launch_kernel(xrep_port=0, pub_port=0, req_port=0, hb_port=0,
381 pylab=False):
381 independent=False, pylab=False):
382 """ Launches a localhost kernel, binding to the specified ports.
382 """ Launches a localhost kernel, binding to the specified ports.
383
383
384 Parameters
384 Parameters
385 ----------
385 ----------
386 xrep_port : int, optional
386 xrep_port : int, optional
387 The port to use for XREP channel.
387 The port to use for XREP channel.
388
388
389 pub_port : int, optional
389 pub_port : int, optional
390 The port to use for the SUB channel.
390 The port to use for the SUB channel.
391
391
392 req_port : int, optional
392 req_port : int, optional
393 The port to use for the REQ (raw input) channel.
393 The port to use for the REQ (raw input) channel.
394
394
395 hb_port : int, optional
396 The port to use for the hearbeat REP channel.
397
395 independent : bool, optional (default False)
398 independent : bool, optional (default False)
396 If set, the kernel process is guaranteed to survive if this process
399 If set, the kernel process is guaranteed to survive if this process
397 dies. If not set, an effort is made to ensure that the kernel is killed
400 dies. If not set, an effort is made to ensure that the kernel is killed
398 when this process dies. Note that in this case it is still good practice
401 when this process dies. Note that in this case it is still good practice
399 to kill kernels manually before exiting.
402 to kill kernels manually before exiting.
400
403
401 pylab : bool or string, optional (default False)
404 pylab : bool or string, optional (default False)
402 If not False, the kernel will be launched with pylab enabled. If a
405 If not False, the kernel will be launched with pylab enabled. If a
403 string is passed, matplotlib will use the specified backend. Otherwise,
406 string is passed, matplotlib will use the specified backend. Otherwise,
404 matplotlib's default backend will be used.
407 matplotlib's default backend will be used.
405
408
406 Returns
409 Returns
407 -------
410 -------
408 A tuple of form:
411 A tuple of form:
409 (kernel_process, xrep_port, pub_port, req_port)
412 (kernel_process, xrep_port, pub_port, req_port)
410 where kernel_process is a Popen object and the ports are integers.
413 where kernel_process is a Popen object and the ports are integers.
411 """
414 """
412 extra_arguments = []
415 extra_arguments = []
413 if pylab:
416 if pylab:
414 extra_arguments.append('--pylab')
417 extra_arguments.append('--pylab')
415 if isinstance(pylab, basestring):
418 if isinstance(pylab, basestring):
416 extra_arguments.append(pylab)
419 extra_arguments.append(pylab)
417 return base_launch_kernel('from IPython.zmq.ipkernel import main; main()',
420 return base_launch_kernel('from IPython.zmq.ipkernel import main; main()',
418 xrep_port, pub_port, req_port, independent,
421 xrep_port, pub_port, req_port, hb_port,
419 extra_arguments)
422 independent, extra_arguments)
420
423
421 def main():
424 def main():
422 """ The IPython kernel main entry point.
425 """ The IPython kernel main entry point.
423 """
426 """
424 parser = make_argument_parser()
427 parser = make_argument_parser()
425 parser.add_argument('--pylab', type=str, metavar='GUI', nargs='?',
428 parser.add_argument('--pylab', type=str, metavar='GUI', nargs='?',
426 const='auto', help = \
429 const='auto', help = \
427 "Pre-load matplotlib and numpy for interactive use. If GUI is not \
430 "Pre-load matplotlib and numpy for interactive use. If GUI is not \
428 given, the GUI backend is matplotlib's, otherwise use one of: \
431 given, the GUI backend is matplotlib's, otherwise use one of: \
429 ['tk', 'gtk', 'qt', 'wx', 'payload-svg'].")
432 ['tk', 'gtk', 'qt', 'wx', 'payload-svg'].")
430 namespace = parser.parse_args()
433 namespace = parser.parse_args()
431
434
432 kernel_class = Kernel
435 kernel_class = Kernel
433
436
434 _kernel_classes = {
437 _kernel_classes = {
435 'qt' : QtKernel,
438 'qt' : QtKernel,
436 'qt4' : QtKernel,
439 'qt4' : QtKernel,
437 'payload-svg':Kernel,
440 'payload-svg':Kernel,
438 'wx' : WxKernel,
441 'wx' : WxKernel,
439 'tk' : TkKernel
442 'tk' : TkKernel
440 }
443 }
441 if namespace.pylab:
444 if namespace.pylab:
442 if namespace.pylab == 'auto':
445 if namespace.pylab == 'auto':
443 gui, backend = pylabtools.find_gui_and_backend()
446 gui, backend = pylabtools.find_gui_and_backend()
444 else:
447 else:
445 gui, backend = pylabtools.find_gui_and_backend(namespace.pylab)
448 gui, backend = pylabtools.find_gui_and_backend(namespace.pylab)
446 kernel_class = _kernel_classes.get(gui)
449 kernel_class = _kernel_classes.get(gui)
447 if kernel_class is None:
450 if kernel_class is None:
448 raise ValueError('GUI is not supported: %r' % gui)
451 raise ValueError('GUI is not supported: %r' % gui)
449 pylabtools.activate_matplotlib(backend)
452 pylabtools.activate_matplotlib(backend)
450
453
451 kernel = make_kernel(namespace, kernel_class, OutStream)
454 kernel = make_kernel(namespace, kernel_class, OutStream)
452
455
453 if namespace.pylab:
456 if namespace.pylab:
454 pylabtools.import_pylab(kernel.shell.user_ns)
457 pylabtools.import_pylab(kernel.shell.user_ns)
455
458
456 start_kernel(namespace, kernel)
459 start_kernel(namespace, kernel)
457
460
458 if __name__ == '__main__':
461 if __name__ == '__main__':
459 main()
462 main()
@@ -1,632 +1,715 b''
1 """Base classes to manage the interaction with a running kernel.
1 """Base classes to manage the interaction with a running kernel.
2
2
3 Todo
3 Todo
4 ====
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, TCPAddress
32 from IPython.utils.traitlets import HasTraits, Any, Instance, Type, TCPAddress
33 from session import Session
33 from session import Session
34
34
35 #-----------------------------------------------------------------------------
35 #-----------------------------------------------------------------------------
36 # Constants and exceptions
36 # Constants and exceptions
37 #-----------------------------------------------------------------------------
37 #-----------------------------------------------------------------------------
38
38
39 LOCALHOST = '127.0.0.1'
39 LOCALHOST = '127.0.0.1'
40
40
41 class InvalidPortNumber(Exception):
41 class InvalidPortNumber(Exception):
42 pass
42 pass
43
43
44 #-----------------------------------------------------------------------------
44 #-----------------------------------------------------------------------------
45 # ZMQ Socket Channel classes
45 # ZMQ Socket Channel classes
46 #-----------------------------------------------------------------------------
46 #-----------------------------------------------------------------------------
47
47
48 class ZmqSocketChannel(Thread):
48 class ZmqSocketChannel(Thread):
49 """The base class for the channels that use ZMQ sockets.
49 """The base class for the channels that use ZMQ sockets.
50 """
50 """
51 context = None
51 context = None
52 session = None
52 session = None
53 socket = None
53 socket = None
54 ioloop = None
54 ioloop = None
55 iostate = None
55 iostate = None
56 _address = None
56 _address = None
57
57
58 def __init__(self, context, session, address):
58 def __init__(self, context, session, address):
59 """Create a channel
59 """Create a channel
60
60
61 Parameters
61 Parameters
62 ----------
62 ----------
63 context : :class:`zmq.Context`
63 context : :class:`zmq.Context`
64 The ZMQ context to use.
64 The ZMQ context to use.
65 session : :class:`session.Session`
65 session : :class:`session.Session`
66 The session to use.
66 The session to use.
67 address : tuple
67 address : tuple
68 Standard (ip, port) tuple that the kernel is listening on.
68 Standard (ip, port) tuple that the kernel is listening on.
69 """
69 """
70 super(ZmqSocketChannel, self).__init__()
70 super(ZmqSocketChannel, self).__init__()
71 self.daemon = True
71 self.daemon = True
72
72
73 self.context = context
73 self.context = context
74 self.session = session
74 self.session = session
75 if address[1] == 0:
75 if address[1] == 0:
76 message = 'The port number for a channel cannot be 0.'
76 message = 'The port number for a channel cannot be 0.'
77 raise InvalidPortNumber(message)
77 raise InvalidPortNumber(message)
78 self._address = address
78 self._address = address
79
79
80 def stop(self):
80 def stop(self):
81 """Stop the channel's activity.
81 """Stop the channel's activity.
82
82
83 This calls :method:`Thread.join` and returns when the thread
83 This calls :method:`Thread.join` and returns when the thread
84 terminates. :class:`RuntimeError` will be raised if
84 terminates. :class:`RuntimeError` will be raised if
85 :method:`self.start` is called again.
85 :method:`self.start` is called again.
86 """
86 """
87 self.join()
87 self.join()
88
88
89 @property
89 @property
90 def address(self):
90 def address(self):
91 """Get the channel's address as an (ip, port) tuple.
91 """Get the channel's address as an (ip, port) tuple.
92
92
93 By the default, the address is (localhost, 0), where 0 means a random
93 By the default, the address is (localhost, 0), where 0 means a random
94 port.
94 port.
95 """
95 """
96 return self._address
96 return self._address
97
97
98 def add_io_state(self, state):
98 def add_io_state(self, state):
99 """Add IO state to the eventloop.
99 """Add IO state to the eventloop.
100
100
101 Parameters
101 Parameters
102 ----------
102 ----------
103 state : zmq.POLLIN|zmq.POLLOUT|zmq.POLLERR
103 state : zmq.POLLIN|zmq.POLLOUT|zmq.POLLERR
104 The IO state flag to set.
104 The IO state flag to set.
105
105
106 This is thread safe as it uses the thread safe IOLoop.add_callback.
106 This is thread safe as it uses the thread safe IOLoop.add_callback.
107 """
107 """
108 def add_io_state_callback():
108 def add_io_state_callback():
109 if not self.iostate & state:
109 if not self.iostate & state:
110 self.iostate = self.iostate | state
110 self.iostate = self.iostate | state
111 self.ioloop.update_handler(self.socket, self.iostate)
111 self.ioloop.update_handler(self.socket, self.iostate)
112 self.ioloop.add_callback(add_io_state_callback)
112 self.ioloop.add_callback(add_io_state_callback)
113
113
114 def drop_io_state(self, state):
114 def drop_io_state(self, state):
115 """Drop IO state from the eventloop.
115 """Drop IO state from the eventloop.
116
116
117 Parameters
117 Parameters
118 ----------
118 ----------
119 state : zmq.POLLIN|zmq.POLLOUT|zmq.POLLERR
119 state : zmq.POLLIN|zmq.POLLOUT|zmq.POLLERR
120 The IO state flag to set.
120 The IO state flag to set.
121
121
122 This is thread safe as it uses the thread safe IOLoop.add_callback.
122 This is thread safe as it uses the thread safe IOLoop.add_callback.
123 """
123 """
124 def drop_io_state_callback():
124 def drop_io_state_callback():
125 if self.iostate & state:
125 if self.iostate & state:
126 self.iostate = self.iostate & (~state)
126 self.iostate = self.iostate & (~state)
127 self.ioloop.update_handler(self.socket, self.iostate)
127 self.ioloop.update_handler(self.socket, self.iostate)
128 self.ioloop.add_callback(drop_io_state_callback)
128 self.ioloop.add_callback(drop_io_state_callback)
129
129
130
130
131 class XReqSocketChannel(ZmqSocketChannel):
131 class XReqSocketChannel(ZmqSocketChannel):
132 """The XREQ channel for issues request/replies to the kernel.
132 """The XREQ channel for issues request/replies to the kernel.
133 """
133 """
134
134
135 command_queue = None
135 command_queue = None
136
136
137 def __init__(self, context, session, address):
137 def __init__(self, context, session, address):
138 self.command_queue = Queue()
138 self.command_queue = Queue()
139 super(XReqSocketChannel, self).__init__(context, session, address)
139 super(XReqSocketChannel, self).__init__(context, session, address)
140
140
141 def run(self):
141 def run(self):
142 """The thread's main activity. Call start() instead."""
142 """The thread's main activity. Call start() instead."""
143 self.socket = self.context.socket(zmq.XREQ)
143 self.socket = self.context.socket(zmq.XREQ)
144 self.socket.setsockopt(zmq.IDENTITY, self.session.session)
144 self.socket.setsockopt(zmq.IDENTITY, self.session.session)
145 self.socket.connect('tcp://%s:%i' % self.address)
145 self.socket.connect('tcp://%s:%i' % self.address)
146 self.ioloop = ioloop.IOLoop()
146 self.ioloop = ioloop.IOLoop()
147 self.iostate = POLLERR|POLLIN
147 self.iostate = POLLERR|POLLIN
148 self.ioloop.add_handler(self.socket, self._handle_events,
148 self.ioloop.add_handler(self.socket, self._handle_events,
149 self.iostate)
149 self.iostate)
150 self.ioloop.start()
150 self.ioloop.start()
151
151
152 def stop(self):
152 def stop(self):
153 self.ioloop.stop()
153 self.ioloop.stop()
154 super(XReqSocketChannel, self).stop()
154 super(XReqSocketChannel, self).stop()
155
155
156 def call_handlers(self, msg):
156 def call_handlers(self, msg):
157 """This method is called in the ioloop thread when a message arrives.
157 """This method is called in the ioloop thread when a message arrives.
158
158
159 Subclasses should override this method to handle incoming messages.
159 Subclasses should override this method to handle incoming messages.
160 It is important to remember that this method is called in the thread
160 It is important to remember that this method is called in the thread
161 so that some logic must be done to ensure that the application leve
161 so that some logic must be done to ensure that the application leve
162 handlers are called in the application thread.
162 handlers are called in the application thread.
163 """
163 """
164 raise NotImplementedError('call_handlers must be defined in a subclass.')
164 raise NotImplementedError('call_handlers must be defined in a subclass.')
165
165
166 def execute(self, code, silent=False):
166 def execute(self, code, silent=False):
167 """Execute code in the kernel.
167 """Execute code in the kernel.
168
168
169 Parameters
169 Parameters
170 ----------
170 ----------
171 code : str
171 code : str
172 A string of Python code.
172 A string of Python code.
173 silent : bool, optional (default False)
173 silent : bool, optional (default False)
174 If set, the kernel will execute the code as quietly possible.
174 If set, the kernel will execute the code as quietly possible.
175
175
176 Returns
176 Returns
177 -------
177 -------
178 The msg_id of the message sent.
178 The msg_id of the message sent.
179 """
179 """
180 # Create class for content/msg creation. Related to, but possibly
180 # Create class for content/msg creation. Related to, but possibly
181 # not in Session.
181 # not in Session.
182 content = dict(code=code, silent=silent)
182 content = dict(code=code, silent=silent)
183 msg = self.session.msg('execute_request', content)
183 msg = self.session.msg('execute_request', content)
184 self._queue_request(msg)
184 self._queue_request(msg)
185 return msg['header']['msg_id']
185 return msg['header']['msg_id']
186
186
187 def complete(self, text, line, cursor_pos, block=None):
187 def complete(self, text, line, cursor_pos, block=None):
188 """Tab complete text in the kernel's namespace.
188 """Tab complete text in the kernel's namespace.
189
189
190 Parameters
190 Parameters
191 ----------
191 ----------
192 text : str
192 text : str
193 The text to complete.
193 The text to complete.
194 line : str
194 line : str
195 The full line of text that is the surrounding context for the
195 The full line of text that is the surrounding context for the
196 text to complete.
196 text to complete.
197 cursor_pos : int
197 cursor_pos : int
198 The position of the cursor in the line where the completion was
198 The position of the cursor in the line where the completion was
199 requested.
199 requested.
200 block : str, optional
200 block : str, optional
201 The full block of code in which the completion is being requested.
201 The full block of code in which the completion is being requested.
202
202
203 Returns
203 Returns
204 -------
204 -------
205 The msg_id of the message sent.
205 The msg_id of the message sent.
206 """
206 """
207 content = dict(text=text, line=line, block=block, cursor_pos=cursor_pos)
207 content = dict(text=text, line=line, block=block, cursor_pos=cursor_pos)
208 msg = self.session.msg('complete_request', content)
208 msg = self.session.msg('complete_request', content)
209 self._queue_request(msg)
209 self._queue_request(msg)
210 return msg['header']['msg_id']
210 return msg['header']['msg_id']
211
211
212 def object_info(self, oname):
212 def object_info(self, oname):
213 """Get metadata information about an object.
213 """Get metadata information about an object.
214
214
215 Parameters
215 Parameters
216 ----------
216 ----------
217 oname : str
217 oname : str
218 A string specifying the object name.
218 A string specifying the object name.
219
219
220 Returns
220 Returns
221 -------
221 -------
222 The msg_id of the message sent.
222 The msg_id of the message sent.
223 """
223 """
224 content = dict(oname=oname)
224 content = dict(oname=oname)
225 msg = self.session.msg('object_info_request', content)
225 msg = self.session.msg('object_info_request', content)
226 self._queue_request(msg)
226 self._queue_request(msg)
227 return msg['header']['msg_id']
227 return msg['header']['msg_id']
228
228
229 def history(self, index=None, raw=False, output=True):
229 def history(self, index=None, raw=False, output=True):
230 """Get the history list.
230 """Get the history list.
231
231
232 Parameters
232 Parameters
233 ----------
233 ----------
234 index : n or (n1, n2) or None
234 index : n or (n1, n2) or None
235 If n, then the last entries. If a tuple, then all in
235 If n, then the last entries. If a tuple, then all in
236 range(n1, n2). If None, then all entries. Raises IndexError if
236 range(n1, n2). If None, then all entries. Raises IndexError if
237 the format of index is incorrect.
237 the format of index is incorrect.
238 raw : bool
238 raw : bool
239 If True, return the raw input.
239 If True, return the raw input.
240 output : bool
240 output : bool
241 If True, then return the output as well.
241 If True, then return the output as well.
242
242
243 Returns
243 Returns
244 -------
244 -------
245 The msg_id of the message sent.
245 The msg_id of the message sent.
246 """
246 """
247 content = dict(index=index, raw=raw, output=output)
247 content = dict(index=index, raw=raw, output=output)
248 msg = self.session.msg('history_request', content)
248 msg = self.session.msg('history_request', content)
249 self._queue_request(msg)
249 self._queue_request(msg)
250 return msg['header']['msg_id']
250 return msg['header']['msg_id']
251
251
252 def prompt(self):
252 def prompt(self):
253 """Requests a prompt number from the kernel.
253 """Requests a prompt number from the kernel.
254
254
255 Returns
255 Returns
256 -------
256 -------
257 The msg_id of the message sent.
257 The msg_id of the message sent.
258 """
258 """
259 msg = self.session.msg('prompt_request')
259 msg = self.session.msg('prompt_request')
260 self._queue_request(msg)
260 self._queue_request(msg)
261 return msg['header']['msg_id']
261 return msg['header']['msg_id']
262
262
263 def _handle_events(self, socket, events):
263 def _handle_events(self, socket, events):
264 if events & POLLERR:
264 if events & POLLERR:
265 self._handle_err()
265 self._handle_err()
266 if events & POLLOUT:
266 if events & POLLOUT:
267 self._handle_send()
267 self._handle_send()
268 if events & POLLIN:
268 if events & POLLIN:
269 self._handle_recv()
269 self._handle_recv()
270
270
271 def _handle_recv(self):
271 def _handle_recv(self):
272 msg = self.socket.recv_json()
272 msg = self.socket.recv_json()
273 self.call_handlers(msg)
273 self.call_handlers(msg)
274
274
275 def _handle_send(self):
275 def _handle_send(self):
276 try:
276 try:
277 msg = self.command_queue.get(False)
277 msg = self.command_queue.get(False)
278 except Empty:
278 except Empty:
279 pass
279 pass
280 else:
280 else:
281 self.socket.send_json(msg)
281 self.socket.send_json(msg)
282 if self.command_queue.empty():
282 if self.command_queue.empty():
283 self.drop_io_state(POLLOUT)
283 self.drop_io_state(POLLOUT)
284
284
285 def _handle_err(self):
285 def _handle_err(self):
286 # We don't want to let this go silently, so eventually we should log.
286 # We don't want to let this go silently, so eventually we should log.
287 raise zmq.ZMQError()
287 raise zmq.ZMQError()
288
288
289 def _queue_request(self, msg):
289 def _queue_request(self, msg):
290 self.command_queue.put(msg)
290 self.command_queue.put(msg)
291 self.add_io_state(POLLOUT)
291 self.add_io_state(POLLOUT)
292
292
293
293
294 class SubSocketChannel(ZmqSocketChannel):
294 class SubSocketChannel(ZmqSocketChannel):
295 """The SUB channel which listens for messages that the kernel publishes.
295 """The SUB channel which listens for messages that the kernel publishes.
296 """
296 """
297
297
298 def __init__(self, context, session, address):
298 def __init__(self, context, session, address):
299 super(SubSocketChannel, self).__init__(context, session, address)
299 super(SubSocketChannel, self).__init__(context, session, address)
300
300
301 def run(self):
301 def run(self):
302 """The thread's main activity. Call start() instead."""
302 """The thread's main activity. Call start() instead."""
303 self.socket = self.context.socket(zmq.SUB)
303 self.socket = self.context.socket(zmq.SUB)
304 self.socket.setsockopt(zmq.SUBSCRIBE,'')
304 self.socket.setsockopt(zmq.SUBSCRIBE,'')
305 self.socket.setsockopt(zmq.IDENTITY, self.session.session)
305 self.socket.setsockopt(zmq.IDENTITY, self.session.session)
306 self.socket.connect('tcp://%s:%i' % self.address)
306 self.socket.connect('tcp://%s:%i' % self.address)
307 self.ioloop = ioloop.IOLoop()
307 self.ioloop = ioloop.IOLoop()
308 self.iostate = POLLIN|POLLERR
308 self.iostate = POLLIN|POLLERR
309 self.ioloop.add_handler(self.socket, self._handle_events,
309 self.ioloop.add_handler(self.socket, self._handle_events,
310 self.iostate)
310 self.iostate)
311 self.ioloop.start()
311 self.ioloop.start()
312
312
313 def stop(self):
313 def stop(self):
314 self.ioloop.stop()
314 self.ioloop.stop()
315 super(SubSocketChannel, self).stop()
315 super(SubSocketChannel, self).stop()
316
316
317 def call_handlers(self, msg):
317 def call_handlers(self, msg):
318 """This method is called in the ioloop thread when a message arrives.
318 """This method is called in the ioloop thread when a message arrives.
319
319
320 Subclasses should override this method to handle incoming messages.
320 Subclasses should override this method to handle incoming messages.
321 It is important to remember that this method is called in the thread
321 It is important to remember that this method is called in the thread
322 so that some logic must be done to ensure that the application leve
322 so that some logic must be done to ensure that the application leve
323 handlers are called in the application thread.
323 handlers are called in the application thread.
324 """
324 """
325 raise NotImplementedError('call_handlers must be defined in a subclass.')
325 raise NotImplementedError('call_handlers must be defined in a subclass.')
326
326
327 def flush(self, timeout=1.0):
327 def flush(self, timeout=1.0):
328 """Immediately processes all pending messages on the SUB channel.
328 """Immediately processes all pending messages on the SUB channel.
329
329
330 Callers should use this method to ensure that :method:`call_handlers`
330 Callers should use this method to ensure that :method:`call_handlers`
331 has been called for all messages that have been received on the
331 has been called for all messages that have been received on the
332 0MQ SUB socket of this channel.
332 0MQ SUB socket of this channel.
333
333
334 This method is thread safe.
334 This method is thread safe.
335
335
336 Parameters
336 Parameters
337 ----------
337 ----------
338 timeout : float, optional
338 timeout : float, optional
339 The maximum amount of time to spend flushing, in seconds. The
339 The maximum amount of time to spend flushing, in seconds. The
340 default is one second.
340 default is one second.
341 """
341 """
342 # We do the IOLoop callback process twice to ensure that the IOLoop
342 # We do the IOLoop callback process twice to ensure that the IOLoop
343 # gets to perform at least one full poll.
343 # gets to perform at least one full poll.
344 stop_time = time.time() + timeout
344 stop_time = time.time() + timeout
345 for i in xrange(2):
345 for i in xrange(2):
346 self._flushed = False
346 self._flushed = False
347 self.ioloop.add_callback(self._flush)
347 self.ioloop.add_callback(self._flush)
348 while not self._flushed and time.time() < stop_time:
348 while not self._flushed and time.time() < stop_time:
349 time.sleep(0.01)
349 time.sleep(0.01)
350
350
351 def _handle_events(self, socket, events):
351 def _handle_events(self, socket, events):
352 # Turn on and off POLLOUT depending on if we have made a request
352 # Turn on and off POLLOUT depending on if we have made a request
353 if events & POLLERR:
353 if events & POLLERR:
354 self._handle_err()
354 self._handle_err()
355 if events & POLLIN:
355 if events & POLLIN:
356 self._handle_recv()
356 self._handle_recv()
357
357
358 def _handle_err(self):
358 def _handle_err(self):
359 # We don't want to let this go silently, so eventually we should log.
359 # We don't want to let this go silently, so eventually we should log.
360 raise zmq.ZMQError()
360 raise zmq.ZMQError()
361
361
362 def _handle_recv(self):
362 def _handle_recv(self):
363 # Get all of the messages we can
363 # Get all of the messages we can
364 while True:
364 while True:
365 try:
365 try:
366 msg = self.socket.recv_json(zmq.NOBLOCK)
366 msg = self.socket.recv_json(zmq.NOBLOCK)
367 except zmq.ZMQError:
367 except zmq.ZMQError:
368 # Check the errno?
368 # Check the errno?
369 # Will this trigger POLLERR?
369 # Will this trigger POLLERR?
370 break
370 break
371 else:
371 else:
372 self.call_handlers(msg)
372 self.call_handlers(msg)
373
373
374 def _flush(self):
374 def _flush(self):
375 """Callback for :method:`self.flush`."""
375 """Callback for :method:`self.flush`."""
376 self._flushed = True
376 self._flushed = True
377
377
378
378
379 class RepSocketChannel(ZmqSocketChannel):
379 class RepSocketChannel(ZmqSocketChannel):
380 """A reply channel to handle raw_input requests that the kernel makes."""
380 """A reply channel to handle raw_input requests that the kernel makes."""
381
381
382 msg_queue = None
382 msg_queue = None
383
383
384 def __init__(self, context, session, address):
384 def __init__(self, context, session, address):
385 self.msg_queue = Queue()
385 self.msg_queue = Queue()
386 super(RepSocketChannel, self).__init__(context, session, address)
386 super(RepSocketChannel, self).__init__(context, session, address)
387
387
388 def run(self):
388 def run(self):
389 """The thread's main activity. Call start() instead."""
389 """The thread's main activity. Call start() instead."""
390 self.socket = self.context.socket(zmq.XREQ)
390 self.socket = self.context.socket(zmq.XREQ)
391 self.socket.setsockopt(zmq.IDENTITY, self.session.session)
391 self.socket.setsockopt(zmq.IDENTITY, self.session.session)
392 self.socket.connect('tcp://%s:%i' % self.address)
392 self.socket.connect('tcp://%s:%i' % self.address)
393 self.ioloop = ioloop.IOLoop()
393 self.ioloop = ioloop.IOLoop()
394 self.iostate = POLLERR|POLLIN
394 self.iostate = POLLERR|POLLIN
395 self.ioloop.add_handler(self.socket, self._handle_events,
395 self.ioloop.add_handler(self.socket, self._handle_events,
396 self.iostate)
396 self.iostate)
397 self.ioloop.start()
397 self.ioloop.start()
398
398
399 def stop(self):
399 def stop(self):
400 self.ioloop.stop()
400 self.ioloop.stop()
401 super(RepSocketChannel, self).stop()
401 super(RepSocketChannel, self).stop()
402
402
403 def call_handlers(self, msg):
403 def call_handlers(self, msg):
404 """This method is called in the ioloop thread when a message arrives.
404 """This method is called in the ioloop thread when a message arrives.
405
405
406 Subclasses should override this method to handle incoming messages.
406 Subclasses should override this method to handle incoming messages.
407 It is important to remember that this method is called in the thread
407 It is important to remember that this method is called in the thread
408 so that some logic must be done to ensure that the application leve
408 so that some logic must be done to ensure that the application leve
409 handlers are called in the application thread.
409 handlers are called in the application thread.
410 """
410 """
411 raise NotImplementedError('call_handlers must be defined in a subclass.')
411 raise NotImplementedError('call_handlers must be defined in a subclass.')
412
412
413 def input(self, string):
413 def input(self, string):
414 """Send a string of raw input to the kernel."""
414 """Send a string of raw input to the kernel."""
415 content = dict(value=string)
415 content = dict(value=string)
416 msg = self.session.msg('input_reply', content)
416 msg = self.session.msg('input_reply', content)
417 self._queue_reply(msg)
417 self._queue_reply(msg)
418
418
419 def _handle_events(self, socket, events):
419 def _handle_events(self, socket, events):
420 if events & POLLERR:
420 if events & POLLERR:
421 self._handle_err()
421 self._handle_err()
422 if events & POLLOUT:
422 if events & POLLOUT:
423 self._handle_send()
423 self._handle_send()
424 if events & POLLIN:
424 if events & POLLIN:
425 self._handle_recv()
425 self._handle_recv()
426
426
427 def _handle_recv(self):
427 def _handle_recv(self):
428 msg = self.socket.recv_json()
428 msg = self.socket.recv_json()
429 self.call_handlers(msg)
429 self.call_handlers(msg)
430
430
431 def _handle_send(self):
431 def _handle_send(self):
432 try:
432 try:
433 msg = self.msg_queue.get(False)
433 msg = self.msg_queue.get(False)
434 except Empty:
434 except Empty:
435 pass
435 pass
436 else:
436 else:
437 self.socket.send_json(msg)
437 self.socket.send_json(msg)
438 if self.msg_queue.empty():
438 if self.msg_queue.empty():
439 self.drop_io_state(POLLOUT)
439 self.drop_io_state(POLLOUT)
440
440
441 def _handle_err(self):
441 def _handle_err(self):
442 # We don't want to let this go silently, so eventually we should log.
442 # We don't want to let this go silently, so eventually we should log.
443 raise zmq.ZMQError()
443 raise zmq.ZMQError()
444
444
445 def _queue_reply(self, msg):
445 def _queue_reply(self, msg):
446 self.msg_queue.put(msg)
446 self.msg_queue.put(msg)
447 self.add_io_state(POLLOUT)
447 self.add_io_state(POLLOUT)
448
448
449
449
450 class HBSocketChannel(ZmqSocketChannel):
451 """The heartbeat channel which monitors the kernel heartbeat.
452 """
453
454 time_to_dead = 5.0
455 socket = None
456 poller = None
457
458 def __init__(self, context, session, address):
459 super(HBSocketChannel, self).__init__(context, session, address)
460
461 def _create_socket(self):
462 self.socket = self.context.socket(zmq.REQ)
463 self.socket.setsockopt(zmq.IDENTITY, self.session.session)
464 self.socket.connect('tcp://%s:%i' % self.address)
465 self.poller = zmq.Poller()
466 self.poller.register(self.socket, zmq.POLLIN)
467
468 def run(self):
469 """The thread's main activity. Call start() instead."""
470 self._create_socket()
471
472 while True:
473 since_last_heartbeat = 0.0
474 request_time = time.time()
475 try:
476 self.socket.send_json('ping')
477 except zmq.ZMQError, e:
478 if e.errno == zmq.EFSM:
479 time.sleep(self.time_to_dead)
480 self._create_socket()
481 else:
482 raise
483 else:
484 while True:
485 try:
486 reply = self.socket.recv_json(zmq.NOBLOCK)
487 except zmq.ZMQError, e:
488 if e.errno == zmq.EAGAIN:
489 until_dead = self.time_to_dead-(time.time()-request_time)
490 self.poller.poll(until_dead)
491 since_last_heartbeat = time.time() - request_time
492 if since_last_heartbeat > self.time_to_dead:
493 self.call_handlers(since_last_heartbeat)
494 break
495 else:
496 # We should probably log this instead
497 raise
498 else:
499 until_dead = self.time_to_dead-(time.time()-request_time)
500 if until_dead > 0.0:
501 time.sleep(until_dead)
502 break
503
504 def call_handlers(self, since_last_heartbeat):
505 """This method is called in the ioloop thread when a message arrives.
506
507 Subclasses should override this method to handle incoming messages.
508 It is important to remember that this method is called in the thread
509 so that some logic must be done to ensure that the application leve
510 handlers are called in the application thread.
511 """
512 raise NotImplementedError('call_handlers must be defined in a subclass.')
513
514
450 #-----------------------------------------------------------------------------
515 #-----------------------------------------------------------------------------
451 # Main kernel manager class
516 # Main kernel manager class
452 #-----------------------------------------------------------------------------
517 #-----------------------------------------------------------------------------
453
518
454 class KernelManager(HasTraits):
519 class KernelManager(HasTraits):
455 """ Manages a kernel for a frontend.
520 """ Manages a kernel for a frontend.
456
521
457 The SUB channel is for the frontend to receive messages published by the
522 The SUB channel is for the frontend to receive messages published by the
458 kernel.
523 kernel.
459
524
460 The REQ channel is for the frontend to make requests of the kernel.
525 The REQ channel is for the frontend to make requests of the kernel.
461
526
462 The REP channel is for the kernel to request stdin (raw_input) from the
527 The REP channel is for the kernel to request stdin (raw_input) from the
463 frontend.
528 frontend.
464 """
529 """
465 # The PyZMQ Context to use for communication with the kernel.
530 # The PyZMQ Context to use for communication with the kernel.
466 context = Instance(zmq.Context,(),{})
531 context = Instance(zmq.Context,(),{})
467
532
468 # The Session to use for communication with the kernel.
533 # The Session to use for communication with the kernel.
469 session = Instance(Session,(),{})
534 session = Instance(Session,(),{})
470
535
471 # The kernel process with which the KernelManager is communicating.
536 # The kernel process with which the KernelManager is communicating.
472 kernel = Instance(Popen)
537 kernel = Instance(Popen)
473
538
474 # The addresses for the communication channels.
539 # The addresses for the communication channels.
475 xreq_address = TCPAddress((LOCALHOST, 0))
540 xreq_address = TCPAddress((LOCALHOST, 0))
476 sub_address = TCPAddress((LOCALHOST, 0))
541 sub_address = TCPAddress((LOCALHOST, 0))
477 rep_address = TCPAddress((LOCALHOST, 0))
542 rep_address = TCPAddress((LOCALHOST, 0))
543 hb_address = TCPAddress((LOCALHOST, 0))
478
544
479 # The classes to use for the various channels.
545 # The classes to use for the various channels.
480 xreq_channel_class = Type(XReqSocketChannel)
546 xreq_channel_class = Type(XReqSocketChannel)
481 sub_channel_class = Type(SubSocketChannel)
547 sub_channel_class = Type(SubSocketChannel)
482 rep_channel_class = Type(RepSocketChannel)
548 rep_channel_class = Type(RepSocketChannel)
549 hb_channel_class = Type(HBSocketChannel)
483
550
484 # Protected traits.
551 # Protected traits.
485 _launch_args = Any
552 _launch_args = Any
486 _xreq_channel = Any
553 _xreq_channel = Any
487 _sub_channel = Any
554 _sub_channel = Any
488 _rep_channel = Any
555 _rep_channel = Any
556 _hb_channel = Any
489
557
490 #--------------------------------------------------------------------------
558 #--------------------------------------------------------------------------
491 # Channel management methods:
559 # Channel management methods:
492 #--------------------------------------------------------------------------
560 #--------------------------------------------------------------------------
493
561
494 def start_channels(self):
562 def start_channels(self):
495 """Starts the channels for this kernel.
563 """Starts the channels for this kernel.
496
564
497 This will create the channels if they do not exist and then start
565 This will create the channels if they do not exist and then start
498 them. If port numbers of 0 are being used (random ports) then you
566 them. If port numbers of 0 are being used (random ports) then you
499 must first call :method:`start_kernel`. If the channels have been
567 must first call :method:`start_kernel`. If the channels have been
500 stopped and you call this, :class:`RuntimeError` will be raised.
568 stopped and you call this, :class:`RuntimeError` will be raised.
501 """
569 """
502 self.xreq_channel.start()
570 self.xreq_channel.start()
503 self.sub_channel.start()
571 self.sub_channel.start()
504 self.rep_channel.start()
572 self.rep_channel.start()
573 self.hb_channel.start()
505
574
506 def stop_channels(self):
575 def stop_channels(self):
507 """Stops the channels for this kernel.
576 """Stops the channels for this kernel.
508
577
509 This stops the channels by joining their threads. If the channels
578 This stops the channels by joining their threads. If the channels
510 were not started, :class:`RuntimeError` will be raised.
579 were not started, :class:`RuntimeError` will be raised.
511 """
580 """
512 self.xreq_channel.stop()
581 self.xreq_channel.stop()
513 self.sub_channel.stop()
582 self.sub_channel.stop()
514 self.rep_channel.stop()
583 self.rep_channel.stop()
584 self.hb_channel.stop()
515
585
516 @property
586 @property
517 def channels_running(self):
587 def channels_running(self):
518 """Are all of the channels created and running?"""
588 """Are all of the channels created and running?"""
519 return self.xreq_channel.is_alive() \
589 return self.xreq_channel.is_alive() \
520 and self.sub_channel.is_alive() \
590 and self.sub_channel.is_alive() \
521 and self.rep_channel.is_alive()
591 and self.rep_channel.is_alive() \
592 and self.hb_channel.is_alive()
522
593
523 #--------------------------------------------------------------------------
594 #--------------------------------------------------------------------------
524 # Kernel process management methods:
595 # Kernel process management methods:
525 #--------------------------------------------------------------------------
596 #--------------------------------------------------------------------------
526
597
527 def start_kernel(self, **kw):
598 def start_kernel(self, **kw):
528 """Starts a kernel process and configures the manager to use it.
599 """Starts a kernel process and configures the manager to use it.
529
600
530 If random ports (port=0) are being used, this method must be called
601 If random ports (port=0) are being used, this method must be called
531 before the channels are created.
602 before the channels are created.
532
603
533 Parameters:
604 Parameters:
534 -----------
605 -----------
535 ipython : bool, optional (default True)
606 ipython : bool, optional (default True)
536 Whether to use an IPython kernel instead of a plain Python kernel.
607 Whether to use an IPython kernel instead of a plain Python kernel.
537 """
608 """
538 xreq, sub, rep = self.xreq_address, self.sub_address, self.rep_address
609 xreq, sub, rep, hb = self.xreq_address, self.sub_address, \
539 if xreq[0] != LOCALHOST or sub[0] != LOCALHOST or rep[0] != LOCALHOST:
610 self.rep_address, self.hb_address
611 if xreq[0] != LOCALHOST or sub[0] != LOCALHOST or rep[0] != LOCALHOST or hb[0] != LOCALHOST:
540 raise RuntimeError("Can only launch a kernel on localhost."
612 raise RuntimeError("Can only launch a kernel on localhost."
541 "Make sure that the '*_address' attributes are "
613 "Make sure that the '*_address' attributes are "
542 "configured properly.")
614 "configured properly.")
543
615
544 self._launch_args = kw.copy()
616 self._launch_args = kw.copy()
545 if kw.pop('ipython', True):
617 if kw.pop('ipython', True):
546 from ipkernel import launch_kernel as launch
618 from ipkernel import launch_kernel as launch
547 else:
619 else:
548 from pykernel import launch_kernel as launch
620 from pykernel import launch_kernel as launch
549 self.kernel, xrep, pub, req = launch(xrep_port=xreq[1], pub_port=sub[1],
621 self.kernel, xrep, pub, req, hb = launch(
550 req_port=rep[1], **kw)
622 xrep_port=xreq[1], pub_port=sub[1], req_port=rep[1],
623 hb_port=hb[1], **kw)
551 self.xreq_address = (LOCALHOST, xrep)
624 self.xreq_address = (LOCALHOST, xrep)
552 self.sub_address = (LOCALHOST, pub)
625 self.sub_address = (LOCALHOST, pub)
553 self.rep_address = (LOCALHOST, req)
626 self.rep_address = (LOCALHOST, req)
627 self.hb_address = (LOCALHOST, hb)
554
628
555 def restart_kernel(self):
629 def restart_kernel(self):
556 """Restarts a kernel with the same arguments that were used to launch
630 """Restarts a kernel with the same arguments that were used to launch
557 it. If the old kernel was launched with random ports, the same ports
631 it. If the old kernel was launched with random ports, the same ports
558 will be used for the new kernel.
632 will be used for the new kernel.
559 """
633 """
560 if self._launch_args is None:
634 if self._launch_args is None:
561 raise RuntimeError("Cannot restart the kernel. "
635 raise RuntimeError("Cannot restart the kernel. "
562 "No previous call to 'start_kernel'.")
636 "No previous call to 'start_kernel'.")
563 else:
637 else:
564 if self.has_kernel:
638 if self.has_kernel:
565 self.kill_kernel()
639 self.kill_kernel()
566 self.start_kernel(*self._launch_args)
640 self.start_kernel(*self._launch_args)
567
641
568 @property
642 @property
569 def has_kernel(self):
643 def has_kernel(self):
570 """Returns whether a kernel process has been specified for the kernel
644 """Returns whether a kernel process has been specified for the kernel
571 manager.
645 manager.
572 """
646 """
573 return self.kernel is not None
647 return self.kernel is not None
574
648
575 def kill_kernel(self):
649 def kill_kernel(self):
576 """ Kill the running kernel. """
650 """ Kill the running kernel. """
577 if self.kernel is not None:
651 if self.kernel is not None:
578 self.kernel.kill()
652 self.kernel.kill()
579 self.kernel = None
653 self.kernel = None
580 else:
654 else:
581 raise RuntimeError("Cannot kill kernel. No kernel is running!")
655 raise RuntimeError("Cannot kill kernel. No kernel is running!")
582
656
583 def signal_kernel(self, signum):
657 def signal_kernel(self, signum):
584 """ Sends a signal to the kernel. """
658 """ Sends a signal to the kernel. """
585 if self.kernel is not None:
659 if self.kernel is not None:
586 self.kernel.send_signal(signum)
660 self.kernel.send_signal(signum)
587 else:
661 else:
588 raise RuntimeError("Cannot signal kernel. No kernel is running!")
662 raise RuntimeError("Cannot signal kernel. No kernel is running!")
589
663
590 @property
664 @property
591 def is_alive(self):
665 def is_alive(self):
592 """Is the kernel process still running?"""
666 """Is the kernel process still running?"""
593 if self.kernel is not None:
667 if self.kernel is not None:
594 if self.kernel.poll() is None:
668 if self.kernel.poll() is None:
595 return True
669 return True
596 else:
670 else:
597 return False
671 return False
598 else:
672 else:
599 # We didn't start the kernel with this KernelManager so we don't
673 # We didn't start the kernel with this KernelManager so we don't
600 # know if it is running. We should use a heartbeat for this case.
674 # know if it is running. We should use a heartbeat for this case.
601 return True
675 return True
602
676
603 #--------------------------------------------------------------------------
677 #--------------------------------------------------------------------------
604 # Channels used for communication with the kernel:
678 # Channels used for communication with the kernel:
605 #--------------------------------------------------------------------------
679 #--------------------------------------------------------------------------
606
680
607 @property
681 @property
608 def xreq_channel(self):
682 def xreq_channel(self):
609 """Get the REQ socket channel object to make requests of the kernel."""
683 """Get the REQ socket channel object to make requests of the kernel."""
610 if self._xreq_channel is None:
684 if self._xreq_channel is None:
611 self._xreq_channel = self.xreq_channel_class(self.context,
685 self._xreq_channel = self.xreq_channel_class(self.context,
612 self.session,
686 self.session,
613 self.xreq_address)
687 self.xreq_address)
614 return self._xreq_channel
688 return self._xreq_channel
615
689
616 @property
690 @property
617 def sub_channel(self):
691 def sub_channel(self):
618 """Get the SUB socket channel object."""
692 """Get the SUB socket channel object."""
619 if self._sub_channel is None:
693 if self._sub_channel is None:
620 self._sub_channel = self.sub_channel_class(self.context,
694 self._sub_channel = self.sub_channel_class(self.context,
621 self.session,
695 self.session,
622 self.sub_address)
696 self.sub_address)
623 return self._sub_channel
697 return self._sub_channel
624
698
625 @property
699 @property
626 def rep_channel(self):
700 def rep_channel(self):
627 """Get the REP socket channel object to handle stdin (raw_input)."""
701 """Get the REP socket channel object to handle stdin (raw_input)."""
628 if self._rep_channel is None:
702 if self._rep_channel is None:
629 self._rep_channel = self.rep_channel_class(self.context,
703 self._rep_channel = self.rep_channel_class(self.context,
630 self.session,
704 self.session,
631 self.rep_address)
705 self.rep_address)
632 return self._rep_channel
706 return self._rep_channel
707
708 @property
709 def hb_channel(self):
710 """Get the REP socket channel object to handle stdin (raw_input)."""
711 if self._hb_channel is None:
712 self._hb_channel = self.hb_channel_class(self.context,
713 self.session,
714 self.hb_address)
715 return self._hb_channel
General Comments 0
You need to be logged in to leave comments. Login now