##// END OF EJS Templates
update imports with new layout
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.kernel.blockingkernelmanager import BlockingKernelManager
37 from IPython.kernel.blocking import BlockingKernelManager
38 from IPython.kernel.kernelmanager import KernelManager
38 from IPython.kernel 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.kernel.zmq.kernelapp import (
45 from IPython.kernel.zmq.kernelapp import (
46 kernel_flags,
46 kernel_flags,
47 kernel_aliases,
47 kernel_aliases,
48 IPKernelApp
48 IPKernelApp
49 )
49 )
50 from IPython.kernel.zmq.session import Session, default_secure
50 from IPython.kernel.zmq.session import Session, default_secure
51 from IPython.kernel.zmq.zmqshell import ZMQInteractiveShell
51 from IPython.kernel.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.kernel.zmq.pylab.backend_inline import InlineBackend
117 from IPython.kernel.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("--IPKernelApp.parent_appname='%s'" % self.name)
197 self.kernel_argv.append("--IPKernelApp.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 IPKernelApp.load_connection_file
247 # this is identical to IPKernelApp.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,10 +1,10 b''
1 """IPython kernels and associated utilities"""
1 """IPython kernels and associated utilities"""
2
2
3 # just for friendlier zmq version check
3 # just for friendlier zmq version check
4 from . import zmq
4 from . import zmq
5
5
6 from .connect import *
6 from .connect import *
7 from .launcher import *
7 from .launcher import *
8 from .kernelmanager import KernelManager
8 from .manager import KernelManager
9 from .blockingkernelmanager import BlockingKernelManager
9 from .blocking import BlockingKernelManager
10 from .multikernelmanager import MultiKernelManager
10 from .multikernelmanager import MultiKernelManager
@@ -0,0 +1,1 b''
1 from .manager import BlockingKernelManager No newline at end of file
@@ -1,89 +1,89 b''
1 """ Implements a fully blocking kernel manager.
1 """ Implements a fully blocking kernel manager.
2
2
3 Useful for test suites and blocking terminal interfaces.
3 Useful for test suites and blocking terminal interfaces.
4 """
4 """
5 #-----------------------------------------------------------------------------
5 #-----------------------------------------------------------------------------
6 # Copyright (C) 2010-2012 The IPython Development Team
6 # Copyright (C) 2010-2012 The IPython Development Team
7 #
7 #
8 # Distributed under the terms of the BSD License. The full license is in
8 # Distributed under the terms of the BSD License. The full license is in
9 # the file COPYING.txt, distributed as part of this software.
9 # the file COPYING.txt, distributed as part of this software.
10 #-----------------------------------------------------------------------------
10 #-----------------------------------------------------------------------------
11
11
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13 # Imports
13 # Imports
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15
15
16 import Queue
16 import Queue
17
17
18 from IPython.utils.traitlets import Type
18 from IPython.utils.traitlets import Type
19 from .kernelmanager import KernelManager, IOPubChannel, HBChannel, \
19 from IPython.kernel.manager import KernelManager, IOPubChannel, HBChannel, \
20 ShellChannel, StdInChannel
20 ShellChannel, StdInChannel
21
21
22 #-----------------------------------------------------------------------------
22 #-----------------------------------------------------------------------------
23 # Blocking kernel manager
23 # Blocking kernel manager
24 #-----------------------------------------------------------------------------
24 #-----------------------------------------------------------------------------
25
25
26
26
27 class BlockingChannelMixin(object):
27 class BlockingChannelMixin(object):
28
28
29 def __init__(self, *args, **kwds):
29 def __init__(self, *args, **kwds):
30 super(BlockingChannelMixin, self).__init__(*args, **kwds)
30 super(BlockingChannelMixin, self).__init__(*args, **kwds)
31 self._in_queue = Queue.Queue()
31 self._in_queue = Queue.Queue()
32
32
33 def call_handlers(self, msg):
33 def call_handlers(self, msg):
34 self._in_queue.put(msg)
34 self._in_queue.put(msg)
35
35
36 def get_msg(self, block=True, timeout=None):
36 def get_msg(self, block=True, timeout=None):
37 """ Gets a message if there is one that is ready. """
37 """ Gets a message if there is one that is ready. """
38 if timeout is None:
38 if timeout is None:
39 # Queue.get(timeout=None) has stupid uninteruptible
39 # Queue.get(timeout=None) has stupid uninteruptible
40 # behavior, so wait for a week instead
40 # behavior, so wait for a week instead
41 timeout = 604800
41 timeout = 604800
42 return self._in_queue.get(block, timeout)
42 return self._in_queue.get(block, timeout)
43
43
44 def get_msgs(self):
44 def get_msgs(self):
45 """ Get all messages that are currently ready. """
45 """ Get all messages that are currently ready. """
46 msgs = []
46 msgs = []
47 while True:
47 while True:
48 try:
48 try:
49 msgs.append(self.get_msg(block=False))
49 msgs.append(self.get_msg(block=False))
50 except Queue.Empty:
50 except Queue.Empty:
51 break
51 break
52 return msgs
52 return msgs
53
53
54 def msg_ready(self):
54 def msg_ready(self):
55 """ Is there a message that has been received? """
55 """ Is there a message that has been received? """
56 return not self._in_queue.empty()
56 return not self._in_queue.empty()
57
57
58
58
59 class BlockingIOPubChannel(BlockingChannelMixin, IOPubChannel):
59 class BlockingIOPubChannel(BlockingChannelMixin, IOPubChannel):
60 pass
60 pass
61
61
62
62
63 class BlockingShellChannel(BlockingChannelMixin, ShellChannel):
63 class BlockingShellChannel(BlockingChannelMixin, ShellChannel):
64 pass
64 pass
65
65
66
66
67 class BlockingStdInChannel(BlockingChannelMixin, StdInChannel):
67 class BlockingStdInChannel(BlockingChannelMixin, StdInChannel):
68 pass
68 pass
69
69
70
70
71 class BlockingHBChannel(HBChannel):
71 class BlockingHBChannel(HBChannel):
72
72
73 # This kernel needs quicker monitoring, shorten to 1 sec.
73 # This kernel needs quicker monitoring, shorten to 1 sec.
74 # less than 0.5s is unreliable, and will get occasional
74 # less than 0.5s is unreliable, and will get occasional
75 # false reports of missed beats.
75 # false reports of missed beats.
76 time_to_dead = 1.
76 time_to_dead = 1.
77
77
78 def call_handlers(self, since_last_heartbeat):
78 def call_handlers(self, since_last_heartbeat):
79 """ Pause beating on missed heartbeat. """
79 """ Pause beating on missed heartbeat. """
80 pass
80 pass
81
81
82
82
83 class BlockingKernelManager(KernelManager):
83 class BlockingKernelManager(KernelManager):
84
84
85 # The classes to use for the various channels.
85 # The classes to use for the various channels.
86 shell_channel_class = Type(BlockingShellChannel)
86 shell_channel_class = Type(BlockingShellChannel)
87 iopub_channel_class = Type(BlockingIOPubChannel)
87 iopub_channel_class = Type(BlockingIOPubChannel)
88 stdin_channel_class = Type(BlockingStdInChannel)
88 stdin_channel_class = Type(BlockingStdInChannel)
89 hb_channel_class = Type(BlockingHBChannel)
89 hb_channel_class = Type(BlockingHBChannel)
@@ -0,0 +1,2 b''
1 from .manager import IOLoopKernelManager
2 from .restarter import IOLoopKernelRestarter
@@ -1,50 +1,50 b''
1 """A kernel manager with ioloop based logic."""
1 """A kernel manager with ioloop based logic."""
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 from __future__ import absolute_import
14 from __future__ import absolute_import
15
15
16 import zmq
16 import zmq
17 from zmq.eventloop import ioloop
17 from zmq.eventloop import ioloop
18
18
19 from IPython.utils.traitlets import (
19 from IPython.utils.traitlets import (
20 Instance
20 Instance
21 )
21 )
22
22
23 from .blockingkernelmanager import BlockingKernelManager
23 from IPython.kernel.blocking.manager import BlockingKernelManager
24 from .ioloopkernelrestarter import IOLoopKernelRestarter
24 from .restarter import IOLoopKernelRestarter
25
25
26 #-----------------------------------------------------------------------------
26 #-----------------------------------------------------------------------------
27 # Code
27 # Code
28 #-----------------------------------------------------------------------------
28 #-----------------------------------------------------------------------------
29
29
30 class IOLoopKernelManager(BlockingKernelManager):
30 class IOLoopKernelManager(BlockingKernelManager):
31
31
32 loop = Instance('zmq.eventloop.ioloop.IOLoop', allow_none=False)
32 loop = Instance('zmq.eventloop.ioloop.IOLoop', allow_none=False)
33 def _loop_default(self):
33 def _loop_default(self):
34 return ioloop.IOLoop.instance()
34 return ioloop.IOLoop.instance()
35
35
36 _restarter = Instance('IPython.kernel.ioloopkernelrestarter.IOLoopKernelRestarter')
36 _restarter = Instance('IPython.kernel.ioloop.IOLoopKernelRestarter')
37
37
38 def start_restarter(self):
38 def start_restarter(self):
39 if self.autorestart and self.has_kernel:
39 if self.autorestart and self.has_kernel:
40 if self._restarter is None:
40 if self._restarter is None:
41 self._restarter = IOLoopKernelRestarter(
41 self._restarter = IOLoopKernelRestarter(
42 kernel_manager=self, loop=self.loop,
42 kernel_manager=self, loop=self.loop,
43 config=self.config, log=self.log
43 config=self.config, log=self.log
44 )
44 )
45 self._restarter.start()
45 self._restarter.start()
46
46
47 def stop_restarter(self):
47 def stop_restarter(self):
48 if self.autorestart:
48 if self.autorestart:
49 if self._restarter is not None:
49 if self._restarter is not None:
50 self._restarter.stop()
50 self._restarter.stop()
@@ -1,74 +1,74 b''
1 """A basic in process kernel monitor with autorestarting.
1 """A basic in process kernel monitor with autorestarting.
2
2
3 This watches a kernel's state using KernelManager.is_alive and auto
3 This watches a kernel's state using KernelManager.is_alive and auto
4 restarts the kernel if it dies.
4 restarts the kernel if it dies.
5 """
5 """
6
6
7 #-----------------------------------------------------------------------------
7 #-----------------------------------------------------------------------------
8 # Copyright (C) 2013 The IPython Development Team
8 # Copyright (C) 2013 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 from __future__ import absolute_import
18 from __future__ import absolute_import
19
19
20 import zmq
20 import zmq
21 from zmq.eventloop import ioloop
21 from zmq.eventloop import ioloop
22
22
23
23
24 from IPython.config.configurable import LoggingConfigurable
24 from IPython.config.configurable import LoggingConfigurable
25 from IPython.utils.traitlets import (
25 from IPython.utils.traitlets import (
26 Instance, Float
26 Instance, Float
27 )
27 )
28
28
29 #-----------------------------------------------------------------------------
29 #-----------------------------------------------------------------------------
30 # Code
30 # Code
31 #-----------------------------------------------------------------------------
31 #-----------------------------------------------------------------------------
32
32
33 class IOLoopKernelRestarter(LoggingConfigurable):
33 class IOLoopKernelRestarter(LoggingConfigurable):
34 """Monitor and autorestart a kernel."""
34 """Monitor and autorestart a kernel."""
35
35
36 loop = Instance('zmq.eventloop.ioloop.IOLoop', allow_none=False)
36 loop = Instance('zmq.eventloop.ioloop.IOLoop', allow_none=False)
37 def _loop_default(self):
37 def _loop_default(self):
38 return ioloop.IOLoop.instance()
38 return ioloop.IOLoop.instance()
39
39
40 kernel_manager = Instance('IPython.kernel.kernelmanager.KernelManager')
40 kernel_manager = Instance('IPython.kernel.KernelManager')
41
41
42 time_to_dead = Float(3.0, config=True,
42 time_to_dead = Float(3.0, config=True,
43 help="""Kernel heartbeat interval in seconds."""
43 help="""Kernel heartbeat interval in seconds."""
44 )
44 )
45
45
46 _pcallback = None
46 _pcallback = None
47
47
48 def start(self):
48 def start(self):
49 """Start the polling of the kernel."""
49 """Start the polling of the kernel."""
50 if self._pcallback is None:
50 if self._pcallback is None:
51 self._pcallback = ioloop.PeriodicCallback(
51 self._pcallback = ioloop.PeriodicCallback(
52 self._poll, 1000*self.time_to_dead, self.loop
52 self._poll, 1000*self.time_to_dead, self.loop
53 )
53 )
54 self._pcallback.start()
54 self._pcallback.start()
55
55
56 def stop(self):
56 def stop(self):
57 """Stop the kernel polling."""
57 """Stop the kernel polling."""
58 if self._pcallback is not None:
58 if self._pcallback is not None:
59 self._pcallback.stop()
59 self._pcallback.stop()
60
60
61 def clear(self):
61 def clear(self):
62 """Clear the underlying PeriodicCallback."""
62 """Clear the underlying PeriodicCallback."""
63 self.stop()
63 self.stop()
64 if self._pcallback is not None:
64 if self._pcallback is not None:
65 self._pcallback = None
65 self._pcallback = None
66
66
67 def _poll(self):
67 def _poll(self):
68 self.log.info('Polling kernel...')
68 self.log.info('Polling kernel...')
69 if not self.kernel_manager.is_alive():
69 if not self.kernel_manager.is_alive():
70 # This restart event should leave the connection file in place so
70 # This restart event should leave the connection file in place so
71 # the ports are the same. Because this takes place below the
71 # the ports are the same. Because this takes place below the
72 # MappingKernelManager, the kernel_id will also remain the same.
72 # MappingKernelManager, the kernel_id will also remain the same.
73 self.log.info('KernelRestarter: restarting kernel')
73 self.log.info('KernelRestarter: restarting kernel')
74 self.kernel_manager.restart_kernel(now=True);
74 self.kernel_manager.restart_kernel(now=True);
@@ -1,1146 +1,1146 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 from __future__ import absolute_import
18 from __future__ import absolute_import
19
19
20 # Standard library imports
20 # Standard library imports
21 import atexit
21 import atexit
22 import errno
22 import errno
23 import json
23 import json
24 import os
24 import os
25 import signal
25 import signal
26 import sys
26 import sys
27 from threading import Thread
27 from threading import Thread
28 import time
28 import time
29
29
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.importstring import import_item
38 from IPython.utils.importstring import import_item
39 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
39 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
40 from IPython.utils.traitlets import (
40 from IPython.utils.traitlets import (
41 Any, Instance, Type, Unicode, List, Integer, Bool,
41 Any, Instance, Type, Unicode, List, Integer, Bool,
42 CaselessStrEnum, DottedObjectName
42 CaselessStrEnum, DottedObjectName
43 )
43 )
44 from IPython.utils.py3compat import str_to_bytes
44 from IPython.utils.py3compat import str_to_bytes
45 from IPython.kernel import (
45 from IPython.kernel import (
46 write_connection_file,
46 write_connection_file,
47 make_ipkernel_cmd,
47 make_ipkernel_cmd,
48 launch_kernel,
48 launch_kernel,
49 )
49 )
50 from .zmq.session import Session
50 from .zmq.session import Session
51 from .kernelmanagerabc import (
51 from .managerabc import (
52 ShellChannelABC, IOPubChannelABC,
52 ShellChannelABC, IOPubChannelABC,
53 HBChannelABC, StdInChannelABC,
53 HBChannelABC, StdInChannelABC,
54 KernelManagerABC
54 KernelManagerABC
55 )
55 )
56
56
57 #-----------------------------------------------------------------------------
57 #-----------------------------------------------------------------------------
58 # Constants and exceptions
58 # Constants and exceptions
59 #-----------------------------------------------------------------------------
59 #-----------------------------------------------------------------------------
60
60
61 class InvalidPortNumber(Exception):
61 class InvalidPortNumber(Exception):
62 pass
62 pass
63
63
64 #-----------------------------------------------------------------------------
64 #-----------------------------------------------------------------------------
65 # Utility functions
65 # Utility functions
66 #-----------------------------------------------------------------------------
66 #-----------------------------------------------------------------------------
67
67
68 # some utilities to validate message structure, these might get moved elsewhere
68 # some utilities to validate message structure, these might get moved elsewhere
69 # if they prove to have more generic utility
69 # if they prove to have more generic utility
70
70
71 def validate_string_list(lst):
71 def validate_string_list(lst):
72 """Validate that the input is a list of strings.
72 """Validate that the input is a list of strings.
73
73
74 Raises ValueError if not."""
74 Raises ValueError if not."""
75 if not isinstance(lst, list):
75 if not isinstance(lst, list):
76 raise ValueError('input %r must be a list' % lst)
76 raise ValueError('input %r must be a list' % lst)
77 for x in lst:
77 for x in lst:
78 if not isinstance(x, basestring):
78 if not isinstance(x, basestring):
79 raise ValueError('element %r in list must be a string' % x)
79 raise ValueError('element %r in list must be a string' % x)
80
80
81
81
82 def validate_string_dict(dct):
82 def validate_string_dict(dct):
83 """Validate that the input is a dict with string keys and values.
83 """Validate that the input is a dict with string keys and values.
84
84
85 Raises ValueError if not."""
85 Raises ValueError if not."""
86 for k,v in dct.iteritems():
86 for k,v in dct.iteritems():
87 if not isinstance(k, basestring):
87 if not isinstance(k, basestring):
88 raise ValueError('key %r in dict must be a string' % k)
88 raise ValueError('key %r in dict must be a string' % k)
89 if not isinstance(v, basestring):
89 if not isinstance(v, basestring):
90 raise ValueError('value %r in dict must be a string' % v)
90 raise ValueError('value %r in dict must be a string' % v)
91
91
92
92
93 #-----------------------------------------------------------------------------
93 #-----------------------------------------------------------------------------
94 # ZMQ Socket Channel classes
94 # ZMQ Socket Channel classes
95 #-----------------------------------------------------------------------------
95 #-----------------------------------------------------------------------------
96
96
97 class ZMQSocketChannel(Thread):
97 class ZMQSocketChannel(Thread):
98 """The base class for the channels that use ZMQ sockets."""
98 """The base class for the channels that use ZMQ sockets."""
99 context = None
99 context = None
100 session = None
100 session = None
101 socket = None
101 socket = None
102 ioloop = None
102 ioloop = None
103 stream = None
103 stream = None
104 _address = None
104 _address = None
105 _exiting = False
105 _exiting = False
106
106
107 def __init__(self, context, session, address):
107 def __init__(self, context, session, address):
108 """Create a channel.
108 """Create a channel.
109
109
110 Parameters
110 Parameters
111 ----------
111 ----------
112 context : :class:`zmq.Context`
112 context : :class:`zmq.Context`
113 The ZMQ context to use.
113 The ZMQ context to use.
114 session : :class:`session.Session`
114 session : :class:`session.Session`
115 The session to use.
115 The session to use.
116 address : zmq url
116 address : zmq url
117 Standard (ip, port) tuple that the kernel is listening on.
117 Standard (ip, port) tuple that the kernel is listening on.
118 """
118 """
119 super(ZMQSocketChannel, self).__init__()
119 super(ZMQSocketChannel, self).__init__()
120 self.daemon = True
120 self.daemon = True
121
121
122 self.context = context
122 self.context = context
123 self.session = session
123 self.session = session
124 if isinstance(address, tuple):
124 if isinstance(address, tuple):
125 if address[1] == 0:
125 if address[1] == 0:
126 message = 'The port number for a channel cannot be 0.'
126 message = 'The port number for a channel cannot be 0.'
127 raise InvalidPortNumber(message)
127 raise InvalidPortNumber(message)
128 address = "tcp://%s:%i" % address
128 address = "tcp://%s:%i" % address
129 self._address = address
129 self._address = address
130 atexit.register(self._notice_exit)
130 atexit.register(self._notice_exit)
131
131
132 def _notice_exit(self):
132 def _notice_exit(self):
133 self._exiting = True
133 self._exiting = True
134
134
135 def _run_loop(self):
135 def _run_loop(self):
136 """Run my loop, ignoring EINTR events in the poller"""
136 """Run my loop, ignoring EINTR events in the poller"""
137 while True:
137 while True:
138 try:
138 try:
139 self.ioloop.start()
139 self.ioloop.start()
140 except ZMQError as e:
140 except ZMQError as e:
141 if e.errno == errno.EINTR:
141 if e.errno == errno.EINTR:
142 continue
142 continue
143 else:
143 else:
144 raise
144 raise
145 except Exception:
145 except Exception:
146 if self._exiting:
146 if self._exiting:
147 break
147 break
148 else:
148 else:
149 raise
149 raise
150 else:
150 else:
151 break
151 break
152
152
153 def stop(self):
153 def stop(self):
154 """Stop the channel's event loop and join its thread.
154 """Stop the channel's event loop and join its thread.
155
155
156 This calls :method:`Thread.join` and returns when the thread
156 This calls :method:`Thread.join` and returns when the thread
157 terminates. :class:`RuntimeError` will be raised if
157 terminates. :class:`RuntimeError` will be raised if
158 :method:`self.start` is called again.
158 :method:`self.start` is called again.
159 """
159 """
160 self.join()
160 self.join()
161
161
162 @property
162 @property
163 def address(self):
163 def address(self):
164 """Get the channel's address as a zmq url string.
164 """Get the channel's address as a zmq url string.
165
165
166 These URLS have the form: 'tcp://127.0.0.1:5555'.
166 These URLS have the form: 'tcp://127.0.0.1:5555'.
167 """
167 """
168 return self._address
168 return self._address
169
169
170 def _queue_send(self, msg):
170 def _queue_send(self, msg):
171 """Queue a message to be sent from the IOLoop's thread.
171 """Queue a message to be sent from the IOLoop's thread.
172
172
173 Parameters
173 Parameters
174 ----------
174 ----------
175 msg : message to send
175 msg : message to send
176
176
177 This is threadsafe, as it uses IOLoop.add_callback to give the loop's
177 This is threadsafe, as it uses IOLoop.add_callback to give the loop's
178 thread control of the action.
178 thread control of the action.
179 """
179 """
180 def thread_send():
180 def thread_send():
181 self.session.send(self.stream, msg)
181 self.session.send(self.stream, msg)
182 self.ioloop.add_callback(thread_send)
182 self.ioloop.add_callback(thread_send)
183
183
184 def _handle_recv(self, msg):
184 def _handle_recv(self, msg):
185 """Callback for stream.on_recv.
185 """Callback for stream.on_recv.
186
186
187 Unpacks message, and calls handlers with it.
187 Unpacks message, and calls handlers with it.
188 """
188 """
189 ident,smsg = self.session.feed_identities(msg)
189 ident,smsg = self.session.feed_identities(msg)
190 self.call_handlers(self.session.unserialize(smsg))
190 self.call_handlers(self.session.unserialize(smsg))
191
191
192
192
193
193
194 class ShellChannel(ZMQSocketChannel):
194 class ShellChannel(ZMQSocketChannel):
195 """The shell channel for issuing request/replies to the kernel."""
195 """The shell channel for issuing request/replies to the kernel."""
196
196
197 command_queue = None
197 command_queue = None
198 # flag for whether execute requests should be allowed to call raw_input:
198 # flag for whether execute requests should be allowed to call raw_input:
199 allow_stdin = True
199 allow_stdin = True
200
200
201 def __init__(self, context, session, address):
201 def __init__(self, context, session, address):
202 super(ShellChannel, self).__init__(context, session, address)
202 super(ShellChannel, self).__init__(context, session, address)
203 self.ioloop = ioloop.IOLoop()
203 self.ioloop = ioloop.IOLoop()
204
204
205 def run(self):
205 def run(self):
206 """The thread's main activity. Call start() instead."""
206 """The thread's main activity. Call start() instead."""
207 self.socket = self.context.socket(zmq.DEALER)
207 self.socket = self.context.socket(zmq.DEALER)
208 self.socket.setsockopt(zmq.IDENTITY, self.session.bsession)
208 self.socket.setsockopt(zmq.IDENTITY, self.session.bsession)
209 self.socket.connect(self.address)
209 self.socket.connect(self.address)
210 self.stream = zmqstream.ZMQStream(self.socket, self.ioloop)
210 self.stream = zmqstream.ZMQStream(self.socket, self.ioloop)
211 self.stream.on_recv(self._handle_recv)
211 self.stream.on_recv(self._handle_recv)
212 self._run_loop()
212 self._run_loop()
213 try:
213 try:
214 self.socket.close()
214 self.socket.close()
215 except:
215 except:
216 pass
216 pass
217
217
218 def stop(self):
218 def stop(self):
219 """Stop the channel's event loop and join its thread."""
219 """Stop the channel's event loop and join its thread."""
220 self.ioloop.stop()
220 self.ioloop.stop()
221 super(ShellChannel, self).stop()
221 super(ShellChannel, self).stop()
222
222
223 def call_handlers(self, msg):
223 def call_handlers(self, msg):
224 """This method is called in the ioloop thread when a message arrives.
224 """This method is called in the ioloop thread when a message arrives.
225
225
226 Subclasses should override this method to handle incoming messages.
226 Subclasses should override this method to handle incoming messages.
227 It is important to remember that this method is called in the thread
227 It is important to remember that this method is called in the thread
228 so that some logic must be done to ensure that the application leve
228 so that some logic must be done to ensure that the application leve
229 handlers are called in the application thread.
229 handlers are called in the application thread.
230 """
230 """
231 raise NotImplementedError('call_handlers must be defined in a subclass.')
231 raise NotImplementedError('call_handlers must be defined in a subclass.')
232
232
233 def execute(self, code, silent=False, store_history=True,
233 def execute(self, code, silent=False, store_history=True,
234 user_variables=None, user_expressions=None, allow_stdin=None):
234 user_variables=None, user_expressions=None, allow_stdin=None):
235 """Execute code in the kernel.
235 """Execute code in the kernel.
236
236
237 Parameters
237 Parameters
238 ----------
238 ----------
239 code : str
239 code : str
240 A string of Python code.
240 A string of Python code.
241
241
242 silent : bool, optional (default False)
242 silent : bool, optional (default False)
243 If set, the kernel will execute the code as quietly possible, and
243 If set, the kernel will execute the code as quietly possible, and
244 will force store_history to be False.
244 will force store_history to be False.
245
245
246 store_history : bool, optional (default True)
246 store_history : bool, optional (default True)
247 If set, the kernel will store command history. This is forced
247 If set, the kernel will store command history. This is forced
248 to be False if silent is True.
248 to be False if silent is True.
249
249
250 user_variables : list, optional
250 user_variables : list, optional
251 A list of variable names to pull from the user's namespace. They
251 A list of variable names to pull from the user's namespace. They
252 will come back as a dict with these names as keys and their
252 will come back as a dict with these names as keys and their
253 :func:`repr` as values.
253 :func:`repr` as values.
254
254
255 user_expressions : dict, optional
255 user_expressions : dict, optional
256 A dict mapping names to expressions to be evaluated in the user's
256 A dict mapping names to expressions to be evaluated in the user's
257 dict. The expression values are returned as strings formatted using
257 dict. The expression values are returned as strings formatted using
258 :func:`repr`.
258 :func:`repr`.
259
259
260 allow_stdin : bool, optional (default self.allow_stdin)
260 allow_stdin : bool, optional (default self.allow_stdin)
261 Flag for whether the kernel can send stdin requests to frontends.
261 Flag for whether the kernel can send stdin requests to frontends.
262
262
263 Some frontends (e.g. the Notebook) do not support stdin requests.
263 Some frontends (e.g. the Notebook) do not support stdin requests.
264 If raw_input is called from code executed from such a frontend, a
264 If raw_input is called from code executed from such a frontend, a
265 StdinNotImplementedError will be raised.
265 StdinNotImplementedError will be raised.
266
266
267 Returns
267 Returns
268 -------
268 -------
269 The msg_id of the message sent.
269 The msg_id of the message sent.
270 """
270 """
271 if user_variables is None:
271 if user_variables is None:
272 user_variables = []
272 user_variables = []
273 if user_expressions is None:
273 if user_expressions is None:
274 user_expressions = {}
274 user_expressions = {}
275 if allow_stdin is None:
275 if allow_stdin is None:
276 allow_stdin = self.allow_stdin
276 allow_stdin = self.allow_stdin
277
277
278
278
279 # Don't waste network traffic if inputs are invalid
279 # Don't waste network traffic if inputs are invalid
280 if not isinstance(code, basestring):
280 if not isinstance(code, basestring):
281 raise ValueError('code %r must be a string' % code)
281 raise ValueError('code %r must be a string' % code)
282 validate_string_list(user_variables)
282 validate_string_list(user_variables)
283 validate_string_dict(user_expressions)
283 validate_string_dict(user_expressions)
284
284
285 # Create class for content/msg creation. Related to, but possibly
285 # Create class for content/msg creation. Related to, but possibly
286 # not in Session.
286 # not in Session.
287 content = dict(code=code, silent=silent, store_history=store_history,
287 content = dict(code=code, silent=silent, store_history=store_history,
288 user_variables=user_variables,
288 user_variables=user_variables,
289 user_expressions=user_expressions,
289 user_expressions=user_expressions,
290 allow_stdin=allow_stdin,
290 allow_stdin=allow_stdin,
291 )
291 )
292 msg = self.session.msg('execute_request', content)
292 msg = self.session.msg('execute_request', content)
293 self._queue_send(msg)
293 self._queue_send(msg)
294 return msg['header']['msg_id']
294 return msg['header']['msg_id']
295
295
296 def complete(self, text, line, cursor_pos, block=None):
296 def complete(self, text, line, cursor_pos, block=None):
297 """Tab complete text in the kernel's namespace.
297 """Tab complete text in the kernel's namespace.
298
298
299 Parameters
299 Parameters
300 ----------
300 ----------
301 text : str
301 text : str
302 The text to complete.
302 The text to complete.
303 line : str
303 line : str
304 The full line of text that is the surrounding context for the
304 The full line of text that is the surrounding context for the
305 text to complete.
305 text to complete.
306 cursor_pos : int
306 cursor_pos : int
307 The position of the cursor in the line where the completion was
307 The position of the cursor in the line where the completion was
308 requested.
308 requested.
309 block : str, optional
309 block : str, optional
310 The full block of code in which the completion is being requested.
310 The full block of code in which the completion is being requested.
311
311
312 Returns
312 Returns
313 -------
313 -------
314 The msg_id of the message sent.
314 The msg_id of the message sent.
315 """
315 """
316 content = dict(text=text, line=line, block=block, cursor_pos=cursor_pos)
316 content = dict(text=text, line=line, block=block, cursor_pos=cursor_pos)
317 msg = self.session.msg('complete_request', content)
317 msg = self.session.msg('complete_request', content)
318 self._queue_send(msg)
318 self._queue_send(msg)
319 return msg['header']['msg_id']
319 return msg['header']['msg_id']
320
320
321 def object_info(self, oname, detail_level=0):
321 def object_info(self, oname, detail_level=0):
322 """Get metadata information about an object in the kernel's namespace.
322 """Get metadata information about an object in the kernel's namespace.
323
323
324 Parameters
324 Parameters
325 ----------
325 ----------
326 oname : str
326 oname : str
327 A string specifying the object name.
327 A string specifying the object name.
328 detail_level : int, optional
328 detail_level : int, optional
329 The level of detail for the introspection (0-2)
329 The level of detail for the introspection (0-2)
330
330
331 Returns
331 Returns
332 -------
332 -------
333 The msg_id of the message sent.
333 The msg_id of the message sent.
334 """
334 """
335 content = dict(oname=oname, detail_level=detail_level)
335 content = dict(oname=oname, detail_level=detail_level)
336 msg = self.session.msg('object_info_request', content)
336 msg = self.session.msg('object_info_request', content)
337 self._queue_send(msg)
337 self._queue_send(msg)
338 return msg['header']['msg_id']
338 return msg['header']['msg_id']
339
339
340 def history(self, raw=True, output=False, hist_access_type='range', **kwargs):
340 def history(self, raw=True, output=False, hist_access_type='range', **kwargs):
341 """Get entries from the kernel's history list.
341 """Get entries from the kernel's history list.
342
342
343 Parameters
343 Parameters
344 ----------
344 ----------
345 raw : bool
345 raw : bool
346 If True, return the raw input.
346 If True, return the raw input.
347 output : bool
347 output : bool
348 If True, then return the output as well.
348 If True, then return the output as well.
349 hist_access_type : str
349 hist_access_type : str
350 'range' (fill in session, start and stop params), 'tail' (fill in n)
350 'range' (fill in session, start and stop params), 'tail' (fill in n)
351 or 'search' (fill in pattern param).
351 or 'search' (fill in pattern param).
352
352
353 session : int
353 session : int
354 For a range request, the session from which to get lines. Session
354 For a range request, the session from which to get lines. Session
355 numbers are positive integers; negative ones count back from the
355 numbers are positive integers; negative ones count back from the
356 current session.
356 current session.
357 start : int
357 start : int
358 The first line number of a history range.
358 The first line number of a history range.
359 stop : int
359 stop : int
360 The final (excluded) line number of a history range.
360 The final (excluded) line number of a history range.
361
361
362 n : int
362 n : int
363 The number of lines of history to get for a tail request.
363 The number of lines of history to get for a tail request.
364
364
365 pattern : str
365 pattern : str
366 The glob-syntax pattern for a search request.
366 The glob-syntax pattern for a search request.
367
367
368 Returns
368 Returns
369 -------
369 -------
370 The msg_id of the message sent.
370 The msg_id of the message sent.
371 """
371 """
372 content = dict(raw=raw, output=output, hist_access_type=hist_access_type,
372 content = dict(raw=raw, output=output, hist_access_type=hist_access_type,
373 **kwargs)
373 **kwargs)
374 msg = self.session.msg('history_request', content)
374 msg = self.session.msg('history_request', content)
375 self._queue_send(msg)
375 self._queue_send(msg)
376 return msg['header']['msg_id']
376 return msg['header']['msg_id']
377
377
378 def kernel_info(self):
378 def kernel_info(self):
379 """Request kernel info."""
379 """Request kernel info."""
380 msg = self.session.msg('kernel_info_request')
380 msg = self.session.msg('kernel_info_request')
381 self._queue_send(msg)
381 self._queue_send(msg)
382 return msg['header']['msg_id']
382 return msg['header']['msg_id']
383
383
384 def shutdown(self, restart=False):
384 def shutdown(self, restart=False):
385 """Request an immediate kernel shutdown.
385 """Request an immediate kernel shutdown.
386
386
387 Upon receipt of the (empty) reply, client code can safely assume that
387 Upon receipt of the (empty) reply, client code can safely assume that
388 the kernel has shut down and it's safe to forcefully terminate it if
388 the kernel has shut down and it's safe to forcefully terminate it if
389 it's still alive.
389 it's still alive.
390
390
391 The kernel will send the reply via a function registered with Python's
391 The kernel will send the reply via a function registered with Python's
392 atexit module, ensuring it's truly done as the kernel is done with all
392 atexit module, ensuring it's truly done as the kernel is done with all
393 normal operation.
393 normal operation.
394 """
394 """
395 # Send quit message to kernel. Once we implement kernel-side setattr,
395 # Send quit message to kernel. Once we implement kernel-side setattr,
396 # this should probably be done that way, but for now this will do.
396 # this should probably be done that way, but for now this will do.
397 msg = self.session.msg('shutdown_request', {'restart':restart})
397 msg = self.session.msg('shutdown_request', {'restart':restart})
398 self._queue_send(msg)
398 self._queue_send(msg)
399 return msg['header']['msg_id']
399 return msg['header']['msg_id']
400
400
401
401
402
402
403 class IOPubChannel(ZMQSocketChannel):
403 class IOPubChannel(ZMQSocketChannel):
404 """The iopub channel which listens for messages that the kernel publishes.
404 """The iopub channel which listens for messages that the kernel publishes.
405
405
406 This channel is where all output is published to frontends.
406 This channel is where all output is published to frontends.
407 """
407 """
408
408
409 def __init__(self, context, session, address):
409 def __init__(self, context, session, address):
410 super(IOPubChannel, self).__init__(context, session, address)
410 super(IOPubChannel, self).__init__(context, session, address)
411 self.ioloop = ioloop.IOLoop()
411 self.ioloop = ioloop.IOLoop()
412
412
413 def run(self):
413 def run(self):
414 """The thread's main activity. Call start() instead."""
414 """The thread's main activity. Call start() instead."""
415 self.socket = self.context.socket(zmq.SUB)
415 self.socket = self.context.socket(zmq.SUB)
416 self.socket.setsockopt(zmq.SUBSCRIBE,b'')
416 self.socket.setsockopt(zmq.SUBSCRIBE,b'')
417 self.socket.setsockopt(zmq.IDENTITY, self.session.bsession)
417 self.socket.setsockopt(zmq.IDENTITY, self.session.bsession)
418 self.socket.connect(self.address)
418 self.socket.connect(self.address)
419 self.stream = zmqstream.ZMQStream(self.socket, self.ioloop)
419 self.stream = zmqstream.ZMQStream(self.socket, self.ioloop)
420 self.stream.on_recv(self._handle_recv)
420 self.stream.on_recv(self._handle_recv)
421 self._run_loop()
421 self._run_loop()
422 try:
422 try:
423 self.socket.close()
423 self.socket.close()
424 except:
424 except:
425 pass
425 pass
426
426
427 def stop(self):
427 def stop(self):
428 """Stop the channel's event loop and join its thread."""
428 """Stop the channel's event loop and join its thread."""
429 self.ioloop.stop()
429 self.ioloop.stop()
430 super(IOPubChannel, self).stop()
430 super(IOPubChannel, self).stop()
431
431
432 def call_handlers(self, msg):
432 def call_handlers(self, msg):
433 """This method is called in the ioloop thread when a message arrives.
433 """This method is called in the ioloop thread when a message arrives.
434
434
435 Subclasses should override this method to handle incoming messages.
435 Subclasses should override this method to handle incoming messages.
436 It is important to remember that this method is called in the thread
436 It is important to remember that this method is called in the thread
437 so that some logic must be done to ensure that the application leve
437 so that some logic must be done to ensure that the application leve
438 handlers are called in the application thread.
438 handlers are called in the application thread.
439 """
439 """
440 raise NotImplementedError('call_handlers must be defined in a subclass.')
440 raise NotImplementedError('call_handlers must be defined in a subclass.')
441
441
442 def flush(self, timeout=1.0):
442 def flush(self, timeout=1.0):
443 """Immediately processes all pending messages on the iopub channel.
443 """Immediately processes all pending messages on the iopub channel.
444
444
445 Callers should use this method to ensure that :method:`call_handlers`
445 Callers should use this method to ensure that :method:`call_handlers`
446 has been called for all messages that have been received on the
446 has been called for all messages that have been received on the
447 0MQ SUB socket of this channel.
447 0MQ SUB socket of this channel.
448
448
449 This method is thread safe.
449 This method is thread safe.
450
450
451 Parameters
451 Parameters
452 ----------
452 ----------
453 timeout : float, optional
453 timeout : float, optional
454 The maximum amount of time to spend flushing, in seconds. The
454 The maximum amount of time to spend flushing, in seconds. The
455 default is one second.
455 default is one second.
456 """
456 """
457 # We do the IOLoop callback process twice to ensure that the IOLoop
457 # We do the IOLoop callback process twice to ensure that the IOLoop
458 # gets to perform at least one full poll.
458 # gets to perform at least one full poll.
459 stop_time = time.time() + timeout
459 stop_time = time.time() + timeout
460 for i in xrange(2):
460 for i in xrange(2):
461 self._flushed = False
461 self._flushed = False
462 self.ioloop.add_callback(self._flush)
462 self.ioloop.add_callback(self._flush)
463 while not self._flushed and time.time() < stop_time:
463 while not self._flushed and time.time() < stop_time:
464 time.sleep(0.01)
464 time.sleep(0.01)
465
465
466 def _flush(self):
466 def _flush(self):
467 """Callback for :method:`self.flush`."""
467 """Callback for :method:`self.flush`."""
468 self.stream.flush()
468 self.stream.flush()
469 self._flushed = True
469 self._flushed = True
470
470
471
471
472 class StdInChannel(ZMQSocketChannel):
472 class StdInChannel(ZMQSocketChannel):
473 """The stdin channel to handle raw_input requests that the kernel makes."""
473 """The stdin channel to handle raw_input requests that the kernel makes."""
474
474
475 msg_queue = None
475 msg_queue = None
476
476
477 def __init__(self, context, session, address):
477 def __init__(self, context, session, address):
478 super(StdInChannel, self).__init__(context, session, address)
478 super(StdInChannel, self).__init__(context, session, address)
479 self.ioloop = ioloop.IOLoop()
479 self.ioloop = ioloop.IOLoop()
480
480
481 def run(self):
481 def run(self):
482 """The thread's main activity. Call start() instead."""
482 """The thread's main activity. Call start() instead."""
483 self.socket = self.context.socket(zmq.DEALER)
483 self.socket = self.context.socket(zmq.DEALER)
484 self.socket.setsockopt(zmq.IDENTITY, self.session.bsession)
484 self.socket.setsockopt(zmq.IDENTITY, self.session.bsession)
485 self.socket.connect(self.address)
485 self.socket.connect(self.address)
486 self.stream = zmqstream.ZMQStream(self.socket, self.ioloop)
486 self.stream = zmqstream.ZMQStream(self.socket, self.ioloop)
487 self.stream.on_recv(self._handle_recv)
487 self.stream.on_recv(self._handle_recv)
488 self._run_loop()
488 self._run_loop()
489 try:
489 try:
490 self.socket.close()
490 self.socket.close()
491 except:
491 except:
492 pass
492 pass
493
493
494 def stop(self):
494 def stop(self):
495 """Stop the channel's event loop and join its thread."""
495 """Stop the channel's event loop and join its thread."""
496 self.ioloop.stop()
496 self.ioloop.stop()
497 super(StdInChannel, self).stop()
497 super(StdInChannel, self).stop()
498
498
499 def call_handlers(self, msg):
499 def call_handlers(self, msg):
500 """This method is called in the ioloop thread when a message arrives.
500 """This method is called in the ioloop thread when a message arrives.
501
501
502 Subclasses should override this method to handle incoming messages.
502 Subclasses should override this method to handle incoming messages.
503 It is important to remember that this method is called in the thread
503 It is important to remember that this method is called in the thread
504 so that some logic must be done to ensure that the application leve
504 so that some logic must be done to ensure that the application leve
505 handlers are called in the application thread.
505 handlers are called in the application thread.
506 """
506 """
507 raise NotImplementedError('call_handlers must be defined in a subclass.')
507 raise NotImplementedError('call_handlers must be defined in a subclass.')
508
508
509 def input(self, string):
509 def input(self, string):
510 """Send a string of raw input to the kernel."""
510 """Send a string of raw input to the kernel."""
511 content = dict(value=string)
511 content = dict(value=string)
512 msg = self.session.msg('input_reply', content)
512 msg = self.session.msg('input_reply', content)
513 self._queue_send(msg)
513 self._queue_send(msg)
514
514
515
515
516 class HBChannel(ZMQSocketChannel):
516 class HBChannel(ZMQSocketChannel):
517 """The heartbeat channel which monitors the kernel heartbeat.
517 """The heartbeat channel which monitors the kernel heartbeat.
518
518
519 Note that the heartbeat channel is paused by default. As long as you start
519 Note that the heartbeat channel is paused by default. As long as you start
520 this channel, the kernel manager will ensure that it is paused and un-paused
520 this channel, the kernel manager will ensure that it is paused and un-paused
521 as appropriate.
521 as appropriate.
522 """
522 """
523
523
524 time_to_dead = 3.0
524 time_to_dead = 3.0
525 socket = None
525 socket = None
526 poller = None
526 poller = None
527 _running = None
527 _running = None
528 _pause = None
528 _pause = None
529 _beating = None
529 _beating = None
530
530
531 def __init__(self, context, session, address):
531 def __init__(self, context, session, address):
532 super(HBChannel, self).__init__(context, session, address)
532 super(HBChannel, self).__init__(context, session, address)
533 self._running = False
533 self._running = False
534 self._pause =True
534 self._pause =True
535 self.poller = zmq.Poller()
535 self.poller = zmq.Poller()
536
536
537 def _create_socket(self):
537 def _create_socket(self):
538 if self.socket is not None:
538 if self.socket is not None:
539 # close previous socket, before opening a new one
539 # close previous socket, before opening a new one
540 self.poller.unregister(self.socket)
540 self.poller.unregister(self.socket)
541 self.socket.close()
541 self.socket.close()
542 self.socket = self.context.socket(zmq.REQ)
542 self.socket = self.context.socket(zmq.REQ)
543 self.socket.setsockopt(zmq.LINGER, 0)
543 self.socket.setsockopt(zmq.LINGER, 0)
544 self.socket.connect(self.address)
544 self.socket.connect(self.address)
545
545
546 self.poller.register(self.socket, zmq.POLLIN)
546 self.poller.register(self.socket, zmq.POLLIN)
547
547
548 def _poll(self, start_time):
548 def _poll(self, start_time):
549 """poll for heartbeat replies until we reach self.time_to_dead.
549 """poll for heartbeat replies until we reach self.time_to_dead.
550
550
551 Ignores interrupts, and returns the result of poll(), which
551 Ignores interrupts, and returns the result of poll(), which
552 will be an empty list if no messages arrived before the timeout,
552 will be an empty list if no messages arrived before the timeout,
553 or the event tuple if there is a message to receive.
553 or the event tuple if there is a message to receive.
554 """
554 """
555
555
556 until_dead = self.time_to_dead - (time.time() - start_time)
556 until_dead = self.time_to_dead - (time.time() - start_time)
557 # ensure poll at least once
557 # ensure poll at least once
558 until_dead = max(until_dead, 1e-3)
558 until_dead = max(until_dead, 1e-3)
559 events = []
559 events = []
560 while True:
560 while True:
561 try:
561 try:
562 events = self.poller.poll(1000 * until_dead)
562 events = self.poller.poll(1000 * until_dead)
563 except ZMQError as e:
563 except ZMQError as e:
564 if e.errno == errno.EINTR:
564 if e.errno == errno.EINTR:
565 # ignore interrupts during heartbeat
565 # ignore interrupts during heartbeat
566 # this may never actually happen
566 # this may never actually happen
567 until_dead = self.time_to_dead - (time.time() - start_time)
567 until_dead = self.time_to_dead - (time.time() - start_time)
568 until_dead = max(until_dead, 1e-3)
568 until_dead = max(until_dead, 1e-3)
569 pass
569 pass
570 else:
570 else:
571 raise
571 raise
572 except Exception:
572 except Exception:
573 if self._exiting:
573 if self._exiting:
574 break
574 break
575 else:
575 else:
576 raise
576 raise
577 else:
577 else:
578 break
578 break
579 return events
579 return events
580
580
581 def run(self):
581 def run(self):
582 """The thread's main activity. Call start() instead."""
582 """The thread's main activity. Call start() instead."""
583 self._create_socket()
583 self._create_socket()
584 self._running = True
584 self._running = True
585 self._beating = True
585 self._beating = True
586
586
587 while self._running:
587 while self._running:
588 if self._pause:
588 if self._pause:
589 # just sleep, and skip the rest of the loop
589 # just sleep, and skip the rest of the loop
590 time.sleep(self.time_to_dead)
590 time.sleep(self.time_to_dead)
591 continue
591 continue
592
592
593 since_last_heartbeat = 0.0
593 since_last_heartbeat = 0.0
594 # io.rprint('Ping from HB channel') # dbg
594 # io.rprint('Ping from HB channel') # dbg
595 # no need to catch EFSM here, because the previous event was
595 # no need to catch EFSM here, because the previous event was
596 # either a recv or connect, which cannot be followed by EFSM
596 # either a recv or connect, which cannot be followed by EFSM
597 self.socket.send(b'ping')
597 self.socket.send(b'ping')
598 request_time = time.time()
598 request_time = time.time()
599 ready = self._poll(request_time)
599 ready = self._poll(request_time)
600 if ready:
600 if ready:
601 self._beating = True
601 self._beating = True
602 # the poll above guarantees we have something to recv
602 # the poll above guarantees we have something to recv
603 self.socket.recv()
603 self.socket.recv()
604 # sleep the remainder of the cycle
604 # sleep the remainder of the cycle
605 remainder = self.time_to_dead - (time.time() - request_time)
605 remainder = self.time_to_dead - (time.time() - request_time)
606 if remainder > 0:
606 if remainder > 0:
607 time.sleep(remainder)
607 time.sleep(remainder)
608 continue
608 continue
609 else:
609 else:
610 # nothing was received within the time limit, signal heart failure
610 # nothing was received within the time limit, signal heart failure
611 self._beating = False
611 self._beating = False
612 since_last_heartbeat = time.time() - request_time
612 since_last_heartbeat = time.time() - request_time
613 self.call_handlers(since_last_heartbeat)
613 self.call_handlers(since_last_heartbeat)
614 # and close/reopen the socket, because the REQ/REP cycle has been broken
614 # and close/reopen the socket, because the REQ/REP cycle has been broken
615 self._create_socket()
615 self._create_socket()
616 continue
616 continue
617 try:
617 try:
618 self.socket.close()
618 self.socket.close()
619 except:
619 except:
620 pass
620 pass
621
621
622 def pause(self):
622 def pause(self):
623 """Pause the heartbeat."""
623 """Pause the heartbeat."""
624 self._pause = True
624 self._pause = True
625
625
626 def unpause(self):
626 def unpause(self):
627 """Unpause the heartbeat."""
627 """Unpause the heartbeat."""
628 self._pause = False
628 self._pause = False
629
629
630 def is_beating(self):
630 def is_beating(self):
631 """Is the heartbeat running and responsive (and not paused)."""
631 """Is the heartbeat running and responsive (and not paused)."""
632 if self.is_alive() and not self._pause and self._beating:
632 if self.is_alive() and not self._pause and self._beating:
633 return True
633 return True
634 else:
634 else:
635 return False
635 return False
636
636
637 def stop(self):
637 def stop(self):
638 """Stop the channel's event loop and join its thread."""
638 """Stop the channel's event loop and join its thread."""
639 self._running = False
639 self._running = False
640 super(HBChannel, self).stop()
640 super(HBChannel, self).stop()
641
641
642 def call_handlers(self, since_last_heartbeat):
642 def call_handlers(self, since_last_heartbeat):
643 """This method is called in the ioloop thread when a message arrives.
643 """This method is called in the ioloop thread when a message arrives.
644
644
645 Subclasses should override this method to handle incoming messages.
645 Subclasses should override this method to handle incoming messages.
646 It is important to remember that this method is called in the thread
646 It is important to remember that this method is called in the thread
647 so that some logic must be done to ensure that the application level
647 so that some logic must be done to ensure that the application level
648 handlers are called in the application thread.
648 handlers are called in the application thread.
649 """
649 """
650 raise NotImplementedError('call_handlers must be defined in a subclass.')
650 raise NotImplementedError('call_handlers must be defined in a subclass.')
651
651
652
652
653 #-----------------------------------------------------------------------------
653 #-----------------------------------------------------------------------------
654 # Main kernel manager class
654 # Main kernel manager class
655 #-----------------------------------------------------------------------------
655 #-----------------------------------------------------------------------------
656
656
657 class KernelManager(Configurable):
657 class KernelManager(Configurable):
658 """Manages a single kernel on this host along with its channels.
658 """Manages a single kernel on this host along with its channels.
659
659
660 There are four channels associated with each kernel:
660 There are four channels associated with each kernel:
661
661
662 * shell: for request/reply calls to the kernel.
662 * shell: for request/reply calls to the kernel.
663 * iopub: for the kernel to publish results to frontends.
663 * iopub: for the kernel to publish results to frontends.
664 * hb: for monitoring the kernel's heartbeat.
664 * hb: for monitoring the kernel's heartbeat.
665 * stdin: for frontends to reply to raw_input calls in the kernel.
665 * stdin: for frontends to reply to raw_input calls in the kernel.
666
666
667 The usage of the channels that this class manages is optional. It is
667 The usage of the channels that this class manages is optional. It is
668 entirely possible to connect to the kernels directly using ZeroMQ
668 entirely possible to connect to the kernels directly using ZeroMQ
669 sockets. These channels are useful primarily for talking to a kernel
669 sockets. These channels are useful primarily for talking to a kernel
670 whose :class:`KernelManager` is in the same process.
670 whose :class:`KernelManager` is in the same process.
671
671
672 This version manages kernels started using Popen.
672 This version manages kernels started using Popen.
673 """
673 """
674 # The PyZMQ Context to use for communication with the kernel.
674 # The PyZMQ Context to use for communication with the kernel.
675 context = Instance(zmq.Context)
675 context = Instance(zmq.Context)
676 def _context_default(self):
676 def _context_default(self):
677 return zmq.Context.instance()
677 return zmq.Context.instance()
678
678
679 # The Session to use for communication with the kernel.
679 # The Session to use for communication with the kernel.
680 session = Instance(Session)
680 session = Instance(Session)
681 def _session_default(self):
681 def _session_default(self):
682 return Session(config=self.config)
682 return Session(config=self.config)
683
683
684 # The kernel process with which the KernelManager is communicating.
684 # The kernel process with which the KernelManager is communicating.
685 # generally a Popen instance
685 # generally a Popen instance
686 kernel = Any()
686 kernel = Any()
687
687
688 kernel_cmd = List(Unicode, config=True,
688 kernel_cmd = List(Unicode, config=True,
689 help="""The Popen Command to launch the kernel.
689 help="""The Popen Command to launch the kernel.
690 Override this if you have a custom
690 Override this if you have a custom
691 """
691 """
692 )
692 )
693
693
694 def _kernel_cmd_changed(self, name, old, new):
694 def _kernel_cmd_changed(self, name, old, new):
695 self.ipython_kernel = False
695 self.ipython_kernel = False
696
696
697 ipython_kernel = Bool(True)
697 ipython_kernel = Bool(True)
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
710
711 def _ip_default(self):
711 def _ip_default(self):
712 if self.transport == 'ipc':
712 if self.transport == 'ipc':
713 if self.connection_file:
713 if self.connection_file:
714 return os.path.splitext(self.connection_file)[0] + '-ipc'
714 return os.path.splitext(self.connection_file)[0] + '-ipc'
715 else:
715 else:
716 return 'kernel-ipc'
716 return 'kernel-ipc'
717 else:
717 else:
718 return LOCALHOST
718 return LOCALHOST
719
719
720 def _ip_changed(self, name, old, new):
720 def _ip_changed(self, name, old, new):
721 if new == '*':
721 if new == '*':
722 self.ip = '0.0.0.0'
722 self.ip = '0.0.0.0'
723
723
724 shell_port = Integer(0)
724 shell_port = Integer(0)
725 iopub_port = Integer(0)
725 iopub_port = Integer(0)
726 stdin_port = Integer(0)
726 stdin_port = Integer(0)
727 hb_port = Integer(0)
727 hb_port = Integer(0)
728
728
729 # The classes to use for the various channels.
729 # The classes to use for the various channels.
730 shell_channel_class = Type(ShellChannel)
730 shell_channel_class = Type(ShellChannel)
731 iopub_channel_class = Type(IOPubChannel)
731 iopub_channel_class = Type(IOPubChannel)
732 stdin_channel_class = Type(StdInChannel)
732 stdin_channel_class = Type(StdInChannel)
733 hb_channel_class = Type(HBChannel)
733 hb_channel_class = Type(HBChannel)
734
734
735 # Protected traits.
735 # Protected traits.
736 _launch_args = Any
736 _launch_args = Any
737 _shell_channel = Any
737 _shell_channel = Any
738 _iopub_channel = Any
738 _iopub_channel = Any
739 _stdin_channel = Any
739 _stdin_channel = Any
740 _hb_channel = Any
740 _hb_channel = Any
741 _connection_file_written=Bool(False)
741 _connection_file_written=Bool(False)
742
742
743 autorestart = Bool(False, config=True,
743 autorestart = Bool(False, config=True,
744 help="""Should we autorestart the kernel if it dies."""
744 help="""Should we autorestart the kernel if it dies."""
745 )
745 )
746
746
747 def __del__(self):
747 def __del__(self):
748 self.cleanup_connection_file()
748 self.cleanup_connection_file()
749
749
750 #--------------------------------------------------------------------------
750 #--------------------------------------------------------------------------
751 # Channel management methods:
751 # Channel management methods:
752 #--------------------------------------------------------------------------
752 #--------------------------------------------------------------------------
753
753
754 def start_channels(self, shell=True, iopub=True, stdin=True, hb=True):
754 def start_channels(self, shell=True, iopub=True, stdin=True, hb=True):
755 """Starts the channels for this kernel.
755 """Starts the channels for this kernel.
756
756
757 This will create the channels if they do not exist and then start
757 This will create the channels if they do not exist and then start
758 them (their activity runs in a thread). If port numbers of 0 are
758 them (their activity runs in a thread). If port numbers of 0 are
759 being used (random ports) then you must first call
759 being used (random ports) then you must first call
760 :method:`start_kernel`. If the channels have been stopped and you
760 :method:`start_kernel`. If the channels have been stopped and you
761 call this, :class:`RuntimeError` will be raised.
761 call this, :class:`RuntimeError` will be raised.
762 """
762 """
763 if shell:
763 if shell:
764 self.shell_channel.start()
764 self.shell_channel.start()
765 if iopub:
765 if iopub:
766 self.iopub_channel.start()
766 self.iopub_channel.start()
767 if stdin:
767 if stdin:
768 self.stdin_channel.start()
768 self.stdin_channel.start()
769 self.shell_channel.allow_stdin = True
769 self.shell_channel.allow_stdin = True
770 else:
770 else:
771 self.shell_channel.allow_stdin = False
771 self.shell_channel.allow_stdin = False
772 if hb:
772 if hb:
773 self.hb_channel.start()
773 self.hb_channel.start()
774
774
775 def stop_channels(self):
775 def stop_channels(self):
776 """Stops all the running channels for this kernel.
776 """Stops all the running channels for this kernel.
777
777
778 This stops their event loops and joins their threads.
778 This stops their event loops and joins their threads.
779 """
779 """
780 if self.shell_channel.is_alive():
780 if self.shell_channel.is_alive():
781 self.shell_channel.stop()
781 self.shell_channel.stop()
782 if self.iopub_channel.is_alive():
782 if self.iopub_channel.is_alive():
783 self.iopub_channel.stop()
783 self.iopub_channel.stop()
784 if self.stdin_channel.is_alive():
784 if self.stdin_channel.is_alive():
785 self.stdin_channel.stop()
785 self.stdin_channel.stop()
786 if self.hb_channel.is_alive():
786 if self.hb_channel.is_alive():
787 self.hb_channel.stop()
787 self.hb_channel.stop()
788
788
789 @property
789 @property
790 def channels_running(self):
790 def channels_running(self):
791 """Are any of the channels created and running?"""
791 """Are any of the channels created and running?"""
792 return (self.shell_channel.is_alive() or self.iopub_channel.is_alive() or
792 return (self.shell_channel.is_alive() or self.iopub_channel.is_alive() or
793 self.stdin_channel.is_alive() or self.hb_channel.is_alive())
793 self.stdin_channel.is_alive() or self.hb_channel.is_alive())
794
794
795 def _make_url(self, port):
795 def _make_url(self, port):
796 """Make a zmq url with a port.
796 """Make a zmq url with a port.
797
797
798 There are two cases that this handles:
798 There are two cases that this handles:
799
799
800 * tcp: tcp://ip:port
800 * tcp: tcp://ip:port
801 * ipc: ipc://ip-port
801 * ipc: ipc://ip-port
802 """
802 """
803 if self.transport == 'tcp':
803 if self.transport == 'tcp':
804 return "tcp://%s:%i" % (self.ip, port)
804 return "tcp://%s:%i" % (self.ip, port)
805 else:
805 else:
806 return "%s://%s-%s" % (self.transport, self.ip, port)
806 return "%s://%s-%s" % (self.transport, self.ip, port)
807
807
808 @property
808 @property
809 def shell_channel(self):
809 def shell_channel(self):
810 """Get the shell channel object for this kernel."""
810 """Get the shell channel object for this kernel."""
811 if self._shell_channel is None:
811 if self._shell_channel is None:
812 self._shell_channel = self.shell_channel_class(
812 self._shell_channel = self.shell_channel_class(
813 self.context, self.session, self._make_url(self.shell_port)
813 self.context, self.session, self._make_url(self.shell_port)
814 )
814 )
815 return self._shell_channel
815 return self._shell_channel
816
816
817 @property
817 @property
818 def iopub_channel(self):
818 def iopub_channel(self):
819 """Get the iopub channel object for this kernel."""
819 """Get the iopub channel object for this kernel."""
820 if self._iopub_channel is None:
820 if self._iopub_channel is None:
821 self._iopub_channel = self.iopub_channel_class(
821 self._iopub_channel = self.iopub_channel_class(
822 self.context, self.session, self._make_url(self.iopub_port)
822 self.context, self.session, self._make_url(self.iopub_port)
823 )
823 )
824 return self._iopub_channel
824 return self._iopub_channel
825
825
826 @property
826 @property
827 def stdin_channel(self):
827 def stdin_channel(self):
828 """Get the stdin channel object for this kernel."""
828 """Get the stdin channel object for this kernel."""
829 if self._stdin_channel is None:
829 if self._stdin_channel is None:
830 self._stdin_channel = self.stdin_channel_class(
830 self._stdin_channel = self.stdin_channel_class(
831 self.context, self.session, self._make_url(self.stdin_port)
831 self.context, self.session, self._make_url(self.stdin_port)
832 )
832 )
833 return self._stdin_channel
833 return self._stdin_channel
834
834
835 @property
835 @property
836 def hb_channel(self):
836 def hb_channel(self):
837 """Get the hb channel object for this kernel."""
837 """Get the hb channel object for this kernel."""
838 if self._hb_channel is None:
838 if self._hb_channel is None:
839 self._hb_channel = self.hb_channel_class(
839 self._hb_channel = self.hb_channel_class(
840 self.context, self.session, self._make_url(self.hb_port)
840 self.context, self.session, self._make_url(self.hb_port)
841 )
841 )
842 return self._hb_channel
842 return self._hb_channel
843
843
844 #--------------------------------------------------------------------------
844 #--------------------------------------------------------------------------
845 # Connection and ipc file management
845 # Connection and ipc file management
846 #--------------------------------------------------------------------------
846 #--------------------------------------------------------------------------
847
847
848 def cleanup_connection_file(self):
848 def cleanup_connection_file(self):
849 """Cleanup connection file *if we wrote it*
849 """Cleanup connection file *if we wrote it*
850
850
851 Will not raise if the connection file was already removed somehow.
851 Will not raise if the connection file was already removed somehow.
852 """
852 """
853 if self._connection_file_written:
853 if self._connection_file_written:
854 # cleanup connection files on full shutdown of kernel we started
854 # cleanup connection files on full shutdown of kernel we started
855 self._connection_file_written = False
855 self._connection_file_written = False
856 try:
856 try:
857 os.remove(self.connection_file)
857 os.remove(self.connection_file)
858 except (IOError, OSError, AttributeError):
858 except (IOError, OSError, AttributeError):
859 pass
859 pass
860
860
861 def cleanup_ipc_files(self):
861 def cleanup_ipc_files(self):
862 """Cleanup ipc files if we wrote them."""
862 """Cleanup ipc files if we wrote them."""
863 if self.transport != 'ipc':
863 if self.transport != 'ipc':
864 return
864 return
865 for port in (self.shell_port, self.iopub_port, self.stdin_port, self.hb_port):
865 for port in (self.shell_port, self.iopub_port, self.stdin_port, self.hb_port):
866 ipcfile = "%s-%i" % (self.ip, port)
866 ipcfile = "%s-%i" % (self.ip, port)
867 try:
867 try:
868 os.remove(ipcfile)
868 os.remove(ipcfile)
869 except (IOError, OSError):
869 except (IOError, OSError):
870 pass
870 pass
871
871
872 def load_connection_file(self):
872 def load_connection_file(self):
873 """Load connection info from JSON dict in self.connection_file."""
873 """Load connection info from JSON dict in self.connection_file."""
874 with open(self.connection_file) as f:
874 with open(self.connection_file) as f:
875 cfg = json.loads(f.read())
875 cfg = json.loads(f.read())
876
876
877 from pprint import pprint
877 from pprint import pprint
878 pprint(cfg)
878 pprint(cfg)
879 self.transport = cfg.get('transport', 'tcp')
879 self.transport = cfg.get('transport', 'tcp')
880 self.ip = cfg['ip']
880 self.ip = cfg['ip']
881 self.shell_port = cfg['shell_port']
881 self.shell_port = cfg['shell_port']
882 self.stdin_port = cfg['stdin_port']
882 self.stdin_port = cfg['stdin_port']
883 self.iopub_port = cfg['iopub_port']
883 self.iopub_port = cfg['iopub_port']
884 self.hb_port = cfg['hb_port']
884 self.hb_port = cfg['hb_port']
885 self.session.key = str_to_bytes(cfg['key'])
885 self.session.key = str_to_bytes(cfg['key'])
886
886
887 def write_connection_file(self):
887 def write_connection_file(self):
888 """Write connection info to JSON dict in self.connection_file."""
888 """Write connection info to JSON dict in self.connection_file."""
889 if self._connection_file_written:
889 if self._connection_file_written:
890 return
890 return
891 self.connection_file,cfg = write_connection_file(self.connection_file,
891 self.connection_file,cfg = write_connection_file(self.connection_file,
892 transport=self.transport, ip=self.ip, key=self.session.key,
892 transport=self.transport, ip=self.ip, key=self.session.key,
893 stdin_port=self.stdin_port, iopub_port=self.iopub_port,
893 stdin_port=self.stdin_port, iopub_port=self.iopub_port,
894 shell_port=self.shell_port, hb_port=self.hb_port)
894 shell_port=self.shell_port, hb_port=self.hb_port)
895 # write_connection_file also sets default ports:
895 # write_connection_file also sets default ports:
896 self.shell_port = cfg['shell_port']
896 self.shell_port = cfg['shell_port']
897 self.stdin_port = cfg['stdin_port']
897 self.stdin_port = cfg['stdin_port']
898 self.iopub_port = cfg['iopub_port']
898 self.iopub_port = cfg['iopub_port']
899 self.hb_port = cfg['hb_port']
899 self.hb_port = cfg['hb_port']
900
900
901 self._connection_file_written = True
901 self._connection_file_written = True
902
902
903 #--------------------------------------------------------------------------
903 #--------------------------------------------------------------------------
904 # Kernel restarter
904 # Kernel restarter
905 #--------------------------------------------------------------------------
905 #--------------------------------------------------------------------------
906
906
907 def start_restarter(self):
907 def start_restarter(self):
908 pass
908 pass
909
909
910 def stop_restarter(self):
910 def stop_restarter(self):
911 pass
911 pass
912
912
913 #--------------------------------------------------------------------------
913 #--------------------------------------------------------------------------
914 # Kernel management
914 # Kernel management
915 #--------------------------------------------------------------------------
915 #--------------------------------------------------------------------------
916
916
917 def format_kernel_cmd(self, **kw):
917 def format_kernel_cmd(self, **kw):
918 """format templated args (e.g. {connection_file})"""
918 """format templated args (e.g. {connection_file})"""
919 if self.kernel_cmd:
919 if self.kernel_cmd:
920 cmd = self.kernel_cmd
920 cmd = self.kernel_cmd
921 else:
921 else:
922 cmd = make_ipkernel_cmd(
922 cmd = make_ipkernel_cmd(
923 'from IPython.kernel.zmq.kernelapp import main; main()',
923 'from IPython.kernel.zmq.kernelapp import main; main()',
924 **kw
924 **kw
925 )
925 )
926 ns = dict(connection_file=self.connection_file)
926 ns = dict(connection_file=self.connection_file)
927 ns.update(self._launch_args)
927 ns.update(self._launch_args)
928 return [ c.format(**ns) for c in cmd ]
928 return [ c.format(**ns) for c in cmd ]
929
929
930 def _launch_kernel(self, kernel_cmd, **kw):
930 def _launch_kernel(self, kernel_cmd, **kw):
931 """actually launch the kernel
931 """actually launch the kernel
932
932
933 override in a subclass to launch kernel subprocesses differently
933 override in a subclass to launch kernel subprocesses differently
934 """
934 """
935 return launch_kernel(kernel_cmd, **kw)
935 return launch_kernel(kernel_cmd, **kw)
936
936
937 def start_kernel(self, **kw):
937 def start_kernel(self, **kw):
938 """Starts a kernel on this host in a separate process.
938 """Starts a kernel on this host in a separate process.
939
939
940 If random ports (port=0) are being used, this method must be called
940 If random ports (port=0) are being used, this method must be called
941 before the channels are created.
941 before the channels are created.
942
942
943 Parameters:
943 Parameters:
944 -----------
944 -----------
945 **kw : optional
945 **kw : optional
946 keyword arguments that are passed down to build the kernel_cmd
946 keyword arguments that are passed down to build the kernel_cmd
947 and launching the kernel (e.g. Popen kwargs).
947 and launching the kernel (e.g. Popen kwargs).
948 """
948 """
949 if self.transport == 'tcp' and self.ip not in LOCAL_IPS:
949 if self.transport == 'tcp' and self.ip not in LOCAL_IPS:
950 raise RuntimeError("Can only launch a kernel on a local interface. "
950 raise RuntimeError("Can only launch a kernel on a local interface. "
951 "Make sure that the '*_address' attributes are "
951 "Make sure that the '*_address' attributes are "
952 "configured properly. "
952 "configured properly. "
953 "Currently valid addresses are: %s"%LOCAL_IPS
953 "Currently valid addresses are: %s"%LOCAL_IPS
954 )
954 )
955
955
956 # write connection file / get default ports
956 # write connection file / get default ports
957 self.write_connection_file()
957 self.write_connection_file()
958
958
959 # save kwargs for use in restart
959 # save kwargs for use in restart
960 self._launch_args = kw.copy()
960 self._launch_args = kw.copy()
961 # build the Popen cmd
961 # build the Popen cmd
962 kernel_cmd = self.format_kernel_cmd(**kw)
962 kernel_cmd = self.format_kernel_cmd(**kw)
963 # launch the kernel subprocess
963 # launch the kernel subprocess
964 self.kernel = self._launch_kernel(kernel_cmd,
964 self.kernel = self._launch_kernel(kernel_cmd,
965 ipython_kernel=self.ipython_kernel,
965 ipython_kernel=self.ipython_kernel,
966 **kw)
966 **kw)
967 self.start_restarter()
967 self.start_restarter()
968
968
969 def shutdown_kernel(self, now=False, restart=False):
969 def shutdown_kernel(self, now=False, restart=False):
970 """Attempts to the stop the kernel process cleanly.
970 """Attempts to the stop the kernel process cleanly.
971
971
972 This attempts to shutdown the kernels cleanly by:
972 This attempts to shutdown the kernels cleanly by:
973
973
974 1. Sending it a shutdown message over the shell channel.
974 1. Sending it a shutdown message over the shell channel.
975 2. If that fails, the kernel is shutdown forcibly by sending it
975 2. If that fails, the kernel is shutdown forcibly by sending it
976 a signal.
976 a signal.
977
977
978 Parameters:
978 Parameters:
979 -----------
979 -----------
980 now : bool
980 now : bool
981 Should the kernel be forcible killed *now*. This skips the
981 Should the kernel be forcible killed *now*. This skips the
982 first, nice shutdown attempt.
982 first, nice shutdown attempt.
983 restart: bool
983 restart: bool
984 Will this kernel be restarted after it is shutdown. When this
984 Will this kernel be restarted after it is shutdown. When this
985 is True, connection files will not be cleaned up.
985 is True, connection files will not be cleaned up.
986 """
986 """
987
987
988 # Pause the heart beat channel if it exists.
988 # Pause the heart beat channel if it exists.
989 if self._hb_channel is not None:
989 if self._hb_channel is not None:
990 self._hb_channel.pause()
990 self._hb_channel.pause()
991
991
992 # Stop monitoring for restarting while we shutdown.
992 # Stop monitoring for restarting while we shutdown.
993 self.stop_restarter()
993 self.stop_restarter()
994
994
995 # FIXME: Shutdown does not work on Windows due to ZMQ errors!
995 # FIXME: Shutdown does not work on Windows due to ZMQ errors!
996 if sys.platform == 'win32':
996 if sys.platform == 'win32':
997 self._kill_kernel()
997 self._kill_kernel()
998 return
998 return
999
999
1000 if now:
1000 if now:
1001 if self.has_kernel:
1001 if self.has_kernel:
1002 self._kill_kernel()
1002 self._kill_kernel()
1003 else:
1003 else:
1004 # Don't send any additional kernel kill messages immediately, to give
1004 # Don't send any additional kernel kill messages immediately, to give
1005 # the kernel a chance to properly execute shutdown actions. Wait for at
1005 # the kernel a chance to properly execute shutdown actions. Wait for at
1006 # most 1s, checking every 0.1s.
1006 # most 1s, checking every 0.1s.
1007 self.shell_channel.shutdown(restart=restart)
1007 self.shell_channel.shutdown(restart=restart)
1008 for i in range(10):
1008 for i in range(10):
1009 if self.is_alive():
1009 if self.is_alive():
1010 time.sleep(0.1)
1010 time.sleep(0.1)
1011 else:
1011 else:
1012 break
1012 break
1013 else:
1013 else:
1014 # OK, we've waited long enough.
1014 # OK, we've waited long enough.
1015 if self.has_kernel:
1015 if self.has_kernel:
1016 self._kill_kernel()
1016 self._kill_kernel()
1017
1017
1018 if not restart:
1018 if not restart:
1019 self.cleanup_connection_file()
1019 self.cleanup_connection_file()
1020 self.cleanup_ipc_files()
1020 self.cleanup_ipc_files()
1021 else:
1021 else:
1022 self.cleanup_ipc_files()
1022 self.cleanup_ipc_files()
1023
1023
1024 def restart_kernel(self, now=False, **kw):
1024 def restart_kernel(self, now=False, **kw):
1025 """Restarts a kernel with the arguments that were used to launch it.
1025 """Restarts a kernel with the arguments that were used to launch it.
1026
1026
1027 If the old kernel was launched with random ports, the same ports will be
1027 If the old kernel was launched with random ports, the same ports will be
1028 used for the new kernel. The same connection file is used again.
1028 used for the new kernel. The same connection file is used again.
1029
1029
1030 Parameters
1030 Parameters
1031 ----------
1031 ----------
1032 now : bool, optional
1032 now : bool, optional
1033 If True, the kernel is forcefully restarted *immediately*, without
1033 If True, the kernel is forcefully restarted *immediately*, without
1034 having a chance to do any cleanup action. Otherwise the kernel is
1034 having a chance to do any cleanup action. Otherwise the kernel is
1035 given 1s to clean up before a forceful restart is issued.
1035 given 1s to clean up before a forceful restart is issued.
1036
1036
1037 In all cases the kernel is restarted, the only difference is whether
1037 In all cases the kernel is restarted, the only difference is whether
1038 it is given a chance to perform a clean shutdown or not.
1038 it is given a chance to perform a clean shutdown or not.
1039
1039
1040 **kw : optional
1040 **kw : optional
1041 Any options specified here will overwrite those used to launch the
1041 Any options specified here will overwrite those used to launch the
1042 kernel.
1042 kernel.
1043 """
1043 """
1044 if self._launch_args is None:
1044 if self._launch_args is None:
1045 raise RuntimeError("Cannot restart the kernel. "
1045 raise RuntimeError("Cannot restart the kernel. "
1046 "No previous call to 'start_kernel'.")
1046 "No previous call to 'start_kernel'.")
1047 else:
1047 else:
1048 # Stop currently running kernel.
1048 # Stop currently running kernel.
1049 self.shutdown_kernel(now=now, restart=True)
1049 self.shutdown_kernel(now=now, restart=True)
1050
1050
1051 # Start new kernel.
1051 # Start new kernel.
1052 self._launch_args.update(kw)
1052 self._launch_args.update(kw)
1053 self.start_kernel(**self._launch_args)
1053 self.start_kernel(**self._launch_args)
1054
1054
1055 # FIXME: Messages get dropped in Windows due to probable ZMQ bug
1055 # FIXME: Messages get dropped in Windows due to probable ZMQ bug
1056 # unless there is some delay here.
1056 # unless there is some delay here.
1057 if sys.platform == 'win32':
1057 if sys.platform == 'win32':
1058 time.sleep(0.2)
1058 time.sleep(0.2)
1059
1059
1060 @property
1060 @property
1061 def has_kernel(self):
1061 def has_kernel(self):
1062 """Has a kernel been started that we are managing."""
1062 """Has a kernel been started that we are managing."""
1063 return self.kernel is not None
1063 return self.kernel is not None
1064
1064
1065 def _kill_kernel(self):
1065 def _kill_kernel(self):
1066 """Kill the running kernel.
1066 """Kill the running kernel.
1067
1067
1068 This is a private method, callers should use shutdown_kernel(now=True).
1068 This is a private method, callers should use shutdown_kernel(now=True).
1069 """
1069 """
1070 if self.has_kernel:
1070 if self.has_kernel:
1071
1071
1072 # Signal the kernel to terminate (sends SIGKILL on Unix and calls
1072 # Signal the kernel to terminate (sends SIGKILL on Unix and calls
1073 # TerminateProcess() on Win32).
1073 # TerminateProcess() on Win32).
1074 try:
1074 try:
1075 self.kernel.kill()
1075 self.kernel.kill()
1076 except OSError as e:
1076 except OSError as e:
1077 # In Windows, we will get an Access Denied error if the process
1077 # In Windows, we will get an Access Denied error if the process
1078 # has already terminated. Ignore it.
1078 # has already terminated. Ignore it.
1079 if sys.platform == 'win32':
1079 if sys.platform == 'win32':
1080 if e.winerror != 5:
1080 if e.winerror != 5:
1081 raise
1081 raise
1082 # On Unix, we may get an ESRCH error if the process has already
1082 # On Unix, we may get an ESRCH error if the process has already
1083 # terminated. Ignore it.
1083 # terminated. Ignore it.
1084 else:
1084 else:
1085 from errno import ESRCH
1085 from errno import ESRCH
1086 if e.errno != ESRCH:
1086 if e.errno != ESRCH:
1087 raise
1087 raise
1088
1088
1089 # Block until the kernel terminates.
1089 # Block until the kernel terminates.
1090 self.kernel.wait()
1090 self.kernel.wait()
1091 self.kernel = None
1091 self.kernel = None
1092 else:
1092 else:
1093 raise RuntimeError("Cannot kill kernel. No kernel is running!")
1093 raise RuntimeError("Cannot kill kernel. No kernel is running!")
1094
1094
1095 def interrupt_kernel(self):
1095 def interrupt_kernel(self):
1096 """Interrupts the kernel by sending it a signal.
1096 """Interrupts the kernel by sending it a signal.
1097
1097
1098 Unlike ``signal_kernel``, this operation is well supported on all
1098 Unlike ``signal_kernel``, this operation is well supported on all
1099 platforms.
1099 platforms.
1100 """
1100 """
1101 if self.has_kernel:
1101 if self.has_kernel:
1102 if sys.platform == 'win32':
1102 if sys.platform == 'win32':
1103 from .zmq.parentpoller import ParentPollerWindows as Poller
1103 from .zmq.parentpoller import ParentPollerWindows as Poller
1104 Poller.send_interrupt(self.kernel.win32_interrupt_event)
1104 Poller.send_interrupt(self.kernel.win32_interrupt_event)
1105 else:
1105 else:
1106 self.kernel.send_signal(signal.SIGINT)
1106 self.kernel.send_signal(signal.SIGINT)
1107 else:
1107 else:
1108 raise RuntimeError("Cannot interrupt kernel. No kernel is running!")
1108 raise RuntimeError("Cannot interrupt kernel. No kernel is running!")
1109
1109
1110 def signal_kernel(self, signum):
1110 def signal_kernel(self, signum):
1111 """Sends a signal to the kernel.
1111 """Sends a signal to the kernel.
1112
1112
1113 Note that since only SIGTERM is supported on Windows, this function is
1113 Note that since only SIGTERM is supported on Windows, this function is
1114 only useful on Unix systems.
1114 only useful on Unix systems.
1115 """
1115 """
1116 if self.has_kernel:
1116 if self.has_kernel:
1117 self.kernel.send_signal(signum)
1117 self.kernel.send_signal(signum)
1118 else:
1118 else:
1119 raise RuntimeError("Cannot signal kernel. No kernel is running!")
1119 raise RuntimeError("Cannot signal kernel. No kernel is running!")
1120
1120
1121 def is_alive(self):
1121 def is_alive(self):
1122 """Is the kernel process still running?"""
1122 """Is the kernel process still running?"""
1123 if self.has_kernel:
1123 if self.has_kernel:
1124 if self.kernel.poll() is None:
1124 if self.kernel.poll() is None:
1125 return True
1125 return True
1126 else:
1126 else:
1127 return False
1127 return False
1128 elif self._hb_channel is not None:
1128 elif self._hb_channel is not None:
1129 # We didn't start the kernel with this KernelManager so we
1129 # We didn't start the kernel with this KernelManager so we
1130 # use the heartbeat.
1130 # use the heartbeat.
1131 return self._hb_channel.is_beating()
1131 return self._hb_channel.is_beating()
1132 else:
1132 else:
1133 # no heartbeat and not local, we can't tell if it's running,
1133 # no heartbeat and not local, we can't tell if it's running,
1134 # so naively return True
1134 # so naively return True
1135 return True
1135 return True
1136
1136
1137
1137
1138 #-----------------------------------------------------------------------------
1138 #-----------------------------------------------------------------------------
1139 # ABC Registration
1139 # ABC Registration
1140 #-----------------------------------------------------------------------------
1140 #-----------------------------------------------------------------------------
1141
1141
1142 ShellChannelABC.register(ShellChannel)
1142 ShellChannelABC.register(ShellChannel)
1143 IOPubChannelABC.register(IOPubChannel)
1143 IOPubChannelABC.register(IOPubChannel)
1144 HBChannelABC.register(HBChannel)
1144 HBChannelABC.register(HBChannel)
1145 StdInChannelABC.register(StdInChannel)
1145 StdInChannelABC.register(StdInChannel)
1146 KernelManagerABC.register(KernelManager)
1146 KernelManagerABC.register(KernelManager)
@@ -1,274 +1,274 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) 2013 The IPython Development Team
9 # Copyright (C) 2013 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 from __future__ import absolute_import
19 from __future__ import absolute_import
20
20
21 import os
21 import os
22 import uuid
22 import uuid
23
23
24 import zmq
24 import zmq
25 from zmq.eventloop.zmqstream import ZMQStream
25 from zmq.eventloop.zmqstream import ZMQStream
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, Unicode, Any, DottedObjectName, Bool
30 Instance, Dict, Unicode, Any, DottedObjectName, Bool
31 )
31 )
32
32
33 #-----------------------------------------------------------------------------
33 #-----------------------------------------------------------------------------
34 # Classes
34 # Classes
35 #-----------------------------------------------------------------------------
35 #-----------------------------------------------------------------------------
36
36
37 class DuplicateKernelError(Exception):
37 class DuplicateKernelError(Exception):
38 pass
38 pass
39
39
40
40
41 class MultiKernelManager(LoggingConfigurable):
41 class MultiKernelManager(LoggingConfigurable):
42 """A class for managing multiple kernels."""
42 """A class for managing multiple kernels."""
43
43
44 kernel_manager_class = DottedObjectName(
44 kernel_manager_class = DottedObjectName(
45 "IPython.kernel.ioloopkernelmanager.IOLoopKernelManager", config=True,
45 "IPython.kernel.ioloop.IOLoopKernelManager", config=True,
46 help="""The kernel manager class. This is configurable to allow
46 help="""The kernel manager class. This is configurable to allow
47 subclassing of the KernelManager for customized behavior.
47 subclassing of the KernelManager for customized behavior.
48 """
48 """
49 )
49 )
50 def _kernel_manager_class_changed(self, name, old, new):
50 def _kernel_manager_class_changed(self, name, old, new):
51 self.kernel_manager_factory = import_item(new)
51 self.kernel_manager_factory = import_item(new)
52
52
53 kernel_manager_factory = Any(help="this is kernel_manager_class after import")
53 kernel_manager_factory = Any(help="this is kernel_manager_class after import")
54 def _kernel_manager_factory_default(self):
54 def _kernel_manager_factory_default(self):
55 return import_item(self.kernel_manager_class)
55 return import_item(self.kernel_manager_class)
56
56
57 context = Instance('zmq.Context')
57 context = Instance('zmq.Context')
58 def _context_default(self):
58 def _context_default(self):
59 return zmq.Context.instance()
59 return zmq.Context.instance()
60
60
61 connection_dir = Unicode('')
61 connection_dir = Unicode('')
62
62
63 _kernels = Dict()
63 _kernels = Dict()
64
64
65 def list_kernel_ids(self):
65 def list_kernel_ids(self):
66 """Return a list of the kernel ids of the active kernels."""
66 """Return a list of the kernel ids of the active kernels."""
67 # Create a copy so we can iterate over kernels in operations
67 # Create a copy so we can iterate over kernels in operations
68 # that delete keys.
68 # that delete keys.
69 return list(self._kernels.keys())
69 return list(self._kernels.keys())
70
70
71 def __len__(self):
71 def __len__(self):
72 """Return the number of running kernels."""
72 """Return the number of running kernels."""
73 return len(self.list_kernel_ids())
73 return len(self.list_kernel_ids())
74
74
75 def __contains__(self, kernel_id):
75 def __contains__(self, kernel_id):
76 return kernel_id in self._kernels
76 return kernel_id in self._kernels
77
77
78 def start_kernel(self, **kwargs):
78 def start_kernel(self, **kwargs):
79 """Start a new kernel.
79 """Start a new kernel.
80
80
81 The caller can pick a kernel_id by passing one in as a keyword arg,
81 The caller can pick a kernel_id by passing one in as a keyword arg,
82 otherwise one will be picked using a uuid.
82 otherwise one will be picked using a uuid.
83
83
84 To silence the kernel's stdout/stderr, call this using::
84 To silence the kernel's stdout/stderr, call this using::
85
85
86 km.start_kernel(stdout=PIPE, stderr=PIPE)
86 km.start_kernel(stdout=PIPE, stderr=PIPE)
87
87
88 """
88 """
89 kernel_id = kwargs.pop('kernel_id', unicode(uuid.uuid4()))
89 kernel_id = kwargs.pop('kernel_id', unicode(uuid.uuid4()))
90 if kernel_id in self:
90 if kernel_id in self:
91 raise DuplicateKernelError('Kernel already exists: %s' % kernel_id)
91 raise DuplicateKernelError('Kernel already exists: %s' % kernel_id)
92 # kernel_manager_factory is the constructor for the KernelManager
92 # kernel_manager_factory is the constructor for the KernelManager
93 # subclass we are using. It can be configured as any Configurable,
93 # subclass we are using. It can be configured as any Configurable,
94 # including things like its transport and ip.
94 # including things like its transport and ip.
95 km = self.kernel_manager_factory(connection_file=os.path.join(
95 km = self.kernel_manager_factory(connection_file=os.path.join(
96 self.connection_dir, "kernel-%s.json" % kernel_id),
96 self.connection_dir, "kernel-%s.json" % kernel_id),
97 config=self.config, autorestart=True, log=self.log
97 config=self.config, autorestart=True, log=self.log
98 )
98 )
99 km.start_kernel(**kwargs)
99 km.start_kernel(**kwargs)
100 # start just the shell channel, needed for graceful restart
100 # start just the shell channel, needed for graceful restart
101 km.start_channels(shell=True, iopub=False, stdin=False, hb=False)
101 km.start_channels(shell=True, iopub=False, stdin=False, hb=False)
102 self._kernels[kernel_id] = km
102 self._kernels[kernel_id] = km
103 return kernel_id
103 return kernel_id
104
104
105 def shutdown_kernel(self, kernel_id, now=False):
105 def shutdown_kernel(self, kernel_id, now=False):
106 """Shutdown a kernel by its kernel uuid.
106 """Shutdown a kernel by its kernel uuid.
107
107
108 Parameters
108 Parameters
109 ==========
109 ==========
110 kernel_id : uuid
110 kernel_id : uuid
111 The id of the kernel to shutdown.
111 The id of the kernel to shutdown.
112 now : bool
112 now : bool
113 Should the kernel be shutdown forcibly using a signal.
113 Should the kernel be shutdown forcibly using a signal.
114 """
114 """
115 k = self.get_kernel(kernel_id)
115 k = self.get_kernel(kernel_id)
116 k.shutdown_kernel(now=now)
116 k.shutdown_kernel(now=now)
117 k.shell_channel.stop()
117 k.shell_channel.stop()
118 del self._kernels[kernel_id]
118 del self._kernels[kernel_id]
119
119
120 def shutdown_all(self, now=False):
120 def shutdown_all(self, now=False):
121 """Shutdown all kernels."""
121 """Shutdown all kernels."""
122 for kid in self.list_kernel_ids():
122 for kid in self.list_kernel_ids():
123 self.shutdown_kernel(kid, now=now)
123 self.shutdown_kernel(kid, now=now)
124
124
125 def interrupt_kernel(self, kernel_id):
125 def interrupt_kernel(self, kernel_id):
126 """Interrupt (SIGINT) the kernel by its uuid.
126 """Interrupt (SIGINT) the kernel by its uuid.
127
127
128 Parameters
128 Parameters
129 ==========
129 ==========
130 kernel_id : uuid
130 kernel_id : uuid
131 The id of the kernel to interrupt.
131 The id of the kernel to interrupt.
132 """
132 """
133 return self.get_kernel(kernel_id).interrupt_kernel()
133 return self.get_kernel(kernel_id).interrupt_kernel()
134
134
135 def signal_kernel(self, kernel_id, signum):
135 def signal_kernel(self, kernel_id, signum):
136 """Sends a signal to the kernel by its uuid.
136 """Sends a signal to the kernel by its uuid.
137
137
138 Note that since only SIGTERM is supported on Windows, this function
138 Note that since only SIGTERM is supported on Windows, this function
139 is only useful on Unix systems.
139 is only useful on Unix systems.
140
140
141 Parameters
141 Parameters
142 ==========
142 ==========
143 kernel_id : uuid
143 kernel_id : uuid
144 The id of the kernel to signal.
144 The id of the kernel to signal.
145 """
145 """
146 return self.get_kernel(kernel_id).signal_kernel(signum)
146 return self.get_kernel(kernel_id).signal_kernel(signum)
147
147
148 def restart_kernel(self, kernel_id):
148 def restart_kernel(self, kernel_id):
149 """Restart a kernel by its uuid, keeping the same ports.
149 """Restart a kernel by its uuid, keeping the same ports.
150
150
151 Parameters
151 Parameters
152 ==========
152 ==========
153 kernel_id : uuid
153 kernel_id : uuid
154 The id of the kernel to interrupt.
154 The id of the kernel to interrupt.
155 """
155 """
156 km = self.get_kernel(kernel_id)
156 km = self.get_kernel(kernel_id)
157 km.restart_kernel()
157 km.restart_kernel()
158
158
159 def is_alive(self, kernel_id):
159 def is_alive(self, kernel_id):
160 """Is the kernel alive.
160 """Is the kernel alive.
161
161
162 This calls KernelManager.is_alive() which calls Popen.poll on the
162 This calls KernelManager.is_alive() which calls Popen.poll on the
163 actual kernel subprocess.
163 actual kernel subprocess.
164
164
165 Parameters
165 Parameters
166 ==========
166 ==========
167 kernel_id : uuid
167 kernel_id : uuid
168 The id of the kernel.
168 The id of the kernel.
169 """
169 """
170 return self.get_kernel(kernel_id).is_alive()
170 return self.get_kernel(kernel_id).is_alive()
171
171
172 def get_kernel(self, kernel_id):
172 def get_kernel(self, kernel_id):
173 """Get the single KernelManager object for a kernel by its uuid.
173 """Get the single KernelManager object for a kernel by its uuid.
174
174
175 Parameters
175 Parameters
176 ==========
176 ==========
177 kernel_id : uuid
177 kernel_id : uuid
178 The id of the kernel.
178 The id of the kernel.
179 """
179 """
180 km = self._kernels.get(kernel_id)
180 km = self._kernels.get(kernel_id)
181 if km is not None:
181 if km is not None:
182 return km
182 return km
183 else:
183 else:
184 raise KeyError("Kernel with id not found: %s" % kernel_id)
184 raise KeyError("Kernel with id not found: %s" % kernel_id)
185
185
186 def get_connection_info(self, kernel_id):
186 def get_connection_info(self, kernel_id):
187 """Return a dictionary of connection data for a kernel.
187 """Return a dictionary of connection data for a kernel.
188
188
189 Parameters
189 Parameters
190 ==========
190 ==========
191 kernel_id : uuid
191 kernel_id : uuid
192 The id of the kernel.
192 The id of the kernel.
193
193
194 Returns
194 Returns
195 =======
195 =======
196 connection_dict : dict
196 connection_dict : dict
197 A dict of the information needed to connect to a kernel.
197 A dict of the information needed to connect to a kernel.
198 This includes the ip address and the integer port
198 This includes the ip address and the integer port
199 numbers of the different channels (stdin_port, iopub_port,
199 numbers of the different channels (stdin_port, iopub_port,
200 shell_port, hb_port).
200 shell_port, hb_port).
201 """
201 """
202 km = self.get_kernel(kernel_id)
202 km = self.get_kernel(kernel_id)
203 return dict(transport=km.transport,
203 return dict(transport=km.transport,
204 ip=km.ip,
204 ip=km.ip,
205 shell_port=km.shell_port,
205 shell_port=km.shell_port,
206 iopub_port=km.iopub_port,
206 iopub_port=km.iopub_port,
207 stdin_port=km.stdin_port,
207 stdin_port=km.stdin_port,
208 hb_port=km.hb_port,
208 hb_port=km.hb_port,
209 )
209 )
210
210
211 def _make_url(self, transport, ip, port):
211 def _make_url(self, transport, ip, port):
212 """Make a ZeroMQ URL for a given transport, ip and port."""
212 """Make a ZeroMQ URL for a given transport, ip and port."""
213 if transport == 'tcp':
213 if transport == 'tcp':
214 return "tcp://%s:%i" % (ip, port)
214 return "tcp://%s:%i" % (ip, port)
215 else:
215 else:
216 return "%s://%s-%s" % (transport, ip, port)
216 return "%s://%s-%s" % (transport, ip, port)
217
217
218 def _create_connected_stream(self, kernel_id, socket_type, channel):
218 def _create_connected_stream(self, kernel_id, socket_type, channel):
219 """Create a connected ZMQStream for a kernel."""
219 """Create a connected ZMQStream for a kernel."""
220 cinfo = self.get_connection_info(kernel_id)
220 cinfo = self.get_connection_info(kernel_id)
221 url = self._make_url(cinfo['transport'], cinfo['ip'],
221 url = self._make_url(cinfo['transport'], cinfo['ip'],
222 cinfo['%s_port' % channel]
222 cinfo['%s_port' % channel]
223 )
223 )
224 sock = self.context.socket(socket_type)
224 sock = self.context.socket(socket_type)
225 self.log.info("Connecting to: %s" % url)
225 self.log.info("Connecting to: %s" % url)
226 sock.connect(url)
226 sock.connect(url)
227 return ZMQStream(sock)
227 return ZMQStream(sock)
228
228
229 def create_iopub_stream(self, kernel_id):
229 def create_iopub_stream(self, kernel_id):
230 """Return a ZMQStream object connected to the iopub channel.
230 """Return a ZMQStream object connected to the iopub channel.
231
231
232 Parameters
232 Parameters
233 ==========
233 ==========
234 kernel_id : uuid
234 kernel_id : uuid
235 The id of the kernel.
235 The id of the kernel.
236
236
237 Returns
237 Returns
238 =======
238 =======
239 stream : ZMQStream
239 stream : ZMQStream
240 """
240 """
241 iopub_stream = self._create_connected_stream(kernel_id, zmq.SUB, 'iopub')
241 iopub_stream = self._create_connected_stream(kernel_id, zmq.SUB, 'iopub')
242 iopub_stream.socket.setsockopt(zmq.SUBSCRIBE, b'')
242 iopub_stream.socket.setsockopt(zmq.SUBSCRIBE, b'')
243 return iopub_stream
243 return iopub_stream
244
244
245 def create_shell_stream(self, kernel_id):
245 def create_shell_stream(self, kernel_id):
246 """Return a ZMQStream object connected to the shell channel.
246 """Return a ZMQStream object connected to the shell 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 shell_stream = self._create_connected_stream(kernel_id, zmq.DEALER, 'shell')
257 shell_stream = self._create_connected_stream(kernel_id, zmq.DEALER, 'shell')
258 return shell_stream
258 return shell_stream
259
259
260 def create_hb_stream(self, kernel_id):
260 def create_hb_stream(self, kernel_id):
261 """Return a ZMQStream object connected to the hb channel.
261 """Return a ZMQStream object connected to the hb channel.
262
262
263 Parameters
263 Parameters
264 ==========
264 ==========
265 kernel_id : uuid
265 kernel_id : uuid
266 The id of the kernel.
266 The id of the kernel.
267
267
268 Returns
268 Returns
269 =======
269 =======
270 stream : ZMQStream
270 stream : ZMQStream
271 """
271 """
272 hb_stream = self._create_connected_stream(kernel_id, zmq.REQ, 'hb')
272 hb_stream = self._create_connected_stream(kernel_id, zmq.REQ, 'hb')
273 return hb_stream
273 return hb_stream
274
274
@@ -1,54 +1,54 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.kernel.kernelmanager import KernelManager
10 from IPython.kernel 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 c = Config()
15 c = Config()
16 # c.KernelManager.autorestart=False
16 # c.KernelManager.autorestart=False
17 km = KernelManager(config=c)
17 km = KernelManager(config=c)
18 return km
18 return km
19
19
20 def _get_ipc_km(self):
20 def _get_ipc_km(self):
21 c = Config()
21 c = Config()
22 c.KernelManager.transport = 'ipc'
22 c.KernelManager.transport = 'ipc'
23 c.KernelManager.ip = 'test'
23 c.KernelManager.ip = 'test'
24 # c.KernelManager.autorestart=False
24 # c.KernelManager.autorestart=False
25 km = KernelManager(config=c)
25 km = KernelManager(config=c)
26 return km
26 return km
27
27
28 def _run_lifecycle(self, km):
28 def _run_lifecycle(self, km):
29 km.start_kernel(stdout=PIPE, stderr=PIPE)
29 km.start_kernel(stdout=PIPE, stderr=PIPE)
30 self.assertTrue(km.is_alive())
30 self.assertTrue(km.is_alive())
31 km.start_channels(shell=True, iopub=False, stdin=False, hb=False)
31 km.start_channels(shell=True, iopub=False, stdin=False, hb=False)
32 km.restart_kernel()
32 km.restart_kernel()
33 self.assertTrue(km.is_alive())
33 self.assertTrue(km.is_alive())
34 # We need a delay here to give the restarting kernel a chance to
34 # We need a delay here to give the restarting kernel a chance to
35 # restart. Otherwise, the interrupt will kill it, causing the test
35 # restart. Otherwise, the interrupt will kill it, causing the test
36 # suite to hang. The reason it *hangs* is that the shutdown
36 # suite to hang. The reason it *hangs* is that the shutdown
37 # message for the restart sometimes hasn't been sent to the kernel.
37 # message for the restart sometimes hasn't been sent to the kernel.
38 # Because linger is oo on the shell channel, the context can't
38 # Because linger is oo on the shell channel, the context can't
39 # close until the message is sent to the kernel, which is not dead.
39 # close until the message is sent to the kernel, which is not dead.
40 time.sleep(1.0)
40 time.sleep(1.0)
41 km.interrupt_kernel()
41 km.interrupt_kernel()
42 self.assertTrue(isinstance(km, KernelManager))
42 self.assertTrue(isinstance(km, KernelManager))
43 km.shutdown_kernel()
43 km.shutdown_kernel()
44 km.shell_channel.stop()
44 km.shell_channel.stop()
45
45
46 def test_tcp_lifecycle(self):
46 def test_tcp_lifecycle(self):
47 km = self._get_tcp_km()
47 km = self._get_tcp_km()
48 self._run_lifecycle(km)
48 self._run_lifecycle(km)
49
49
50 @dec.skip_win32
50 @dec.skip_win32
51 def test_ipc_lifecycle(self):
51 def test_ipc_lifecycle(self):
52 km = self._get_ipc_km()
52 km = self._get_ipc_km()
53 self._run_lifecycle(km)
53 self._run_lifecycle(km)
54
54
@@ -1,501 +1,501 b''
1 """Test suite for our zeromq-based messaging specification.
1 """Test suite for our zeromq-based messaging specification.
2 """
2 """
3 #-----------------------------------------------------------------------------
3 #-----------------------------------------------------------------------------
4 # Copyright (C) 2010-2011 The IPython Development Team
4 # Copyright (C) 2010-2011 The IPython Development Team
5 #
5 #
6 # Distributed under the terms of the BSD License. The full license is in
6 # Distributed under the terms of the BSD License. The full license is in
7 # the file COPYING.txt, distributed as part of this software.
7 # the file COPYING.txt, distributed as part of this software.
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9
9
10 import re
10 import re
11 import sys
11 import sys
12 import time
12 import time
13 from subprocess import PIPE
13 from subprocess import PIPE
14 from Queue import Empty
14 from Queue import Empty
15
15
16 import nose.tools as nt
16 import nose.tools as nt
17
17
18 from ..blockingkernelmanager import BlockingKernelManager
18 from IPython.kernel.blocking import BlockingKernelManager
19
19
20
20
21 from IPython.testing import decorators as dec
21 from IPython.testing import decorators as dec
22 from IPython.utils import io
22 from IPython.utils import io
23 from IPython.utils.traitlets import (
23 from IPython.utils.traitlets import (
24 HasTraits, TraitError, Bool, Unicode, Dict, Integer, List, Enum, Any,
24 HasTraits, TraitError, Bool, Unicode, Dict, Integer, List, Enum, Any,
25 )
25 )
26
26
27 #-----------------------------------------------------------------------------
27 #-----------------------------------------------------------------------------
28 # Global setup and utilities
28 # Global setup and utilities
29 #-----------------------------------------------------------------------------
29 #-----------------------------------------------------------------------------
30
30
31 def setup():
31 def setup():
32 global KM
32 global KM
33 KM = BlockingKernelManager()
33 KM = BlockingKernelManager()
34
34
35 KM.start_kernel(stdout=PIPE, stderr=PIPE)
35 KM.start_kernel(stdout=PIPE, stderr=PIPE)
36 KM.start_channels()
36 KM.start_channels()
37
37
38 # wait for kernel to be ready
38 # wait for kernel to be ready
39 KM.shell_channel.execute("pass")
39 KM.shell_channel.execute("pass")
40 KM.shell_channel.get_msg(block=True, timeout=5)
40 KM.shell_channel.get_msg(block=True, timeout=5)
41 flush_channels()
41 flush_channels()
42
42
43
43
44 def teardown():
44 def teardown():
45 KM.stop_channels()
45 KM.stop_channels()
46 KM.shutdown_kernel()
46 KM.shutdown_kernel()
47
47
48
48
49 def flush_channels(km=None):
49 def flush_channels(km=None):
50 if km is None:
50 if km is None:
51 km = KM
51 km = KM
52 """flush any messages waiting on the queue"""
52 """flush any messages waiting on the queue"""
53 for channel in (km.shell_channel, km.iopub_channel):
53 for channel in (km.shell_channel, km.iopub_channel):
54 while True:
54 while True:
55 try:
55 try:
56 msg = channel.get_msg(block=True, timeout=0.1)
56 msg = channel.get_msg(block=True, timeout=0.1)
57 except Empty:
57 except Empty:
58 break
58 break
59 else:
59 else:
60 list(validate_message(msg))
60 list(validate_message(msg))
61
61
62
62
63 def execute(code='', km=None, **kwargs):
63 def execute(code='', km=None, **kwargs):
64 """wrapper for doing common steps for validating an execution request"""
64 """wrapper for doing common steps for validating an execution request"""
65 if km is None:
65 if km is None:
66 km = KM
66 km = KM
67 shell = km.shell_channel
67 shell = km.shell_channel
68 sub = km.iopub_channel
68 sub = km.iopub_channel
69
69
70 msg_id = shell.execute(code=code, **kwargs)
70 msg_id = shell.execute(code=code, **kwargs)
71 reply = shell.get_msg(timeout=2)
71 reply = shell.get_msg(timeout=2)
72 list(validate_message(reply, 'execute_reply', msg_id))
72 list(validate_message(reply, 'execute_reply', msg_id))
73 busy = sub.get_msg(timeout=2)
73 busy = sub.get_msg(timeout=2)
74 list(validate_message(busy, 'status', msg_id))
74 list(validate_message(busy, 'status', msg_id))
75 nt.assert_equal(busy['content']['execution_state'], 'busy')
75 nt.assert_equal(busy['content']['execution_state'], 'busy')
76
76
77 if not kwargs.get('silent'):
77 if not kwargs.get('silent'):
78 pyin = sub.get_msg(timeout=2)
78 pyin = sub.get_msg(timeout=2)
79 list(validate_message(pyin, 'pyin', msg_id))
79 list(validate_message(pyin, 'pyin', msg_id))
80 nt.assert_equal(pyin['content']['code'], code)
80 nt.assert_equal(pyin['content']['code'], code)
81
81
82 return msg_id, reply['content']
82 return msg_id, reply['content']
83
83
84 #-----------------------------------------------------------------------------
84 #-----------------------------------------------------------------------------
85 # MSG Spec References
85 # MSG Spec References
86 #-----------------------------------------------------------------------------
86 #-----------------------------------------------------------------------------
87
87
88
88
89 class Reference(HasTraits):
89 class Reference(HasTraits):
90
90
91 """
91 """
92 Base class for message spec specification testing.
92 Base class for message spec specification testing.
93
93
94 This class is the core of the message specification test. The
94 This class is the core of the message specification test. The
95 idea is that child classes implement trait attributes for each
95 idea is that child classes implement trait attributes for each
96 message keys, so that message keys can be tested against these
96 message keys, so that message keys can be tested against these
97 traits using :meth:`check` method.
97 traits using :meth:`check` method.
98
98
99 """
99 """
100
100
101 def check(self, d):
101 def check(self, d):
102 """validate a dict against our traits"""
102 """validate a dict against our traits"""
103 for key in self.trait_names():
103 for key in self.trait_names():
104 yield nt.assert_true(key in d, "Missing key: %r, should be found in %s" % (key, d))
104 yield nt.assert_true(key in d, "Missing key: %r, should be found in %s" % (key, d))
105 # FIXME: always allow None, probably not a good idea
105 # FIXME: always allow None, probably not a good idea
106 if d[key] is None:
106 if d[key] is None:
107 continue
107 continue
108 try:
108 try:
109 setattr(self, key, d[key])
109 setattr(self, key, d[key])
110 except TraitError as e:
110 except TraitError as e:
111 yield nt.assert_true(False, str(e))
111 yield nt.assert_true(False, str(e))
112
112
113
113
114 class RMessage(Reference):
114 class RMessage(Reference):
115 msg_id = Unicode()
115 msg_id = Unicode()
116 msg_type = Unicode()
116 msg_type = Unicode()
117 header = Dict()
117 header = Dict()
118 parent_header = Dict()
118 parent_header = Dict()
119 content = Dict()
119 content = Dict()
120
120
121 class RHeader(Reference):
121 class RHeader(Reference):
122 msg_id = Unicode()
122 msg_id = Unicode()
123 msg_type = Unicode()
123 msg_type = Unicode()
124 session = Unicode()
124 session = Unicode()
125 username = Unicode()
125 username = Unicode()
126
126
127 class RContent(Reference):
127 class RContent(Reference):
128 status = Enum((u'ok', u'error'))
128 status = Enum((u'ok', u'error'))
129
129
130
130
131 class ExecuteReply(Reference):
131 class ExecuteReply(Reference):
132 execution_count = Integer()
132 execution_count = Integer()
133 status = Enum((u'ok', u'error'))
133 status = Enum((u'ok', u'error'))
134
134
135 def check(self, d):
135 def check(self, d):
136 for tst in Reference.check(self, d):
136 for tst in Reference.check(self, d):
137 yield tst
137 yield tst
138 if d['status'] == 'ok':
138 if d['status'] == 'ok':
139 for tst in ExecuteReplyOkay().check(d):
139 for tst in ExecuteReplyOkay().check(d):
140 yield tst
140 yield tst
141 elif d['status'] == 'error':
141 elif d['status'] == 'error':
142 for tst in ExecuteReplyError().check(d):
142 for tst in ExecuteReplyError().check(d):
143 yield tst
143 yield tst
144
144
145
145
146 class ExecuteReplyOkay(Reference):
146 class ExecuteReplyOkay(Reference):
147 payload = List(Dict)
147 payload = List(Dict)
148 user_variables = Dict()
148 user_variables = Dict()
149 user_expressions = Dict()
149 user_expressions = Dict()
150
150
151
151
152 class ExecuteReplyError(Reference):
152 class ExecuteReplyError(Reference):
153 ename = Unicode()
153 ename = Unicode()
154 evalue = Unicode()
154 evalue = Unicode()
155 traceback = List(Unicode)
155 traceback = List(Unicode)
156
156
157
157
158 class OInfoReply(Reference):
158 class OInfoReply(Reference):
159 name = Unicode()
159 name = Unicode()
160 found = Bool()
160 found = Bool()
161 ismagic = Bool()
161 ismagic = Bool()
162 isalias = Bool()
162 isalias = Bool()
163 namespace = Enum((u'builtin', u'magics', u'alias', u'Interactive'))
163 namespace = Enum((u'builtin', u'magics', u'alias', u'Interactive'))
164 type_name = Unicode()
164 type_name = Unicode()
165 string_form = Unicode()
165 string_form = Unicode()
166 base_class = Unicode()
166 base_class = Unicode()
167 length = Integer()
167 length = Integer()
168 file = Unicode()
168 file = Unicode()
169 definition = Unicode()
169 definition = Unicode()
170 argspec = Dict()
170 argspec = Dict()
171 init_definition = Unicode()
171 init_definition = Unicode()
172 docstring = Unicode()
172 docstring = Unicode()
173 init_docstring = Unicode()
173 init_docstring = Unicode()
174 class_docstring = Unicode()
174 class_docstring = Unicode()
175 call_def = Unicode()
175 call_def = Unicode()
176 call_docstring = Unicode()
176 call_docstring = Unicode()
177 source = Unicode()
177 source = Unicode()
178
178
179 def check(self, d):
179 def check(self, d):
180 for tst in Reference.check(self, d):
180 for tst in Reference.check(self, d):
181 yield tst
181 yield tst
182 if d['argspec'] is not None:
182 if d['argspec'] is not None:
183 for tst in ArgSpec().check(d['argspec']):
183 for tst in ArgSpec().check(d['argspec']):
184 yield tst
184 yield tst
185
185
186
186
187 class ArgSpec(Reference):
187 class ArgSpec(Reference):
188 args = List(Unicode)
188 args = List(Unicode)
189 varargs = Unicode()
189 varargs = Unicode()
190 varkw = Unicode()
190 varkw = Unicode()
191 defaults = List()
191 defaults = List()
192
192
193
193
194 class Status(Reference):
194 class Status(Reference):
195 execution_state = Enum((u'busy', u'idle'))
195 execution_state = Enum((u'busy', u'idle'))
196
196
197
197
198 class CompleteReply(Reference):
198 class CompleteReply(Reference):
199 matches = List(Unicode)
199 matches = List(Unicode)
200
200
201
201
202 def Version(num, trait=Integer):
202 def Version(num, trait=Integer):
203 return List(trait, default_value=[0] * num, minlen=num, maxlen=num)
203 return List(trait, default_value=[0] * num, minlen=num, maxlen=num)
204
204
205
205
206 class KernelInfoReply(Reference):
206 class KernelInfoReply(Reference):
207
207
208 protocol_version = Version(2)
208 protocol_version = Version(2)
209 ipython_version = Version(4, Any)
209 ipython_version = Version(4, Any)
210 language_version = Version(3)
210 language_version = Version(3)
211 language = Unicode()
211 language = Unicode()
212
212
213 def _ipython_version_changed(self, name, old, new):
213 def _ipython_version_changed(self, name, old, new):
214 for v in new:
214 for v in new:
215 nt.assert_true(
215 nt.assert_true(
216 isinstance(v, int) or isinstance(v, basestring),
216 isinstance(v, int) or isinstance(v, basestring),
217 'expected int or string as version component, got {0!r}'
217 'expected int or string as version component, got {0!r}'
218 .format(v))
218 .format(v))
219
219
220
220
221 # IOPub messages
221 # IOPub messages
222
222
223 class PyIn(Reference):
223 class PyIn(Reference):
224 code = Unicode()
224 code = Unicode()
225 execution_count = Integer()
225 execution_count = Integer()
226
226
227
227
228 PyErr = ExecuteReplyError
228 PyErr = ExecuteReplyError
229
229
230
230
231 class Stream(Reference):
231 class Stream(Reference):
232 name = Enum((u'stdout', u'stderr'))
232 name = Enum((u'stdout', u'stderr'))
233 data = Unicode()
233 data = Unicode()
234
234
235
235
236 mime_pat = re.compile(r'\w+/\w+')
236 mime_pat = re.compile(r'\w+/\w+')
237
237
238 class DisplayData(Reference):
238 class DisplayData(Reference):
239 source = Unicode()
239 source = Unicode()
240 metadata = Dict()
240 metadata = Dict()
241 data = Dict()
241 data = Dict()
242 def _data_changed(self, name, old, new):
242 def _data_changed(self, name, old, new):
243 for k,v in new.iteritems():
243 for k,v in new.iteritems():
244 nt.assert_true(mime_pat.match(k))
244 nt.assert_true(mime_pat.match(k))
245 nt.assert_true(isinstance(v, basestring), "expected string data, got %r" % v)
245 nt.assert_true(isinstance(v, basestring), "expected string data, got %r" % v)
246
246
247
247
248 class PyOut(Reference):
248 class PyOut(Reference):
249 execution_count = Integer()
249 execution_count = Integer()
250 data = Dict()
250 data = Dict()
251 def _data_changed(self, name, old, new):
251 def _data_changed(self, name, old, new):
252 for k,v in new.iteritems():
252 for k,v in new.iteritems():
253 nt.assert_true(mime_pat.match(k))
253 nt.assert_true(mime_pat.match(k))
254 nt.assert_true(isinstance(v, basestring), "expected string data, got %r" % v)
254 nt.assert_true(isinstance(v, basestring), "expected string data, got %r" % v)
255
255
256
256
257 references = {
257 references = {
258 'execute_reply' : ExecuteReply(),
258 'execute_reply' : ExecuteReply(),
259 'object_info_reply' : OInfoReply(),
259 'object_info_reply' : OInfoReply(),
260 'status' : Status(),
260 'status' : Status(),
261 'complete_reply' : CompleteReply(),
261 'complete_reply' : CompleteReply(),
262 'kernel_info_reply': KernelInfoReply(),
262 'kernel_info_reply': KernelInfoReply(),
263 'pyin' : PyIn(),
263 'pyin' : PyIn(),
264 'pyout' : PyOut(),
264 'pyout' : PyOut(),
265 'pyerr' : PyErr(),
265 'pyerr' : PyErr(),
266 'stream' : Stream(),
266 'stream' : Stream(),
267 'display_data' : DisplayData(),
267 'display_data' : DisplayData(),
268 }
268 }
269 """
269 """
270 Specifications of `content` part of the reply messages.
270 Specifications of `content` part of the reply messages.
271 """
271 """
272
272
273
273
274 def validate_message(msg, msg_type=None, parent=None):
274 def validate_message(msg, msg_type=None, parent=None):
275 """validate a message
275 """validate a message
276
276
277 This is a generator, and must be iterated through to actually
277 This is a generator, and must be iterated through to actually
278 trigger each test.
278 trigger each test.
279
279
280 If msg_type and/or parent are given, the msg_type and/or parent msg_id
280 If msg_type and/or parent are given, the msg_type and/or parent msg_id
281 are compared with the given values.
281 are compared with the given values.
282 """
282 """
283 RMessage().check(msg)
283 RMessage().check(msg)
284 if msg_type:
284 if msg_type:
285 yield nt.assert_equal(msg['msg_type'], msg_type)
285 yield nt.assert_equal(msg['msg_type'], msg_type)
286 if parent:
286 if parent:
287 yield nt.assert_equal(msg['parent_header']['msg_id'], parent)
287 yield nt.assert_equal(msg['parent_header']['msg_id'], parent)
288 content = msg['content']
288 content = msg['content']
289 ref = references[msg['msg_type']]
289 ref = references[msg['msg_type']]
290 for tst in ref.check(content):
290 for tst in ref.check(content):
291 yield tst
291 yield tst
292
292
293
293
294 #-----------------------------------------------------------------------------
294 #-----------------------------------------------------------------------------
295 # Tests
295 # Tests
296 #-----------------------------------------------------------------------------
296 #-----------------------------------------------------------------------------
297
297
298 # Shell channel
298 # Shell channel
299
299
300 @dec.parametric
300 @dec.parametric
301 def test_execute():
301 def test_execute():
302 flush_channels()
302 flush_channels()
303
303
304 shell = KM.shell_channel
304 shell = KM.shell_channel
305 msg_id = shell.execute(code='x=1')
305 msg_id = shell.execute(code='x=1')
306 reply = shell.get_msg(timeout=2)
306 reply = shell.get_msg(timeout=2)
307 for tst in validate_message(reply, 'execute_reply', msg_id):
307 for tst in validate_message(reply, 'execute_reply', msg_id):
308 yield tst
308 yield tst
309
309
310
310
311 @dec.parametric
311 @dec.parametric
312 def test_execute_silent():
312 def test_execute_silent():
313 flush_channels()
313 flush_channels()
314 msg_id, reply = execute(code='x=1', silent=True)
314 msg_id, reply = execute(code='x=1', silent=True)
315
315
316 # flush status=idle
316 # flush status=idle
317 status = KM.iopub_channel.get_msg(timeout=2)
317 status = KM.iopub_channel.get_msg(timeout=2)
318 for tst in validate_message(status, 'status', msg_id):
318 for tst in validate_message(status, 'status', msg_id):
319 yield tst
319 yield tst
320 nt.assert_equal(status['content']['execution_state'], 'idle')
320 nt.assert_equal(status['content']['execution_state'], 'idle')
321
321
322 yield nt.assert_raises(Empty, KM.iopub_channel.get_msg, timeout=0.1)
322 yield nt.assert_raises(Empty, KM.iopub_channel.get_msg, timeout=0.1)
323 count = reply['execution_count']
323 count = reply['execution_count']
324
324
325 msg_id, reply = execute(code='x=2', silent=True)
325 msg_id, reply = execute(code='x=2', silent=True)
326
326
327 # flush status=idle
327 # flush status=idle
328 status = KM.iopub_channel.get_msg(timeout=2)
328 status = KM.iopub_channel.get_msg(timeout=2)
329 for tst in validate_message(status, 'status', msg_id):
329 for tst in validate_message(status, 'status', msg_id):
330 yield tst
330 yield tst
331 yield nt.assert_equal(status['content']['execution_state'], 'idle')
331 yield nt.assert_equal(status['content']['execution_state'], 'idle')
332
332
333 yield nt.assert_raises(Empty, KM.iopub_channel.get_msg, timeout=0.1)
333 yield nt.assert_raises(Empty, KM.iopub_channel.get_msg, timeout=0.1)
334 count_2 = reply['execution_count']
334 count_2 = reply['execution_count']
335 yield nt.assert_equal(count_2, count)
335 yield nt.assert_equal(count_2, count)
336
336
337
337
338 @dec.parametric
338 @dec.parametric
339 def test_execute_error():
339 def test_execute_error():
340 flush_channels()
340 flush_channels()
341
341
342 msg_id, reply = execute(code='1/0')
342 msg_id, reply = execute(code='1/0')
343 yield nt.assert_equal(reply['status'], 'error')
343 yield nt.assert_equal(reply['status'], 'error')
344 yield nt.assert_equal(reply['ename'], 'ZeroDivisionError')
344 yield nt.assert_equal(reply['ename'], 'ZeroDivisionError')
345
345
346 pyerr = KM.iopub_channel.get_msg(timeout=2)
346 pyerr = KM.iopub_channel.get_msg(timeout=2)
347 for tst in validate_message(pyerr, 'pyerr', msg_id):
347 for tst in validate_message(pyerr, 'pyerr', msg_id):
348 yield tst
348 yield tst
349
349
350
350
351 def test_execute_inc():
351 def test_execute_inc():
352 """execute request should increment execution_count"""
352 """execute request should increment execution_count"""
353 flush_channels()
353 flush_channels()
354
354
355 msg_id, reply = execute(code='x=1')
355 msg_id, reply = execute(code='x=1')
356 count = reply['execution_count']
356 count = reply['execution_count']
357
357
358 flush_channels()
358 flush_channels()
359
359
360 msg_id, reply = execute(code='x=2')
360 msg_id, reply = execute(code='x=2')
361 count_2 = reply['execution_count']
361 count_2 = reply['execution_count']
362 nt.assert_equal(count_2, count+1)
362 nt.assert_equal(count_2, count+1)
363
363
364
364
365 def test_user_variables():
365 def test_user_variables():
366 flush_channels()
366 flush_channels()
367
367
368 msg_id, reply = execute(code='x=1', user_variables=['x'])
368 msg_id, reply = execute(code='x=1', user_variables=['x'])
369 user_variables = reply['user_variables']
369 user_variables = reply['user_variables']
370 nt.assert_equal(user_variables, {u'x' : u'1'})
370 nt.assert_equal(user_variables, {u'x' : u'1'})
371
371
372
372
373 def test_user_expressions():
373 def test_user_expressions():
374 flush_channels()
374 flush_channels()
375
375
376 msg_id, reply = execute(code='x=1', user_expressions=dict(foo='x+1'))
376 msg_id, reply = execute(code='x=1', user_expressions=dict(foo='x+1'))
377 user_expressions = reply['user_expressions']
377 user_expressions = reply['user_expressions']
378 nt.assert_equal(user_expressions, {u'foo' : u'2'})
378 nt.assert_equal(user_expressions, {u'foo' : u'2'})
379
379
380
380
381 @dec.parametric
381 @dec.parametric
382 def test_oinfo():
382 def test_oinfo():
383 flush_channels()
383 flush_channels()
384
384
385 shell = KM.shell_channel
385 shell = KM.shell_channel
386
386
387 msg_id = shell.object_info('a')
387 msg_id = shell.object_info('a')
388 reply = shell.get_msg(timeout=2)
388 reply = shell.get_msg(timeout=2)
389 for tst in validate_message(reply, 'object_info_reply', msg_id):
389 for tst in validate_message(reply, 'object_info_reply', msg_id):
390 yield tst
390 yield tst
391
391
392
392
393 @dec.parametric
393 @dec.parametric
394 def test_oinfo_found():
394 def test_oinfo_found():
395 flush_channels()
395 flush_channels()
396
396
397 shell = KM.shell_channel
397 shell = KM.shell_channel
398
398
399 msg_id, reply = execute(code='a=5')
399 msg_id, reply = execute(code='a=5')
400
400
401 msg_id = shell.object_info('a')
401 msg_id = shell.object_info('a')
402 reply = shell.get_msg(timeout=2)
402 reply = shell.get_msg(timeout=2)
403 for tst in validate_message(reply, 'object_info_reply', msg_id):
403 for tst in validate_message(reply, 'object_info_reply', msg_id):
404 yield tst
404 yield tst
405 content = reply['content']
405 content = reply['content']
406 yield nt.assert_true(content['found'])
406 yield nt.assert_true(content['found'])
407 argspec = content['argspec']
407 argspec = content['argspec']
408 yield nt.assert_true(argspec is None, "didn't expect argspec dict, got %r" % argspec)
408 yield nt.assert_true(argspec is None, "didn't expect argspec dict, got %r" % argspec)
409
409
410
410
411 @dec.parametric
411 @dec.parametric
412 def test_oinfo_detail():
412 def test_oinfo_detail():
413 flush_channels()
413 flush_channels()
414
414
415 shell = KM.shell_channel
415 shell = KM.shell_channel
416
416
417 msg_id, reply = execute(code='ip=get_ipython()')
417 msg_id, reply = execute(code='ip=get_ipython()')
418
418
419 msg_id = shell.object_info('ip.object_inspect', detail_level=2)
419 msg_id = shell.object_info('ip.object_inspect', detail_level=2)
420 reply = shell.get_msg(timeout=2)
420 reply = shell.get_msg(timeout=2)
421 for tst in validate_message(reply, 'object_info_reply', msg_id):
421 for tst in validate_message(reply, 'object_info_reply', msg_id):
422 yield tst
422 yield tst
423 content = reply['content']
423 content = reply['content']
424 yield nt.assert_true(content['found'])
424 yield nt.assert_true(content['found'])
425 argspec = content['argspec']
425 argspec = content['argspec']
426 yield nt.assert_true(isinstance(argspec, dict), "expected non-empty argspec dict, got %r" % argspec)
426 yield nt.assert_true(isinstance(argspec, dict), "expected non-empty argspec dict, got %r" % argspec)
427 yield nt.assert_equal(argspec['defaults'], [0])
427 yield nt.assert_equal(argspec['defaults'], [0])
428
428
429
429
430 @dec.parametric
430 @dec.parametric
431 def test_oinfo_not_found():
431 def test_oinfo_not_found():
432 flush_channels()
432 flush_channels()
433
433
434 shell = KM.shell_channel
434 shell = KM.shell_channel
435
435
436 msg_id = shell.object_info('dne')
436 msg_id = shell.object_info('dne')
437 reply = shell.get_msg(timeout=2)
437 reply = shell.get_msg(timeout=2)
438 for tst in validate_message(reply, 'object_info_reply', msg_id):
438 for tst in validate_message(reply, 'object_info_reply', msg_id):
439 yield tst
439 yield tst
440 content = reply['content']
440 content = reply['content']
441 yield nt.assert_false(content['found'])
441 yield nt.assert_false(content['found'])
442
442
443
443
444 @dec.parametric
444 @dec.parametric
445 def test_complete():
445 def test_complete():
446 flush_channels()
446 flush_channels()
447
447
448 shell = KM.shell_channel
448 shell = KM.shell_channel
449
449
450 msg_id, reply = execute(code="alpha = albert = 5")
450 msg_id, reply = execute(code="alpha = albert = 5")
451
451
452 msg_id = shell.complete('al', 'al', 2)
452 msg_id = shell.complete('al', 'al', 2)
453 reply = shell.get_msg(timeout=2)
453 reply = shell.get_msg(timeout=2)
454 for tst in validate_message(reply, 'complete_reply', msg_id):
454 for tst in validate_message(reply, 'complete_reply', msg_id):
455 yield tst
455 yield tst
456 matches = reply['content']['matches']
456 matches = reply['content']['matches']
457 for name in ('alpha', 'albert'):
457 for name in ('alpha', 'albert'):
458 yield nt.assert_true(name in matches, "Missing match: %r" % name)
458 yield nt.assert_true(name in matches, "Missing match: %r" % name)
459
459
460
460
461 @dec.parametric
461 @dec.parametric
462 def test_kernel_info_request():
462 def test_kernel_info_request():
463 flush_channels()
463 flush_channels()
464
464
465 shell = KM.shell_channel
465 shell = KM.shell_channel
466
466
467 msg_id = shell.kernel_info()
467 msg_id = shell.kernel_info()
468 reply = shell.get_msg(timeout=2)
468 reply = shell.get_msg(timeout=2)
469 for tst in validate_message(reply, 'kernel_info_reply', msg_id):
469 for tst in validate_message(reply, 'kernel_info_reply', msg_id):
470 yield tst
470 yield tst
471
471
472
472
473 # IOPub channel
473 # IOPub channel
474
474
475
475
476 @dec.parametric
476 @dec.parametric
477 def test_stream():
477 def test_stream():
478 flush_channels()
478 flush_channels()
479
479
480 msg_id, reply = execute("print('hi')")
480 msg_id, reply = execute("print('hi')")
481
481
482 stdout = KM.iopub_channel.get_msg(timeout=2)
482 stdout = KM.iopub_channel.get_msg(timeout=2)
483 for tst in validate_message(stdout, 'stream', msg_id):
483 for tst in validate_message(stdout, 'stream', msg_id):
484 yield tst
484 yield tst
485 content = stdout['content']
485 content = stdout['content']
486 yield nt.assert_equal(content['name'], u'stdout')
486 yield nt.assert_equal(content['name'], u'stdout')
487 yield nt.assert_equal(content['data'], u'hi\n')
487 yield nt.assert_equal(content['data'], u'hi\n')
488
488
489
489
490 @dec.parametric
490 @dec.parametric
491 def test_display_data():
491 def test_display_data():
492 flush_channels()
492 flush_channels()
493
493
494 msg_id, reply = execute("from IPython.core.display import display; display(1)")
494 msg_id, reply = execute("from IPython.core.display import display; display(1)")
495
495
496 display = KM.iopub_channel.get_msg(timeout=2)
496 display = KM.iopub_channel.get_msg(timeout=2)
497 for tst in validate_message(display, 'display_data', parent=msg_id):
497 for tst in validate_message(display, 'display_data', parent=msg_id):
498 yield tst
498 yield tst
499 data = display['content']['data']
499 data = display['content']['data']
500 yield nt.assert_equal(data['text/plain'], u'1')
500 yield nt.assert_equal(data['text/plain'], u'1')
501
501
@@ -1,87 +1,87 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.utils.localinterfaces import LOCALHOST
10 from IPython.utils.localinterfaces import LOCALHOST
11 from IPython.kernel.kernelmanager import KernelManager
11 from IPython.kernel import KernelManager
12 from IPython.kernel.multikernelmanager import MultiKernelManager
12 from IPython.kernel.multikernelmanager import MultiKernelManager
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 c = Config()
17 c = Config()
18 # c.KernelManager.autorestart=False
18 # c.KernelManager.autorestart=False
19 km = MultiKernelManager(config=c)
19 km = MultiKernelManager(config=c)
20 return km
20 return km
21
21
22 def _get_ipc_km(self):
22 def _get_ipc_km(self):
23 c = Config()
23 c = Config()
24 c.KernelManager.transport = 'ipc'
24 c.KernelManager.transport = 'ipc'
25 c.KernelManager.ip = 'test'
25 c.KernelManager.ip = 'test'
26 # c.KernelManager.autorestart=False
26 # c.KernelManager.autorestart=False
27 km = MultiKernelManager(config=c)
27 km = MultiKernelManager(config=c)
28 return km
28 return km
29
29
30 def _run_lifecycle(self, km):
30 def _run_lifecycle(self, km):
31 kid = km.start_kernel(stdout=PIPE, stderr=PIPE)
31 kid = km.start_kernel(stdout=PIPE, stderr=PIPE)
32 self.assertTrue(km.is_alive(kid))
32 self.assertTrue(km.is_alive(kid))
33 self.assertTrue(kid in km)
33 self.assertTrue(kid in km)
34 self.assertTrue(kid in km.list_kernel_ids())
34 self.assertTrue(kid in km.list_kernel_ids())
35 self.assertEqual(len(km),1)
35 self.assertEqual(len(km),1)
36 km.restart_kernel(kid)
36 km.restart_kernel(kid)
37 self.assertTrue(km.is_alive(kid))
37 self.assertTrue(km.is_alive(kid))
38 self.assertTrue(kid in km.list_kernel_ids())
38 self.assertTrue(kid in km.list_kernel_ids())
39 # We need a delay here to give the restarting kernel a chance to
39 # We need a delay here to give the restarting kernel a chance to
40 # restart. Otherwise, the interrupt will kill it, causing the test
40 # restart. Otherwise, the interrupt will kill it, causing the test
41 # suite to hang. The reason it *hangs* is that the shutdown
41 # suite to hang. The reason it *hangs* is that the shutdown
42 # message for the restart sometimes hasn't been sent to the kernel.
42 # message for the restart sometimes hasn't been sent to the kernel.
43 # Because linger is oo on the shell channel, the context can't
43 # Because linger is oo on the shell channel, the context can't
44 # close until the message is sent to the kernel, which is not dead.
44 # close until the message is sent to the kernel, which is not dead.
45 time.sleep(1.0)
45 time.sleep(1.0)
46 km.interrupt_kernel(kid)
46 km.interrupt_kernel(kid)
47 k = km.get_kernel(kid)
47 k = km.get_kernel(kid)
48 self.assertTrue(isinstance(k, KernelManager))
48 self.assertTrue(isinstance(k, KernelManager))
49 km.shutdown_kernel(kid)
49 km.shutdown_kernel(kid)
50 self.assertTrue(not kid in km)
50 self.assertTrue(not kid in km)
51
51
52 def _run_cinfo(self, km, transport, ip):
52 def _run_cinfo(self, km, transport, ip):
53 kid = km.start_kernel(stdout=PIPE, stderr=PIPE)
53 kid = km.start_kernel(stdout=PIPE, stderr=PIPE)
54 k = km.get_kernel(kid)
54 k = km.get_kernel(kid)
55 cinfo = km.get_connection_info(kid)
55 cinfo = km.get_connection_info(kid)
56 self.assertEqual(transport, cinfo['transport'])
56 self.assertEqual(transport, cinfo['transport'])
57 self.assertEqual(ip, cinfo['ip'])
57 self.assertEqual(ip, cinfo['ip'])
58 self.assertTrue('stdin_port' in cinfo)
58 self.assertTrue('stdin_port' in cinfo)
59 self.assertTrue('iopub_port' in cinfo)
59 self.assertTrue('iopub_port' in cinfo)
60 stream = km.create_iopub_stream(kid)
60 stream = km.create_iopub_stream(kid)
61 stream.close()
61 stream.close()
62 self.assertTrue('shell_port' in cinfo)
62 self.assertTrue('shell_port' in cinfo)
63 stream = km.create_shell_stream(kid)
63 stream = km.create_shell_stream(kid)
64 stream.close()
64 stream.close()
65 self.assertTrue('hb_port' in cinfo)
65 self.assertTrue('hb_port' in cinfo)
66 stream = km.create_hb_stream(kid)
66 stream = km.create_hb_stream(kid)
67 stream.close()
67 stream.close()
68 km.shutdown_kernel(kid)
68 km.shutdown_kernel(kid)
69
69
70 def test_tcp_lifecycle(self):
70 def test_tcp_lifecycle(self):
71 km = self._get_tcp_km()
71 km = self._get_tcp_km()
72 self._run_lifecycle(km)
72 self._run_lifecycle(km)
73
73
74 def test_tcp_cinfo(self):
74 def test_tcp_cinfo(self):
75 km = self._get_tcp_km()
75 km = self._get_tcp_km()
76 self._run_cinfo(km, 'tcp', LOCALHOST)
76 self._run_cinfo(km, 'tcp', LOCALHOST)
77
77
78 @dec.skip_win32
78 @dec.skip_win32
79 def test_ipc_lifecycle(self):
79 def test_ipc_lifecycle(self):
80 km = self._get_ipc_km()
80 km = self._get_ipc_km()
81 self._run_lifecycle(km)
81 self._run_lifecycle(km)
82
82
83 @dec.skip_win32
83 @dec.skip_win32
84 def test_ipc_cinfo(self):
84 def test_ipc_cinfo(self):
85 km = self._get_ipc_km()
85 km = self._get_ipc_km()
86 self._run_cinfo(km, 'ipc', 'test')
86 self._run_cinfo(km, 'ipc', 'test')
87
87
@@ -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.kernel.blockingkernelmanager import BlockingKernelManager
25 from IPython.kernel.blocking 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
General Comments 0
You need to be logged in to leave comments. Login now