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