##// END OF EJS Templates
move zmq.KernelManagers into IPython.kernel
MinRK -
Show More
@@ -1,362 +1,362 b''
1 """ A minimal application base mixin for all ZMQ based IPython frontends.
1 """ A minimal application base mixin for all ZMQ based IPython frontends.
2
2
3 This is not a complete console app, as subprocess will not be able to receive
3 This is not a complete console app, as subprocess will not be able to receive
4 input, there is no real readline support, among other limitations. This is a
4 input, there is no real readline support, among other limitations. This is a
5 refactoring of what used to be the IPython/frontend/qt/console/qtconsoleapp.py
5 refactoring of what used to be the IPython/frontend/qt/console/qtconsoleapp.py
6
6
7 Authors:
7 Authors:
8
8
9 * Evan Patterson
9 * Evan Patterson
10 * Min RK
10 * Min RK
11 * Erik Tollerud
11 * Erik Tollerud
12 * Fernando Perez
12 * Fernando Perez
13 * Bussonnier Matthias
13 * Bussonnier Matthias
14 * Thomas Kluyver
14 * Thomas Kluyver
15 * Paul Ivanov
15 * Paul Ivanov
16
16
17 """
17 """
18
18
19 #-----------------------------------------------------------------------------
19 #-----------------------------------------------------------------------------
20 # Imports
20 # Imports
21 #-----------------------------------------------------------------------------
21 #-----------------------------------------------------------------------------
22
22
23 # stdlib imports
23 # stdlib imports
24 import atexit
24 import atexit
25 import json
25 import json
26 import os
26 import os
27 import shutil
27 import shutil
28 import signal
28 import signal
29 import sys
29 import sys
30 import uuid
30 import uuid
31
31
32
32
33 # Local imports
33 # Local imports
34 from IPython.config.application import boolean_flag
34 from IPython.config.application import boolean_flag
35 from IPython.config.configurable import Configurable
35 from IPython.config.configurable import Configurable
36 from IPython.core.profiledir import ProfileDir
36 from IPython.core.profiledir import ProfileDir
37 from IPython.zmq.blockingkernelmanager import BlockingKernelManager
37 from IPython.kernel.blockingkernelmanager import BlockingKernelManager
38 from IPython.zmq.kernelmanager import KernelManager
38 from IPython.kernel.kernelmanager import KernelManager
39 from IPython.kernel import tunnel_to_kernel, find_connection_file, swallow_argv
39 from IPython.kernel import tunnel_to_kernel, find_connection_file, swallow_argv
40 from IPython.utils.path import filefind
40 from IPython.utils.path import filefind
41 from IPython.utils.py3compat import str_to_bytes
41 from IPython.utils.py3compat import str_to_bytes
42 from IPython.utils.traitlets import (
42 from IPython.utils.traitlets import (
43 Dict, List, Unicode, CUnicode, Int, CBool, Any, CaselessStrEnum
43 Dict, List, Unicode, CUnicode, Int, CBool, Any, CaselessStrEnum
44 )
44 )
45 from IPython.zmq.kernelapp import (
45 from IPython.zmq.kernelapp import (
46 kernel_flags,
46 kernel_flags,
47 kernel_aliases,
47 kernel_aliases,
48 IPKernelApp
48 IPKernelApp
49 )
49 )
50 from IPython.zmq.session import Session, default_secure
50 from IPython.zmq.session import Session, default_secure
51 from IPython.zmq.zmqshell import ZMQInteractiveShell
51 from IPython.zmq.zmqshell import ZMQInteractiveShell
52
52
53 #-----------------------------------------------------------------------------
53 #-----------------------------------------------------------------------------
54 # Network Constants
54 # Network Constants
55 #-----------------------------------------------------------------------------
55 #-----------------------------------------------------------------------------
56
56
57 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
57 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
58
58
59 #-----------------------------------------------------------------------------
59 #-----------------------------------------------------------------------------
60 # Globals
60 # Globals
61 #-----------------------------------------------------------------------------
61 #-----------------------------------------------------------------------------
62
62
63
63
64 #-----------------------------------------------------------------------------
64 #-----------------------------------------------------------------------------
65 # Aliases and Flags
65 # Aliases and Flags
66 #-----------------------------------------------------------------------------
66 #-----------------------------------------------------------------------------
67
67
68 flags = dict(kernel_flags)
68 flags = dict(kernel_flags)
69
69
70 # the flags that are specific to the frontend
70 # the flags that are specific to the frontend
71 # these must be scrubbed before being passed to the kernel,
71 # these must be scrubbed before being passed to the kernel,
72 # or it will raise an error on unrecognized flags
72 # or it will raise an error on unrecognized flags
73 app_flags = {
73 app_flags = {
74 'existing' : ({'IPythonConsoleApp' : {'existing' : 'kernel*.json'}},
74 'existing' : ({'IPythonConsoleApp' : {'existing' : 'kernel*.json'}},
75 "Connect to an existing kernel. If no argument specified, guess most recent"),
75 "Connect to an existing kernel. If no argument specified, guess most recent"),
76 }
76 }
77 app_flags.update(boolean_flag(
77 app_flags.update(boolean_flag(
78 'confirm-exit', 'IPythonConsoleApp.confirm_exit',
78 'confirm-exit', 'IPythonConsoleApp.confirm_exit',
79 """Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
79 """Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
80 to force a direct exit without any confirmation.
80 to force a direct exit without any confirmation.
81 """,
81 """,
82 """Don't prompt the user when exiting. This will terminate the kernel
82 """Don't prompt the user when exiting. This will terminate the kernel
83 if it is owned by the frontend, and leave it alive if it is external.
83 if it is owned by the frontend, and leave it alive if it is external.
84 """
84 """
85 ))
85 ))
86 flags.update(app_flags)
86 flags.update(app_flags)
87
87
88 aliases = dict(kernel_aliases)
88 aliases = dict(kernel_aliases)
89
89
90 # also scrub aliases from the frontend
90 # also scrub aliases from the frontend
91 app_aliases = dict(
91 app_aliases = dict(
92 ip = 'KernelManager.ip',
92 ip = 'KernelManager.ip',
93 transport = 'KernelManager.transport',
93 transport = 'KernelManager.transport',
94 hb = 'IPythonConsoleApp.hb_port',
94 hb = 'IPythonConsoleApp.hb_port',
95 shell = 'IPythonConsoleApp.shell_port',
95 shell = 'IPythonConsoleApp.shell_port',
96 iopub = 'IPythonConsoleApp.iopub_port',
96 iopub = 'IPythonConsoleApp.iopub_port',
97 stdin = 'IPythonConsoleApp.stdin_port',
97 stdin = 'IPythonConsoleApp.stdin_port',
98 existing = 'IPythonConsoleApp.existing',
98 existing = 'IPythonConsoleApp.existing',
99 f = 'IPythonConsoleApp.connection_file',
99 f = 'IPythonConsoleApp.connection_file',
100
100
101
101
102 ssh = 'IPythonConsoleApp.sshserver',
102 ssh = 'IPythonConsoleApp.sshserver',
103 )
103 )
104 aliases.update(app_aliases)
104 aliases.update(app_aliases)
105
105
106 #-----------------------------------------------------------------------------
106 #-----------------------------------------------------------------------------
107 # Classes
107 # Classes
108 #-----------------------------------------------------------------------------
108 #-----------------------------------------------------------------------------
109
109
110 #-----------------------------------------------------------------------------
110 #-----------------------------------------------------------------------------
111 # IPythonConsole
111 # IPythonConsole
112 #-----------------------------------------------------------------------------
112 #-----------------------------------------------------------------------------
113
113
114 classes = [IPKernelApp, ZMQInteractiveShell, KernelManager, ProfileDir, Session]
114 classes = [IPKernelApp, ZMQInteractiveShell, KernelManager, ProfileDir, Session]
115
115
116 try:
116 try:
117 from IPython.zmq.pylab.backend_inline import InlineBackend
117 from IPython.zmq.pylab.backend_inline import InlineBackend
118 except ImportError:
118 except ImportError:
119 pass
119 pass
120 else:
120 else:
121 classes.append(InlineBackend)
121 classes.append(InlineBackend)
122
122
123 class IPythonConsoleApp(Configurable):
123 class IPythonConsoleApp(Configurable):
124 name = 'ipython-console-mixin'
124 name = 'ipython-console-mixin'
125 default_config_file_name='ipython_config.py'
125 default_config_file_name='ipython_config.py'
126
126
127 description = """
127 description = """
128 The IPython Mixin Console.
128 The IPython Mixin Console.
129
129
130 This class contains the common portions of console client (QtConsole,
130 This class contains the common portions of console client (QtConsole,
131 ZMQ-based terminal console, etc). It is not a full console, in that
131 ZMQ-based terminal console, etc). It is not a full console, in that
132 launched terminal subprocesses will not be able to accept input.
132 launched terminal subprocesses will not be able to accept input.
133
133
134 The Console using this mixing supports various extra features beyond
134 The Console using this mixing supports various extra features beyond
135 the single-process Terminal IPython shell, such as connecting to
135 the single-process Terminal IPython shell, such as connecting to
136 existing kernel, via:
136 existing kernel, via:
137
137
138 ipython <appname> --existing
138 ipython <appname> --existing
139
139
140 as well as tunnel via SSH
140 as well as tunnel via SSH
141
141
142 """
142 """
143
143
144 classes = classes
144 classes = classes
145 flags = Dict(flags)
145 flags = Dict(flags)
146 aliases = Dict(aliases)
146 aliases = Dict(aliases)
147 kernel_manager_class = BlockingKernelManager
147 kernel_manager_class = BlockingKernelManager
148
148
149 kernel_argv = List(Unicode)
149 kernel_argv = List(Unicode)
150 # frontend flags&aliases to be stripped when building kernel_argv
150 # frontend flags&aliases to be stripped when building kernel_argv
151 frontend_flags = Any(app_flags)
151 frontend_flags = Any(app_flags)
152 frontend_aliases = Any(app_aliases)
152 frontend_aliases = Any(app_aliases)
153
153
154 # create requested profiles by default, if they don't exist:
154 # create requested profiles by default, if they don't exist:
155 auto_create = CBool(True)
155 auto_create = CBool(True)
156 # connection info:
156 # connection info:
157
157
158 sshserver = Unicode('', config=True,
158 sshserver = Unicode('', config=True,
159 help="""The SSH server to use to connect to the kernel.""")
159 help="""The SSH server to use to connect to the kernel.""")
160 sshkey = Unicode('', config=True,
160 sshkey = Unicode('', config=True,
161 help="""Path to the ssh key to use for logging in to the ssh server.""")
161 help="""Path to the ssh key to use for logging in to the ssh server.""")
162
162
163 hb_port = Int(0, config=True,
163 hb_port = Int(0, config=True,
164 help="set the heartbeat port [default: random]")
164 help="set the heartbeat port [default: random]")
165 shell_port = Int(0, config=True,
165 shell_port = Int(0, config=True,
166 help="set the shell (ROUTER) port [default: random]")
166 help="set the shell (ROUTER) port [default: random]")
167 iopub_port = Int(0, config=True,
167 iopub_port = Int(0, config=True,
168 help="set the iopub (PUB) port [default: random]")
168 help="set the iopub (PUB) port [default: random]")
169 stdin_port = Int(0, config=True,
169 stdin_port = Int(0, config=True,
170 help="set the stdin (DEALER) port [default: random]")
170 help="set the stdin (DEALER) port [default: random]")
171 connection_file = Unicode('', config=True,
171 connection_file = Unicode('', config=True,
172 help="""JSON file in which to store connection info [default: kernel-<pid>.json]
172 help="""JSON file in which to store connection info [default: kernel-<pid>.json]
173
173
174 This file will contain the IP, ports, and authentication key needed to connect
174 This file will contain the IP, ports, and authentication key needed to connect
175 clients to this kernel. By default, this file will be created in the security-dir
175 clients to this kernel. By default, this file will be created in the security-dir
176 of the current profile, but can be specified by absolute path.
176 of the current profile, but can be specified by absolute path.
177 """)
177 """)
178 def _connection_file_default(self):
178 def _connection_file_default(self):
179 return 'kernel-%i.json' % os.getpid()
179 return 'kernel-%i.json' % os.getpid()
180
180
181 existing = CUnicode('', config=True,
181 existing = CUnicode('', config=True,
182 help="""Connect to an already running kernel""")
182 help="""Connect to an already running kernel""")
183
183
184 confirm_exit = CBool(True, config=True,
184 confirm_exit = CBool(True, config=True,
185 help="""
185 help="""
186 Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
186 Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
187 to force a direct exit without any confirmation.""",
187 to force a direct exit without any confirmation.""",
188 )
188 )
189
189
190
190
191 def build_kernel_argv(self, argv=None):
191 def build_kernel_argv(self, argv=None):
192 """build argv to be passed to kernel subprocess"""
192 """build argv to be passed to kernel subprocess"""
193 if argv is None:
193 if argv is None:
194 argv = sys.argv[1:]
194 argv = sys.argv[1:]
195 self.kernel_argv = swallow_argv(argv, self.frontend_aliases, self.frontend_flags)
195 self.kernel_argv = swallow_argv(argv, self.frontend_aliases, self.frontend_flags)
196 # kernel should inherit default config file from frontend
196 # kernel should inherit default config file from frontend
197 self.kernel_argv.append("--KernelApp.parent_appname='%s'"%self.name)
197 self.kernel_argv.append("--KernelApp.parent_appname='%s'"%self.name)
198
198
199 def init_connection_file(self):
199 def init_connection_file(self):
200 """find the connection file, and load the info if found.
200 """find the connection file, and load the info if found.
201
201
202 The current working directory and the current profile's security
202 The current working directory and the current profile's security
203 directory will be searched for the file if it is not given by
203 directory will be searched for the file if it is not given by
204 absolute path.
204 absolute path.
205
205
206 When attempting to connect to an existing kernel and the `--existing`
206 When attempting to connect to an existing kernel and the `--existing`
207 argument does not match an existing file, it will be interpreted as a
207 argument does not match an existing file, it will be interpreted as a
208 fileglob, and the matching file in the current profile's security dir
208 fileglob, and the matching file in the current profile's security dir
209 with the latest access time will be used.
209 with the latest access time will be used.
210
210
211 After this method is called, self.connection_file contains the *full path*
211 After this method is called, self.connection_file contains the *full path*
212 to the connection file, never just its name.
212 to the connection file, never just its name.
213 """
213 """
214 if self.existing:
214 if self.existing:
215 try:
215 try:
216 cf = find_connection_file(self.existing)
216 cf = find_connection_file(self.existing)
217 except Exception:
217 except Exception:
218 self.log.critical("Could not find existing kernel connection file %s", self.existing)
218 self.log.critical("Could not find existing kernel connection file %s", self.existing)
219 self.exit(1)
219 self.exit(1)
220 self.log.info("Connecting to existing kernel: %s" % cf)
220 self.log.info("Connecting to existing kernel: %s" % cf)
221 self.connection_file = cf
221 self.connection_file = cf
222 else:
222 else:
223 # not existing, check if we are going to write the file
223 # not existing, check if we are going to write the file
224 # and ensure that self.connection_file is a full path, not just the shortname
224 # and ensure that self.connection_file is a full path, not just the shortname
225 try:
225 try:
226 cf = find_connection_file(self.connection_file)
226 cf = find_connection_file(self.connection_file)
227 except Exception:
227 except Exception:
228 # file might not exist
228 # file might not exist
229 if self.connection_file == os.path.basename(self.connection_file):
229 if self.connection_file == os.path.basename(self.connection_file):
230 # just shortname, put it in security dir
230 # just shortname, put it in security dir
231 cf = os.path.join(self.profile_dir.security_dir, self.connection_file)
231 cf = os.path.join(self.profile_dir.security_dir, self.connection_file)
232 else:
232 else:
233 cf = self.connection_file
233 cf = self.connection_file
234 self.connection_file = cf
234 self.connection_file = cf
235
235
236 # should load_connection_file only be used for existing?
236 # should load_connection_file only be used for existing?
237 # as it is now, this allows reusing ports if an existing
237 # as it is now, this allows reusing ports if an existing
238 # file is requested
238 # file is requested
239 try:
239 try:
240 self.load_connection_file()
240 self.load_connection_file()
241 except Exception:
241 except Exception:
242 self.log.error("Failed to load connection file: %r", self.connection_file, exc_info=True)
242 self.log.error("Failed to load connection file: %r", self.connection_file, exc_info=True)
243 self.exit(1)
243 self.exit(1)
244
244
245 def load_connection_file(self):
245 def load_connection_file(self):
246 """load ip/port/hmac config from JSON connection file"""
246 """load ip/port/hmac config from JSON connection file"""
247 # this is identical to KernelApp.load_connection_file
247 # this is identical to KernelApp.load_connection_file
248 # perhaps it can be centralized somewhere?
248 # perhaps it can be centralized somewhere?
249 try:
249 try:
250 fname = filefind(self.connection_file, ['.', self.profile_dir.security_dir])
250 fname = filefind(self.connection_file, ['.', self.profile_dir.security_dir])
251 except IOError:
251 except IOError:
252 self.log.debug("Connection File not found: %s", self.connection_file)
252 self.log.debug("Connection File not found: %s", self.connection_file)
253 return
253 return
254 self.log.debug(u"Loading connection file %s", fname)
254 self.log.debug(u"Loading connection file %s", fname)
255 with open(fname) as f:
255 with open(fname) as f:
256 cfg = json.load(f)
256 cfg = json.load(f)
257
257
258 self.config.KernelManager.transport = cfg.get('transport', 'tcp')
258 self.config.KernelManager.transport = cfg.get('transport', 'tcp')
259 self.config.KernelManager.ip = cfg.get('ip', LOCALHOST)
259 self.config.KernelManager.ip = cfg.get('ip', LOCALHOST)
260
260
261 for channel in ('hb', 'shell', 'iopub', 'stdin'):
261 for channel in ('hb', 'shell', 'iopub', 'stdin'):
262 name = channel + '_port'
262 name = channel + '_port'
263 if getattr(self, name) == 0 and name in cfg:
263 if getattr(self, name) == 0 and name in cfg:
264 # not overridden by config or cl_args
264 # not overridden by config or cl_args
265 setattr(self, name, cfg[name])
265 setattr(self, name, cfg[name])
266 if 'key' in cfg:
266 if 'key' in cfg:
267 self.config.Session.key = str_to_bytes(cfg['key'])
267 self.config.Session.key = str_to_bytes(cfg['key'])
268
268
269 def init_ssh(self):
269 def init_ssh(self):
270 """set up ssh tunnels, if needed."""
270 """set up ssh tunnels, if needed."""
271 if not self.existing or (not self.sshserver and not self.sshkey):
271 if not self.existing or (not self.sshserver and not self.sshkey):
272 return
272 return
273
273
274 self.load_connection_file()
274 self.load_connection_file()
275
275
276 transport = self.config.KernelManager.transport
276 transport = self.config.KernelManager.transport
277 ip = self.config.KernelManager.ip
277 ip = self.config.KernelManager.ip
278
278
279 if transport != 'tcp':
279 if transport != 'tcp':
280 self.log.error("Can only use ssh tunnels with TCP sockets, not %s", transport)
280 self.log.error("Can only use ssh tunnels with TCP sockets, not %s", transport)
281 sys.exit(-1)
281 sys.exit(-1)
282
282
283 if self.sshkey and not self.sshserver:
283 if self.sshkey and not self.sshserver:
284 # specifying just the key implies that we are connecting directly
284 # specifying just the key implies that we are connecting directly
285 self.sshserver = ip
285 self.sshserver = ip
286 ip = LOCALHOST
286 ip = LOCALHOST
287
287
288 # build connection dict for tunnels:
288 # build connection dict for tunnels:
289 info = dict(ip=ip,
289 info = dict(ip=ip,
290 shell_port=self.shell_port,
290 shell_port=self.shell_port,
291 iopub_port=self.iopub_port,
291 iopub_port=self.iopub_port,
292 stdin_port=self.stdin_port,
292 stdin_port=self.stdin_port,
293 hb_port=self.hb_port
293 hb_port=self.hb_port
294 )
294 )
295
295
296 self.log.info("Forwarding connections to %s via %s"%(ip, self.sshserver))
296 self.log.info("Forwarding connections to %s via %s"%(ip, self.sshserver))
297
297
298 # tunnels return a new set of ports, which will be on localhost:
298 # tunnels return a new set of ports, which will be on localhost:
299 self.config.KernelManager.ip = LOCALHOST
299 self.config.KernelManager.ip = LOCALHOST
300 try:
300 try:
301 newports = tunnel_to_kernel(info, self.sshserver, self.sshkey)
301 newports = tunnel_to_kernel(info, self.sshserver, self.sshkey)
302 except:
302 except:
303 # even catch KeyboardInterrupt
303 # even catch KeyboardInterrupt
304 self.log.error("Could not setup tunnels", exc_info=True)
304 self.log.error("Could not setup tunnels", exc_info=True)
305 self.exit(1)
305 self.exit(1)
306
306
307 self.shell_port, self.iopub_port, self.stdin_port, self.hb_port = newports
307 self.shell_port, self.iopub_port, self.stdin_port, self.hb_port = newports
308
308
309 cf = self.connection_file
309 cf = self.connection_file
310 base,ext = os.path.splitext(cf)
310 base,ext = os.path.splitext(cf)
311 base = os.path.basename(base)
311 base = os.path.basename(base)
312 self.connection_file = os.path.basename(base)+'-ssh'+ext
312 self.connection_file = os.path.basename(base)+'-ssh'+ext
313 self.log.critical("To connect another client via this tunnel, use:")
313 self.log.critical("To connect another client via this tunnel, use:")
314 self.log.critical("--existing %s" % self.connection_file)
314 self.log.critical("--existing %s" % self.connection_file)
315
315
316 def _new_connection_file(self):
316 def _new_connection_file(self):
317 cf = ''
317 cf = ''
318 while not cf:
318 while not cf:
319 # we don't need a 128b id to distinguish kernels, use more readable
319 # we don't need a 128b id to distinguish kernels, use more readable
320 # 48b node segment (12 hex chars). Users running more than 32k simultaneous
320 # 48b node segment (12 hex chars). Users running more than 32k simultaneous
321 # kernels can subclass.
321 # kernels can subclass.
322 ident = str(uuid.uuid4()).split('-')[-1]
322 ident = str(uuid.uuid4()).split('-')[-1]
323 cf = os.path.join(self.profile_dir.security_dir, 'kernel-%s.json' % ident)
323 cf = os.path.join(self.profile_dir.security_dir, 'kernel-%s.json' % ident)
324 # only keep if it's actually new. Protect against unlikely collision
324 # only keep if it's actually new. Protect against unlikely collision
325 # in 48b random search space
325 # in 48b random search space
326 cf = cf if not os.path.exists(cf) else ''
326 cf = cf if not os.path.exists(cf) else ''
327 return cf
327 return cf
328
328
329 def init_kernel_manager(self):
329 def init_kernel_manager(self):
330 # Don't let Qt or ZMQ swallow KeyboardInterupts.
330 # Don't let Qt or ZMQ swallow KeyboardInterupts.
331 signal.signal(signal.SIGINT, signal.SIG_DFL)
331 signal.signal(signal.SIGINT, signal.SIG_DFL)
332
332
333 # Create a KernelManager and start a kernel.
333 # Create a KernelManager and start a kernel.
334 self.kernel_manager = self.kernel_manager_class(
334 self.kernel_manager = self.kernel_manager_class(
335 shell_port=self.shell_port,
335 shell_port=self.shell_port,
336 iopub_port=self.iopub_port,
336 iopub_port=self.iopub_port,
337 stdin_port=self.stdin_port,
337 stdin_port=self.stdin_port,
338 hb_port=self.hb_port,
338 hb_port=self.hb_port,
339 connection_file=self.connection_file,
339 connection_file=self.connection_file,
340 config=self.config,
340 config=self.config,
341 )
341 )
342 # start the kernel
342 # start the kernel
343 if not self.existing:
343 if not self.existing:
344 self.kernel_manager.start_kernel(extra_arguments=self.kernel_argv)
344 self.kernel_manager.start_kernel(extra_arguments=self.kernel_argv)
345 atexit.register(self.kernel_manager.cleanup_ipc_files)
345 atexit.register(self.kernel_manager.cleanup_ipc_files)
346 elif self.sshserver:
346 elif self.sshserver:
347 # ssh, write new connection file
347 # ssh, write new connection file
348 self.kernel_manager.write_connection_file()
348 self.kernel_manager.write_connection_file()
349 atexit.register(self.kernel_manager.cleanup_connection_file)
349 atexit.register(self.kernel_manager.cleanup_connection_file)
350 self.kernel_manager.start_channels()
350 self.kernel_manager.start_channels()
351
351
352
352
353 def initialize(self, argv=None):
353 def initialize(self, argv=None):
354 """
354 """
355 Classes which mix this class in should call:
355 Classes which mix this class in should call:
356 IPythonConsoleApp.initialize(self,argv)
356 IPythonConsoleApp.initialize(self,argv)
357 """
357 """
358 self.init_connection_file()
358 self.init_connection_file()
359 default_secure(self.config)
359 default_secure(self.config)
360 self.init_ssh()
360 self.init_ssh()
361 self.init_kernel_manager()
361 self.init_kernel_manager()
362
362
@@ -1,364 +1,364 b''
1 """A kernel manager for multiple kernels.
1 """A kernel manager for multiple kernels.
2
2
3 Authors:
3 Authors:
4
4
5 * Brian Granger
5 * Brian Granger
6 """
6 """
7
7
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2008-2011 The IPython Development Team
9 # Copyright (C) 2008-2011 The IPython Development Team
10 #
10 #
11 # Distributed under the terms of the BSD License. The full license is in
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
12 # the file COPYING, distributed as part of this software.
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 # Imports
16 # Imports
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18
18
19 import os
19 import os
20 import uuid
20 import uuid
21
21
22 import zmq
22 import zmq
23 from zmq.eventloop.zmqstream import ZMQStream
23 from zmq.eventloop.zmqstream import ZMQStream
24
24
25 from tornado import web
25 from tornado import web
26
26
27 from IPython.config.configurable import LoggingConfigurable
27 from IPython.config.configurable import LoggingConfigurable
28 from IPython.utils.importstring import import_item
28 from IPython.utils.importstring import import_item
29 from IPython.utils.traitlets import (
29 from IPython.utils.traitlets import (
30 Instance, Dict, List, Unicode, Float, Integer, Any, DottedObjectName,
30 Instance, Dict, List, Unicode, Float, Integer, Any, DottedObjectName,
31 )
31 )
32 #-----------------------------------------------------------------------------
32 #-----------------------------------------------------------------------------
33 # Classes
33 # Classes
34 #-----------------------------------------------------------------------------
34 #-----------------------------------------------------------------------------
35
35
36 class DuplicateKernelError(Exception):
36 class DuplicateKernelError(Exception):
37 pass
37 pass
38
38
39
39
40 class MultiKernelManager(LoggingConfigurable):
40 class MultiKernelManager(LoggingConfigurable):
41 """A class for managing multiple kernels."""
41 """A class for managing multiple kernels."""
42
42
43 kernel_manager_class = DottedObjectName(
43 kernel_manager_class = DottedObjectName(
44 "IPython.zmq.blockingkernelmanager.BlockingKernelManager", config=True,
44 "IPython.kernel.blockingkernelmanager.BlockingKernelManager", config=True,
45 help="""The kernel manager class. This is configurable to allow
45 help="""The kernel manager class. This is configurable to allow
46 subclassing of the KernelManager for customized behavior.
46 subclassing of the KernelManager for customized behavior.
47 """
47 """
48 )
48 )
49 def _kernel_manager_class_changed(self, name, old, new):
49 def _kernel_manager_class_changed(self, name, old, new):
50 self.kernel_manager_factory = import_item(new)
50 self.kernel_manager_factory = import_item(new)
51
51
52 kernel_manager_factory = Any(help="this is kernel_manager_class after import")
52 kernel_manager_factory = Any(help="this is kernel_manager_class after import")
53 def _kernel_manager_factory_default(self):
53 def _kernel_manager_factory_default(self):
54 return import_item(self.kernel_manager_class)
54 return import_item(self.kernel_manager_class)
55
55
56 context = Instance('zmq.Context')
56 context = Instance('zmq.Context')
57 def _context_default(self):
57 def _context_default(self):
58 return zmq.Context.instance()
58 return zmq.Context.instance()
59
59
60 connection_dir = Unicode('')
60 connection_dir = Unicode('')
61
61
62 _kernels = Dict()
62 _kernels = Dict()
63
63
64 def list_kernel_ids(self):
64 def list_kernel_ids(self):
65 """Return a list of the kernel ids of the active kernels."""
65 """Return a list of the kernel ids of the active kernels."""
66 # Create a copy so we can iterate over kernels in operations
66 # Create a copy so we can iterate over kernels in operations
67 # that delete keys.
67 # that delete keys.
68 return list(self._kernels.keys())
68 return list(self._kernels.keys())
69
69
70 def __len__(self):
70 def __len__(self):
71 """Return the number of running kernels."""
71 """Return the number of running kernels."""
72 return len(self.list_kernel_ids())
72 return len(self.list_kernel_ids())
73
73
74 def __contains__(self, kernel_id):
74 def __contains__(self, kernel_id):
75 return kernel_id in self._kernels
75 return kernel_id in self._kernels
76
76
77 def start_kernel(self, **kwargs):
77 def start_kernel(self, **kwargs):
78 """Start a new kernel.
78 """Start a new kernel.
79
79
80 The caller can pick a kernel_id by passing one in as a keyword arg,
80 The caller can pick a kernel_id by passing one in as a keyword arg,
81 otherwise one will be picked using a uuid.
81 otherwise one will be picked using a uuid.
82
82
83 To silence the kernel's stdout/stderr, call this using::
83 To silence the kernel's stdout/stderr, call this using::
84
84
85 km.start_kernel(stdout=PIPE, stderr=PIPE)
85 km.start_kernel(stdout=PIPE, stderr=PIPE)
86
86
87 """
87 """
88 kernel_id = kwargs.pop('kernel_id', unicode(uuid.uuid4()))
88 kernel_id = kwargs.pop('kernel_id', unicode(uuid.uuid4()))
89 if kernel_id in self:
89 if kernel_id in self:
90 raise DuplicateKernelError('Kernel already exists: %s' % kernel_id)
90 raise DuplicateKernelError('Kernel already exists: %s' % kernel_id)
91 # kernel_manager_factory is the constructor for the KernelManager
91 # kernel_manager_factory is the constructor for the KernelManager
92 # subclass we are using. It can be configured as any Configurable,
92 # subclass we are using. It can be configured as any Configurable,
93 # including things like its transport and ip.
93 # including things like its transport and ip.
94 km = self.kernel_manager_factory(connection_file=os.path.join(
94 km = self.kernel_manager_factory(connection_file=os.path.join(
95 self.connection_dir, "kernel-%s.json" % kernel_id),
95 self.connection_dir, "kernel-%s.json" % kernel_id),
96 config=self.config,
96 config=self.config,
97 )
97 )
98 km.start_kernel(**kwargs)
98 km.start_kernel(**kwargs)
99 # start just the shell channel, needed for graceful restart
99 # start just the shell channel, needed for graceful restart
100 km.start_channels(shell=True, iopub=False, stdin=False, hb=False)
100 km.start_channels(shell=True, iopub=False, stdin=False, hb=False)
101 self._kernels[kernel_id] = km
101 self._kernels[kernel_id] = km
102 return kernel_id
102 return kernel_id
103
103
104 def shutdown_kernel(self, kernel_id, now=False):
104 def shutdown_kernel(self, kernel_id, now=False):
105 """Shutdown a kernel by its kernel uuid.
105 """Shutdown a kernel by its kernel uuid.
106
106
107 Parameters
107 Parameters
108 ==========
108 ==========
109 kernel_id : uuid
109 kernel_id : uuid
110 The id of the kernel to shutdown.
110 The id of the kernel to shutdown.
111 now : bool
111 now : bool
112 Should the kernel be shutdown forcibly using a signal.
112 Should the kernel be shutdown forcibly using a signal.
113 """
113 """
114 k = self.get_kernel(kernel_id)
114 k = self.get_kernel(kernel_id)
115 k.shutdown_kernel(now=now)
115 k.shutdown_kernel(now=now)
116 k.shell_channel.stop()
116 k.shell_channel.stop()
117 del self._kernels[kernel_id]
117 del self._kernels[kernel_id]
118
118
119 def shutdown_all(self, now=False):
119 def shutdown_all(self, now=False):
120 """Shutdown all kernels."""
120 """Shutdown all kernels."""
121 for kid in self.list_kernel_ids():
121 for kid in self.list_kernel_ids():
122 self.shutdown_kernel(kid, now=now)
122 self.shutdown_kernel(kid, now=now)
123
123
124 def interrupt_kernel(self, kernel_id):
124 def interrupt_kernel(self, kernel_id):
125 """Interrupt (SIGINT) the kernel by its uuid.
125 """Interrupt (SIGINT) the kernel by its uuid.
126
126
127 Parameters
127 Parameters
128 ==========
128 ==========
129 kernel_id : uuid
129 kernel_id : uuid
130 The id of the kernel to interrupt.
130 The id of the kernel to interrupt.
131 """
131 """
132 return self.get_kernel(kernel_id).interrupt_kernel()
132 return self.get_kernel(kernel_id).interrupt_kernel()
133
133
134 def signal_kernel(self, kernel_id, signum):
134 def signal_kernel(self, kernel_id, signum):
135 """Sends a signal to the kernel by its uuid.
135 """Sends a signal to the kernel by its uuid.
136
136
137 Note that since only SIGTERM is supported on Windows, this function
137 Note that since only SIGTERM is supported on Windows, this function
138 is only useful on Unix systems.
138 is only useful on Unix systems.
139
139
140 Parameters
140 Parameters
141 ==========
141 ==========
142 kernel_id : uuid
142 kernel_id : uuid
143 The id of the kernel to signal.
143 The id of the kernel to signal.
144 """
144 """
145 return self.get_kernel(kernel_id).signal_kernel(signum)
145 return self.get_kernel(kernel_id).signal_kernel(signum)
146
146
147 def restart_kernel(self, kernel_id):
147 def restart_kernel(self, kernel_id):
148 """Restart a kernel by its uuid, keeping the same ports.
148 """Restart a kernel by its uuid, keeping the same ports.
149
149
150 Parameters
150 Parameters
151 ==========
151 ==========
152 kernel_id : uuid
152 kernel_id : uuid
153 The id of the kernel to interrupt.
153 The id of the kernel to interrupt.
154 """
154 """
155 return self.get_kernel(kernel_id).restart_kernel()
155 return self.get_kernel(kernel_id).restart_kernel()
156
156
157 def get_kernel(self, kernel_id):
157 def get_kernel(self, kernel_id):
158 """Get the single KernelManager object for a kernel by its uuid.
158 """Get the single KernelManager object for a kernel by its uuid.
159
159
160 Parameters
160 Parameters
161 ==========
161 ==========
162 kernel_id : uuid
162 kernel_id : uuid
163 The id of the kernel.
163 The id of the kernel.
164 """
164 """
165 km = self._kernels.get(kernel_id)
165 km = self._kernels.get(kernel_id)
166 if km is not None:
166 if km is not None:
167 return km
167 return km
168 else:
168 else:
169 raise KeyError("Kernel with id not found: %s" % kernel_id)
169 raise KeyError("Kernel with id not found: %s" % kernel_id)
170
170
171 def get_connection_info(self, kernel_id):
171 def get_connection_info(self, kernel_id):
172 """Return a dictionary of connection data for a kernel.
172 """Return a dictionary of connection data for a kernel.
173
173
174 Parameters
174 Parameters
175 ==========
175 ==========
176 kernel_id : uuid
176 kernel_id : uuid
177 The id of the kernel.
177 The id of the kernel.
178
178
179 Returns
179 Returns
180 =======
180 =======
181 connection_dict : dict
181 connection_dict : dict
182 A dict of the information needed to connect to a kernel.
182 A dict of the information needed to connect to a kernel.
183 This includes the ip address and the integer port
183 This includes the ip address and the integer port
184 numbers of the different channels (stdin_port, iopub_port,
184 numbers of the different channels (stdin_port, iopub_port,
185 shell_port, hb_port).
185 shell_port, hb_port).
186 """
186 """
187 km = self.get_kernel(kernel_id)
187 km = self.get_kernel(kernel_id)
188 return dict(transport=km.transport,
188 return dict(transport=km.transport,
189 ip=km.ip,
189 ip=km.ip,
190 shell_port=km.shell_port,
190 shell_port=km.shell_port,
191 iopub_port=km.iopub_port,
191 iopub_port=km.iopub_port,
192 stdin_port=km.stdin_port,
192 stdin_port=km.stdin_port,
193 hb_port=km.hb_port,
193 hb_port=km.hb_port,
194 )
194 )
195
195
196 def _make_url(self, transport, ip, port):
196 def _make_url(self, transport, ip, port):
197 """Make a ZeroMQ URL for a given transport, ip and port."""
197 """Make a ZeroMQ URL for a given transport, ip and port."""
198 if transport == 'tcp':
198 if transport == 'tcp':
199 return "tcp://%s:%i" % (ip, port)
199 return "tcp://%s:%i" % (ip, port)
200 else:
200 else:
201 return "%s://%s-%s" % (transport, ip, port)
201 return "%s://%s-%s" % (transport, ip, port)
202
202
203 def _create_connected_stream(self, kernel_id, socket_type, channel):
203 def _create_connected_stream(self, kernel_id, socket_type, channel):
204 """Create a connected ZMQStream for a kernel."""
204 """Create a connected ZMQStream for a kernel."""
205 cinfo = self.get_connection_info(kernel_id)
205 cinfo = self.get_connection_info(kernel_id)
206 url = self._make_url(cinfo['transport'], cinfo['ip'],
206 url = self._make_url(cinfo['transport'], cinfo['ip'],
207 cinfo['%s_port' % channel]
207 cinfo['%s_port' % channel]
208 )
208 )
209 sock = self.context.socket(socket_type)
209 sock = self.context.socket(socket_type)
210 self.log.info("Connecting to: %s" % url)
210 self.log.info("Connecting to: %s" % url)
211 sock.connect(url)
211 sock.connect(url)
212 return ZMQStream(sock)
212 return ZMQStream(sock)
213
213
214 def create_iopub_stream(self, kernel_id):
214 def create_iopub_stream(self, kernel_id):
215 """Return a ZMQStream object connected to the iopub channel.
215 """Return a ZMQStream object connected to the iopub channel.
216
216
217 Parameters
217 Parameters
218 ==========
218 ==========
219 kernel_id : uuid
219 kernel_id : uuid
220 The id of the kernel.
220 The id of the kernel.
221
221
222 Returns
222 Returns
223 =======
223 =======
224 stream : ZMQStream
224 stream : ZMQStream
225 """
225 """
226 iopub_stream = self._create_connected_stream(kernel_id, zmq.SUB, 'iopub')
226 iopub_stream = self._create_connected_stream(kernel_id, zmq.SUB, 'iopub')
227 iopub_stream.socket.setsockopt(zmq.SUBSCRIBE, b'')
227 iopub_stream.socket.setsockopt(zmq.SUBSCRIBE, b'')
228 return iopub_stream
228 return iopub_stream
229
229
230 def create_shell_stream(self, kernel_id):
230 def create_shell_stream(self, kernel_id):
231 """Return a ZMQStream object connected to the shell channel.
231 """Return a ZMQStream object connected to the shell channel.
232
232
233 Parameters
233 Parameters
234 ==========
234 ==========
235 kernel_id : uuid
235 kernel_id : uuid
236 The id of the kernel.
236 The id of the kernel.
237
237
238 Returns
238 Returns
239 =======
239 =======
240 stream : ZMQStream
240 stream : ZMQStream
241 """
241 """
242 shell_stream = self._create_connected_stream(kernel_id, zmq.DEALER, 'shell')
242 shell_stream = self._create_connected_stream(kernel_id, zmq.DEALER, 'shell')
243 return shell_stream
243 return shell_stream
244
244
245 def create_hb_stream(self, kernel_id):
245 def create_hb_stream(self, kernel_id):
246 """Return a ZMQStream object connected to the hb channel.
246 """Return a ZMQStream object connected to the hb channel.
247
247
248 Parameters
248 Parameters
249 ==========
249 ==========
250 kernel_id : uuid
250 kernel_id : uuid
251 The id of the kernel.
251 The id of the kernel.
252
252
253 Returns
253 Returns
254 =======
254 =======
255 stream : ZMQStream
255 stream : ZMQStream
256 """
256 """
257 hb_stream = self._create_connected_stream(kernel_id, zmq.REQ, 'hb')
257 hb_stream = self._create_connected_stream(kernel_id, zmq.REQ, 'hb')
258 return hb_stream
258 return hb_stream
259
259
260
260
261 class MappingKernelManager(MultiKernelManager):
261 class MappingKernelManager(MultiKernelManager):
262 """A KernelManager that handles notebok mapping and HTTP error handling"""
262 """A KernelManager that handles notebok mapping and HTTP error handling"""
263
263
264 kernel_argv = List(Unicode)
264 kernel_argv = List(Unicode)
265
265
266 time_to_dead = Float(3.0, config=True, help="""Kernel heartbeat interval in seconds.""")
266 time_to_dead = Float(3.0, config=True, help="""Kernel heartbeat interval in seconds.""")
267 first_beat = Float(5.0, config=True, help="Delay (in seconds) before sending first heartbeat.")
267 first_beat = Float(5.0, config=True, help="Delay (in seconds) before sending first heartbeat.")
268
268
269 max_msg_size = Integer(65536, config=True, help="""
269 max_msg_size = Integer(65536, config=True, help="""
270 The max raw message size accepted from the browser
270 The max raw message size accepted from the browser
271 over a WebSocket connection.
271 over a WebSocket connection.
272 """)
272 """)
273
273
274 _notebook_mapping = Dict()
274 _notebook_mapping = Dict()
275
275
276 #-------------------------------------------------------------------------
276 #-------------------------------------------------------------------------
277 # Methods for managing kernels and sessions
277 # Methods for managing kernels and sessions
278 #-------------------------------------------------------------------------
278 #-------------------------------------------------------------------------
279
279
280 def kernel_for_notebook(self, notebook_id):
280 def kernel_for_notebook(self, notebook_id):
281 """Return the kernel_id for a notebook_id or None."""
281 """Return the kernel_id for a notebook_id or None."""
282 return self._notebook_mapping.get(notebook_id)
282 return self._notebook_mapping.get(notebook_id)
283
283
284 def set_kernel_for_notebook(self, notebook_id, kernel_id):
284 def set_kernel_for_notebook(self, notebook_id, kernel_id):
285 """Associate a notebook with a kernel."""
285 """Associate a notebook with a kernel."""
286 if notebook_id is not None:
286 if notebook_id is not None:
287 self._notebook_mapping[notebook_id] = kernel_id
287 self._notebook_mapping[notebook_id] = kernel_id
288
288
289 def notebook_for_kernel(self, kernel_id):
289 def notebook_for_kernel(self, kernel_id):
290 """Return the notebook_id for a kernel_id or None."""
290 """Return the notebook_id for a kernel_id or None."""
291 notebook_ids = [k for k, v in self._notebook_mapping.iteritems() if v == kernel_id]
291 notebook_ids = [k for k, v in self._notebook_mapping.iteritems() if v == kernel_id]
292 if len(notebook_ids) == 1:
292 if len(notebook_ids) == 1:
293 return notebook_ids[0]
293 return notebook_ids[0]
294 else:
294 else:
295 return None
295 return None
296
296
297 def delete_mapping_for_kernel(self, kernel_id):
297 def delete_mapping_for_kernel(self, kernel_id):
298 """Remove the kernel/notebook mapping for kernel_id."""
298 """Remove the kernel/notebook mapping for kernel_id."""
299 notebook_id = self.notebook_for_kernel(kernel_id)
299 notebook_id = self.notebook_for_kernel(kernel_id)
300 if notebook_id is not None:
300 if notebook_id is not None:
301 del self._notebook_mapping[notebook_id]
301 del self._notebook_mapping[notebook_id]
302
302
303 def start_kernel(self, notebook_id=None, **kwargs):
303 def start_kernel(self, notebook_id=None, **kwargs):
304 """Start a kernel for a notebok an return its kernel_id.
304 """Start a kernel for a notebok an return its kernel_id.
305
305
306 Parameters
306 Parameters
307 ----------
307 ----------
308 notebook_id : uuid
308 notebook_id : uuid
309 The uuid of the notebook to associate the new kernel with. If this
309 The uuid of the notebook to associate the new kernel with. If this
310 is not None, this kernel will be persistent whenever the notebook
310 is not None, this kernel will be persistent whenever the notebook
311 requests a kernel.
311 requests a kernel.
312 """
312 """
313 kernel_id = self.kernel_for_notebook(notebook_id)
313 kernel_id = self.kernel_for_notebook(notebook_id)
314 if kernel_id is None:
314 if kernel_id is None:
315 kwargs['extra_arguments'] = self.kernel_argv
315 kwargs['extra_arguments'] = self.kernel_argv
316 kernel_id = super(MappingKernelManager, self).start_kernel(**kwargs)
316 kernel_id = super(MappingKernelManager, self).start_kernel(**kwargs)
317 self.set_kernel_for_notebook(notebook_id, kernel_id)
317 self.set_kernel_for_notebook(notebook_id, kernel_id)
318 self.log.info("Kernel started: %s" % kernel_id)
318 self.log.info("Kernel started: %s" % kernel_id)
319 self.log.debug("Kernel args: %r" % kwargs)
319 self.log.debug("Kernel args: %r" % kwargs)
320 else:
320 else:
321 self.log.info("Using existing kernel: %s" % kernel_id)
321 self.log.info("Using existing kernel: %s" % kernel_id)
322 return kernel_id
322 return kernel_id
323
323
324 def shutdown_kernel(self, kernel_id, now=False):
324 def shutdown_kernel(self, kernel_id, now=False):
325 """Shutdown a kernel and remove its notebook association."""
325 """Shutdown a kernel and remove its notebook association."""
326 self._check_kernel_id(kernel_id)
326 self._check_kernel_id(kernel_id)
327 super(MappingKernelManager, self).shutdown_kernel(
327 super(MappingKernelManager, self).shutdown_kernel(
328 kernel_id, now=now
328 kernel_id, now=now
329 )
329 )
330 self.delete_mapping_for_kernel(kernel_id)
330 self.delete_mapping_for_kernel(kernel_id)
331 self.log.info("Kernel shutdown: %s" % kernel_id)
331 self.log.info("Kernel shutdown: %s" % kernel_id)
332
332
333 def interrupt_kernel(self, kernel_id):
333 def interrupt_kernel(self, kernel_id):
334 """Interrupt a kernel."""
334 """Interrupt a kernel."""
335 self._check_kernel_id(kernel_id)
335 self._check_kernel_id(kernel_id)
336 super(MappingKernelManager, self).interrupt_kernel(kernel_id)
336 super(MappingKernelManager, self).interrupt_kernel(kernel_id)
337 self.log.info("Kernel interrupted: %s" % kernel_id)
337 self.log.info("Kernel interrupted: %s" % kernel_id)
338
338
339 def restart_kernel(self, kernel_id):
339 def restart_kernel(self, kernel_id):
340 """Restart a kernel while keeping clients connected."""
340 """Restart a kernel while keeping clients connected."""
341 self._check_kernel_id(kernel_id)
341 self._check_kernel_id(kernel_id)
342 super(MappingKernelManager, self).restart_kernel(kernel_id)
342 super(MappingKernelManager, self).restart_kernel(kernel_id)
343 self.log.info("Kernel restarted: %s" % kernel_id)
343 self.log.info("Kernel restarted: %s" % kernel_id)
344
344
345 def create_iopub_stream(self, kernel_id):
345 def create_iopub_stream(self, kernel_id):
346 """Create a new iopub stream."""
346 """Create a new iopub stream."""
347 self._check_kernel_id(kernel_id)
347 self._check_kernel_id(kernel_id)
348 return super(MappingKernelManager, self).create_iopub_stream(kernel_id)
348 return super(MappingKernelManager, self).create_iopub_stream(kernel_id)
349
349
350 def create_shell_stream(self, kernel_id):
350 def create_shell_stream(self, kernel_id):
351 """Create a new shell stream."""
351 """Create a new shell stream."""
352 self._check_kernel_id(kernel_id)
352 self._check_kernel_id(kernel_id)
353 return super(MappingKernelManager, self).create_shell_stream(kernel_id)
353 return super(MappingKernelManager, self).create_shell_stream(kernel_id)
354
354
355 def create_hb_stream(self, kernel_id):
355 def create_hb_stream(self, kernel_id):
356 """Create a new hb stream."""
356 """Create a new hb stream."""
357 self._check_kernel_id(kernel_id)
357 self._check_kernel_id(kernel_id)
358 return super(MappingKernelManager, self).create_hb_stream(kernel_id)
358 return super(MappingKernelManager, self).create_hb_stream(kernel_id)
359
359
360 def _check_kernel_id(self, kernel_id):
360 def _check_kernel_id(self, kernel_id):
361 """Check a that a kernel_id exists and raise 404 if not."""
361 """Check a that a kernel_id exists and raise 404 if not."""
362 if kernel_id not in self:
362 if kernel_id not in self:
363 raise web.HTTPError(404, u'Kernel does not exist: %s' % kernel_id)
363 raise web.HTTPError(404, u'Kernel does not exist: %s' % kernel_id)
364
364
@@ -1,80 +1,80 b''
1 """Tests for the notebook kernel and session manager."""
1 """Tests for the notebook kernel and session manager."""
2
2
3 from subprocess import PIPE
3 from subprocess import PIPE
4 import time
4 import time
5 from unittest import TestCase
5 from unittest import TestCase
6
6
7 from IPython.testing import decorators as dec
7 from IPython.testing import decorators as dec
8
8
9 from IPython.config.loader import Config
9 from IPython.config.loader import Config
10 from IPython.frontend.html.notebook.kernelmanager import MultiKernelManager
10 from IPython.frontend.html.notebook.kernelmanager import MultiKernelManager
11 from IPython.utils.localinterfaces import LOCALHOST
11 from IPython.utils.localinterfaces import LOCALHOST
12 from IPython.zmq.kernelmanager import KernelManager
12 from IPython.kernel.kernelmanager import KernelManager
13
13
14 class TestKernelManager(TestCase):
14 class TestKernelManager(TestCase):
15
15
16 def _get_tcp_km(self):
16 def _get_tcp_km(self):
17 return MultiKernelManager()
17 return MultiKernelManager()
18
18
19 def _get_ipc_km(self):
19 def _get_ipc_km(self):
20 c = Config()
20 c = Config()
21 c.KernelManager.transport = 'ipc'
21 c.KernelManager.transport = 'ipc'
22 c.KernelManager.ip = 'test'
22 c.KernelManager.ip = 'test'
23 km = MultiKernelManager(config=c)
23 km = MultiKernelManager(config=c)
24 return km
24 return km
25
25
26 def _run_lifecycle(self, km):
26 def _run_lifecycle(self, km):
27 kid = km.start_kernel(stdout=PIPE, stderr=PIPE)
27 kid = km.start_kernel(stdout=PIPE, stderr=PIPE)
28 self.assertTrue(kid in km)
28 self.assertTrue(kid in km)
29 self.assertTrue(kid in km.list_kernel_ids())
29 self.assertTrue(kid in km.list_kernel_ids())
30 self.assertEqual(len(km),1)
30 self.assertEqual(len(km),1)
31 km.restart_kernel(kid)
31 km.restart_kernel(kid)
32 self.assertTrue(kid in km.list_kernel_ids())
32 self.assertTrue(kid in km.list_kernel_ids())
33 # We need a delay here to give the restarting kernel a chance to
33 # We need a delay here to give the restarting kernel a chance to
34 # restart. Otherwise, the interrupt will kill it, causing the test
34 # restart. Otherwise, the interrupt will kill it, causing the test
35 # suite to hang. The reason it *hangs* is that the shutdown
35 # suite to hang. The reason it *hangs* is that the shutdown
36 # message for the restart sometimes hasn't been sent to the kernel.
36 # message for the restart sometimes hasn't been sent to the kernel.
37 # Because linger is oo on the shell channel, the context can't
37 # Because linger is oo on the shell channel, the context can't
38 # close until the message is sent to the kernel, which is not dead.
38 # close until the message is sent to the kernel, which is not dead.
39 time.sleep(1.0)
39 time.sleep(1.0)
40 km.interrupt_kernel(kid)
40 km.interrupt_kernel(kid)
41 k = km.get_kernel(kid)
41 k = km.get_kernel(kid)
42 self.assertTrue(isinstance(k, KernelManager))
42 self.assertTrue(isinstance(k, KernelManager))
43 km.shutdown_kernel(kid)
43 km.shutdown_kernel(kid)
44 self.assertTrue(not kid in km)
44 self.assertTrue(not kid in km)
45
45
46 def _run_cinfo(self, km, transport, ip):
46 def _run_cinfo(self, km, transport, ip):
47 kid = km.start_kernel(stdout=PIPE, stderr=PIPE)
47 kid = km.start_kernel(stdout=PIPE, stderr=PIPE)
48 k = km.get_kernel(kid)
48 k = km.get_kernel(kid)
49 cinfo = km.get_connection_info(kid)
49 cinfo = km.get_connection_info(kid)
50 self.assertEqual(transport, cinfo['transport'])
50 self.assertEqual(transport, cinfo['transport'])
51 self.assertEqual(ip, cinfo['ip'])
51 self.assertEqual(ip, cinfo['ip'])
52 self.assertTrue('stdin_port' in cinfo)
52 self.assertTrue('stdin_port' in cinfo)
53 self.assertTrue('iopub_port' in cinfo)
53 self.assertTrue('iopub_port' in cinfo)
54 stream = km.create_iopub_stream(kid)
54 stream = km.create_iopub_stream(kid)
55 stream.close()
55 stream.close()
56 self.assertTrue('shell_port' in cinfo)
56 self.assertTrue('shell_port' in cinfo)
57 stream = km.create_shell_stream(kid)
57 stream = km.create_shell_stream(kid)
58 stream.close()
58 stream.close()
59 self.assertTrue('hb_port' in cinfo)
59 self.assertTrue('hb_port' in cinfo)
60 stream = km.create_hb_stream(kid)
60 stream = km.create_hb_stream(kid)
61 stream.close()
61 stream.close()
62 km.shutdown_kernel(kid)
62 km.shutdown_kernel(kid)
63
63
64 def test_tcp_lifecycle(self):
64 def test_tcp_lifecycle(self):
65 km = self._get_tcp_km()
65 km = self._get_tcp_km()
66 self._run_lifecycle(km)
66 self._run_lifecycle(km)
67
67
68 @dec.skip_win32
68 @dec.skip_win32
69 def test_tcp_cinfo(self):
69 def test_tcp_cinfo(self):
70 km = self._get_tcp_km()
70 km = self._get_tcp_km()
71 self._run_cinfo(km, 'tcp', LOCALHOST)
71 self._run_cinfo(km, 'tcp', LOCALHOST)
72
72
73 def test_ipc_lifecycle(self):
73 def test_ipc_lifecycle(self):
74 km = self._get_ipc_km()
74 km = self._get_ipc_km()
75 self._run_lifecycle(km)
75 self._run_lifecycle(km)
76
76
77 def test_ipc_cinfo(self):
77 def test_ipc_cinfo(self):
78 km = self._get_ipc_km()
78 km = self._get_ipc_km()
79 self._run_cinfo(km, 'ipc', 'test')
79 self._run_cinfo(km, 'ipc', 'test')
80
80
@@ -1,32 +1,32 b''
1 """ Defines a KernelManager that provides signals and slots.
1 """ Defines a KernelManager that provides signals and slots.
2 """
2 """
3
3
4 # Local imports.
4 # Local imports.
5 from IPython.utils.traitlets import Type
5 from IPython.utils.traitlets import Type
6 from IPython.zmq.kernelmanager import ShellChannel, IOPubChannel, \
6 from IPython.kernel.kernelmanager import ShellChannel, IOPubChannel, \
7 StdInChannel, HBChannel, KernelManager
7 StdInChannel, HBChannel, KernelManager
8 from base_kernelmanager import QtShellChannelMixin, QtIOPubChannelMixin, \
8 from base_kernelmanager import QtShellChannelMixin, QtIOPubChannelMixin, \
9 QtStdInChannelMixin, QtHBChannelMixin, QtKernelManagerMixin
9 QtStdInChannelMixin, QtHBChannelMixin, QtKernelManagerMixin
10
10
11
11
12 class QtShellChannel(QtShellChannelMixin, ShellChannel):
12 class QtShellChannel(QtShellChannelMixin, ShellChannel):
13 pass
13 pass
14
14
15 class QtIOPubChannel(QtIOPubChannelMixin, IOPubChannel):
15 class QtIOPubChannel(QtIOPubChannelMixin, IOPubChannel):
16 pass
16 pass
17
17
18 class QtStdInChannel(QtStdInChannelMixin, StdInChannel):
18 class QtStdInChannel(QtStdInChannelMixin, StdInChannel):
19 pass
19 pass
20
20
21 class QtHBChannel(QtHBChannelMixin, HBChannel):
21 class QtHBChannel(QtHBChannelMixin, HBChannel):
22 pass
22 pass
23
23
24
24
25 class QtKernelManager(QtKernelManagerMixin, KernelManager):
25 class QtKernelManager(QtKernelManagerMixin, KernelManager):
26 """ A KernelManager that provides signals and slots.
26 """ A KernelManager that provides signals and slots.
27 """
27 """
28
28
29 iopub_channel_class = Type(QtIOPubChannel)
29 iopub_channel_class = Type(QtIOPubChannel)
30 shell_channel_class = Type(QtShellChannel)
30 shell_channel_class = Type(QtShellChannel)
31 stdin_channel_class = Type(QtStdInChannel)
31 stdin_channel_class = Type(QtStdInChannel)
32 hb_channel_class = Type(QtHBChannel)
32 hb_channel_class = Type(QtHBChannel)
@@ -1,95 +1,95 b''
1 #-----------------------------------------------------------------------------
1 #-----------------------------------------------------------------------------
2 # Copyright (C) 2012 The IPython Development Team
2 # Copyright (C) 2012 The IPython Development Team
3 #
3 #
4 # Distributed under the terms of the BSD License. The full license is in
4 # Distributed under the terms of the BSD License. The full license is in
5 # the file COPYING, distributed as part of this software.
5 # the file COPYING, distributed as part of this software.
6 #-----------------------------------------------------------------------------
6 #-----------------------------------------------------------------------------
7
7
8 import os
8 import os
9 import sys
9 import sys
10 import unittest
10 import unittest
11 import base64
11 import base64
12
12
13 from IPython.zmq.kernelmanager import KernelManager
13 from IPython.kernel.kernelmanager import KernelManager
14 from IPython.frontend.terminal.console.interactiveshell \
14 from IPython.frontend.terminal.console.interactiveshell \
15 import ZMQTerminalInteractiveShell
15 import ZMQTerminalInteractiveShell
16 from IPython.utils.tempdir import TemporaryDirectory
16 from IPython.utils.tempdir import TemporaryDirectory
17 from IPython.testing.tools import monkeypatch
17 from IPython.testing.tools import monkeypatch
18 from IPython.testing.decorators import skip_without
18 from IPython.testing.decorators import skip_without
19 from IPython.utils.ipstruct import Struct
19 from IPython.utils.ipstruct import Struct
20
20
21
21
22 SCRIPT_PATH = os.path.join(
22 SCRIPT_PATH = os.path.join(
23 os.path.abspath(os.path.dirname(__file__)), 'writetofile.py')
23 os.path.abspath(os.path.dirname(__file__)), 'writetofile.py')
24
24
25
25
26 class ZMQTerminalInteractiveShellTestCase(unittest.TestCase):
26 class ZMQTerminalInteractiveShellTestCase(unittest.TestCase):
27
27
28 def setUp(self):
28 def setUp(self):
29 km = KernelManager()
29 km = KernelManager()
30 self.shell = ZMQTerminalInteractiveShell(kernel_manager=km)
30 self.shell = ZMQTerminalInteractiveShell(kernel_manager=km)
31 self.raw = b'dummy data'
31 self.raw = b'dummy data'
32 self.mime = 'image/png'
32 self.mime = 'image/png'
33 self.data = {self.mime: base64.encodestring(self.raw).decode('ascii')}
33 self.data = {self.mime: base64.encodestring(self.raw).decode('ascii')}
34
34
35 def test_no_call_by_default(self):
35 def test_no_call_by_default(self):
36 def raise_if_called(*args, **kwds):
36 def raise_if_called(*args, **kwds):
37 assert False
37 assert False
38
38
39 shell = self.shell
39 shell = self.shell
40 shell.handle_image_PIL = raise_if_called
40 shell.handle_image_PIL = raise_if_called
41 shell.handle_image_stream = raise_if_called
41 shell.handle_image_stream = raise_if_called
42 shell.handle_image_tempfile = raise_if_called
42 shell.handle_image_tempfile = raise_if_called
43 shell.handle_image_callable = raise_if_called
43 shell.handle_image_callable = raise_if_called
44
44
45 shell.handle_image(None, None) # arguments are dummy
45 shell.handle_image(None, None) # arguments are dummy
46
46
47 @skip_without('PIL')
47 @skip_without('PIL')
48 def test_handle_image_PIL(self):
48 def test_handle_image_PIL(self):
49 import PIL.Image
49 import PIL.Image
50
50
51 open_called_with = []
51 open_called_with = []
52 show_called_with = []
52 show_called_with = []
53
53
54 def fake_open(arg):
54 def fake_open(arg):
55 open_called_with.append(arg)
55 open_called_with.append(arg)
56 return Struct(show=lambda: show_called_with.append(None))
56 return Struct(show=lambda: show_called_with.append(None))
57
57
58 with monkeypatch(PIL.Image, 'open', fake_open):
58 with monkeypatch(PIL.Image, 'open', fake_open):
59 self.shell.handle_image_PIL(self.data, self.mime)
59 self.shell.handle_image_PIL(self.data, self.mime)
60
60
61 self.assertEqual(len(open_called_with), 1)
61 self.assertEqual(len(open_called_with), 1)
62 self.assertEqual(len(show_called_with), 1)
62 self.assertEqual(len(show_called_with), 1)
63 self.assertEqual(open_called_with[0].getvalue(), self.raw)
63 self.assertEqual(open_called_with[0].getvalue(), self.raw)
64
64
65 def check_handler_with_file(self, inpath, handler):
65 def check_handler_with_file(self, inpath, handler):
66 shell = self.shell
66 shell = self.shell
67 configname = '{0}_image_handler'.format(handler)
67 configname = '{0}_image_handler'.format(handler)
68 funcname = 'handle_image_{0}'.format(handler)
68 funcname = 'handle_image_{0}'.format(handler)
69
69
70 assert hasattr(shell, configname)
70 assert hasattr(shell, configname)
71 assert hasattr(shell, funcname)
71 assert hasattr(shell, funcname)
72
72
73 with TemporaryDirectory() as tmpdir:
73 with TemporaryDirectory() as tmpdir:
74 outpath = os.path.join(tmpdir, 'data')
74 outpath = os.path.join(tmpdir, 'data')
75 cmd = [sys.executable, SCRIPT_PATH, inpath, outpath]
75 cmd = [sys.executable, SCRIPT_PATH, inpath, outpath]
76 setattr(shell, configname, cmd)
76 setattr(shell, configname, cmd)
77 getattr(shell, funcname)(self.data, self.mime)
77 getattr(shell, funcname)(self.data, self.mime)
78 # cmd is called and file is closed. So it's safe to open now.
78 # cmd is called and file is closed. So it's safe to open now.
79 with open(outpath, 'rb') as file:
79 with open(outpath, 'rb') as file:
80 transferred = file.read()
80 transferred = file.read()
81
81
82 self.assertEqual(transferred, self.raw)
82 self.assertEqual(transferred, self.raw)
83
83
84 def test_handle_image_stream(self):
84 def test_handle_image_stream(self):
85 self.check_handler_with_file('-', 'stream')
85 self.check_handler_with_file('-', 'stream')
86
86
87 def test_handle_image_tempfile(self):
87 def test_handle_image_tempfile(self):
88 self.check_handler_with_file('{file}', 'tempfile')
88 self.check_handler_with_file('{file}', 'tempfile')
89
89
90 def test_handle_image_callable(self):
90 def test_handle_image_callable(self):
91 called_with = []
91 called_with = []
92 self.shell.callable_image_handler = called_with.append
92 self.shell.callable_image_handler = called_with.append
93 self.shell.handle_image_callable(self.data, self.mime)
93 self.shell.handle_image_callable(self.data, self.mime)
94 self.assertEqual(len(called_with), 1)
94 self.assertEqual(len(called_with), 1)
95 assert called_with[0] is self.data
95 assert called_with[0] is self.data
@@ -1,54 +1,54 b''
1 """ Implements a fully blocking kernel manager.
1 """ Implements a fully blocking kernel manager.
2
2
3 Useful for test suites and blocking terminal interfaces.
3 Useful for test suites and blocking terminal interfaces.
4 """
4 """
5 #-----------------------------------------------------------------------------
5 #-----------------------------------------------------------------------------
6 # Copyright (C) 2012 The IPython Development Team
6 # Copyright (C) 2012 The IPython Development Team
7 #
7 #
8 # Distributed under the terms of the BSD License. The full license is in
8 # Distributed under the terms of the BSD License. The full license is in
9 # the file COPYING.txt, distributed as part of this software.
9 # the file COPYING.txt, distributed as part of this software.
10 #-----------------------------------------------------------------------------
10 #-----------------------------------------------------------------------------
11
11
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13 # Imports
13 # Imports
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15 from __future__ import print_function
15 from __future__ import print_function
16
16
17 # Local imports.
17 # Local imports.
18 from IPython.utils.io import raw_print
18 from IPython.utils.io import raw_print
19 from IPython.utils.traitlets import Type
19 from IPython.utils.traitlets import Type
20 from kernelmanager import InProcessKernelManager, InProcessShellChannel, \
20 from kernelmanager import InProcessKernelManager, InProcessShellChannel, \
21 InProcessIOPubChannel, InProcessStdInChannel
21 InProcessIOPubChannel, InProcessStdInChannel
22 from IPython.zmq.blockingkernelmanager import BlockingChannelMixin
22 from IPython.kernel.blockingkernelmanager import BlockingChannelMixin
23
23
24
24
25 #-----------------------------------------------------------------------------
25 #-----------------------------------------------------------------------------
26 # Blocking kernel manager
26 # Blocking kernel manager
27 #-----------------------------------------------------------------------------
27 #-----------------------------------------------------------------------------
28
28
29 class BlockingInProcessShellChannel(BlockingChannelMixin, InProcessShellChannel):
29 class BlockingInProcessShellChannel(BlockingChannelMixin, InProcessShellChannel):
30 pass
30 pass
31
31
32 class BlockingInProcessIOPubChannel(BlockingChannelMixin, InProcessIOPubChannel):
32 class BlockingInProcessIOPubChannel(BlockingChannelMixin, InProcessIOPubChannel):
33 pass
33 pass
34
34
35 class BlockingInProcessStdInChannel(BlockingChannelMixin, InProcessStdInChannel):
35 class BlockingInProcessStdInChannel(BlockingChannelMixin, InProcessStdInChannel):
36
36
37 def call_handlers(self, msg):
37 def call_handlers(self, msg):
38 """ Overridden for the in-process channel.
38 """ Overridden for the in-process channel.
39
39
40 This methods simply calls raw_input directly.
40 This methods simply calls raw_input directly.
41 """
41 """
42 msg_type = msg['header']['msg_type']
42 msg_type = msg['header']['msg_type']
43 if msg_type == 'input_request':
43 if msg_type == 'input_request':
44 _raw_input = self.manager.kernel._sys_raw_input
44 _raw_input = self.manager.kernel._sys_raw_input
45 prompt = msg['content']['prompt']
45 prompt = msg['content']['prompt']
46 raw_print(prompt, end='')
46 raw_print(prompt, end='')
47 self.input(_raw_input())
47 self.input(_raw_input())
48
48
49 class BlockingInProcessKernelManager(InProcessKernelManager):
49 class BlockingInProcessKernelManager(InProcessKernelManager):
50
50
51 # The classes to use for the various channels.
51 # The classes to use for the various channels.
52 shell_channel_class = Type(BlockingInProcessShellChannel)
52 shell_channel_class = Type(BlockingInProcessShellChannel)
53 iopub_channel_class = Type(BlockingInProcessIOPubChannel)
53 iopub_channel_class = Type(BlockingInProcessIOPubChannel)
54 stdin_channel_class = Type(BlockingInProcessStdInChannel)
54 stdin_channel_class = Type(BlockingInProcessStdInChannel)
@@ -1,313 +1,313 b''
1 """ A kernel manager for in-process kernels. """
1 """ A kernel manager for in-process kernels. """
2
2
3 #-----------------------------------------------------------------------------
3 #-----------------------------------------------------------------------------
4 # Copyright (C) 2012 The IPython Development Team
4 # Copyright (C) 2012 The IPython Development Team
5 #
5 #
6 # Distributed under the terms of the BSD License. The full license is in
6 # Distributed under the terms of the BSD License. The full license is in
7 # the file COPYING, distributed as part of this software.
7 # the file COPYING, distributed as part of this software.
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9
9
10 #-----------------------------------------------------------------------------
10 #-----------------------------------------------------------------------------
11 # Imports
11 # Imports
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13
13
14 # Local imports.
14 # Local imports.
15 from IPython.config.configurable import Configurable
15 from IPython.config.configurable import Configurable
16 from IPython.inprocess.socket import DummySocket
16 from IPython.inprocess.socket import DummySocket
17 from IPython.utils.traitlets import Any, Instance, Type
17 from IPython.utils.traitlets import Any, Instance, Type
18 from IPython.kernel import (
18 from IPython.kernel import (
19 ShellChannelABC, IOPubChannelABC,
19 ShellChannelABC, IOPubChannelABC,
20 HBChannelABC, StdInChannelABC,
20 HBChannelABC, StdInChannelABC,
21 KernelManagerABC
21 KernelManagerABC
22 )
22 )
23
23
24 #-----------------------------------------------------------------------------
24 #-----------------------------------------------------------------------------
25 # Channel classes
25 # Channel classes
26 #-----------------------------------------------------------------------------
26 #-----------------------------------------------------------------------------
27
27
28 class InProcessChannel(object):
28 class InProcessChannel(object):
29 """Base class for in-process channels."""
29 """Base class for in-process channels."""
30
30
31 def __init__(self, manager):
31 def __init__(self, manager):
32 super(InProcessChannel, self).__init__()
32 super(InProcessChannel, self).__init__()
33 self.manager = manager
33 self.manager = manager
34 self._is_alive = False
34 self._is_alive = False
35
35
36 #--------------------------------------------------------------------------
36 #--------------------------------------------------------------------------
37 # Channel interface
37 # Channel interface
38 #--------------------------------------------------------------------------
38 #--------------------------------------------------------------------------
39
39
40 def is_alive(self):
40 def is_alive(self):
41 return self._is_alive
41 return self._is_alive
42
42
43 def start(self):
43 def start(self):
44 self._is_alive = True
44 self._is_alive = True
45
45
46 def stop(self):
46 def stop(self):
47 self._is_alive = False
47 self._is_alive = False
48
48
49 def call_handlers(self, msg):
49 def call_handlers(self, msg):
50 """ This method is called in the main thread when a message arrives.
50 """ This method is called in the main thread when a message arrives.
51
51
52 Subclasses should override this method to handle incoming messages.
52 Subclasses should override this method to handle incoming messages.
53 """
53 """
54 raise NotImplementedError('call_handlers must be defined in a subclass.')
54 raise NotImplementedError('call_handlers must be defined in a subclass.')
55
55
56 #--------------------------------------------------------------------------
56 #--------------------------------------------------------------------------
57 # InProcessChannel interface
57 # InProcessChannel interface
58 #--------------------------------------------------------------------------
58 #--------------------------------------------------------------------------
59
59
60 def call_handlers_later(self, *args, **kwds):
60 def call_handlers_later(self, *args, **kwds):
61 """ Call the message handlers later.
61 """ Call the message handlers later.
62
62
63 The default implementation just calls the handlers immediately, but this
63 The default implementation just calls the handlers immediately, but this
64 method exists so that GUI toolkits can defer calling the handlers until
64 method exists so that GUI toolkits can defer calling the handlers until
65 after the event loop has run, as expected by GUI frontends.
65 after the event loop has run, as expected by GUI frontends.
66 """
66 """
67 self.call_handlers(*args, **kwds)
67 self.call_handlers(*args, **kwds)
68
68
69 def process_events(self):
69 def process_events(self):
70 """ Process any pending GUI events.
70 """ Process any pending GUI events.
71
71
72 This method will be never be called from a frontend without an event
72 This method will be never be called from a frontend without an event
73 loop (e.g., a terminal frontend).
73 loop (e.g., a terminal frontend).
74 """
74 """
75 raise NotImplementedError
75 raise NotImplementedError
76
76
77
77
78 class InProcessShellChannel(InProcessChannel):
78 class InProcessShellChannel(InProcessChannel):
79 """See `IPython.zmq.kernelmanager.ShellChannel` for docstrings."""
79 """See `IPython.kernel.kernelmanager.ShellChannel` for docstrings."""
80
80
81 # flag for whether execute requests should be allowed to call raw_input
81 # flag for whether execute requests should be allowed to call raw_input
82 allow_stdin = True
82 allow_stdin = True
83
83
84 #--------------------------------------------------------------------------
84 #--------------------------------------------------------------------------
85 # ShellChannel interface
85 # ShellChannel interface
86 #--------------------------------------------------------------------------
86 #--------------------------------------------------------------------------
87
87
88 def execute(self, code, silent=False, store_history=True,
88 def execute(self, code, silent=False, store_history=True,
89 user_variables=[], user_expressions={}, allow_stdin=None):
89 user_variables=[], user_expressions={}, allow_stdin=None):
90 if allow_stdin is None:
90 if allow_stdin is None:
91 allow_stdin = self.allow_stdin
91 allow_stdin = self.allow_stdin
92 content = dict(code=code, silent=silent, store_history=store_history,
92 content = dict(code=code, silent=silent, store_history=store_history,
93 user_variables=user_variables,
93 user_variables=user_variables,
94 user_expressions=user_expressions,
94 user_expressions=user_expressions,
95 allow_stdin=allow_stdin)
95 allow_stdin=allow_stdin)
96 msg = self.manager.session.msg('execute_request', content)
96 msg = self.manager.session.msg('execute_request', content)
97 self._dispatch_to_kernel(msg)
97 self._dispatch_to_kernel(msg)
98 return msg['header']['msg_id']
98 return msg['header']['msg_id']
99
99
100 def complete(self, text, line, cursor_pos, block=None):
100 def complete(self, text, line, cursor_pos, block=None):
101 content = dict(text=text, line=line, block=block, cursor_pos=cursor_pos)
101 content = dict(text=text, line=line, block=block, cursor_pos=cursor_pos)
102 msg = self.manager.session.msg('complete_request', content)
102 msg = self.manager.session.msg('complete_request', content)
103 self._dispatch_to_kernel(msg)
103 self._dispatch_to_kernel(msg)
104 return msg['header']['msg_id']
104 return msg['header']['msg_id']
105
105
106 def object_info(self, oname, detail_level=0):
106 def object_info(self, oname, detail_level=0):
107 content = dict(oname=oname, detail_level=detail_level)
107 content = dict(oname=oname, detail_level=detail_level)
108 msg = self.manager.session.msg('object_info_request', content)
108 msg = self.manager.session.msg('object_info_request', content)
109 self._dispatch_to_kernel(msg)
109 self._dispatch_to_kernel(msg)
110 return msg['header']['msg_id']
110 return msg['header']['msg_id']
111
111
112 def history(self, raw=True, output=False, hist_access_type='range', **kwds):
112 def history(self, raw=True, output=False, hist_access_type='range', **kwds):
113 content = dict(raw=raw, output=output,
113 content = dict(raw=raw, output=output,
114 hist_access_type=hist_access_type, **kwds)
114 hist_access_type=hist_access_type, **kwds)
115 msg = self.manager.session.msg('history_request', content)
115 msg = self.manager.session.msg('history_request', content)
116 self._dispatch_to_kernel(msg)
116 self._dispatch_to_kernel(msg)
117 return msg['header']['msg_id']
117 return msg['header']['msg_id']
118
118
119 def shutdown(self, restart=False):
119 def shutdown(self, restart=False):
120 # FIXME: What to do here?
120 # FIXME: What to do here?
121 raise NotImplementedError('Cannot shutdown in-process kernel')
121 raise NotImplementedError('Cannot shutdown in-process kernel')
122
122
123 #--------------------------------------------------------------------------
123 #--------------------------------------------------------------------------
124 # Protected interface
124 # Protected interface
125 #--------------------------------------------------------------------------
125 #--------------------------------------------------------------------------
126
126
127 def _dispatch_to_kernel(self, msg):
127 def _dispatch_to_kernel(self, msg):
128 """ Send a message to the kernel and handle a reply.
128 """ Send a message to the kernel and handle a reply.
129 """
129 """
130 kernel = self.manager.kernel
130 kernel = self.manager.kernel
131 if kernel is None:
131 if kernel is None:
132 raise RuntimeError('Cannot send request. No kernel exists.')
132 raise RuntimeError('Cannot send request. No kernel exists.')
133
133
134 stream = DummySocket()
134 stream = DummySocket()
135 self.manager.session.send(stream, msg)
135 self.manager.session.send(stream, msg)
136 msg_parts = stream.recv_multipart()
136 msg_parts = stream.recv_multipart()
137 kernel.dispatch_shell(stream, msg_parts)
137 kernel.dispatch_shell(stream, msg_parts)
138
138
139 idents, reply_msg = self.manager.session.recv(stream, copy=False)
139 idents, reply_msg = self.manager.session.recv(stream, copy=False)
140 self.call_handlers_later(reply_msg)
140 self.call_handlers_later(reply_msg)
141
141
142
142
143 class InProcessIOPubChannel(InProcessChannel):
143 class InProcessIOPubChannel(InProcessChannel):
144 """See `IPython.zmq.kernelmanager.IOPubChannel` for docstrings."""
144 """See `IPython.kernel.kernelmanager.IOPubChannel` for docstrings."""
145
145
146 def flush(self, timeout=1.0):
146 def flush(self, timeout=1.0):
147 pass
147 pass
148
148
149
149
150 class InProcessStdInChannel(InProcessChannel):
150 class InProcessStdInChannel(InProcessChannel):
151 """See `IPython.zmq.kernelmanager.StdInChannel` for docstrings."""
151 """See `IPython.kernel.kernelmanager.StdInChannel` for docstrings."""
152
152
153 def input(self, string):
153 def input(self, string):
154 kernel = self.manager.kernel
154 kernel = self.manager.kernel
155 if kernel is None:
155 if kernel is None:
156 raise RuntimeError('Cannot send input reply. No kernel exists.')
156 raise RuntimeError('Cannot send input reply. No kernel exists.')
157 kernel.raw_input_str = string
157 kernel.raw_input_str = string
158
158
159
159
160 class InProcessHBChannel(InProcessChannel):
160 class InProcessHBChannel(InProcessChannel):
161 """See `IPython.zmq.kernelmanager.HBChannel` for docstrings."""
161 """See `IPython.kernel.kernelmanager.HBChannel` for docstrings."""
162
162
163 time_to_dead = 3.0
163 time_to_dead = 3.0
164
164
165 def __init__(self, *args, **kwds):
165 def __init__(self, *args, **kwds):
166 super(InProcessHBChannel, self).__init__(*args, **kwds)
166 super(InProcessHBChannel, self).__init__(*args, **kwds)
167 self._pause = True
167 self._pause = True
168
168
169 def pause(self):
169 def pause(self):
170 self._pause = True
170 self._pause = True
171
171
172 def unpause(self):
172 def unpause(self):
173 self._pause = False
173 self._pause = False
174
174
175 def is_beating(self):
175 def is_beating(self):
176 return not self._pause
176 return not self._pause
177
177
178
178
179 #-----------------------------------------------------------------------------
179 #-----------------------------------------------------------------------------
180 # Main kernel manager class
180 # Main kernel manager class
181 #-----------------------------------------------------------------------------
181 #-----------------------------------------------------------------------------
182
182
183 class InProcessKernelManager(Configurable):
183 class InProcessKernelManager(Configurable):
184 """A manager for an in-process kernel.
184 """A manager for an in-process kernel.
185
185
186 This class implements the interface of
186 This class implements the interface of
187 `IPython.kernel.kernelmanagerabc.KernelManagerABC` and allows
187 `IPython.kernel.kernelmanagerabc.KernelManagerABC` and allows
188 (asynchronous) frontends to be used seamlessly with an in-process kernel.
188 (asynchronous) frontends to be used seamlessly with an in-process kernel.
189
189
190 See `IPython.zmq.kernelmanager.KernelManager` for docstrings.
190 See `IPython.kernel.kernelmanager.KernelManager` for docstrings.
191 """
191 """
192
192
193 # The Session to use for building messages.
193 # The Session to use for building messages.
194 session = Instance('IPython.zmq.session.Session')
194 session = Instance('IPython.zmq.session.Session')
195 def _session_default(self):
195 def _session_default(self):
196 from IPython.zmq.session import Session
196 from IPython.zmq.session import Session
197 return Session(config=self.config)
197 return Session(config=self.config)
198
198
199 # The kernel process with which the KernelManager is communicating.
199 # The kernel process with which the KernelManager is communicating.
200 kernel = Instance('IPython.inprocess.ipkernel.InProcessKernel')
200 kernel = Instance('IPython.inprocess.ipkernel.InProcessKernel')
201
201
202 # The classes to use for the various channels.
202 # The classes to use for the various channels.
203 shell_channel_class = Type(InProcessShellChannel)
203 shell_channel_class = Type(InProcessShellChannel)
204 iopub_channel_class = Type(InProcessIOPubChannel)
204 iopub_channel_class = Type(InProcessIOPubChannel)
205 stdin_channel_class = Type(InProcessStdInChannel)
205 stdin_channel_class = Type(InProcessStdInChannel)
206 hb_channel_class = Type(InProcessHBChannel)
206 hb_channel_class = Type(InProcessHBChannel)
207
207
208 # Protected traits.
208 # Protected traits.
209 _shell_channel = Any
209 _shell_channel = Any
210 _iopub_channel = Any
210 _iopub_channel = Any
211 _stdin_channel = Any
211 _stdin_channel = Any
212 _hb_channel = Any
212 _hb_channel = Any
213
213
214 #--------------------------------------------------------------------------
214 #--------------------------------------------------------------------------
215 # Channel management methods.
215 # Channel management methods.
216 #--------------------------------------------------------------------------
216 #--------------------------------------------------------------------------
217
217
218 def start_channels(self, shell=True, iopub=True, stdin=True, hb=True):
218 def start_channels(self, shell=True, iopub=True, stdin=True, hb=True):
219 if shell:
219 if shell:
220 self.shell_channel.start()
220 self.shell_channel.start()
221 if iopub:
221 if iopub:
222 self.iopub_channel.start()
222 self.iopub_channel.start()
223 if stdin:
223 if stdin:
224 self.stdin_channel.start()
224 self.stdin_channel.start()
225 self.shell_channel.allow_stdin = True
225 self.shell_channel.allow_stdin = True
226 else:
226 else:
227 self.shell_channel.allow_stdin = False
227 self.shell_channel.allow_stdin = False
228 if hb:
228 if hb:
229 self.hb_channel.start()
229 self.hb_channel.start()
230
230
231 def stop_channels(self):
231 def stop_channels(self):
232 if self.shell_channel.is_alive():
232 if self.shell_channel.is_alive():
233 self.shell_channel.stop()
233 self.shell_channel.stop()
234 if self.iopub_channel.is_alive():
234 if self.iopub_channel.is_alive():
235 self.iopub_channel.stop()
235 self.iopub_channel.stop()
236 if self.stdin_channel.is_alive():
236 if self.stdin_channel.is_alive():
237 self.stdin_channel.stop()
237 self.stdin_channel.stop()
238 if self.hb_channel.is_alive():
238 if self.hb_channel.is_alive():
239 self.hb_channel.stop()
239 self.hb_channel.stop()
240
240
241 @property
241 @property
242 def channels_running(self):
242 def channels_running(self):
243 return (self.shell_channel.is_alive() or self.iopub_channel.is_alive() or
243 return (self.shell_channel.is_alive() or self.iopub_channel.is_alive() or
244 self.stdin_channel.is_alive() or self.hb_channel.is_alive())
244 self.stdin_channel.is_alive() or self.hb_channel.is_alive())
245
245
246 @property
246 @property
247 def shell_channel(self):
247 def shell_channel(self):
248 if self._shell_channel is None:
248 if self._shell_channel is None:
249 self._shell_channel = self.shell_channel_class(self)
249 self._shell_channel = self.shell_channel_class(self)
250 return self._shell_channel
250 return self._shell_channel
251
251
252 @property
252 @property
253 def iopub_channel(self):
253 def iopub_channel(self):
254 if self._iopub_channel is None:
254 if self._iopub_channel is None:
255 self._iopub_channel = self.iopub_channel_class(self)
255 self._iopub_channel = self.iopub_channel_class(self)
256 return self._iopub_channel
256 return self._iopub_channel
257
257
258 @property
258 @property
259 def stdin_channel(self):
259 def stdin_channel(self):
260 if self._stdin_channel is None:
260 if self._stdin_channel is None:
261 self._stdin_channel = self.stdin_channel_class(self)
261 self._stdin_channel = self.stdin_channel_class(self)
262 return self._stdin_channel
262 return self._stdin_channel
263
263
264 @property
264 @property
265 def hb_channel(self):
265 def hb_channel(self):
266 if self._hb_channel is None:
266 if self._hb_channel is None:
267 self._hb_channel = self.hb_channel_class(self)
267 self._hb_channel = self.hb_channel_class(self)
268 return self._hb_channel
268 return self._hb_channel
269
269
270 #--------------------------------------------------------------------------
270 #--------------------------------------------------------------------------
271 # Kernel management methods:
271 # Kernel management methods:
272 #--------------------------------------------------------------------------
272 #--------------------------------------------------------------------------
273
273
274 def start_kernel(self, **kwds):
274 def start_kernel(self, **kwds):
275 from IPython.inprocess.ipkernel import InProcessKernel
275 from IPython.inprocess.ipkernel import InProcessKernel
276 self.kernel = InProcessKernel()
276 self.kernel = InProcessKernel()
277 self.kernel.frontends.append(self)
277 self.kernel.frontends.append(self)
278
278
279 def shutdown_kernel(self):
279 def shutdown_kernel(self):
280 self._kill_kernel()
280 self._kill_kernel()
281
281
282 def restart_kernel(self, now=False, **kwds):
282 def restart_kernel(self, now=False, **kwds):
283 self.shutdown_kernel()
283 self.shutdown_kernel()
284 self.start_kernel(**kwds)
284 self.start_kernel(**kwds)
285
285
286 @property
286 @property
287 def has_kernel(self):
287 def has_kernel(self):
288 return self.kernel is not None
288 return self.kernel is not None
289
289
290 def _kill_kernel(self):
290 def _kill_kernel(self):
291 self.kernel.frontends.remove(self)
291 self.kernel.frontends.remove(self)
292 self.kernel = None
292 self.kernel = None
293
293
294 def interrupt_kernel(self):
294 def interrupt_kernel(self):
295 raise NotImplementedError("Cannot interrupt in-process kernel.")
295 raise NotImplementedError("Cannot interrupt in-process kernel.")
296
296
297 def signal_kernel(self, signum):
297 def signal_kernel(self, signum):
298 raise NotImplementedError("Cannot signal in-process kernel.")
298 raise NotImplementedError("Cannot signal in-process kernel.")
299
299
300 @property
300 @property
301 def is_alive(self):
301 def is_alive(self):
302 return True
302 return True
303
303
304
304
305 #-----------------------------------------------------------------------------
305 #-----------------------------------------------------------------------------
306 # ABC Registration
306 # ABC Registration
307 #-----------------------------------------------------------------------------
307 #-----------------------------------------------------------------------------
308
308
309 ShellChannelABC.register(InProcessShellChannel)
309 ShellChannelABC.register(InProcessShellChannel)
310 IOPubChannelABC.register(InProcessIOPubChannel)
310 IOPubChannelABC.register(InProcessIOPubChannel)
311 HBChannelABC.register(InProcessHBChannel)
311 HBChannelABC.register(InProcessHBChannel)
312 StdInChannelABC.register(InProcessStdInChannel)
312 StdInChannelABC.register(InProcessStdInChannel)
313 KernelManagerABC.register(InProcessKernelManager)
313 KernelManagerABC.register(InProcessKernelManager)
1 NO CONTENT: file renamed from IPython/zmq/blockingkernelmanager.py to IPython/kernel/blockingkernelmanager.py
NO CONTENT: file renamed from IPython/zmq/blockingkernelmanager.py to IPython/kernel/blockingkernelmanager.py
@@ -1,1129 +1,1129 b''
1 """Base classes to manage the interaction with a running kernel.
1 """Base classes to manage the interaction with a running kernel.
2
2
3 TODO
3 TODO
4 * Create logger to handle debugging and console messages.
4 * Create logger to handle debugging and console messages.
5 """
5 """
6
6
7 #-----------------------------------------------------------------------------
7 #-----------------------------------------------------------------------------
8 # Copyright (C) 2008-2011 The IPython Development Team
8 # Copyright (C) 2008-2011 The IPython Development Team
9 #
9 #
10 # Distributed under the terms of the BSD License. The full license is in
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
11 # the file COPYING, distributed as part of this software.
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13
13
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15 # Imports
15 # Imports
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17
17
18 # Standard library imports.
18 # Standard library imports
19 import atexit
19 import atexit
20 import errno
20 import errno
21 import json
21 import json
22 from subprocess import Popen
22 from subprocess import Popen
23 import os
23 import os
24 import signal
24 import signal
25 import sys
25 import sys
26 from threading import Thread
26 from threading import Thread
27 import time
27 import time
28
28
29 # System library imports.
29 # System library imports
30 import zmq
30 import zmq
31 # import ZMQError in top-level namespace, to avoid ugly attribute-error messages
31 # import ZMQError in top-level namespace, to avoid ugly attribute-error messages
32 # during garbage collection of threads at exit:
32 # during garbage collection of threads at exit:
33 from zmq import ZMQError
33 from zmq import ZMQError
34 from zmq.eventloop import ioloop, zmqstream
34 from zmq.eventloop import ioloop, zmqstream
35
35
36 # Local imports.
36 # Local imports
37 from IPython.config.configurable import Configurable
37 from IPython.config.configurable import Configurable
38 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
38 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
39 from IPython.utils.traitlets import (
39 from IPython.utils.traitlets import (
40 Any, Instance, Type, Unicode, List, Integer, Bool, CaselessStrEnum
40 Any, Instance, Type, Unicode, List, Integer, Bool, CaselessStrEnum
41 )
41 )
42 from IPython.utils.py3compat import str_to_bytes
42 from IPython.utils.py3compat import str_to_bytes
43 from IPython.kernel import (
43 from IPython.kernel import (
44 write_connection_file,
44 write_connection_file,
45 make_ipkernel_cmd,
45 make_ipkernel_cmd,
46 launch_kernel,
46 launch_kernel,
47 )
47 )
48 from session import Session
48 from IPython.zmq.session import Session
49 from IPython.kernel import (
49 from IPython.kernel import (
50 ShellChannelABC, IOPubChannelABC,
50 ShellChannelABC, IOPubChannelABC,
51 HBChannelABC, StdInChannelABC,
51 HBChannelABC, StdInChannelABC,
52 KernelManagerABC
52 KernelManagerABC
53 )
53 )
54
54
55
55
56 #-----------------------------------------------------------------------------
56 #-----------------------------------------------------------------------------
57 # Constants and exceptions
57 # Constants and exceptions
58 #-----------------------------------------------------------------------------
58 #-----------------------------------------------------------------------------
59
59
60 class InvalidPortNumber(Exception):
60 class InvalidPortNumber(Exception):
61 pass
61 pass
62
62
63 #-----------------------------------------------------------------------------
63 #-----------------------------------------------------------------------------
64 # Utility functions
64 # Utility functions
65 #-----------------------------------------------------------------------------
65 #-----------------------------------------------------------------------------
66
66
67 # some utilities to validate message structure, these might get moved elsewhere
67 # some utilities to validate message structure, these might get moved elsewhere
68 # if they prove to have more generic utility
68 # if they prove to have more generic utility
69
69
70 def validate_string_list(lst):
70 def validate_string_list(lst):
71 """Validate that the input is a list of strings.
71 """Validate that the input is a list of strings.
72
72
73 Raises ValueError if not."""
73 Raises ValueError if not."""
74 if not isinstance(lst, list):
74 if not isinstance(lst, list):
75 raise ValueError('input %r must be a list' % lst)
75 raise ValueError('input %r must be a list' % lst)
76 for x in lst:
76 for x in lst:
77 if not isinstance(x, basestring):
77 if not isinstance(x, basestring):
78 raise ValueError('element %r in list must be a string' % x)
78 raise ValueError('element %r in list must be a string' % x)
79
79
80
80
81 def validate_string_dict(dct):
81 def validate_string_dict(dct):
82 """Validate that the input is a dict with string keys and values.
82 """Validate that the input is a dict with string keys and values.
83
83
84 Raises ValueError if not."""
84 Raises ValueError if not."""
85 for k,v in dct.iteritems():
85 for k,v in dct.iteritems():
86 if not isinstance(k, basestring):
86 if not isinstance(k, basestring):
87 raise ValueError('key %r in dict must be a string' % k)
87 raise ValueError('key %r in dict must be a string' % k)
88 if not isinstance(v, basestring):
88 if not isinstance(v, basestring):
89 raise ValueError('value %r in dict must be a string' % v)
89 raise ValueError('value %r in dict must be a string' % v)
90
90
91
91
92 #-----------------------------------------------------------------------------
92 #-----------------------------------------------------------------------------
93 # ZMQ Socket Channel classes
93 # ZMQ Socket Channel classes
94 #-----------------------------------------------------------------------------
94 #-----------------------------------------------------------------------------
95
95
96 class ZMQSocketChannel(Thread):
96 class ZMQSocketChannel(Thread):
97 """The base class for the channels that use ZMQ sockets."""
97 """The base class for the channels that use ZMQ sockets."""
98 context = None
98 context = None
99 session = None
99 session = None
100 socket = None
100 socket = None
101 ioloop = None
101 ioloop = None
102 stream = None
102 stream = None
103 _address = None
103 _address = None
104 _exiting = False
104 _exiting = False
105
105
106 def __init__(self, context, session, address):
106 def __init__(self, context, session, address):
107 """Create a channel.
107 """Create a channel.
108
108
109 Parameters
109 Parameters
110 ----------
110 ----------
111 context : :class:`zmq.Context`
111 context : :class:`zmq.Context`
112 The ZMQ context to use.
112 The ZMQ context to use.
113 session : :class:`session.Session`
113 session : :class:`session.Session`
114 The session to use.
114 The session to use.
115 address : zmq url
115 address : zmq url
116 Standard (ip, port) tuple that the kernel is listening on.
116 Standard (ip, port) tuple that the kernel is listening on.
117 """
117 """
118 super(ZMQSocketChannel, self).__init__()
118 super(ZMQSocketChannel, self).__init__()
119 self.daemon = True
119 self.daemon = True
120
120
121 self.context = context
121 self.context = context
122 self.session = session
122 self.session = session
123 if isinstance(address, tuple):
123 if isinstance(address, tuple):
124 if address[1] == 0:
124 if address[1] == 0:
125 message = 'The port number for a channel cannot be 0.'
125 message = 'The port number for a channel cannot be 0.'
126 raise InvalidPortNumber(message)
126 raise InvalidPortNumber(message)
127 address = "tcp://%s:%i" % address
127 address = "tcp://%s:%i" % address
128 self._address = address
128 self._address = address
129 atexit.register(self._notice_exit)
129 atexit.register(self._notice_exit)
130
130
131 def _notice_exit(self):
131 def _notice_exit(self):
132 self._exiting = True
132 self._exiting = True
133
133
134 def _run_loop(self):
134 def _run_loop(self):
135 """Run my loop, ignoring EINTR events in the poller"""
135 """Run my loop, ignoring EINTR events in the poller"""
136 while True:
136 while True:
137 try:
137 try:
138 self.ioloop.start()
138 self.ioloop.start()
139 except ZMQError as e:
139 except ZMQError as e:
140 if e.errno == errno.EINTR:
140 if e.errno == errno.EINTR:
141 continue
141 continue
142 else:
142 else:
143 raise
143 raise
144 except Exception:
144 except Exception:
145 if self._exiting:
145 if self._exiting:
146 break
146 break
147 else:
147 else:
148 raise
148 raise
149 else:
149 else:
150 break
150 break
151
151
152 def stop(self):
152 def stop(self):
153 """Stop the channel's event loop and join its thread.
153 """Stop the channel's event loop and join its thread.
154
154
155 This calls :method:`Thread.join` and returns when the thread
155 This calls :method:`Thread.join` and returns when the thread
156 terminates. :class:`RuntimeError` will be raised if
156 terminates. :class:`RuntimeError` will be raised if
157 :method:`self.start` is called again.
157 :method:`self.start` is called again.
158 """
158 """
159 self.join()
159 self.join()
160
160
161 @property
161 @property
162 def address(self):
162 def address(self):
163 """Get the channel's address as a zmq url string.
163 """Get the channel's address as a zmq url string.
164
164
165 These URLS have the form: 'tcp://127.0.0.1:5555'.
165 These URLS have the form: 'tcp://127.0.0.1:5555'.
166 """
166 """
167 return self._address
167 return self._address
168
168
169 def _queue_send(self, msg):
169 def _queue_send(self, msg):
170 """Queue a message to be sent from the IOLoop's thread.
170 """Queue a message to be sent from the IOLoop's thread.
171
171
172 Parameters
172 Parameters
173 ----------
173 ----------
174 msg : message to send
174 msg : message to send
175
175
176 This is threadsafe, as it uses IOLoop.add_callback to give the loop's
176 This is threadsafe, as it uses IOLoop.add_callback to give the loop's
177 thread control of the action.
177 thread control of the action.
178 """
178 """
179 def thread_send():
179 def thread_send():
180 self.session.send(self.stream, msg)
180 self.session.send(self.stream, msg)
181 self.ioloop.add_callback(thread_send)
181 self.ioloop.add_callback(thread_send)
182
182
183 def _handle_recv(self, msg):
183 def _handle_recv(self, msg):
184 """Callback for stream.on_recv.
184 """Callback for stream.on_recv.
185
185
186 Unpacks message, and calls handlers with it.
186 Unpacks message, and calls handlers with it.
187 """
187 """
188 ident,smsg = self.session.feed_identities(msg)
188 ident,smsg = self.session.feed_identities(msg)
189 self.call_handlers(self.session.unserialize(smsg))
189 self.call_handlers(self.session.unserialize(smsg))
190
190
191
191
192
192
193 class ShellChannel(ZMQSocketChannel):
193 class ShellChannel(ZMQSocketChannel):
194 """The shell channel for issuing request/replies to the kernel."""
194 """The shell channel for issuing request/replies to the kernel."""
195
195
196 command_queue = None
196 command_queue = None
197 # flag for whether execute requests should be allowed to call raw_input:
197 # flag for whether execute requests should be allowed to call raw_input:
198 allow_stdin = True
198 allow_stdin = True
199
199
200 def __init__(self, context, session, address):
200 def __init__(self, context, session, address):
201 super(ShellChannel, self).__init__(context, session, address)
201 super(ShellChannel, self).__init__(context, session, address)
202 self.ioloop = ioloop.IOLoop()
202 self.ioloop = ioloop.IOLoop()
203
203
204 def run(self):
204 def run(self):
205 """The thread's main activity. Call start() instead."""
205 """The thread's main activity. Call start() instead."""
206 self.socket = self.context.socket(zmq.DEALER)
206 self.socket = self.context.socket(zmq.DEALER)
207 self.socket.setsockopt(zmq.IDENTITY, self.session.bsession)
207 self.socket.setsockopt(zmq.IDENTITY, self.session.bsession)
208 self.socket.connect(self.address)
208 self.socket.connect(self.address)
209 self.stream = zmqstream.ZMQStream(self.socket, self.ioloop)
209 self.stream = zmqstream.ZMQStream(self.socket, self.ioloop)
210 self.stream.on_recv(self._handle_recv)
210 self.stream.on_recv(self._handle_recv)
211 self._run_loop()
211 self._run_loop()
212 try:
212 try:
213 self.socket.close()
213 self.socket.close()
214 except:
214 except:
215 pass
215 pass
216
216
217 def stop(self):
217 def stop(self):
218 """Stop the channel's event loop and join its thread."""
218 """Stop the channel's event loop and join its thread."""
219 self.ioloop.stop()
219 self.ioloop.stop()
220 super(ShellChannel, self).stop()
220 super(ShellChannel, self).stop()
221
221
222 def call_handlers(self, msg):
222 def call_handlers(self, msg):
223 """This method is called in the ioloop thread when a message arrives.
223 """This method is called in the ioloop thread when a message arrives.
224
224
225 Subclasses should override this method to handle incoming messages.
225 Subclasses should override this method to handle incoming messages.
226 It is important to remember that this method is called in the thread
226 It is important to remember that this method is called in the thread
227 so that some logic must be done to ensure that the application leve
227 so that some logic must be done to ensure that the application leve
228 handlers are called in the application thread.
228 handlers are called in the application thread.
229 """
229 """
230 raise NotImplementedError('call_handlers must be defined in a subclass.')
230 raise NotImplementedError('call_handlers must be defined in a subclass.')
231
231
232 def execute(self, code, silent=False, store_history=True,
232 def execute(self, code, silent=False, store_history=True,
233 user_variables=None, user_expressions=None, allow_stdin=None):
233 user_variables=None, user_expressions=None, allow_stdin=None):
234 """Execute code in the kernel.
234 """Execute code in the kernel.
235
235
236 Parameters
236 Parameters
237 ----------
237 ----------
238 code : str
238 code : str
239 A string of Python code.
239 A string of Python code.
240
240
241 silent : bool, optional (default False)
241 silent : bool, optional (default False)
242 If set, the kernel will execute the code as quietly possible, and
242 If set, the kernel will execute the code as quietly possible, and
243 will force store_history to be False.
243 will force store_history to be False.
244
244
245 store_history : bool, optional (default True)
245 store_history : bool, optional (default True)
246 If set, the kernel will store command history. This is forced
246 If set, the kernel will store command history. This is forced
247 to be False if silent is True.
247 to be False if silent is True.
248
248
249 user_variables : list, optional
249 user_variables : list, optional
250 A list of variable names to pull from the user's namespace. They
250 A list of variable names to pull from the user's namespace. They
251 will come back as a dict with these names as keys and their
251 will come back as a dict with these names as keys and their
252 :func:`repr` as values.
252 :func:`repr` as values.
253
253
254 user_expressions : dict, optional
254 user_expressions : dict, optional
255 A dict mapping names to expressions to be evaluated in the user's
255 A dict mapping names to expressions to be evaluated in the user's
256 dict. The expression values are returned as strings formatted using
256 dict. The expression values are returned as strings formatted using
257 :func:`repr`.
257 :func:`repr`.
258
258
259 allow_stdin : bool, optional (default self.allow_stdin)
259 allow_stdin : bool, optional (default self.allow_stdin)
260 Flag for whether the kernel can send stdin requests to frontends.
260 Flag for whether the kernel can send stdin requests to frontends.
261
261
262 Some frontends (e.g. the Notebook) do not support stdin requests.
262 Some frontends (e.g. the Notebook) do not support stdin requests.
263 If raw_input is called from code executed from such a frontend, a
263 If raw_input is called from code executed from such a frontend, a
264 StdinNotImplementedError will be raised.
264 StdinNotImplementedError will be raised.
265
265
266 Returns
266 Returns
267 -------
267 -------
268 The msg_id of the message sent.
268 The msg_id of the message sent.
269 """
269 """
270 if user_variables is None:
270 if user_variables is None:
271 user_variables = []
271 user_variables = []
272 if user_expressions is None:
272 if user_expressions is None:
273 user_expressions = {}
273 user_expressions = {}
274 if allow_stdin is None:
274 if allow_stdin is None:
275 allow_stdin = self.allow_stdin
275 allow_stdin = self.allow_stdin
276
276
277
277
278 # Don't waste network traffic if inputs are invalid
278 # Don't waste network traffic if inputs are invalid
279 if not isinstance(code, basestring):
279 if not isinstance(code, basestring):
280 raise ValueError('code %r must be a string' % code)
280 raise ValueError('code %r must be a string' % code)
281 validate_string_list(user_variables)
281 validate_string_list(user_variables)
282 validate_string_dict(user_expressions)
282 validate_string_dict(user_expressions)
283
283
284 # Create class for content/msg creation. Related to, but possibly
284 # Create class for content/msg creation. Related to, but possibly
285 # not in Session.
285 # not in Session.
286 content = dict(code=code, silent=silent, store_history=store_history,
286 content = dict(code=code, silent=silent, store_history=store_history,
287 user_variables=user_variables,
287 user_variables=user_variables,
288 user_expressions=user_expressions,
288 user_expressions=user_expressions,
289 allow_stdin=allow_stdin,
289 allow_stdin=allow_stdin,
290 )
290 )
291 msg = self.session.msg('execute_request', content)
291 msg = self.session.msg('execute_request', content)
292 self._queue_send(msg)
292 self._queue_send(msg)
293 return msg['header']['msg_id']
293 return msg['header']['msg_id']
294
294
295 def complete(self, text, line, cursor_pos, block=None):
295 def complete(self, text, line, cursor_pos, block=None):
296 """Tab complete text in the kernel's namespace.
296 """Tab complete text in the kernel's namespace.
297
297
298 Parameters
298 Parameters
299 ----------
299 ----------
300 text : str
300 text : str
301 The text to complete.
301 The text to complete.
302 line : str
302 line : str
303 The full line of text that is the surrounding context for the
303 The full line of text that is the surrounding context for the
304 text to complete.
304 text to complete.
305 cursor_pos : int
305 cursor_pos : int
306 The position of the cursor in the line where the completion was
306 The position of the cursor in the line where the completion was
307 requested.
307 requested.
308 block : str, optional
308 block : str, optional
309 The full block of code in which the completion is being requested.
309 The full block of code in which the completion is being requested.
310
310
311 Returns
311 Returns
312 -------
312 -------
313 The msg_id of the message sent.
313 The msg_id of the message sent.
314 """
314 """
315 content = dict(text=text, line=line, block=block, cursor_pos=cursor_pos)
315 content = dict(text=text, line=line, block=block, cursor_pos=cursor_pos)
316 msg = self.session.msg('complete_request', content)
316 msg = self.session.msg('complete_request', content)
317 self._queue_send(msg)
317 self._queue_send(msg)
318 return msg['header']['msg_id']
318 return msg['header']['msg_id']
319
319
320 def object_info(self, oname, detail_level=0):
320 def object_info(self, oname, detail_level=0):
321 """Get metadata information about an object in the kernel's namespace.
321 """Get metadata information about an object in the kernel's namespace.
322
322
323 Parameters
323 Parameters
324 ----------
324 ----------
325 oname : str
325 oname : str
326 A string specifying the object name.
326 A string specifying the object name.
327 detail_level : int, optional
327 detail_level : int, optional
328 The level of detail for the introspection (0-2)
328 The level of detail for the introspection (0-2)
329
329
330 Returns
330 Returns
331 -------
331 -------
332 The msg_id of the message sent.
332 The msg_id of the message sent.
333 """
333 """
334 content = dict(oname=oname, detail_level=detail_level)
334 content = dict(oname=oname, detail_level=detail_level)
335 msg = self.session.msg('object_info_request', content)
335 msg = self.session.msg('object_info_request', content)
336 self._queue_send(msg)
336 self._queue_send(msg)
337 return msg['header']['msg_id']
337 return msg['header']['msg_id']
338
338
339 def history(self, raw=True, output=False, hist_access_type='range', **kwargs):
339 def history(self, raw=True, output=False, hist_access_type='range', **kwargs):
340 """Get entries from the kernel's history list.
340 """Get entries from the kernel's history list.
341
341
342 Parameters
342 Parameters
343 ----------
343 ----------
344 raw : bool
344 raw : bool
345 If True, return the raw input.
345 If True, return the raw input.
346 output : bool
346 output : bool
347 If True, then return the output as well.
347 If True, then return the output as well.
348 hist_access_type : str
348 hist_access_type : str
349 'range' (fill in session, start and stop params), 'tail' (fill in n)
349 'range' (fill in session, start and stop params), 'tail' (fill in n)
350 or 'search' (fill in pattern param).
350 or 'search' (fill in pattern param).
351
351
352 session : int
352 session : int
353 For a range request, the session from which to get lines. Session
353 For a range request, the session from which to get lines. Session
354 numbers are positive integers; negative ones count back from the
354 numbers are positive integers; negative ones count back from the
355 current session.
355 current session.
356 start : int
356 start : int
357 The first line number of a history range.
357 The first line number of a history range.
358 stop : int
358 stop : int
359 The final (excluded) line number of a history range.
359 The final (excluded) line number of a history range.
360
360
361 n : int
361 n : int
362 The number of lines of history to get for a tail request.
362 The number of lines of history to get for a tail request.
363
363
364 pattern : str
364 pattern : str
365 The glob-syntax pattern for a search request.
365 The glob-syntax pattern for a search request.
366
366
367 Returns
367 Returns
368 -------
368 -------
369 The msg_id of the message sent.
369 The msg_id of the message sent.
370 """
370 """
371 content = dict(raw=raw, output=output, hist_access_type=hist_access_type,
371 content = dict(raw=raw, output=output, hist_access_type=hist_access_type,
372 **kwargs)
372 **kwargs)
373 msg = self.session.msg('history_request', content)
373 msg = self.session.msg('history_request', content)
374 self._queue_send(msg)
374 self._queue_send(msg)
375 return msg['header']['msg_id']
375 return msg['header']['msg_id']
376
376
377 def kernel_info(self):
377 def kernel_info(self):
378 """Request kernel info."""
378 """Request kernel info."""
379 msg = self.session.msg('kernel_info_request')
379 msg = self.session.msg('kernel_info_request')
380 self._queue_send(msg)
380 self._queue_send(msg)
381 return msg['header']['msg_id']
381 return msg['header']['msg_id']
382
382
383 def shutdown(self, restart=False):
383 def shutdown(self, restart=False):
384 """Request an immediate kernel shutdown.
384 """Request an immediate kernel shutdown.
385
385
386 Upon receipt of the (empty) reply, client code can safely assume that
386 Upon receipt of the (empty) reply, client code can safely assume that
387 the kernel has shut down and it's safe to forcefully terminate it if
387 the kernel has shut down and it's safe to forcefully terminate it if
388 it's still alive.
388 it's still alive.
389
389
390 The kernel will send the reply via a function registered with Python's
390 The kernel will send the reply via a function registered with Python's
391 atexit module, ensuring it's truly done as the kernel is done with all
391 atexit module, ensuring it's truly done as the kernel is done with all
392 normal operation.
392 normal operation.
393 """
393 """
394 # Send quit message to kernel. Once we implement kernel-side setattr,
394 # Send quit message to kernel. Once we implement kernel-side setattr,
395 # this should probably be done that way, but for now this will do.
395 # this should probably be done that way, but for now this will do.
396 msg = self.session.msg('shutdown_request', {'restart':restart})
396 msg = self.session.msg('shutdown_request', {'restart':restart})
397 self._queue_send(msg)
397 self._queue_send(msg)
398 return msg['header']['msg_id']
398 return msg['header']['msg_id']
399
399
400
400
401
401
402 class IOPubChannel(ZMQSocketChannel):
402 class IOPubChannel(ZMQSocketChannel):
403 """The iopub channel which listens for messages that the kernel publishes.
403 """The iopub channel which listens for messages that the kernel publishes.
404
404
405 This channel is where all output is published to frontends.
405 This channel is where all output is published to frontends.
406 """
406 """
407
407
408 def __init__(self, context, session, address):
408 def __init__(self, context, session, address):
409 super(IOPubChannel, self).__init__(context, session, address)
409 super(IOPubChannel, self).__init__(context, session, address)
410 self.ioloop = ioloop.IOLoop()
410 self.ioloop = ioloop.IOLoop()
411
411
412 def run(self):
412 def run(self):
413 """The thread's main activity. Call start() instead."""
413 """The thread's main activity. Call start() instead."""
414 self.socket = self.context.socket(zmq.SUB)
414 self.socket = self.context.socket(zmq.SUB)
415 self.socket.setsockopt(zmq.SUBSCRIBE,b'')
415 self.socket.setsockopt(zmq.SUBSCRIBE,b'')
416 self.socket.setsockopt(zmq.IDENTITY, self.session.bsession)
416 self.socket.setsockopt(zmq.IDENTITY, self.session.bsession)
417 self.socket.connect(self.address)
417 self.socket.connect(self.address)
418 self.stream = zmqstream.ZMQStream(self.socket, self.ioloop)
418 self.stream = zmqstream.ZMQStream(self.socket, self.ioloop)
419 self.stream.on_recv(self._handle_recv)
419 self.stream.on_recv(self._handle_recv)
420 self._run_loop()
420 self._run_loop()
421 try:
421 try:
422 self.socket.close()
422 self.socket.close()
423 except:
423 except:
424 pass
424 pass
425
425
426 def stop(self):
426 def stop(self):
427 """Stop the channel's event loop and join its thread."""
427 """Stop the channel's event loop and join its thread."""
428 self.ioloop.stop()
428 self.ioloop.stop()
429 super(IOPubChannel, self).stop()
429 super(IOPubChannel, self).stop()
430
430
431 def call_handlers(self, msg):
431 def call_handlers(self, msg):
432 """This method is called in the ioloop thread when a message arrives.
432 """This method is called in the ioloop thread when a message arrives.
433
433
434 Subclasses should override this method to handle incoming messages.
434 Subclasses should override this method to handle incoming messages.
435 It is important to remember that this method is called in the thread
435 It is important to remember that this method is called in the thread
436 so that some logic must be done to ensure that the application leve
436 so that some logic must be done to ensure that the application leve
437 handlers are called in the application thread.
437 handlers are called in the application thread.
438 """
438 """
439 raise NotImplementedError('call_handlers must be defined in a subclass.')
439 raise NotImplementedError('call_handlers must be defined in a subclass.')
440
440
441 def flush(self, timeout=1.0):
441 def flush(self, timeout=1.0):
442 """Immediately processes all pending messages on the iopub channel.
442 """Immediately processes all pending messages on the iopub channel.
443
443
444 Callers should use this method to ensure that :method:`call_handlers`
444 Callers should use this method to ensure that :method:`call_handlers`
445 has been called for all messages that have been received on the
445 has been called for all messages that have been received on the
446 0MQ SUB socket of this channel.
446 0MQ SUB socket of this channel.
447
447
448 This method is thread safe.
448 This method is thread safe.
449
449
450 Parameters
450 Parameters
451 ----------
451 ----------
452 timeout : float, optional
452 timeout : float, optional
453 The maximum amount of time to spend flushing, in seconds. The
453 The maximum amount of time to spend flushing, in seconds. The
454 default is one second.
454 default is one second.
455 """
455 """
456 # We do the IOLoop callback process twice to ensure that the IOLoop
456 # We do the IOLoop callback process twice to ensure that the IOLoop
457 # gets to perform at least one full poll.
457 # gets to perform at least one full poll.
458 stop_time = time.time() + timeout
458 stop_time = time.time() + timeout
459 for i in xrange(2):
459 for i in xrange(2):
460 self._flushed = False
460 self._flushed = False
461 self.ioloop.add_callback(self._flush)
461 self.ioloop.add_callback(self._flush)
462 while not self._flushed and time.time() < stop_time:
462 while not self._flushed and time.time() < stop_time:
463 time.sleep(0.01)
463 time.sleep(0.01)
464
464
465 def _flush(self):
465 def _flush(self):
466 """Callback for :method:`self.flush`."""
466 """Callback for :method:`self.flush`."""
467 self.stream.flush()
467 self.stream.flush()
468 self._flushed = True
468 self._flushed = True
469
469
470
470
471 class StdInChannel(ZMQSocketChannel):
471 class StdInChannel(ZMQSocketChannel):
472 """The stdin channel to handle raw_input requests that the kernel makes."""
472 """The stdin channel to handle raw_input requests that the kernel makes."""
473
473
474 msg_queue = None
474 msg_queue = None
475
475
476 def __init__(self, context, session, address):
476 def __init__(self, context, session, address):
477 super(StdInChannel, self).__init__(context, session, address)
477 super(StdInChannel, self).__init__(context, session, address)
478 self.ioloop = ioloop.IOLoop()
478 self.ioloop = ioloop.IOLoop()
479
479
480 def run(self):
480 def run(self):
481 """The thread's main activity. Call start() instead."""
481 """The thread's main activity. Call start() instead."""
482 self.socket = self.context.socket(zmq.DEALER)
482 self.socket = self.context.socket(zmq.DEALER)
483 self.socket.setsockopt(zmq.IDENTITY, self.session.bsession)
483 self.socket.setsockopt(zmq.IDENTITY, self.session.bsession)
484 self.socket.connect(self.address)
484 self.socket.connect(self.address)
485 self.stream = zmqstream.ZMQStream(self.socket, self.ioloop)
485 self.stream = zmqstream.ZMQStream(self.socket, self.ioloop)
486 self.stream.on_recv(self._handle_recv)
486 self.stream.on_recv(self._handle_recv)
487 self._run_loop()
487 self._run_loop()
488 try:
488 try:
489 self.socket.close()
489 self.socket.close()
490 except:
490 except:
491 pass
491 pass
492
492
493 def stop(self):
493 def stop(self):
494 """Stop the channel's event loop and join its thread."""
494 """Stop the channel's event loop and join its thread."""
495 self.ioloop.stop()
495 self.ioloop.stop()
496 super(StdInChannel, self).stop()
496 super(StdInChannel, self).stop()
497
497
498 def call_handlers(self, msg):
498 def call_handlers(self, msg):
499 """This method is called in the ioloop thread when a message arrives.
499 """This method is called in the ioloop thread when a message arrives.
500
500
501 Subclasses should override this method to handle incoming messages.
501 Subclasses should override this method to handle incoming messages.
502 It is important to remember that this method is called in the thread
502 It is important to remember that this method is called in the thread
503 so that some logic must be done to ensure that the application leve
503 so that some logic must be done to ensure that the application leve
504 handlers are called in the application thread.
504 handlers are called in the application thread.
505 """
505 """
506 raise NotImplementedError('call_handlers must be defined in a subclass.')
506 raise NotImplementedError('call_handlers must be defined in a subclass.')
507
507
508 def input(self, string):
508 def input(self, string):
509 """Send a string of raw input to the kernel."""
509 """Send a string of raw input to the kernel."""
510 content = dict(value=string)
510 content = dict(value=string)
511 msg = self.session.msg('input_reply', content)
511 msg = self.session.msg('input_reply', content)
512 self._queue_send(msg)
512 self._queue_send(msg)
513
513
514
514
515 class HBChannel(ZMQSocketChannel):
515 class HBChannel(ZMQSocketChannel):
516 """The heartbeat channel which monitors the kernel heartbeat.
516 """The heartbeat channel which monitors the kernel heartbeat.
517
517
518 Note that the heartbeat channel is paused by default. As long as you start
518 Note that the heartbeat channel is paused by default. As long as you start
519 this channel, the kernel manager will ensure that it is paused and un-paused
519 this channel, the kernel manager will ensure that it is paused and un-paused
520 as appropriate.
520 as appropriate.
521 """
521 """
522
522
523 time_to_dead = 3.0
523 time_to_dead = 3.0
524 socket = None
524 socket = None
525 poller = None
525 poller = None
526 _running = None
526 _running = None
527 _pause = None
527 _pause = None
528 _beating = None
528 _beating = None
529
529
530 def __init__(self, context, session, address):
530 def __init__(self, context, session, address):
531 super(HBChannel, self).__init__(context, session, address)
531 super(HBChannel, self).__init__(context, session, address)
532 self._running = False
532 self._running = False
533 self._pause =True
533 self._pause =True
534 self.poller = zmq.Poller()
534 self.poller = zmq.Poller()
535
535
536 def _create_socket(self):
536 def _create_socket(self):
537 if self.socket is not None:
537 if self.socket is not None:
538 # close previous socket, before opening a new one
538 # close previous socket, before opening a new one
539 self.poller.unregister(self.socket)
539 self.poller.unregister(self.socket)
540 self.socket.close()
540 self.socket.close()
541 self.socket = self.context.socket(zmq.REQ)
541 self.socket = self.context.socket(zmq.REQ)
542 self.socket.setsockopt(zmq.LINGER, 0)
542 self.socket.setsockopt(zmq.LINGER, 0)
543 self.socket.connect(self.address)
543 self.socket.connect(self.address)
544
544
545 self.poller.register(self.socket, zmq.POLLIN)
545 self.poller.register(self.socket, zmq.POLLIN)
546
546
547 def _poll(self, start_time):
547 def _poll(self, start_time):
548 """poll for heartbeat replies until we reach self.time_to_dead.
548 """poll for heartbeat replies until we reach self.time_to_dead.
549
549
550 Ignores interrupts, and returns the result of poll(), which
550 Ignores interrupts, and returns the result of poll(), which
551 will be an empty list if no messages arrived before the timeout,
551 will be an empty list if no messages arrived before the timeout,
552 or the event tuple if there is a message to receive.
552 or the event tuple if there is a message to receive.
553 """
553 """
554
554
555 until_dead = self.time_to_dead - (time.time() - start_time)
555 until_dead = self.time_to_dead - (time.time() - start_time)
556 # ensure poll at least once
556 # ensure poll at least once
557 until_dead = max(until_dead, 1e-3)
557 until_dead = max(until_dead, 1e-3)
558 events = []
558 events = []
559 while True:
559 while True:
560 try:
560 try:
561 events = self.poller.poll(1000 * until_dead)
561 events = self.poller.poll(1000 * until_dead)
562 except ZMQError as e:
562 except ZMQError as e:
563 if e.errno == errno.EINTR:
563 if e.errno == errno.EINTR:
564 # ignore interrupts during heartbeat
564 # ignore interrupts during heartbeat
565 # this may never actually happen
565 # this may never actually happen
566 until_dead = self.time_to_dead - (time.time() - start_time)
566 until_dead = self.time_to_dead - (time.time() - start_time)
567 until_dead = max(until_dead, 1e-3)
567 until_dead = max(until_dead, 1e-3)
568 pass
568 pass
569 else:
569 else:
570 raise
570 raise
571 except Exception:
571 except Exception:
572 if self._exiting:
572 if self._exiting:
573 break
573 break
574 else:
574 else:
575 raise
575 raise
576 else:
576 else:
577 break
577 break
578 return events
578 return events
579
579
580 def run(self):
580 def run(self):
581 """The thread's main activity. Call start() instead."""
581 """The thread's main activity. Call start() instead."""
582 self._create_socket()
582 self._create_socket()
583 self._running = True
583 self._running = True
584 self._beating = True
584 self._beating = True
585
585
586 while self._running:
586 while self._running:
587 if self._pause:
587 if self._pause:
588 # just sleep, and skip the rest of the loop
588 # just sleep, and skip the rest of the loop
589 time.sleep(self.time_to_dead)
589 time.sleep(self.time_to_dead)
590 continue
590 continue
591
591
592 since_last_heartbeat = 0.0
592 since_last_heartbeat = 0.0
593 # io.rprint('Ping from HB channel') # dbg
593 # io.rprint('Ping from HB channel') # dbg
594 # no need to catch EFSM here, because the previous event was
594 # no need to catch EFSM here, because the previous event was
595 # either a recv or connect, which cannot be followed by EFSM
595 # either a recv or connect, which cannot be followed by EFSM
596 self.socket.send(b'ping')
596 self.socket.send(b'ping')
597 request_time = time.time()
597 request_time = time.time()
598 ready = self._poll(request_time)
598 ready = self._poll(request_time)
599 if ready:
599 if ready:
600 self._beating = True
600 self._beating = True
601 # the poll above guarantees we have something to recv
601 # the poll above guarantees we have something to recv
602 self.socket.recv()
602 self.socket.recv()
603 # sleep the remainder of the cycle
603 # sleep the remainder of the cycle
604 remainder = self.time_to_dead - (time.time() - request_time)
604 remainder = self.time_to_dead - (time.time() - request_time)
605 if remainder > 0:
605 if remainder > 0:
606 time.sleep(remainder)
606 time.sleep(remainder)
607 continue
607 continue
608 else:
608 else:
609 # nothing was received within the time limit, signal heart failure
609 # nothing was received within the time limit, signal heart failure
610 self._beating = False
610 self._beating = False
611 since_last_heartbeat = time.time() - request_time
611 since_last_heartbeat = time.time() - request_time
612 self.call_handlers(since_last_heartbeat)
612 self.call_handlers(since_last_heartbeat)
613 # and close/reopen the socket, because the REQ/REP cycle has been broken
613 # and close/reopen the socket, because the REQ/REP cycle has been broken
614 self._create_socket()
614 self._create_socket()
615 continue
615 continue
616 try:
616 try:
617 self.socket.close()
617 self.socket.close()
618 except:
618 except:
619 pass
619 pass
620
620
621 def pause(self):
621 def pause(self):
622 """Pause the heartbeat."""
622 """Pause the heartbeat."""
623 self._pause = True
623 self._pause = True
624
624
625 def unpause(self):
625 def unpause(self):
626 """Unpause the heartbeat."""
626 """Unpause the heartbeat."""
627 self._pause = False
627 self._pause = False
628
628
629 def is_beating(self):
629 def is_beating(self):
630 """Is the heartbeat running and responsive (and not paused)."""
630 """Is the heartbeat running and responsive (and not paused)."""
631 if self.is_alive() and not self._pause and self._beating:
631 if self.is_alive() and not self._pause and self._beating:
632 return True
632 return True
633 else:
633 else:
634 return False
634 return False
635
635
636 def stop(self):
636 def stop(self):
637 """Stop the channel's event loop and join its thread."""
637 """Stop the channel's event loop and join its thread."""
638 self._running = False
638 self._running = False
639 super(HBChannel, self).stop()
639 super(HBChannel, self).stop()
640
640
641 def call_handlers(self, since_last_heartbeat):
641 def call_handlers(self, since_last_heartbeat):
642 """This method is called in the ioloop thread when a message arrives.
642 """This method is called in the ioloop thread when a message arrives.
643
643
644 Subclasses should override this method to handle incoming messages.
644 Subclasses should override this method to handle incoming messages.
645 It is important to remember that this method is called in the thread
645 It is important to remember that this method is called in the thread
646 so that some logic must be done to ensure that the application level
646 so that some logic must be done to ensure that the application level
647 handlers are called in the application thread.
647 handlers are called in the application thread.
648 """
648 """
649 raise NotImplementedError('call_handlers must be defined in a subclass.')
649 raise NotImplementedError('call_handlers must be defined in a subclass.')
650
650
651
651
652 #-----------------------------------------------------------------------------
652 #-----------------------------------------------------------------------------
653 # Main kernel manager class
653 # Main kernel manager class
654 #-----------------------------------------------------------------------------
654 #-----------------------------------------------------------------------------
655
655
656 class KernelManager(Configurable):
656 class KernelManager(Configurable):
657 """Manages a single kernel on this host along with its channels.
657 """Manages a single kernel on this host along with its channels.
658
658
659 There are four channels associated with each kernel:
659 There are four channels associated with each kernel:
660
660
661 * shell: for request/reply calls to the kernel.
661 * shell: for request/reply calls to the kernel.
662 * iopub: for the kernel to publish results to frontends.
662 * iopub: for the kernel to publish results to frontends.
663 * hb: for monitoring the kernel's heartbeat.
663 * hb: for monitoring the kernel's heartbeat.
664 * stdin: for frontends to reply to raw_input calls in the kernel.
664 * stdin: for frontends to reply to raw_input calls in the kernel.
665
665
666 The usage of the channels that this class manages is optional. It is
666 The usage of the channels that this class manages is optional. It is
667 entirely possible to connect to the kernels directly using ZeroMQ
667 entirely possible to connect to the kernels directly using ZeroMQ
668 sockets. These channels are useful primarily for talking to a kernel
668 sockets. These channels are useful primarily for talking to a kernel
669 whose :class:`KernelManager` is in the same process.
669 whose :class:`KernelManager` is in the same process.
670
670
671 This version manages kernels started using Popen.
671 This version manages kernels started using Popen.
672 """
672 """
673 # The PyZMQ Context to use for communication with the kernel.
673 # The PyZMQ Context to use for communication with the kernel.
674 context = Instance(zmq.Context)
674 context = Instance(zmq.Context)
675 def _context_default(self):
675 def _context_default(self):
676 return zmq.Context.instance()
676 return zmq.Context.instance()
677
677
678 # The Session to use for communication with the kernel.
678 # The Session to use for communication with the kernel.
679 session = Instance(Session)
679 session = Instance(Session)
680 def _session_default(self):
680 def _session_default(self):
681 return Session(config=self.config)
681 return Session(config=self.config)
682
682
683 # The kernel process with which the KernelManager is communicating.
683 # The kernel process with which the KernelManager is communicating.
684 # generally a Popen instance
684 # generally a Popen instance
685 kernel = Any()
685 kernel = Any()
686
686
687 kernel_cmd = List(Unicode, config=True,
687 kernel_cmd = List(Unicode, config=True,
688 help="""The Popen Command to launch the kernel.
688 help="""The Popen Command to launch the kernel.
689 Override this if you have a custom
689 Override this if you have a custom
690 """
690 """
691 )
691 )
692 def _kernel_cmd_changed(self, name, old, new):
692 def _kernel_cmd_changed(self, name, old, new):
693 print 'kernel cmd changed', new
693 print 'kernel cmd changed', new
694 self.ipython_kernel = False
694 self.ipython_kernel = False
695
695
696 ipython_kernel = Bool(True)
696 ipython_kernel = Bool(True)
697
697
698
698
699 # The addresses for the communication channels.
699 # The addresses for the communication channels.
700 connection_file = Unicode('')
700 connection_file = Unicode('')
701
701
702 transport = CaselessStrEnum(['tcp', 'ipc'], default_value='tcp', config=True)
702 transport = CaselessStrEnum(['tcp', 'ipc'], default_value='tcp', config=True)
703
703
704 ip = Unicode(LOCALHOST, config=True,
704 ip = Unicode(LOCALHOST, config=True,
705 help="""Set the kernel\'s IP address [default localhost].
705 help="""Set the kernel\'s IP address [default localhost].
706 If the IP address is something other than localhost, then
706 If the IP address is something other than localhost, then
707 Consoles on other machines will be able to connect
707 Consoles on other machines will be able to connect
708 to the Kernel, so be careful!"""
708 to the Kernel, so be careful!"""
709 )
709 )
710 def _ip_default(self):
710 def _ip_default(self):
711 if self.transport == 'ipc':
711 if self.transport == 'ipc':
712 if self.connection_file:
712 if self.connection_file:
713 return os.path.splitext(self.connection_file)[0] + '-ipc'
713 return os.path.splitext(self.connection_file)[0] + '-ipc'
714 else:
714 else:
715 return 'kernel-ipc'
715 return 'kernel-ipc'
716 else:
716 else:
717 return LOCALHOST
717 return LOCALHOST
718 def _ip_changed(self, name, old, new):
718 def _ip_changed(self, name, old, new):
719 if new == '*':
719 if new == '*':
720 self.ip = '0.0.0.0'
720 self.ip = '0.0.0.0'
721 shell_port = Integer(0)
721 shell_port = Integer(0)
722 iopub_port = Integer(0)
722 iopub_port = Integer(0)
723 stdin_port = Integer(0)
723 stdin_port = Integer(0)
724 hb_port = Integer(0)
724 hb_port = Integer(0)
725
725
726 # The classes to use for the various channels.
726 # The classes to use for the various channels.
727 shell_channel_class = Type(ShellChannel)
727 shell_channel_class = Type(ShellChannel)
728 iopub_channel_class = Type(IOPubChannel)
728 iopub_channel_class = Type(IOPubChannel)
729 stdin_channel_class = Type(StdInChannel)
729 stdin_channel_class = Type(StdInChannel)
730 hb_channel_class = Type(HBChannel)
730 hb_channel_class = Type(HBChannel)
731
731
732 # Protected traits.
732 # Protected traits.
733 _launch_args = Any
733 _launch_args = Any
734 _shell_channel = Any
734 _shell_channel = Any
735 _iopub_channel = Any
735 _iopub_channel = Any
736 _stdin_channel = Any
736 _stdin_channel = Any
737 _hb_channel = Any
737 _hb_channel = Any
738 _connection_file_written=Bool(False)
738 _connection_file_written=Bool(False)
739
739
740 def __del__(self):
740 def __del__(self):
741 self.cleanup_connection_file()
741 self.cleanup_connection_file()
742
742
743 #--------------------------------------------------------------------------
743 #--------------------------------------------------------------------------
744 # Channel management methods:
744 # Channel management methods:
745 #--------------------------------------------------------------------------
745 #--------------------------------------------------------------------------
746
746
747 def start_channels(self, shell=True, iopub=True, stdin=True, hb=True):
747 def start_channels(self, shell=True, iopub=True, stdin=True, hb=True):
748 """Starts the channels for this kernel.
748 """Starts the channels for this kernel.
749
749
750 This will create the channels if they do not exist and then start
750 This will create the channels if they do not exist and then start
751 them (their activity runs in a thread). If port numbers of 0 are
751 them (their activity runs in a thread). If port numbers of 0 are
752 being used (random ports) then you must first call
752 being used (random ports) then you must first call
753 :method:`start_kernel`. If the channels have been stopped and you
753 :method:`start_kernel`. If the channels have been stopped and you
754 call this, :class:`RuntimeError` will be raised.
754 call this, :class:`RuntimeError` will be raised.
755 """
755 """
756 if shell:
756 if shell:
757 self.shell_channel.start()
757 self.shell_channel.start()
758 if iopub:
758 if iopub:
759 self.iopub_channel.start()
759 self.iopub_channel.start()
760 if stdin:
760 if stdin:
761 self.stdin_channel.start()
761 self.stdin_channel.start()
762 self.shell_channel.allow_stdin = True
762 self.shell_channel.allow_stdin = True
763 else:
763 else:
764 self.shell_channel.allow_stdin = False
764 self.shell_channel.allow_stdin = False
765 if hb:
765 if hb:
766 self.hb_channel.start()
766 self.hb_channel.start()
767
767
768 def stop_channels(self):
768 def stop_channels(self):
769 """Stops all the running channels for this kernel.
769 """Stops all the running channels for this kernel.
770
770
771 This stops their event loops and joins their threads.
771 This stops their event loops and joins their threads.
772 """
772 """
773 if self.shell_channel.is_alive():
773 if self.shell_channel.is_alive():
774 self.shell_channel.stop()
774 self.shell_channel.stop()
775 if self.iopub_channel.is_alive():
775 if self.iopub_channel.is_alive():
776 self.iopub_channel.stop()
776 self.iopub_channel.stop()
777 if self.stdin_channel.is_alive():
777 if self.stdin_channel.is_alive():
778 self.stdin_channel.stop()
778 self.stdin_channel.stop()
779 if self.hb_channel.is_alive():
779 if self.hb_channel.is_alive():
780 self.hb_channel.stop()
780 self.hb_channel.stop()
781
781
782 @property
782 @property
783 def channels_running(self):
783 def channels_running(self):
784 """Are any of the channels created and running?"""
784 """Are any of the channels created and running?"""
785 return (self.shell_channel.is_alive() or self.iopub_channel.is_alive() or
785 return (self.shell_channel.is_alive() or self.iopub_channel.is_alive() or
786 self.stdin_channel.is_alive() or self.hb_channel.is_alive())
786 self.stdin_channel.is_alive() or self.hb_channel.is_alive())
787
787
788 def _make_url(self, port):
788 def _make_url(self, port):
789 """Make a zmq url with a port.
789 """Make a zmq url with a port.
790
790
791 There are two cases that this handles:
791 There are two cases that this handles:
792
792
793 * tcp: tcp://ip:port
793 * tcp: tcp://ip:port
794 * ipc: ipc://ip-port
794 * ipc: ipc://ip-port
795 """
795 """
796 if self.transport == 'tcp':
796 if self.transport == 'tcp':
797 return "tcp://%s:%i" % (self.ip, port)
797 return "tcp://%s:%i" % (self.ip, port)
798 else:
798 else:
799 return "%s://%s-%s" % (self.transport, self.ip, port)
799 return "%s://%s-%s" % (self.transport, self.ip, port)
800
800
801 @property
801 @property
802 def shell_channel(self):
802 def shell_channel(self):
803 """Get the shell channel object for this kernel."""
803 """Get the shell channel object for this kernel."""
804 if self._shell_channel is None:
804 if self._shell_channel is None:
805 self._shell_channel = self.shell_channel_class(
805 self._shell_channel = self.shell_channel_class(
806 self.context, self.session, self._make_url(self.shell_port)
806 self.context, self.session, self._make_url(self.shell_port)
807 )
807 )
808 return self._shell_channel
808 return self._shell_channel
809
809
810 @property
810 @property
811 def iopub_channel(self):
811 def iopub_channel(self):
812 """Get the iopub channel object for this kernel."""
812 """Get the iopub channel object for this kernel."""
813 if self._iopub_channel is None:
813 if self._iopub_channel is None:
814 self._iopub_channel = self.iopub_channel_class(
814 self._iopub_channel = self.iopub_channel_class(
815 self.context, self.session, self._make_url(self.iopub_port)
815 self.context, self.session, self._make_url(self.iopub_port)
816 )
816 )
817 return self._iopub_channel
817 return self._iopub_channel
818
818
819 @property
819 @property
820 def stdin_channel(self):
820 def stdin_channel(self):
821 """Get the stdin channel object for this kernel."""
821 """Get the stdin channel object for this kernel."""
822 if self._stdin_channel is None:
822 if self._stdin_channel is None:
823 self._stdin_channel = self.stdin_channel_class(
823 self._stdin_channel = self.stdin_channel_class(
824 self.context, self.session, self._make_url(self.stdin_port)
824 self.context, self.session, self._make_url(self.stdin_port)
825 )
825 )
826 return self._stdin_channel
826 return self._stdin_channel
827
827
828 @property
828 @property
829 def hb_channel(self):
829 def hb_channel(self):
830 """Get the hb channel object for this kernel."""
830 """Get the hb channel object for this kernel."""
831 if self._hb_channel is None:
831 if self._hb_channel is None:
832 self._hb_channel = self.hb_channel_class(
832 self._hb_channel = self.hb_channel_class(
833 self.context, self.session, self._make_url(self.hb_port)
833 self.context, self.session, self._make_url(self.hb_port)
834 )
834 )
835 return self._hb_channel
835 return self._hb_channel
836
836
837 #--------------------------------------------------------------------------
837 #--------------------------------------------------------------------------
838 # Connection and ipc file management
838 # Connection and ipc file management
839 #--------------------------------------------------------------------------
839 #--------------------------------------------------------------------------
840
840
841 def cleanup_connection_file(self):
841 def cleanup_connection_file(self):
842 """Cleanup connection file *if we wrote it*
842 """Cleanup connection file *if we wrote it*
843
843
844 Will not raise if the connection file was already removed somehow.
844 Will not raise if the connection file was already removed somehow.
845 """
845 """
846 if self._connection_file_written:
846 if self._connection_file_written:
847 # cleanup connection files on full shutdown of kernel we started
847 # cleanup connection files on full shutdown of kernel we started
848 self._connection_file_written = False
848 self._connection_file_written = False
849 try:
849 try:
850 os.remove(self.connection_file)
850 os.remove(self.connection_file)
851 except (IOError, OSError):
851 except (IOError, OSError):
852 pass
852 pass
853
853
854 def cleanup_ipc_files(self):
854 def cleanup_ipc_files(self):
855 """Cleanup ipc files if we wrote them."""
855 """Cleanup ipc files if we wrote them."""
856 if self.transport != 'ipc':
856 if self.transport != 'ipc':
857 return
857 return
858 for port in (self.shell_port, self.iopub_port, self.stdin_port, self.hb_port):
858 for port in (self.shell_port, self.iopub_port, self.stdin_port, self.hb_port):
859 ipcfile = "%s-%i" % (self.ip, port)
859 ipcfile = "%s-%i" % (self.ip, port)
860 try:
860 try:
861 os.remove(ipcfile)
861 os.remove(ipcfile)
862 except (IOError, OSError):
862 except (IOError, OSError):
863 pass
863 pass
864
864
865 def load_connection_file(self):
865 def load_connection_file(self):
866 """Load connection info from JSON dict in self.connection_file."""
866 """Load connection info from JSON dict in self.connection_file."""
867 with open(self.connection_file) as f:
867 with open(self.connection_file) as f:
868 cfg = json.loads(f.read())
868 cfg = json.loads(f.read())
869
869
870 from pprint import pprint
870 from pprint import pprint
871 pprint(cfg)
871 pprint(cfg)
872 self.transport = cfg.get('transport', 'tcp')
872 self.transport = cfg.get('transport', 'tcp')
873 self.ip = cfg['ip']
873 self.ip = cfg['ip']
874 self.shell_port = cfg['shell_port']
874 self.shell_port = cfg['shell_port']
875 self.stdin_port = cfg['stdin_port']
875 self.stdin_port = cfg['stdin_port']
876 self.iopub_port = cfg['iopub_port']
876 self.iopub_port = cfg['iopub_port']
877 self.hb_port = cfg['hb_port']
877 self.hb_port = cfg['hb_port']
878 self.session.key = str_to_bytes(cfg['key'])
878 self.session.key = str_to_bytes(cfg['key'])
879
879
880 def write_connection_file(self):
880 def write_connection_file(self):
881 """Write connection info to JSON dict in self.connection_file."""
881 """Write connection info to JSON dict in self.connection_file."""
882 if self._connection_file_written:
882 if self._connection_file_written:
883 return
883 return
884 self.connection_file,cfg = write_connection_file(self.connection_file,
884 self.connection_file,cfg = write_connection_file(self.connection_file,
885 transport=self.transport, ip=self.ip, key=self.session.key,
885 transport=self.transport, ip=self.ip, key=self.session.key,
886 stdin_port=self.stdin_port, iopub_port=self.iopub_port,
886 stdin_port=self.stdin_port, iopub_port=self.iopub_port,
887 shell_port=self.shell_port, hb_port=self.hb_port)
887 shell_port=self.shell_port, hb_port=self.hb_port)
888 # write_connection_file also sets default ports:
888 # write_connection_file also sets default ports:
889 self.shell_port = cfg['shell_port']
889 self.shell_port = cfg['shell_port']
890 self.stdin_port = cfg['stdin_port']
890 self.stdin_port = cfg['stdin_port']
891 self.iopub_port = cfg['iopub_port']
891 self.iopub_port = cfg['iopub_port']
892 self.hb_port = cfg['hb_port']
892 self.hb_port = cfg['hb_port']
893
893
894 self._connection_file_written = True
894 self._connection_file_written = True
895
895
896 #--------------------------------------------------------------------------
896 #--------------------------------------------------------------------------
897 # Kernel management
897 # Kernel management
898 #--------------------------------------------------------------------------
898 #--------------------------------------------------------------------------
899
899
900 def format_kernel_cmd(self, **kw):
900 def format_kernel_cmd(self, **kw):
901 """format templated args (e.g. {connection_file})"""
901 """format templated args (e.g. {connection_file})"""
902 if self.kernel_cmd:
902 if self.kernel_cmd:
903 cmd = self.kernel_cmd
903 cmd = self.kernel_cmd
904 else:
904 else:
905 cmd = make_ipkernel_cmd(
905 cmd = make_ipkernel_cmd(
906 'from IPython.zmq.kernelapp import main; main()',
906 'from IPython.zmq.kernelapp import main; main()',
907 **kw
907 **kw
908 )
908 )
909 ns = dict(connection_file=self.connection_file)
909 ns = dict(connection_file=self.connection_file)
910 ns.update(self._launch_args)
910 ns.update(self._launch_args)
911 return [ c.format(**ns) for c in cmd ]
911 return [ c.format(**ns) for c in cmd ]
912
912
913 def _launch_kernel(self, kernel_cmd, **kw):
913 def _launch_kernel(self, kernel_cmd, **kw):
914 """actually launch the kernel
914 """actually launch the kernel
915
915
916 override in a subclass to launch kernel subprocesses differently
916 override in a subclass to launch kernel subprocesses differently
917 """
917 """
918 return launch_kernel(kernel_cmd, **kw)
918 return launch_kernel(kernel_cmd, **kw)
919
919
920 def start_kernel(self, **kw):
920 def start_kernel(self, **kw):
921 """Starts a kernel on this host in a separate process.
921 """Starts a kernel on this host in a separate process.
922
922
923 If random ports (port=0) are being used, this method must be called
923 If random ports (port=0) are being used, this method must be called
924 before the channels are created.
924 before the channels are created.
925
925
926 Parameters:
926 Parameters:
927 -----------
927 -----------
928 **kw : optional
928 **kw : optional
929 keyword arguments that are passed down to build the kernel_cmd
929 keyword arguments that are passed down to build the kernel_cmd
930 and launching the kernel (e.g. Popen kwargs).
930 and launching the kernel (e.g. Popen kwargs).
931 """
931 """
932 if self.transport == 'tcp' and self.ip not in LOCAL_IPS:
932 if self.transport == 'tcp' and self.ip not in LOCAL_IPS:
933 raise RuntimeError("Can only launch a kernel on a local interface. "
933 raise RuntimeError("Can only launch a kernel on a local interface. "
934 "Make sure that the '*_address' attributes are "
934 "Make sure that the '*_address' attributes are "
935 "configured properly. "
935 "configured properly. "
936 "Currently valid addresses are: %s"%LOCAL_IPS
936 "Currently valid addresses are: %s"%LOCAL_IPS
937 )
937 )
938
938
939 # write connection file / get default ports
939 # write connection file / get default ports
940 self.write_connection_file()
940 self.write_connection_file()
941
941
942 # save kwargs for use in restart
942 # save kwargs for use in restart
943 self._launch_args = kw.copy()
943 self._launch_args = kw.copy()
944 # build the Popen cmd
944 # build the Popen cmd
945 kernel_cmd = self.format_kernel_cmd(**kw)
945 kernel_cmd = self.format_kernel_cmd(**kw)
946 # launch the kernel subprocess
946 # launch the kernel subprocess
947 self.kernel = self._launch_kernel(kernel_cmd,
947 self.kernel = self._launch_kernel(kernel_cmd,
948 ipython_kernel=self.ipython_kernel,
948 ipython_kernel=self.ipython_kernel,
949 **kw)
949 **kw)
950
950
951 def shutdown_kernel(self, now=False, restart=False):
951 def shutdown_kernel(self, now=False, restart=False):
952 """Attempts to the stop the kernel process cleanly.
952 """Attempts to the stop the kernel process cleanly.
953
953
954 This attempts to shutdown the kernels cleanly by:
954 This attempts to shutdown the kernels cleanly by:
955
955
956 1. Sending it a shutdown message over the shell channel.
956 1. Sending it a shutdown message over the shell channel.
957 2. If that fails, the kernel is shutdown forcibly by sending it
957 2. If that fails, the kernel is shutdown forcibly by sending it
958 a signal.
958 a signal.
959
959
960 Parameters:
960 Parameters:
961 -----------
961 -----------
962 now : bool
962 now : bool
963 Should the kernel be forcible killed *now*. This skips the
963 Should the kernel be forcible killed *now*. This skips the
964 first, nice shutdown attempt.
964 first, nice shutdown attempt.
965 restart: bool
965 restart: bool
966 Will this kernel be restarted after it is shutdown. When this
966 Will this kernel be restarted after it is shutdown. When this
967 is True, connection files will not be cleaned up.
967 is True, connection files will not be cleaned up.
968 """
968 """
969 # FIXME: Shutdown does not work on Windows due to ZMQ errors!
969 # FIXME: Shutdown does not work on Windows due to ZMQ errors!
970 if sys.platform == 'win32':
970 if sys.platform == 'win32':
971 self._kill_kernel()
971 self._kill_kernel()
972 return
972 return
973
973
974 # Pause the heart beat channel if it exists.
974 # Pause the heart beat channel if it exists.
975 if self._hb_channel is not None:
975 if self._hb_channel is not None:
976 self._hb_channel.pause()
976 self._hb_channel.pause()
977
977
978 if now:
978 if now:
979 if self.has_kernel:
979 if self.has_kernel:
980 self._kill_kernel()
980 self._kill_kernel()
981 else:
981 else:
982 # Don't send any additional kernel kill messages immediately, to give
982 # Don't send any additional kernel kill messages immediately, to give
983 # the kernel a chance to properly execute shutdown actions. Wait for at
983 # the kernel a chance to properly execute shutdown actions. Wait for at
984 # most 1s, checking every 0.1s.
984 # most 1s, checking every 0.1s.
985 self.shell_channel.shutdown(restart=restart)
985 self.shell_channel.shutdown(restart=restart)
986 for i in range(10):
986 for i in range(10):
987 if self.is_alive:
987 if self.is_alive:
988 time.sleep(0.1)
988 time.sleep(0.1)
989 else:
989 else:
990 break
990 break
991 else:
991 else:
992 # OK, we've waited long enough.
992 # OK, we've waited long enough.
993 if self.has_kernel:
993 if self.has_kernel:
994 self._kill_kernel()
994 self._kill_kernel()
995
995
996 if not restart:
996 if not restart:
997 self.cleanup_connection_file()
997 self.cleanup_connection_file()
998 self.cleanup_ipc_files()
998 self.cleanup_ipc_files()
999 else:
999 else:
1000 self.cleanup_ipc_files()
1000 self.cleanup_ipc_files()
1001
1001
1002 def restart_kernel(self, now=False, **kw):
1002 def restart_kernel(self, now=False, **kw):
1003 """Restarts a kernel with the arguments that were used to launch it.
1003 """Restarts a kernel with the arguments that were used to launch it.
1004
1004
1005 If the old kernel was launched with random ports, the same ports will be
1005 If the old kernel was launched with random ports, the same ports will be
1006 used for the new kernel. The same connection file is used again.
1006 used for the new kernel. The same connection file is used again.
1007
1007
1008 Parameters
1008 Parameters
1009 ----------
1009 ----------
1010 now : bool, optional
1010 now : bool, optional
1011 If True, the kernel is forcefully restarted *immediately*, without
1011 If True, the kernel is forcefully restarted *immediately*, without
1012 having a chance to do any cleanup action. Otherwise the kernel is
1012 having a chance to do any cleanup action. Otherwise the kernel is
1013 given 1s to clean up before a forceful restart is issued.
1013 given 1s to clean up before a forceful restart is issued.
1014
1014
1015 In all cases the kernel is restarted, the only difference is whether
1015 In all cases the kernel is restarted, the only difference is whether
1016 it is given a chance to perform a clean shutdown or not.
1016 it is given a chance to perform a clean shutdown or not.
1017
1017
1018 **kw : optional
1018 **kw : optional
1019 Any options specified here will overwrite those used to launch the
1019 Any options specified here will overwrite those used to launch the
1020 kernel.
1020 kernel.
1021 """
1021 """
1022 if self._launch_args is None:
1022 if self._launch_args is None:
1023 raise RuntimeError("Cannot restart the kernel. "
1023 raise RuntimeError("Cannot restart the kernel. "
1024 "No previous call to 'start_kernel'.")
1024 "No previous call to 'start_kernel'.")
1025 else:
1025 else:
1026 # Stop currently running kernel.
1026 # Stop currently running kernel.
1027 self.shutdown_kernel(now=now, restart=True)
1027 self.shutdown_kernel(now=now, restart=True)
1028
1028
1029 # Start new kernel.
1029 # Start new kernel.
1030 self._launch_args.update(kw)
1030 self._launch_args.update(kw)
1031 self.start_kernel(**self._launch_args)
1031 self.start_kernel(**self._launch_args)
1032
1032
1033 # FIXME: Messages get dropped in Windows due to probable ZMQ bug
1033 # FIXME: Messages get dropped in Windows due to probable ZMQ bug
1034 # unless there is some delay here.
1034 # unless there is some delay here.
1035 if sys.platform == 'win32':
1035 if sys.platform == 'win32':
1036 time.sleep(0.2)
1036 time.sleep(0.2)
1037
1037
1038 @property
1038 @property
1039 def has_kernel(self):
1039 def has_kernel(self):
1040 """Has a kernel been started that we are managing."""
1040 """Has a kernel been started that we are managing."""
1041 return self.kernel is not None
1041 return self.kernel is not None
1042
1042
1043 def _kill_kernel(self):
1043 def _kill_kernel(self):
1044 """Kill the running kernel.
1044 """Kill the running kernel.
1045
1045
1046 This is a private method, callers should use shutdown_kernel(now=True).
1046 This is a private method, callers should use shutdown_kernel(now=True).
1047 """
1047 """
1048 if self.has_kernel:
1048 if self.has_kernel:
1049 # Pause the heart beat channel if it exists.
1049 # Pause the heart beat channel if it exists.
1050 if self._hb_channel is not None:
1050 if self._hb_channel is not None:
1051 self._hb_channel.pause()
1051 self._hb_channel.pause()
1052
1052
1053 # Signal the kernel to terminate (sends SIGKILL on Unix and calls
1053 # Signal the kernel to terminate (sends SIGKILL on Unix and calls
1054 # TerminateProcess() on Win32).
1054 # TerminateProcess() on Win32).
1055 try:
1055 try:
1056 self.kernel.kill()
1056 self.kernel.kill()
1057 except OSError as e:
1057 except OSError as e:
1058 # In Windows, we will get an Access Denied error if the process
1058 # In Windows, we will get an Access Denied error if the process
1059 # has already terminated. Ignore it.
1059 # has already terminated. Ignore it.
1060 if sys.platform == 'win32':
1060 if sys.platform == 'win32':
1061 if e.winerror != 5:
1061 if e.winerror != 5:
1062 raise
1062 raise
1063 # On Unix, we may get an ESRCH error if the process has already
1063 # On Unix, we may get an ESRCH error if the process has already
1064 # terminated. Ignore it.
1064 # terminated. Ignore it.
1065 else:
1065 else:
1066 from errno import ESRCH
1066 from errno import ESRCH
1067 if e.errno != ESRCH:
1067 if e.errno != ESRCH:
1068 raise
1068 raise
1069
1069
1070 # Block until the kernel terminates.
1070 # Block until the kernel terminates.
1071 self.kernel.wait()
1071 self.kernel.wait()
1072 self.kernel = None
1072 self.kernel = None
1073 else:
1073 else:
1074 raise RuntimeError("Cannot kill kernel. No kernel is running!")
1074 raise RuntimeError("Cannot kill kernel. No kernel is running!")
1075
1075
1076 def interrupt_kernel(self):
1076 def interrupt_kernel(self):
1077 """Interrupts the kernel by sending it a signal.
1077 """Interrupts the kernel by sending it a signal.
1078
1078
1079 Unlike ``signal_kernel``, this operation is well supported on all
1079 Unlike ``signal_kernel``, this operation is well supported on all
1080 platforms.
1080 platforms.
1081 """
1081 """
1082 if self.has_kernel:
1082 if self.has_kernel:
1083 if sys.platform == 'win32':
1083 if sys.platform == 'win32':
1084 from parentpoller import ParentPollerWindows as Poller
1084 from parentpoller import ParentPollerWindows as Poller
1085 Poller.send_interrupt(self.kernel.win32_interrupt_event)
1085 Poller.send_interrupt(self.kernel.win32_interrupt_event)
1086 else:
1086 else:
1087 self.kernel.send_signal(signal.SIGINT)
1087 self.kernel.send_signal(signal.SIGINT)
1088 else:
1088 else:
1089 raise RuntimeError("Cannot interrupt kernel. No kernel is running!")
1089 raise RuntimeError("Cannot interrupt kernel. No kernel is running!")
1090
1090
1091 def signal_kernel(self, signum):
1091 def signal_kernel(self, signum):
1092 """Sends a signal to the kernel.
1092 """Sends a signal to the kernel.
1093
1093
1094 Note that since only SIGTERM is supported on Windows, this function is
1094 Note that since only SIGTERM is supported on Windows, this function is
1095 only useful on Unix systems.
1095 only useful on Unix systems.
1096 """
1096 """
1097 if self.has_kernel:
1097 if self.has_kernel:
1098 self.kernel.send_signal(signum)
1098 self.kernel.send_signal(signum)
1099 else:
1099 else:
1100 raise RuntimeError("Cannot signal kernel. No kernel is running!")
1100 raise RuntimeError("Cannot signal kernel. No kernel is running!")
1101
1101
1102 @property
1102 @property
1103 def is_alive(self):
1103 def is_alive(self):
1104 """Is the kernel process still running?"""
1104 """Is the kernel process still running?"""
1105 if self.has_kernel:
1105 if self.has_kernel:
1106 if self.kernel.poll() is None:
1106 if self.kernel.poll() is None:
1107 return True
1107 return True
1108 else:
1108 else:
1109 return False
1109 return False
1110 elif self._hb_channel is not None:
1110 elif self._hb_channel is not None:
1111 # We didn't start the kernel with this KernelManager so we
1111 # We didn't start the kernel with this KernelManager so we
1112 # use the heartbeat.
1112 # use the heartbeat.
1113 return self._hb_channel.is_beating()
1113 return self._hb_channel.is_beating()
1114 else:
1114 else:
1115 # no heartbeat and not local, we can't tell if it's running,
1115 # no heartbeat and not local, we can't tell if it's running,
1116 # so naively return True
1116 # so naively return True
1117 return True
1117 return True
1118
1118
1119
1119
1120 #-----------------------------------------------------------------------------
1120 #-----------------------------------------------------------------------------
1121 # ABC Registration
1121 # ABC Registration
1122 #-----------------------------------------------------------------------------
1122 #-----------------------------------------------------------------------------
1123
1123
1124 ShellChannelABC.register(ShellChannel)
1124 ShellChannelABC.register(ShellChannel)
1125 IOPubChannelABC.register(IOPubChannel)
1125 IOPubChannelABC.register(IOPubChannel)
1126 HBChannelABC.register(HBChannel)
1126 HBChannelABC.register(HBChannel)
1127 StdInChannelABC.register(StdInChannel)
1127 StdInChannelABC.register(StdInChannel)
1128 KernelManagerABC.register(KernelManager)
1128 KernelManagerABC.register(KernelManager)
1129
1129
@@ -1,226 +1,226 b''
1 """Abstract base classes for kernel manager and channels."""
1 """Abstract base classes for kernel manager and channels."""
2
2
3 #-----------------------------------------------------------------------------
3 #-----------------------------------------------------------------------------
4 # Copyright (C) 2013 The IPython Development Team
4 # Copyright (C) 2013 The IPython Development Team
5 #
5 #
6 # Distributed under the terms of the BSD License. The full license is in
6 # Distributed under the terms of the BSD License. The full license is in
7 # the file COPYING, distributed as part of this software.
7 # the file COPYING, distributed as part of this software.
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9
9
10 #-----------------------------------------------------------------------------
10 #-----------------------------------------------------------------------------
11 # Imports
11 # Imports
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13
13
14 # Standard library imports.
14 # Standard library imports.
15 import abc
15 import abc
16
16
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18 # Channels
18 # Channels
19 #-----------------------------------------------------------------------------
19 #-----------------------------------------------------------------------------
20
20
21
21
22 class ChannelABC(object):
22 class ChannelABC(object):
23 """A base class for all channel ABCs."""
23 """A base class for all channel ABCs."""
24
24
25 __metaclass__ = abc.ABCMeta
25 __metaclass__ = abc.ABCMeta
26
26
27 @abc.abstractmethod
27 @abc.abstractmethod
28 def start(self):
28 def start(self):
29 pass
29 pass
30
30
31 @abc.abstractmethod
31 @abc.abstractmethod
32 def stop(self):
32 def stop(self):
33 pass
33 pass
34
34
35 @abc.abstractmethod
35 @abc.abstractmethod
36 def is_alive(self):
36 def is_alive(self):
37 pass
37 pass
38
38
39
39
40 class ShellChannelABC(ChannelABC):
40 class ShellChannelABC(ChannelABC):
41 """ShellChannel ABC.
41 """ShellChannel ABC.
42
42
43 The docstrings for this class can be found in the base implementation:
43 The docstrings for this class can be found in the base implementation:
44
44
45 `IPython.zmq.kernelmanager.ShellChannel`
45 `IPython.kernel.kernelmanager.ShellChannel`
46 """
46 """
47
47
48 @abc.abstractproperty
48 @abc.abstractproperty
49 def allow_stdin(self):
49 def allow_stdin(self):
50 pass
50 pass
51
51
52 @abc.abstractmethod
52 @abc.abstractmethod
53 def execute(self, code, silent=False, store_history=True,
53 def execute(self, code, silent=False, store_history=True,
54 user_variables=None, user_expressions=None, allow_stdin=None):
54 user_variables=None, user_expressions=None, allow_stdin=None):
55 pass
55 pass
56
56
57 @abc.abstractmethod
57 @abc.abstractmethod
58 def complete(self, text, line, cursor_pos, block=None):
58 def complete(self, text, line, cursor_pos, block=None):
59 pass
59 pass
60
60
61 @abc.abstractmethod
61 @abc.abstractmethod
62 def object_info(self, oname, detail_level=0):
62 def object_info(self, oname, detail_level=0):
63 pass
63 pass
64
64
65 @abc.abstractmethod
65 @abc.abstractmethod
66 def history(self, raw=True, output=False, hist_access_type='range', **kwargs):
66 def history(self, raw=True, output=False, hist_access_type='range', **kwargs):
67 pass
67 pass
68
68
69 @abc.abstractmethod
69 @abc.abstractmethod
70 def kernel_info(self):
70 def kernel_info(self):
71 pass
71 pass
72
72
73 @abc.abstractmethod
73 @abc.abstractmethod
74 def shutdown(self, restart=False):
74 def shutdown(self, restart=False):
75 pass
75 pass
76
76
77
77
78 class IOPubChannelABC(ChannelABC):
78 class IOPubChannelABC(ChannelABC):
79 """IOPubChannel ABC.
79 """IOPubChannel ABC.
80
80
81 The docstrings for this class can be found in the base implementation:
81 The docstrings for this class can be found in the base implementation:
82
82
83 `IPython.zmq.kernelmanager.IOPubChannel`
83 `IPython.kernel.kernelmanager.IOPubChannel`
84 """
84 """
85
85
86 @abc.abstractmethod
86 @abc.abstractmethod
87 def flush(self, timeout=1.0):
87 def flush(self, timeout=1.0):
88 pass
88 pass
89
89
90
90
91 class StdInChannelABC(ChannelABC):
91 class StdInChannelABC(ChannelABC):
92 """StdInChannel ABC.
92 """StdInChannel ABC.
93
93
94 The docstrings for this class can be found in the base implementation:
94 The docstrings for this class can be found in the base implementation:
95
95
96 `IPython.zmq.kernelmanager.StdInChannel`
96 `IPython.kernel.kernelmanager.StdInChannel`
97 """
97 """
98
98
99 @abc.abstractmethod
99 @abc.abstractmethod
100 def input(self, string):
100 def input(self, string):
101 pass
101 pass
102
102
103
103
104 class HBChannelABC(ChannelABC):
104 class HBChannelABC(ChannelABC):
105 """HBChannel ABC.
105 """HBChannel ABC.
106
106
107 The docstrings for this class can be found in the base implementation:
107 The docstrings for this class can be found in the base implementation:
108
108
109 `IPython.zmq.kernelmanager.HBChannel`
109 `IPython.kernel.kernelmanager.HBChannel`
110 """
110 """
111
111
112 @abc.abstractproperty
112 @abc.abstractproperty
113 def time_to_dead(self):
113 def time_to_dead(self):
114 pass
114 pass
115
115
116 @abc.abstractmethod
116 @abc.abstractmethod
117 def pause(self):
117 def pause(self):
118 pass
118 pass
119
119
120 @abc.abstractmethod
120 @abc.abstractmethod
121 def unpause(self):
121 def unpause(self):
122 pass
122 pass
123
123
124 @abc.abstractmethod
124 @abc.abstractmethod
125 def is_beating(self):
125 def is_beating(self):
126 pass
126 pass
127
127
128
128
129 #-----------------------------------------------------------------------------
129 #-----------------------------------------------------------------------------
130 # Main kernel manager class
130 # Main kernel manager class
131 #-----------------------------------------------------------------------------
131 #-----------------------------------------------------------------------------
132
132
133 class KernelManagerABC(object):
133 class KernelManagerABC(object):
134 """KernelManager ABC.
134 """KernelManager ABC.
135
135
136 The docstrings for this class can be found in the base implementation:
136 The docstrings for this class can be found in the base implementation:
137
137
138 `IPython.zmq.kernelmanager.KernelManager`
138 `IPython.kernel.kernelmanager.KernelManager`
139 """
139 """
140
140
141 __metaclass__ = abc.ABCMeta
141 __metaclass__ = abc.ABCMeta
142
142
143 @abc.abstractproperty
143 @abc.abstractproperty
144 def kernel(self):
144 def kernel(self):
145 pass
145 pass
146
146
147 @abc.abstractproperty
147 @abc.abstractproperty
148 def shell_channel_class(self):
148 def shell_channel_class(self):
149 pass
149 pass
150
150
151 @abc.abstractproperty
151 @abc.abstractproperty
152 def iopub_channel_class(self):
152 def iopub_channel_class(self):
153 pass
153 pass
154
154
155 @abc.abstractproperty
155 @abc.abstractproperty
156 def hb_channel_class(self):
156 def hb_channel_class(self):
157 pass
157 pass
158
158
159 @abc.abstractproperty
159 @abc.abstractproperty
160 def stdin_channel_class(self):
160 def stdin_channel_class(self):
161 pass
161 pass
162
162
163 #--------------------------------------------------------------------------
163 #--------------------------------------------------------------------------
164 # Channel management methods
164 # Channel management methods
165 #--------------------------------------------------------------------------
165 #--------------------------------------------------------------------------
166
166
167 @abc.abstractmethod
167 @abc.abstractmethod
168 def start_channels(self, shell=True, iopub=True, stdin=True, hb=True):
168 def start_channels(self, shell=True, iopub=True, stdin=True, hb=True):
169 pass
169 pass
170
170
171 @abc.abstractmethod
171 @abc.abstractmethod
172 def stop_channels(self):
172 def stop_channels(self):
173 pass
173 pass
174
174
175 @abc.abstractproperty
175 @abc.abstractproperty
176 def channels_running(self):
176 def channels_running(self):
177 pass
177 pass
178
178
179 @abc.abstractproperty
179 @abc.abstractproperty
180 def shell_channel(self):
180 def shell_channel(self):
181 pass
181 pass
182
182
183 @abc.abstractproperty
183 @abc.abstractproperty
184 def iopub_channel(self):
184 def iopub_channel(self):
185 pass
185 pass
186
186
187 @abc.abstractproperty
187 @abc.abstractproperty
188 def stdin_channel(self):
188 def stdin_channel(self):
189 pass
189 pass
190
190
191 @abc.abstractproperty
191 @abc.abstractproperty
192 def hb_channel(self):
192 def hb_channel(self):
193 pass
193 pass
194
194
195 #--------------------------------------------------------------------------
195 #--------------------------------------------------------------------------
196 # Kernel management
196 # Kernel management
197 #--------------------------------------------------------------------------
197 #--------------------------------------------------------------------------
198
198
199 @abc.abstractmethod
199 @abc.abstractmethod
200 def start_kernel(self, **kw):
200 def start_kernel(self, **kw):
201 pass
201 pass
202
202
203 @abc.abstractmethod
203 @abc.abstractmethod
204 def shutdown_kernel(self, now=False, restart=False):
204 def shutdown_kernel(self, now=False, restart=False):
205 pass
205 pass
206
206
207 @abc.abstractmethod
207 @abc.abstractmethod
208 def restart_kernel(self, now=False, **kw):
208 def restart_kernel(self, now=False, **kw):
209 pass
209 pass
210
210
211 @abc.abstractproperty
211 @abc.abstractproperty
212 def has_kernel(self):
212 def has_kernel(self):
213 pass
213 pass
214
214
215 @abc.abstractmethod
215 @abc.abstractmethod
216 def interrupt_kernel(self):
216 def interrupt_kernel(self):
217 pass
217 pass
218
218
219 @abc.abstractmethod
219 @abc.abstractmethod
220 def signal_kernel(self, signum):
220 def signal_kernel(self, signum):
221 pass
221 pass
222
222
223 @abc.abstractproperty
223 @abc.abstractproperty
224 def is_alive(self):
224 def is_alive(self):
225 pass
225 pass
226
226
1 NO CONTENT: file renamed from IPython/zmq/tests/test_message_spec.py to IPython/kernel/tests/test_message_spec.py
NO CONTENT: file renamed from IPython/zmq/tests/test_message_spec.py to IPython/kernel/tests/test_message_spec.py
@@ -1,51 +1,51 b''
1 #-----------------------------------------------------------------------------
1 #-----------------------------------------------------------------------------
2 # Copyright (C) 2010 The IPython Development Team
2 # Copyright (C) 2010 The IPython Development Team
3 #
3 #
4 # Distributed under the terms of the BSD License. The full license is in
4 # Distributed under the terms of the BSD License. The full license is in
5 # the file COPYING.txt, distributed as part of this software.
5 # the file COPYING.txt, distributed as part of this software.
6 #-----------------------------------------------------------------------------
6 #-----------------------------------------------------------------------------
7
7
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9 # Verify zmq version dependency >= 2.1.11
9 # Verify zmq version dependency >= 2.1.11
10 #-----------------------------------------------------------------------------
10 #-----------------------------------------------------------------------------
11
11
12 import warnings
12 import warnings
13 from IPython.utils.version import check_version
13 from IPython.utils.version import check_version
14
14
15
15
16 def patch_pyzmq():
16 def patch_pyzmq():
17 """backport a few patches from newer pyzmq
17 """backport a few patches from newer pyzmq
18
18
19 These can be removed as we bump our minimum pyzmq version
19 These can be removed as we bump our minimum pyzmq version
20 """
20 """
21
21
22 import zmq
22 import zmq
23
23
24 # fallback on stdlib json if jsonlib is selected, because jsonlib breaks things.
24 # fallback on stdlib json if jsonlib is selected, because jsonlib breaks things.
25 # jsonlib support is removed from pyzmq >= 2.2.0
25 # jsonlib support is removed from pyzmq >= 2.2.0
26
26
27 from zmq.utils import jsonapi
27 from zmq.utils import jsonapi
28 if jsonapi.jsonmod.__name__ == 'jsonlib':
28 if jsonapi.jsonmod.__name__ == 'jsonlib':
29 import json
29 import json
30 jsonapi.jsonmod = json
30 jsonapi.jsonmod = json
31
31
32
32
33 def check_for_zmq(minimum_version, module='IPython.zmq'):
33 def check_for_zmq(minimum_version, module='IPython.zmq'):
34 try:
34 try:
35 import zmq
35 import zmq
36 except ImportError:
36 except ImportError:
37 raise ImportError("%s requires pyzmq >= %s"%(module, minimum_version))
37 raise ImportError("%s requires pyzmq >= %s"%(module, minimum_version))
38
38
39 pyzmq_version = zmq.__version__
39 pyzmq_version = zmq.__version__
40
40
41 if not check_version(pyzmq_version, minimum_version):
41 if not check_version(pyzmq_version, minimum_version):
42 raise ImportError("%s requires pyzmq >= %s, but you have %s"%(
42 raise ImportError("%s requires pyzmq >= %s, but you have %s"%(
43 module, minimum_version, pyzmq_version))
43 module, minimum_version, pyzmq_version))
44
44
45 check_for_zmq('2.1.11')
45 check_for_zmq('2.1.11')
46 patch_pyzmq()
46 patch_pyzmq()
47
47
48 from .blockingkernelmanager import BlockingKernelManager
48 from IPython.kernel.blockingkernelmanager import BlockingKernelManager
49 from .kernelmanager import *
49 from IPython.kernel.kernelmanager import *
50 from .session import Session
50 from .session import Session
51
51
@@ -1,193 +1,193 b''
1 """test IPython.embed_kernel()"""
1 """test IPython.embed_kernel()"""
2
2
3 #-------------------------------------------------------------------------------
3 #-------------------------------------------------------------------------------
4 # Copyright (C) 2012 The IPython Development Team
4 # Copyright (C) 2012 The IPython Development Team
5 #
5 #
6 # Distributed under the terms of the BSD License. The full license is in
6 # Distributed under the terms of the BSD License. The full license is in
7 # the file COPYING, distributed as part of this software.
7 # the file COPYING, distributed as part of this software.
8 #-------------------------------------------------------------------------------
8 #-------------------------------------------------------------------------------
9
9
10 #-------------------------------------------------------------------------------
10 #-------------------------------------------------------------------------------
11 # Imports
11 # Imports
12 #-------------------------------------------------------------------------------
12 #-------------------------------------------------------------------------------
13
13
14 import os
14 import os
15 import shutil
15 import shutil
16 import sys
16 import sys
17 import tempfile
17 import tempfile
18 import time
18 import time
19
19
20 from contextlib import contextmanager
20 from contextlib import contextmanager
21 from subprocess import Popen, PIPE
21 from subprocess import Popen, PIPE
22
22
23 import nose.tools as nt
23 import nose.tools as nt
24
24
25 from IPython.zmq.blockingkernelmanager import BlockingKernelManager
25 from IPython.kernel.blockingkernelmanager import BlockingKernelManager
26 from IPython.utils import path, py3compat
26 from IPython.utils import path, py3compat
27
27
28 #-------------------------------------------------------------------------------
28 #-------------------------------------------------------------------------------
29 # Tests
29 # Tests
30 #-------------------------------------------------------------------------------
30 #-------------------------------------------------------------------------------
31
31
32 def setup():
32 def setup():
33 """setup temporary IPYTHONDIR for tests"""
33 """setup temporary IPYTHONDIR for tests"""
34 global IPYTHONDIR
34 global IPYTHONDIR
35 global env
35 global env
36 global save_get_ipython_dir
36 global save_get_ipython_dir
37
37
38 IPYTHONDIR = tempfile.mkdtemp()
38 IPYTHONDIR = tempfile.mkdtemp()
39
39
40 env = os.environ.copy()
40 env = os.environ.copy()
41 env["IPYTHONDIR"] = IPYTHONDIR
41 env["IPYTHONDIR"] = IPYTHONDIR
42
42
43 save_get_ipython_dir = path.get_ipython_dir
43 save_get_ipython_dir = path.get_ipython_dir
44 path.get_ipython_dir = lambda : IPYTHONDIR
44 path.get_ipython_dir = lambda : IPYTHONDIR
45
45
46
46
47 def teardown():
47 def teardown():
48 path.get_ipython_dir = save_get_ipython_dir
48 path.get_ipython_dir = save_get_ipython_dir
49
49
50 try:
50 try:
51 shutil.rmtree(IPYTHONDIR)
51 shutil.rmtree(IPYTHONDIR)
52 except (OSError, IOError):
52 except (OSError, IOError):
53 # no such file
53 # no such file
54 pass
54 pass
55
55
56
56
57 @contextmanager
57 @contextmanager
58 def setup_kernel(cmd):
58 def setup_kernel(cmd):
59 """start an embedded kernel in a subprocess, and wait for it to be ready
59 """start an embedded kernel in a subprocess, and wait for it to be ready
60
60
61 Returns
61 Returns
62 -------
62 -------
63 kernel_manager: connected KernelManager instance
63 kernel_manager: connected KernelManager instance
64 """
64 """
65 kernel = Popen([sys.executable, '-c', cmd], stdout=PIPE, stderr=PIPE, env=env)
65 kernel = Popen([sys.executable, '-c', cmd], stdout=PIPE, stderr=PIPE, env=env)
66 connection_file = os.path.join(IPYTHONDIR,
66 connection_file = os.path.join(IPYTHONDIR,
67 'profile_default',
67 'profile_default',
68 'security',
68 'security',
69 'kernel-%i.json' % kernel.pid
69 'kernel-%i.json' % kernel.pid
70 )
70 )
71 # wait for connection file to exist, timeout after 5s
71 # wait for connection file to exist, timeout after 5s
72 tic = time.time()
72 tic = time.time()
73 while not os.path.exists(connection_file) and kernel.poll() is None and time.time() < tic + 10:
73 while not os.path.exists(connection_file) and kernel.poll() is None and time.time() < tic + 10:
74 time.sleep(0.1)
74 time.sleep(0.1)
75
75
76 if kernel.poll() is not None:
76 if kernel.poll() is not None:
77 o,e = kernel.communicate()
77 o,e = kernel.communicate()
78 e = py3compat.cast_unicode(e)
78 e = py3compat.cast_unicode(e)
79 raise IOError("Kernel failed to start:\n%s" % e)
79 raise IOError("Kernel failed to start:\n%s" % e)
80
80
81 if not os.path.exists(connection_file):
81 if not os.path.exists(connection_file):
82 if kernel.poll() is None:
82 if kernel.poll() is None:
83 kernel.terminate()
83 kernel.terminate()
84 raise IOError("Connection file %r never arrived" % connection_file)
84 raise IOError("Connection file %r never arrived" % connection_file)
85
85
86 km = BlockingKernelManager(connection_file=connection_file)
86 km = BlockingKernelManager(connection_file=connection_file)
87 km.load_connection_file()
87 km.load_connection_file()
88 km.start_channels()
88 km.start_channels()
89
89
90 try:
90 try:
91 yield km
91 yield km
92 finally:
92 finally:
93 km.stop_channels()
93 km.stop_channels()
94 kernel.terminate()
94 kernel.terminate()
95
95
96 def test_embed_kernel_basic():
96 def test_embed_kernel_basic():
97 """IPython.embed_kernel() is basically functional"""
97 """IPython.embed_kernel() is basically functional"""
98 cmd = '\n'.join([
98 cmd = '\n'.join([
99 'from IPython import embed_kernel',
99 'from IPython import embed_kernel',
100 'def go():',
100 'def go():',
101 ' a=5',
101 ' a=5',
102 ' b="hi there"',
102 ' b="hi there"',
103 ' embed_kernel()',
103 ' embed_kernel()',
104 'go()',
104 'go()',
105 '',
105 '',
106 ])
106 ])
107
107
108 with setup_kernel(cmd) as km:
108 with setup_kernel(cmd) as km:
109 shell = km.shell_channel
109 shell = km.shell_channel
110
110
111 # oinfo a (int)
111 # oinfo a (int)
112 msg_id = shell.object_info('a')
112 msg_id = shell.object_info('a')
113 msg = shell.get_msg(block=True, timeout=2)
113 msg = shell.get_msg(block=True, timeout=2)
114 content = msg['content']
114 content = msg['content']
115 nt.assert_true(content['found'])
115 nt.assert_true(content['found'])
116
116
117 msg_id = shell.execute("c=a*2")
117 msg_id = shell.execute("c=a*2")
118 msg = shell.get_msg(block=True, timeout=2)
118 msg = shell.get_msg(block=True, timeout=2)
119 content = msg['content']
119 content = msg['content']
120 nt.assert_equal(content['status'], u'ok')
120 nt.assert_equal(content['status'], u'ok')
121
121
122 # oinfo c (should be 10)
122 # oinfo c (should be 10)
123 msg_id = shell.object_info('c')
123 msg_id = shell.object_info('c')
124 msg = shell.get_msg(block=True, timeout=2)
124 msg = shell.get_msg(block=True, timeout=2)
125 content = msg['content']
125 content = msg['content']
126 nt.assert_true(content['found'])
126 nt.assert_true(content['found'])
127 nt.assert_equal(content['string_form'], u'10')
127 nt.assert_equal(content['string_form'], u'10')
128
128
129 def test_embed_kernel_namespace():
129 def test_embed_kernel_namespace():
130 """IPython.embed_kernel() inherits calling namespace"""
130 """IPython.embed_kernel() inherits calling namespace"""
131 cmd = '\n'.join([
131 cmd = '\n'.join([
132 'from IPython import embed_kernel',
132 'from IPython import embed_kernel',
133 'def go():',
133 'def go():',
134 ' a=5',
134 ' a=5',
135 ' b="hi there"',
135 ' b="hi there"',
136 ' embed_kernel()',
136 ' embed_kernel()',
137 'go()',
137 'go()',
138 '',
138 '',
139 ])
139 ])
140
140
141 with setup_kernel(cmd) as km:
141 with setup_kernel(cmd) as km:
142 shell = km.shell_channel
142 shell = km.shell_channel
143
143
144 # oinfo a (int)
144 # oinfo a (int)
145 msg_id = shell.object_info('a')
145 msg_id = shell.object_info('a')
146 msg = shell.get_msg(block=True, timeout=2)
146 msg = shell.get_msg(block=True, timeout=2)
147 content = msg['content']
147 content = msg['content']
148 nt.assert_true(content['found'])
148 nt.assert_true(content['found'])
149 nt.assert_equal(content['string_form'], u'5')
149 nt.assert_equal(content['string_form'], u'5')
150
150
151 # oinfo b (str)
151 # oinfo b (str)
152 msg_id = shell.object_info('b')
152 msg_id = shell.object_info('b')
153 msg = shell.get_msg(block=True, timeout=2)
153 msg = shell.get_msg(block=True, timeout=2)
154 content = msg['content']
154 content = msg['content']
155 nt.assert_true(content['found'])
155 nt.assert_true(content['found'])
156 nt.assert_equal(content['string_form'], u'hi there')
156 nt.assert_equal(content['string_form'], u'hi there')
157
157
158 # oinfo c (undefined)
158 # oinfo c (undefined)
159 msg_id = shell.object_info('c')
159 msg_id = shell.object_info('c')
160 msg = shell.get_msg(block=True, timeout=2)
160 msg = shell.get_msg(block=True, timeout=2)
161 content = msg['content']
161 content = msg['content']
162 nt.assert_false(content['found'])
162 nt.assert_false(content['found'])
163
163
164 def test_embed_kernel_reentrant():
164 def test_embed_kernel_reentrant():
165 """IPython.embed_kernel() can be called multiple times"""
165 """IPython.embed_kernel() can be called multiple times"""
166 cmd = '\n'.join([
166 cmd = '\n'.join([
167 'from IPython import embed_kernel',
167 'from IPython import embed_kernel',
168 'count = 0',
168 'count = 0',
169 'def go():',
169 'def go():',
170 ' global count',
170 ' global count',
171 ' embed_kernel()',
171 ' embed_kernel()',
172 ' count = count + 1',
172 ' count = count + 1',
173 '',
173 '',
174 'while True:'
174 'while True:'
175 ' go()',
175 ' go()',
176 '',
176 '',
177 ])
177 ])
178
178
179 with setup_kernel(cmd) as km:
179 with setup_kernel(cmd) as km:
180 shell = km.shell_channel
180 shell = km.shell_channel
181 for i in range(5):
181 for i in range(5):
182 msg_id = shell.object_info('count')
182 msg_id = shell.object_info('count')
183 msg = shell.get_msg(block=True, timeout=2)
183 msg = shell.get_msg(block=True, timeout=2)
184 content = msg['content']
184 content = msg['content']
185 nt.assert_true(content['found'])
185 nt.assert_true(content['found'])
186 nt.assert_equal(content['string_form'], unicode(i))
186 nt.assert_equal(content['string_form'], unicode(i))
187
187
188 # exit from embed_kernel
188 # exit from embed_kernel
189 shell.execute("get_ipython().exit_now = True")
189 shell.execute("get_ipython().exit_now = True")
190 msg = shell.get_msg(block=True, timeout=2)
190 msg = shell.get_msg(block=True, timeout=2)
191 time.sleep(0.2)
191 time.sleep(0.2)
192
192
193
193
@@ -1,48 +1,48 b''
1 """Tests for the notebook kernel and session manager"""
1 """Tests for the notebook kernel and session manager"""
2
2
3 from subprocess import PIPE
3 from subprocess import PIPE
4 import time
4 import time
5 from unittest import TestCase
5 from unittest import TestCase
6
6
7 from IPython.testing import decorators as dec
7 from IPython.testing import decorators as dec
8
8
9 from IPython.config.loader import Config
9 from IPython.config.loader import Config
10 from IPython.zmq.kernelmanager import KernelManager
10 from IPython.kernel.kernelmanager import KernelManager
11
11
12 class TestKernelManager(TestCase):
12 class TestKernelManager(TestCase):
13
13
14 def _get_tcp_km(self):
14 def _get_tcp_km(self):
15 return KernelManager()
15 return KernelManager()
16
16
17 def _get_ipc_km(self):
17 def _get_ipc_km(self):
18 c = Config()
18 c = Config()
19 c.KernelManager.transport = 'ipc'
19 c.KernelManager.transport = 'ipc'
20 c.KernelManager.ip = 'test'
20 c.KernelManager.ip = 'test'
21 km = KernelManager(config=c)
21 km = KernelManager(config=c)
22 return km
22 return km
23
23
24 def _run_lifecycle(self, km):
24 def _run_lifecycle(self, km):
25 km.start_kernel(stdout=PIPE, stderr=PIPE)
25 km.start_kernel(stdout=PIPE, stderr=PIPE)
26 km.start_channels(shell=True, iopub=False, stdin=False, hb=False)
26 km.start_channels(shell=True, iopub=False, stdin=False, hb=False)
27 km.restart_kernel()
27 km.restart_kernel()
28 # We need a delay here to give the restarting kernel a chance to
28 # We need a delay here to give the restarting kernel a chance to
29 # restart. Otherwise, the interrupt will kill it, causing the test
29 # restart. Otherwise, the interrupt will kill it, causing the test
30 # suite to hang. The reason it *hangs* is that the shutdown
30 # suite to hang. The reason it *hangs* is that the shutdown
31 # message for the restart sometimes hasn't been sent to the kernel.
31 # message for the restart sometimes hasn't been sent to the kernel.
32 # Because linger is oo on the shell channel, the context can't
32 # Because linger is oo on the shell channel, the context can't
33 # close until the message is sent to the kernel, which is not dead.
33 # close until the message is sent to the kernel, which is not dead.
34 time.sleep(1.0)
34 time.sleep(1.0)
35 km.interrupt_kernel()
35 km.interrupt_kernel()
36 self.assertTrue(isinstance(km, KernelManager))
36 self.assertTrue(isinstance(km, KernelManager))
37 km.shutdown_kernel()
37 km.shutdown_kernel()
38 km.shell_channel.stop()
38 km.shell_channel.stop()
39
39
40 def test_tcp_lifecycle(self):
40 def test_tcp_lifecycle(self):
41 km = self._get_tcp_km()
41 km = self._get_tcp_km()
42 self._run_lifecycle(km)
42 self._run_lifecycle(km)
43
43
44 @dec.skip_win32
44 @dec.skip_win32
45 def testipc_lifecycle(self):
45 def testipc_lifecycle(self):
46 km = self._get_ipc_km()
46 km = self._get_ipc_km()
47 self._run_lifecycle(km)
47 self._run_lifecycle(km)
48
48
General Comments 0
You need to be logged in to leave comments. Login now