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