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