##// END OF EJS Templates
* Restored functionality after major merge....
epatters -
Show More
@@ -0,0 +1,189 b''
1 """ Defines helper functions for creating kernel entry points and process
2 launchers.
3 """
4
5 # Standard library imports.
6 import socket
7 from subprocess import Popen
8 import sys
9
10 # System library imports.
11 import zmq
12
13 # Local imports.
14 from IPython.external.argparse import ArgumentParser
15 from exitpoller import ExitPollerUnix, ExitPollerWindows
16 from displayhook import DisplayHook
17 from iostream import OutStream
18 from session import Session
19
20
21 def bind_port(socket, ip, port):
22 """ Binds the specified ZMQ socket. If the port is zero, a random port is
23 chosen. Returns the port that was bound.
24 """
25 connection = 'tcp://%s' % ip
26 if port <= 0:
27 port = socket.bind_to_random_port(connection)
28 else:
29 connection += ':%i' % port
30 socket.bind(connection)
31 return port
32
33
34 def make_argument_parser():
35 """ Creates an ArgumentParser for the generic arguments supported by all
36 kernel entry points.
37 """
38 parser = ArgumentParser()
39 parser.add_argument('--ip', type=str, default='127.0.0.1',
40 help='set the kernel\'s IP address [default: local]')
41 parser.add_argument('--xrep', type=int, metavar='PORT', default=0,
42 help='set the XREP channel port [default: random]')
43 parser.add_argument('--pub', type=int, metavar='PORT', default=0,
44 help='set the PUB channel port [default: random]')
45 parser.add_argument('--req', type=int, metavar='PORT', default=0,
46 help='set the REQ channel port [default: random]')
47
48 if sys.platform == 'win32':
49 parser.add_argument('--parent', type=int, metavar='HANDLE',
50 default=0, help='kill this process if the process '
51 'with HANDLE dies')
52 else:
53 parser.add_argument('--parent', action='store_true',
54 help='kill this process if its parent dies')
55
56 return parser
57
58
59 def make_kernel(namespace, kernel_factory, out_stream_factory=OutStream,
60 display_hook_factory=DisplayHook):
61 """ Creates a kernel.
62 """
63 # Create a context, a session, and the kernel sockets.
64 print >>sys.__stdout__, "Starting the kernel..."
65 context = zmq.Context()
66 session = Session(username=u'kernel')
67
68 reply_socket = context.socket(zmq.XREP)
69 xrep_port = bind_port(reply_socket, namespace.ip, namespace.xrep)
70 print >>sys.__stdout__, "XREP Channel on port", xrep_port
71
72 pub_socket = context.socket(zmq.PUB)
73 pub_port = bind_port(pub_socket, namespace.ip, namespace.pub)
74 print >>sys.__stdout__, "PUB Channel on port", pub_port
75
76 req_socket = context.socket(zmq.XREQ)
77 req_port = bind_port(req_socket, namespace.ip, namespace.req)
78 print >>sys.__stdout__, "REQ Channel on port", req_port
79
80 # Redirect input streams and set a display hook.
81 sys.stdout = out_stream_factory(session, pub_socket, u'stdout')
82 sys.stderr = out_stream_factory(session, pub_socket, u'stderr')
83 sys.displayhook = display_hook_factory(session, pub_socket)
84
85 # Create the kernel.
86 return kernel_factory(session=session, reply_socket=reply_socket,
87 pub_socket=pub_socket, req_socket=req_socket)
88
89
90 def start_kernel(namespace, kernel):
91 """ Starts a kernel.
92 """
93 # Configure this kernel/process to die on parent termination, if necessary.
94 if namespace.parent:
95 if sys.platform == 'win32':
96 poller = ExitPollerWindows(namespace.parent)
97 else:
98 poller = ExitPollerUnix()
99 poller.start()
100
101 # Start the kernel mainloop.
102 kernel.start()
103
104
105 def make_default_main(kernel_factory):
106 """ Creates the simplest possible kernel entry point.
107 """
108 def main():
109 namespace = make_argument_parser().parse_args()
110 kernel = make_kernel(namespace, kernel_factory)
111 start_kernel(namespace, kernel)
112 return main
113
114
115 def base_launch_kernel(code, xrep_port=0, pub_port=0, req_port=0,
116 independent=False, extra_arguments=[]):
117 """ Launches a localhost kernel, binding to the specified ports.
118
119 Parameters
120 ----------
121 code : str,
122 A string of Python code that imports and executes a kernel entry point.
123
124 xrep_port : int, optional
125 The port to use for XREP channel.
126
127 pub_port : int, optional
128 The port to use for the SUB channel.
129
130 req_port : int, optional
131 The port to use for the REQ (raw input) channel.
132
133 independent : bool, optional (default False)
134 If set, the kernel process is guaranteed to survive if this process
135 dies. If not set, an effort is made to ensure that the kernel is killed
136 when this process dies. Note that in this case it is still good practice
137 to kill kernels manually before exiting.
138
139 extra_arguments = list, optional
140 A list of extra arguments to pass when executing the launch code.
141
142 Returns
143 -------
144 A tuple of form:
145 (kernel_process, xrep_port, pub_port, req_port)
146 where kernel_process is a Popen object and the ports are integers.
147 """
148 # Find open ports as necessary.
149 ports = []
150 ports_needed = int(xrep_port <= 0) + int(pub_port <= 0) + int(req_port <= 0)
151 for i in xrange(ports_needed):
152 sock = socket.socket()
153 sock.bind(('', 0))
154 ports.append(sock)
155 for i, sock in enumerate(ports):
156 port = sock.getsockname()[1]
157 sock.close()
158 ports[i] = port
159 if xrep_port <= 0:
160 xrep_port = ports.pop(0)
161 if pub_port <= 0:
162 pub_port = ports.pop(0)
163 if req_port <= 0:
164 req_port = ports.pop(0)
165
166 # Build the kernel launch command.
167 arguments = [ sys.executable, '-c', code, '--xrep', str(xrep_port),
168 '--pub', str(pub_port), '--req', str(req_port) ]
169 arguments.extend(extra_arguments)
170
171 # Spawn a kernel.
172 if independent:
173 if sys.platform == 'win32':
174 proc = Popen(['start', '/b'] + arguments, shell=True)
175 else:
176 proc = Popen(arguments, preexec_fn=lambda: os.setsid())
177 else:
178 if sys.platform == 'win32':
179 from _subprocess import DuplicateHandle, GetCurrentProcess, \
180 DUPLICATE_SAME_ACCESS
181 pid = GetCurrentProcess()
182 handle = DuplicateHandle(pid, pid, pid, 0,
183 True, # Inheritable by new processes.
184 DUPLICATE_SAME_ACCESS)
185 proc = Popen(arguments + ['--parent', str(int(handle))])
186 else:
187 proc = Popen(arguments + ['--parent'])
188
189 return proc, xrep_port, pub_port, req_port
@@ -1,53 +1,63 b''
1 1 """ A demo of the Qt console-style IPython frontend.
2 2 """
3 3
4 4 # Systemm library imports
5 5 from PyQt4 import QtCore, QtGui
6 6
7 7 # Local imports
8 8 from IPython.external.argparse import ArgumentParser
9 9 from IPython.frontend.qt.kernelmanager import QtKernelManager
10 from ipython_widget import IPythonWidget
11 from rich_ipython_widget import RichIPythonWidget
12 10
13 11
14 12 def main():
15 13 """ Entry point for demo.
16 14 """
17 15 # Parse command line arguments.
18 16 parser = ArgumentParser()
19 parser.add_argument('--pylab', action='store_true',
20 help='start kernel with pylab enabled')
17 group = parser.add_mutually_exclusive_group()
18 group.add_argument('--pure', action='store_true', help = \
19 'use a pure Python kernel instead of an IPython kernel')
20 group.add_argument('--pylab', action='store_true',
21 help='use a kernel with PyLab enabled')
21 22 parser.add_argument('--rich', action='store_true',
22 help='use rich text frontend')
23 help='use a rich text frontend')
23 24 namespace = parser.parse_args()
24 25
25 26 # Don't let Qt or ZMQ swallow KeyboardInterupts.
26 27 import signal
27 28 signal.signal(signal.SIGINT, signal.SIG_DFL)
28 29
29 30 # Create a KernelManager and start a kernel.
30 31 kernel_manager = QtKernelManager()
31 if namespace.pylab:
32 if namespace.pure:
33 kernel_manager.start_kernel(ipython=False)
34 elif namespace.pylab:
32 35 if namespace.rich:
33 36 kernel_manager.start_kernel(pylab='payload-svg')
34 37 else:
35 38 kernel_manager.start_kernel(pylab='qt4')
36 39 else:
37 40 kernel_manager.start_kernel()
38 41 kernel_manager.start_channels()
39 42
40 43 # Launch the application.
41 44 app = QtGui.QApplication([])
42 if namespace.rich:
43 widget = RichIPythonWidget()
45 if namespace.pure:
46 from frontend_widget import FrontendWidget
47 kind = 'rich' if namespace.rich else 'plain'
48 widget = FrontendWidget(kind=kind)
44 49 else:
45 widget = IPythonWidget()
50 if namespace.rich:
51 from rich_ipython_widget import RichIPythonWidget
52 widget = RichIPythonWidget()
53 else:
54 from ipython_widget import IPythonWidget
55 widget = IPythonWidget()
46 56 widget.kernel_manager = kernel_manager
47 57 widget.setWindowTitle('Python')
48 58 widget.show()
49 59 app.exec_()
50 60
51 61
52 62 if __name__ == '__main__':
53 63 main()
@@ -1,367 +1,367 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 import os
21 20 import sys
22 21 import time
23 22 import traceback
24 23
25 24 # System library imports.
26 25 import zmq
27 26
28 27 # Local imports.
29 28 from IPython.config.configurable import Configurable
30 from IPython.zmq.zmqshell import ZMQInteractiveShell
31 from IPython.external.argparse import ArgumentParser
32 29 from IPython.utils.traitlets import Instance
33 from IPython.zmq.session import Session, Message
34 30 from completer import KernelCompleter
35 from iostream import OutStream
36 from displayhook import DisplayHook
37 from exitpoller import ExitPollerUnix, ExitPollerWindows
31 from entry_point import base_launch_kernel, make_argument_parser, make_kernel, \
32 start_kernel
33 from session import Session, Message
34 from zmqshell import ZMQInteractiveShell
38 35
39 36 #-----------------------------------------------------------------------------
40 37 # Main kernel class
41 38 #-----------------------------------------------------------------------------
42 39
43 40 class Kernel(Configurable):
44 41
42 #---------------------------------------------------------------------------
43 # Kernel interface
44 #---------------------------------------------------------------------------
45
45 46 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
46 session = Instance('IPython.zmq.session.Session')
47 session = Instance(Session)
47 48 reply_socket = Instance('zmq.Socket')
48 49 pub_socket = Instance('zmq.Socket')
49 50 req_socket = Instance('zmq.Socket')
50 51
52 # The global kernel instance.
53 _kernel = None
54
55 # Maps user-friendly backend names to matplotlib backend identifiers.
56 _pylab_map = { 'tk': 'TkAgg',
57 'gtk': 'GTKAgg',
58 'wx': 'WXAgg',
59 'qt': 'Qt4Agg', # qt3 not supported
60 'qt4': 'Qt4Agg',
61 'payload-svg' : \
62 'module://IPython.zmq.pylab.backend_payload_svg' }
63
51 64 def __init__(self, **kwargs):
52 65 super(Kernel, self).__init__(**kwargs)
53 66 self.shell = ZMQInteractiveShell.instance()
67
68 # Protected variables.
69 self._exec_payload = {}
54 70
55 71 # Build dict of handlers for message types
56 72 msg_types = [ 'execute_request', 'complete_request',
57 73 'object_info_request' ]
58 74 self.handlers = {}
59 75 for msg_type in msg_types:
60 76 self.handlers[msg_type] = getattr(self, msg_type)
61 77
62 def abort_queue(self):
78 def add_exec_payload(self, key, value):
79 """ Adds a key/value pair to the execute payload.
80 """
81 self._exec_payload[key] = value
82
83 def activate_pylab(self, backend=None, import_all=True):
84 """ Activates pylab in this kernel's namespace.
85
86 Parameters:
87 -----------
88 backend : str, optional
89 A valid backend name.
90
91 import_all : bool, optional
92 If true, an 'import *' is done from numpy and pylab.
93 """
94 # FIXME: This is adapted from IPython.lib.pylabtools.pylab_activate.
95 # Common funtionality should be refactored.
96
97 # We must set the desired backend before importing pylab.
98 import matplotlib
99 if backend:
100 backend_id = self._pylab_map[backend]
101 if backend_id.startswith('module://'):
102 # Work around bug in matplotlib: matplotlib.use converts the
103 # backend_id to lowercase even if a module name is specified!
104 matplotlib.rcParams['backend'] = backend_id
105 else:
106 matplotlib.use(backend_id)
107
108 # Import numpy as np/pyplot as plt are conventions we're trying to
109 # somewhat standardize on. Making them available to users by default
110 # will greatly help this.
111 exec ("import numpy\n"
112 "import matplotlib\n"
113 "from matplotlib import pylab, mlab, pyplot\n"
114 "np = numpy\n"
115 "plt = pyplot\n"
116 ) in self.shell.user_ns
117
118 if import_all:
119 exec("from matplotlib.pylab import *\n"
120 "from numpy import *\n") in self.shell.user_ns
121
122 matplotlib.interactive(True)
123
124 @classmethod
125 def get_kernel(cls):
126 """ Return the global kernel instance or raise a RuntimeError if it does
127 not exist.
128 """
129 if cls._kernel is None:
130 raise RuntimeError("Kernel not started!")
131 else:
132 return cls._kernel
133
134 def start(self):
135 """ Start the kernel main loop.
136 """
137 # Set the global kernel instance.
138 self.__class__._kernel = self
139
63 140 while True:
64 try:
65 ident = self.reply_socket.recv(zmq.NOBLOCK)
66 except zmq.ZMQError, e:
67 if e.errno == zmq.EAGAIN:
68 break
141 ident = self.reply_socket.recv()
142 assert self.reply_socket.rcvmore(), "Missing message part."
143 msg = self.reply_socket.recv_json()
144 omsg = Message(msg)
145 print>>sys.__stdout__
146 print>>sys.__stdout__, omsg
147 handler = self.handlers.get(omsg.msg_type, None)
148 if handler is None:
149 print >> sys.__stderr__, "UNKNOWN MESSAGE TYPE:", omsg
69 150 else:
70 assert self.reply_socket.rcvmore(), "Unexpected missing message part."
71 msg = self.reply_socket.recv_json()
72 print>>sys.__stdout__, "Aborting:"
73 print>>sys.__stdout__, Message(msg)
74 msg_type = msg['msg_type']
75 reply_type = msg_type.split('_')[0] + '_reply'
76 reply_msg = self.session.msg(reply_type, {'status' : 'aborted'}, msg)
77 print>>sys.__stdout__, Message(reply_msg)
78 self.reply_socket.send(ident,zmq.SNDMORE)
79 self.reply_socket.send_json(reply_msg)
80 # We need to wait a bit for requests to come in. This can probably
81 # be set shorter for true asynchronous clients.
82 time.sleep(0.1)
151 handler(ident, omsg)
152
153 #---------------------------------------------------------------------------
154 # Kernel request handlers
155 #---------------------------------------------------------------------------
83 156
84 157 def execute_request(self, ident, parent):
85 158 try:
86 159 code = parent[u'content'][u'code']
87 160 except:
88 161 print>>sys.__stderr__, "Got bad msg: "
89 162 print>>sys.__stderr__, Message(parent)
90 163 return
91 164 pyin_msg = self.session.msg(u'pyin',{u'code':code}, parent=parent)
92 165 self.pub_socket.send_json(pyin_msg)
93 166
167 # Clear the execute payload from the last request.
168 self._exec_payload = {}
169
94 170 try:
95 171 # Replace raw_input. Note that is not sufficient to replace
96 172 # raw_input in the user namespace.
97 raw_input = lambda prompt='': self.raw_input(prompt, ident, parent)
173 raw_input = lambda prompt='': self._raw_input(prompt, ident, parent)
98 174 __builtin__.raw_input = raw_input
99 175
100 176 # Configure the display hook.
101 177 sys.displayhook.set_parent(parent)
102 178
103 179 self.shell.runlines(code)
104 180 # exec comp_code in self.user_ns, self.user_ns
105 181 except:
106 182 etype, evalue, tb = sys.exc_info()
107 183 tb = traceback.format_exception(etype, evalue, tb)
108 184 exc_content = {
109 185 u'status' : u'error',
110 186 u'traceback' : tb,
111 187 u'ename' : unicode(etype.__name__),
112 188 u'evalue' : unicode(evalue)
113 189 }
114 190 exc_msg = self.session.msg(u'pyerr', exc_content, parent)
115 191 self.pub_socket.send_json(exc_msg)
116 192 reply_content = exc_content
117 193 else:
118 reply_content = {'status' : 'ok'}
194 reply_content = { 'status' : 'ok', 'payload' : self._exec_payload }
119 195
120 196 # Flush output before sending the reply.
121 197 sys.stderr.flush()
122 198 sys.stdout.flush()
123 199
124 200 # Send the reply.
125 201 reply_msg = self.session.msg(u'execute_reply', reply_content, parent)
126 202 print>>sys.__stdout__, Message(reply_msg)
127 203 self.reply_socket.send(ident, zmq.SNDMORE)
128 204 self.reply_socket.send_json(reply_msg)
129 205 if reply_msg['content']['status'] == u'error':
130 self.abort_queue()
206 self._abort_queue()
207
208 def complete_request(self, ident, parent):
209 matches = {'matches' : self._complete(parent),
210 'status' : 'ok'}
211 completion_msg = self.session.send(self.reply_socket, 'complete_reply',
212 matches, parent, ident)
213 print >> sys.__stdout__, completion_msg
214
215 def object_info_request(self, ident, parent):
216 context = parent['content']['oname'].split('.')
217 object_info = self._object_info(context)
218 msg = self.session.send(self.reply_socket, 'object_info_reply',
219 object_info, parent, ident)
220 print >> sys.__stdout__, msg
221
222 #---------------------------------------------------------------------------
223 # Protected interface
224 #---------------------------------------------------------------------------
131 225
132 def raw_input(self, prompt, ident, parent):
226 def _abort_queue(self):
227 while True:
228 try:
229 ident = self.reply_socket.recv(zmq.NOBLOCK)
230 except zmq.ZMQError, e:
231 if e.errno == zmq.EAGAIN:
232 break
233 else:
234 assert self.reply_socket.rcvmore(), "Unexpected missing message part."
235 msg = self.reply_socket.recv_json()
236 print>>sys.__stdout__, "Aborting:"
237 print>>sys.__stdout__, Message(msg)
238 msg_type = msg['msg_type']
239 reply_type = msg_type.split('_')[0] + '_reply'
240 reply_msg = self.session.msg(reply_type, {'status' : 'aborted'}, msg)
241 print>>sys.__stdout__, Message(reply_msg)
242 self.reply_socket.send(ident,zmq.SNDMORE)
243 self.reply_socket.send_json(reply_msg)
244 # We need to wait a bit for requests to come in. This can probably
245 # be set shorter for true asynchronous clients.
246 time.sleep(0.1)
247
248 def _raw_input(self, prompt, ident, parent):
133 249 # Flush output before making the request.
134 250 sys.stderr.flush()
135 251 sys.stdout.flush()
136 252
137 253 # Send the input request.
138 254 content = dict(prompt=prompt)
139 255 msg = self.session.msg(u'input_request', content, parent)
140 256 self.req_socket.send_json(msg)
141 257
142 258 # Await a response.
143 259 reply = self.req_socket.recv_json()
144 260 try:
145 261 value = reply['content']['value']
146 262 except:
147 263 print>>sys.__stderr__, "Got bad raw_input reply: "
148 264 print>>sys.__stderr__, Message(parent)
149 265 value = ''
150 266 return value
151
152 def complete_request(self, ident, parent):
153 matches = {'matches' : self.complete(parent),
154 'status' : 'ok'}
155 completion_msg = self.session.send(self.reply_socket, 'complete_reply',
156 matches, parent, ident)
157 print >> sys.__stdout__, completion_msg
158
159 def complete(self, msg):
267
268 def _complete(self, msg):
160 269 return self.shell.complete(msg.content.line)
161 270
162 def object_info_request(self, ident, parent):
163 context = parent['content']['oname'].split('.')
164 object_info = self.object_info(context)
165 msg = self.session.send(self.reply_socket, 'object_info_reply',
166 object_info, parent, ident)
167 print >> sys.__stdout__, msg
168
169 def object_info(self, context):
170 symbol, leftover = self.symbol_from_context(context)
271 def _object_info(self, context):
272 symbol, leftover = self._symbol_from_context(context)
171 273 if symbol is not None and not leftover:
172 274 doc = getattr(symbol, '__doc__', '')
173 275 else:
174 276 doc = ''
175 277 object_info = dict(docstring = doc)
176 278 return object_info
177 279
178 def symbol_from_context(self, context):
280 def _symbol_from_context(self, context):
179 281 if not context:
180 282 return None, context
181 283
182 284 base_symbol_string = context[0]
183 285 symbol = self.shell.user_ns.get(base_symbol_string, None)
184 286 if symbol is None:
185 287 symbol = __builtin__.__dict__.get(base_symbol_string, None)
186 288 if symbol is None:
187 289 return None, context
188 290
189 291 context = context[1:]
190 292 for i, name in enumerate(context):
191 293 new_symbol = getattr(symbol, name, None)
192 294 if new_symbol is None:
193 295 return symbol, context[i:]
194 296 else:
195 297 symbol = new_symbol
196 298
197 299 return symbol, []
198 300
199 def start(self):
200 while True:
201 ident = self.reply_socket.recv()
202 assert self.reply_socket.rcvmore(), "Missing message part."
203 msg = self.reply_socket.recv_json()
204 omsg = Message(msg)
205 print>>sys.__stdout__
206 print>>sys.__stdout__, omsg
207 handler = self.handlers.get(omsg.msg_type, None)
208 if handler is None:
209 print >> sys.__stderr__, "UNKNOWN MESSAGE TYPE:", omsg
210 else:
211 handler(ident, omsg)
212
213 301 #-----------------------------------------------------------------------------
214 302 # Kernel main and launch functions
215 303 #-----------------------------------------------------------------------------
216 304
217 def bind_port(socket, ip, port):
218 """ Binds the specified ZMQ socket. If the port is less than zero, a random
219 port is chosen. Returns the port that was bound.
220 """
221 connection = 'tcp://%s' % ip
222 if port <= 0:
223 port = socket.bind_to_random_port(connection)
224 else:
225 connection += ':%i' % port
226 socket.bind(connection)
227 return port
228
229
230 def main():
231 """ Main entry point for launching a kernel.
232 """
233 # Parse command line arguments.
234 parser = ArgumentParser()
235 parser.add_argument('--ip', type=str, default='127.0.0.1',
236 help='set the kernel\'s IP address [default: local]')
237 parser.add_argument('--xrep', type=int, metavar='PORT', default=0,
238 help='set the XREP channel port [default: random]')
239 parser.add_argument('--pub', type=int, metavar='PORT', default=0,
240 help='set the PUB channel port [default: random]')
241 parser.add_argument('--req', type=int, metavar='PORT', default=0,
242 help='set the REQ channel port [default: random]')
243 if sys.platform == 'win32':
244 parser.add_argument('--parent', type=int, metavar='HANDLE',
245 default=0, help='kill this process if the process '
246 'with HANDLE dies')
247 else:
248 parser.add_argument('--parent', action='store_true',
249 help='kill this process if its parent dies')
250 namespace = parser.parse_args()
251
252 # Create a context, a session, and the kernel sockets.
253 print >>sys.__stdout__, "Starting the kernel..."
254 context = zmq.Context()
255 session = Session(username=u'kernel')
256
257 reply_socket = context.socket(zmq.XREP)
258 xrep_port = bind_port(reply_socket, namespace.ip, namespace.xrep)
259 print >>sys.__stdout__, "XREP Channel on port", xrep_port
260
261 pub_socket = context.socket(zmq.PUB)
262 pub_port = bind_port(pub_socket, namespace.ip, namespace.pub)
263 print >>sys.__stdout__, "PUB Channel on port", pub_port
264
265 req_socket = context.socket(zmq.XREQ)
266 req_port = bind_port(req_socket, namespace.ip, namespace.req)
267 print >>sys.__stdout__, "REQ Channel on port", req_port
268
269 # Redirect input streams. This needs to be done before the Kernel is done
270 # because currently the Kernel creates a ZMQInteractiveShell, which
271 # holds references to sys.stdout and sys.stderr.
272 sys.stdout = OutStream(session, pub_socket, u'stdout')
273 sys.stderr = OutStream(session, pub_socket, u'stderr')
274 # Set a displayhook.
275 sys.displayhook = DisplayHook(session, pub_socket)
276
277 # Create the kernel.
278 kernel = Kernel(
279 session=session, reply_socket=reply_socket,
280 pub_socket=pub_socket, req_socket=req_socket
281 )
282
283 # Configure this kernel/process to die on parent termination, if necessary.
284 if namespace.parent:
285 if sys.platform == 'win32':
286 poller = ExitPollerWindows(namespace.parent)
287 else:
288 poller = ExitPollerUnix()
289 poller.start()
290
291 # Start the kernel mainloop.
292 kernel.start()
293
294
295 def launch_kernel(xrep_port=0, pub_port=0, req_port=0, independent=False):
305 def launch_kernel(xrep_port=0, pub_port=0, req_port=0, independent=False,
306 pylab=False):
296 307 """ Launches a localhost kernel, binding to the specified ports.
297 308
298 309 Parameters
299 310 ----------
300 311 xrep_port : int, optional
301 312 The port to use for XREP channel.
302 313
303 314 pub_port : int, optional
304 315 The port to use for the SUB channel.
305 316
306 317 req_port : int, optional
307 318 The port to use for the REQ (raw input) channel.
308 319
309 320 independent : bool, optional (default False)
310 321 If set, the kernel process is guaranteed to survive if this process
311 322 dies. If not set, an effort is made to ensure that the kernel is killed
312 323 when this process dies. Note that in this case it is still good practice
313 324 to kill kernels manually before exiting.
314 325
326 pylab : bool or string, optional (default False)
327 If not False, the kernel will be launched with pylab enabled. If a
328 string is passed, matplotlib will use the specified backend. Otherwise,
329 matplotlib's default backend will be used.
330
315 331 Returns
316 332 -------
317 333 A tuple of form:
318 334 (kernel_process, xrep_port, pub_port, req_port)
319 335 where kernel_process is a Popen object and the ports are integers.
320 336 """
321 import socket
322 from subprocess import Popen
323
324 # Find open ports as necessary.
325 ports = []
326 ports_needed = int(xrep_port <= 0) + int(pub_port <= 0) + int(req_port <= 0)
327 for i in xrange(ports_needed):
328 sock = socket.socket()
329 sock.bind(('', 0))
330 ports.append(sock)
331 for i, sock in enumerate(ports):
332 port = sock.getsockname()[1]
333 sock.close()
334 ports[i] = port
335 if xrep_port <= 0:
336 xrep_port = ports.pop(0)
337 if pub_port <= 0:
338 pub_port = ports.pop(0)
339 if req_port <= 0:
340 req_port = ports.pop(0)
341
342 # Spawn a kernel.
343 command = 'from IPython.zmq.ipkernel import main; main()'
344 arguments = [ sys.executable, '-c', command, '--xrep', str(xrep_port),
345 '--pub', str(pub_port), '--req', str(req_port) ]
346 if independent:
347 if sys.platform == 'win32':
348 proc = Popen(['start', '/b'] + arguments, shell=True)
349 else:
350 proc = Popen(arguments, preexec_fn=lambda: os.setsid())
351 else:
352 if sys.platform == 'win32':
353 from _subprocess import DuplicateHandle, GetCurrentProcess, \
354 DUPLICATE_SAME_ACCESS
355 pid = GetCurrentProcess()
356 handle = DuplicateHandle(pid, pid, pid, 0,
357 True, # Inheritable by new processes.
358 DUPLICATE_SAME_ACCESS)
359 proc = Popen(arguments + ['--parent', str(int(handle))])
360 else:
361 proc = Popen(arguments + ['--parent'])
337 extra_arguments = []
338 if pylab:
339 extra_arguments.append('--pylab')
340 if isinstance(pylab, basestring):
341 extra_arguments.append(pylab)
342 return base_launch_kernel('from IPython.zmq.ipkernel import main; main()',
343 xrep_port, pub_port, req_port, independent,
344 extra_arguments)
362 345
363 return proc, xrep_port, pub_port, req_port
346 def main():
347 """ The IPython kernel main entry point.
348 """
349 parser = make_argument_parser()
350 parser.add_argument('--pylab', type=str, metavar='GUI', nargs='?',
351 const='auto', help = \
352 "Pre-load matplotlib and numpy for interactive use. If GUI is not \
353 given, the GUI backend is matplotlib's, otherwise use one of: \
354 ['tk', 'gtk', 'qt', 'wx', 'payload-svg'].")
355 namespace = parser.parse_args()
356
357 kernel = make_kernel(namespace, Kernel)
358 if namespace.pylab:
359 if namespace.pylab == 'auto':
360 kernel.activate_pylab()
361 else:
362 kernel.activate_pylab(namespace.pylab)
364 363
364 start_kernel(namespace, kernel)
365 365
366 366 if __name__ == '__main__':
367 367 main()
@@ -1,575 +1,578 b''
1 1 """Base classes to manage the interaction with a running kernel.
2 2
3 3 Todo
4 4 ====
5 5
6 6 * Create logger to handle debugging and console messages.
7 7 """
8 8
9 9 #-----------------------------------------------------------------------------
10 10 # Copyright (C) 2008-2010 The IPython Development Team
11 11 #
12 12 # Distributed under the terms of the BSD License. The full license is in
13 13 # the file COPYING, distributed as part of this software.
14 14 #-----------------------------------------------------------------------------
15 15
16 16 #-----------------------------------------------------------------------------
17 17 # Imports
18 18 #-----------------------------------------------------------------------------
19 19
20 20 # Standard library imports.
21 21 from Queue import Queue, Empty
22 22 from subprocess import Popen
23 23 from threading import Thread
24 24 import time
25 25
26 26 # System library imports.
27 27 import zmq
28 28 from zmq import POLLIN, POLLOUT, POLLERR
29 29 from zmq.eventloop import ioloop
30 30
31 31 # Local imports.
32 32 from IPython.utils.traitlets import HasTraits, Any, Instance, Type, TCPAddress
33 from ipkernel import launch_kernel
34 33 from session import Session
35 34
36 35 #-----------------------------------------------------------------------------
37 36 # Constants and exceptions
38 37 #-----------------------------------------------------------------------------
39 38
40 39 LOCALHOST = '127.0.0.1'
41 40
42 41 class InvalidPortNumber(Exception):
43 42 pass
44 43
45 44 #-----------------------------------------------------------------------------
46 45 # ZMQ Socket Channel classes
47 46 #-----------------------------------------------------------------------------
48 47
49 48 class ZmqSocketChannel(Thread):
50 49 """The base class for the channels that use ZMQ sockets.
51 50 """
52 51 context = None
53 52 session = None
54 53 socket = None
55 54 ioloop = None
56 55 iostate = None
57 56 _address = None
58 57
59 58 def __init__(self, context, session, address):
60 59 """Create a channel
61 60
62 61 Parameters
63 62 ----------
64 63 context : :class:`zmq.Context`
65 64 The ZMQ context to use.
66 65 session : :class:`session.Session`
67 66 The session to use.
68 67 address : tuple
69 68 Standard (ip, port) tuple that the kernel is listening on.
70 69 """
71 70 super(ZmqSocketChannel, self).__init__()
72 71 self.daemon = True
73 72
74 73 self.context = context
75 74 self.session = session
76 75 if address[1] == 0:
77 76 message = 'The port number for a channel cannot be 0.'
78 77 raise InvalidPortNumber(message)
79 78 self._address = address
80 79
81 80 def stop(self):
82 81 """Stop the channel's activity.
83 82
84 83 This calls :method:`Thread.join` and returns when the thread
85 84 terminates. :class:`RuntimeError` will be raised if
86 85 :method:`self.start` is called again.
87 86 """
88 87 self.join()
89 88
90 89 @property
91 90 def address(self):
92 91 """Get the channel's address as an (ip, port) tuple.
93 92
94 93 By the default, the address is (localhost, 0), where 0 means a random
95 94 port.
96 95 """
97 96 return self._address
98 97
99 98 def add_io_state(self, state):
100 99 """Add IO state to the eventloop.
101 100
102 101 Parameters
103 102 ----------
104 103 state : zmq.POLLIN|zmq.POLLOUT|zmq.POLLERR
105 104 The IO state flag to set.
106 105
107 106 This is thread safe as it uses the thread safe IOLoop.add_callback.
108 107 """
109 108 def add_io_state_callback():
110 109 if not self.iostate & state:
111 110 self.iostate = self.iostate | state
112 111 self.ioloop.update_handler(self.socket, self.iostate)
113 112 self.ioloop.add_callback(add_io_state_callback)
114 113
115 114 def drop_io_state(self, state):
116 115 """Drop IO state from the eventloop.
117 116
118 117 Parameters
119 118 ----------
120 119 state : zmq.POLLIN|zmq.POLLOUT|zmq.POLLERR
121 120 The IO state flag to set.
122 121
123 122 This is thread safe as it uses the thread safe IOLoop.add_callback.
124 123 """
125 124 def drop_io_state_callback():
126 125 if self.iostate & state:
127 126 self.iostate = self.iostate & (~state)
128 127 self.ioloop.update_handler(self.socket, self.iostate)
129 128 self.ioloop.add_callback(drop_io_state_callback)
130 129
131 130
132 131 class XReqSocketChannel(ZmqSocketChannel):
133 132 """The XREQ channel for issues request/replies to the kernel.
134 133 """
135 134
136 135 command_queue = None
137 136
138 137 def __init__(self, context, session, address):
139 138 self.command_queue = Queue()
140 139 super(XReqSocketChannel, self).__init__(context, session, address)
141 140
142 141 def run(self):
143 142 """The thread's main activity. Call start() instead."""
144 143 self.socket = self.context.socket(zmq.XREQ)
145 144 self.socket.setsockopt(zmq.IDENTITY, self.session.session)
146 145 self.socket.connect('tcp://%s:%i' % self.address)
147 146 self.ioloop = ioloop.IOLoop()
148 147 self.iostate = POLLERR|POLLIN
149 148 self.ioloop.add_handler(self.socket, self._handle_events,
150 149 self.iostate)
151 150 self.ioloop.start()
152 151
153 152 def stop(self):
154 153 self.ioloop.stop()
155 154 super(XReqSocketChannel, self).stop()
156 155
157 156 def call_handlers(self, msg):
158 157 """This method is called in the ioloop thread when a message arrives.
159 158
160 159 Subclasses should override this method to handle incoming messages.
161 160 It is important to remember that this method is called in the thread
162 161 so that some logic must be done to ensure that the application leve
163 162 handlers are called in the application thread.
164 163 """
165 164 raise NotImplementedError('call_handlers must be defined in a subclass.')
166 165
167 166 def execute(self, code):
168 167 """Execute code in the kernel.
169 168
170 169 Parameters
171 170 ----------
172 171 code : str
173 172 A string of Python code.
174 173
175 174 Returns
176 175 -------
177 176 The msg_id of the message sent.
178 177 """
179 178 # Create class for content/msg creation. Related to, but possibly
180 179 # not in Session.
181 180 content = dict(code=code)
182 181 msg = self.session.msg('execute_request', content)
183 182 self._queue_request(msg)
184 183 return msg['header']['msg_id']
185 184
186 185 def complete(self, text, line, block=None):
187 186 """Tab complete text, line, block in the kernel's namespace.
188 187
189 188 Parameters
190 189 ----------
191 190 text : str
192 191 The text to complete.
193 192 line : str
194 193 The full line of text that is the surrounding context for the
195 194 text to complete.
196 195 block : str
197 196 The full block of code in which the completion is being requested.
198 197
199 198 Returns
200 199 -------
201 200 The msg_id of the message sent.
202 201 """
203 202 content = dict(text=text, line=line)
204 203 msg = self.session.msg('complete_request', content)
205 204 self._queue_request(msg)
206 205 return msg['header']['msg_id']
207 206
208 207 def object_info(self, oname):
209 208 """Get metadata information about an object.
210 209
211 210 Parameters
212 211 ----------
213 212 oname : str
214 213 A string specifying the object name.
215 214
216 215 Returns
217 216 -------
218 217 The msg_id of the message sent.
219 218 """
220 219 content = dict(oname=oname)
221 220 msg = self.session.msg('object_info_request', content)
222 221 self._queue_request(msg)
223 222 return msg['header']['msg_id']
224 223
225 224 def _handle_events(self, socket, events):
226 225 if events & POLLERR:
227 226 self._handle_err()
228 227 if events & POLLOUT:
229 228 self._handle_send()
230 229 if events & POLLIN:
231 230 self._handle_recv()
232 231
233 232 def _handle_recv(self):
234 233 msg = self.socket.recv_json()
235 234 self.call_handlers(msg)
236 235
237 236 def _handle_send(self):
238 237 try:
239 238 msg = self.command_queue.get(False)
240 239 except Empty:
241 240 pass
242 241 else:
243 242 self.socket.send_json(msg)
244 243 if self.command_queue.empty():
245 244 self.drop_io_state(POLLOUT)
246 245
247 246 def _handle_err(self):
248 247 # We don't want to let this go silently, so eventually we should log.
249 248 raise zmq.ZMQError()
250 249
251 250 def _queue_request(self, msg):
252 251 self.command_queue.put(msg)
253 252 self.add_io_state(POLLOUT)
254 253
255 254
256 255 class SubSocketChannel(ZmqSocketChannel):
257 256 """The SUB channel which listens for messages that the kernel publishes.
258 257 """
259 258
260 259 def __init__(self, context, session, address):
261 260 super(SubSocketChannel, self).__init__(context, session, address)
262 261
263 262 def run(self):
264 263 """The thread's main activity. Call start() instead."""
265 264 self.socket = self.context.socket(zmq.SUB)
266 265 self.socket.setsockopt(zmq.SUBSCRIBE,'')
267 266 self.socket.setsockopt(zmq.IDENTITY, self.session.session)
268 267 self.socket.connect('tcp://%s:%i' % self.address)
269 268 self.ioloop = ioloop.IOLoop()
270 269 self.iostate = POLLIN|POLLERR
271 270 self.ioloop.add_handler(self.socket, self._handle_events,
272 271 self.iostate)
273 272 self.ioloop.start()
274 273
275 274 def stop(self):
276 275 self.ioloop.stop()
277 276 super(SubSocketChannel, self).stop()
278 277
279 278 def call_handlers(self, msg):
280 279 """This method is called in the ioloop thread when a message arrives.
281 280
282 281 Subclasses should override this method to handle incoming messages.
283 282 It is important to remember that this method is called in the thread
284 283 so that some logic must be done to ensure that the application leve
285 284 handlers are called in the application thread.
286 285 """
287 286 raise NotImplementedError('call_handlers must be defined in a subclass.')
288 287
289 288 def flush(self, timeout=1.0):
290 289 """Immediately processes all pending messages on the SUB channel.
291 290
292 291 Callers should use this method to ensure that :method:`call_handlers`
293 292 has been called for all messages that have been received on the
294 293 0MQ SUB socket of this channel.
295 294
296 295 This method is thread safe.
297 296
298 297 Parameters
299 298 ----------
300 299 timeout : float, optional
301 300 The maximum amount of time to spend flushing, in seconds. The
302 301 default is one second.
303 302 """
304 303 # We do the IOLoop callback process twice to ensure that the IOLoop
305 304 # gets to perform at least one full poll.
306 305 stop_time = time.time() + timeout
307 306 for i in xrange(2):
308 307 self._flushed = False
309 308 self.ioloop.add_callback(self._flush)
310 309 while not self._flushed and time.time() < stop_time:
311 310 time.sleep(0.01)
312 311
313 312 def _handle_events(self, socket, events):
314 313 # Turn on and off POLLOUT depending on if we have made a request
315 314 if events & POLLERR:
316 315 self._handle_err()
317 316 if events & POLLIN:
318 317 self._handle_recv()
319 318
320 319 def _handle_err(self):
321 320 # We don't want to let this go silently, so eventually we should log.
322 321 raise zmq.ZMQError()
323 322
324 323 def _handle_recv(self):
325 324 # Get all of the messages we can
326 325 while True:
327 326 try:
328 327 msg = self.socket.recv_json(zmq.NOBLOCK)
329 328 except zmq.ZMQError:
330 329 # Check the errno?
331 330 # Will this trigger POLLERR?
332 331 break
333 332 else:
334 333 self.call_handlers(msg)
335 334
336 335 def _flush(self):
337 336 """Callback for :method:`self.flush`."""
338 337 self._flushed = True
339 338
340 339
341 340 class RepSocketChannel(ZmqSocketChannel):
342 341 """A reply channel to handle raw_input requests that the kernel makes."""
343 342
344 343 msg_queue = None
345 344
346 345 def __init__(self, context, session, address):
347 346 self.msg_queue = Queue()
348 347 super(RepSocketChannel, self).__init__(context, session, address)
349 348
350 349 def run(self):
351 350 """The thread's main activity. Call start() instead."""
352 351 self.socket = self.context.socket(zmq.XREQ)
353 352 self.socket.setsockopt(zmq.IDENTITY, self.session.session)
354 353 self.socket.connect('tcp://%s:%i' % self.address)
355 354 self.ioloop = ioloop.IOLoop()
356 355 self.iostate = POLLERR|POLLIN
357 356 self.ioloop.add_handler(self.socket, self._handle_events,
358 357 self.iostate)
359 358 self.ioloop.start()
360 359
361 360 def stop(self):
362 361 self.ioloop.stop()
363 362 super(RepSocketChannel, self).stop()
364 363
365 364 def call_handlers(self, msg):
366 365 """This method is called in the ioloop thread when a message arrives.
367 366
368 367 Subclasses should override this method to handle incoming messages.
369 368 It is important to remember that this method is called in the thread
370 369 so that some logic must be done to ensure that the application leve
371 370 handlers are called in the application thread.
372 371 """
373 372 raise NotImplementedError('call_handlers must be defined in a subclass.')
374 373
375 374 def input(self, string):
376 375 """Send a string of raw input to the kernel."""
377 376 content = dict(value=string)
378 377 msg = self.session.msg('input_reply', content)
379 378 self._queue_reply(msg)
380 379
381 380 def _handle_events(self, socket, events):
382 381 if events & POLLERR:
383 382 self._handle_err()
384 383 if events & POLLOUT:
385 384 self._handle_send()
386 385 if events & POLLIN:
387 386 self._handle_recv()
388 387
389 388 def _handle_recv(self):
390 389 msg = self.socket.recv_json()
391 390 self.call_handlers(msg)
392 391
393 392 def _handle_send(self):
394 393 try:
395 394 msg = self.msg_queue.get(False)
396 395 except Empty:
397 396 pass
398 397 else:
399 398 self.socket.send_json(msg)
400 399 if self.msg_queue.empty():
401 400 self.drop_io_state(POLLOUT)
402 401
403 402 def _handle_err(self):
404 403 # We don't want to let this go silently, so eventually we should log.
405 404 raise zmq.ZMQError()
406 405
407 406 def _queue_reply(self, msg):
408 407 self.msg_queue.put(msg)
409 408 self.add_io_state(POLLOUT)
410 409
411 410
412 411 #-----------------------------------------------------------------------------
413 412 # Main kernel manager class
414 413 #-----------------------------------------------------------------------------
415 414
416 415 class KernelManager(HasTraits):
417 416 """ Manages a kernel for a frontend.
418 417
419 418 The SUB channel is for the frontend to receive messages published by the
420 419 kernel.
421 420
422 421 The REQ channel is for the frontend to make requests of the kernel.
423 422
424 423 The REP channel is for the kernel to request stdin (raw_input) from the
425 424 frontend.
426 425 """
427 426 # The PyZMQ Context to use for communication with the kernel.
428 427 context = Instance(zmq.Context,(),{})
429 428
430 429 # The Session to use for communication with the kernel.
431 430 session = Instance(Session,(),{})
432 431
433 432 # The kernel process with which the KernelManager is communicating.
434 433 kernel = Instance(Popen)
435 434
436 435 # The addresses for the communication channels.
437 436 xreq_address = TCPAddress((LOCALHOST, 0))
438 437 sub_address = TCPAddress((LOCALHOST, 0))
439 438 rep_address = TCPAddress((LOCALHOST, 0))
440 439
441 440 # The classes to use for the various channels.
442 441 xreq_channel_class = Type(XReqSocketChannel)
443 442 sub_channel_class = Type(SubSocketChannel)
444 443 rep_channel_class = Type(RepSocketChannel)
445 444
446 445 # Protected traits.
447 446 _xreq_channel = Any
448 447 _sub_channel = Any
449 448 _rep_channel = Any
450 449
451 450 #--------------------------------------------------------------------------
452 451 # Channel management methods:
453 452 #--------------------------------------------------------------------------
454 453
455 454 def start_channels(self):
456 455 """Starts the channels for this kernel.
457 456
458 457 This will create the channels if they do not exist and then start
459 458 them. If port numbers of 0 are being used (random ports) then you
460 459 must first call :method:`start_kernel`. If the channels have been
461 460 stopped and you call this, :class:`RuntimeError` will be raised.
462 461 """
463 462 self.xreq_channel.start()
464 463 self.sub_channel.start()
465 464 self.rep_channel.start()
466 465
467 466 def stop_channels(self):
468 467 """Stops the channels for this kernel.
469 468
470 469 This stops the channels by joining their threads. If the channels
471 470 were not started, :class:`RuntimeError` will be raised.
472 471 """
473 472 self.xreq_channel.stop()
474 473 self.sub_channel.stop()
475 474 self.rep_channel.stop()
476 475
477 476 @property
478 477 def channels_running(self):
479 478 """Are all of the channels created and running?"""
480 479 return self.xreq_channel.is_alive() \
481 480 and self.sub_channel.is_alive() \
482 481 and self.rep_channel.is_alive()
483 482
484 483 #--------------------------------------------------------------------------
485 484 # Kernel process management methods:
486 485 #--------------------------------------------------------------------------
487 486
488 def start_kernel(self, pylab=False):
487 def start_kernel(self, ipython=True, **kw):
489 488 """Starts a kernel process and configures the manager to use it.
490 489
491 490 If random ports (port=0) are being used, this method must be called
492 491 before the channels are created.
493 492
494 493 Parameters:
495 494 -----------
496 pylab : bool or string, optional (default False)
497 See IPython.zmq.kernel.launch_kernel for documentation.
495 ipython : bool, optional (default True)
496 Whether to use an IPython kernel instead of a plain Python kernel.
498 497 """
499 498 xreq, sub, rep = self.xreq_address, self.sub_address, self.rep_address
500 499 if xreq[0] != LOCALHOST or sub[0] != LOCALHOST or rep[0] != LOCALHOST:
501 500 raise RuntimeError("Can only launch a kernel on localhost."
502 501 "Make sure that the '*_address' attributes are "
503 502 "configured properly.")
504 503
505 self.kernel, xrep, pub, req = launch_kernel(
506 xrep_port=xreq[1], pub_port=sub[1], req_port=rep[1], pylab=pylab)
504 if ipython:
505 from ipkernel import launch_kernel as launch
506 else:
507 from pykernel import launch_kernel as launch
508 self.kernel, xrep, pub, req = launch(xrep_port=xreq[1], pub_port=sub[1],
509 req_port=rep[1], **kw)
507 510 self.xreq_address = (LOCALHOST, xrep)
508 511 self.sub_address = (LOCALHOST, pub)
509 512 self.rep_address = (LOCALHOST, req)
510 513
511 514 @property
512 515 def has_kernel(self):
513 516 """Returns whether a kernel process has been specified for the kernel
514 517 manager.
515 518 """
516 519 return self.kernel is not None
517 520
518 521 def kill_kernel(self):
519 522 """ Kill the running kernel. """
520 523 if self.kernel is not None:
521 524 self.kernel.kill()
522 525 self.kernel = None
523 526 else:
524 527 raise RuntimeError("Cannot kill kernel. No kernel is running!")
525 528
526 529 def signal_kernel(self, signum):
527 530 """ Sends a signal to the kernel. """
528 531 if self.kernel is not None:
529 532 self.kernel.send_signal(signum)
530 533 else:
531 534 raise RuntimeError("Cannot signal kernel. No kernel is running!")
532 535
533 536 @property
534 537 def is_alive(self):
535 538 """Is the kernel process still running?"""
536 539 if self.kernel is not None:
537 540 if self.kernel.poll() is None:
538 541 return True
539 542 else:
540 543 return False
541 544 else:
542 545 # We didn't start the kernel with this KernelManager so we don't
543 546 # know if it is running. We should use a heartbeat for this case.
544 547 return True
545 548
546 549 #--------------------------------------------------------------------------
547 550 # Channels used for communication with the kernel:
548 551 #--------------------------------------------------------------------------
549 552
550 553 @property
551 554 def xreq_channel(self):
552 555 """Get the REQ socket channel object to make requests of the kernel."""
553 556 if self._xreq_channel is None:
554 557 self._xreq_channel = self.xreq_channel_class(self.context,
555 558 self.session,
556 559 self.xreq_address)
557 560 return self._xreq_channel
558 561
559 562 @property
560 563 def sub_channel(self):
561 564 """Get the SUB socket channel object."""
562 565 if self._sub_channel is None:
563 566 self._sub_channel = self.sub_channel_class(self.context,
564 567 self.session,
565 568 self.sub_address)
566 569 return self._sub_channel
567 570
568 571 @property
569 572 def rep_channel(self):
570 573 """Get the REP socket channel object to handle stdin (raw_input)."""
571 574 if self._rep_channel is None:
572 575 self._rep_channel = self.rep_channel_class(self.context,
573 576 self.session,
574 577 self.rep_address)
575 578 return self._rep_channel
@@ -1,472 +1,260 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 import os
21 20 import sys
22 21 import time
23 22 import traceback
24 23
25 24 # System library imports.
26 25 import zmq
27 26
28 27 # Local imports.
29 from IPython.external.argparse import ArgumentParser
30 from session import Session, Message
28 from IPython.utils.traitlets import HasTraits, Instance
31 29 from completer import KernelCompleter
32 from iostream import OutStream
33 from displayhook import DisplayHook
34 from exitpoller import ExitPollerUnix, ExitPollerWindows
30 from entry_point import base_launch_kernel, make_default_main
31 from session import Session, Message
35 32
36 33 #-----------------------------------------------------------------------------
37 34 # Main kernel class
38 35 #-----------------------------------------------------------------------------
39 36
40 class Kernel(object):
41
42 # The global kernel instance.
43 _kernel = None
44
45 # Maps user-friendly backend names to matplotlib backend identifiers.
46 _pylab_map = { 'tk': 'TkAgg',
47 'gtk': 'GTKAgg',
48 'wx': 'WXAgg',
49 'qt': 'Qt4Agg', # qt3 not supported
50 'qt4': 'Qt4Agg',
51 'payload-svg' : \
52 'module://IPython.zmq.pylab.backend_payload_svg' }
37 class Kernel(HasTraits):
53 38
54 39 #---------------------------------------------------------------------------
55 40 # Kernel interface
56 41 #---------------------------------------------------------------------------
57 42
58 def __init__(self, session, reply_socket, pub_socket, req_socket):
59 self.session = session
60 self.reply_socket = reply_socket
61 self.pub_socket = pub_socket
62 self.req_socket = req_socket
43 session = Instance(Session)
44 reply_socket = Instance('zmq.Socket')
45 pub_socket = Instance('zmq.Socket')
46 req_socket = Instance('zmq.Socket')
47
48 def __init__(self, **kwargs):
49 super(Kernel, self).__init__(**kwargs)
63 50 self.user_ns = {}
64 51 self.history = []
65 52 self.compiler = CommandCompiler()
66 53 self.completer = KernelCompleter(self.user_ns)
67 54
68 # Protected variables.
69 self._exec_payload = {}
70
71 55 # Build dict of handlers for message types
72 56 msg_types = [ 'execute_request', 'complete_request',
73 57 'object_info_request' ]
74 58 self.handlers = {}
75 59 for msg_type in msg_types:
76 60 self.handlers[msg_type] = getattr(self, msg_type)
77 61
78 def add_exec_payload(self, key, value):
79 """ Adds a key/value pair to the execute payload.
80 """
81 self._exec_payload[key] = value
82
83 def activate_pylab(self, backend=None, import_all=True):
84 """ Activates pylab in this kernel's namespace.
85
86 Parameters:
87 -----------
88 backend : str, optional
89 A valid backend name.
90
91 import_all : bool, optional
92 If true, an 'import *' is done from numpy and pylab.
93 """
94 # FIXME: This is adapted from IPython.lib.pylabtools.pylab_activate.
95 # Common funtionality should be refactored.
96
97 # We must set the desired backend before importing pylab.
98 import matplotlib
99 if backend:
100 backend_id = self._pylab_map[backend]
101 if backend_id.startswith('module://'):
102 # Work around bug in matplotlib: matplotlib.use converts the
103 # backend_id to lowercase even if a module name is specified!
104 matplotlib.rcParams['backend'] = backend_id
105 else:
106 matplotlib.use(backend_id)
107
108 # Import numpy as np/pyplot as plt are conventions we're trying to
109 # somewhat standardize on. Making them available to users by default
110 # will greatly help this.
111 exec ("import numpy\n"
112 "import matplotlib\n"
113 "from matplotlib import pylab, mlab, pyplot\n"
114 "np = numpy\n"
115 "plt = pyplot\n"
116 ) in self.user_ns
117
118 if import_all:
119 exec("from matplotlib.pylab import *\n"
120 "from numpy import *\n") in self.user_ns
121
122 matplotlib.interactive(True)
123
124 @classmethod
125 def get_kernel(cls):
126 """ Return the global kernel instance or raise a RuntimeError if it does
127 not exist.
128 """
129 if cls._kernel is None:
130 raise RuntimeError("Kernel not started!")
131 else:
132 return cls._kernel
133
134 62 def start(self):
135 63 """ Start the kernel main loop.
136 64 """
137 # Set the global kernel instance.
138 Kernel._kernel = self
139
140 65 while True:
141 66 ident = self.reply_socket.recv()
142 67 assert self.reply_socket.rcvmore(), "Missing message part."
143 68 msg = self.reply_socket.recv_json()
144 69 omsg = Message(msg)
145 70 print>>sys.__stdout__
146 71 print>>sys.__stdout__, omsg
147 72 handler = self.handlers.get(omsg.msg_type, None)
148 73 if handler is None:
149 74 print >> sys.__stderr__, "UNKNOWN MESSAGE TYPE:", omsg
150 75 else:
151 76 handler(ident, omsg)
152 77
153 78 #---------------------------------------------------------------------------
154 79 # Kernel request handlers
155 80 #---------------------------------------------------------------------------
156 81
157 82 def execute_request(self, ident, parent):
158 83 try:
159 84 code = parent[u'content'][u'code']
160 85 except:
161 86 print>>sys.__stderr__, "Got bad msg: "
162 87 print>>sys.__stderr__, Message(parent)
163 88 return
164 89 pyin_msg = self.session.msg(u'pyin',{u'code':code}, parent=parent)
165 90 self.pub_socket.send_json(pyin_msg)
166 91
167 # Clear the execute payload from the last request.
168 self._exec_payload = {}
169
170 92 try:
171 93 comp_code = self.compiler(code, '<zmq-kernel>')
172 94
173 95 # Replace raw_input. Note that is not sufficient to replace
174 96 # raw_input in the user namespace.
175 97 raw_input = lambda prompt='': self._raw_input(prompt, ident, parent)
176 98 __builtin__.raw_input = raw_input
177 99
178 100 # Configure the display hook.
179 101 sys.displayhook.set_parent(parent)
180 102
181 103 exec comp_code in self.user_ns, self.user_ns
182 104 except:
183 105 etype, evalue, tb = sys.exc_info()
184 106 tb = traceback.format_exception(etype, evalue, tb)
185 107 exc_content = {
186 108 u'status' : u'error',
187 109 u'traceback' : tb,
188 110 u'ename' : unicode(etype.__name__),
189 111 u'evalue' : unicode(evalue)
190 112 }
191 113 exc_msg = self.session.msg(u'pyerr', exc_content, parent)
192 114 self.pub_socket.send_json(exc_msg)
193 115 reply_content = exc_content
194 116 else:
195 reply_content = { 'status' : 'ok', 'payload' : self._exec_payload }
117 reply_content = { 'status' : 'ok', 'payload' : {} }
196 118
197 119 # Flush output before sending the reply.
198 120 sys.stderr.flush()
199 121 sys.stdout.flush()
200 122
201 123 # Send the reply.
202 124 reply_msg = self.session.msg(u'execute_reply', reply_content, parent)
203 125 print>>sys.__stdout__, Message(reply_msg)
204 126 self.reply_socket.send(ident, zmq.SNDMORE)
205 127 self.reply_socket.send_json(reply_msg)
206 128 if reply_msg['content']['status'] == u'error':
207 129 self._abort_queue()
208 130
209 131 def complete_request(self, ident, parent):
210 comp = self.completer.complete(parent.content.line, parent.content.text)
211 matches = {'matches' : comp, 'status' : 'ok'}
132 matches = {'matches' : self.complete(parent),
133 'status' : 'ok'}
212 134 completion_msg = self.session.send(self.reply_socket, 'complete_reply',
213 135 matches, parent, ident)
214 136 print >> sys.__stdout__, completion_msg
215 137
216 138 def object_info_request(self, ident, parent):
217 139 context = parent['content']['oname'].split('.')
218 140 object_info = self._object_info(context)
219 141 msg = self.session.send(self.reply_socket, 'object_info_reply',
220 142 object_info, parent, ident)
221 143 print >> sys.__stdout__, msg
222 144
223 145 #---------------------------------------------------------------------------
224 146 # Protected interface
225 147 #---------------------------------------------------------------------------
226 148
227 149 def _abort_queue(self):
228 150 while True:
229 151 try:
230 152 ident = self.reply_socket.recv(zmq.NOBLOCK)
231 153 except zmq.ZMQError, e:
232 154 if e.errno == zmq.EAGAIN:
233 155 break
234 156 else:
235 157 assert self.reply_socket.rcvmore(), "Missing message part."
236 158 msg = self.reply_socket.recv_json()
237 159 print>>sys.__stdout__, "Aborting:"
238 160 print>>sys.__stdout__, Message(msg)
239 161 msg_type = msg['msg_type']
240 162 reply_type = msg_type.split('_')[0] + '_reply'
241 163 reply_msg = self.session.msg(reply_type, {'status':'aborted'}, msg)
242 164 print>>sys.__stdout__, Message(reply_msg)
243 165 self.reply_socket.send(ident,zmq.SNDMORE)
244 166 self.reply_socket.send_json(reply_msg)
245 167 # We need to wait a bit for requests to come in. This can probably
246 168 # be set shorter for true asynchronous clients.
247 169 time.sleep(0.1)
248 170
249 171 def _raw_input(self, prompt, ident, parent):
250 172 # Flush output before making the request.
251 173 sys.stderr.flush()
252 174 sys.stdout.flush()
253 175
254 176 # Send the input request.
255 177 content = dict(prompt=prompt)
256 178 msg = self.session.msg(u'input_request', content, parent)
257 179 self.req_socket.send_json(msg)
258 180
259 181 # Await a response.
260 182 reply = self.req_socket.recv_json()
261 183 try:
262 184 value = reply['content']['value']
263 185 except:
264 186 print>>sys.__stderr__, "Got bad raw_input reply: "
265 187 print>>sys.__stderr__, Message(parent)
266 188 value = ''
267 189 return value
268 190
191 def _complete(self, msg):
192 return self.completer.complete(msg.content.line, msg.content.text)
193
269 194 def _object_info(self, context):
270 195 symbol, leftover = self._symbol_from_context(context)
271 196 if symbol is not None and not leftover:
272 197 doc = getattr(symbol, '__doc__', '')
273 198 else:
274 199 doc = ''
275 200 object_info = dict(docstring = doc)
276 201 return object_info
277 202
278 203 def _symbol_from_context(self, context):
279 204 if not context:
280 205 return None, context
281 206
282 207 base_symbol_string = context[0]
283 208 symbol = self.user_ns.get(base_symbol_string, None)
284 209 if symbol is None:
285 210 symbol = __builtin__.__dict__.get(base_symbol_string, None)
286 211 if symbol is None:
287 212 return None, context
288 213
289 214 context = context[1:]
290 215 for i, name in enumerate(context):
291 216 new_symbol = getattr(symbol, name, None)
292 217 if new_symbol is None:
293 218 return symbol, context[i:]
294 219 else:
295 220 symbol = new_symbol
296 221
297 222 return symbol, []
298 223
299 224 #-----------------------------------------------------------------------------
300 225 # Kernel main and launch functions
301 226 #-----------------------------------------------------------------------------
302 227
303 def bind_port(socket, ip, port):
304 """ Binds the specified ZMQ socket. If the port is zero, a random port is
305 chosen. Returns the port that was bound.
306 """
307 connection = 'tcp://%s' % ip
308 if port <= 0:
309 port = socket.bind_to_random_port(connection)
310 else:
311 connection += ':%i' % port
312 socket.bind(connection)
313 return port
314
315
316 def main():
317 """ Main entry point for launching a kernel.
318 """
319 # Parse command line arguments.
320 parser = ArgumentParser()
321 parser.add_argument('--ip', type=str, default='127.0.0.1',
322 help='set the kernel\'s IP address [default: local]')
323 parser.add_argument('--xrep', type=int, metavar='PORT', default=0,
324 help='set the XREP channel port [default: random]')
325 parser.add_argument('--pub', type=int, metavar='PORT', default=0,
326 help='set the PUB channel port [default: random]')
327 parser.add_argument('--req', type=int, metavar='PORT', default=0,
328 help='set the REQ channel port [default: random]')
329 if sys.platform == 'win32':
330 parser.add_argument('--parent', type=int, metavar='HANDLE',
331 default=0, help='kill this process if the process '
332 'with HANDLE dies')
333 else:
334 parser.add_argument('--parent', action='store_true',
335 help='kill this process if its parent dies')
336 parser.add_argument('--pylab', type=str, metavar='GUI', nargs='?',
337 const='auto', help = \
338 "Pre-load matplotlib and numpy for interactive use. If GUI is not \
339 given, the GUI backend is matplotlib's, otherwise use one of: \
340 ['tk', 'gtk', 'qt', 'wx', 'payload-svg'].")
341
342 namespace = parser.parse_args()
343
344 # Create a context, a session, and the kernel sockets.
345 print >>sys.__stdout__, "Starting the kernel..."
346 context = zmq.Context()
347 session = Session(username=u'kernel')
348
349 reply_socket = context.socket(zmq.XREP)
350 xrep_port = bind_port(reply_socket, namespace.ip, namespace.xrep)
351 print >>sys.__stdout__, "XREP Channel on port", xrep_port
352
353 pub_socket = context.socket(zmq.PUB)
354 pub_port = bind_port(pub_socket, namespace.ip, namespace.pub)
355 print >>sys.__stdout__, "PUB Channel on port", pub_port
356
357 req_socket = context.socket(zmq.XREQ)
358 req_port = bind_port(req_socket, namespace.ip, namespace.req)
359 print >>sys.__stdout__, "REQ Channel on port", req_port
360
361 # Create the kernel.
362 kernel = Kernel(session, reply_socket, pub_socket, req_socket)
363
364 # Set up pylab, if necessary.
365 if namespace.pylab:
366 if namespace.pylab == 'auto':
367 kernel.activate_pylab()
368 else:
369 kernel.activate_pylab(namespace.pylab)
370
371 # Redirect input streams and set a display hook.
372 sys.stdout = OutStream(session, pub_socket, u'stdout')
373 sys.stderr = OutStream(session, pub_socket, u'stderr')
374 sys.displayhook = DisplayHook(session, pub_socket)
375
376 # Configure this kernel/process to die on parent termination, if necessary.
377 if namespace.parent:
378 if sys.platform == 'win32':
379 poller = ExitPollerWindows(namespace.parent)
380 else:
381 poller = ExitPollerUnix()
382 poller.start()
383
384 # Start the kernel mainloop.
385 kernel.start()
386
387
388 def launch_kernel(xrep_port=0, pub_port=0, req_port=0,
389 pylab=False, independent=False):
228 def launch_kernel(xrep_port=0, pub_port=0, req_port=0, independent=False):
390 229 """ Launches a localhost kernel, binding to the specified ports.
391 230
392 231 Parameters
393 232 ----------
394 233 xrep_port : int, optional
395 234 The port to use for XREP channel.
396 235
397 236 pub_port : int, optional
398 237 The port to use for the SUB channel.
399 238
400 239 req_port : int, optional
401 240 The port to use for the REQ (raw input) channel.
402 241
403 pylab : bool or string, optional (default False)
404 If not False, the kernel will be launched with pylab enabled. If a
405 string is passed, matplotlib will use the specified backend. Otherwise,
406 matplotlib's default backend will be used.
407
408 242 independent : bool, optional (default False)
409 243 If set, the kernel process is guaranteed to survive if this process
410 244 dies. If not set, an effort is made to ensure that the kernel is killed
411 245 when this process dies. Note that in this case it is still good practice
412 246 to kill kernels manually before exiting.
413 247
414 248 Returns
415 249 -------
416 250 A tuple of form:
417 251 (kernel_process, xrep_port, pub_port, req_port)
418 252 where kernel_process is a Popen object and the ports are integers.
419 253 """
420 import socket
421 from subprocess import Popen
422
423 # Find open ports as necessary.
424 ports = []
425 ports_needed = int(xrep_port <= 0) + int(pub_port <= 0) + int(req_port <= 0)
426 for i in xrange(ports_needed):
427 sock = socket.socket()
428 sock.bind(('', 0))
429 ports.append(sock)
430 for i, sock in enumerate(ports):
431 port = sock.getsockname()[1]
432 sock.close()
433 ports[i] = port
434 if xrep_port <= 0:
435 xrep_port = ports.pop(0)
436 if pub_port <= 0:
437 pub_port = ports.pop(0)
438 if req_port <= 0:
439 req_port = ports.pop(0)
440
441 # Build the kernel launch command.
442 command = 'from IPython.zmq.pykernel import main; main()'
443 arguments = [ sys.executable, '-c', command, '--xrep', str(xrep_port),
444 '--pub', str(pub_port), '--req', str(req_port) ]
445 if pylab:
446 arguments.append('--pylab')
447 if isinstance(pylab, basestring):
448 arguments.append(pylab)
449
450 # Spawn a kernel.
451 if independent:
452 if sys.platform == 'win32':
453 proc = Popen(['start', '/b'] + arguments, shell=True)
454 else:
455 proc = Popen(arguments, preexec_fn=lambda: os.setsid())
456 else:
457 if sys.platform == 'win32':
458 from _subprocess import DuplicateHandle, GetCurrentProcess, \
459 DUPLICATE_SAME_ACCESS
460 pid = GetCurrentProcess()
461 handle = DuplicateHandle(pid, pid, pid, 0,
462 True, # Inheritable by new processes.
463 DUPLICATE_SAME_ACCESS)
464 proc = Popen(arguments + ['--parent', str(int(handle))])
465 else:
466 proc = Popen(arguments + ['--parent'])
254 return base_launch_kernel('from IPython.zmq.pykernel import main; main()',
255 xrep_port, pub_port, req_port, independent)
467 256
468 return proc, xrep_port, pub_port, req_port
469
257 main = make_default_main(Kernel)
470 258
471 259 if __name__ == '__main__':
472 260 main()
@@ -1,23 +1,23 b''
1 1 """ Provides basic funtionality for payload backends.
2 2 """
3 3
4 4 # Local imports.
5 from IPython.zmq.kernel import Kernel
5 from IPython.zmq.ipkernel import Kernel
6 6
7 7
8 8 def add_plot_payload(format, data, metadata={}):
9 9 """ Add a plot payload to the current execution reply.
10 10
11 11 Parameters:
12 12 -----------
13 13 format : str
14 14 Identifies the format of the plot data.
15 15
16 16 data : str
17 17 The raw plot data.
18 18
19 19 metadata : dict, optional [default empty]
20 20 Allows for specification of additional information about the plot data.
21 21 """
22 22 payload = dict(format=format, data=data, metadata=metadata)
23 23 Kernel.get_kernel().add_exec_payload('plot', payload)
General Comments 0
You need to be logged in to leave comments. Login now