##// END OF EJS Templates
Cleanup naming and organization of channels....
Brian Granger -
Show More
@@ -1,121 +1,121
1 1 """ Defines a convenient mix-in class for implementing Qt frontends.
2 2 """
3 3
4 4 class BaseFrontendMixin(object):
5 5 """ A mix-in class for implementing Qt frontends.
6 6
7 7 To handle messages of a particular type, frontends need only define an
8 8 appropriate handler method. For example, to handle 'stream' messaged, define
9 9 a '_handle_stream(msg)' method.
10 10 """
11 11
12 12 #---------------------------------------------------------------------------
13 13 # 'BaseFrontendMixin' concrete interface
14 14 #---------------------------------------------------------------------------
15 15
16 16 def _get_kernel_manager(self):
17 17 """ Returns the current kernel manager.
18 18 """
19 19 return self._kernel_manager
20 20
21 21 def _set_kernel_manager(self, kernel_manager):
22 22 """ Disconnect from the current kernel manager (if any) and set a new
23 23 kernel manager.
24 24 """
25 25 # Disconnect the old kernel manager, if necessary.
26 26 old_manager = self._kernel_manager
27 27 if old_manager is not None:
28 28 old_manager.started_kernel.disconnect(self._started_kernel)
29 29 old_manager.started_channels.disconnect(self._started_channels)
30 30 old_manager.stopped_channels.disconnect(self._stopped_channels)
31 31
32 32 # Disconnect the old kernel manager's channels.
33 old_manager.sub_channel.message_received.disconnect(self._dispatch)
33 old_manager.iopub_channel.message_received.disconnect(self._dispatch)
34 34 old_manager.shell_channel.message_received.disconnect(self._dispatch)
35 35 old_manager.stdin_channel.message_received.disconnect(self._dispatch)
36 36 old_manager.hb_channel.kernel_died.disconnect(
37 37 self._handle_kernel_died)
38 38
39 39 # Handle the case where the old kernel manager is still listening.
40 40 if old_manager.channels_running:
41 41 self._stopped_channels()
42 42
43 43 # Set the new kernel manager.
44 44 self._kernel_manager = kernel_manager
45 45 if kernel_manager is None:
46 46 return
47 47
48 48 # Connect the new kernel manager.
49 49 kernel_manager.started_kernel.connect(self._started_kernel)
50 50 kernel_manager.started_channels.connect(self._started_channels)
51 51 kernel_manager.stopped_channels.connect(self._stopped_channels)
52 52
53 53 # Connect the new kernel manager's channels.
54 kernel_manager.sub_channel.message_received.connect(self._dispatch)
54 kernel_manager.iopub_channel.message_received.connect(self._dispatch)
55 55 kernel_manager.shell_channel.message_received.connect(self._dispatch)
56 56 kernel_manager.stdin_channel.message_received.connect(self._dispatch)
57 57 kernel_manager.hb_channel.kernel_died.connect(self._handle_kernel_died)
58 58
59 59 # Handle the case where the kernel manager started channels before
60 60 # we connected.
61 61 if kernel_manager.channels_running:
62 62 self._started_channels()
63 63
64 64 kernel_manager = property(_get_kernel_manager, _set_kernel_manager)
65 65
66 66 #---------------------------------------------------------------------------
67 67 # 'BaseFrontendMixin' abstract interface
68 68 #---------------------------------------------------------------------------
69 69
70 70 def _handle_kernel_died(self, since_last_heartbeat):
71 71 """ This is called when the ``kernel_died`` signal is emitted.
72 72
73 73 This method is called when the kernel heartbeat has not been
74 74 active for a certain amount of time. The typical action will be to
75 75 give the user the option of restarting the kernel.
76 76
77 77 Parameters
78 78 ----------
79 79 since_last_heartbeat : float
80 80 The time since the heartbeat was last received.
81 81 """
82 82
83 83 def _started_kernel(self):
84 84 """Called when the KernelManager starts (or restarts) the kernel subprocess.
85 85 Channels may or may not be running at this point.
86 86 """
87 87
88 88 def _started_channels(self):
89 89 """ Called when the KernelManager channels have started listening or
90 90 when the frontend is assigned an already listening KernelManager.
91 91 """
92 92
93 93 def _stopped_channels(self):
94 94 """ Called when the KernelManager channels have stopped listening or
95 95 when a listening KernelManager is removed from the frontend.
96 96 """
97 97
98 98 #---------------------------------------------------------------------------
99 99 # 'BaseFrontendMixin' protected interface
100 100 #---------------------------------------------------------------------------
101 101
102 102 def _dispatch(self, msg):
103 103 """ Calls the frontend handler associated with the message type of the
104 104 given message.
105 105 """
106 106 msg_type = msg['header']['msg_type']
107 107 handler = getattr(self, '_handle_' + msg_type, None)
108 108 if handler:
109 109 handler(msg)
110 110
111 111 def _is_from_this_session(self, msg):
112 112 """ Returns whether a reply from the kernel originated from a request
113 113 from this frontend.
114 114 """
115 115 session = self._kernel_manager.session.session
116 116 parent = msg['parent_header']
117 117 if not parent:
118 118 # if the message has no parent, assume it is meant for all frontends
119 119 return True
120 120 else:
121 121 return parent.get('session') == session
@@ -1,260 +1,260
1 1 """ Defines a KernelManager that provides signals and slots.
2 2 """
3 3
4 4 # System library imports.
5 5 from IPython.external.qt import QtCore
6 6
7 7 # IPython imports.
8 8 from IPython.utils.traitlets import HasTraits, Type
9 9 from util import MetaQObjectHasTraits, SuperQObject
10 10
11 11
12 12 class ChannelQObject(SuperQObject):
13 13
14 14 # Emitted when the channel is started.
15 15 started = QtCore.Signal()
16 16
17 17 # Emitted when the channel is stopped.
18 18 stopped = QtCore.Signal()
19 19
20 20 #---------------------------------------------------------------------------
21 21 # Channel interface
22 22 #---------------------------------------------------------------------------
23 23
24 24 def start(self):
25 25 """ Reimplemented to emit signal.
26 26 """
27 27 super(ChannelQObject, self).start()
28 28 self.started.emit()
29 29
30 30 def stop(self):
31 31 """ Reimplemented to emit signal.
32 32 """
33 33 super(ChannelQObject, self).stop()
34 34 self.stopped.emit()
35 35
36 36 #---------------------------------------------------------------------------
37 37 # InProcessChannel interface
38 38 #---------------------------------------------------------------------------
39 39
40 40 def call_handlers_later(self, *args, **kwds):
41 41 """ Call the message handlers later.
42 42 """
43 43 do_later = lambda: self.call_handlers(*args, **kwds)
44 44 QtCore.QTimer.singleShot(0, do_later)
45 45
46 46 def process_events(self):
47 47 """ Process any pending GUI events.
48 48 """
49 49 QtCore.QCoreApplication.instance().processEvents()
50 50
51 51
52 52 class QtShellChannelMixin(ChannelQObject):
53 53
54 54 # Emitted when any message is received.
55 55 message_received = QtCore.Signal(object)
56 56
57 57 # Emitted when a reply has been received for the corresponding request
58 58 # type.
59 59 execute_reply = QtCore.Signal(object)
60 60 complete_reply = QtCore.Signal(object)
61 61 object_info_reply = QtCore.Signal(object)
62 62 history_reply = QtCore.Signal(object)
63 63
64 64 # Emitted when the first reply comes back.
65 65 first_reply = QtCore.Signal()
66 66
67 67 # Used by the first_reply signal logic to determine if a reply is the
68 68 # first.
69 69 _handlers_called = False
70 70
71 71 #---------------------------------------------------------------------------
72 # 'ShellSocketChannel' interface
72 # 'ShellChannel' interface
73 73 #---------------------------------------------------------------------------
74 74
75 75 def call_handlers(self, msg):
76 76 """ Reimplemented to emit signals instead of making callbacks.
77 77 """
78 78 # Emit the generic signal.
79 79 self.message_received.emit(msg)
80 80
81 81 # Emit signals for specialized message types.
82 82 msg_type = msg['header']['msg_type']
83 83 signal = getattr(self, msg_type, None)
84 84 if signal:
85 85 signal.emit(msg)
86 86
87 87 if not self._handlers_called:
88 88 self.first_reply.emit()
89 89 self._handlers_called = True
90 90
91 91 #---------------------------------------------------------------------------
92 92 # 'QtShellChannelMixin' interface
93 93 #---------------------------------------------------------------------------
94 94
95 95 def reset_first_reply(self):
96 96 """ Reset the first_reply signal to fire again on the next reply.
97 97 """
98 98 self._handlers_called = False
99 99
100 100
101 class QtSubChannelMixin(ChannelQObject):
101 class QtIOPubChannelMixin(ChannelQObject):
102 102
103 103 # Emitted when any message is received.
104 104 message_received = QtCore.Signal(object)
105 105
106 106 # Emitted when a message of type 'stream' is received.
107 107 stream_received = QtCore.Signal(object)
108 108
109 109 # Emitted when a message of type 'pyin' is received.
110 110 pyin_received = QtCore.Signal(object)
111 111
112 112 # Emitted when a message of type 'pyout' is received.
113 113 pyout_received = QtCore.Signal(object)
114 114
115 115 # Emitted when a message of type 'pyerr' is received.
116 116 pyerr_received = QtCore.Signal(object)
117 117
118 118 # Emitted when a message of type 'display_data' is received
119 119 display_data_received = QtCore.Signal(object)
120 120
121 121 # Emitted when a crash report message is received from the kernel's
122 122 # last-resort sys.excepthook.
123 123 crash_received = QtCore.Signal(object)
124 124
125 125 # Emitted when a shutdown is noticed.
126 126 shutdown_reply_received = QtCore.Signal(object)
127 127
128 128 #---------------------------------------------------------------------------
129 # 'SubSocketChannel' interface
129 # 'IOPubChannel' interface
130 130 #---------------------------------------------------------------------------
131 131
132 132 def call_handlers(self, msg):
133 133 """ Reimplemented to emit signals instead of making callbacks.
134 134 """
135 135 # Emit the generic signal.
136 136 self.message_received.emit(msg)
137 137 # Emit signals for specialized message types.
138 138 msg_type = msg['header']['msg_type']
139 139 signal = getattr(self, msg_type + '_received', None)
140 140 if signal:
141 141 signal.emit(msg)
142 142 elif msg_type in ('stdout', 'stderr'):
143 143 self.stream_received.emit(msg)
144 144
145 145 def flush(self):
146 146 """ Reimplemented to ensure that signals are dispatched immediately.
147 147 """
148 super(QtSubChannelMixin, self).flush()
148 super(QtIOPubChannelMixin, self).flush()
149 149 QtCore.QCoreApplication.instance().processEvents()
150 150
151 151
152 152 class QtStdInChannelMixin(ChannelQObject):
153 153
154 154 # Emitted when any message is received.
155 155 message_received = QtCore.Signal(object)
156 156
157 157 # Emitted when an input request is received.
158 158 input_requested = QtCore.Signal(object)
159 159
160 160 #---------------------------------------------------------------------------
161 # 'StdInSocketChannel' interface
161 # 'StdInChannel' interface
162 162 #---------------------------------------------------------------------------
163 163
164 164 def call_handlers(self, msg):
165 165 """ Reimplemented to emit signals instead of making callbacks.
166 166 """
167 167 # Emit the generic signal.
168 168 self.message_received.emit(msg)
169 169
170 170 # Emit signals for specialized message types.
171 171 msg_type = msg['header']['msg_type']
172 172 if msg_type == 'input_request':
173 173 self.input_requested.emit(msg)
174 174
175 175
176 176 class QtHBChannelMixin(ChannelQObject):
177 177
178 178 # Emitted when the kernel has died.
179 179 kernel_died = QtCore.Signal(object)
180 180
181 181 #---------------------------------------------------------------------------
182 # 'HBSocketChannel' interface
182 # 'HBChannel' interface
183 183 #---------------------------------------------------------------------------
184 184
185 185 def call_handlers(self, since_last_heartbeat):
186 186 """ Reimplemented to emit signals instead of making callbacks.
187 187 """
188 188 # Emit the generic signal.
189 189 self.kernel_died.emit(since_last_heartbeat)
190 190
191 191
192 192 class QtKernelManagerMixin(HasTraits, SuperQObject):
193 193 """ A KernelManager that provides signals and slots.
194 194 """
195 195
196 196 __metaclass__ = MetaQObjectHasTraits
197 197
198 198 # Emitted when the kernel manager has started listening.
199 199 started_kernel = QtCore.Signal()
200 200
201 201 # Emitted when the kernel manager has started listening.
202 202 started_channels = QtCore.Signal()
203 203
204 204 # Emitted when the kernel manager has stopped listening.
205 205 stopped_channels = QtCore.Signal()
206 206
207 207 # Use Qt-specific channel classes that emit signals.
208 sub_channel_class = Type(QtSubChannelMixin)
208 iopub_channel_class = Type(QtIOPubChannelMixin)
209 209 shell_channel_class = Type(QtShellChannelMixin)
210 210 stdin_channel_class = Type(QtStdInChannelMixin)
211 211 hb_channel_class = Type(QtHBChannelMixin)
212 212
213 213 #---------------------------------------------------------------------------
214 214 # 'KernelManager' interface
215 215 #---------------------------------------------------------------------------
216 216
217 217 #------ Kernel process management ------------------------------------------
218 218
219 219 def start_kernel(self, *args, **kw):
220 220 """ Reimplemented for proper heartbeat management.
221 221 """
222 222 if self._shell_channel is not None:
223 223 self._shell_channel.reset_first_reply()
224 224 super(QtKernelManagerMixin, self).start_kernel(*args, **kw)
225 225 self.started_kernel.emit()
226 226
227 227 #------ Channel management -------------------------------------------------
228 228
229 229 def start_channels(self, *args, **kw):
230 230 """ Reimplemented to emit signal.
231 231 """
232 232 super(QtKernelManagerMixin, self).start_channels(*args, **kw)
233 233 self.started_channels.emit()
234 234
235 235 def stop_channels(self):
236 236 """ Reimplemented to emit signal.
237 237 """
238 238 super(QtKernelManagerMixin, self).stop_channels()
239 239 self.stopped_channels.emit()
240 240
241 241 @property
242 242 def shell_channel(self):
243 243 """ Reimplemented for proper heartbeat management.
244 244 """
245 245 if self._shell_channel is None:
246 246 self._shell_channel = super(QtKernelManagerMixin,self).shell_channel
247 247 self._shell_channel.first_reply.connect(self._first_reply)
248 248 return self._shell_channel
249 249
250 250 #---------------------------------------------------------------------------
251 251 # Protected interface
252 252 #---------------------------------------------------------------------------
253 253
254 254 def _first_reply(self):
255 255 """ Unpauses the heartbeat channel when the first reply is received on
256 256 the execute channel. Note that this will *not* start the heartbeat
257 257 channel if it is not already running!
258 258 """
259 259 if self._hb_channel is not None:
260 260 self._hb_channel.unpause()
@@ -1,767 +1,767
1 1 from __future__ import print_function
2 2
3 3 # Standard library imports
4 4 from collections import namedtuple
5 5 import sys
6 6 import time
7 7 import uuid
8 8
9 9 # System library imports
10 10 from pygments.lexers import PythonLexer
11 11 from IPython.external import qt
12 12 from IPython.external.qt import QtCore, QtGui
13 13
14 14 # Local imports
15 15 from IPython.core.inputsplitter import InputSplitter, transform_classic_prompt
16 16 from IPython.core.oinspect import call_tip
17 17 from IPython.frontend.qt.base_frontend_mixin import BaseFrontendMixin
18 18 from IPython.utils.traitlets import Bool, Instance, Unicode
19 19 from bracket_matcher import BracketMatcher
20 20 from call_tip_widget import CallTipWidget
21 21 from completion_lexer import CompletionLexer
22 22 from history_console_widget import HistoryConsoleWidget
23 23 from pygments_highlighter import PygmentsHighlighter
24 24
25 25
26 26 class FrontendHighlighter(PygmentsHighlighter):
27 27 """ A PygmentsHighlighter that understands and ignores prompts.
28 28 """
29 29
30 30 def __init__(self, frontend):
31 31 super(FrontendHighlighter, self).__init__(frontend._control.document())
32 32 self._current_offset = 0
33 33 self._frontend = frontend
34 34 self.highlighting_on = False
35 35
36 36 def highlightBlock(self, string):
37 37 """ Highlight a block of text. Reimplemented to highlight selectively.
38 38 """
39 39 if not self.highlighting_on:
40 40 return
41 41
42 42 # The input to this function is a unicode string that may contain
43 43 # paragraph break characters, non-breaking spaces, etc. Here we acquire
44 44 # the string as plain text so we can compare it.
45 45 current_block = self.currentBlock()
46 46 string = self._frontend._get_block_plain_text(current_block)
47 47
48 48 # Decide whether to check for the regular or continuation prompt.
49 49 if current_block.contains(self._frontend._prompt_pos):
50 50 prompt = self._frontend._prompt
51 51 else:
52 52 prompt = self._frontend._continuation_prompt
53 53
54 54 # Only highlight if we can identify a prompt, but make sure not to
55 55 # highlight the prompt.
56 56 if string.startswith(prompt):
57 57 self._current_offset = len(prompt)
58 58 string = string[len(prompt):]
59 59 super(FrontendHighlighter, self).highlightBlock(string)
60 60
61 61 def rehighlightBlock(self, block):
62 62 """ Reimplemented to temporarily enable highlighting if disabled.
63 63 """
64 64 old = self.highlighting_on
65 65 self.highlighting_on = True
66 66 super(FrontendHighlighter, self).rehighlightBlock(block)
67 67 self.highlighting_on = old
68 68
69 69 def setFormat(self, start, count, format):
70 70 """ Reimplemented to highlight selectively.
71 71 """
72 72 start += self._current_offset
73 73 super(FrontendHighlighter, self).setFormat(start, count, format)
74 74
75 75
76 76 class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):
77 77 """ A Qt frontend for a generic Python kernel.
78 78 """
79 79
80 80 # The text to show when the kernel is (re)started.
81 81 banner = Unicode()
82 82
83 83 # An option and corresponding signal for overriding the default kernel
84 84 # interrupt behavior.
85 85 custom_interrupt = Bool(False)
86 86 custom_interrupt_requested = QtCore.Signal()
87 87
88 88 # An option and corresponding signals for overriding the default kernel
89 89 # restart behavior.
90 90 custom_restart = Bool(False)
91 91 custom_restart_kernel_died = QtCore.Signal(float)
92 92 custom_restart_requested = QtCore.Signal()
93 93
94 94 # Whether to automatically show calltips on open-parentheses.
95 95 enable_calltips = Bool(True, config=True,
96 96 help="Whether to draw information calltips on open-parentheses.")
97 97
98 98 clear_on_kernel_restart = Bool(True, config=True,
99 99 help="Whether to clear the console when the kernel is restarted")
100 100
101 101 confirm_restart = Bool(True, config=True,
102 102 help="Whether to ask for user confirmation when restarting kernel")
103 103
104 104 # Emitted when a user visible 'execute_request' has been submitted to the
105 105 # kernel from the FrontendWidget. Contains the code to be executed.
106 106 executing = QtCore.Signal(object)
107 107
108 108 # Emitted when a user-visible 'execute_reply' has been received from the
109 109 # kernel and processed by the FrontendWidget. Contains the response message.
110 110 executed = QtCore.Signal(object)
111 111
112 112 # Emitted when an exit request has been received from the kernel.
113 113 exit_requested = QtCore.Signal(object)
114 114
115 115 # Protected class variables.
116 116 _transform_prompt = staticmethod(transform_classic_prompt)
117 117 _CallTipRequest = namedtuple('_CallTipRequest', ['id', 'pos'])
118 118 _CompletionRequest = namedtuple('_CompletionRequest', ['id', 'pos'])
119 119 _ExecutionRequest = namedtuple('_ExecutionRequest', ['id', 'kind'])
120 120 _input_splitter_class = InputSplitter
121 121 _local_kernel = False
122 122 _highlighter = Instance(FrontendHighlighter)
123 123
124 124 #---------------------------------------------------------------------------
125 125 # 'object' interface
126 126 #---------------------------------------------------------------------------
127 127
128 128 def __init__(self, *args, **kw):
129 129 super(FrontendWidget, self).__init__(*args, **kw)
130 130 # FIXME: remove this when PySide min version is updated past 1.0.7
131 131 # forcefully disable calltips if PySide is < 1.0.7, because they crash
132 132 if qt.QT_API == qt.QT_API_PYSIDE:
133 133 import PySide
134 134 if PySide.__version_info__ < (1,0,7):
135 135 self.log.warn("PySide %s < 1.0.7 detected, disabling calltips" % PySide.__version__)
136 136 self.enable_calltips = False
137 137
138 138 # FrontendWidget protected variables.
139 139 self._bracket_matcher = BracketMatcher(self._control)
140 140 self._call_tip_widget = CallTipWidget(self._control)
141 141 self._completion_lexer = CompletionLexer(PythonLexer())
142 142 self._copy_raw_action = QtGui.QAction('Copy (Raw Text)', None)
143 143 self._hidden = False
144 144 self._highlighter = FrontendHighlighter(self)
145 145 self._input_splitter = self._input_splitter_class(input_mode='cell')
146 146 self._kernel_manager = None
147 147 self._request_info = {}
148 148 self._request_info['execute'] = {};
149 149 self._callback_dict = {}
150 150
151 151 # Configure the ConsoleWidget.
152 152 self.tab_width = 4
153 153 self._set_continuation_prompt('... ')
154 154
155 155 # Configure the CallTipWidget.
156 156 self._call_tip_widget.setFont(self.font)
157 157 self.font_changed.connect(self._call_tip_widget.setFont)
158 158
159 159 # Configure actions.
160 160 action = self._copy_raw_action
161 161 key = QtCore.Qt.CTRL | QtCore.Qt.SHIFT | QtCore.Qt.Key_C
162 162 action.setEnabled(False)
163 163 action.setShortcut(QtGui.QKeySequence(key))
164 164 action.setShortcutContext(QtCore.Qt.WidgetWithChildrenShortcut)
165 165 action.triggered.connect(self.copy_raw)
166 166 self.copy_available.connect(action.setEnabled)
167 167 self.addAction(action)
168 168
169 169 # Connect signal handlers.
170 170 document = self._control.document()
171 171 document.contentsChange.connect(self._document_contents_change)
172 172
173 173 # Set flag for whether we are connected via localhost.
174 174 self._local_kernel = kw.get('local_kernel',
175 175 FrontendWidget._local_kernel)
176 176
177 177 #---------------------------------------------------------------------------
178 178 # 'ConsoleWidget' public interface
179 179 #---------------------------------------------------------------------------
180 180
181 181 def copy(self):
182 182 """ Copy the currently selected text to the clipboard, removing prompts.
183 183 """
184 184 if self._page_control is not None and self._page_control.hasFocus():
185 185 self._page_control.copy()
186 186 elif self._control.hasFocus():
187 187 text = self._control.textCursor().selection().toPlainText()
188 188 if text:
189 189 lines = map(self._transform_prompt, text.splitlines())
190 190 text = '\n'.join(lines)
191 191 QtGui.QApplication.clipboard().setText(text)
192 192 else:
193 193 self.log.debug("frontend widget : unknown copy target")
194 194
195 195 #---------------------------------------------------------------------------
196 196 # 'ConsoleWidget' abstract interface
197 197 #---------------------------------------------------------------------------
198 198
199 199 def _is_complete(self, source, interactive):
200 200 """ Returns whether 'source' can be completely processed and a new
201 201 prompt created. When triggered by an Enter/Return key press,
202 202 'interactive' is True; otherwise, it is False.
203 203 """
204 204 complete = self._input_splitter.push(source)
205 205 if interactive:
206 206 complete = not self._input_splitter.push_accepts_more()
207 207 return complete
208 208
209 209 def _execute(self, source, hidden):
210 210 """ Execute 'source'. If 'hidden', do not show any output.
211 211
212 212 See parent class :meth:`execute` docstring for full details.
213 213 """
214 214 msg_id = self.kernel_manager.shell_channel.execute(source, hidden)
215 215 self._request_info['execute'][msg_id] = self._ExecutionRequest(msg_id, 'user')
216 216 self._hidden = hidden
217 217 if not hidden:
218 218 self.executing.emit(source)
219 219
220 220 def _prompt_started_hook(self):
221 221 """ Called immediately after a new prompt is displayed.
222 222 """
223 223 if not self._reading:
224 224 self._highlighter.highlighting_on = True
225 225
226 226 def _prompt_finished_hook(self):
227 227 """ Called immediately after a prompt is finished, i.e. when some input
228 228 will be processed and a new prompt displayed.
229 229 """
230 230 # Flush all state from the input splitter so the next round of
231 231 # reading input starts with a clean buffer.
232 232 self._input_splitter.reset()
233 233
234 234 if not self._reading:
235 235 self._highlighter.highlighting_on = False
236 236
237 237 def _tab_pressed(self):
238 238 """ Called when the tab key is pressed. Returns whether to continue
239 239 processing the event.
240 240 """
241 241 # Perform tab completion if:
242 242 # 1) The cursor is in the input buffer.
243 243 # 2) There is a non-whitespace character before the cursor.
244 244 text = self._get_input_buffer_cursor_line()
245 245 if text is None:
246 246 return False
247 247 complete = bool(text[:self._get_input_buffer_cursor_column()].strip())
248 248 if complete:
249 249 self._complete()
250 250 return not complete
251 251
252 252 #---------------------------------------------------------------------------
253 253 # 'ConsoleWidget' protected interface
254 254 #---------------------------------------------------------------------------
255 255
256 256 def _context_menu_make(self, pos):
257 257 """ Reimplemented to add an action for raw copy.
258 258 """
259 259 menu = super(FrontendWidget, self)._context_menu_make(pos)
260 260 for before_action in menu.actions():
261 261 if before_action.shortcut().matches(QtGui.QKeySequence.Paste) == \
262 262 QtGui.QKeySequence.ExactMatch:
263 263 menu.insertAction(before_action, self._copy_raw_action)
264 264 break
265 265 return menu
266 266
267 267 def request_interrupt_kernel(self):
268 268 if self._executing:
269 269 self.interrupt_kernel()
270 270
271 271 def request_restart_kernel(self):
272 272 message = 'Are you sure you want to restart the kernel?'
273 273 self.restart_kernel(message, now=False)
274 274
275 275 def _event_filter_console_keypress(self, event):
276 276 """ Reimplemented for execution interruption and smart backspace.
277 277 """
278 278 key = event.key()
279 279 if self._control_key_down(event.modifiers(), include_command=False):
280 280
281 281 if key == QtCore.Qt.Key_C and self._executing:
282 282 self.request_interrupt_kernel()
283 283 return True
284 284
285 285 elif key == QtCore.Qt.Key_Period:
286 286 self.request_restart_kernel()
287 287 return True
288 288
289 289 elif not event.modifiers() & QtCore.Qt.AltModifier:
290 290
291 291 # Smart backspace: remove four characters in one backspace if:
292 292 # 1) everything left of the cursor is whitespace
293 293 # 2) the four characters immediately left of the cursor are spaces
294 294 if key == QtCore.Qt.Key_Backspace:
295 295 col = self._get_input_buffer_cursor_column()
296 296 cursor = self._control.textCursor()
297 297 if col > 3 and not cursor.hasSelection():
298 298 text = self._get_input_buffer_cursor_line()[:col]
299 299 if text.endswith(' ') and not text.strip():
300 300 cursor.movePosition(QtGui.QTextCursor.Left,
301 301 QtGui.QTextCursor.KeepAnchor, 4)
302 302 cursor.removeSelectedText()
303 303 return True
304 304
305 305 return super(FrontendWidget, self)._event_filter_console_keypress(event)
306 306
307 307 def _insert_continuation_prompt(self, cursor):
308 308 """ Reimplemented for auto-indentation.
309 309 """
310 310 super(FrontendWidget, self)._insert_continuation_prompt(cursor)
311 311 cursor.insertText(' ' * self._input_splitter.indent_spaces)
312 312
313 313 #---------------------------------------------------------------------------
314 314 # 'BaseFrontendMixin' abstract interface
315 315 #---------------------------------------------------------------------------
316 316
317 317 def _handle_complete_reply(self, rep):
318 318 """ Handle replies for tab completion.
319 319 """
320 320 self.log.debug("complete: %s", rep.get('content', ''))
321 321 cursor = self._get_cursor()
322 322 info = self._request_info.get('complete')
323 323 if info and info.id == rep['parent_header']['msg_id'] and \
324 324 info.pos == cursor.position():
325 325 text = '.'.join(self._get_context())
326 326 cursor.movePosition(QtGui.QTextCursor.Left, n=len(text))
327 327 self._complete_with_items(cursor, rep['content']['matches'])
328 328
329 329 def _silent_exec_callback(self, expr, callback):
330 330 """Silently execute `expr` in the kernel and call `callback` with reply
331 331
332 332 the `expr` is evaluated silently in the kernel (without) output in
333 333 the frontend. Call `callback` with the
334 334 `repr <http://docs.python.org/library/functions.html#repr> `_ as first argument
335 335
336 336 Parameters
337 337 ----------
338 338 expr : string
339 339 valid string to be executed by the kernel.
340 340 callback : function
341 341 function accepting one argument, as a string. The string will be
342 342 the `repr` of the result of evaluating `expr`
343 343
344 344 The `callback` is called with the `repr()` of the result of `expr` as
345 345 first argument. To get the object, do `eval()` on the passed value.
346 346
347 347 See Also
348 348 --------
349 349 _handle_exec_callback : private method, deal with calling callback with reply
350 350
351 351 """
352 352
353 353 # generate uuid, which would be used as an indication of whether or
354 354 # not the unique request originated from here (can use msg id ?)
355 355 local_uuid = str(uuid.uuid1())
356 356 msg_id = self.kernel_manager.shell_channel.execute('',
357 357 silent=True, user_expressions={ local_uuid:expr })
358 358 self._callback_dict[local_uuid] = callback
359 359 self._request_info['execute'][msg_id] = self._ExecutionRequest(msg_id, 'silent_exec_callback')
360 360
361 361 def _handle_exec_callback(self, msg):
362 362 """Execute `callback` corresponding to `msg` reply, after ``_silent_exec_callback``
363 363
364 364 Parameters
365 365 ----------
366 366 msg : raw message send by the kernel containing an `user_expressions`
367 367 and having a 'silent_exec_callback' kind.
368 368
369 369 Notes
370 370 -----
371 371 This function will look for a `callback` associated with the
372 372 corresponding message id. Association has been made by
373 373 `_silent_exec_callback`. `callback` is then called with the `repr()`
374 374 of the value of corresponding `user_expressions` as argument.
375 375 `callback` is then removed from the known list so that any message
376 376 coming again with the same id won't trigger it.
377 377
378 378 """
379 379
380 380 user_exp = msg['content'].get('user_expressions')
381 381 if not user_exp:
382 382 return
383 383 for expression in user_exp:
384 384 if expression in self._callback_dict:
385 385 self._callback_dict.pop(expression)(user_exp[expression])
386 386
387 387 def _handle_execute_reply(self, msg):
388 388 """ Handles replies for code execution.
389 389 """
390 390 self.log.debug("execute: %s", msg.get('content', ''))
391 391 msg_id = msg['parent_header']['msg_id']
392 392 info = self._request_info['execute'].get(msg_id)
393 393 # unset reading flag, because if execute finished, raw_input can't
394 394 # still be pending.
395 395 self._reading = False
396 396 if info and info.kind == 'user' and not self._hidden:
397 397 # Make sure that all output from the SUB channel has been processed
398 398 # before writing a new prompt.
399 self.kernel_manager.sub_channel.flush()
399 self.kernel_manager.iopub_channel.flush()
400 400
401 401 # Reset the ANSI style information to prevent bad text in stdout
402 402 # from messing up our colors. We're not a true terminal so we're
403 403 # allowed to do this.
404 404 if self.ansi_codes:
405 405 self._ansi_processor.reset_sgr()
406 406
407 407 content = msg['content']
408 408 status = content['status']
409 409 if status == 'ok':
410 410 self._process_execute_ok(msg)
411 411 elif status == 'error':
412 412 self._process_execute_error(msg)
413 413 elif status == 'aborted':
414 414 self._process_execute_abort(msg)
415 415
416 416 self._show_interpreter_prompt_for_reply(msg)
417 417 self.executed.emit(msg)
418 418 self._request_info['execute'].pop(msg_id)
419 419 elif info and info.kind == 'silent_exec_callback' and not self._hidden:
420 420 self._handle_exec_callback(msg)
421 421 self._request_info['execute'].pop(msg_id)
422 422 else:
423 423 super(FrontendWidget, self)._handle_execute_reply(msg)
424 424
425 425 def _handle_input_request(self, msg):
426 426 """ Handle requests for raw_input.
427 427 """
428 428 self.log.debug("input: %s", msg.get('content', ''))
429 429 if self._hidden:
430 430 raise RuntimeError('Request for raw input during hidden execution.')
431 431
432 432 # Make sure that all output from the SUB channel has been processed
433 433 # before entering readline mode.
434 self.kernel_manager.sub_channel.flush()
434 self.kernel_manager.iopub_channel.flush()
435 435
436 436 def callback(line):
437 437 self.kernel_manager.stdin_channel.input(line)
438 438 if self._reading:
439 439 self.log.debug("Got second input request, assuming first was interrupted.")
440 440 self._reading = False
441 441 self._readline(msg['content']['prompt'], callback=callback)
442 442
443 443 def _handle_kernel_died(self, since_last_heartbeat):
444 444 """ Handle the kernel's death by asking if the user wants to restart.
445 445 """
446 446 self.log.debug("kernel died: %s", since_last_heartbeat)
447 447 if self.custom_restart:
448 448 self.custom_restart_kernel_died.emit(since_last_heartbeat)
449 449 else:
450 450 message = 'The kernel heartbeat has been inactive for %.2f ' \
451 451 'seconds. Do you want to restart the kernel? You may ' \
452 452 'first want to check the network connection.' % \
453 453 since_last_heartbeat
454 454 self.restart_kernel(message, now=True)
455 455
456 456 def _handle_object_info_reply(self, rep):
457 457 """ Handle replies for call tips.
458 458 """
459 459 self.log.debug("oinfo: %s", rep.get('content', ''))
460 460 cursor = self._get_cursor()
461 461 info = self._request_info.get('call_tip')
462 462 if info and info.id == rep['parent_header']['msg_id'] and \
463 463 info.pos == cursor.position():
464 464 # Get the information for a call tip. For now we format the call
465 465 # line as string, later we can pass False to format_call and
466 466 # syntax-highlight it ourselves for nicer formatting in the
467 467 # calltip.
468 468 content = rep['content']
469 469 # if this is from pykernel, 'docstring' will be the only key
470 470 if content.get('ismagic', False):
471 471 # Don't generate a call-tip for magics. Ideally, we should
472 472 # generate a tooltip, but not on ( like we do for actual
473 473 # callables.
474 474 call_info, doc = None, None
475 475 else:
476 476 call_info, doc = call_tip(content, format_call=True)
477 477 if call_info or doc:
478 478 self._call_tip_widget.show_call_info(call_info, doc)
479 479
480 480 def _handle_pyout(self, msg):
481 481 """ Handle display hook output.
482 482 """
483 483 self.log.debug("pyout: %s", msg.get('content', ''))
484 484 if not self._hidden and self._is_from_this_session(msg):
485 485 text = msg['content']['data']
486 486 self._append_plain_text(text + '\n', before_prompt=True)
487 487
488 488 def _handle_stream(self, msg):
489 489 """ Handle stdout, stderr, and stdin.
490 490 """
491 491 self.log.debug("stream: %s", msg.get('content', ''))
492 492 if not self._hidden and self._is_from_this_session(msg):
493 493 # Most consoles treat tabs as being 8 space characters. Convert tabs
494 494 # to spaces so that output looks as expected regardless of this
495 495 # widget's tab width.
496 496 text = msg['content']['data'].expandtabs(8)
497 497
498 498 self._append_plain_text(text, before_prompt=True)
499 499 self._control.moveCursor(QtGui.QTextCursor.End)
500 500
501 501 def _handle_shutdown_reply(self, msg):
502 502 """ Handle shutdown signal, only if from other console.
503 503 """
504 504 self.log.debug("shutdown: %s", msg.get('content', ''))
505 505 if not self._hidden and not self._is_from_this_session(msg):
506 506 if self._local_kernel:
507 507 if not msg['content']['restart']:
508 508 self.exit_requested.emit(self)
509 509 else:
510 510 # we just got notified of a restart!
511 511 time.sleep(0.25) # wait 1/4 sec to reset
512 512 # lest the request for a new prompt
513 513 # goes to the old kernel
514 514 self.reset()
515 515 else: # remote kernel, prompt on Kernel shutdown/reset
516 516 title = self.window().windowTitle()
517 517 if not msg['content']['restart']:
518 518 reply = QtGui.QMessageBox.question(self, title,
519 519 "Kernel has been shutdown permanently. "
520 520 "Close the Console?",
521 521 QtGui.QMessageBox.Yes,QtGui.QMessageBox.No)
522 522 if reply == QtGui.QMessageBox.Yes:
523 523 self.exit_requested.emit(self)
524 524 else:
525 525 # XXX: remove message box in favor of using the
526 526 # clear_on_kernel_restart setting?
527 527 reply = QtGui.QMessageBox.question(self, title,
528 528 "Kernel has been reset. Clear the Console?",
529 529 QtGui.QMessageBox.Yes,QtGui.QMessageBox.No)
530 530 if reply == QtGui.QMessageBox.Yes:
531 531 time.sleep(0.25) # wait 1/4 sec to reset
532 532 # lest the request for a new prompt
533 533 # goes to the old kernel
534 534 self.reset()
535 535
536 536 def _started_channels(self):
537 537 """ Called when the KernelManager channels have started listening or
538 538 when the frontend is assigned an already listening KernelManager.
539 539 """
540 540 self.reset(clear=True)
541 541
542 542 #---------------------------------------------------------------------------
543 543 # 'FrontendWidget' public interface
544 544 #---------------------------------------------------------------------------
545 545
546 546 def copy_raw(self):
547 547 """ Copy the currently selected text to the clipboard without attempting
548 548 to remove prompts or otherwise alter the text.
549 549 """
550 550 self._control.copy()
551 551
552 552 def execute_file(self, path, hidden=False):
553 553 """ Attempts to execute file with 'path'. If 'hidden', no output is
554 554 shown.
555 555 """
556 556 self.execute('execfile(%r)' % path, hidden=hidden)
557 557
558 558 def interrupt_kernel(self):
559 559 """ Attempts to interrupt the running kernel.
560 560
561 561 Also unsets _reading flag, to avoid runtime errors
562 562 if raw_input is called again.
563 563 """
564 564 if self.custom_interrupt:
565 565 self._reading = False
566 566 self.custom_interrupt_requested.emit()
567 567 elif self.kernel_manager.has_kernel:
568 568 self._reading = False
569 569 self.kernel_manager.interrupt_kernel()
570 570 else:
571 571 self._append_plain_text('Kernel process is either remote or '
572 572 'unspecified. Cannot interrupt.\n')
573 573
574 574 def reset(self, clear=False):
575 575 """ Resets the widget to its initial state if ``clear`` parameter or
576 576 ``clear_on_kernel_restart`` configuration setting is True, otherwise
577 577 prints a visual indication of the fact that the kernel restarted, but
578 578 does not clear the traces from previous usage of the kernel before it
579 579 was restarted. With ``clear=True``, it is similar to ``%clear``, but
580 580 also re-writes the banner and aborts execution if necessary.
581 581 """
582 582 if self._executing:
583 583 self._executing = False
584 584 self._request_info['execute'] = {}
585 585 self._reading = False
586 586 self._highlighter.highlighting_on = False
587 587
588 588 if self.clear_on_kernel_restart or clear:
589 589 self._control.clear()
590 590 self._append_plain_text(self.banner)
591 591 else:
592 592 self._append_plain_text("# restarting kernel...")
593 593 self._append_html("<hr><br>")
594 594 # XXX: Reprinting the full banner may be too much, but once #1680 is
595 595 # addressed, that will mitigate it.
596 596 #self._append_plain_text(self.banner)
597 597 # update output marker for stdout/stderr, so that startup
598 598 # messages appear after banner:
599 599 self._append_before_prompt_pos = self._get_cursor().position()
600 600 self._show_interpreter_prompt()
601 601
602 602 def restart_kernel(self, message, now=False):
603 603 """ Attempts to restart the running kernel.
604 604 """
605 605 # FIXME: now should be configurable via a checkbox in the dialog. Right
606 606 # now at least the heartbeat path sets it to True and the manual restart
607 607 # to False. But those should just be the pre-selected states of a
608 608 # checkbox that the user could override if so desired. But I don't know
609 609 # enough Qt to go implementing the checkbox now.
610 610
611 611 if self.custom_restart:
612 612 self.custom_restart_requested.emit()
613 613
614 614 elif self.kernel_manager.has_kernel:
615 615 # Pause the heart beat channel to prevent further warnings.
616 616 self.kernel_manager.hb_channel.pause()
617 617
618 618 # Prompt the user to restart the kernel. Un-pause the heartbeat if
619 619 # they decline. (If they accept, the heartbeat will be un-paused
620 620 # automatically when the kernel is restarted.)
621 621 if self.confirm_restart:
622 622 buttons = QtGui.QMessageBox.Yes | QtGui.QMessageBox.No
623 623 result = QtGui.QMessageBox.question(self, 'Restart kernel?',
624 624 message, buttons)
625 625 do_restart = result == QtGui.QMessageBox.Yes
626 626 else:
627 627 # confirm_restart is False, so we don't need to ask user
628 628 # anything, just do the restart
629 629 do_restart = True
630 630 if do_restart:
631 631 try:
632 632 self.kernel_manager.restart_kernel(now=now)
633 633 except RuntimeError:
634 634 self._append_plain_text('Kernel started externally. '
635 635 'Cannot restart.\n',
636 636 before_prompt=True
637 637 )
638 638 else:
639 639 self.reset()
640 640 else:
641 641 self.kernel_manager.hb_channel.unpause()
642 642
643 643 else:
644 644 self._append_plain_text('Kernel process is either remote or '
645 645 'unspecified. Cannot restart.\n',
646 646 before_prompt=True
647 647 )
648 648
649 649 #---------------------------------------------------------------------------
650 650 # 'FrontendWidget' protected interface
651 651 #---------------------------------------------------------------------------
652 652
653 653 def _call_tip(self):
654 654 """ Shows a call tip, if appropriate, at the current cursor location.
655 655 """
656 656 # Decide if it makes sense to show a call tip
657 657 if not self.enable_calltips:
658 658 return False
659 659 cursor = self._get_cursor()
660 660 cursor.movePosition(QtGui.QTextCursor.Left)
661 661 if cursor.document().characterAt(cursor.position()) != '(':
662 662 return False
663 663 context = self._get_context(cursor)
664 664 if not context:
665 665 return False
666 666
667 667 # Send the metadata request to the kernel
668 668 name = '.'.join(context)
669 669 msg_id = self.kernel_manager.shell_channel.object_info(name)
670 670 pos = self._get_cursor().position()
671 671 self._request_info['call_tip'] = self._CallTipRequest(msg_id, pos)
672 672 return True
673 673
674 674 def _complete(self):
675 675 """ Performs completion at the current cursor location.
676 676 """
677 677 context = self._get_context()
678 678 if context:
679 679 # Send the completion request to the kernel
680 680 msg_id = self.kernel_manager.shell_channel.complete(
681 681 '.'.join(context), # text
682 682 self._get_input_buffer_cursor_line(), # line
683 683 self._get_input_buffer_cursor_column(), # cursor_pos
684 684 self.input_buffer) # block
685 685 pos = self._get_cursor().position()
686 686 info = self._CompletionRequest(msg_id, pos)
687 687 self._request_info['complete'] = info
688 688
689 689 def _get_context(self, cursor=None):
690 690 """ Gets the context for the specified cursor (or the current cursor
691 691 if none is specified).
692 692 """
693 693 if cursor is None:
694 694 cursor = self._get_cursor()
695 695 cursor.movePosition(QtGui.QTextCursor.StartOfBlock,
696 696 QtGui.QTextCursor.KeepAnchor)
697 697 text = cursor.selection().toPlainText()
698 698 return self._completion_lexer.get_context(text)
699 699
700 700 def _process_execute_abort(self, msg):
701 701 """ Process a reply for an aborted execution request.
702 702 """
703 703 self._append_plain_text("ERROR: execution aborted\n")
704 704
705 705 def _process_execute_error(self, msg):
706 706 """ Process a reply for an execution request that resulted in an error.
707 707 """
708 708 content = msg['content']
709 709 # If a SystemExit is passed along, this means exit() was called - also
710 710 # all the ipython %exit magic syntax of '-k' to be used to keep
711 711 # the kernel running
712 712 if content['ename']=='SystemExit':
713 713 keepkernel = content['evalue']=='-k' or content['evalue']=='True'
714 714 self._keep_kernel_on_exit = keepkernel
715 715 self.exit_requested.emit(self)
716 716 else:
717 717 traceback = ''.join(content['traceback'])
718 718 self._append_plain_text(traceback)
719 719
720 720 def _process_execute_ok(self, msg):
721 721 """ Process a reply for a successful execution request.
722 722 """
723 723 payload = msg['content']['payload']
724 724 for item in payload:
725 725 if not self._process_execute_payload(item):
726 726 warning = 'Warning: received unknown payload of type %s'
727 727 print(warning % repr(item['source']))
728 728
729 729 def _process_execute_payload(self, item):
730 730 """ Process a single payload item from the list of payload items in an
731 731 execution reply. Returns whether the payload was handled.
732 732 """
733 733 # The basic FrontendWidget doesn't handle payloads, as they are a
734 734 # mechanism for going beyond the standard Python interpreter model.
735 735 return False
736 736
737 737 def _show_interpreter_prompt(self):
738 738 """ Shows a prompt for the interpreter.
739 739 """
740 740 self._show_prompt('>>> ')
741 741
742 742 def _show_interpreter_prompt_for_reply(self, msg):
743 743 """ Shows a prompt for the interpreter given an 'execute_reply' message.
744 744 """
745 745 self._show_interpreter_prompt()
746 746
747 747 #------ Signal handlers ----------------------------------------------------
748 748
749 749 def _document_contents_change(self, position, removed, added):
750 750 """ Called whenever the document's content changes. Display a call tip
751 751 if appropriate.
752 752 """
753 753 # Calculate where the cursor should be *after* the change:
754 754 position += added
755 755
756 756 document = self._control.document()
757 757 if position == self._get_cursor().position():
758 758 self._call_tip()
759 759
760 760 #------ Trait default initializers -----------------------------------------
761 761
762 762 def _banner_default(self):
763 763 """ Returns the standard Python banner.
764 764 """
765 765 banner = 'Python %s on %s\nType "help", "copyright", "credits" or ' \
766 766 '"license" for more information.'
767 767 return banner % (sys.version, sys.platform)
@@ -1,33 +1,33
1 1 """ Defines an in-process KernelManager with signals and slots.
2 2 """
3 3
4 4 # Local imports.
5 5 from IPython.inprocess.kernelmanager import \
6 ShellInProcessChannel, SubInProcessChannel, StdInInProcessChannel, \
7 HBInProcessChannel, InProcessKernelManager
6 InProcessShellChannel, InProcessIOPubChannel, InProcessStdInChannel, \
7 InProcessHBChannel, InProcessKernelManager
8 8 from IPython.utils.traitlets import Type
9 from base_kernelmanager import QtShellChannelMixin, QtSubChannelMixin, \
9 from base_kernelmanager import QtShellChannelMixin, QtIOPubChannelMixin, \
10 10 QtStdInChannelMixin, QtHBChannelMixin, QtKernelManagerMixin
11 11
12 12
13 class QtShellInProcessChannel(QtShellChannelMixin, ShellInProcessChannel):
13 class QtInProcessShellChannel(QtShellChannelMixin, InProcessShellChannel):
14 14 pass
15 15
16 class QtSubInProcessChannel(QtSubChannelMixin, SubInProcessChannel):
16 class QtInProcessIOPubChannel(QtIOPubChannelMixin, InProcessIOPubChannel):
17 17 pass
18 18
19 class QtStdInInProcessChannel(QtStdInChannelMixin, StdInInProcessChannel):
19 class QtInProcessStdInChannel(QtStdInChannelMixin, InProcessStdInChannel):
20 20 pass
21 21
22 class QtHBInProcessChannel(QtHBChannelMixin, HBInProcessChannel):
22 class QtInProcessHBChannel(QtHBChannelMixin, InProcessHBChannel):
23 23 pass
24 24
25 25
26 26 class QtInProcessKernelManager(QtKernelManagerMixin, InProcessKernelManager):
27 27 """ An in-process KernelManager with signals and slots.
28 28 """
29 29
30 sub_channel_class = Type(QtSubInProcessChannel)
31 shell_channel_class = Type(QtShellInProcessChannel)
32 stdin_channel_class = Type(QtStdInInProcessChannel)
33 hb_channel_class = Type(QtHBInProcessChannel)
30 iopub_channel_class = Type(QtInProcessIOPubChannel)
31 shell_channel_class = Type(QtInProcessShellChannel)
32 stdin_channel_class = Type(QtInProcessStdInChannel)
33 hb_channel_class = Type(QtInProcessHBChannel)
@@ -1,32 +1,32
1 1 """ Defines a KernelManager that provides signals and slots.
2 2 """
3 3
4 4 # Local imports.
5 5 from IPython.utils.traitlets import Type
6 from IPython.zmq.kernelmanager import ShellSocketChannel, SubSocketChannel, \
7 StdInSocketChannel, HBSocketChannel, KernelManager
8 from base_kernelmanager import QtShellChannelMixin, QtSubChannelMixin, \
6 from IPython.zmq.kernelmanager import ShellChannel, IOPubChannel, \
7 StdInChannel, HBChannel, KernelManager
8 from base_kernelmanager import QtShellChannelMixin, QtIOPubChannelMixin, \
9 9 QtStdInChannelMixin, QtHBChannelMixin, QtKernelManagerMixin
10 10
11 11
12 class QtShellSocketChannel(QtShellChannelMixin, ShellSocketChannel):
12 class QtShellChannel(QtShellChannelMixin, ShellChannel):
13 13 pass
14 14
15 class QtSubSocketChannel(QtSubChannelMixin, SubSocketChannel):
15 class QtIOPubChannel(QtIOPubChannelMixin, IOPubChannel):
16 16 pass
17 17
18 class QtStdInSocketChannel(QtStdInChannelMixin, StdInSocketChannel):
18 class QtStdInChannel(QtStdInChannelMixin, StdInChannel):
19 19 pass
20 20
21 class QtHBSocketChannel(QtHBChannelMixin, HBSocketChannel):
21 class QtHBChannel(QtHBChannelMixin, HBChannel):
22 22 pass
23 23
24 24
25 25 class QtKernelManager(QtKernelManagerMixin, KernelManager):
26 26 """ A KernelManager that provides signals and slots.
27 27 """
28 28
29 sub_channel_class = Type(QtSubSocketChannel)
30 shell_channel_class = Type(QtShellSocketChannel)
31 stdin_channel_class = Type(QtStdInSocketChannel)
32 hb_channel_class = Type(QtHBSocketChannel)
29 iopub_channel_class = Type(QtIOPubChannel)
30 shell_channel_class = Type(QtShellChannel)
31 stdin_channel_class = Type(QtStdInChannel)
32 hb_channel_class = Type(QtHBChannel)
@@ -1,466 +1,466
1 1 # -*- coding: utf-8 -*-
2 2 """Frontend of ipython working with python-zmq
3 3
4 4 Ipython's frontend, is a ipython interface that send request to kernel and proccess the kernel's outputs.
5 5
6 6 For more details, see the ipython-zmq design
7 7 """
8 8 #-----------------------------------------------------------------------------
9 9 # Copyright (C) 2011 The IPython Development Team
10 10 #
11 11 # Distributed under the terms of the BSD License. The full license is in
12 12 # the file COPYING, distributed as part of this software.
13 13 #-----------------------------------------------------------------------------
14 14
15 15 #-----------------------------------------------------------------------------
16 16 # Imports
17 17 #-----------------------------------------------------------------------------
18 18 from __future__ import print_function
19 19
20 20 import bdb
21 21 import signal
22 22 import os
23 23 import sys
24 24 import time
25 25 import subprocess
26 26 from io import BytesIO
27 27 import base64
28 28
29 29 from Queue import Empty
30 30
31 31 try:
32 32 from contextlib import nested
33 33 except:
34 34 from IPython.utils.nested_context import nested
35 35
36 36 from IPython.core.alias import AliasManager, AliasError
37 37 from IPython.core import page
38 38 from IPython.utils.warn import warn, error, fatal
39 39 from IPython.utils import io
40 40 from IPython.utils.traitlets import List, Enum, Any
41 41 from IPython.utils.tempdir import NamedFileInTemporaryDirectory
42 42
43 43 from IPython.frontend.terminal.interactiveshell import TerminalInteractiveShell
44 44 from IPython.frontend.terminal.console.completer import ZMQCompleter
45 45
46 46
47 47 class ZMQTerminalInteractiveShell(TerminalInteractiveShell):
48 48 """A subclass of TerminalInteractiveShell that uses the 0MQ kernel"""
49 49 _executing = False
50 50
51 51 image_handler = Enum(('PIL', 'stream', 'tempfile', 'callable'),
52 52 config=True, help=
53 53 """
54 54 Handler for image type output. This is useful, for example,
55 55 when connecting to the kernel in which pylab inline backend is
56 56 activated. There are four handlers defined. 'PIL': Use
57 57 Python Imaging Library to popup image; 'stream': Use an
58 58 external program to show the image. Image will be fed into
59 59 the STDIN of the program. You will need to configure
60 60 `stream_image_handler`; 'tempfile': Use an external program to
61 61 show the image. Image will be saved in a temporally file and
62 62 the program is called with the temporally file. You will need
63 63 to configure `tempfile_image_handler`; 'callable': You can set
64 64 any Python callable which is called with the image data. You
65 65 will need to configure `callable_image_handler`.
66 66 """
67 67 )
68 68
69 69 stream_image_handler = List(config=True, help=
70 70 """
71 71 Command to invoke an image viewer program when you are using
72 72 'stream' image handler. This option is a list of string where
73 73 the first element is the command itself and reminders are the
74 74 options for the command. Raw image data is given as STDIN to
75 75 the program.
76 76 """
77 77 )
78 78
79 79 tempfile_image_handler = List(config=True, help=
80 80 """
81 81 Command to invoke an image viewer program when you are using
82 82 'tempfile' image handler. This option is a list of string
83 83 where the first element is the command itself and reminders
84 84 are the options for the command. You can use {file} and
85 85 {format} in the string to represent the location of the
86 86 generated image file and image format.
87 87 """
88 88 )
89 89
90 90 callable_image_handler = Any(config=True, help=
91 91 """
92 92 Callable object called via 'callable' image handler with one
93 93 argument, `data`, which is `msg["content"]["data"]` where
94 94 `msg` is the message from iopub channel. For exmaple, you can
95 95 find base64 encoded PNG data as `data['image/png']`.
96 96 """
97 97 )
98 98
99 99 mime_preference = List(
100 100 default_value=['image/png', 'image/jpeg', 'image/svg+xml'],
101 101 config=True, allow_none=False, help=
102 102 """
103 103 Preferred object representation MIME type in order. First
104 104 matched MIME type will be used.
105 105 """
106 106 )
107 107
108 108 def __init__(self, *args, **kwargs):
109 109 self.km = kwargs.pop('kernel_manager')
110 110 self.session_id = self.km.session.session
111 111 super(ZMQTerminalInteractiveShell, self).__init__(*args, **kwargs)
112 112
113 113 def init_completer(self):
114 114 """Initialize the completion machinery.
115 115
116 116 This creates completion machinery that can be used by client code,
117 117 either interactively in-process (typically triggered by the readline
118 118 library), programatically (such as in test suites) or out-of-prcess
119 119 (typically over the network by remote frontends).
120 120 """
121 121 from IPython.core.completerlib import (module_completer,
122 122 magic_run_completer, cd_completer)
123 123
124 124 self.Completer = ZMQCompleter(self, self.km)
125 125
126 126
127 127 self.set_hook('complete_command', module_completer, str_key = 'import')
128 128 self.set_hook('complete_command', module_completer, str_key = 'from')
129 129 self.set_hook('complete_command', magic_run_completer, str_key = '%run')
130 130 self.set_hook('complete_command', cd_completer, str_key = '%cd')
131 131
132 132 # Only configure readline if we truly are using readline. IPython can
133 133 # do tab-completion over the network, in GUIs, etc, where readline
134 134 # itself may be absent
135 135 if self.has_readline:
136 136 self.set_readline_completer()
137 137
138 138 def run_cell(self, cell, store_history=True):
139 139 """Run a complete IPython cell.
140 140
141 141 Parameters
142 142 ----------
143 143 cell : str
144 144 The code (including IPython code such as %magic functions) to run.
145 145 store_history : bool
146 146 If True, the raw and translated cell will be stored in IPython's
147 147 history. For user code calling back into IPython's machinery, this
148 148 should be set to False.
149 149 """
150 150 if (not cell) or cell.isspace():
151 151 return
152 152
153 153 if cell.strip() == 'exit':
154 154 # explicitly handle 'exit' command
155 155 return self.ask_exit()
156 156
157 157 self._executing = True
158 158 # flush stale replies, which could have been ignored, due to missed heartbeats
159 159 while self.km.shell_channel.msg_ready():
160 160 self.km.shell_channel.get_msg()
161 161 # shell_channel.execute takes 'hidden', which is the inverse of store_hist
162 162 msg_id = self.km.shell_channel.execute(cell, not store_history)
163 163 while not self.km.shell_channel.msg_ready() and self.km.is_alive:
164 164 try:
165 165 self.handle_stdin_request(timeout=0.05)
166 166 except Empty:
167 167 # display intermediate print statements, etc.
168 168 self.handle_iopub()
169 169 pass
170 170 if self.km.shell_channel.msg_ready():
171 171 self.handle_execute_reply(msg_id)
172 172 self._executing = False
173 173
174 174 #-----------------
175 175 # message handlers
176 176 #-----------------
177 177
178 178 def handle_execute_reply(self, msg_id):
179 179 msg = self.km.shell_channel.get_msg()
180 180 if msg["parent_header"].get("msg_id", None) == msg_id:
181 181
182 182 self.handle_iopub()
183 183
184 184 content = msg["content"]
185 185 status = content['status']
186 186
187 187 if status == 'aborted':
188 188 self.write('Aborted\n')
189 189 return
190 190 elif status == 'ok':
191 191 # print execution payloads as well:
192 192 for item in content["payload"]:
193 193 text = item.get('text', None)
194 194 if text:
195 195 page.page(text)
196 196
197 197 elif status == 'error':
198 198 for frame in content["traceback"]:
199 199 print(frame, file=io.stderr)
200 200
201 201 self.execution_count = int(content["execution_count"] + 1)
202 202
203 203
204 204 def handle_iopub(self):
205 205 """ Method to procces subscribe channel's messages
206 206
207 207 This method reads a message and processes the content in different
208 208 outputs like stdout, stderr, pyout and status
209 209
210 210 Arguments:
211 211 sub_msg: message receive from kernel in the sub socket channel
212 212 capture by kernel manager.
213 213 """
214 while self.km.sub_channel.msg_ready():
215 sub_msg = self.km.sub_channel.get_msg()
214 while self.km.iopub_channel.msg_ready():
215 sub_msg = self.km.iopub_channel.get_msg()
216 216 msg_type = sub_msg['header']['msg_type']
217 217 parent = sub_msg["parent_header"]
218 218 if (not parent) or self.session_id == parent['session']:
219 219 if msg_type == 'status' :
220 220 if sub_msg["content"]["execution_state"] == "busy" :
221 221 pass
222 222
223 223 elif msg_type == 'stream' :
224 224 if sub_msg["content"]["name"] == "stdout":
225 225 print(sub_msg["content"]["data"], file=io.stdout, end="")
226 226 io.stdout.flush()
227 227 elif sub_msg["content"]["name"] == "stderr" :
228 228 print(sub_msg["content"]["data"], file=io.stderr, end="")
229 229 io.stderr.flush()
230 230
231 231 elif msg_type == 'pyout':
232 232 self.execution_count = int(sub_msg["content"]["execution_count"])
233 233 format_dict = sub_msg["content"]["data"]
234 234 self.handle_rich_data(format_dict)
235 235 # taken from DisplayHook.__call__:
236 236 hook = self.displayhook
237 237 hook.start_displayhook()
238 238 hook.write_output_prompt()
239 239 hook.write_format_data(format_dict)
240 240 hook.log_output(format_dict)
241 241 hook.finish_displayhook()
242 242
243 243 elif msg_type == 'display_data':
244 244 self.handle_rich_data(sub_msg["content"]["data"])
245 245
246 246 _imagemime = {
247 247 'image/png': 'png',
248 248 'image/jpeg': 'jpeg',
249 249 'image/svg+xml': 'svg',
250 250 }
251 251
252 252 def handle_rich_data(self, data):
253 253 for mime in self.mime_preference:
254 254 if mime in data and mime in self._imagemime:
255 255 self.handle_image(data, mime)
256 256 return
257 257
258 258 def handle_image(self, data, mime):
259 259 handler = getattr(
260 260 self, 'handle_image_{0}'.format(self.image_handler), None)
261 261 if handler:
262 262 handler(data, mime)
263 263
264 264 def handle_image_PIL(self, data, mime):
265 265 if mime not in ('image/png', 'image/jpeg'):
266 266 return
267 267 import PIL.Image
268 268 raw = base64.decodestring(data[mime].encode('ascii'))
269 269 img = PIL.Image.open(BytesIO(raw))
270 270 img.show()
271 271
272 272 def handle_image_stream(self, data, mime):
273 273 raw = base64.decodestring(data[mime].encode('ascii'))
274 274 imageformat = self._imagemime[mime]
275 275 fmt = dict(format=imageformat)
276 276 args = [s.format(**fmt) for s in self.stream_image_handler]
277 277 with open(os.devnull, 'w') as devnull:
278 278 proc = subprocess.Popen(
279 279 args, stdin=subprocess.PIPE,
280 280 stdout=devnull, stderr=devnull)
281 281 proc.communicate(raw)
282 282
283 283 def handle_image_tempfile(self, data, mime):
284 284 raw = base64.decodestring(data[mime].encode('ascii'))
285 285 imageformat = self._imagemime[mime]
286 286 filename = 'tmp.{0}'.format(imageformat)
287 287 with nested(NamedFileInTemporaryDirectory(filename),
288 288 open(os.devnull, 'w')) as (f, devnull):
289 289 f.write(raw)
290 290 f.flush()
291 291 fmt = dict(file=f.name, format=imageformat)
292 292 args = [s.format(**fmt) for s in self.tempfile_image_handler]
293 293 subprocess.call(args, stdout=devnull, stderr=devnull)
294 294
295 295 def handle_image_callable(self, data, mime):
296 296 self.callable_image_handler(data)
297 297
298 298 def handle_stdin_request(self, timeout=0.1):
299 299 """ Method to capture raw_input
300 300 """
301 301 msg_rep = self.km.stdin_channel.get_msg(timeout=timeout)
302 302 # in case any iopub came while we were waiting:
303 303 self.handle_iopub()
304 304 if self.session_id == msg_rep["parent_header"].get("session"):
305 305 # wrap SIGINT handler
306 306 real_handler = signal.getsignal(signal.SIGINT)
307 307 def double_int(sig,frame):
308 308 # call real handler (forwards sigint to kernel),
309 309 # then raise local interrupt, stopping local raw_input
310 310 real_handler(sig,frame)
311 311 raise KeyboardInterrupt
312 312 signal.signal(signal.SIGINT, double_int)
313 313
314 314 try:
315 315 raw_data = raw_input(msg_rep["content"]["prompt"])
316 316 except EOFError:
317 317 # turn EOFError into EOF character
318 318 raw_data = '\x04'
319 319 except KeyboardInterrupt:
320 320 sys.stdout.write('\n')
321 321 return
322 322 finally:
323 323 # restore SIGINT handler
324 324 signal.signal(signal.SIGINT, real_handler)
325 325
326 326 # only send stdin reply if there *was not* another request
327 327 # or execution finished while we were reading.
328 328 if not (self.km.stdin_channel.msg_ready() or self.km.shell_channel.msg_ready()):
329 329 self.km.stdin_channel.input(raw_data)
330 330
331 331 def mainloop(self, display_banner=False):
332 332 while True:
333 333 try:
334 334 self.interact(display_banner=display_banner)
335 335 #self.interact_with_readline()
336 336 # XXX for testing of a readline-decoupled repl loop, call
337 337 # interact_with_readline above
338 338 break
339 339 except KeyboardInterrupt:
340 340 # this should not be necessary, but KeyboardInterrupt
341 341 # handling seems rather unpredictable...
342 342 self.write("\nKeyboardInterrupt in interact()\n")
343 343
344 344 def wait_for_kernel(self, timeout=None):
345 345 """method to wait for a kernel to be ready"""
346 346 tic = time.time()
347 347 self.km.hb_channel.unpause()
348 348 while True:
349 349 self.run_cell('1', False)
350 350 if self.km.hb_channel.is_beating():
351 351 # heart failure was not the reason this returned
352 352 break
353 353 else:
354 354 # heart failed
355 355 if timeout is not None and (time.time() - tic) > timeout:
356 356 return False
357 357 return True
358 358
359 359 def interact(self, display_banner=None):
360 360 """Closely emulate the interactive Python console."""
361 361
362 362 # batch run -> do not interact
363 363 if self.exit_now:
364 364 return
365 365
366 366 if display_banner is None:
367 367 display_banner = self.display_banner
368 368
369 369 if isinstance(display_banner, basestring):
370 370 self.show_banner(display_banner)
371 371 elif display_banner:
372 372 self.show_banner()
373 373
374 374 more = False
375 375
376 376 # run a non-empty no-op, so that we don't get a prompt until
377 377 # we know the kernel is ready. This keeps the connection
378 378 # message above the first prompt.
379 379 if not self.wait_for_kernel(3):
380 380 error("Kernel did not respond\n")
381 381 return
382 382
383 383 if self.has_readline:
384 384 self.readline_startup_hook(self.pre_readline)
385 385 hlen_b4_cell = self.readline.get_current_history_length()
386 386 else:
387 387 hlen_b4_cell = 0
388 388 # exit_now is set by a call to %Exit or %Quit, through the
389 389 # ask_exit callback.
390 390
391 391 while not self.exit_now:
392 392 if not self.km.is_alive:
393 393 # kernel died, prompt for action or exit
394 394 action = "restart" if self.km.has_kernel else "wait for restart"
395 395 ans = self.ask_yes_no("kernel died, %s ([y]/n)?" % action, default='y')
396 396 if ans:
397 397 if self.km.has_kernel:
398 398 self.km.restart_kernel(True)
399 399 self.wait_for_kernel(3)
400 400 else:
401 401 self.exit_now = True
402 402 continue
403 403 try:
404 404 # protect prompt block from KeyboardInterrupt
405 405 # when sitting on ctrl-C
406 406 self.hooks.pre_prompt_hook()
407 407 if more:
408 408 try:
409 409 prompt = self.prompt_manager.render('in2')
410 410 except Exception:
411 411 self.showtraceback()
412 412 if self.autoindent:
413 413 self.rl_do_indent = True
414 414
415 415 else:
416 416 try:
417 417 prompt = self.separate_in + self.prompt_manager.render('in')
418 418 except Exception:
419 419 self.showtraceback()
420 420
421 421 line = self.raw_input(prompt)
422 422 if self.exit_now:
423 423 # quick exit on sys.std[in|out] close
424 424 break
425 425 if self.autoindent:
426 426 self.rl_do_indent = False
427 427
428 428 except KeyboardInterrupt:
429 429 #double-guard against keyboardinterrupts during kbdint handling
430 430 try:
431 431 self.write('\nKeyboardInterrupt\n')
432 432 source_raw = self.input_splitter.source_raw_reset()[1]
433 433 hlen_b4_cell = self._replace_rlhist_multiline(source_raw, hlen_b4_cell)
434 434 more = False
435 435 except KeyboardInterrupt:
436 436 pass
437 437 except EOFError:
438 438 if self.autoindent:
439 439 self.rl_do_indent = False
440 440 if self.has_readline:
441 441 self.readline_startup_hook(None)
442 442 self.write('\n')
443 443 self.exit()
444 444 except bdb.BdbQuit:
445 445 warn('The Python debugger has exited with a BdbQuit exception.\n'
446 446 'Because of how pdb handles the stack, it is impossible\n'
447 447 'for IPython to properly format this particular exception.\n'
448 448 'IPython will resume normal operation.')
449 449 except:
450 450 # exceptions here are VERY RARE, but they can be triggered
451 451 # asynchronously by signal handlers, for example.
452 452 self.showtraceback()
453 453 else:
454 454 self.input_splitter.push(line)
455 455 more = self.input_splitter.push_accepts_more()
456 456 if (self.SyntaxTB.last_syntax_error and
457 457 self.autoedit_syntax):
458 458 self.edit_syntax_error()
459 459 if not more:
460 460 source_raw = self.input_splitter.source_raw_reset()[1]
461 461 hlen_b4_cell = self._replace_rlhist_multiline(source_raw, hlen_b4_cell)
462 462 self.run_cell(source_raw)
463 463
464 464
465 465 # Turn off the exit flag, so the mainloop can be restarted if desired
466 466 self.exit_now = False
@@ -1,87 +1,58
1 1 """ Implements a fully blocking kernel manager.
2 2
3 3 Useful for test suites and blocking terminal interfaces.
4 4 """
5 5 #-----------------------------------------------------------------------------
6 6 # Copyright (C) 2012 The IPython Development Team
7 7 #
8 8 # Distributed under the terms of the BSD License. The full license is in
9 9 # the file COPYING.txt, distributed as part of this software.
10 10 #-----------------------------------------------------------------------------
11 11
12 12 #-----------------------------------------------------------------------------
13 13 # Imports
14 14 #-----------------------------------------------------------------------------
15 15 from __future__ import print_function
16 16
17 17 # Standard library imports.
18 18 import Queue
19 19 from threading import Event
20 20
21 21 # Local imports.
22 22 from IPython.utils.io import raw_print
23 23 from IPython.utils.traitlets import Type
24 from kernelmanager import InProcessKernelManager, ShellInProcessChannel, \
25 SubInProcessChannel, StdInInProcessChannel
24 from kernelmanager import InProcessKernelManager, InProcessShellChannel, \
25 InProcessIOPubChannel, InProcessStdInChannel
26 from IPython.zmq.blockingkernelmanager import BlockingChannelMixin
26 27
27 #-----------------------------------------------------------------------------
28 # Utility classes
29 #-----------------------------------------------------------------------------
30
31 class BlockingChannelMixin(object):
32
33 def __init__(self, *args, **kwds):
34 super(BlockingChannelMixin, self).__init__(*args, **kwds)
35 self._in_queue = Queue.Queue()
36
37 def call_handlers(self, msg):
38 self._in_queue.put(msg)
39
40 def get_msg(self, block=True, timeout=None):
41 """ Gets a message if there is one that is ready. """
42 return self._in_queue.get(block, timeout)
43
44 def get_msgs(self):
45 """ Get all messages that are currently ready. """
46 msgs = []
47 while True:
48 try:
49 msgs.append(self.get_msg(block=False))
50 except Queue.Empty:
51 break
52 return msgs
53
54 def msg_ready(self):
55 """ Is there a message that has been received? """
56 return not self._in_queue.empty()
57 28
58 29 #-----------------------------------------------------------------------------
59 30 # Blocking kernel manager
60 31 #-----------------------------------------------------------------------------
61 32
62 class BlockingShellInProcessChannel(BlockingChannelMixin, ShellInProcessChannel):
33 class BlockingInProcessShellChannel(BlockingChannelMixin, InProcessShellChannel):
63 34 pass
64 35
65 class BlockingSubInProcessChannel(BlockingChannelMixin, SubInProcessChannel):
36 class BlockingInProcessIOPubChannel(BlockingChannelMixin, InProcessIOPubChannel):
66 37 pass
67 38
68 class BlockingStdInInProcessChannel(BlockingChannelMixin, StdInInProcessChannel):
39 class BlockingInProcessStdInChannel(BlockingChannelMixin, InProcessStdInChannel):
69 40
70 41 def call_handlers(self, msg):
71 42 """ Overridden for the in-process channel.
72 43
73 44 This methods simply calls raw_input directly.
74 45 """
75 46 msg_type = msg['header']['msg_type']
76 47 if msg_type == 'input_request':
77 48 _raw_input = self.manager.kernel._sys_raw_input
78 49 prompt = msg['content']['prompt']
79 50 raw_print(prompt, end='')
80 51 self.input(_raw_input())
81 52
82 53 class BlockingInProcessKernelManager(InProcessKernelManager):
83 54
84 55 # The classes to use for the various channels.
85 shell_channel_class = Type(BlockingShellInProcessChannel)
86 sub_channel_class = Type(BlockingSubInProcessChannel)
87 stdin_channel_class = Type(BlockingStdInInProcessChannel)
56 shell_channel_class = Type(BlockingInProcessShellChannel)
57 iopub_channel_class = Type(BlockingInProcessIOPubChannel)
58 stdin_channel_class = Type(BlockingInProcessStdInChannel)
@@ -1,176 +1,176
1 1 """ An in-process kernel. """
2 2
3 3 #-----------------------------------------------------------------------------
4 4 # Copyright (C) 2012 The IPython Development Team
5 5 #
6 6 # Distributed under the terms of the BSD License. The full license is in
7 7 # the file COPYING, distributed as part of this software.
8 8 #-----------------------------------------------------------------------------
9 9
10 10 #-----------------------------------------------------------------------------
11 11 # Imports
12 12 #-----------------------------------------------------------------------------
13 13
14 14 # Standard library imports
15 15 from contextlib import contextmanager
16 16 import logging
17 17 import sys
18 18
19 19 # Local imports.
20 20 from IPython.core.interactiveshell import InteractiveShellABC
21 21 from IPython.inprocess.socket import DummySocket
22 22 from IPython.utils.jsonutil import json_clean
23 23 from IPython.utils.traitlets import Any, Enum, Instance, List, Type
24 24 from IPython.zmq.ipkernel import Kernel
25 25 from IPython.zmq.zmqshell import ZMQInteractiveShell
26 26
27 27 #-----------------------------------------------------------------------------
28 28 # Main kernel class
29 29 #-----------------------------------------------------------------------------
30 30
31 31 class InProcessKernel(Kernel):
32 32
33 33 #-------------------------------------------------------------------------
34 34 # InProcessKernel interface
35 35 #-------------------------------------------------------------------------
36 36
37 37 # The frontends connected to this kernel.
38 38 frontends = List(
39 39 Instance('IPython.inprocess.kernelmanager.InProcessKernelManager'))
40 40
41 41 # The GUI environment that the kernel is running under. This need not be
42 42 # specified for the normal operation for the kernel, but is required for
43 43 # IPython's GUI support (including pylab). The default is 'inline' because
44 44 # it is safe under all GUI toolkits.
45 45 gui = Enum(('tk', 'gtk', 'wx', 'qt', 'qt4', 'inline'),
46 46 default_value='inline')
47 47
48 48 raw_input_str = Any()
49 49 stdout = Any()
50 50 stderr = Any()
51 51
52 52 #-------------------------------------------------------------------------
53 53 # Kernel interface
54 54 #-------------------------------------------------------------------------
55 55
56 56 shell_class = Type()
57 57 shell_streams = List()
58 58 control_stream = Any()
59 59 iopub_socket = Instance(DummySocket, ())
60 60 stdin_socket = Instance(DummySocket, ())
61 61
62 62 def __init__(self, **traits):
63 63 # When an InteractiveShell is instantiated by our base class, it binds
64 64 # the current values of sys.stdout and sys.stderr.
65 65 with self._redirected_io():
66 66 super(InProcessKernel, self).__init__(**traits)
67 67
68 68 self.iopub_socket.on_trait_change(self._io_dispatch, 'message_sent')
69 69 self.shell.kernel = self
70 70
71 71 def execute_request(self, stream, ident, parent):
72 72 """ Override for temporary IO redirection. """
73 73 with self._redirected_io():
74 74 super(InProcessKernel, self).execute_request(stream, ident, parent)
75 75
76 76 def start(self):
77 77 """ Override registration of dispatchers for streams. """
78 78 self.shell.exit_now = False
79 79
80 80 def _abort_queue(self, stream):
81 81 """ The in-process kernel doesn't abort requests. """
82 82 pass
83 83
84 84 def _raw_input(self, prompt, ident, parent):
85 85 # Flush output before making the request.
86 86 self.raw_input_str = None
87 87 sys.stderr.flush()
88 88 sys.stdout.flush()
89 89
90 90 # Send the input request.
91 91 content = json_clean(dict(prompt=prompt))
92 92 msg = self.session.msg(u'input_request', content, parent)
93 93 for frontend in self.frontends:
94 94 if frontend.session.session == parent['header']['session']:
95 95 frontend.stdin_channel.call_handlers(msg)
96 96 break
97 97 else:
98 98 logging.error('No frontend found for raw_input request')
99 99 return str()
100 100
101 101 # Await a response.
102 102 while self.raw_input_str is None:
103 103 frontend.stdin_channel.process_events()
104 104 return self.raw_input_str
105 105
106 106 #-------------------------------------------------------------------------
107 107 # Protected interface
108 108 #-------------------------------------------------------------------------
109 109
110 110 @contextmanager
111 111 def _redirected_io(self):
112 112 """ Temporarily redirect IO to the kernel.
113 113 """
114 114 sys_stdout, sys_stderr = sys.stdout, sys.stderr
115 115 sys.stdout, sys.stderr = self.stdout, self.stderr
116 116 yield
117 117 sys.stdout, sys.stderr = sys_stdout, sys_stderr
118 118
119 119 #------ Trait change handlers --------------------------------------------
120 120
121 121 def _io_dispatch(self):
122 122 """ Called when a message is sent to the IO socket.
123 123 """
124 124 ident, msg = self.session.recv(self.iopub_socket, copy=False)
125 125 for frontend in self.frontends:
126 frontend.sub_channel.call_handlers(msg)
126 frontend.iopub_channel.call_handlers(msg)
127 127
128 128 #------ Trait initializers -----------------------------------------------
129 129
130 130 def _log_default(self):
131 131 return logging.getLogger(__name__)
132 132
133 133 def _session_default(self):
134 134 from IPython.zmq.session import Session
135 135 return Session(config=self.config)
136 136
137 137 def _shell_class_default(self):
138 138 return InProcessInteractiveShell
139 139
140 140 def _stdout_default(self):
141 141 from IPython.zmq.iostream import OutStream
142 142 return OutStream(self.session, self.iopub_socket, u'stdout')
143 143
144 144 def _stderr_default(self):
145 145 from IPython.zmq.iostream import OutStream
146 146 return OutStream(self.session, self.iopub_socket, u'stderr')
147 147
148 148 #-----------------------------------------------------------------------------
149 149 # Interactive shell subclass
150 150 #-----------------------------------------------------------------------------
151 151
152 152 class InProcessInteractiveShell(ZMQInteractiveShell):
153 153
154 154 kernel = Instance('IPython.inprocess.ipkernel.InProcessKernel')
155 155
156 156 #-------------------------------------------------------------------------
157 157 # InteractiveShell interface
158 158 #-------------------------------------------------------------------------
159 159
160 160 def enable_gui(self, gui=None):
161 161 """ Enable GUI integration for the kernel.
162 162 """
163 163 from IPython.zmq.eventloops import enable_gui
164 164 if not gui:
165 165 gui = self.kernel.gui
166 166 enable_gui(gui, kernel=self.kernel)
167 167
168 168 def enable_pylab(self, gui=None, import_all=True, welcome_message=False):
169 169 """ Activate pylab support at runtime.
170 170 """
171 171 if not gui:
172 172 gui = self.kernel.gui
173 173 super(InProcessInteractiveShell, self).enable_pylab(gui, import_all,
174 174 welcome_message)
175 175
176 176 InteractiveShellABC.register(InProcessInteractiveShell)
@@ -1,443 +1,443
1 1 """ A kernel manager for in-process kernels. """
2 2
3 3 #-----------------------------------------------------------------------------
4 4 # Copyright (C) 2012 The IPython Development Team
5 5 #
6 6 # Distributed under the terms of the BSD License. The full license is in
7 7 # the file COPYING, distributed as part of this software.
8 8 #-----------------------------------------------------------------------------
9 9
10 10 #-----------------------------------------------------------------------------
11 11 # Imports
12 12 #-----------------------------------------------------------------------------
13 13
14 14 # Local imports.
15 15 from IPython.config.loader import Config
16 16 from IPython.inprocess.socket import DummySocket
17 17 from IPython.utils.traitlets import HasTraits, Any, Instance, Type
18 18
19 19 #-----------------------------------------------------------------------------
20 20 # Channel classes
21 21 #-----------------------------------------------------------------------------
22 22
23 23 class InProcessChannel(object):
24 24 """ Base class for in-process channels.
25 25 """
26 26
27 27 def __init__(self, manager):
28 28 super(InProcessChannel, self).__init__()
29 29 self.manager = manager
30 30 self._is_alive = False
31 31
32 32 #--------------------------------------------------------------------------
33 33 # Channel interface
34 34 #--------------------------------------------------------------------------
35 35
36 36 def is_alive(self):
37 37 return self._is_alive
38 38
39 39 def start(self):
40 40 self._is_alive = True
41 41
42 42 def stop(self):
43 43 self._is_alive = False
44 44
45 45 def call_handlers(self, msg):
46 46 """ This method is called in the main thread when a message arrives.
47 47
48 48 Subclasses should override this method to handle incoming messages.
49 49 """
50 50 raise NotImplementedError('call_handlers must be defined in a subclass.')
51 51
52 52 #--------------------------------------------------------------------------
53 53 # InProcessChannel interface
54 54 #--------------------------------------------------------------------------
55 55
56 56 def call_handlers_later(self, *args, **kwds):
57 57 """ Call the message handlers later.
58 58
59 59 The default implementation just calls the handlers immediately, but this
60 60 method exists so that GUI toolkits can defer calling the handlers until
61 61 after the event loop has run, as expected by GUI frontends.
62 62 """
63 63 self.call_handlers(*args, **kwds)
64 64
65 65 def process_events(self):
66 66 """ Process any pending GUI events.
67 67
68 68 This method will be never be called from a frontend without an event
69 69 loop (e.g., a terminal frontend).
70 70 """
71 71 raise NotImplementedError
72 72
73 73
74 class ShellInProcessChannel(InProcessChannel):
74 class InProcessShellChannel(InProcessChannel):
75 75 """The DEALER channel for issues request/replies to the kernel.
76 76 """
77 77
78 78 # flag for whether execute requests should be allowed to call raw_input
79 79 allow_stdin = True
80 80
81 81 #--------------------------------------------------------------------------
82 82 # ShellChannel interface
83 83 #--------------------------------------------------------------------------
84 84
85 85 def execute(self, code, silent=False, store_history=True,
86 86 user_variables=[], user_expressions={}, allow_stdin=None):
87 87 """Execute code in the kernel.
88 88
89 89 Parameters
90 90 ----------
91 91 code : str
92 92 A string of Python code.
93 93
94 94 silent : bool, optional (default False)
95 95 If set, the kernel will execute the code as quietly possible, and
96 96 will force store_history to be False.
97 97
98 98 store_history : bool, optional (default True)
99 99 If set, the kernel will store command history. This is forced
100 100 to be False if silent is True.
101 101
102 102 user_variables : list, optional
103 103 A list of variable names to pull from the user's namespace. They
104 104 will come back as a dict with these names as keys and their
105 105 :func:`repr` as values.
106 106
107 107 user_expressions : dict, optional
108 108 A dict mapping names to expressions to be evaluated in the user's
109 109 dict. The expression values are returned as strings formatted using
110 110 :func:`repr`.
111 111
112 112 allow_stdin : bool, optional (default self.allow_stdin)
113 113 Flag for whether the kernel can send stdin requests to frontends.
114 114
115 115 Some frontends (e.g. the Notebook) do not support stdin requests.
116 116 If raw_input is called from code executed from such a frontend, a
117 117 StdinNotImplementedError will be raised.
118 118
119 119 Returns
120 120 -------
121 121 The msg_id of the message sent.
122 122 """
123 123 if allow_stdin is None:
124 124 allow_stdin = self.allow_stdin
125 125 content = dict(code=code, silent=silent, store_history=store_history,
126 126 user_variables=user_variables,
127 127 user_expressions=user_expressions,
128 128 allow_stdin=allow_stdin)
129 129 msg = self.manager.session.msg('execute_request', content)
130 130 self._dispatch_to_kernel(msg)
131 131 return msg['header']['msg_id']
132 132
133 133 def complete(self, text, line, cursor_pos, block=None):
134 134 """Tab complete text in the kernel's namespace.
135 135
136 136 Parameters
137 137 ----------
138 138 text : str
139 139 The text to complete.
140 140 line : str
141 141 The full line of text that is the surrounding context for the
142 142 text to complete.
143 143 cursor_pos : int
144 144 The position of the cursor in the line where the completion was
145 145 requested.
146 146 block : str, optional
147 147 The full block of code in which the completion is being requested.
148 148
149 149 Returns
150 150 -------
151 151 The msg_id of the message sent.
152 152 """
153 153 content = dict(text=text, line=line, block=block, cursor_pos=cursor_pos)
154 154 msg = self.manager.session.msg('complete_request', content)
155 155 self._dispatch_to_kernel(msg)
156 156 return msg['header']['msg_id']
157 157
158 158 def object_info(self, oname, detail_level=0):
159 159 """Get metadata information about an object.
160 160
161 161 Parameters
162 162 ----------
163 163 oname : str
164 164 A string specifying the object name.
165 165 detail_level : int, optional
166 166 The level of detail for the introspection (0-2)
167 167
168 168 Returns
169 169 -------
170 170 The msg_id of the message sent.
171 171 """
172 172 content = dict(oname=oname, detail_level=detail_level)
173 173 msg = self.manager.session.msg('object_info_request', content)
174 174 self._dispatch_to_kernel(msg)
175 175 return msg['header']['msg_id']
176 176
177 177 def history(self, raw=True, output=False, hist_access_type='range', **kwds):
178 178 """Get entries from the history list.
179 179
180 180 Parameters
181 181 ----------
182 182 raw : bool
183 183 If True, return the raw input.
184 184 output : bool
185 185 If True, then return the output as well.
186 186 hist_access_type : str
187 187 'range' (fill in session, start and stop params), 'tail' (fill in n)
188 188 or 'search' (fill in pattern param).
189 189
190 190 session : int
191 191 For a range request, the session from which to get lines. Session
192 192 numbers are positive integers; negative ones count back from the
193 193 current session.
194 194 start : int
195 195 The first line number of a history range.
196 196 stop : int
197 197 The final (excluded) line number of a history range.
198 198
199 199 n : int
200 200 The number of lines of history to get for a tail request.
201 201
202 202 pattern : str
203 203 The glob-syntax pattern for a search request.
204 204
205 205 Returns
206 206 -------
207 207 The msg_id of the message sent.
208 208 """
209 209 content = dict(raw=raw, output=output,
210 210 hist_access_type=hist_access_type, **kwds)
211 211 msg = self.manager.session.msg('history_request', content)
212 212 self._dispatch_to_kernel(msg)
213 213 return msg['header']['msg_id']
214 214
215 215 def shutdown(self, restart=False):
216 216 """ Request an immediate kernel shutdown.
217 217
218 218 A dummy method for the in-process kernel.
219 219 """
220 220 # FIXME: What to do here?
221 221 raise NotImplementedError('Cannot shutdown in-process kernel')
222 222
223 223 #--------------------------------------------------------------------------
224 224 # Protected interface
225 225 #--------------------------------------------------------------------------
226 226
227 227 def _dispatch_to_kernel(self, msg):
228 228 """ Send a message to the kernel and handle a reply.
229 229 """
230 230 kernel = self.manager.kernel
231 231 if kernel is None:
232 232 raise RuntimeError('Cannot send request. No kernel exists.')
233 233
234 234 stream = DummySocket()
235 235 self.manager.session.send(stream, msg)
236 236 msg_parts = stream.recv_multipart()
237 237 kernel.dispatch_shell(stream, msg_parts)
238 238
239 239 idents, reply_msg = self.manager.session.recv(stream, copy=False)
240 240 self.call_handlers_later(reply_msg)
241 241
242 242
243 class SubInProcessChannel(InProcessChannel):
243 class InProcessIOPubChannel(InProcessChannel):
244 244 """The SUB channel which listens for messages that the kernel publishes.
245 245 """
246 246
247 247 def flush(self, timeout=1.0):
248 248 """ Immediately processes all pending messages on the SUB channel.
249 249
250 250 A dummy method for the in-process kernel.
251 251 """
252 252 pass
253 253
254 254
255 class StdInInProcessChannel(InProcessChannel):
255 class InProcessStdInChannel(InProcessChannel):
256 256 """ A reply channel to handle raw_input requests that the kernel makes. """
257 257
258 258 def input(self, string):
259 259 """ Send a string of raw input to the kernel.
260 260 """
261 261 kernel = self.manager.kernel
262 262 if kernel is None:
263 263 raise RuntimeError('Cannot send input reply. No kernel exists.')
264 264 kernel.raw_input_str = string
265 265
266 266
267 class HBInProcessChannel(InProcessChannel):
267 class InProcessHBChannel(InProcessChannel):
268 268 """ A dummy heartbeat channel. """
269 269
270 270 time_to_dead = 3.0
271 271
272 272 def __init__(self, *args, **kwds):
273 super(HBInProcessChannel, self).__init__(*args, **kwds)
273 super(InProcessHBChannel, self).__init__(*args, **kwds)
274 274 self._pause = True
275 275
276 276 def pause(self):
277 277 """ Pause the heartbeat. """
278 278 self._pause = True
279 279
280 280 def unpause(self):
281 281 """ Unpause the heartbeat. """
282 282 self._pause = False
283 283
284 284 def is_beating(self):
285 285 """ Is the heartbeat running and responsive (and not paused). """
286 286 return not self._pause
287 287
288 288
289 289 #-----------------------------------------------------------------------------
290 290 # Main kernel manager class
291 291 #-----------------------------------------------------------------------------
292 292
293 293 class InProcessKernelManager(HasTraits):
294 294 """ A manager for an in-process kernel.
295 295
296 296 This class implements most of the interface of
297 297 ``IPython.zmq.kernelmanager.KernelManager`` and allows (asynchronous)
298 298 frontends to be used seamlessly with an in-process kernel.
299 299 """
300 300 # Config object for passing to child configurables
301 301 config = Instance(Config)
302 302
303 303 # The Session to use for building messages.
304 304 session = Instance('IPython.zmq.session.Session')
305 305 def _session_default(self):
306 306 from IPython.zmq.session import Session
307 307 return Session(config=self.config)
308 308
309 309 # The kernel process with which the KernelManager is communicating.
310 310 kernel = Instance('IPython.inprocess.ipkernel.InProcessKernel')
311 311
312 312 # The classes to use for the various channels.
313 shell_channel_class = Type(ShellInProcessChannel)
314 sub_channel_class = Type(SubInProcessChannel)
315 stdin_channel_class = Type(StdInInProcessChannel)
316 hb_channel_class = Type(HBInProcessChannel)
313 shell_channel_class = Type(InProcessShellChannel)
314 iopub_channel_class = Type(InProcessIOPubChannel)
315 stdin_channel_class = Type(InProcessStdInChannel)
316 hb_channel_class = Type(InProcessHBChannel)
317 317
318 318 # Protected traits.
319 319 _shell_channel = Any
320 _sub_channel = Any
320 _iopub_channel = Any
321 321 _stdin_channel = Any
322 322 _hb_channel = Any
323 323
324 324 #--------------------------------------------------------------------------
325 325 # Channel management methods:
326 326 #--------------------------------------------------------------------------
327 327
328 328 def start_channels(self, shell=True, sub=True, stdin=True, hb=True):
329 329 """ Starts the channels for this kernel.
330 330 """
331 331 if shell:
332 332 self.shell_channel.start()
333 333 if sub:
334 self.sub_channel.start()
334 self.iopub_channel.start()
335 335 if stdin:
336 336 self.stdin_channel.start()
337 337 self.shell_channel.allow_stdin = True
338 338 else:
339 339 self.shell_channel.allow_stdin = False
340 340 if hb:
341 341 self.hb_channel.start()
342 342
343 343 def stop_channels(self):
344 344 """ Stops all the running channels for this kernel.
345 345 """
346 346 if self.shell_channel.is_alive():
347 347 self.shell_channel.stop()
348 if self.sub_channel.is_alive():
349 self.sub_channel.stop()
348 if self.iopub_channel.is_alive():
349 self.iopub_channel.stop()
350 350 if self.stdin_channel.is_alive():
351 351 self.stdin_channel.stop()
352 352 if self.hb_channel.is_alive():
353 353 self.hb_channel.stop()
354 354
355 355 @property
356 356 def channels_running(self):
357 357 """ Are any of the channels created and running? """
358 return (self.shell_channel.is_alive() or self.sub_channel.is_alive() or
358 return (self.shell_channel.is_alive() or self.iopub_channel.is_alive() or
359 359 self.stdin_channel.is_alive() or self.hb_channel.is_alive())
360 360
361 361 #--------------------------------------------------------------------------
362 362 # Kernel management methods:
363 363 #--------------------------------------------------------------------------
364 364
365 365 def start_kernel(self, **kwds):
366 366 """ Starts a kernel process and configures the manager to use it.
367 367 """
368 368 from IPython.inprocess.ipkernel import InProcessKernel
369 369 self.kernel = InProcessKernel()
370 370 self.kernel.frontends.append(self)
371 371
372 372 def shutdown_kernel(self):
373 373 """ Attempts to the stop the kernel process cleanly. If the kernel
374 374 cannot be stopped and the kernel is local, it is killed.
375 375 """
376 376 self.kill_kernel()
377 377
378 378 def restart_kernel(self, now=False, **kwds):
379 379 """ Restarts a kernel with the arguments that were used to launch it.
380 380
381 381 The 'now' parameter is ignored.
382 382 """
383 383 self.shutdown_kernel()
384 384 self.start_kernel(**kwds)
385 385
386 386 @property
387 387 def has_kernel(self):
388 388 """ Returns whether a kernel process has been specified for the kernel
389 389 manager.
390 390 """
391 391 return self.kernel is not None
392 392
393 393 def kill_kernel(self):
394 394 """ Kill the running kernel.
395 395 """
396 396 self.kernel.frontends.remove(self)
397 397 self.kernel = None
398 398
399 399 def interrupt_kernel(self):
400 400 """ Interrupts the kernel. """
401 401 raise NotImplementedError("Cannot interrupt in-process kernel.")
402 402
403 403 def signal_kernel(self, signum):
404 404 """ Sends a signal to the kernel. """
405 405 raise NotImplementedError("Cannot signal in-process kernel.")
406 406
407 407 @property
408 408 def is_alive(self):
409 409 """ Is the kernel process still running? """
410 410 return True
411 411
412 412 #--------------------------------------------------------------------------
413 413 # Channels used for communication with the kernel:
414 414 #--------------------------------------------------------------------------
415 415
416 416 @property
417 417 def shell_channel(self):
418 418 """Get the REQ socket channel object to make requests of the kernel."""
419 419 if self._shell_channel is None:
420 420 self._shell_channel = self.shell_channel_class(self)
421 421 return self._shell_channel
422 422
423 423 @property
424 def sub_channel(self):
424 def iopub_channel(self):
425 425 """Get the SUB socket channel object."""
426 if self._sub_channel is None:
427 self._sub_channel = self.sub_channel_class(self)
428 return self._sub_channel
426 if self._iopub_channel is None:
427 self._iopub_channel = self.iopub_channel_class(self)
428 return self._iopub_channel
429 429
430 430 @property
431 431 def stdin_channel(self):
432 432 """Get the REP socket channel object to handle stdin (raw_input)."""
433 433 if self._stdin_channel is None:
434 434 self._stdin_channel = self.stdin_channel_class(self)
435 435 return self._stdin_channel
436 436
437 437 @property
438 438 def hb_channel(self):
439 439 """Get the heartbeat socket channel object to check that the
440 440 kernel is alive."""
441 441 if self._hb_channel is None:
442 442 self._hb_channel = self.hb_channel_class(self)
443 443 return self._hb_channel
@@ -1,89 +1,89
1 1 #-------------------------------------------------------------------------------
2 2 # Copyright (C) 2012 The IPython Development Team
3 3 #
4 4 # Distributed under the terms of the BSD License. The full license is in
5 5 # the file COPYING, distributed as part of this software.
6 6 #-------------------------------------------------------------------------------
7 7
8 8 #-----------------------------------------------------------------------------
9 9 # Imports
10 10 #-----------------------------------------------------------------------------
11 11 from __future__ import print_function
12 12
13 13 # Standard library imports
14 14 from StringIO import StringIO
15 15 import sys
16 16 import unittest
17 17
18 18 # Local imports
19 19 from IPython.inprocess.blockingkernelmanager import \
20 20 BlockingInProcessKernelManager
21 21 from IPython.inprocess.ipkernel import InProcessKernel
22 22 from IPython.testing.decorators import skipif_not_matplotlib
23 23 from IPython.utils.io import capture_output
24 24 from IPython.utils import py3compat
25 25
26 26 #-----------------------------------------------------------------------------
27 27 # Test case
28 28 #-----------------------------------------------------------------------------
29 29
30 30 class InProcessKernelTestCase(unittest.TestCase):
31 31
32 32 @skipif_not_matplotlib
33 33 def test_pylab(self):
34 34 """ Does pylab work in the in-process kernel?
35 35 """
36 36 km = BlockingInProcessKernelManager()
37 37 km.start_kernel()
38 38 km.shell_channel.execute('%pylab')
39 39 msg = get_stream_message(km)
40 40 self.assert_('Welcome to pylab' in msg['content']['data'])
41 41
42 42 def test_raw_input(self):
43 43 """ Does the in-process kernel handle raw_input correctly?
44 44 """
45 45 km = BlockingInProcessKernelManager()
46 46 km.start_kernel()
47 47
48 48 io = StringIO('foobar\n')
49 49 sys_stdin = sys.stdin
50 50 sys.stdin = io
51 51 try:
52 52 if py3compat.PY3:
53 53 km.shell_channel.execute('x = input()')
54 54 else:
55 55 km.shell_channel.execute('x = raw_input()')
56 56 finally:
57 57 sys.stdin = sys_stdin
58 58 self.assertEqual(km.kernel.shell.user_ns.get('x'), 'foobar')
59 59
60 60 def test_stdout(self):
61 61 """ Does the in-process kernel correctly capture IO?
62 62 """
63 63 kernel = InProcessKernel()
64 64
65 65 with capture_output() as io:
66 66 kernel.shell.run_cell('print("foo")')
67 67 self.assertEqual(io.stdout, 'foo\n')
68 68
69 69 km = BlockingInProcessKernelManager(kernel=kernel)
70 70 kernel.frontends.append(km)
71 71 km.shell_channel.execute('print("bar")')
72 72 msg = get_stream_message(km)
73 73 self.assertEqual(msg['content']['data'], 'bar\n')
74 74
75 75 #-----------------------------------------------------------------------------
76 76 # Utility functions
77 77 #-----------------------------------------------------------------------------
78 78
79 79 def get_stream_message(kernel_manager, timeout=5):
80 80 """ Gets a single stream message synchronously from the sub channel.
81 81 """
82 82 while True:
83 msg = kernel_manager.sub_channel.get_msg(timeout=timeout)
83 msg = kernel_manager.iopub_channel.get_msg(timeout=timeout)
84 84 if msg['header']['msg_type'] == 'stream':
85 85 return msg
86 86
87 87
88 88 if __name__ == '__main__':
89 89 unittest.main()
@@ -1,53 +1,84
1 1 """ Implements a fully blocking kernel manager.
2 2
3 3 Useful for test suites and blocking terminal interfaces.
4 4 """
5 5 #-----------------------------------------------------------------------------
6 6 # Copyright (C) 2010-2012 The IPython Development Team
7 7 #
8 8 # Distributed under the terms of the BSD License. The full license is in
9 9 # the file COPYING.txt, distributed as part of this software.
10 10 #-----------------------------------------------------------------------------
11 11
12 12 #-----------------------------------------------------------------------------
13 13 # Imports
14 14 #-----------------------------------------------------------------------------
15 15
16 # Local imports.
17 from IPython.inprocess.blockingkernelmanager import BlockingChannelMixin
18 16 from IPython.utils.traitlets import Type
19 from kernelmanager import KernelManager, SubSocketChannel, HBSocketChannel, \
20 ShellSocketChannel, StdInSocketChannel
17 from kernelmanager import KernelManager, IOPubChannel, HBChannel, \
18 ShellChannel, StdInChannel
21 19
22 20 #-----------------------------------------------------------------------------
23 21 # Blocking kernel manager
24 22 #-----------------------------------------------------------------------------
25 23
26 class BlockingSubSocketChannel(BlockingChannelMixin, SubSocketChannel):
24
25 class BlockingChannelMixin(object):
26
27 def __init__(self, *args, **kwds):
28 super(BlockingChannelMixin, self).__init__(*args, **kwds)
29 self._in_queue = Queue.Queue()
30
31 def call_handlers(self, msg):
32 self._in_queue.put(msg)
33
34 def get_msg(self, block=True, timeout=None):
35 """ Gets a message if there is one that is ready. """
36 return self._in_queue.get(block, timeout)
37
38 def get_msgs(self):
39 """ Get all messages that are currently ready. """
40 msgs = []
41 while True:
42 try:
43 msgs.append(self.get_msg(block=False))
44 except Queue.Empty:
45 break
46 return msgs
47
48 def msg_ready(self):
49 """ Is there a message that has been received? """
50 return not self._in_queue.empty()
51
52
53 class BlockingIOPubChannel(BlockingChannelMixin, IOPubChannel):
27 54 pass
28 55
29 class BlockingShellSocketChannel(BlockingChannelMixin, ShellSocketChannel):
56
57 class BlockingShellChannel(BlockingChannelMixin, ShellChannel):
30 58 pass
31 59
32 class BlockingStdInSocketChannel(BlockingChannelMixin, StdInSocketChannel):
60
61 class BlockingStdInChannel(BlockingChannelMixin, StdInChannel):
33 62 pass
34 63
35 class BlockingHBSocketChannel(HBSocketChannel):
64
65 class BlockingHBChannel(HBChannel):
36 66
37 67 # This kernel needs quicker monitoring, shorten to 1 sec.
38 68 # less than 0.5s is unreliable, and will get occasional
39 69 # false reports of missed beats.
40 70 time_to_dead = 1.
41 71
42 72 def call_handlers(self, since_last_heartbeat):
43 73 """ Pause beating on missed heartbeat. """
44 74 pass
45 75
76
46 77 class BlockingKernelManager(KernelManager):
47 78
48 79 # The classes to use for the various channels.
49 shell_channel_class = Type(BlockingShellSocketChannel)
50 sub_channel_class = Type(BlockingSubSocketChannel)
51 stdin_channel_class = Type(BlockingStdInSocketChannel)
52 hb_channel_class = Type(BlockingHBSocketChannel)
80 shell_channel_class = Type(BlockingShellChannel)
81 iopub_channel_class = Type(BlockingIOPubChannel)
82 stdin_channel_class = Type(BlockingStdInChannel)
83 hb_channel_class = Type(BlockingHBChannel)
53 84
@@ -1,1038 +1,1038
1 1 """Base classes to manage the interaction with a running kernel.
2 2
3 3 TODO
4 4 * Create logger to handle debugging and console messages.
5 5 """
6 6
7 7 #-----------------------------------------------------------------------------
8 8 # Copyright (C) 2008-2011 The IPython Development Team
9 9 #
10 10 # Distributed under the terms of the BSD License. The full license is in
11 11 # the file COPYING, distributed as part of this software.
12 12 #-----------------------------------------------------------------------------
13 13
14 14 #-----------------------------------------------------------------------------
15 15 # Imports
16 16 #-----------------------------------------------------------------------------
17 17
18 18 # Standard library imports.
19 19 import atexit
20 20 import errno
21 21 import json
22 22 from subprocess import Popen
23 23 import os
24 24 import signal
25 25 import sys
26 26 from threading import Thread
27 27 import time
28 28
29 29 # System library imports.
30 30 import zmq
31 31 # import ZMQError in top-level namespace, to avoid ugly attribute-error messages
32 32 # during garbage collection of threads at exit:
33 33 from zmq import ZMQError
34 34 from zmq.eventloop import ioloop, zmqstream
35 35
36 36 # Local imports.
37 37 from IPython.config.loader import Config
38 38 from IPython.config.configurable import Configurable
39 39 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
40 40 from IPython.utils.traitlets import (
41 41 HasTraits, Any, Instance, Type, Unicode, Integer, Bool, CaselessStrEnum
42 42 )
43 43 from IPython.utils.py3compat import str_to_bytes
44 44 from IPython.zmq.entry_point import write_connection_file
45 45 from session import Session
46 46
47 47 #-----------------------------------------------------------------------------
48 48 # Constants and exceptions
49 49 #-----------------------------------------------------------------------------
50 50
51 51 class InvalidPortNumber(Exception):
52 52 pass
53 53
54 54 #-----------------------------------------------------------------------------
55 55 # Utility functions
56 56 #-----------------------------------------------------------------------------
57 57
58 58 # some utilities to validate message structure, these might get moved elsewhere
59 59 # if they prove to have more generic utility
60 60
61 61 def validate_string_list(lst):
62 62 """Validate that the input is a list of strings.
63 63
64 64 Raises ValueError if not."""
65 65 if not isinstance(lst, list):
66 66 raise ValueError('input %r must be a list' % lst)
67 67 for x in lst:
68 68 if not isinstance(x, basestring):
69 69 raise ValueError('element %r in list must be a string' % x)
70 70
71 71
72 72 def validate_string_dict(dct):
73 73 """Validate that the input is a dict with string keys and values.
74 74
75 75 Raises ValueError if not."""
76 76 for k,v in dct.iteritems():
77 77 if not isinstance(k, basestring):
78 78 raise ValueError('key %r in dict must be a string' % k)
79 79 if not isinstance(v, basestring):
80 80 raise ValueError('value %r in dict must be a string' % v)
81 81
82 82
83 83 #-----------------------------------------------------------------------------
84 84 # ZMQ Socket Channel classes
85 85 #-----------------------------------------------------------------------------
86 86
87 87 class ZMQSocketChannel(Thread):
88 88 """The base class for the channels that use ZMQ sockets.
89 89 """
90 90 context = None
91 91 session = None
92 92 socket = None
93 93 ioloop = None
94 94 stream = None
95 95 _address = None
96 96 _exiting = False
97 97
98 98 def __init__(self, context, session, address):
99 99 """Create a channel
100 100
101 101 Parameters
102 102 ----------
103 103 context : :class:`zmq.Context`
104 104 The ZMQ context to use.
105 105 session : :class:`session.Session`
106 106 The session to use.
107 107 address : zmq url
108 108 Standard (ip, port) tuple that the kernel is listening on.
109 109 """
110 110 super(ZMQSocketChannel, self).__init__()
111 111 self.daemon = True
112 112
113 113 self.context = context
114 114 self.session = session
115 115 if isinstance(address, tuple):
116 116 if address[1] == 0:
117 117 message = 'The port number for a channel cannot be 0.'
118 118 raise InvalidPortNumber(message)
119 119 address = "tcp://%s:%i" % address
120 120 self._address = address
121 121 atexit.register(self._notice_exit)
122 122
123 123 def _notice_exit(self):
124 124 self._exiting = True
125 125
126 126 def _run_loop(self):
127 127 """Run my loop, ignoring EINTR events in the poller"""
128 128 while True:
129 129 try:
130 130 self.ioloop.start()
131 131 except ZMQError as e:
132 132 if e.errno == errno.EINTR:
133 133 continue
134 134 else:
135 135 raise
136 136 except Exception:
137 137 if self._exiting:
138 138 break
139 139 else:
140 140 raise
141 141 else:
142 142 break
143 143
144 144 def stop(self):
145 145 """Stop the channel's activity.
146 146
147 147 This calls :method:`Thread.join` and returns when the thread
148 148 terminates. :class:`RuntimeError` will be raised if
149 149 :method:`self.start` is called again.
150 150 """
151 151 self.join()
152 152
153 153 @property
154 154 def address(self):
155 155 """Get the channel's address as a zmq url string ('tcp://127.0.0.1:5555').
156 156 """
157 157 return self._address
158 158
159 159 def _queue_send(self, msg):
160 160 """Queue a message to be sent from the IOLoop's thread.
161 161
162 162 Parameters
163 163 ----------
164 164 msg : message to send
165 165
166 166 This is threadsafe, as it uses IOLoop.add_callback to give the loop's
167 167 thread control of the action.
168 168 """
169 169 def thread_send():
170 170 self.session.send(self.stream, msg)
171 171 self.ioloop.add_callback(thread_send)
172 172
173 173 def _handle_recv(self, msg):
174 174 """callback for stream.on_recv
175 175
176 176 unpacks message, and calls handlers with it.
177 177 """
178 178 ident,smsg = self.session.feed_identities(msg)
179 179 self.call_handlers(self.session.unserialize(smsg))
180 180
181 181
182 182
183 class ShellSocketChannel(ZMQSocketChannel):
183 class ShellChannel(ZMQSocketChannel):
184 184 """The DEALER channel for issues request/replies to the kernel.
185 185 """
186 186
187 187 command_queue = None
188 188 # flag for whether execute requests should be allowed to call raw_input:
189 189 allow_stdin = True
190 190
191 191 def __init__(self, context, session, address):
192 super(ShellSocketChannel, self).__init__(context, session, address)
192 super(ShellChannel, self).__init__(context, session, address)
193 193 self.ioloop = ioloop.IOLoop()
194 194
195 195 def run(self):
196 196 """The thread's main activity. Call start() instead."""
197 197 self.socket = self.context.socket(zmq.DEALER)
198 198 self.socket.setsockopt(zmq.IDENTITY, self.session.bsession)
199 199 self.socket.connect(self.address)
200 200 self.stream = zmqstream.ZMQStream(self.socket, self.ioloop)
201 201 self.stream.on_recv(self._handle_recv)
202 202 self._run_loop()
203 203 try:
204 204 self.socket.close()
205 205 except:
206 206 pass
207 207
208 208 def stop(self):
209 209 self.ioloop.stop()
210 super(ShellSocketChannel, self).stop()
210 super(ShellChannel, self).stop()
211 211
212 212 def call_handlers(self, msg):
213 213 """This method is called in the ioloop thread when a message arrives.
214 214
215 215 Subclasses should override this method to handle incoming messages.
216 216 It is important to remember that this method is called in the thread
217 217 so that some logic must be done to ensure that the application leve
218 218 handlers are called in the application thread.
219 219 """
220 220 raise NotImplementedError('call_handlers must be defined in a subclass.')
221 221
222 222 def execute(self, code, silent=False, store_history=True,
223 223 user_variables=None, user_expressions=None, allow_stdin=None):
224 224 """Execute code in the kernel.
225 225
226 226 Parameters
227 227 ----------
228 228 code : str
229 229 A string of Python code.
230 230
231 231 silent : bool, optional (default False)
232 232 If set, the kernel will execute the code as quietly possible, and
233 233 will force store_history to be False.
234 234
235 235 store_history : bool, optional (default True)
236 236 If set, the kernel will store command history. This is forced
237 237 to be False if silent is True.
238 238
239 239 user_variables : list, optional
240 240 A list of variable names to pull from the user's namespace. They
241 241 will come back as a dict with these names as keys and their
242 242 :func:`repr` as values.
243 243
244 244 user_expressions : dict, optional
245 245 A dict mapping names to expressions to be evaluated in the user's
246 246 dict. The expression values are returned as strings formatted using
247 247 :func:`repr`.
248 248
249 249 allow_stdin : bool, optional (default self.allow_stdin)
250 250 Flag for whether the kernel can send stdin requests to frontends.
251 251
252 252 Some frontends (e.g. the Notebook) do not support stdin requests.
253 253 If raw_input is called from code executed from such a frontend, a
254 254 StdinNotImplementedError will be raised.
255 255
256 256 Returns
257 257 -------
258 258 The msg_id of the message sent.
259 259 """
260 260 if user_variables is None:
261 261 user_variables = []
262 262 if user_expressions is None:
263 263 user_expressions = {}
264 264 if allow_stdin is None:
265 265 allow_stdin = self.allow_stdin
266 266
267 267
268 268 # Don't waste network traffic if inputs are invalid
269 269 if not isinstance(code, basestring):
270 270 raise ValueError('code %r must be a string' % code)
271 271 validate_string_list(user_variables)
272 272 validate_string_dict(user_expressions)
273 273
274 274 # Create class for content/msg creation. Related to, but possibly
275 275 # not in Session.
276 276 content = dict(code=code, silent=silent, store_history=store_history,
277 277 user_variables=user_variables,
278 278 user_expressions=user_expressions,
279 279 allow_stdin=allow_stdin,
280 280 )
281 281 msg = self.session.msg('execute_request', content)
282 282 self._queue_send(msg)
283 283 return msg['header']['msg_id']
284 284
285 285 def complete(self, text, line, cursor_pos, block=None):
286 286 """Tab complete text in the kernel's namespace.
287 287
288 288 Parameters
289 289 ----------
290 290 text : str
291 291 The text to complete.
292 292 line : str
293 293 The full line of text that is the surrounding context for the
294 294 text to complete.
295 295 cursor_pos : int
296 296 The position of the cursor in the line where the completion was
297 297 requested.
298 298 block : str, optional
299 299 The full block of code in which the completion is being requested.
300 300
301 301 Returns
302 302 -------
303 303 The msg_id of the message sent.
304 304 """
305 305 content = dict(text=text, line=line, block=block, cursor_pos=cursor_pos)
306 306 msg = self.session.msg('complete_request', content)
307 307 self._queue_send(msg)
308 308 return msg['header']['msg_id']
309 309
310 310 def object_info(self, oname, detail_level=0):
311 311 """Get metadata information about an object.
312 312
313 313 Parameters
314 314 ----------
315 315 oname : str
316 316 A string specifying the object name.
317 317 detail_level : int, optional
318 318 The level of detail for the introspection (0-2)
319 319
320 320 Returns
321 321 -------
322 322 The msg_id of the message sent.
323 323 """
324 324 content = dict(oname=oname, detail_level=detail_level)
325 325 msg = self.session.msg('object_info_request', content)
326 326 self._queue_send(msg)
327 327 return msg['header']['msg_id']
328 328
329 329 def history(self, raw=True, output=False, hist_access_type='range', **kwargs):
330 330 """Get entries from the history list.
331 331
332 332 Parameters
333 333 ----------
334 334 raw : bool
335 335 If True, return the raw input.
336 336 output : bool
337 337 If True, then return the output as well.
338 338 hist_access_type : str
339 339 'range' (fill in session, start and stop params), 'tail' (fill in n)
340 340 or 'search' (fill in pattern param).
341 341
342 342 session : int
343 343 For a range request, the session from which to get lines. Session
344 344 numbers are positive integers; negative ones count back from the
345 345 current session.
346 346 start : int
347 347 The first line number of a history range.
348 348 stop : int
349 349 The final (excluded) line number of a history range.
350 350
351 351 n : int
352 352 The number of lines of history to get for a tail request.
353 353
354 354 pattern : str
355 355 The glob-syntax pattern for a search request.
356 356
357 357 Returns
358 358 -------
359 359 The msg_id of the message sent.
360 360 """
361 361 content = dict(raw=raw, output=output, hist_access_type=hist_access_type,
362 362 **kwargs)
363 363 msg = self.session.msg('history_request', content)
364 364 self._queue_send(msg)
365 365 return msg['header']['msg_id']
366 366
367 367 def kernel_info(self):
368 368 """Request kernel info."""
369 369 msg = self.session.msg('kernel_info_request')
370 370 self._queue_send(msg)
371 371 return msg['header']['msg_id']
372 372
373 373 def shutdown(self, restart=False):
374 374 """Request an immediate kernel shutdown.
375 375
376 376 Upon receipt of the (empty) reply, client code can safely assume that
377 377 the kernel has shut down and it's safe to forcefully terminate it if
378 378 it's still alive.
379 379
380 380 The kernel will send the reply via a function registered with Python's
381 381 atexit module, ensuring it's truly done as the kernel is done with all
382 382 normal operation.
383 383 """
384 384 # Send quit message to kernel. Once we implement kernel-side setattr,
385 385 # this should probably be done that way, but for now this will do.
386 386 msg = self.session.msg('shutdown_request', {'restart':restart})
387 387 self._queue_send(msg)
388 388 return msg['header']['msg_id']
389 389
390 390
391 391
392 class SubSocketChannel(ZMQSocketChannel):
392 class IOPubChannel(ZMQSocketChannel):
393 393 """The SUB channel which listens for messages that the kernel publishes.
394 394 """
395 395
396 396 def __init__(self, context, session, address):
397 super(SubSocketChannel, self).__init__(context, session, address)
397 super(IOPubChannel, self).__init__(context, session, address)
398 398 self.ioloop = ioloop.IOLoop()
399 399
400 400 def run(self):
401 401 """The thread's main activity. Call start() instead."""
402 402 self.socket = self.context.socket(zmq.SUB)
403 403 self.socket.setsockopt(zmq.SUBSCRIBE,b'')
404 404 self.socket.setsockopt(zmq.IDENTITY, self.session.bsession)
405 405 self.socket.connect(self.address)
406 406 self.stream = zmqstream.ZMQStream(self.socket, self.ioloop)
407 407 self.stream.on_recv(self._handle_recv)
408 408 self._run_loop()
409 409 try:
410 410 self.socket.close()
411 411 except:
412 412 pass
413 413
414 414 def stop(self):
415 415 self.ioloop.stop()
416 super(SubSocketChannel, self).stop()
416 super(IOPubChannel, self).stop()
417 417
418 418 def call_handlers(self, msg):
419 419 """This method is called in the ioloop thread when a message arrives.
420 420
421 421 Subclasses should override this method to handle incoming messages.
422 422 It is important to remember that this method is called in the thread
423 423 so that some logic must be done to ensure that the application leve
424 424 handlers are called in the application thread.
425 425 """
426 426 raise NotImplementedError('call_handlers must be defined in a subclass.')
427 427
428 428 def flush(self, timeout=1.0):
429 429 """Immediately processes all pending messages on the SUB channel.
430 430
431 431 Callers should use this method to ensure that :method:`call_handlers`
432 432 has been called for all messages that have been received on the
433 433 0MQ SUB socket of this channel.
434 434
435 435 This method is thread safe.
436 436
437 437 Parameters
438 438 ----------
439 439 timeout : float, optional
440 440 The maximum amount of time to spend flushing, in seconds. The
441 441 default is one second.
442 442 """
443 443 # We do the IOLoop callback process twice to ensure that the IOLoop
444 444 # gets to perform at least one full poll.
445 445 stop_time = time.time() + timeout
446 446 for i in xrange(2):
447 447 self._flushed = False
448 448 self.ioloop.add_callback(self._flush)
449 449 while not self._flushed and time.time() < stop_time:
450 450 time.sleep(0.01)
451 451
452 452 def _flush(self):
453 453 """Callback for :method:`self.flush`."""
454 454 self.stream.flush()
455 455 self._flushed = True
456 456
457 457
458 class StdInSocketChannel(ZMQSocketChannel):
458 class StdInChannel(ZMQSocketChannel):
459 459 """A reply channel to handle raw_input requests that the kernel makes."""
460 460
461 461 msg_queue = None
462 462
463 463 def __init__(self, context, session, address):
464 super(StdInSocketChannel, self).__init__(context, session, address)
464 super(StdInChannel, self).__init__(context, session, address)
465 465 self.ioloop = ioloop.IOLoop()
466 466
467 467 def run(self):
468 468 """The thread's main activity. Call start() instead."""
469 469 self.socket = self.context.socket(zmq.DEALER)
470 470 self.socket.setsockopt(zmq.IDENTITY, self.session.bsession)
471 471 self.socket.connect(self.address)
472 472 self.stream = zmqstream.ZMQStream(self.socket, self.ioloop)
473 473 self.stream.on_recv(self._handle_recv)
474 474 self._run_loop()
475 475 try:
476 476 self.socket.close()
477 477 except:
478 478 pass
479 479
480 480
481 481 def stop(self):
482 482 self.ioloop.stop()
483 super(StdInSocketChannel, self).stop()
483 super(StdInChannel, self).stop()
484 484
485 485 def call_handlers(self, msg):
486 486 """This method is called in the ioloop thread when a message arrives.
487 487
488 488 Subclasses should override this method to handle incoming messages.
489 489 It is important to remember that this method is called in the thread
490 490 so that some logic must be done to ensure that the application leve
491 491 handlers are called in the application thread.
492 492 """
493 493 raise NotImplementedError('call_handlers must be defined in a subclass.')
494 494
495 495 def input(self, string):
496 496 """Send a string of raw input to the kernel."""
497 497 content = dict(value=string)
498 498 msg = self.session.msg('input_reply', content)
499 499 self._queue_send(msg)
500 500
501 501
502 class HBSocketChannel(ZMQSocketChannel):
502 class HBChannel(ZMQSocketChannel):
503 503 """The heartbeat channel which monitors the kernel heartbeat.
504 504
505 505 Note that the heartbeat channel is paused by default. As long as you start
506 506 this channel, the kernel manager will ensure that it is paused and un-paused
507 507 as appropriate.
508 508 """
509 509
510 510 time_to_dead = 3.0
511 511 socket = None
512 512 poller = None
513 513 _running = None
514 514 _pause = None
515 515 _beating = None
516 516
517 517 def __init__(self, context, session, address):
518 super(HBSocketChannel, self).__init__(context, session, address)
518 super(HBChannel, self).__init__(context, session, address)
519 519 self._running = False
520 520 self._pause =True
521 521 self.poller = zmq.Poller()
522 522
523 523 def _create_socket(self):
524 524 if self.socket is not None:
525 525 # close previous socket, before opening a new one
526 526 self.poller.unregister(self.socket)
527 527 self.socket.close()
528 528 self.socket = self.context.socket(zmq.REQ)
529 529 self.socket.setsockopt(zmq.LINGER, 0)
530 530 self.socket.connect(self.address)
531 531
532 532 self.poller.register(self.socket, zmq.POLLIN)
533 533
534 534 def _poll(self, start_time):
535 535 """poll for heartbeat replies until we reach self.time_to_dead
536 536
537 537 Ignores interrupts, and returns the result of poll(), which
538 538 will be an empty list if no messages arrived before the timeout,
539 539 or the event tuple if there is a message to receive.
540 540 """
541 541
542 542 until_dead = self.time_to_dead - (time.time() - start_time)
543 543 # ensure poll at least once
544 544 until_dead = max(until_dead, 1e-3)
545 545 events = []
546 546 while True:
547 547 try:
548 548 events = self.poller.poll(1000 * until_dead)
549 549 except ZMQError as e:
550 550 if e.errno == errno.EINTR:
551 551 # ignore interrupts during heartbeat
552 552 # this may never actually happen
553 553 until_dead = self.time_to_dead - (time.time() - start_time)
554 554 until_dead = max(until_dead, 1e-3)
555 555 pass
556 556 else:
557 557 raise
558 558 except Exception:
559 559 if self._exiting:
560 560 break
561 561 else:
562 562 raise
563 563 else:
564 564 break
565 565 return events
566 566
567 567 def run(self):
568 568 """The thread's main activity. Call start() instead."""
569 569 self._create_socket()
570 570 self._running = True
571 571 self._beating = True
572 572
573 573 while self._running:
574 574 if self._pause:
575 575 # just sleep, and skip the rest of the loop
576 576 time.sleep(self.time_to_dead)
577 577 continue
578 578
579 579 since_last_heartbeat = 0.0
580 580 # io.rprint('Ping from HB channel') # dbg
581 581 # no need to catch EFSM here, because the previous event was
582 582 # either a recv or connect, which cannot be followed by EFSM
583 583 self.socket.send(b'ping')
584 584 request_time = time.time()
585 585 ready = self._poll(request_time)
586 586 if ready:
587 587 self._beating = True
588 588 # the poll above guarantees we have something to recv
589 589 self.socket.recv()
590 590 # sleep the remainder of the cycle
591 591 remainder = self.time_to_dead - (time.time() - request_time)
592 592 if remainder > 0:
593 593 time.sleep(remainder)
594 594 continue
595 595 else:
596 596 # nothing was received within the time limit, signal heart failure
597 597 self._beating = False
598 598 since_last_heartbeat = time.time() - request_time
599 599 self.call_handlers(since_last_heartbeat)
600 600 # and close/reopen the socket, because the REQ/REP cycle has been broken
601 601 self._create_socket()
602 602 continue
603 603 try:
604 604 self.socket.close()
605 605 except:
606 606 pass
607 607
608 608 def pause(self):
609 609 """Pause the heartbeat."""
610 610 self._pause = True
611 611
612 612 def unpause(self):
613 613 """Unpause the heartbeat."""
614 614 self._pause = False
615 615
616 616 def is_beating(self):
617 617 """Is the heartbeat running and responsive (and not paused)."""
618 618 if self.is_alive() and not self._pause and self._beating:
619 619 return True
620 620 else:
621 621 return False
622 622
623 623 def stop(self):
624 624 self._running = False
625 super(HBSocketChannel, self).stop()
625 super(HBChannel, self).stop()
626 626
627 627 def call_handlers(self, since_last_heartbeat):
628 628 """This method is called in the ioloop thread when a message arrives.
629 629
630 630 Subclasses should override this method to handle incoming messages.
631 631 It is important to remember that this method is called in the thread
632 632 so that some logic must be done to ensure that the application level
633 633 handlers are called in the application thread.
634 634 """
635 635 raise NotImplementedError('call_handlers must be defined in a subclass.')
636 636
637 637
638 638 #-----------------------------------------------------------------------------
639 639 # Main kernel manager class
640 640 #-----------------------------------------------------------------------------
641 641
642 642 class KernelManager(Configurable):
643 643 """ Manages a kernel for a frontend.
644 644
645 645 The SUB channel is for the frontend to receive messages published by the
646 646 kernel.
647 647
648 648 The REQ channel is for the frontend to make requests of the kernel.
649 649
650 650 The REP channel is for the kernel to request stdin (raw_input) from the
651 651 frontend.
652 652 """
653 653 # The PyZMQ Context to use for communication with the kernel.
654 654 context = Instance(zmq.Context)
655 655 def _context_default(self):
656 656 return zmq.Context.instance()
657 657
658 658 # The Session to use for communication with the kernel.
659 659 session = Instance(Session)
660 660 def _session_default(self):
661 661 return Session(config=self.config)
662 662
663 663 # The kernel process with which the KernelManager is communicating.
664 664 kernel = Instance(Popen)
665 665
666 666 # The addresses for the communication channels.
667 667 connection_file = Unicode('')
668 668
669 669 transport = CaselessStrEnum(['tcp', 'ipc'], default_value='tcp', config=True)
670 670
671 671 ip = Unicode(LOCALHOST, config=True)
672 672 def _ip_changed(self, name, old, new):
673 673 if new == '*':
674 674 self.ip = '0.0.0.0'
675 675 shell_port = Integer(0)
676 676 iopub_port = Integer(0)
677 677 stdin_port = Integer(0)
678 678 hb_port = Integer(0)
679 679
680 680 # The classes to use for the various channels.
681 shell_channel_class = Type(ShellSocketChannel)
682 sub_channel_class = Type(SubSocketChannel)
683 stdin_channel_class = Type(StdInSocketChannel)
684 hb_channel_class = Type(HBSocketChannel)
681 shell_channel_class = Type(ShellChannel)
682 iopub_channel_class = Type(IOPubChannel)
683 stdin_channel_class = Type(StdInChannel)
684 hb_channel_class = Type(HBChannel)
685 685
686 686 # Protected traits.
687 687 _launch_args = Any
688 688 _shell_channel = Any
689 _sub_channel = Any
689 _iopub_channel = Any
690 690 _stdin_channel = Any
691 691 _hb_channel = Any
692 692 _connection_file_written=Bool(False)
693 693
694 694 def __del__(self):
695 695 self.cleanup_connection_file()
696 696
697 697 #--------------------------------------------------------------------------
698 698 # Channel management methods:
699 699 #--------------------------------------------------------------------------
700 700
701 701 def start_channels(self, shell=True, sub=True, stdin=True, hb=True):
702 702 """Starts the channels for this kernel.
703 703
704 704 This will create the channels if they do not exist and then start
705 705 them. If port numbers of 0 are being used (random ports) then you
706 706 must first call :method:`start_kernel`. If the channels have been
707 707 stopped and you call this, :class:`RuntimeError` will be raised.
708 708 """
709 709 if shell:
710 710 self.shell_channel.start()
711 711 if sub:
712 self.sub_channel.start()
712 self.iopub_channel.start()
713 713 if stdin:
714 714 self.stdin_channel.start()
715 715 self.shell_channel.allow_stdin = True
716 716 else:
717 717 self.shell_channel.allow_stdin = False
718 718 if hb:
719 719 self.hb_channel.start()
720 720
721 721 def stop_channels(self):
722 722 """Stops all the running channels for this kernel.
723 723 """
724 724 if self.shell_channel.is_alive():
725 725 self.shell_channel.stop()
726 if self.sub_channel.is_alive():
727 self.sub_channel.stop()
726 if self.iopub_channel.is_alive():
727 self.iopub_channel.stop()
728 728 if self.stdin_channel.is_alive():
729 729 self.stdin_channel.stop()
730 730 if self.hb_channel.is_alive():
731 731 self.hb_channel.stop()
732 732
733 733 @property
734 734 def channels_running(self):
735 735 """Are any of the channels created and running?"""
736 return (self.shell_channel.is_alive() or self.sub_channel.is_alive() or
736 return (self.shell_channel.is_alive() or self.iopub_channel.is_alive() or
737 737 self.stdin_channel.is_alive() or self.hb_channel.is_alive())
738 738
739 739 #--------------------------------------------------------------------------
740 740 # Kernel process management methods:
741 741 #--------------------------------------------------------------------------
742 742
743 743 def cleanup_connection_file(self):
744 744 """cleanup connection file *if we wrote it*
745 745
746 746 Will not raise if the connection file was already removed somehow.
747 747 """
748 748 if self._connection_file_written:
749 749 # cleanup connection files on full shutdown of kernel we started
750 750 self._connection_file_written = False
751 751 try:
752 752 os.remove(self.connection_file)
753 753 except (IOError, OSError):
754 754 pass
755 755
756 756 self.cleanup_ipc_files()
757 757
758 758 def cleanup_ipc_files(self):
759 759 """cleanup ipc files if we wrote them"""
760 760 if self.transport != 'ipc':
761 761 return
762 762 for port in (self.shell_port, self.iopub_port, self.stdin_port, self.hb_port):
763 763 ipcfile = "%s-%i" % (self.ip, port)
764 764 try:
765 765 os.remove(ipcfile)
766 766 except (IOError, OSError):
767 767 pass
768 768
769 769 def load_connection_file(self):
770 770 """load connection info from JSON dict in self.connection_file"""
771 771 with open(self.connection_file) as f:
772 772 cfg = json.loads(f.read())
773 773
774 774 from pprint import pprint
775 775 pprint(cfg)
776 776 self.transport = cfg.get('transport', 'tcp')
777 777 self.ip = cfg['ip']
778 778 self.shell_port = cfg['shell_port']
779 779 self.stdin_port = cfg['stdin_port']
780 780 self.iopub_port = cfg['iopub_port']
781 781 self.hb_port = cfg['hb_port']
782 782 self.session.key = str_to_bytes(cfg['key'])
783 783
784 784 def write_connection_file(self):
785 785 """write connection info to JSON dict in self.connection_file"""
786 786 if self._connection_file_written:
787 787 return
788 788 self.connection_file,cfg = write_connection_file(self.connection_file,
789 789 transport=self.transport, ip=self.ip, key=self.session.key,
790 790 stdin_port=self.stdin_port, iopub_port=self.iopub_port,
791 791 shell_port=self.shell_port, hb_port=self.hb_port)
792 792 # write_connection_file also sets default ports:
793 793 self.shell_port = cfg['shell_port']
794 794 self.stdin_port = cfg['stdin_port']
795 795 self.iopub_port = cfg['iopub_port']
796 796 self.hb_port = cfg['hb_port']
797 797
798 798 self._connection_file_written = True
799 799
800 800 def start_kernel(self, **kw):
801 801 """Starts a kernel process and configures the manager to use it.
802 802
803 803 If random ports (port=0) are being used, this method must be called
804 804 before the channels are created.
805 805
806 806 Parameters:
807 807 -----------
808 808 launcher : callable, optional (default None)
809 809 A custom function for launching the kernel process (generally a
810 810 wrapper around ``entry_point.base_launch_kernel``). In most cases,
811 811 it should not be necessary to use this parameter.
812 812
813 813 **kw : optional
814 814 See respective options for IPython and Python kernels.
815 815 """
816 816 if self.transport == 'tcp' and self.ip not in LOCAL_IPS:
817 817 raise RuntimeError("Can only launch a kernel on a local interface. "
818 818 "Make sure that the '*_address' attributes are "
819 819 "configured properly. "
820 820 "Currently valid addresses are: %s"%LOCAL_IPS
821 821 )
822 822
823 823 # write connection file / get default ports
824 824 self.write_connection_file()
825 825
826 826 self._launch_args = kw.copy()
827 827 launch_kernel = kw.pop('launcher', None)
828 828 if launch_kernel is None:
829 829 from ipkernel import launch_kernel
830 830 self.kernel = launch_kernel(fname=self.connection_file, **kw)
831 831
832 832 def shutdown_kernel(self, now=False, restart=False):
833 833 """ Attempts to the stop the kernel process cleanly.
834 834
835 835 If the kernel cannot be stopped and the kernel is local, it is killed.
836 836 """
837 837 # FIXME: Shutdown does not work on Windows due to ZMQ errors!
838 838 if sys.platform == 'win32':
839 839 self.kill_kernel()
840 840 return
841 841
842 842 # Pause the heart beat channel if it exists.
843 843 if self._hb_channel is not None:
844 844 self._hb_channel.pause()
845 845
846 846 if now:
847 847 if self.has_kernel:
848 848 self.kill_kernel()
849 849 else:
850 850 # Don't send any additional kernel kill messages immediately, to give
851 851 # the kernel a chance to properly execute shutdown actions. Wait for at
852 852 # most 1s, checking every 0.1s.
853 853 self.shell_channel.shutdown(restart=restart)
854 854 for i in range(10):
855 855 if self.is_alive:
856 856 time.sleep(0.1)
857 857 else:
858 858 break
859 859 else:
860 860 # OK, we've waited long enough.
861 861 if self.has_kernel:
862 862 self.kill_kernel()
863 863
864 864 if not restart:
865 865 self.cleanup_connection_file()
866 866 else:
867 867 self.cleanup_ipc_files()
868 868
869 869 def restart_kernel(self, now=False, **kw):
870 870 """Restarts a kernel with the arguments that were used to launch it.
871 871
872 872 If the old kernel was launched with random ports, the same ports will be
873 873 used for the new kernel.
874 874
875 875 Parameters
876 876 ----------
877 877 now : bool, optional
878 878 If True, the kernel is forcefully restarted *immediately*, without
879 879 having a chance to do any cleanup action. Otherwise the kernel is
880 880 given 1s to clean up before a forceful restart is issued.
881 881
882 882 In all cases the kernel is restarted, the only difference is whether
883 883 it is given a chance to perform a clean shutdown or not.
884 884
885 885 **kw : optional
886 886 Any options specified here will replace those used to launch the
887 887 kernel.
888 888 """
889 889 if self._launch_args is None:
890 890 raise RuntimeError("Cannot restart the kernel. "
891 891 "No previous call to 'start_kernel'.")
892 892 else:
893 893 # Stop currently running kernel.
894 894 self.shutdown_kernel(now=now, restart=True)
895 895
896 896 # Start new kernel.
897 897 self._launch_args.update(kw)
898 898 self.start_kernel(**self._launch_args)
899 899
900 900 # FIXME: Messages get dropped in Windows due to probable ZMQ bug
901 901 # unless there is some delay here.
902 902 if sys.platform == 'win32':
903 903 time.sleep(0.2)
904 904
905 905 @property
906 906 def has_kernel(self):
907 907 """Returns whether a kernel process has been specified for the kernel
908 908 manager.
909 909 """
910 910 return self.kernel is not None
911 911
912 912 def kill_kernel(self):
913 913 """ Kill the running kernel.
914 914
915 915 This method blocks until the kernel process has terminated.
916 916 """
917 917 if self.has_kernel:
918 918 # Pause the heart beat channel if it exists.
919 919 if self._hb_channel is not None:
920 920 self._hb_channel.pause()
921 921
922 922 # Signal the kernel to terminate (sends SIGKILL on Unix and calls
923 923 # TerminateProcess() on Win32).
924 924 try:
925 925 self.kernel.kill()
926 926 except OSError as e:
927 927 # In Windows, we will get an Access Denied error if the process
928 928 # has already terminated. Ignore it.
929 929 if sys.platform == 'win32':
930 930 if e.winerror != 5:
931 931 raise
932 932 # On Unix, we may get an ESRCH error if the process has already
933 933 # terminated. Ignore it.
934 934 else:
935 935 from errno import ESRCH
936 936 if e.errno != ESRCH:
937 937 raise
938 938
939 939 # Block until the kernel terminates.
940 940 self.kernel.wait()
941 941 self.kernel = None
942 942 else:
943 943 raise RuntimeError("Cannot kill kernel. No kernel is running!")
944 944
945 945 def interrupt_kernel(self):
946 946 """ Interrupts the kernel.
947 947
948 948 Unlike ``signal_kernel``, this operation is well supported on all
949 949 platforms.
950 950 """
951 951 if self.has_kernel:
952 952 if sys.platform == 'win32':
953 953 from parentpoller import ParentPollerWindows as Poller
954 954 Poller.send_interrupt(self.kernel.win32_interrupt_event)
955 955 else:
956 956 self.kernel.send_signal(signal.SIGINT)
957 957 else:
958 958 raise RuntimeError("Cannot interrupt kernel. No kernel is running!")
959 959
960 960 def signal_kernel(self, signum):
961 961 """ Sends a signal to the kernel.
962 962
963 963 Note that since only SIGTERM is supported on Windows, this function is
964 964 only useful on Unix systems.
965 965 """
966 966 if self.has_kernel:
967 967 self.kernel.send_signal(signum)
968 968 else:
969 969 raise RuntimeError("Cannot signal kernel. No kernel is running!")
970 970
971 971 @property
972 972 def is_alive(self):
973 973 """Is the kernel process still running?"""
974 974 if self.has_kernel:
975 975 if self.kernel.poll() is None:
976 976 return True
977 977 else:
978 978 return False
979 979 elif self._hb_channel is not None:
980 980 # We didn't start the kernel with this KernelManager so we
981 981 # use the heartbeat.
982 982 return self._hb_channel.is_beating()
983 983 else:
984 984 # no heartbeat and not local, we can't tell if it's running,
985 985 # so naively return True
986 986 return True
987 987
988 988 #--------------------------------------------------------------------------
989 989 # Channels used for communication with the kernel:
990 990 #--------------------------------------------------------------------------
991 991
992 992 def _make_url(self, port):
993 993 """make a zmq url with a port"""
994 994 if self.transport == 'tcp':
995 995 return "tcp://%s:%i" % (self.ip, port)
996 996 else:
997 997 return "%s://%s-%s" % (self.transport, self.ip, port)
998 998
999 999 @property
1000 1000 def shell_channel(self):
1001 1001 """Get the REQ socket channel object to make requests of the kernel."""
1002 1002 if self._shell_channel is None:
1003 1003 self._shell_channel = self.shell_channel_class(self.context,
1004 1004 self.session,
1005 1005 self._make_url(self.shell_port),
1006 1006 )
1007 1007 return self._shell_channel
1008 1008
1009 1009 @property
1010 def sub_channel(self):
1010 def iopub_channel(self):
1011 1011 """Get the SUB socket channel object."""
1012 if self._sub_channel is None:
1013 self._sub_channel = self.sub_channel_class(self.context,
1012 if self._iopub_channel is None:
1013 self._iopub_channel = self.iopub_channel_class(self.context,
1014 1014 self.session,
1015 1015 self._make_url(self.iopub_port),
1016 1016 )
1017 return self._sub_channel
1017 return self._iopub_channel
1018 1018
1019 1019 @property
1020 1020 def stdin_channel(self):
1021 1021 """Get the REP socket channel object to handle stdin (raw_input)."""
1022 1022 if self._stdin_channel is None:
1023 1023 self._stdin_channel = self.stdin_channel_class(self.context,
1024 1024 self.session,
1025 1025 self._make_url(self.stdin_port),
1026 1026 )
1027 1027 return self._stdin_channel
1028 1028
1029 1029 @property
1030 1030 def hb_channel(self):
1031 1031 """Get the heartbeat socket channel object to check that the
1032 1032 kernel is alive."""
1033 1033 if self._hb_channel is None:
1034 1034 self._hb_channel = self.hb_channel_class(self.context,
1035 1035 self.session,
1036 1036 self._make_url(self.hb_port),
1037 1037 )
1038 1038 return self._hb_channel
@@ -1,497 +1,497
1 1 """Test suite for our zeromq-based messaging specification.
2 2 """
3 3 #-----------------------------------------------------------------------------
4 4 # Copyright (C) 2010-2011 The IPython Development Team
5 5 #
6 6 # Distributed under the terms of the BSD License. The full license is in
7 7 # the file COPYING.txt, distributed as part of this software.
8 8 #-----------------------------------------------------------------------------
9 9
10 10 import re
11 11 import sys
12 12 import time
13 13 from subprocess import PIPE
14 14 from Queue import Empty
15 15
16 16 import nose.tools as nt
17 17
18 18 from ..blockingkernelmanager import BlockingKernelManager
19 19
20 20
21 21 from IPython.testing import decorators as dec
22 22 from IPython.utils import io
23 23 from IPython.utils.traitlets import (
24 24 HasTraits, TraitError, Bool, Unicode, Dict, Integer, List, Enum, Any,
25 25 )
26 26
27 27 #-----------------------------------------------------------------------------
28 28 # Global setup and utilities
29 29 #-----------------------------------------------------------------------------
30 30
31 31 def setup():
32 32 global KM
33 33 KM = BlockingKernelManager()
34 34
35 35 KM.start_kernel(stdout=PIPE, stderr=PIPE)
36 36 KM.start_channels()
37 37
38 38 # wait for kernel to be ready
39 39 KM.shell_channel.execute("pass")
40 40 KM.shell_channel.get_msg(block=True, timeout=5)
41 41 flush_channels()
42 42
43 43
44 44 def teardown():
45 45 KM.stop_channels()
46 46 KM.shutdown_kernel()
47 47
48 48
49 49 def flush_channels():
50 50 """flush any messages waiting on the queue"""
51 for channel in (KM.shell_channel, KM.sub_channel):
51 for channel in (KM.shell_channel, KM.iopub_channel):
52 52 while True:
53 53 try:
54 54 msg = channel.get_msg(block=True, timeout=0.1)
55 55 except Empty:
56 56 break
57 57 else:
58 58 list(validate_message(msg))
59 59
60 60
61 61 def execute(code='', **kwargs):
62 62 """wrapper for doing common steps for validating an execution request"""
63 63 shell = KM.shell_channel
64 sub = KM.sub_channel
64 sub = KM.iopub_channel
65 65
66 66 msg_id = shell.execute(code=code, **kwargs)
67 67 reply = shell.get_msg(timeout=2)
68 68 list(validate_message(reply, 'execute_reply', msg_id))
69 69 busy = sub.get_msg(timeout=2)
70 70 list(validate_message(busy, 'status', msg_id))
71 71 nt.assert_equal(busy['content']['execution_state'], 'busy')
72 72
73 73 if not kwargs.get('silent'):
74 74 pyin = sub.get_msg(timeout=2)
75 75 list(validate_message(pyin, 'pyin', msg_id))
76 76 nt.assert_equal(pyin['content']['code'], code)
77 77
78 78 return msg_id, reply['content']
79 79
80 80 #-----------------------------------------------------------------------------
81 81 # MSG Spec References
82 82 #-----------------------------------------------------------------------------
83 83
84 84
85 85 class Reference(HasTraits):
86 86
87 87 """
88 88 Base class for message spec specification testing.
89 89
90 90 This class is the core of the message specification test. The
91 91 idea is that child classes implement trait attributes for each
92 92 message keys, so that message keys can be tested against these
93 93 traits using :meth:`check` method.
94 94
95 95 """
96 96
97 97 def check(self, d):
98 98 """validate a dict against our traits"""
99 99 for key in self.trait_names():
100 100 yield nt.assert_true(key in d, "Missing key: %r, should be found in %s" % (key, d))
101 101 # FIXME: always allow None, probably not a good idea
102 102 if d[key] is None:
103 103 continue
104 104 try:
105 105 setattr(self, key, d[key])
106 106 except TraitError as e:
107 107 yield nt.assert_true(False, str(e))
108 108
109 109
110 110 class RMessage(Reference):
111 111 msg_id = Unicode()
112 112 msg_type = Unicode()
113 113 header = Dict()
114 114 parent_header = Dict()
115 115 content = Dict()
116 116
117 117 class RHeader(Reference):
118 118 msg_id = Unicode()
119 119 msg_type = Unicode()
120 120 session = Unicode()
121 121 username = Unicode()
122 122
123 123 class RContent(Reference):
124 124 status = Enum((u'ok', u'error'))
125 125
126 126
127 127 class ExecuteReply(Reference):
128 128 execution_count = Integer()
129 129 status = Enum((u'ok', u'error'))
130 130
131 131 def check(self, d):
132 132 for tst in Reference.check(self, d):
133 133 yield tst
134 134 if d['status'] == 'ok':
135 135 for tst in ExecuteReplyOkay().check(d):
136 136 yield tst
137 137 elif d['status'] == 'error':
138 138 for tst in ExecuteReplyError().check(d):
139 139 yield tst
140 140
141 141
142 142 class ExecuteReplyOkay(Reference):
143 143 payload = List(Dict)
144 144 user_variables = Dict()
145 145 user_expressions = Dict()
146 146
147 147
148 148 class ExecuteReplyError(Reference):
149 149 ename = Unicode()
150 150 evalue = Unicode()
151 151 traceback = List(Unicode)
152 152
153 153
154 154 class OInfoReply(Reference):
155 155 name = Unicode()
156 156 found = Bool()
157 157 ismagic = Bool()
158 158 isalias = Bool()
159 159 namespace = Enum((u'builtin', u'magics', u'alias', u'Interactive'))
160 160 type_name = Unicode()
161 161 string_form = Unicode()
162 162 base_class = Unicode()
163 163 length = Integer()
164 164 file = Unicode()
165 165 definition = Unicode()
166 166 argspec = Dict()
167 167 init_definition = Unicode()
168 168 docstring = Unicode()
169 169 init_docstring = Unicode()
170 170 class_docstring = Unicode()
171 171 call_def = Unicode()
172 172 call_docstring = Unicode()
173 173 source = Unicode()
174 174
175 175 def check(self, d):
176 176 for tst in Reference.check(self, d):
177 177 yield tst
178 178 if d['argspec'] is not None:
179 179 for tst in ArgSpec().check(d['argspec']):
180 180 yield tst
181 181
182 182
183 183 class ArgSpec(Reference):
184 184 args = List(Unicode)
185 185 varargs = Unicode()
186 186 varkw = Unicode()
187 187 defaults = List()
188 188
189 189
190 190 class Status(Reference):
191 191 execution_state = Enum((u'busy', u'idle'))
192 192
193 193
194 194 class CompleteReply(Reference):
195 195 matches = List(Unicode)
196 196
197 197
198 198 def Version(num, trait=Integer):
199 199 return List(trait, default_value=[0] * num, minlen=num, maxlen=num)
200 200
201 201
202 202 class KernelInfoReply(Reference):
203 203
204 204 protocol_version = Version(2)
205 205 ipython_version = Version(4, Any)
206 206 language_version = Version(3)
207 207 language = Unicode()
208 208
209 209 def _ipython_version_changed(self, name, old, new):
210 210 for v in new:
211 211 nt.assert_true(
212 212 isinstance(v, int) or isinstance(v, basestring),
213 213 'expected int or string as version component, got {0!r}'
214 214 .format(v))
215 215
216 216
217 217 # IOPub messages
218 218
219 219 class PyIn(Reference):
220 220 code = Unicode()
221 221 execution_count = Integer()
222 222
223 223
224 224 PyErr = ExecuteReplyError
225 225
226 226
227 227 class Stream(Reference):
228 228 name = Enum((u'stdout', u'stderr'))
229 229 data = Unicode()
230 230
231 231
232 232 mime_pat = re.compile(r'\w+/\w+')
233 233
234 234 class DisplayData(Reference):
235 235 source = Unicode()
236 236 metadata = Dict()
237 237 data = Dict()
238 238 def _data_changed(self, name, old, new):
239 239 for k,v in new.iteritems():
240 240 nt.assert_true(mime_pat.match(k))
241 241 nt.assert_true(isinstance(v, basestring), "expected string data, got %r" % v)
242 242
243 243
244 244 class PyOut(Reference):
245 245 execution_count = Integer()
246 246 data = Dict()
247 247 def _data_changed(self, name, old, new):
248 248 for k,v in new.iteritems():
249 249 nt.assert_true(mime_pat.match(k))
250 250 nt.assert_true(isinstance(v, basestring), "expected string data, got %r" % v)
251 251
252 252
253 253 references = {
254 254 'execute_reply' : ExecuteReply(),
255 255 'object_info_reply' : OInfoReply(),
256 256 'status' : Status(),
257 257 'complete_reply' : CompleteReply(),
258 258 'kernel_info_reply': KernelInfoReply(),
259 259 'pyin' : PyIn(),
260 260 'pyout' : PyOut(),
261 261 'pyerr' : PyErr(),
262 262 'stream' : Stream(),
263 263 'display_data' : DisplayData(),
264 264 }
265 265 """
266 266 Specifications of `content` part of the reply messages.
267 267 """
268 268
269 269
270 270 def validate_message(msg, msg_type=None, parent=None):
271 271 """validate a message
272 272
273 273 This is a generator, and must be iterated through to actually
274 274 trigger each test.
275 275
276 276 If msg_type and/or parent are given, the msg_type and/or parent msg_id
277 277 are compared with the given values.
278 278 """
279 279 RMessage().check(msg)
280 280 if msg_type:
281 281 yield nt.assert_equal(msg['msg_type'], msg_type)
282 282 if parent:
283 283 yield nt.assert_equal(msg['parent_header']['msg_id'], parent)
284 284 content = msg['content']
285 285 ref = references[msg['msg_type']]
286 286 for tst in ref.check(content):
287 287 yield tst
288 288
289 289
290 290 #-----------------------------------------------------------------------------
291 291 # Tests
292 292 #-----------------------------------------------------------------------------
293 293
294 294 # Shell channel
295 295
296 296 @dec.parametric
297 297 def test_execute():
298 298 flush_channels()
299 299
300 300 shell = KM.shell_channel
301 301 msg_id = shell.execute(code='x=1')
302 302 reply = shell.get_msg(timeout=2)
303 303 for tst in validate_message(reply, 'execute_reply', msg_id):
304 304 yield tst
305 305
306 306
307 307 @dec.parametric
308 308 def test_execute_silent():
309 309 flush_channels()
310 310 msg_id, reply = execute(code='x=1', silent=True)
311 311
312 312 # flush status=idle
313 status = KM.sub_channel.get_msg(timeout=2)
313 status = KM.iopub_channel.get_msg(timeout=2)
314 314 for tst in validate_message(status, 'status', msg_id):
315 315 yield tst
316 316 nt.assert_equal(status['content']['execution_state'], 'idle')
317 317
318 yield nt.assert_raises(Empty, KM.sub_channel.get_msg, timeout=0.1)
318 yield nt.assert_raises(Empty, KM.iopub_channel.get_msg, timeout=0.1)
319 319 count = reply['execution_count']
320 320
321 321 msg_id, reply = execute(code='x=2', silent=True)
322 322
323 323 # flush status=idle
324 status = KM.sub_channel.get_msg(timeout=2)
324 status = KM.iopub_channel.get_msg(timeout=2)
325 325 for tst in validate_message(status, 'status', msg_id):
326 326 yield tst
327 327 yield nt.assert_equal(status['content']['execution_state'], 'idle')
328 328
329 yield nt.assert_raises(Empty, KM.sub_channel.get_msg, timeout=0.1)
329 yield nt.assert_raises(Empty, KM.iopub_channel.get_msg, timeout=0.1)
330 330 count_2 = reply['execution_count']
331 331 yield nt.assert_equal(count_2, count)
332 332
333 333
334 334 @dec.parametric
335 335 def test_execute_error():
336 336 flush_channels()
337 337
338 338 msg_id, reply = execute(code='1/0')
339 339 yield nt.assert_equal(reply['status'], 'error')
340 340 yield nt.assert_equal(reply['ename'], 'ZeroDivisionError')
341 341
342 pyerr = KM.sub_channel.get_msg(timeout=2)
342 pyerr = KM.iopub_channel.get_msg(timeout=2)
343 343 for tst in validate_message(pyerr, 'pyerr', msg_id):
344 344 yield tst
345 345
346 346
347 347 def test_execute_inc():
348 348 """execute request should increment execution_count"""
349 349 flush_channels()
350 350
351 351 msg_id, reply = execute(code='x=1')
352 352 count = reply['execution_count']
353 353
354 354 flush_channels()
355 355
356 356 msg_id, reply = execute(code='x=2')
357 357 count_2 = reply['execution_count']
358 358 nt.assert_equal(count_2, count+1)
359 359
360 360
361 361 def test_user_variables():
362 362 flush_channels()
363 363
364 364 msg_id, reply = execute(code='x=1', user_variables=['x'])
365 365 user_variables = reply['user_variables']
366 366 nt.assert_equal(user_variables, {u'x' : u'1'})
367 367
368 368
369 369 def test_user_expressions():
370 370 flush_channels()
371 371
372 372 msg_id, reply = execute(code='x=1', user_expressions=dict(foo='x+1'))
373 373 user_expressions = reply['user_expressions']
374 374 nt.assert_equal(user_expressions, {u'foo' : u'2'})
375 375
376 376
377 377 @dec.parametric
378 378 def test_oinfo():
379 379 flush_channels()
380 380
381 381 shell = KM.shell_channel
382 382
383 383 msg_id = shell.object_info('a')
384 384 reply = shell.get_msg(timeout=2)
385 385 for tst in validate_message(reply, 'object_info_reply', msg_id):
386 386 yield tst
387 387
388 388
389 389 @dec.parametric
390 390 def test_oinfo_found():
391 391 flush_channels()
392 392
393 393 shell = KM.shell_channel
394 394
395 395 msg_id, reply = execute(code='a=5')
396 396
397 397 msg_id = shell.object_info('a')
398 398 reply = shell.get_msg(timeout=2)
399 399 for tst in validate_message(reply, 'object_info_reply', msg_id):
400 400 yield tst
401 401 content = reply['content']
402 402 yield nt.assert_true(content['found'])
403 403 argspec = content['argspec']
404 404 yield nt.assert_true(argspec is None, "didn't expect argspec dict, got %r" % argspec)
405 405
406 406
407 407 @dec.parametric
408 408 def test_oinfo_detail():
409 409 flush_channels()
410 410
411 411 shell = KM.shell_channel
412 412
413 413 msg_id, reply = execute(code='ip=get_ipython()')
414 414
415 415 msg_id = shell.object_info('ip.object_inspect', detail_level=2)
416 416 reply = shell.get_msg(timeout=2)
417 417 for tst in validate_message(reply, 'object_info_reply', msg_id):
418 418 yield tst
419 419 content = reply['content']
420 420 yield nt.assert_true(content['found'])
421 421 argspec = content['argspec']
422 422 yield nt.assert_true(isinstance(argspec, dict), "expected non-empty argspec dict, got %r" % argspec)
423 423 yield nt.assert_equal(argspec['defaults'], [0])
424 424
425 425
426 426 @dec.parametric
427 427 def test_oinfo_not_found():
428 428 flush_channels()
429 429
430 430 shell = KM.shell_channel
431 431
432 432 msg_id = shell.object_info('dne')
433 433 reply = shell.get_msg(timeout=2)
434 434 for tst in validate_message(reply, 'object_info_reply', msg_id):
435 435 yield tst
436 436 content = reply['content']
437 437 yield nt.assert_false(content['found'])
438 438
439 439
440 440 @dec.parametric
441 441 def test_complete():
442 442 flush_channels()
443 443
444 444 shell = KM.shell_channel
445 445
446 446 msg_id, reply = execute(code="alpha = albert = 5")
447 447
448 448 msg_id = shell.complete('al', 'al', 2)
449 449 reply = shell.get_msg(timeout=2)
450 450 for tst in validate_message(reply, 'complete_reply', msg_id):
451 451 yield tst
452 452 matches = reply['content']['matches']
453 453 for name in ('alpha', 'albert'):
454 454 yield nt.assert_true(name in matches, "Missing match: %r" % name)
455 455
456 456
457 457 @dec.parametric
458 458 def test_kernel_info_request():
459 459 flush_channels()
460 460
461 461 shell = KM.shell_channel
462 462
463 463 msg_id = shell.kernel_info()
464 464 reply = shell.get_msg(timeout=2)
465 465 for tst in validate_message(reply, 'kernel_info_reply', msg_id):
466 466 yield tst
467 467
468 468
469 469 # IOPub channel
470 470
471 471
472 472 @dec.parametric
473 473 def test_stream():
474 474 flush_channels()
475 475
476 476 msg_id, reply = execute("print('hi')")
477 477
478 stdout = KM.sub_channel.get_msg(timeout=2)
478 stdout = KM.iopub_channel.get_msg(timeout=2)
479 479 for tst in validate_message(stdout, 'stream', msg_id):
480 480 yield tst
481 481 content = stdout['content']
482 482 yield nt.assert_equal(content['name'], u'stdout')
483 483 yield nt.assert_equal(content['data'], u'hi\n')
484 484
485 485
486 486 @dec.parametric
487 487 def test_display_data():
488 488 flush_channels()
489 489
490 490 msg_id, reply = execute("from IPython.core.display import display; display(1)")
491 491
492 display = KM.sub_channel.get_msg(timeout=2)
492 display = KM.iopub_channel.get_msg(timeout=2)
493 493 for tst in validate_message(display, 'display_data', parent=msg_id):
494 494 yield tst
495 495 data = display['content']['data']
496 496 yield nt.assert_equal(data['text/plain'], u'1')
497 497
General Comments 0
You need to be logged in to leave comments. Login now