##// END OF EJS Templates
Kernel event loop is robust against random SIGINT.
Thomas Kluyver -
Show More
@@ -1,685 +1,689 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 """A simple interactive kernel that talks to a frontend over 0MQ.
2 """A simple interactive kernel that talks to a frontend over 0MQ.
3
3
4 Things to do:
4 Things to do:
5
5
6 * Implement `set_parent` logic. Right before doing exec, the Kernel should
6 * Implement `set_parent` logic. Right before doing exec, the Kernel should
7 call set_parent on all the PUB objects with the message about to be executed.
7 call set_parent on all the PUB objects with the message about to be executed.
8 * Implement random port and security key logic.
8 * Implement random port and security key logic.
9 * Implement control messages.
9 * Implement control messages.
10 * Implement event loop and poll version.
10 * Implement event loop and poll version.
11 """
11 """
12
12
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14 # Imports
14 # Imports
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 from __future__ import print_function
16 from __future__ import print_function
17
17
18 # Standard library imports.
18 # Standard library imports.
19 import __builtin__
19 import __builtin__
20 import atexit
20 import atexit
21 import sys
21 import sys
22 import time
22 import time
23 import traceback
23 import traceback
24 import logging
24 import logging
25 # System library imports.
25 # System library imports.
26 import zmq
26 import zmq
27
27
28 # Local imports.
28 # Local imports.
29 from IPython.config.configurable import Configurable
29 from IPython.config.configurable import Configurable
30 from IPython.utils import io
30 from IPython.utils import io
31 from IPython.utils.jsonutil import json_clean
31 from IPython.utils.jsonutil import json_clean
32 from IPython.lib import pylabtools
32 from IPython.lib import pylabtools
33 from IPython.utils.traitlets import Instance, Float
33 from IPython.utils.traitlets import Instance, Float
34 from entry_point import (base_launch_kernel, make_argument_parser, make_kernel,
34 from entry_point import (base_launch_kernel, make_argument_parser, make_kernel,
35 start_kernel)
35 start_kernel)
36 from iostream import OutStream
36 from iostream import OutStream
37 from session import Session, Message
37 from session import Session, Message
38 from zmqshell import ZMQInteractiveShell
38 from zmqshell import ZMQInteractiveShell
39
39
40 #-----------------------------------------------------------------------------
40 #-----------------------------------------------------------------------------
41 # Globals
41 # Globals
42 #-----------------------------------------------------------------------------
42 #-----------------------------------------------------------------------------
43
43
44 # Module-level logger
44 # Module-level logger
45 logger = logging.getLogger(__name__)
45 logger = logging.getLogger(__name__)
46
46
47 # FIXME: this needs to be done more cleanly later, once we have proper
47 # FIXME: this needs to be done more cleanly later, once we have proper
48 # configuration support. This is a library, so it shouldn't set a stream
48 # configuration support. This is a library, so it shouldn't set a stream
49 # handler, see:
49 # handler, see:
50 # http://docs.python.org/library/logging.html#configuring-logging-for-a-library
50 # http://docs.python.org/library/logging.html#configuring-logging-for-a-library
51 # But this lets us at least do developer debugging for now by manually turning
51 # But this lets us at least do developer debugging for now by manually turning
52 # it on/off. And once we have full config support, the client entry points
52 # it on/off. And once we have full config support, the client entry points
53 # will select their logging handlers, as well as passing to this library the
53 # will select their logging handlers, as well as passing to this library the
54 # logging level.
54 # logging level.
55
55
56 if 0: # dbg - set to 1 to actually see the messages.
56 if 0: # dbg - set to 1 to actually see the messages.
57 logger.addHandler(logging.StreamHandler())
57 logger.addHandler(logging.StreamHandler())
58 logger.setLevel(logging.DEBUG)
58 logger.setLevel(logging.DEBUG)
59
59
60 # /FIXME
60 # /FIXME
61
61
62 #-----------------------------------------------------------------------------
62 #-----------------------------------------------------------------------------
63 # Main kernel class
63 # Main kernel class
64 #-----------------------------------------------------------------------------
64 #-----------------------------------------------------------------------------
65
65
66 class Kernel(Configurable):
66 class Kernel(Configurable):
67
67
68 #---------------------------------------------------------------------------
68 #---------------------------------------------------------------------------
69 # Kernel interface
69 # Kernel interface
70 #---------------------------------------------------------------------------
70 #---------------------------------------------------------------------------
71
71
72 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
72 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
73 session = Instance(Session)
73 session = Instance(Session)
74 reply_socket = Instance('zmq.Socket')
74 reply_socket = Instance('zmq.Socket')
75 pub_socket = Instance('zmq.Socket')
75 pub_socket = Instance('zmq.Socket')
76 req_socket = Instance('zmq.Socket')
76 req_socket = Instance('zmq.Socket')
77
77
78 # Private interface
78 # Private interface
79
79
80 # Time to sleep after flushing the stdout/err buffers in each execute
80 # Time to sleep after flushing the stdout/err buffers in each execute
81 # cycle. While this introduces a hard limit on the minimal latency of the
81 # cycle. While this introduces a hard limit on the minimal latency of the
82 # execute cycle, it helps prevent output synchronization problems for
82 # execute cycle, it helps prevent output synchronization problems for
83 # clients.
83 # clients.
84 # Units are in seconds. The minimum zmq latency on local host is probably
84 # Units are in seconds. The minimum zmq latency on local host is probably
85 # ~150 microseconds, set this to 500us for now. We may need to increase it
85 # ~150 microseconds, set this to 500us for now. We may need to increase it
86 # a little if it's not enough after more interactive testing.
86 # a little if it's not enough after more interactive testing.
87 _execute_sleep = Float(0.0005, config=True)
87 _execute_sleep = Float(0.0005, config=True)
88
88
89 # Frequency of the kernel's event loop.
89 # Frequency of the kernel's event loop.
90 # Units are in seconds, kernel subclasses for GUI toolkits may need to
90 # Units are in seconds, kernel subclasses for GUI toolkits may need to
91 # adapt to milliseconds.
91 # adapt to milliseconds.
92 _poll_interval = Float(0.05, config=True)
92 _poll_interval = Float(0.05, config=True)
93
93
94 # If the shutdown was requested over the network, we leave here the
94 # If the shutdown was requested over the network, we leave here the
95 # necessary reply message so it can be sent by our registered atexit
95 # necessary reply message so it can be sent by our registered atexit
96 # handler. This ensures that the reply is only sent to clients truly at
96 # handler. This ensures that the reply is only sent to clients truly at
97 # the end of our shutdown process (which happens after the underlying
97 # the end of our shutdown process (which happens after the underlying
98 # IPython shell's own shutdown).
98 # IPython shell's own shutdown).
99 _shutdown_message = None
99 _shutdown_message = None
100
100
101 # This is a dict of port number that the kernel is listening on. It is set
101 # This is a dict of port number that the kernel is listening on. It is set
102 # by record_ports and used by connect_request.
102 # by record_ports and used by connect_request.
103 _recorded_ports = None
103 _recorded_ports = None
104
104
105
105
106 def __init__(self, **kwargs):
106 def __init__(self, **kwargs):
107 super(Kernel, self).__init__(**kwargs)
107 super(Kernel, self).__init__(**kwargs)
108
108
109 # Before we even start up the shell, register *first* our exit handlers
109 # Before we even start up the shell, register *first* our exit handlers
110 # so they come before the shell's
110 # so they come before the shell's
111 atexit.register(self._at_shutdown)
111 atexit.register(self._at_shutdown)
112
112
113 # Initialize the InteractiveShell subclass
113 # Initialize the InteractiveShell subclass
114 self.shell = ZMQInteractiveShell.instance()
114 self.shell = ZMQInteractiveShell.instance()
115 self.shell.displayhook.session = self.session
115 self.shell.displayhook.session = self.session
116 self.shell.displayhook.pub_socket = self.pub_socket
116 self.shell.displayhook.pub_socket = self.pub_socket
117 self.shell.display_pub.session = self.session
117 self.shell.display_pub.session = self.session
118 self.shell.display_pub.pub_socket = self.pub_socket
118 self.shell.display_pub.pub_socket = self.pub_socket
119
119
120 # TMP - hack while developing
120 # TMP - hack while developing
121 self.shell._reply_content = None
121 self.shell._reply_content = None
122
122
123 # Build dict of handlers for message types
123 # Build dict of handlers for message types
124 msg_types = [ 'execute_request', 'complete_request',
124 msg_types = [ 'execute_request', 'complete_request',
125 'object_info_request', 'history_request',
125 'object_info_request', 'history_request',
126 'connect_request', 'shutdown_request']
126 'connect_request', 'shutdown_request']
127 self.handlers = {}
127 self.handlers = {}
128 for msg_type in msg_types:
128 for msg_type in msg_types:
129 self.handlers[msg_type] = getattr(self, msg_type)
129 self.handlers[msg_type] = getattr(self, msg_type)
130
130
131 def do_one_iteration(self):
131 def do_one_iteration(self):
132 """Do one iteration of the kernel's evaluation loop.
132 """Do one iteration of the kernel's evaluation loop.
133 """
133 """
134 ident,msg = self.session.recv(self.reply_socket, zmq.NOBLOCK)
134 ident,msg = self.session.recv(self.reply_socket, zmq.NOBLOCK)
135 if msg is None:
135 if msg is None:
136 return
136 return
137
137
138 # This assert will raise in versions of zeromq 2.0.7 and lesser.
138 # This assert will raise in versions of zeromq 2.0.7 and lesser.
139 # We now require 2.0.8 or above, so we can uncomment for safety.
139 # We now require 2.0.8 or above, so we can uncomment for safety.
140 # print(ident,msg, file=sys.__stdout__)
140 # print(ident,msg, file=sys.__stdout__)
141 assert ident is not None, "Missing message part."
141 assert ident is not None, "Missing message part."
142
142
143 # Print some info about this message and leave a '--->' marker, so it's
143 # Print some info about this message and leave a '--->' marker, so it's
144 # easier to trace visually the message chain when debugging. Each
144 # easier to trace visually the message chain when debugging. Each
145 # handler prints its message at the end.
145 # handler prints its message at the end.
146 # Eventually we'll move these from stdout to a logger.
146 # Eventually we'll move these from stdout to a logger.
147 logger.debug('\n*** MESSAGE TYPE:'+str(msg['msg_type'])+'***')
147 logger.debug('\n*** MESSAGE TYPE:'+str(msg['msg_type'])+'***')
148 logger.debug(' Content: '+str(msg['content'])+'\n --->\n ')
148 logger.debug(' Content: '+str(msg['content'])+'\n --->\n ')
149
149
150 # Find and call actual handler for message
150 # Find and call actual handler for message
151 handler = self.handlers.get(msg['msg_type'], None)
151 handler = self.handlers.get(msg['msg_type'], None)
152 if handler is None:
152 if handler is None:
153 logger.error("UNKNOWN MESSAGE TYPE:" +str(msg))
153 logger.error("UNKNOWN MESSAGE TYPE:" +str(msg))
154 else:
154 else:
155 handler(ident, msg)
155 handler(ident, msg)
156
156
157 # Check whether we should exit, in case the incoming message set the
157 # Check whether we should exit, in case the incoming message set the
158 # exit flag on
158 # exit flag on
159 if self.shell.exit_now:
159 if self.shell.exit_now:
160 logger.debug('\nExiting IPython kernel...')
160 logger.debug('\nExiting IPython kernel...')
161 # We do a normal, clean exit, which allows any actions registered
161 # We do a normal, clean exit, which allows any actions registered
162 # via atexit (such as history saving) to take place.
162 # via atexit (such as history saving) to take place.
163 sys.exit(0)
163 sys.exit(0)
164
164
165
165
166 def start(self):
166 def start(self):
167 """ Start the kernel main loop.
167 """ Start the kernel main loop.
168 """
168 """
169 while True:
169 while True:
170 time.sleep(self._poll_interval)
170 try:
171 self.do_one_iteration()
171 time.sleep(self._poll_interval)
172 self.do_one_iteration()
173 except KeyboardInterrupt:
174 # Ctrl-C shouldn't crash the kernel
175 continue
172
176
173 def record_ports(self, xrep_port, pub_port, req_port, hb_port):
177 def record_ports(self, xrep_port, pub_port, req_port, hb_port):
174 """Record the ports that this kernel is using.
178 """Record the ports that this kernel is using.
175
179
176 The creator of the Kernel instance must call this methods if they
180 The creator of the Kernel instance must call this methods if they
177 want the :meth:`connect_request` method to return the port numbers.
181 want the :meth:`connect_request` method to return the port numbers.
178 """
182 """
179 self._recorded_ports = {
183 self._recorded_ports = {
180 'xrep_port' : xrep_port,
184 'xrep_port' : xrep_port,
181 'pub_port' : pub_port,
185 'pub_port' : pub_port,
182 'req_port' : req_port,
186 'req_port' : req_port,
183 'hb_port' : hb_port
187 'hb_port' : hb_port
184 }
188 }
185
189
186 #---------------------------------------------------------------------------
190 #---------------------------------------------------------------------------
187 # Kernel request handlers
191 # Kernel request handlers
188 #---------------------------------------------------------------------------
192 #---------------------------------------------------------------------------
189
193
190 def _publish_pyin(self, code, parent):
194 def _publish_pyin(self, code, parent):
191 """Publish the code request on the pyin stream."""
195 """Publish the code request on the pyin stream."""
192
196
193 pyin_msg = self.session.send(self.pub_socket, u'pyin',{u'code':code}, parent=parent)
197 pyin_msg = self.session.send(self.pub_socket, u'pyin',{u'code':code}, parent=parent)
194
198
195 def execute_request(self, ident, parent):
199 def execute_request(self, ident, parent):
196
200
197 status_msg = self.session.send(self.pub_socket,
201 status_msg = self.session.send(self.pub_socket,
198 u'status',
202 u'status',
199 {u'execution_state':u'busy'},
203 {u'execution_state':u'busy'},
200 parent=parent
204 parent=parent
201 )
205 )
202
206
203 try:
207 try:
204 content = parent[u'content']
208 content = parent[u'content']
205 code = content[u'code']
209 code = content[u'code']
206 silent = content[u'silent']
210 silent = content[u'silent']
207 except:
211 except:
208 logger.error("Got bad msg: ")
212 logger.error("Got bad msg: ")
209 logger.error(str(Message(parent)))
213 logger.error(str(Message(parent)))
210 return
214 return
211
215
212 shell = self.shell # we'll need this a lot here
216 shell = self.shell # we'll need this a lot here
213
217
214 # Replace raw_input. Note that is not sufficient to replace
218 # Replace raw_input. Note that is not sufficient to replace
215 # raw_input in the user namespace.
219 # raw_input in the user namespace.
216 raw_input = lambda prompt='': self._raw_input(prompt, ident, parent)
220 raw_input = lambda prompt='': self._raw_input(prompt, ident, parent)
217 __builtin__.raw_input = raw_input
221 __builtin__.raw_input = raw_input
218
222
219 # Set the parent message of the display hook and out streams.
223 # Set the parent message of the display hook and out streams.
220 shell.displayhook.set_parent(parent)
224 shell.displayhook.set_parent(parent)
221 shell.display_pub.set_parent(parent)
225 shell.display_pub.set_parent(parent)
222 sys.stdout.set_parent(parent)
226 sys.stdout.set_parent(parent)
223 sys.stderr.set_parent(parent)
227 sys.stderr.set_parent(parent)
224
228
225 # Re-broadcast our input for the benefit of listening clients, and
229 # Re-broadcast our input for the benefit of listening clients, and
226 # start computing output
230 # start computing output
227 if not silent:
231 if not silent:
228 self._publish_pyin(code, parent)
232 self._publish_pyin(code, parent)
229
233
230 reply_content = {}
234 reply_content = {}
231 try:
235 try:
232 if silent:
236 if silent:
233 # run_code uses 'exec' mode, so no displayhook will fire, and it
237 # run_code uses 'exec' mode, so no displayhook will fire, and it
234 # doesn't call logging or history manipulations. Print
238 # doesn't call logging or history manipulations. Print
235 # statements in that code will obviously still execute.
239 # statements in that code will obviously still execute.
236 shell.run_code(code)
240 shell.run_code(code)
237 else:
241 else:
238 # FIXME: the shell calls the exception handler itself.
242 # FIXME: the shell calls the exception handler itself.
239 shell.run_cell(code)
243 shell.run_cell(code)
240 except:
244 except:
241 status = u'error'
245 status = u'error'
242 # FIXME: this code right now isn't being used yet by default,
246 # FIXME: this code right now isn't being used yet by default,
243 # because the run_cell() call above directly fires off exception
247 # because the run_cell() call above directly fires off exception
244 # reporting. This code, therefore, is only active in the scenario
248 # reporting. This code, therefore, is only active in the scenario
245 # where runlines itself has an unhandled exception. We need to
249 # where runlines itself has an unhandled exception. We need to
246 # uniformize this, for all exception construction to come from a
250 # uniformize this, for all exception construction to come from a
247 # single location in the codbase.
251 # single location in the codbase.
248 etype, evalue, tb = sys.exc_info()
252 etype, evalue, tb = sys.exc_info()
249 tb_list = traceback.format_exception(etype, evalue, tb)
253 tb_list = traceback.format_exception(etype, evalue, tb)
250 reply_content.update(shell._showtraceback(etype, evalue, tb_list))
254 reply_content.update(shell._showtraceback(etype, evalue, tb_list))
251 else:
255 else:
252 status = u'ok'
256 status = u'ok'
253
257
254 reply_content[u'status'] = status
258 reply_content[u'status'] = status
255
259
256 # Return the execution counter so clients can display prompts
260 # Return the execution counter so clients can display prompts
257 reply_content['execution_count'] = shell.execution_count -1
261 reply_content['execution_count'] = shell.execution_count -1
258
262
259 # FIXME - fish exception info out of shell, possibly left there by
263 # FIXME - fish exception info out of shell, possibly left there by
260 # runlines. We'll need to clean up this logic later.
264 # runlines. We'll need to clean up this logic later.
261 if shell._reply_content is not None:
265 if shell._reply_content is not None:
262 reply_content.update(shell._reply_content)
266 reply_content.update(shell._reply_content)
263 # reset after use
267 # reset after use
264 shell._reply_content = None
268 shell._reply_content = None
265
269
266 # At this point, we can tell whether the main code execution succeeded
270 # At this point, we can tell whether the main code execution succeeded
267 # or not. If it did, we proceed to evaluate user_variables/expressions
271 # or not. If it did, we proceed to evaluate user_variables/expressions
268 if reply_content['status'] == 'ok':
272 if reply_content['status'] == 'ok':
269 reply_content[u'user_variables'] = \
273 reply_content[u'user_variables'] = \
270 shell.user_variables(content[u'user_variables'])
274 shell.user_variables(content[u'user_variables'])
271 reply_content[u'user_expressions'] = \
275 reply_content[u'user_expressions'] = \
272 shell.user_expressions(content[u'user_expressions'])
276 shell.user_expressions(content[u'user_expressions'])
273 else:
277 else:
274 # If there was an error, don't even try to compute variables or
278 # If there was an error, don't even try to compute variables or
275 # expressions
279 # expressions
276 reply_content[u'user_variables'] = {}
280 reply_content[u'user_variables'] = {}
277 reply_content[u'user_expressions'] = {}
281 reply_content[u'user_expressions'] = {}
278
282
279 # Payloads should be retrieved regardless of outcome, so we can both
283 # Payloads should be retrieved regardless of outcome, so we can both
280 # recover partial output (that could have been generated early in a
284 # recover partial output (that could have been generated early in a
281 # block, before an error) and clear the payload system always.
285 # block, before an error) and clear the payload system always.
282 reply_content[u'payload'] = shell.payload_manager.read_payload()
286 reply_content[u'payload'] = shell.payload_manager.read_payload()
283 # Be agressive about clearing the payload because we don't want
287 # Be agressive about clearing the payload because we don't want
284 # it to sit in memory until the next execute_request comes in.
288 # it to sit in memory until the next execute_request comes in.
285 shell.payload_manager.clear_payload()
289 shell.payload_manager.clear_payload()
286
290
287 # Flush output before sending the reply.
291 # Flush output before sending the reply.
288 sys.stdout.flush()
292 sys.stdout.flush()
289 sys.stderr.flush()
293 sys.stderr.flush()
290 # FIXME: on rare occasions, the flush doesn't seem to make it to the
294 # FIXME: on rare occasions, the flush doesn't seem to make it to the
291 # clients... This seems to mitigate the problem, but we definitely need
295 # clients... This seems to mitigate the problem, but we definitely need
292 # to better understand what's going on.
296 # to better understand what's going on.
293 if self._execute_sleep:
297 if self._execute_sleep:
294 time.sleep(self._execute_sleep)
298 time.sleep(self._execute_sleep)
295
299
296 # Send the reply.
300 # Send the reply.
297 reply_msg = self.session.send(self.reply_socket, u'execute_reply',
301 reply_msg = self.session.send(self.reply_socket, u'execute_reply',
298 reply_content, parent, ident=ident)
302 reply_content, parent, ident=ident)
299 logger.debug(str(reply_msg))
303 logger.debug(str(reply_msg))
300
304
301 if reply_msg['content']['status'] == u'error':
305 if reply_msg['content']['status'] == u'error':
302 self._abort_queue()
306 self._abort_queue()
303
307
304 status_msg = self.session.send(self.pub_socket,
308 status_msg = self.session.send(self.pub_socket,
305 u'status',
309 u'status',
306 {u'execution_state':u'idle'},
310 {u'execution_state':u'idle'},
307 parent=parent
311 parent=parent
308 )
312 )
309
313
310 def complete_request(self, ident, parent):
314 def complete_request(self, ident, parent):
311 txt, matches = self._complete(parent)
315 txt, matches = self._complete(parent)
312 matches = {'matches' : matches,
316 matches = {'matches' : matches,
313 'matched_text' : txt,
317 'matched_text' : txt,
314 'status' : 'ok'}
318 'status' : 'ok'}
315 completion_msg = self.session.send(self.reply_socket, 'complete_reply',
319 completion_msg = self.session.send(self.reply_socket, 'complete_reply',
316 matches, parent, ident)
320 matches, parent, ident)
317 logger.debug(str(completion_msg))
321 logger.debug(str(completion_msg))
318
322
319 def object_info_request(self, ident, parent):
323 def object_info_request(self, ident, parent):
320 object_info = self.shell.object_inspect(parent['content']['oname'])
324 object_info = self.shell.object_inspect(parent['content']['oname'])
321 # Before we send this object over, we scrub it for JSON usage
325 # Before we send this object over, we scrub it for JSON usage
322 oinfo = json_clean(object_info)
326 oinfo = json_clean(object_info)
323 msg = self.session.send(self.reply_socket, 'object_info_reply',
327 msg = self.session.send(self.reply_socket, 'object_info_reply',
324 oinfo, parent, ident)
328 oinfo, parent, ident)
325 logger.debug(msg)
329 logger.debug(msg)
326
330
327 def history_request(self, ident, parent):
331 def history_request(self, ident, parent):
328 # We need to pull these out, as passing **kwargs doesn't work with
332 # We need to pull these out, as passing **kwargs doesn't work with
329 # unicode keys before Python 2.6.5.
333 # unicode keys before Python 2.6.5.
330 hist_access_type = parent['content']['hist_access_type']
334 hist_access_type = parent['content']['hist_access_type']
331 raw = parent['content']['raw']
335 raw = parent['content']['raw']
332 output = parent['content']['output']
336 output = parent['content']['output']
333 if hist_access_type == 'tail':
337 if hist_access_type == 'tail':
334 n = parent['content']['n']
338 n = parent['content']['n']
335 hist = self.shell.history_manager.get_tail(n, raw=raw, output=output,
339 hist = self.shell.history_manager.get_tail(n, raw=raw, output=output,
336 include_latest=True)
340 include_latest=True)
337
341
338 elif hist_access_type == 'range':
342 elif hist_access_type == 'range':
339 session = parent['content']['session']
343 session = parent['content']['session']
340 start = parent['content']['start']
344 start = parent['content']['start']
341 stop = parent['content']['stop']
345 stop = parent['content']['stop']
342 hist = self.shell.history_manager.get_range(session, start, stop,
346 hist = self.shell.history_manager.get_range(session, start, stop,
343 raw=raw, output=output)
347 raw=raw, output=output)
344
348
345 elif hist_access_type == 'search':
349 elif hist_access_type == 'search':
346 pattern = parent['content']['pattern']
350 pattern = parent['content']['pattern']
347 hist = self.shell.history_manager.search(pattern, raw=raw, output=output)
351 hist = self.shell.history_manager.search(pattern, raw=raw, output=output)
348
352
349 else:
353 else:
350 hist = []
354 hist = []
351 content = {'history' : list(hist)}
355 content = {'history' : list(hist)}
352 msg = self.session.send(self.reply_socket, 'history_reply',
356 msg = self.session.send(self.reply_socket, 'history_reply',
353 content, parent, ident)
357 content, parent, ident)
354 logger.debug(str(msg))
358 logger.debug(str(msg))
355
359
356 def connect_request(self, ident, parent):
360 def connect_request(self, ident, parent):
357 if self._recorded_ports is not None:
361 if self._recorded_ports is not None:
358 content = self._recorded_ports.copy()
362 content = self._recorded_ports.copy()
359 else:
363 else:
360 content = {}
364 content = {}
361 msg = self.session.send(self.reply_socket, 'connect_reply',
365 msg = self.session.send(self.reply_socket, 'connect_reply',
362 content, parent, ident)
366 content, parent, ident)
363 logger.debug(msg)
367 logger.debug(msg)
364
368
365 def shutdown_request(self, ident, parent):
369 def shutdown_request(self, ident, parent):
366 self.shell.exit_now = True
370 self.shell.exit_now = True
367 self._shutdown_message = self.session.msg(u'shutdown_reply', parent['content'], parent)
371 self._shutdown_message = self.session.msg(u'shutdown_reply', parent['content'], parent)
368 sys.exit(0)
372 sys.exit(0)
369
373
370 #---------------------------------------------------------------------------
374 #---------------------------------------------------------------------------
371 # Protected interface
375 # Protected interface
372 #---------------------------------------------------------------------------
376 #---------------------------------------------------------------------------
373
377
374 def _abort_queue(self):
378 def _abort_queue(self):
375 while True:
379 while True:
376 ident,msg = self.session.recv(self.reply_socket, zmq.NOBLOCK)
380 ident,msg = self.session.recv(self.reply_socket, zmq.NOBLOCK)
377 if msg is None:
381 if msg is None:
378 break
382 break
379 else:
383 else:
380 assert ident is not None, \
384 assert ident is not None, \
381 "Unexpected missing message part."
385 "Unexpected missing message part."
382
386
383 logger.debug("Aborting:\n"+str(Message(msg)))
387 logger.debug("Aborting:\n"+str(Message(msg)))
384 msg_type = msg['msg_type']
388 msg_type = msg['msg_type']
385 reply_type = msg_type.split('_')[0] + '_reply'
389 reply_type = msg_type.split('_')[0] + '_reply'
386 reply_msg = self.session.send(self.reply_socket, reply_type,
390 reply_msg = self.session.send(self.reply_socket, reply_type,
387 {'status' : 'aborted'}, msg, ident=ident)
391 {'status' : 'aborted'}, msg, ident=ident)
388 logger.debug(reply_msg)
392 logger.debug(reply_msg)
389 # We need to wait a bit for requests to come in. This can probably
393 # We need to wait a bit for requests to come in. This can probably
390 # be set shorter for true asynchronous clients.
394 # be set shorter for true asynchronous clients.
391 time.sleep(0.1)
395 time.sleep(0.1)
392
396
393 def _raw_input(self, prompt, ident, parent):
397 def _raw_input(self, prompt, ident, parent):
394 # Flush output before making the request.
398 # Flush output before making the request.
395 sys.stderr.flush()
399 sys.stderr.flush()
396 sys.stdout.flush()
400 sys.stdout.flush()
397
401
398 # Send the input request.
402 # Send the input request.
399 content = dict(prompt=prompt)
403 content = dict(prompt=prompt)
400 msg = self.session.send(self.req_socket, u'input_request', content, parent)
404 msg = self.session.send(self.req_socket, u'input_request', content, parent)
401
405
402 # Await a response.
406 # Await a response.
403 ident, reply = self.session.recv(self.req_socket, 0)
407 ident, reply = self.session.recv(self.req_socket, 0)
404 try:
408 try:
405 value = reply['content']['value']
409 value = reply['content']['value']
406 except:
410 except:
407 logger.error("Got bad raw_input reply: ")
411 logger.error("Got bad raw_input reply: ")
408 logger.error(str(Message(parent)))
412 logger.error(str(Message(parent)))
409 value = ''
413 value = ''
410 return value
414 return value
411
415
412 def _complete(self, msg):
416 def _complete(self, msg):
413 c = msg['content']
417 c = msg['content']
414 try:
418 try:
415 cpos = int(c['cursor_pos'])
419 cpos = int(c['cursor_pos'])
416 except:
420 except:
417 # If we don't get something that we can convert to an integer, at
421 # If we don't get something that we can convert to an integer, at
418 # least attempt the completion guessing the cursor is at the end of
422 # least attempt the completion guessing the cursor is at the end of
419 # the text, if there's any, and otherwise of the line
423 # the text, if there's any, and otherwise of the line
420 cpos = len(c['text'])
424 cpos = len(c['text'])
421 if cpos==0:
425 if cpos==0:
422 cpos = len(c['line'])
426 cpos = len(c['line'])
423 return self.shell.complete(c['text'], c['line'], cpos)
427 return self.shell.complete(c['text'], c['line'], cpos)
424
428
425 def _object_info(self, context):
429 def _object_info(self, context):
426 symbol, leftover = self._symbol_from_context(context)
430 symbol, leftover = self._symbol_from_context(context)
427 if symbol is not None and not leftover:
431 if symbol is not None and not leftover:
428 doc = getattr(symbol, '__doc__', '')
432 doc = getattr(symbol, '__doc__', '')
429 else:
433 else:
430 doc = ''
434 doc = ''
431 object_info = dict(docstring = doc)
435 object_info = dict(docstring = doc)
432 return object_info
436 return object_info
433
437
434 def _symbol_from_context(self, context):
438 def _symbol_from_context(self, context):
435 if not context:
439 if not context:
436 return None, context
440 return None, context
437
441
438 base_symbol_string = context[0]
442 base_symbol_string = context[0]
439 symbol = self.shell.user_ns.get(base_symbol_string, None)
443 symbol = self.shell.user_ns.get(base_symbol_string, None)
440 if symbol is None:
444 if symbol is None:
441 symbol = __builtin__.__dict__.get(base_symbol_string, None)
445 symbol = __builtin__.__dict__.get(base_symbol_string, None)
442 if symbol is None:
446 if symbol is None:
443 return None, context
447 return None, context
444
448
445 context = context[1:]
449 context = context[1:]
446 for i, name in enumerate(context):
450 for i, name in enumerate(context):
447 new_symbol = getattr(symbol, name, None)
451 new_symbol = getattr(symbol, name, None)
448 if new_symbol is None:
452 if new_symbol is None:
449 return symbol, context[i:]
453 return symbol, context[i:]
450 else:
454 else:
451 symbol = new_symbol
455 symbol = new_symbol
452
456
453 return symbol, []
457 return symbol, []
454
458
455 def _at_shutdown(self):
459 def _at_shutdown(self):
456 """Actions taken at shutdown by the kernel, called by python's atexit.
460 """Actions taken at shutdown by the kernel, called by python's atexit.
457 """
461 """
458 # io.rprint("Kernel at_shutdown") # dbg
462 # io.rprint("Kernel at_shutdown") # dbg
459 if self._shutdown_message is not None:
463 if self._shutdown_message is not None:
460 self.session.send(self.reply_socket, self._shutdown_message)
464 self.session.send(self.reply_socket, self._shutdown_message)
461 self.session.send(self.pub_socket, self._shutdown_message)
465 self.session.send(self.pub_socket, self._shutdown_message)
462 logger.debug(str(self._shutdown_message))
466 logger.debug(str(self._shutdown_message))
463 # A very short sleep to give zmq time to flush its message buffers
467 # A very short sleep to give zmq time to flush its message buffers
464 # before Python truly shuts down.
468 # before Python truly shuts down.
465 time.sleep(0.01)
469 time.sleep(0.01)
466
470
467
471
468 class QtKernel(Kernel):
472 class QtKernel(Kernel):
469 """A Kernel subclass with Qt support."""
473 """A Kernel subclass with Qt support."""
470
474
471 def start(self):
475 def start(self):
472 """Start a kernel with QtPy4 event loop integration."""
476 """Start a kernel with QtPy4 event loop integration."""
473
477
474 from PyQt4 import QtCore
478 from PyQt4 import QtCore
475 from IPython.lib.guisupport import get_app_qt4, start_event_loop_qt4
479 from IPython.lib.guisupport import get_app_qt4, start_event_loop_qt4
476
480
477 self.app = get_app_qt4([" "])
481 self.app = get_app_qt4([" "])
478 self.app.setQuitOnLastWindowClosed(False)
482 self.app.setQuitOnLastWindowClosed(False)
479 self.timer = QtCore.QTimer()
483 self.timer = QtCore.QTimer()
480 self.timer.timeout.connect(self.do_one_iteration)
484 self.timer.timeout.connect(self.do_one_iteration)
481 # Units for the timer are in milliseconds
485 # Units for the timer are in milliseconds
482 self.timer.start(1000*self._poll_interval)
486 self.timer.start(1000*self._poll_interval)
483 start_event_loop_qt4(self.app)
487 start_event_loop_qt4(self.app)
484
488
485
489
486 class WxKernel(Kernel):
490 class WxKernel(Kernel):
487 """A Kernel subclass with Wx support."""
491 """A Kernel subclass with Wx support."""
488
492
489 def start(self):
493 def start(self):
490 """Start a kernel with wx event loop support."""
494 """Start a kernel with wx event loop support."""
491
495
492 import wx
496 import wx
493 from IPython.lib.guisupport import start_event_loop_wx
497 from IPython.lib.guisupport import start_event_loop_wx
494
498
495 doi = self.do_one_iteration
499 doi = self.do_one_iteration
496 # Wx uses milliseconds
500 # Wx uses milliseconds
497 poll_interval = int(1000*self._poll_interval)
501 poll_interval = int(1000*self._poll_interval)
498
502
499 # We have to put the wx.Timer in a wx.Frame for it to fire properly.
503 # We have to put the wx.Timer in a wx.Frame for it to fire properly.
500 # We make the Frame hidden when we create it in the main app below.
504 # We make the Frame hidden when we create it in the main app below.
501 class TimerFrame(wx.Frame):
505 class TimerFrame(wx.Frame):
502 def __init__(self, func):
506 def __init__(self, func):
503 wx.Frame.__init__(self, None, -1)
507 wx.Frame.__init__(self, None, -1)
504 self.timer = wx.Timer(self)
508 self.timer = wx.Timer(self)
505 # Units for the timer are in milliseconds
509 # Units for the timer are in milliseconds
506 self.timer.Start(poll_interval)
510 self.timer.Start(poll_interval)
507 self.Bind(wx.EVT_TIMER, self.on_timer)
511 self.Bind(wx.EVT_TIMER, self.on_timer)
508 self.func = func
512 self.func = func
509
513
510 def on_timer(self, event):
514 def on_timer(self, event):
511 self.func()
515 self.func()
512
516
513 # We need a custom wx.App to create our Frame subclass that has the
517 # We need a custom wx.App to create our Frame subclass that has the
514 # wx.Timer to drive the ZMQ event loop.
518 # wx.Timer to drive the ZMQ event loop.
515 class IPWxApp(wx.App):
519 class IPWxApp(wx.App):
516 def OnInit(self):
520 def OnInit(self):
517 self.frame = TimerFrame(doi)
521 self.frame = TimerFrame(doi)
518 self.frame.Show(False)
522 self.frame.Show(False)
519 return True
523 return True
520
524
521 # The redirect=False here makes sure that wx doesn't replace
525 # The redirect=False here makes sure that wx doesn't replace
522 # sys.stdout/stderr with its own classes.
526 # sys.stdout/stderr with its own classes.
523 self.app = IPWxApp(redirect=False)
527 self.app = IPWxApp(redirect=False)
524 start_event_loop_wx(self.app)
528 start_event_loop_wx(self.app)
525
529
526
530
527 class TkKernel(Kernel):
531 class TkKernel(Kernel):
528 """A Kernel subclass with Tk support."""
532 """A Kernel subclass with Tk support."""
529
533
530 def start(self):
534 def start(self):
531 """Start a Tk enabled event loop."""
535 """Start a Tk enabled event loop."""
532
536
533 import Tkinter
537 import Tkinter
534 doi = self.do_one_iteration
538 doi = self.do_one_iteration
535 # Tk uses milliseconds
539 # Tk uses milliseconds
536 poll_interval = int(1000*self._poll_interval)
540 poll_interval = int(1000*self._poll_interval)
537 # For Tkinter, we create a Tk object and call its withdraw method.
541 # For Tkinter, we create a Tk object and call its withdraw method.
538 class Timer(object):
542 class Timer(object):
539 def __init__(self, func):
543 def __init__(self, func):
540 self.app = Tkinter.Tk()
544 self.app = Tkinter.Tk()
541 self.app.withdraw()
545 self.app.withdraw()
542 self.func = func
546 self.func = func
543
547
544 def on_timer(self):
548 def on_timer(self):
545 self.func()
549 self.func()
546 self.app.after(poll_interval, self.on_timer)
550 self.app.after(poll_interval, self.on_timer)
547
551
548 def start(self):
552 def start(self):
549 self.on_timer() # Call it once to get things going.
553 self.on_timer() # Call it once to get things going.
550 self.app.mainloop()
554 self.app.mainloop()
551
555
552 self.timer = Timer(doi)
556 self.timer = Timer(doi)
553 self.timer.start()
557 self.timer.start()
554
558
555
559
556 class GTKKernel(Kernel):
560 class GTKKernel(Kernel):
557 """A Kernel subclass with GTK support."""
561 """A Kernel subclass with GTK support."""
558
562
559 def start(self):
563 def start(self):
560 """Start the kernel, coordinating with the GTK event loop"""
564 """Start the kernel, coordinating with the GTK event loop"""
561 from .gui.gtkembed import GTKEmbed
565 from .gui.gtkembed import GTKEmbed
562
566
563 gtk_kernel = GTKEmbed(self)
567 gtk_kernel = GTKEmbed(self)
564 gtk_kernel.start()
568 gtk_kernel.start()
565
569
566
570
567 #-----------------------------------------------------------------------------
571 #-----------------------------------------------------------------------------
568 # Kernel main and launch functions
572 # Kernel main and launch functions
569 #-----------------------------------------------------------------------------
573 #-----------------------------------------------------------------------------
570
574
571 def launch_kernel(ip=None, xrep_port=0, pub_port=0, req_port=0, hb_port=0,
575 def launch_kernel(ip=None, xrep_port=0, pub_port=0, req_port=0, hb_port=0,
572 stdin=None, stdout=None, stderr=None,
576 stdin=None, stdout=None, stderr=None,
573 executable=None, independent=False, pylab=False, colors=None):
577 executable=None, independent=False, pylab=False, colors=None):
574 """Launches a localhost kernel, binding to the specified ports.
578 """Launches a localhost kernel, binding to the specified ports.
575
579
576 Parameters
580 Parameters
577 ----------
581 ----------
578 ip : str, optional
582 ip : str, optional
579 The ip address the kernel will bind to.
583 The ip address the kernel will bind to.
580
584
581 xrep_port : int, optional
585 xrep_port : int, optional
582 The port to use for XREP channel.
586 The port to use for XREP channel.
583
587
584 pub_port : int, optional
588 pub_port : int, optional
585 The port to use for the SUB channel.
589 The port to use for the SUB channel.
586
590
587 req_port : int, optional
591 req_port : int, optional
588 The port to use for the REQ (raw input) channel.
592 The port to use for the REQ (raw input) channel.
589
593
590 hb_port : int, optional
594 hb_port : int, optional
591 The port to use for the hearbeat REP channel.
595 The port to use for the hearbeat REP channel.
592
596
593 stdin, stdout, stderr : optional (default None)
597 stdin, stdout, stderr : optional (default None)
594 Standards streams, as defined in subprocess.Popen.
598 Standards streams, as defined in subprocess.Popen.
595
599
596 executable : str, optional (default sys.executable)
600 executable : str, optional (default sys.executable)
597 The Python executable to use for the kernel process.
601 The Python executable to use for the kernel process.
598
602
599 independent : bool, optional (default False)
603 independent : bool, optional (default False)
600 If set, the kernel process is guaranteed to survive if this process
604 If set, the kernel process is guaranteed to survive if this process
601 dies. If not set, an effort is made to ensure that the kernel is killed
605 dies. If not set, an effort is made to ensure that the kernel is killed
602 when this process dies. Note that in this case it is still good practice
606 when this process dies. Note that in this case it is still good practice
603 to kill kernels manually before exiting.
607 to kill kernels manually before exiting.
604
608
605 pylab : bool or string, optional (default False)
609 pylab : bool or string, optional (default False)
606 If not False, the kernel will be launched with pylab enabled. If a
610 If not False, the kernel will be launched with pylab enabled. If a
607 string is passed, matplotlib will use the specified backend. Otherwise,
611 string is passed, matplotlib will use the specified backend. Otherwise,
608 matplotlib's default backend will be used.
612 matplotlib's default backend will be used.
609
613
610 colors : None or string, optional (default None)
614 colors : None or string, optional (default None)
611 If not None, specify the color scheme. One of (NoColor, LightBG, Linux)
615 If not None, specify the color scheme. One of (NoColor, LightBG, Linux)
612
616
613 Returns
617 Returns
614 -------
618 -------
615 A tuple of form:
619 A tuple of form:
616 (kernel_process, xrep_port, pub_port, req_port)
620 (kernel_process, xrep_port, pub_port, req_port)
617 where kernel_process is a Popen object and the ports are integers.
621 where kernel_process is a Popen object and the ports are integers.
618 """
622 """
619 extra_arguments = []
623 extra_arguments = []
620 if pylab:
624 if pylab:
621 extra_arguments.append('--pylab')
625 extra_arguments.append('--pylab')
622 if isinstance(pylab, basestring):
626 if isinstance(pylab, basestring):
623 extra_arguments.append(pylab)
627 extra_arguments.append(pylab)
624 if ip is not None:
628 if ip is not None:
625 extra_arguments.append('--ip')
629 extra_arguments.append('--ip')
626 if isinstance(ip, basestring):
630 if isinstance(ip, basestring):
627 extra_arguments.append(ip)
631 extra_arguments.append(ip)
628 if colors is not None:
632 if colors is not None:
629 extra_arguments.append('--colors')
633 extra_arguments.append('--colors')
630 extra_arguments.append(colors)
634 extra_arguments.append(colors)
631 return base_launch_kernel('from IPython.zmq.ipkernel import main; main()',
635 return base_launch_kernel('from IPython.zmq.ipkernel import main; main()',
632 xrep_port, pub_port, req_port, hb_port,
636 xrep_port, pub_port, req_port, hb_port,
633 stdin, stdout, stderr,
637 stdin, stdout, stderr,
634 executable, independent, extra_arguments)
638 executable, independent, extra_arguments)
635
639
636
640
637 def main():
641 def main():
638 """ The IPython kernel main entry point.
642 """ The IPython kernel main entry point.
639 """
643 """
640 parser = make_argument_parser()
644 parser = make_argument_parser()
641 parser.add_argument('--pylab', type=str, metavar='GUI', nargs='?',
645 parser.add_argument('--pylab', type=str, metavar='GUI', nargs='?',
642 const='auto', help = \
646 const='auto', help = \
643 "Pre-load matplotlib and numpy for interactive use. If GUI is not \
647 "Pre-load matplotlib and numpy for interactive use. If GUI is not \
644 given, the GUI backend is matplotlib's, otherwise use one of: \
648 given, the GUI backend is matplotlib's, otherwise use one of: \
645 ['tk', 'gtk', 'qt', 'wx', 'osx', 'inline'].")
649 ['tk', 'gtk', 'qt', 'wx', 'osx', 'inline'].")
646 parser.add_argument('--colors',
650 parser.add_argument('--colors',
647 type=str, dest='colors',
651 type=str, dest='colors',
648 help="Set the color scheme (NoColor, Linux, and LightBG).",
652 help="Set the color scheme (NoColor, Linux, and LightBG).",
649 metavar='ZMQInteractiveShell.colors')
653 metavar='ZMQInteractiveShell.colors')
650 namespace = parser.parse_args()
654 namespace = parser.parse_args()
651
655
652 kernel_class = Kernel
656 kernel_class = Kernel
653
657
654 kernel_classes = {
658 kernel_classes = {
655 'qt' : QtKernel,
659 'qt' : QtKernel,
656 'qt4': QtKernel,
660 'qt4': QtKernel,
657 'inline': Kernel,
661 'inline': Kernel,
658 'osx': TkKernel,
662 'osx': TkKernel,
659 'wx' : WxKernel,
663 'wx' : WxKernel,
660 'tk' : TkKernel,
664 'tk' : TkKernel,
661 'gtk': GTKKernel,
665 'gtk': GTKKernel,
662 }
666 }
663 if namespace.pylab:
667 if namespace.pylab:
664 if namespace.pylab == 'auto':
668 if namespace.pylab == 'auto':
665 gui, backend = pylabtools.find_gui_and_backend()
669 gui, backend = pylabtools.find_gui_and_backend()
666 else:
670 else:
667 gui, backend = pylabtools.find_gui_and_backend(namespace.pylab)
671 gui, backend = pylabtools.find_gui_and_backend(namespace.pylab)
668 kernel_class = kernel_classes.get(gui)
672 kernel_class = kernel_classes.get(gui)
669 if kernel_class is None:
673 if kernel_class is None:
670 raise ValueError('GUI is not supported: %r' % gui)
674 raise ValueError('GUI is not supported: %r' % gui)
671 pylabtools.activate_matplotlib(backend)
675 pylabtools.activate_matplotlib(backend)
672 if namespace.colors:
676 if namespace.colors:
673 ZMQInteractiveShell.colors=namespace.colors
677 ZMQInteractiveShell.colors=namespace.colors
674
678
675 kernel = make_kernel(namespace, kernel_class, OutStream)
679 kernel = make_kernel(namespace, kernel_class, OutStream)
676
680
677 if namespace.pylab:
681 if namespace.pylab:
678 pylabtools.import_pylab(kernel.shell.user_ns, backend,
682 pylabtools.import_pylab(kernel.shell.user_ns, backend,
679 shell=kernel.shell)
683 shell=kernel.shell)
680
684
681 start_kernel(namespace, kernel)
685 start_kernel(namespace, kernel)
682
686
683
687
684 if __name__ == '__main__':
688 if __name__ == '__main__':
685 main()
689 main()
General Comments 0
You need to be logged in to leave comments. Login now