##// END OF EJS Templates
move IPKernelApp from zmq.ipkernel to zmq.kernelapp...
MinRK -
Show More
@@ -0,0 +1,57 b''
1 """Simple function for embedding an IPython kernel
2 """
3 #-----------------------------------------------------------------------------
4 # Imports
5 #-----------------------------------------------------------------------------
6
7 import sys
8
9 from IPython.utils.frame import extract_module_locals
10
11 from kernelapp import IPKernelApp
12
13 #-----------------------------------------------------------------------------
14 # Code
15 #-----------------------------------------------------------------------------
16
17 def embed_kernel(module=None, local_ns=None, **kwargs):
18 """Embed and start an IPython kernel in a given scope.
19
20 Parameters
21 ----------
22 module : ModuleType, optional
23 The module to load into IPython globals (default: caller)
24 local_ns : dict, optional
25 The namespace to load into IPython user namespace (default: caller)
26
27 kwargs : various, optional
28 Further keyword args are relayed to the KernelApp constructor,
29 allowing configuration of the Kernel. Will only have an effect
30 on the first embed_kernel call for a given process.
31
32 """
33 # get the app if it exists, or set it up if it doesn't
34 if IPKernelApp.initialized():
35 app = IPKernelApp.instance()
36 else:
37 app = IPKernelApp.instance(**kwargs)
38 app.initialize([])
39 # Undo unnecessary sys module mangling from init_sys_modules.
40 # This would not be necessary if we could prevent it
41 # in the first place by using a different InteractiveShell
42 # subclass, as in the regular embed case.
43 main = app.kernel.shell._orig_sys_modules_main_mod
44 if main is not None:
45 sys.modules[app.kernel.shell._orig_sys_modules_main_name] = main
46
47 # load the calling scope if not given
48 (caller_module, caller_locals) = extract_module_locals(1)
49 if module is None:
50 module = caller_module
51 if local_ns is None:
52 local_ns = caller_locals
53
54 app.kernel.user_module = module
55 app.kernel.user_ns = local_ns
56 app.shell.set_completer_frame()
57 app.start()
@@ -1,85 +1,85 b''
1 1 # encoding: utf-8
2 2 """
3 3 IPython: tools for interactive and parallel computing in Python.
4 4
5 5 http://ipython.org
6 6 """
7 7 #-----------------------------------------------------------------------------
8 8 # Copyright (c) 2008-2011, IPython Development Team.
9 9 # Copyright (c) 2001-2007, Fernando Perez <fernando.perez@colorado.edu>
10 10 # Copyright (c) 2001, Janko Hauser <jhauser@zscout.de>
11 11 # Copyright (c) 2001, Nathaniel Gray <n8gray@caltech.edu>
12 12 #
13 13 # Distributed under the terms of the Modified BSD License.
14 14 #
15 15 # The full license is in the file COPYING.txt, distributed with this software.
16 16 #-----------------------------------------------------------------------------
17 17
18 18 #-----------------------------------------------------------------------------
19 19 # Imports
20 20 #-----------------------------------------------------------------------------
21 21 from __future__ import absolute_import
22 22
23 23 import os
24 24 import sys
25 25
26 26 #-----------------------------------------------------------------------------
27 27 # Setup everything
28 28 #-----------------------------------------------------------------------------
29 29
30 30 # Don't forget to also update setup.py when this changes!
31 31 if sys.version[0:3] < '2.6':
32 32 raise ImportError('Python Version 2.6 or above is required for IPython.')
33 33
34 34 # Make it easy to import extensions - they are always directly on pythonpath.
35 35 # Therefore, non-IPython modules can be added to extensions directory.
36 36 # This should probably be in ipapp.py.
37 37 sys.path.append(os.path.join(os.path.dirname(__file__), "extensions"))
38 38
39 39 #-----------------------------------------------------------------------------
40 40 # Setup the top level names
41 41 #-----------------------------------------------------------------------------
42 42
43 43 from .config.loader import Config
44 44 from .core import release
45 45 from .core.application import Application
46 46 from .frontend.terminal.embed import embed
47 47
48 48 from .core.error import TryNext
49 49 from .core.interactiveshell import InteractiveShell
50 50 from .testing import test
51 51 from .utils.sysinfo import sys_info
52 52 from .utils.frame import extract_module_locals
53 53
54 54 # Release data
55 55 __author__ = '%s <%s>' % (release.author, release.author_email)
56 56 __license__ = release.license
57 57 __version__ = release.version
58 58 version_info = release.version_info
59 59
60 60 def embed_kernel(module=None, local_ns=None, **kwargs):
61 61 """Embed and start an IPython kernel in a given scope.
62 62
63 63 Parameters
64 64 ----------
65 65 module : ModuleType, optional
66 66 The module to load into IPython globals (default: caller)
67 67 local_ns : dict, optional
68 68 The namespace to load into IPython user namespace (default: caller)
69 69
70 70 kwargs : various, optional
71 71 Further keyword args are relayed to the KernelApp constructor,
72 72 allowing configuration of the Kernel. Will only have an effect
73 73 on the first embed_kernel call for a given process.
74 74
75 75 """
76 76
77 77 (caller_module, caller_locals) = extract_module_locals(1)
78 78 if module is None:
79 79 module = caller_module
80 80 if local_ns is None:
81 81 local_ns = caller_locals
82 82
83 83 # Only import .zmq when we really need it
84 from .zmq.ipkernel import embed_kernel as real_embed_kernel
84 from .zmq.embed import embed_kernel as real_embed_kernel
85 85 real_embed_kernel(module=module, local_ns=local_ns, **kwargs)
@@ -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 37 from IPython.zmq.blockingkernelmanager import BlockingKernelManager
38 38 from IPython.zmq.kernelmanager 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 from IPython.zmq.ipkernel import (
46 flags as ipkernel_flags,
47 aliases as ipkernel_aliases,
45 from IPython.zmq.kernelapp import (
46 kernel_flags,
47 kernel_aliases,
48 48 IPKernelApp
49 49 )
50 50 from IPython.zmq.session import Session, default_secure
51 51 from IPython.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 flags = dict(ipkernel_flags)
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 aliases = dict(ipkernel_aliases)
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.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("--KernelApp.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 KernelApp.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,643 +1,643 b''
1 1 # coding: utf-8
2 2 """A tornado based IPython notebook server.
3 3
4 4 Authors:
5 5
6 6 * Brian Granger
7 7 """
8 8 #-----------------------------------------------------------------------------
9 9 # Copyright (C) 2008-2011 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 # stdlib
20 20 import errno
21 21 import logging
22 22 import os
23 23 import random
24 24 import re
25 25 import select
26 26 import signal
27 27 import socket
28 28 import sys
29 29 import threading
30 30 import time
31 31 import uuid
32 32 import webbrowser
33 33
34 34 # Third party
35 35 import zmq
36 36 from jinja2 import Environment, FileSystemLoader
37 37
38 38 # Install the pyzmq ioloop. This has to be done before anything else from
39 39 # tornado is imported.
40 40 from zmq.eventloop import ioloop
41 41 ioloop.install()
42 42
43 43 from tornado import httpserver
44 44 from tornado import web
45 45
46 46 # Our own libraries
47 47 from .kernelmanager import MappingKernelManager
48 48 from .handlers import (LoginHandler, LogoutHandler,
49 49 ProjectDashboardHandler, NewHandler, NamedNotebookHandler,
50 50 MainKernelHandler, KernelHandler, KernelActionHandler, IOPubHandler,
51 51 ShellHandler, NotebookRootHandler, NotebookHandler, NotebookCopyHandler,
52 52 RSTHandler, AuthenticatedFileHandler, PrintNotebookHandler,
53 53 MainClusterHandler, ClusterProfileHandler, ClusterActionHandler,
54 54 FileFindHandler,
55 55 )
56 56 from .nbmanager import NotebookManager
57 57 from .filenbmanager import FileNotebookManager
58 58 from .clustermanager import ClusterManager
59 59
60 60 from IPython.config.application import catch_config_error, boolean_flag
61 61 from IPython.core.application import BaseIPythonApplication
62 62 from IPython.core.profiledir import ProfileDir
63 63 from IPython.frontend.consoleapp import IPythonConsoleApp
64 64 from IPython.zmq.session import Session, default_secure
65 65 from IPython.zmq.zmqshell import ZMQInteractiveShell
66 from IPython.zmq.ipkernel import (
67 flags as ipkernel_flags,
68 aliases as ipkernel_aliases,
66 from IPython.zmq.kernelapp import (
67 kernel_flags,
68 kernel_aliases,
69 69 IPKernelApp
70 70 )
71 71 from IPython.utils.importstring import import_item
72 72 from IPython.utils.localinterfaces import LOCALHOST
73 73 from IPython.kernel import swallow_argv
74 74 from IPython.utils.traitlets import (
75 75 Dict, Unicode, Integer, List, Enum, Bool,
76 76 DottedObjectName
77 77 )
78 78 from IPython.utils import py3compat
79 79 from IPython.utils.path import filefind
80 80
81 81 #-----------------------------------------------------------------------------
82 82 # Module globals
83 83 #-----------------------------------------------------------------------------
84 84
85 85 _kernel_id_regex = r"(?P<kernel_id>\w+-\w+-\w+-\w+-\w+)"
86 86 _kernel_action_regex = r"(?P<action>restart|interrupt)"
87 87 _notebook_id_regex = r"(?P<notebook_id>\w+-\w+-\w+-\w+-\w+)"
88 88 _profile_regex = r"(?P<profile>[^\/]+)" # there is almost no text that is invalid
89 89 _cluster_action_regex = r"(?P<action>start|stop)"
90 90
91 91 _examples = """
92 92 ipython notebook # start the notebook
93 93 ipython notebook --profile=sympy # use the sympy profile
94 94 ipython notebook --pylab=inline # pylab in inline plotting mode
95 95 ipython notebook --certfile=mycert.pem # use SSL/TLS certificate
96 96 ipython notebook --port=5555 --ip=* # Listen on port 5555, all interfaces
97 97 """
98 98
99 99 #-----------------------------------------------------------------------------
100 100 # Helper functions
101 101 #-----------------------------------------------------------------------------
102 102
103 103 def url_path_join(a,b):
104 104 if a.endswith('/') and b.startswith('/'):
105 105 return a[:-1]+b
106 106 else:
107 107 return a+b
108 108
109 109 def random_ports(port, n):
110 110 """Generate a list of n random ports near the given port.
111 111
112 112 The first 5 ports will be sequential, and the remaining n-5 will be
113 113 randomly selected in the range [port-2*n, port+2*n].
114 114 """
115 115 for i in range(min(5, n)):
116 116 yield port + i
117 117 for i in range(n-5):
118 118 yield port + random.randint(-2*n, 2*n)
119 119
120 120 #-----------------------------------------------------------------------------
121 121 # The Tornado web application
122 122 #-----------------------------------------------------------------------------
123 123
124 124 class NotebookWebApplication(web.Application):
125 125
126 126 def __init__(self, ipython_app, kernel_manager, notebook_manager,
127 127 cluster_manager, log,
128 128 base_project_url, settings_overrides):
129 129 handlers = [
130 130 (r"/", ProjectDashboardHandler),
131 131 (r"/login", LoginHandler),
132 132 (r"/logout", LogoutHandler),
133 133 (r"/new", NewHandler),
134 134 (r"/%s" % _notebook_id_regex, NamedNotebookHandler),
135 135 (r"/%s/copy" % _notebook_id_regex, NotebookCopyHandler),
136 136 (r"/%s/print" % _notebook_id_regex, PrintNotebookHandler),
137 137 (r"/kernels", MainKernelHandler),
138 138 (r"/kernels/%s" % _kernel_id_regex, KernelHandler),
139 139 (r"/kernels/%s/%s" % (_kernel_id_regex, _kernel_action_regex), KernelActionHandler),
140 140 (r"/kernels/%s/iopub" % _kernel_id_regex, IOPubHandler),
141 141 (r"/kernels/%s/shell" % _kernel_id_regex, ShellHandler),
142 142 (r"/notebooks", NotebookRootHandler),
143 143 (r"/notebooks/%s" % _notebook_id_regex, NotebookHandler),
144 144 (r"/rstservice/render", RSTHandler),
145 145 (r"/files/(.*)", AuthenticatedFileHandler, {'path' : notebook_manager.notebook_dir}),
146 146 (r"/clusters", MainClusterHandler),
147 147 (r"/clusters/%s/%s" % (_profile_regex, _cluster_action_regex), ClusterActionHandler),
148 148 (r"/clusters/%s" % _profile_regex, ClusterProfileHandler),
149 149 ]
150 150
151 151 # Python < 2.6.5 doesn't accept unicode keys in f(**kwargs), and
152 152 # base_project_url will always be unicode, which will in turn
153 153 # make the patterns unicode, and ultimately result in unicode
154 154 # keys in kwargs to handler._execute(**kwargs) in tornado.
155 155 # This enforces that base_project_url be ascii in that situation.
156 156 #
157 157 # Note that the URLs these patterns check against are escaped,
158 158 # and thus guaranteed to be ASCII: 'hΓ©llo' is really 'h%C3%A9llo'.
159 159 base_project_url = py3compat.unicode_to_str(base_project_url, 'ascii')
160 160
161 161 settings = dict(
162 162 template_path=os.path.join(os.path.dirname(__file__), "templates"),
163 163 static_path=ipython_app.static_file_path,
164 164 static_handler_class = FileFindHandler,
165 165 static_url_prefix = url_path_join(base_project_url,'/static/'),
166 166 cookie_secret=os.urandom(1024),
167 167 login_url=url_path_join(base_project_url,'/login'),
168 168 cookie_name='username-%s' % uuid.uuid4(),
169 169 )
170 170
171 171 # allow custom overrides for the tornado web app.
172 172 settings.update(settings_overrides)
173 173
174 174 # prepend base_project_url onto the patterns that we match
175 175 new_handlers = []
176 176 for handler in handlers:
177 177 pattern = url_path_join(base_project_url, handler[0])
178 178 new_handler = tuple([pattern]+list(handler[1:]))
179 179 new_handlers.append( new_handler )
180 180
181 181 super(NotebookWebApplication, self).__init__(new_handlers, **settings)
182 182
183 183 self.kernel_manager = kernel_manager
184 184 self.notebook_manager = notebook_manager
185 185 self.cluster_manager = cluster_manager
186 186 self.ipython_app = ipython_app
187 187 self.read_only = self.ipython_app.read_only
188 188 self.config = self.ipython_app.config
189 189 self.log = log
190 190 self.jinja2_env = Environment(loader=FileSystemLoader(os.path.join(os.path.dirname(__file__), "templates")))
191 191
192 192
193 193
194 194 #-----------------------------------------------------------------------------
195 195 # Aliases and Flags
196 196 #-----------------------------------------------------------------------------
197 197
198 flags = dict(ipkernel_flags)
198 flags = dict(kernel_flags)
199 199 flags['no-browser']=(
200 200 {'NotebookApp' : {'open_browser' : False}},
201 201 "Don't open the notebook in a browser after startup."
202 202 )
203 203 flags['no-mathjax']=(
204 204 {'NotebookApp' : {'enable_mathjax' : False}},
205 205 """Disable MathJax
206 206
207 207 MathJax is the javascript library IPython uses to render math/LaTeX. It is
208 208 very large, so you may want to disable it if you have a slow internet
209 209 connection, or for offline use of the notebook.
210 210
211 211 When disabled, equations etc. will appear as their untransformed TeX source.
212 212 """
213 213 )
214 214 flags['read-only'] = (
215 215 {'NotebookApp' : {'read_only' : True}},
216 216 """Allow read-only access to notebooks.
217 217
218 218 When using a password to protect the notebook server, this flag
219 219 allows unauthenticated clients to view the notebook list, and
220 220 individual notebooks, but not edit them, start kernels, or run
221 221 code.
222 222
223 223 If no password is set, the server will be entirely read-only.
224 224 """
225 225 )
226 226
227 227 # Add notebook manager flags
228 228 flags.update(boolean_flag('script', 'FileNotebookManager.save_script',
229 229 'Auto-save a .py script everytime the .ipynb notebook is saved',
230 230 'Do not auto-save .py scripts for every notebook'))
231 231
232 232 # the flags that are specific to the frontend
233 233 # these must be scrubbed before being passed to the kernel,
234 234 # or it will raise an error on unrecognized flags
235 235 notebook_flags = ['no-browser', 'no-mathjax', 'read-only', 'script', 'no-script']
236 236
237 aliases = dict(ipkernel_aliases)
237 aliases = dict(kernel_aliases)
238 238
239 239 aliases.update({
240 240 'ip': 'NotebookApp.ip',
241 241 'port': 'NotebookApp.port',
242 242 'port-retries': 'NotebookApp.port_retries',
243 243 'transport': 'KernelManager.transport',
244 244 'keyfile': 'NotebookApp.keyfile',
245 245 'certfile': 'NotebookApp.certfile',
246 246 'notebook-dir': 'NotebookManager.notebook_dir',
247 247 'browser': 'NotebookApp.browser',
248 248 })
249 249
250 250 # remove ipkernel flags that are singletons, and don't make sense in
251 251 # multi-kernel evironment:
252 252 aliases.pop('f', None)
253 253
254 254 notebook_aliases = [u'port', u'port-retries', u'ip', u'keyfile', u'certfile',
255 255 u'notebook-dir']
256 256
257 257 #-----------------------------------------------------------------------------
258 258 # NotebookApp
259 259 #-----------------------------------------------------------------------------
260 260
261 261 class NotebookApp(BaseIPythonApplication):
262 262
263 263 name = 'ipython-notebook'
264 264 default_config_file_name='ipython_notebook_config.py'
265 265
266 266 description = """
267 267 The IPython HTML Notebook.
268 268
269 269 This launches a Tornado based HTML Notebook Server that serves up an
270 270 HTML5/Javascript Notebook client.
271 271 """
272 272 examples = _examples
273 273
274 274 classes = IPythonConsoleApp.classes + [MappingKernelManager, NotebookManager,
275 275 FileNotebookManager]
276 276 flags = Dict(flags)
277 277 aliases = Dict(aliases)
278 278
279 279 kernel_argv = List(Unicode)
280 280
281 281 log_level = Enum((0,10,20,30,40,50,'DEBUG','INFO','WARN','ERROR','CRITICAL'),
282 282 default_value=logging.INFO,
283 283 config=True,
284 284 help="Set the log level by value or name.")
285 285
286 286 # create requested profiles by default, if they don't exist:
287 287 auto_create = Bool(True)
288 288
289 289 # file to be opened in the notebook server
290 290 file_to_run = Unicode('')
291 291
292 292 # Network related information.
293 293
294 294 ip = Unicode(LOCALHOST, config=True,
295 295 help="The IP address the notebook server will listen on."
296 296 )
297 297
298 298 def _ip_changed(self, name, old, new):
299 299 if new == u'*': self.ip = u''
300 300
301 301 port = Integer(8888, config=True,
302 302 help="The port the notebook server will listen on."
303 303 )
304 304 port_retries = Integer(50, config=True,
305 305 help="The number of additional ports to try if the specified port is not available."
306 306 )
307 307
308 308 certfile = Unicode(u'', config=True,
309 309 help="""The full path to an SSL/TLS certificate file."""
310 310 )
311 311
312 312 keyfile = Unicode(u'', config=True,
313 313 help="""The full path to a private key file for usage with SSL/TLS."""
314 314 )
315 315
316 316 password = Unicode(u'', config=True,
317 317 help="""Hashed password to use for web authentication.
318 318
319 319 To generate, type in a python/IPython shell:
320 320
321 321 from IPython.lib import passwd; passwd()
322 322
323 323 The string should be of the form type:salt:hashed-password.
324 324 """
325 325 )
326 326
327 327 open_browser = Bool(True, config=True,
328 328 help="""Whether to open in a browser after starting.
329 329 The specific browser used is platform dependent and
330 330 determined by the python standard library `webbrowser`
331 331 module, unless it is overridden using the --browser
332 332 (NotebookApp.browser) configuration option.
333 333 """)
334 334
335 335 browser = Unicode(u'', config=True,
336 336 help="""Specify what command to use to invoke a web
337 337 browser when opening the notebook. If not specified, the
338 338 default browser will be determined by the `webbrowser`
339 339 standard library module, which allows setting of the
340 340 BROWSER environment variable to override it.
341 341 """)
342 342
343 343 read_only = Bool(False, config=True,
344 344 help="Whether to prevent editing/execution of notebooks."
345 345 )
346 346
347 347 webapp_settings = Dict(config=True,
348 348 help="Supply overrides for the tornado.web.Application that the "
349 349 "IPython notebook uses.")
350 350
351 351 enable_mathjax = Bool(True, config=True,
352 352 help="""Whether to enable MathJax for typesetting math/TeX
353 353
354 354 MathJax is the javascript library IPython uses to render math/LaTeX. It is
355 355 very large, so you may want to disable it if you have a slow internet
356 356 connection, or for offline use of the notebook.
357 357
358 358 When disabled, equations etc. will appear as their untransformed TeX source.
359 359 """
360 360 )
361 361 def _enable_mathjax_changed(self, name, old, new):
362 362 """set mathjax url to empty if mathjax is disabled"""
363 363 if not new:
364 364 self.mathjax_url = u''
365 365
366 366 base_project_url = Unicode('/', config=True,
367 367 help='''The base URL for the notebook server.
368 368
369 369 Leading and trailing slashes can be omitted,
370 370 and will automatically be added.
371 371 ''')
372 372 def _base_project_url_changed(self, name, old, new):
373 373 if not new.startswith('/'):
374 374 self.base_project_url = '/'+new
375 375 elif not new.endswith('/'):
376 376 self.base_project_url = new+'/'
377 377
378 378 base_kernel_url = Unicode('/', config=True,
379 379 help='''The base URL for the kernel server
380 380
381 381 Leading and trailing slashes can be omitted,
382 382 and will automatically be added.
383 383 ''')
384 384 def _base_kernel_url_changed(self, name, old, new):
385 385 if not new.startswith('/'):
386 386 self.base_kernel_url = '/'+new
387 387 elif not new.endswith('/'):
388 388 self.base_kernel_url = new+'/'
389 389
390 390 websocket_host = Unicode("", config=True,
391 391 help="""The hostname for the websocket server."""
392 392 )
393 393
394 394 extra_static_paths = List(Unicode, config=True,
395 395 help="""Extra paths to search for serving static files.
396 396
397 397 This allows adding javascript/css to be available from the notebook server machine,
398 398 or overriding individual files in the IPython"""
399 399 )
400 400 def _extra_static_paths_default(self):
401 401 return [os.path.join(self.profile_dir.location, 'static')]
402 402
403 403 @property
404 404 def static_file_path(self):
405 405 """return extra paths + the default location"""
406 406 return self.extra_static_paths + [os.path.join(os.path.dirname(__file__), "static")]
407 407
408 408 mathjax_url = Unicode("", config=True,
409 409 help="""The url for MathJax.js."""
410 410 )
411 411 def _mathjax_url_default(self):
412 412 if not self.enable_mathjax:
413 413 return u''
414 414 static_url_prefix = self.webapp_settings.get("static_url_prefix",
415 415 "/static/")
416 416 try:
417 417 mathjax = filefind(os.path.join('mathjax', 'MathJax.js'), self.static_file_path)
418 418 except IOError:
419 419 if self.certfile:
420 420 # HTTPS: load from Rackspace CDN, because SSL certificate requires it
421 421 base = u"https://c328740.ssl.cf1.rackcdn.com"
422 422 else:
423 423 base = u"http://cdn.mathjax.org"
424 424
425 425 url = base + u"/mathjax/latest/MathJax.js"
426 426 self.log.info("Using MathJax from CDN: %s", url)
427 427 return url
428 428 else:
429 429 self.log.info("Using local MathJax from %s" % mathjax)
430 430 return static_url_prefix+u"mathjax/MathJax.js"
431 431
432 432 def _mathjax_url_changed(self, name, old, new):
433 433 if new and not self.enable_mathjax:
434 434 # enable_mathjax=False overrides mathjax_url
435 435 self.mathjax_url = u''
436 436 else:
437 437 self.log.info("Using MathJax: %s", new)
438 438
439 439 notebook_manager_class = DottedObjectName('IPython.frontend.html.notebook.filenbmanager.FileNotebookManager',
440 440 config=True,
441 441 help='The notebook manager class to use.')
442 442
443 443 def parse_command_line(self, argv=None):
444 444 super(NotebookApp, self).parse_command_line(argv)
445 445 if argv is None:
446 446 argv = sys.argv[1:]
447 447
448 448 # Scrub frontend-specific flags
449 449 self.kernel_argv = swallow_argv(argv, notebook_aliases, notebook_flags)
450 450 # Kernel should inherit default config file from frontend
451 451 self.kernel_argv.append("--KernelApp.parent_appname='%s'"%self.name)
452 452
453 453 if self.extra_args:
454 454 f = os.path.abspath(self.extra_args[0])
455 455 if os.path.isdir(f):
456 456 nbdir = f
457 457 else:
458 458 self.file_to_run = f
459 459 nbdir = os.path.dirname(f)
460 460 self.config.NotebookManager.notebook_dir = nbdir
461 461
462 462 def init_configurables(self):
463 463 # force Session default to be secure
464 464 default_secure(self.config)
465 465 self.kernel_manager = MappingKernelManager(
466 466 config=self.config, log=self.log, kernel_argv=self.kernel_argv,
467 467 connection_dir = self.profile_dir.security_dir,
468 468 )
469 469 kls = import_item(self.notebook_manager_class)
470 470 self.notebook_manager = kls(config=self.config, log=self.log)
471 471 self.notebook_manager.log_info()
472 472 self.notebook_manager.load_notebook_names()
473 473 self.cluster_manager = ClusterManager(config=self.config, log=self.log)
474 474 self.cluster_manager.update_profiles()
475 475
476 476 def init_logging(self):
477 477 # This prevents double log messages because tornado use a root logger that
478 478 # self.log is a child of. The logging module dipatches log messages to a log
479 479 # and all of its ancenstors until propagate is set to False.
480 480 self.log.propagate = False
481 481
482 482 def init_webapp(self):
483 483 """initialize tornado webapp and httpserver"""
484 484 self.web_app = NotebookWebApplication(
485 485 self, self.kernel_manager, self.notebook_manager,
486 486 self.cluster_manager, self.log,
487 487 self.base_project_url, self.webapp_settings
488 488 )
489 489 if self.certfile:
490 490 ssl_options = dict(certfile=self.certfile)
491 491 if self.keyfile:
492 492 ssl_options['keyfile'] = self.keyfile
493 493 else:
494 494 ssl_options = None
495 495 self.web_app.password = self.password
496 496 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options)
497 497 if not self.ip:
498 498 warning = "WARNING: The notebook server is listening on all IP addresses"
499 499 if ssl_options is None:
500 500 self.log.critical(warning + " and not using encryption. This"
501 501 "is not recommended.")
502 502 if not self.password and not self.read_only:
503 503 self.log.critical(warning + "and not using authentication."
504 504 "This is highly insecure and not recommended.")
505 505 success = None
506 506 for port in random_ports(self.port, self.port_retries+1):
507 507 try:
508 508 self.http_server.listen(port, self.ip)
509 509 except socket.error as e:
510 510 if e.errno != errno.EADDRINUSE:
511 511 raise
512 512 self.log.info('The port %i is already in use, trying another random port.' % port)
513 513 else:
514 514 self.port = port
515 515 success = True
516 516 break
517 517 if not success:
518 518 self.log.critical('ERROR: the notebook server could not be started because '
519 519 'no available port could be found.')
520 520 self.exit(1)
521 521
522 522 def init_signal(self):
523 523 # FIXME: remove this check when pyzmq dependency is >= 2.1.11
524 524 # safely extract zmq version info:
525 525 try:
526 526 zmq_v = zmq.pyzmq_version_info()
527 527 except AttributeError:
528 528 zmq_v = [ int(n) for n in re.findall(r'\d+', zmq.__version__) ]
529 529 if 'dev' in zmq.__version__:
530 530 zmq_v.append(999)
531 531 zmq_v = tuple(zmq_v)
532 532 if zmq_v >= (2,1,9) and not sys.platform.startswith('win'):
533 533 # This won't work with 2.1.7 and
534 534 # 2.1.9-10 will log ugly 'Interrupted system call' messages,
535 535 # but it will work
536 536 signal.signal(signal.SIGINT, self._handle_sigint)
537 537 signal.signal(signal.SIGTERM, self._signal_stop)
538 538
539 539 def _handle_sigint(self, sig, frame):
540 540 """SIGINT handler spawns confirmation dialog"""
541 541 # register more forceful signal handler for ^C^C case
542 542 signal.signal(signal.SIGINT, self._signal_stop)
543 543 # request confirmation dialog in bg thread, to avoid
544 544 # blocking the App
545 545 thread = threading.Thread(target=self._confirm_exit)
546 546 thread.daemon = True
547 547 thread.start()
548 548
549 549 def _restore_sigint_handler(self):
550 550 """callback for restoring original SIGINT handler"""
551 551 signal.signal(signal.SIGINT, self._handle_sigint)
552 552
553 553 def _confirm_exit(self):
554 554 """confirm shutdown on ^C
555 555
556 556 A second ^C, or answering 'y' within 5s will cause shutdown,
557 557 otherwise original SIGINT handler will be restored.
558 558
559 559 This doesn't work on Windows.
560 560 """
561 561 # FIXME: remove this delay when pyzmq dependency is >= 2.1.11
562 562 time.sleep(0.1)
563 563 sys.stdout.write("Shutdown Notebook Server (y/[n])? ")
564 564 sys.stdout.flush()
565 565 r,w,x = select.select([sys.stdin], [], [], 5)
566 566 if r:
567 567 line = sys.stdin.readline()
568 568 if line.lower().startswith('y'):
569 569 self.log.critical("Shutdown confirmed")
570 570 ioloop.IOLoop.instance().stop()
571 571 return
572 572 else:
573 573 print "No answer for 5s:",
574 574 print "resuming operation..."
575 575 # no answer, or answer is no:
576 576 # set it back to original SIGINT handler
577 577 # use IOLoop.add_callback because signal.signal must be called
578 578 # from main thread
579 579 ioloop.IOLoop.instance().add_callback(self._restore_sigint_handler)
580 580
581 581 def _signal_stop(self, sig, frame):
582 582 self.log.critical("received signal %s, stopping", sig)
583 583 ioloop.IOLoop.instance().stop()
584 584
585 585 @catch_config_error
586 586 def initialize(self, argv=None):
587 587 self.init_logging()
588 588 super(NotebookApp, self).initialize(argv)
589 589 self.init_configurables()
590 590 self.init_webapp()
591 591 self.init_signal()
592 592
593 593 def cleanup_kernels(self):
594 594 """Shutdown all kernels.
595 595
596 596 The kernels will shutdown themselves when this process no longer exists,
597 597 but explicit shutdown allows the KernelManagers to cleanup the connection files.
598 598 """
599 599 self.log.info('Shutting down kernels')
600 600 self.kernel_manager.shutdown_all()
601 601
602 602 def start(self):
603 603 ip = self.ip if self.ip else '[all ip addresses on your system]'
604 604 proto = 'https' if self.certfile else 'http'
605 605 info = self.log.info
606 606 info("The IPython Notebook is running at: %s://%s:%i%s" %
607 607 (proto, ip, self.port,self.base_project_url) )
608 608 info("Use Control-C to stop this server and shut down all kernels.")
609 609
610 610 if self.open_browser or self.file_to_run:
611 611 ip = self.ip or LOCALHOST
612 612 try:
613 613 browser = webbrowser.get(self.browser or None)
614 614 except webbrowser.Error as e:
615 615 self.log.warn('No web browser found: %s.' % e)
616 616 browser = None
617 617
618 618 if self.file_to_run:
619 619 name, _ = os.path.splitext(os.path.basename(self.file_to_run))
620 620 url = self.notebook_manager.rev_mapping.get(name, '')
621 621 else:
622 622 url = ''
623 623 if browser:
624 624 b = lambda : browser.open("%s://%s:%i%s%s" % (proto, ip,
625 625 self.port, self.base_project_url, url), new=2)
626 626 threading.Thread(target=b).start()
627 627 try:
628 628 ioloop.IOLoop.instance().start()
629 629 except KeyboardInterrupt:
630 630 info("Interrupted...")
631 631 finally:
632 632 self.cleanup_kernels()
633 633
634 634
635 635 #-----------------------------------------------------------------------------
636 636 # Main entry point
637 637 #-----------------------------------------------------------------------------
638 638
639 639 def launch_new_instance():
640 640 app = NotebookApp.instance()
641 641 app.initialize()
642 642 app.start()
643 643
@@ -1,370 +1,370 b''
1 1 """ A minimal application using the Qt console-style IPython frontend.
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.
5 5
6 6 Authors:
7 7
8 8 * Evan Patterson
9 9 * Min RK
10 10 * Erik Tollerud
11 11 * Fernando Perez
12 12 * Bussonnier Matthias
13 13 * Thomas Kluyver
14 14 * Paul Ivanov
15 15
16 16 """
17 17
18 18 #-----------------------------------------------------------------------------
19 19 # Imports
20 20 #-----------------------------------------------------------------------------
21 21
22 22 # stdlib imports
23 23 import json
24 24 import os
25 25 import signal
26 26 import sys
27 27 import uuid
28 28
29 29 # If run on Windows, install an exception hook which pops up a
30 30 # message box. Pythonw.exe hides the console, so without this
31 31 # the application silently fails to load.
32 32 #
33 33 # We always install this handler, because the expectation is for
34 34 # qtconsole to bring up a GUI even if called from the console.
35 35 # The old handler is called, so the exception is printed as well.
36 36 # If desired, check for pythonw with an additional condition
37 37 # (sys.executable.lower().find('pythonw.exe') >= 0).
38 38 if os.name == 'nt':
39 39 old_excepthook = sys.excepthook
40 40
41 41 def gui_excepthook(exctype, value, tb):
42 42 try:
43 43 import ctypes, traceback
44 44 MB_ICONERROR = 0x00000010L
45 45 title = u'Error starting IPython QtConsole'
46 46 msg = u''.join(traceback.format_exception(exctype, value, tb))
47 47 ctypes.windll.user32.MessageBoxW(0, msg, title, MB_ICONERROR)
48 48 finally:
49 49 # Also call the old exception hook to let it do
50 50 # its thing too.
51 51 old_excepthook(exctype, value, tb)
52 52
53 53 sys.excepthook = gui_excepthook
54 54
55 55 # System library imports
56 56 from IPython.external.qt import QtCore, QtGui
57 57
58 58 # Local imports
59 59 from IPython.config.application import boolean_flag, catch_config_error
60 60 from IPython.core.application import BaseIPythonApplication
61 61 from IPython.core.profiledir import ProfileDir
62 62 from IPython.frontend.qt.console.frontend_widget import FrontendWidget
63 63 from IPython.frontend.qt.console.ipython_widget import IPythonWidget
64 64 from IPython.frontend.qt.console.rich_ipython_widget import RichIPythonWidget
65 65 from IPython.frontend.qt.console import styles
66 66 from IPython.frontend.qt.console.mainwindow import MainWindow
67 67 from IPython.frontend.qt.kernelmanager import QtKernelManager
68 68 from IPython.kernel import tunnel_to_kernel, find_connection_file
69 69 from IPython.utils.path import filefind
70 70 from IPython.utils.py3compat import str_to_bytes
71 71 from IPython.utils.traitlets import (
72 72 Dict, List, Unicode, Integer, CaselessStrEnum, CBool, Any
73 73 )
74 from IPython.zmq.ipkernel import IPKernelApp
74 from IPython.zmq.kernelapp import IPKernelApp
75 75 from IPython.zmq.session import Session, default_secure
76 76 from IPython.zmq.zmqshell import ZMQInteractiveShell
77 77
78 78 from IPython.frontend.consoleapp import (
79 79 IPythonConsoleApp, app_aliases, app_flags, flags, aliases
80 80 )
81 81
82 82 #-----------------------------------------------------------------------------
83 83 # Network Constants
84 84 #-----------------------------------------------------------------------------
85 85
86 86 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
87 87
88 88 #-----------------------------------------------------------------------------
89 89 # Globals
90 90 #-----------------------------------------------------------------------------
91 91
92 92 _examples = """
93 93 ipython qtconsole # start the qtconsole
94 94 ipython qtconsole --pylab=inline # start with pylab in inline plotting mode
95 95 """
96 96
97 97 #-----------------------------------------------------------------------------
98 98 # Aliases and Flags
99 99 #-----------------------------------------------------------------------------
100 100
101 101 # start with copy of flags
102 102 flags = dict(flags)
103 103 qt_flags = {
104 104 'plain' : ({'IPythonQtConsoleApp' : {'plain' : True}},
105 105 "Disable rich text support."),
106 106 }
107 107
108 108 # and app_flags from the Console Mixin
109 109 qt_flags.update(app_flags)
110 110 # add frontend flags to the full set
111 111 flags.update(qt_flags)
112 112
113 113 # start with copy of front&backend aliases list
114 114 aliases = dict(aliases)
115 115 qt_aliases = dict(
116 116 style = 'IPythonWidget.syntax_style',
117 117 stylesheet = 'IPythonQtConsoleApp.stylesheet',
118 118 colors = 'ZMQInteractiveShell.colors',
119 119
120 120 editor = 'IPythonWidget.editor',
121 121 paging = 'ConsoleWidget.paging',
122 122 )
123 123 # and app_aliases from the Console Mixin
124 124 qt_aliases.update(app_aliases)
125 125 qt_aliases.update({'gui-completion':'ConsoleWidget.gui_completion'})
126 126 # add frontend aliases to the full set
127 127 aliases.update(qt_aliases)
128 128
129 129 # get flags&aliases into sets, and remove a couple that
130 130 # shouldn't be scrubbed from backend flags:
131 131 qt_aliases = set(qt_aliases.keys())
132 132 qt_aliases.remove('colors')
133 133 qt_flags = set(qt_flags.keys())
134 134
135 135 #-----------------------------------------------------------------------------
136 136 # Classes
137 137 #-----------------------------------------------------------------------------
138 138
139 139 #-----------------------------------------------------------------------------
140 140 # IPythonQtConsole
141 141 #-----------------------------------------------------------------------------
142 142
143 143
144 144 class IPythonQtConsoleApp(BaseIPythonApplication, IPythonConsoleApp):
145 145 name = 'ipython-qtconsole'
146 146
147 147 description = """
148 148 The IPython QtConsole.
149 149
150 150 This launches a Console-style application using Qt. It is not a full
151 151 console, in that launched terminal subprocesses will not be able to accept
152 152 input.
153 153
154 154 The QtConsole supports various extra features beyond the Terminal IPython
155 155 shell, such as inline plotting with matplotlib, via:
156 156
157 157 ipython qtconsole --pylab=inline
158 158
159 159 as well as saving your session as HTML, and printing the output.
160 160
161 161 """
162 162 examples = _examples
163 163
164 164 classes = [IPythonWidget] + IPythonConsoleApp.classes
165 165 flags = Dict(flags)
166 166 aliases = Dict(aliases)
167 167 frontend_flags = Any(qt_flags)
168 168 frontend_aliases = Any(qt_aliases)
169 169 kernel_manager_class = QtKernelManager
170 170
171 171 stylesheet = Unicode('', config=True,
172 172 help="path to a custom CSS stylesheet")
173 173
174 174 plain = CBool(False, config=True,
175 175 help="Use a plaintext widget instead of rich text (plain can't print/save).")
176 176
177 177 def _plain_changed(self, name, old, new):
178 178 kind = 'plain' if new else 'rich'
179 179 self.config.ConsoleWidget.kind = kind
180 180 if new:
181 181 self.widget_factory = IPythonWidget
182 182 else:
183 183 self.widget_factory = RichIPythonWidget
184 184
185 185 # the factory for creating a widget
186 186 widget_factory = Any(RichIPythonWidget)
187 187
188 188 def parse_command_line(self, argv=None):
189 189 super(IPythonQtConsoleApp, self).parse_command_line(argv)
190 190 self.build_kernel_argv(argv)
191 191
192 192
193 193 def new_frontend_master(self):
194 194 """ Create and return new frontend attached to new kernel, launched on localhost.
195 195 """
196 196 kernel_manager = self.kernel_manager_class(
197 197 connection_file=self._new_connection_file(),
198 198 config=self.config,
199 199 )
200 200 # start the kernel
201 201 kwargs = dict()
202 202 kwargs['extra_arguments'] = self.kernel_argv
203 203 kernel_manager.start_kernel(**kwargs)
204 204 kernel_manager.start_channels()
205 205 widget = self.widget_factory(config=self.config,
206 206 local_kernel=True)
207 207 self.init_colors(widget)
208 208 widget.kernel_manager = kernel_manager
209 209 widget._existing = False
210 210 widget._may_close = True
211 211 widget._confirm_exit = self.confirm_exit
212 212 return widget
213 213
214 214 def new_frontend_slave(self, current_widget):
215 215 """Create and return a new frontend attached to an existing kernel.
216 216
217 217 Parameters
218 218 ----------
219 219 current_widget : IPythonWidget
220 220 The IPythonWidget whose kernel this frontend is to share
221 221 """
222 222 kernel_manager = self.kernel_manager_class(
223 223 connection_file=current_widget.kernel_manager.connection_file,
224 224 config = self.config,
225 225 )
226 226 kernel_manager.load_connection_file()
227 227 kernel_manager.start_channels()
228 228 widget = self.widget_factory(config=self.config,
229 229 local_kernel=False)
230 230 self.init_colors(widget)
231 231 widget._existing = True
232 232 widget._may_close = False
233 233 widget._confirm_exit = False
234 234 widget.kernel_manager = kernel_manager
235 235 return widget
236 236
237 237 def init_qt_elements(self):
238 238 # Create the widget.
239 239 self.app = QtGui.QApplication([])
240 240
241 241 base_path = os.path.abspath(os.path.dirname(__file__))
242 242 icon_path = os.path.join(base_path, 'resources', 'icon', 'IPythonConsole.svg')
243 243 self.app.icon = QtGui.QIcon(icon_path)
244 244 QtGui.QApplication.setWindowIcon(self.app.icon)
245 245
246 246 try:
247 247 ip = self.config.KernelManager.ip
248 248 except AttributeError:
249 249 ip = LOCALHOST
250 250 local_kernel = (not self.existing) or ip in LOCAL_IPS
251 251 self.widget = self.widget_factory(config=self.config,
252 252 local_kernel=local_kernel)
253 253 self.init_colors(self.widget)
254 254 self.widget._existing = self.existing
255 255 self.widget._may_close = not self.existing
256 256 self.widget._confirm_exit = self.confirm_exit
257 257
258 258 self.widget.kernel_manager = self.kernel_manager
259 259 self.window = MainWindow(self.app,
260 260 confirm_exit=self.confirm_exit,
261 261 new_frontend_factory=self.new_frontend_master,
262 262 slave_frontend_factory=self.new_frontend_slave,
263 263 )
264 264 self.window.log = self.log
265 265 self.window.add_tab_with_frontend(self.widget)
266 266 self.window.init_menu_bar()
267 267
268 268 self.window.setWindowTitle('IPython')
269 269
270 270 def init_colors(self, widget):
271 271 """Configure the coloring of the widget"""
272 272 # Note: This will be dramatically simplified when colors
273 273 # are removed from the backend.
274 274
275 275 # parse the colors arg down to current known labels
276 276 try:
277 277 colors = self.config.ZMQInteractiveShell.colors
278 278 except AttributeError:
279 279 colors = None
280 280 try:
281 281 style = self.config.IPythonWidget.syntax_style
282 282 except AttributeError:
283 283 style = None
284 284 try:
285 285 sheet = self.config.IPythonWidget.style_sheet
286 286 except AttributeError:
287 287 sheet = None
288 288
289 289 # find the value for colors:
290 290 if colors:
291 291 colors=colors.lower()
292 292 if colors in ('lightbg', 'light'):
293 293 colors='lightbg'
294 294 elif colors in ('dark', 'linux'):
295 295 colors='linux'
296 296 else:
297 297 colors='nocolor'
298 298 elif style:
299 299 if style=='bw':
300 300 colors='nocolor'
301 301 elif styles.dark_style(style):
302 302 colors='linux'
303 303 else:
304 304 colors='lightbg'
305 305 else:
306 306 colors=None
307 307
308 308 # Configure the style
309 309 if style:
310 310 widget.style_sheet = styles.sheet_from_template(style, colors)
311 311 widget.syntax_style = style
312 312 widget._syntax_style_changed()
313 313 widget._style_sheet_changed()
314 314 elif colors:
315 315 # use a default dark/light/bw style
316 316 widget.set_default_style(colors=colors)
317 317
318 318 if self.stylesheet:
319 319 # we got an explicit stylesheet
320 320 if os.path.isfile(self.stylesheet):
321 321 with open(self.stylesheet) as f:
322 322 sheet = f.read()
323 323 else:
324 324 raise IOError("Stylesheet %r not found." % self.stylesheet)
325 325 if sheet:
326 326 widget.style_sheet = sheet
327 327 widget._style_sheet_changed()
328 328
329 329
330 330 def init_signal(self):
331 331 """allow clean shutdown on sigint"""
332 332 signal.signal(signal.SIGINT, lambda sig, frame: self.exit(-2))
333 333 # need a timer, so that QApplication doesn't block until a real
334 334 # Qt event fires (can require mouse movement)
335 335 # timer trick from http://stackoverflow.com/q/4938723/938949
336 336 timer = QtCore.QTimer()
337 337 # Let the interpreter run each 200 ms:
338 338 timer.timeout.connect(lambda: None)
339 339 timer.start(200)
340 340 # hold onto ref, so the timer doesn't get cleaned up
341 341 self._sigint_timer = timer
342 342
343 343 @catch_config_error
344 344 def initialize(self, argv=None):
345 345 super(IPythonQtConsoleApp, self).initialize(argv)
346 346 IPythonConsoleApp.initialize(self,argv)
347 347 self.init_qt_elements()
348 348 self.init_signal()
349 349
350 350 def start(self):
351 351
352 352 # draw the window
353 353 self.window.show()
354 354 self.window.raise_()
355 355
356 356 # Start the application main loop.
357 357 self.app.exec_()
358 358
359 359 #-----------------------------------------------------------------------------
360 360 # Main entry point
361 361 #-----------------------------------------------------------------------------
362 362
363 363 def main():
364 364 app = IPythonQtConsoleApp()
365 365 app.initialize()
366 366 app.start()
367 367
368 368
369 369 if __name__ == '__main__':
370 370 main()
@@ -1,154 +1,154 b''
1 1 """ A minimal application using the ZMQ-based terminal IPython frontend.
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.
5 5
6 6 Authors:
7 7
8 8 * Min RK
9 9 * Paul Ivanov
10 10
11 11 """
12 12
13 13 #-----------------------------------------------------------------------------
14 14 # Imports
15 15 #-----------------------------------------------------------------------------
16 16 import signal
17 17 import sys
18 18 import time
19 19
20 20 from IPython.frontend.terminal.ipapp import TerminalIPythonApp, frontend_flags as term_flags
21 21
22 22 from IPython.utils.traitlets import (
23 23 Dict, List, Unicode, Int, CaselessStrEnum, CBool, Any
24 24 )
25 25 from IPython.utils.warn import warn,error
26 26
27 from IPython.zmq.ipkernel import IPKernelApp
27 from IPython.zmq.kernelapp import IPKernelApp
28 28 from IPython.zmq.session import Session, default_secure
29 29 from IPython.zmq.zmqshell import ZMQInteractiveShell
30 30 from IPython.frontend.consoleapp import (
31 31 IPythonConsoleApp, app_aliases, app_flags, aliases, app_aliases, flags
32 32 )
33 33
34 34 from IPython.frontend.terminal.console.interactiveshell import ZMQTerminalInteractiveShell
35 35
36 36 #-----------------------------------------------------------------------------
37 37 # Globals
38 38 #-----------------------------------------------------------------------------
39 39
40 40 _examples = """
41 41 ipython console # start the ZMQ-based console
42 42 ipython console --existing # connect to an existing ipython session
43 43 """
44 44
45 45 #-----------------------------------------------------------------------------
46 46 # Flags and Aliases
47 47 #-----------------------------------------------------------------------------
48 48
49 49 # copy flags from mixin:
50 50 flags = dict(flags)
51 51 # start with mixin frontend flags:
52 52 frontend_flags = dict(app_flags)
53 53 # add TerminalIPApp flags:
54 54 frontend_flags.update(term_flags)
55 55 # disable quick startup, as it won't propagate to the kernel anyway
56 56 frontend_flags.pop('quick')
57 57 # update full dict with frontend flags:
58 58 flags.update(frontend_flags)
59 59
60 60 # copy flags from mixin
61 61 aliases = dict(aliases)
62 62 # start with mixin frontend flags
63 63 frontend_aliases = dict(app_aliases)
64 64 # load updated frontend flags into full dict
65 65 aliases.update(frontend_aliases)
66 66
67 67 # get flags&aliases into sets, and remove a couple that
68 68 # shouldn't be scrubbed from backend flags:
69 69 frontend_aliases = set(frontend_aliases.keys())
70 70 frontend_flags = set(frontend_flags.keys())
71 71
72 72
73 73 #-----------------------------------------------------------------------------
74 74 # Classes
75 75 #-----------------------------------------------------------------------------
76 76
77 77
78 78 class ZMQTerminalIPythonApp(TerminalIPythonApp, IPythonConsoleApp):
79 79 name = "ipython-console"
80 80 """Start a terminal frontend to the IPython zmq kernel."""
81 81
82 82 description = """
83 83 The IPython terminal-based Console.
84 84
85 85 This launches a Console application inside a terminal.
86 86
87 87 The Console supports various extra features beyond the traditional
88 88 single-process Terminal IPython shell, such as connecting to an
89 89 existing ipython session, via:
90 90
91 91 ipython console --existing
92 92
93 93 where the previous session could have been created by another ipython
94 94 console, an ipython qtconsole, or by opening an ipython notebook.
95 95
96 96 """
97 97 examples = _examples
98 98
99 99 classes = [ZMQTerminalInteractiveShell] + IPythonConsoleApp.classes
100 100 flags = Dict(flags)
101 101 aliases = Dict(aliases)
102 102 frontend_aliases = Any(frontend_aliases)
103 103 frontend_flags = Any(frontend_flags)
104 104
105 105 subcommands = Dict()
106 106
107 107 def parse_command_line(self, argv=None):
108 108 super(ZMQTerminalIPythonApp, self).parse_command_line(argv)
109 109 self.build_kernel_argv(argv)
110 110
111 111 def init_shell(self):
112 112 IPythonConsoleApp.initialize(self)
113 113 # relay sigint to kernel
114 114 signal.signal(signal.SIGINT, self.handle_sigint)
115 115 self.shell = ZMQTerminalInteractiveShell.instance(config=self.config,
116 116 display_banner=False, profile_dir=self.profile_dir,
117 117 ipython_dir=self.ipython_dir, kernel_manager=self.kernel_manager)
118 118
119 119 def init_gui_pylab(self):
120 120 # no-op, because we don't want to import matplotlib in the frontend.
121 121 pass
122 122
123 123 def handle_sigint(self, *args):
124 124 if self.shell._executing:
125 125 if self.kernel_manager.has_kernel:
126 126 # interrupt already gets passed to subprocess by signal handler.
127 127 # Only if we prevent that should we need to explicitly call
128 128 # interrupt_kernel, until which time, this would result in a
129 129 # double-interrupt:
130 130 # self.kernel_manager.interrupt_kernel()
131 131 pass
132 132 else:
133 133 self.shell.write_err('\n')
134 134 error("Cannot interrupt kernels we didn't start.\n")
135 135 else:
136 136 # raise the KeyboardInterrupt if we aren't waiting for execution,
137 137 # so that the interact loop advances, and prompt is redrawn, etc.
138 138 raise KeyboardInterrupt
139 139
140 140
141 141 def init_code(self):
142 142 # no-op in the frontend, code gets run in the backend
143 143 pass
144 144
145 145 def launch_new_instance():
146 146 """Create and run a full blown IPython instance"""
147 147 app = ZMQTerminalIPythonApp.instance()
148 148 app.initialize()
149 149 app.start()
150 150
151 151
152 152 if __name__ == '__main__':
153 153 launch_new_instance()
154 154
@@ -1,393 +1,393 b''
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 """
4 4 The :class:`~IPython.core.application.Application` object for the command
5 5 line :command:`ipython` program.
6 6
7 7 Authors
8 8 -------
9 9
10 10 * Brian Granger
11 11 * Fernando Perez
12 12 * Min Ragan-Kelley
13 13 """
14 14
15 15 #-----------------------------------------------------------------------------
16 16 # Copyright (C) 2008-2011 The IPython Development Team
17 17 #
18 18 # Distributed under the terms of the BSD License. The full license is in
19 19 # the file COPYING, distributed as part of this software.
20 20 #-----------------------------------------------------------------------------
21 21
22 22 #-----------------------------------------------------------------------------
23 23 # Imports
24 24 #-----------------------------------------------------------------------------
25 25
26 26 from __future__ import absolute_import
27 27
28 28 import logging
29 29 import os
30 30 import sys
31 31
32 32 from IPython.config.loader import (
33 33 Config, PyFileConfigLoader, ConfigFileNotFound
34 34 )
35 35 from IPython.config.application import boolean_flag, catch_config_error
36 36 from IPython.core import release
37 37 from IPython.core import usage
38 38 from IPython.core.completer import IPCompleter
39 39 from IPython.core.crashhandler import CrashHandler
40 40 from IPython.core.formatters import PlainTextFormatter
41 41 from IPython.core.history import HistoryManager
42 42 from IPython.core.prompts import PromptManager
43 43 from IPython.core.application import (
44 44 ProfileDir, BaseIPythonApplication, base_flags, base_aliases
45 45 )
46 46 from IPython.core.magics import ScriptMagics
47 47 from IPython.core.shellapp import (
48 48 InteractiveShellApp, shell_flags, shell_aliases
49 49 )
50 50 from IPython.frontend.terminal.interactiveshell import TerminalInteractiveShell
51 51 from IPython.lib import inputhook
52 52 from IPython.utils import warn
53 53 from IPython.utils.path import get_ipython_dir, check_for_old_config
54 54 from IPython.utils.traitlets import (
55 55 Bool, List, Dict, CaselessStrEnum
56 56 )
57 57
58 58 #-----------------------------------------------------------------------------
59 59 # Globals, utilities and helpers
60 60 #-----------------------------------------------------------------------------
61 61
62 62 #: The default config file name for this application.
63 63 default_config_file_name = u'ipython_config.py'
64 64
65 65 _examples = """
66 66 ipython --pylab # start in pylab mode
67 67 ipython --pylab=qt # start in pylab mode with the qt4 backend
68 68 ipython --log-level=DEBUG # set logging to DEBUG
69 69 ipython --profile=foo # start with profile foo
70 70
71 71 ipython qtconsole # start the qtconsole GUI application
72 72 ipython help qtconsole # show the help for the qtconsole subcmd
73 73
74 74 ipython console # start the terminal-based console application
75 75 ipython help console # show the help for the console subcmd
76 76
77 77 ipython notebook # start the IPython notebook
78 78 ipython help notebook # show the help for the notebook subcmd
79 79
80 80 ipython profile create foo # create profile foo w/ default config files
81 81 ipython help profile # show the help for the profile subcmd
82 82
83 83 ipython locate # print the path to the IPython directory
84 84 ipython locate profile foo # print the path to the directory for profile `foo`
85 85 """
86 86
87 87 #-----------------------------------------------------------------------------
88 88 # Crash handler for this application
89 89 #-----------------------------------------------------------------------------
90 90
91 91 class IPAppCrashHandler(CrashHandler):
92 92 """sys.excepthook for IPython itself, leaves a detailed report on disk."""
93 93
94 94 def __init__(self, app):
95 95 contact_name = release.author
96 96 contact_email = release.author_email
97 97 bug_tracker = 'https://github.com/ipython/ipython/issues'
98 98 super(IPAppCrashHandler,self).__init__(
99 99 app, contact_name, contact_email, bug_tracker
100 100 )
101 101
102 102 def make_report(self,traceback):
103 103 """Return a string containing a crash report."""
104 104
105 105 sec_sep = self.section_sep
106 106 # Start with parent report
107 107 report = [super(IPAppCrashHandler, self).make_report(traceback)]
108 108 # Add interactive-specific info we may have
109 109 rpt_add = report.append
110 110 try:
111 111 rpt_add(sec_sep+"History of session input:")
112 112 for line in self.app.shell.user_ns['_ih']:
113 113 rpt_add(line)
114 114 rpt_add('\n*** Last line of input (may not be in above history):\n')
115 115 rpt_add(self.app.shell._last_input_line+'\n')
116 116 except:
117 117 pass
118 118
119 119 return ''.join(report)
120 120
121 121 #-----------------------------------------------------------------------------
122 122 # Aliases and Flags
123 123 #-----------------------------------------------------------------------------
124 124 flags = dict(base_flags)
125 125 flags.update(shell_flags)
126 126 frontend_flags = {}
127 127 addflag = lambda *args: frontend_flags.update(boolean_flag(*args))
128 128 addflag('autoedit-syntax', 'TerminalInteractiveShell.autoedit_syntax',
129 129 'Turn on auto editing of files with syntax errors.',
130 130 'Turn off auto editing of files with syntax errors.'
131 131 )
132 132 addflag('banner', 'TerminalIPythonApp.display_banner',
133 133 "Display a banner upon starting IPython.",
134 134 "Don't display a banner upon starting IPython."
135 135 )
136 136 addflag('confirm-exit', 'TerminalInteractiveShell.confirm_exit',
137 137 """Set to confirm when you try to exit IPython with an EOF (Control-D
138 138 in Unix, Control-Z/Enter in Windows). By typing 'exit' or 'quit',
139 139 you can force a direct exit without any confirmation.""",
140 140 "Don't prompt the user when exiting."
141 141 )
142 142 addflag('term-title', 'TerminalInteractiveShell.term_title',
143 143 "Enable auto setting the terminal title.",
144 144 "Disable auto setting the terminal title."
145 145 )
146 146 classic_config = Config()
147 147 classic_config.InteractiveShell.cache_size = 0
148 148 classic_config.PlainTextFormatter.pprint = False
149 149 classic_config.PromptManager.in_template = '>>> '
150 150 classic_config.PromptManager.in2_template = '... '
151 151 classic_config.PromptManager.out_template = ''
152 152 classic_config.InteractiveShell.separate_in = ''
153 153 classic_config.InteractiveShell.separate_out = ''
154 154 classic_config.InteractiveShell.separate_out2 = ''
155 155 classic_config.InteractiveShell.colors = 'NoColor'
156 156 classic_config.InteractiveShell.xmode = 'Plain'
157 157
158 158 frontend_flags['classic']=(
159 159 classic_config,
160 160 "Gives IPython a similar feel to the classic Python prompt."
161 161 )
162 162 # # log doesn't make so much sense this way anymore
163 163 # paa('--log','-l',
164 164 # action='store_true', dest='InteractiveShell.logstart',
165 165 # help="Start logging to the default log file (./ipython_log.py).")
166 166 #
167 167 # # quick is harder to implement
168 168 frontend_flags['quick']=(
169 169 {'TerminalIPythonApp' : {'quick' : True}},
170 170 "Enable quick startup with no config files."
171 171 )
172 172
173 173 frontend_flags['i'] = (
174 174 {'TerminalIPythonApp' : {'force_interact' : True}},
175 175 """If running code from the command line, become interactive afterwards.
176 176 Note: can also be given simply as '-i.'"""
177 177 )
178 178 flags.update(frontend_flags)
179 179
180 180 aliases = dict(base_aliases)
181 181 aliases.update(shell_aliases)
182 182
183 183 #-----------------------------------------------------------------------------
184 184 # Main classes and functions
185 185 #-----------------------------------------------------------------------------
186 186
187 187
188 188 class LocateIPythonApp(BaseIPythonApplication):
189 189 description = """print the path to the IPython dir"""
190 190 subcommands = Dict(dict(
191 191 profile=('IPython.core.profileapp.ProfileLocate',
192 192 "print the path to an IPython profile directory",
193 193 ),
194 194 ))
195 195 def start(self):
196 196 if self.subapp is not None:
197 197 return self.subapp.start()
198 198 else:
199 199 print self.ipython_dir
200 200
201 201
202 202 class TerminalIPythonApp(BaseIPythonApplication, InteractiveShellApp):
203 203 name = u'ipython'
204 204 description = usage.cl_usage
205 205 default_config_file_name = default_config_file_name
206 206 crash_handler_class = IPAppCrashHandler
207 207 examples = _examples
208 208
209 209 flags = Dict(flags)
210 210 aliases = Dict(aliases)
211 211 classes = List()
212 212 def _classes_default(self):
213 213 """This has to be in a method, for TerminalIPythonApp to be available."""
214 214 return [
215 215 InteractiveShellApp, # ShellApp comes before TerminalApp, because
216 216 self.__class__, # it will also affect subclasses (e.g. QtConsole)
217 217 TerminalInteractiveShell,
218 218 PromptManager,
219 219 HistoryManager,
220 220 ProfileDir,
221 221 PlainTextFormatter,
222 222 IPCompleter,
223 223 ScriptMagics,
224 224 ]
225 225
226 226 subcommands = Dict(dict(
227 227 qtconsole=('IPython.frontend.qt.console.qtconsoleapp.IPythonQtConsoleApp',
228 228 """Launch the IPython Qt Console."""
229 229 ),
230 230 notebook=('IPython.frontend.html.notebook.notebookapp.NotebookApp',
231 231 """Launch the IPython HTML Notebook Server."""
232 232 ),
233 233 profile = ("IPython.core.profileapp.ProfileApp",
234 234 "Create and manage IPython profiles."
235 235 ),
236 kernel = ("IPython.zmq.ipkernel.IPKernelApp",
236 kernel = ("IPython.zmq.kernelapp.IPKernelApp",
237 237 "Start a kernel without an attached frontend."
238 238 ),
239 239 console=('IPython.frontend.terminal.console.app.ZMQTerminalIPythonApp',
240 240 """Launch the IPython terminal-based Console."""
241 241 ),
242 242 locate=('IPython.frontend.terminal.ipapp.LocateIPythonApp',
243 243 LocateIPythonApp.description
244 244 ),
245 245 ))
246 246
247 247 # *do* autocreate requested profile, but don't create the config file.
248 248 auto_create=Bool(True)
249 249 # configurables
250 250 ignore_old_config=Bool(False, config=True,
251 251 help="Suppress warning messages about legacy config files"
252 252 )
253 253 quick = Bool(False, config=True,
254 254 help="""Start IPython quickly by skipping the loading of config files."""
255 255 )
256 256 def _quick_changed(self, name, old, new):
257 257 if new:
258 258 self.load_config_file = lambda *a, **kw: None
259 259 self.ignore_old_config=True
260 260
261 261 display_banner = Bool(True, config=True,
262 262 help="Whether to display a banner upon starting IPython."
263 263 )
264 264
265 265 # if there is code of files to run from the cmd line, don't interact
266 266 # unless the --i flag (App.force_interact) is true.
267 267 force_interact = Bool(False, config=True,
268 268 help="""If a command or file is given via the command-line,
269 269 e.g. 'ipython foo.py"""
270 270 )
271 271 def _force_interact_changed(self, name, old, new):
272 272 if new:
273 273 self.interact = True
274 274
275 275 def _file_to_run_changed(self, name, old, new):
276 276 if new:
277 277 self.something_to_run = True
278 278 if new and not self.force_interact:
279 279 self.interact = False
280 280 _code_to_run_changed = _file_to_run_changed
281 281 _module_to_run_changed = _file_to_run_changed
282 282
283 283 # internal, not-configurable
284 284 interact=Bool(True)
285 285 something_to_run=Bool(False)
286 286
287 287 def parse_command_line(self, argv=None):
288 288 """override to allow old '-pylab' flag with deprecation warning"""
289 289
290 290 argv = sys.argv[1:] if argv is None else argv
291 291
292 292 if '-pylab' in argv:
293 293 # deprecated `-pylab` given,
294 294 # warn and transform into current syntax
295 295 argv = argv[:] # copy, don't clobber
296 296 idx = argv.index('-pylab')
297 297 warn.warn("`-pylab` flag has been deprecated.\n"
298 298 " Use `--pylab` instead, or `--pylab=foo` to specify a backend.")
299 299 sub = '--pylab'
300 300 if len(argv) > idx+1:
301 301 # check for gui arg, as in '-pylab qt'
302 302 gui = argv[idx+1]
303 303 if gui in ('wx', 'qt', 'qt4', 'gtk', 'auto'):
304 304 sub = '--pylab='+gui
305 305 argv.pop(idx+1)
306 306 argv[idx] = sub
307 307
308 308 return super(TerminalIPythonApp, self).parse_command_line(argv)
309 309
310 310 @catch_config_error
311 311 def initialize(self, argv=None):
312 312 """Do actions after construct, but before starting the app."""
313 313 super(TerminalIPythonApp, self).initialize(argv)
314 314 if self.subapp is not None:
315 315 # don't bother initializing further, starting subapp
316 316 return
317 317 if not self.ignore_old_config:
318 318 check_for_old_config(self.ipython_dir)
319 319 # print self.extra_args
320 320 if self.extra_args and not self.something_to_run:
321 321 self.file_to_run = self.extra_args[0]
322 322 self.init_path()
323 323 # create the shell
324 324 self.init_shell()
325 325 # and draw the banner
326 326 self.init_banner()
327 327 # Now a variety of things that happen after the banner is printed.
328 328 self.init_gui_pylab()
329 329 self.init_extensions()
330 330 self.init_code()
331 331
332 332 def init_shell(self):
333 333 """initialize the InteractiveShell instance"""
334 334 # Create an InteractiveShell instance.
335 335 # shell.display_banner should always be False for the terminal
336 336 # based app, because we call shell.show_banner() by hand below
337 337 # so the banner shows *before* all extension loading stuff.
338 338 self.shell = TerminalInteractiveShell.instance(config=self.config,
339 339 display_banner=False, profile_dir=self.profile_dir,
340 340 ipython_dir=self.ipython_dir)
341 341 self.shell.configurables.append(self)
342 342
343 343 def init_banner(self):
344 344 """optionally display the banner"""
345 345 if self.display_banner and self.interact:
346 346 self.shell.show_banner()
347 347 # Make sure there is a space below the banner.
348 348 if self.log_level <= logging.INFO: print
349 349
350 350 def _pylab_changed(self, name, old, new):
351 351 """Replace --pylab='inline' with --pylab='auto'"""
352 352 if new == 'inline':
353 353 warn.warn("'inline' not available as pylab backend, "
354 354 "using 'auto' instead.")
355 355 self.pylab = 'auto'
356 356
357 357 def start(self):
358 358 if self.subapp is not None:
359 359 return self.subapp.start()
360 360 # perform any prexec steps:
361 361 if self.interact:
362 362 self.log.debug("Starting IPython's mainloop...")
363 363 self.shell.mainloop()
364 364 else:
365 365 self.log.debug("IPython not interactive...")
366 366
367 367
368 368 def load_default_config(ipython_dir=None):
369 369 """Load the default config file from the default ipython_dir.
370 370
371 371 This is useful for embedded shells.
372 372 """
373 373 if ipython_dir is None:
374 374 ipython_dir = get_ipython_dir()
375 375 profile_dir = os.path.join(ipython_dir, 'profile_default')
376 376 cl = PyFileConfigLoader(default_config_file_name, profile_dir)
377 377 try:
378 378 config = cl.load_config()
379 379 except ConfigFileNotFound:
380 380 # no config found
381 381 config = Config()
382 382 return config
383 383
384 384
385 385 def launch_new_instance():
386 386 """Create and run a full blown IPython instance"""
387 387 app = TerminalIPythonApp.instance()
388 388 app.initialize()
389 389 app.start()
390 390
391 391
392 392 if __name__ == '__main__':
393 393 launch_new_instance()
@@ -1,339 +1,339 b''
1 1 """Utilities for connecting to kernels
2 2
3 3 Authors:
4 4
5 5 * Min Ragan-Kelley
6 6
7 7 """
8 8
9 9 #-----------------------------------------------------------------------------
10 10 # Copyright (C) 2013 The IPython Development Team
11 11 #
12 12 # Distributed under the terms of the BSD License. The full license is in
13 13 # the file COPYING, distributed as part of this software.
14 14 #-----------------------------------------------------------------------------
15 15
16 16 #-----------------------------------------------------------------------------
17 17 # Imports
18 18 #-----------------------------------------------------------------------------
19 19
20 20 import glob
21 21 import json
22 22 import os
23 23 import socket
24 24 import sys
25 25 from getpass import getpass
26 26 from subprocess import Popen, PIPE
27 27 import tempfile
28 28
29 29 # external imports
30 30 from IPython.external.ssh import tunnel
31 31
32 32 # IPython imports
33 33 from IPython.core.profiledir import ProfileDir
34 34 from IPython.utils.localinterfaces import LOCALHOST
35 35 from IPython.utils.path import filefind, get_ipython_dir
36 36 from IPython.utils.py3compat import str_to_bytes, bytes_to_str
37 37
38 38
39 39 #-----------------------------------------------------------------------------
40 40 # Working with Connection Files
41 41 #-----------------------------------------------------------------------------
42 42
43 43 def write_connection_file(fname=None, shell_port=0, iopub_port=0, stdin_port=0, hb_port=0,
44 44 ip=LOCALHOST, key=b'', transport='tcp'):
45 45 """Generates a JSON config file, including the selection of random ports.
46 46
47 47 Parameters
48 48 ----------
49 49
50 50 fname : unicode
51 51 The path to the file to write
52 52
53 53 shell_port : int, optional
54 54 The port to use for ROUTER channel.
55 55
56 56 iopub_port : int, optional
57 57 The port to use for the SUB channel.
58 58
59 59 stdin_port : int, optional
60 60 The port to use for the REQ (raw input) channel.
61 61
62 62 hb_port : int, optional
63 63 The port to use for the hearbeat REP channel.
64 64
65 65 ip : str, optional
66 66 The ip address the kernel will bind to.
67 67
68 68 key : str, optional
69 69 The Session key used for HMAC authentication.
70 70
71 71 """
72 72 # default to temporary connector file
73 73 if not fname:
74 74 fname = tempfile.mktemp('.json')
75 75
76 76 # Find open ports as necessary.
77 77
78 78 ports = []
79 79 ports_needed = int(shell_port <= 0) + int(iopub_port <= 0) + \
80 80 int(stdin_port <= 0) + int(hb_port <= 0)
81 81 if transport == 'tcp':
82 82 for i in range(ports_needed):
83 83 sock = socket.socket()
84 84 sock.bind(('', 0))
85 85 ports.append(sock)
86 86 for i, sock in enumerate(ports):
87 87 port = sock.getsockname()[1]
88 88 sock.close()
89 89 ports[i] = port
90 90 else:
91 91 N = 1
92 92 for i in range(ports_needed):
93 93 while os.path.exists("%s-%s" % (ip, str(N))):
94 94 N += 1
95 95 ports.append(N)
96 96 N += 1
97 97 if shell_port <= 0:
98 98 shell_port = ports.pop(0)
99 99 if iopub_port <= 0:
100 100 iopub_port = ports.pop(0)
101 101 if stdin_port <= 0:
102 102 stdin_port = ports.pop(0)
103 103 if hb_port <= 0:
104 104 hb_port = ports.pop(0)
105 105
106 106 cfg = dict( shell_port=shell_port,
107 107 iopub_port=iopub_port,
108 108 stdin_port=stdin_port,
109 109 hb_port=hb_port,
110 110 )
111 111 cfg['ip'] = ip
112 112 cfg['key'] = bytes_to_str(key)
113 113 cfg['transport'] = transport
114 114
115 115 with open(fname, 'w') as f:
116 116 f.write(json.dumps(cfg, indent=2))
117 117
118 118 return fname, cfg
119 119
120 120
121 121 def get_connection_file(app=None):
122 122 """Return the path to the connection file of an app
123 123
124 124 Parameters
125 125 ----------
126 126 app : KernelApp instance [optional]
127 127 If unspecified, the currently running app will be used
128 128 """
129 129 if app is None:
130 from IPython.zmq.ipkernel import IPKernelApp
130 from IPython.zmq.kernelapp import IPKernelApp
131 131 if not IPKernelApp.initialized():
132 132 raise RuntimeError("app not specified, and not in a running Kernel")
133 133
134 134 app = IPKernelApp.instance()
135 135 return filefind(app.connection_file, ['.', app.profile_dir.security_dir])
136 136
137 137
138 138 def find_connection_file(filename, profile=None):
139 139 """find a connection file, and return its absolute path.
140 140
141 141 The current working directory and the profile's security
142 142 directory will be searched for the file if it is not given by
143 143 absolute path.
144 144
145 145 If profile is unspecified, then the current running application's
146 146 profile will be used, or 'default', if not run from IPython.
147 147
148 148 If the argument does not match an existing file, it will be interpreted as a
149 149 fileglob, and the matching file in the profile's security dir with
150 150 the latest access time will be used.
151 151
152 152 Parameters
153 153 ----------
154 154 filename : str
155 155 The connection file or fileglob to search for.
156 156 profile : str [optional]
157 157 The name of the profile to use when searching for the connection file,
158 158 if different from the current IPython session or 'default'.
159 159
160 160 Returns
161 161 -------
162 162 str : The absolute path of the connection file.
163 163 """
164 164 from IPython.core.application import BaseIPythonApplication as IPApp
165 165 try:
166 166 # quick check for absolute path, before going through logic
167 167 return filefind(filename)
168 168 except IOError:
169 169 pass
170 170
171 171 if profile is None:
172 172 # profile unspecified, check if running from an IPython app
173 173 if IPApp.initialized():
174 174 app = IPApp.instance()
175 175 profile_dir = app.profile_dir
176 176 else:
177 177 # not running in IPython, use default profile
178 178 profile_dir = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), 'default')
179 179 else:
180 180 # find profiledir by profile name:
181 181 profile_dir = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), profile)
182 182 security_dir = profile_dir.security_dir
183 183
184 184 try:
185 185 # first, try explicit name
186 186 return filefind(filename, ['.', security_dir])
187 187 except IOError:
188 188 pass
189 189
190 190 # not found by full name
191 191
192 192 if '*' in filename:
193 193 # given as a glob already
194 194 pat = filename
195 195 else:
196 196 # accept any substring match
197 197 pat = '*%s*' % filename
198 198 matches = glob.glob( os.path.join(security_dir, pat) )
199 199 if not matches:
200 200 raise IOError("Could not find %r in %r" % (filename, security_dir))
201 201 elif len(matches) == 1:
202 202 return matches[0]
203 203 else:
204 204 # get most recent match, by access time:
205 205 return sorted(matches, key=lambda f: os.stat(f).st_atime)[-1]
206 206
207 207
208 208 def get_connection_info(connection_file=None, unpack=False, profile=None):
209 209 """Return the connection information for the current Kernel.
210 210
211 211 Parameters
212 212 ----------
213 213 connection_file : str [optional]
214 214 The connection file to be used. Can be given by absolute path, or
215 215 IPython will search in the security directory of a given profile.
216 216 If run from IPython,
217 217
218 218 If unspecified, the connection file for the currently running
219 219 IPython Kernel will be used, which is only allowed from inside a kernel.
220 220 unpack : bool [default: False]
221 221 if True, return the unpacked dict, otherwise just the string contents
222 222 of the file.
223 223 profile : str [optional]
224 224 The name of the profile to use when searching for the connection file,
225 225 if different from the current IPython session or 'default'.
226 226
227 227
228 228 Returns
229 229 -------
230 230 The connection dictionary of the current kernel, as string or dict,
231 231 depending on `unpack`.
232 232 """
233 233 if connection_file is None:
234 234 # get connection file from current kernel
235 235 cf = get_connection_file()
236 236 else:
237 237 # connection file specified, allow shortnames:
238 238 cf = find_connection_file(connection_file, profile=profile)
239 239
240 240 with open(cf) as f:
241 241 info = f.read()
242 242
243 243 if unpack:
244 244 info = json.loads(info)
245 245 # ensure key is bytes:
246 246 info['key'] = str_to_bytes(info.get('key', ''))
247 247 return info
248 248
249 249
250 250 def connect_qtconsole(connection_file=None, argv=None, profile=None):
251 251 """Connect a qtconsole to the current kernel.
252 252
253 253 This is useful for connecting a second qtconsole to a kernel, or to a
254 254 local notebook.
255 255
256 256 Parameters
257 257 ----------
258 258 connection_file : str [optional]
259 259 The connection file to be used. Can be given by absolute path, or
260 260 IPython will search in the security directory of a given profile.
261 261 If run from IPython,
262 262
263 263 If unspecified, the connection file for the currently running
264 264 IPython Kernel will be used, which is only allowed from inside a kernel.
265 265 argv : list [optional]
266 266 Any extra args to be passed to the console.
267 267 profile : str [optional]
268 268 The name of the profile to use when searching for the connection file,
269 269 if different from the current IPython session or 'default'.
270 270
271 271
272 272 Returns
273 273 -------
274 274 subprocess.Popen instance running the qtconsole frontend
275 275 """
276 276 argv = [] if argv is None else argv
277 277
278 278 if connection_file is None:
279 279 # get connection file from current kernel
280 280 cf = get_connection_file()
281 281 else:
282 282 cf = find_connection_file(connection_file, profile=profile)
283 283
284 284 cmd = ';'.join([
285 285 "from IPython.frontend.qt.console import qtconsoleapp",
286 286 "qtconsoleapp.main()"
287 287 ])
288 288
289 289 return Popen([sys.executable, '-c', cmd, '--existing', cf] + argv, stdout=PIPE, stderr=PIPE)
290 290
291 291
292 292 def tunnel_to_kernel(connection_info, sshserver, sshkey=None):
293 293 """tunnel connections to a kernel via ssh
294 294
295 295 This will open four SSH tunnels from localhost on this machine to the
296 296 ports associated with the kernel. They can be either direct
297 297 localhost-localhost tunnels, or if an intermediate server is necessary,
298 298 the kernel must be listening on a public IP.
299 299
300 300 Parameters
301 301 ----------
302 302 connection_info : dict or str (path)
303 303 Either a connection dict, or the path to a JSON connection file
304 304 sshserver : str
305 305 The ssh sever to use to tunnel to the kernel. Can be a full
306 306 `user@server:port` string. ssh config aliases are respected.
307 307 sshkey : str [optional]
308 308 Path to file containing ssh key to use for authentication.
309 309 Only necessary if your ssh config does not already associate
310 310 a keyfile with the host.
311 311
312 312 Returns
313 313 -------
314 314
315 315 (shell, iopub, stdin, hb) : ints
316 316 The four ports on localhost that have been forwarded to the kernel.
317 317 """
318 318 if isinstance(connection_info, basestring):
319 319 # it's a path, unpack it
320 320 with open(connection_info) as f:
321 321 connection_info = json.loads(f.read())
322 322
323 323 cf = connection_info
324 324
325 325 lports = tunnel.select_random_ports(4)
326 326 rports = cf['shell_port'], cf['iopub_port'], cf['stdin_port'], cf['hb_port']
327 327
328 328 remote_ip = cf['ip']
329 329
330 330 if tunnel.try_passwordless_ssh(sshserver, sshkey):
331 331 password=False
332 332 else:
333 333 password = getpass("SSH Password for %s: "%sshserver)
334 334
335 335 for lp,rp in zip(lports, rports):
336 336 tunnel.ssh_tunnel(lp, rp, sshserver, remote_ip, sshkey, password)
337 337
338 338 return tuple(lports)
339 339
@@ -1,72 +1,72 b''
1 1 """The IPython ZMQ-based parallel computing interface.
2 2
3 3 Authors:
4 4
5 5 * MinRK
6 6 """
7 7 #-----------------------------------------------------------------------------
8 8 # Copyright (C) 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 import os
19 19 import warnings
20 20
21 21 import zmq
22 22
23 23 from IPython.config.configurable import MultipleInstanceError
24 24 from IPython.zmq import check_for_zmq
25 25
26 26 min_pyzmq = '2.1.11'
27 27
28 28 check_for_zmq(min_pyzmq, 'IPython.parallel')
29 29
30 30 from IPython.utils.pickleutil import Reference
31 31
32 32 from .client.asyncresult import *
33 33 from .client.client import Client
34 34 from .client.remotefunction import *
35 35 from .client.view import *
36 36 from .controller.dependency import *
37 37 from .error import *
38 38 from .util import interactive
39 39
40 40 #-----------------------------------------------------------------------------
41 41 # Functions
42 42 #-----------------------------------------------------------------------------
43 43
44 44
45 45 def bind_kernel(**kwargs):
46 46 """Bind an Engine's Kernel to be used as a full IPython kernel.
47 47
48 48 This allows a running Engine to be used simultaneously as a full IPython kernel
49 49 with the QtConsole or other frontends.
50 50
51 51 This function returns immediately.
52 52 """
53 from IPython.zmq.ipkernel import IPKernelApp
53 from IPython.zmq.kernelapp import IPKernelApp
54 54 from IPython.parallel.apps.ipengineapp import IPEngineApp
55 55
56 56 # first check for IPKernelApp, in which case this should be a no-op
57 57 # because there is already a bound kernel
58 58 if IPKernelApp.initialized() and isinstance(IPKernelApp._instance, IPKernelApp):
59 59 return
60 60
61 61 if IPEngineApp.initialized():
62 62 try:
63 63 app = IPEngineApp.instance()
64 64 except MultipleInstanceError:
65 65 pass
66 66 else:
67 67 return app.bind_kernel(**kwargs)
68 68
69 69 raise RuntimeError("bind_kernel be called from an IPEngineApp instance")
70 70
71 71
72 72
@@ -1,399 +1,400 b''
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 """
4 4 The IPython engine application
5 5
6 6 Authors:
7 7
8 8 * Brian Granger
9 9 * MinRK
10 10
11 11 """
12 12
13 13 #-----------------------------------------------------------------------------
14 14 # Copyright (C) 2008-2011 The IPython Development Team
15 15 #
16 16 # Distributed under the terms of the BSD License. The full license is in
17 17 # the file COPYING, distributed as part of this software.
18 18 #-----------------------------------------------------------------------------
19 19
20 20 #-----------------------------------------------------------------------------
21 21 # Imports
22 22 #-----------------------------------------------------------------------------
23 23
24 24 import json
25 25 import os
26 26 import sys
27 27 import time
28 28
29 29 import zmq
30 30 from zmq.eventloop import ioloop
31 31
32 32 from IPython.core.profiledir import ProfileDir
33 33 from IPython.parallel.apps.baseapp import (
34 34 BaseParallelApplication,
35 35 base_aliases,
36 36 base_flags,
37 37 catch_config_error,
38 38 )
39 39 from IPython.zmq.log import EnginePUBHandler
40 from IPython.zmq.ipkernel import Kernel, IPKernelApp
40 from IPython.zmq.ipkernel import Kernel
41 from IPython.zmq.kernelapp import IPKernelApp
41 42 from IPython.zmq.session import (
42 43 Session, session_aliases, session_flags
43 44 )
44 45 from IPython.zmq.zmqshell import ZMQInteractiveShell
45 46
46 47 from IPython.config.configurable import Configurable
47 48
48 49 from IPython.parallel.engine.engine import EngineFactory
49 50 from IPython.parallel.util import disambiguate_ip_address
50 51
51 52 from IPython.utils.importstring import import_item
52 53 from IPython.utils.py3compat import cast_bytes
53 54 from IPython.utils.traitlets import Bool, Unicode, Dict, List, Float, Instance
54 55
55 56
56 57 #-----------------------------------------------------------------------------
57 58 # Module level variables
58 59 #-----------------------------------------------------------------------------
59 60
60 61 #: The default config file name for this application
61 62 default_config_file_name = u'ipengine_config.py'
62 63
63 64 _description = """Start an IPython engine for parallel computing.
64 65
65 66 IPython engines run in parallel and perform computations on behalf of a client
66 67 and controller. A controller needs to be started before the engines. The
67 68 engine can be configured using command line options or using a cluster
68 69 directory. Cluster directories contain config, log and security files and are
69 70 usually located in your ipython directory and named as "profile_name".
70 71 See the `profile` and `profile-dir` options for details.
71 72 """
72 73
73 74 _examples = """
74 75 ipengine --ip=192.168.0.1 --port=1000 # connect to hub at ip and port
75 76 ipengine --log-to-file --log-level=DEBUG # log to a file with DEBUG verbosity
76 77 """
77 78
78 79 #-----------------------------------------------------------------------------
79 80 # MPI configuration
80 81 #-----------------------------------------------------------------------------
81 82
82 83 mpi4py_init = """from mpi4py import MPI as mpi
83 84 mpi.size = mpi.COMM_WORLD.Get_size()
84 85 mpi.rank = mpi.COMM_WORLD.Get_rank()
85 86 """
86 87
87 88
88 89 pytrilinos_init = """from PyTrilinos import Epetra
89 90 class SimpleStruct:
90 91 pass
91 92 mpi = SimpleStruct()
92 93 mpi.rank = 0
93 94 mpi.size = 0
94 95 """
95 96
96 97 class MPI(Configurable):
97 98 """Configurable for MPI initialization"""
98 99 use = Unicode('', config=True,
99 100 help='How to enable MPI (mpi4py, pytrilinos, or empty string to disable).'
100 101 )
101 102
102 103 def _use_changed(self, name, old, new):
103 104 # load default init script if it's not set
104 105 if not self.init_script:
105 106 self.init_script = self.default_inits.get(new, '')
106 107
107 108 init_script = Unicode('', config=True,
108 109 help="Initialization code for MPI")
109 110
110 111 default_inits = Dict({'mpi4py' : mpi4py_init, 'pytrilinos':pytrilinos_init},
111 112 config=True)
112 113
113 114
114 115 #-----------------------------------------------------------------------------
115 116 # Main application
116 117 #-----------------------------------------------------------------------------
117 118 aliases = dict(
118 119 file = 'IPEngineApp.url_file',
119 120 c = 'IPEngineApp.startup_command',
120 121 s = 'IPEngineApp.startup_script',
121 122
122 123 url = 'EngineFactory.url',
123 124 ssh = 'EngineFactory.sshserver',
124 125 sshkey = 'EngineFactory.sshkey',
125 126 ip = 'EngineFactory.ip',
126 127 transport = 'EngineFactory.transport',
127 128 port = 'EngineFactory.regport',
128 129 location = 'EngineFactory.location',
129 130
130 131 timeout = 'EngineFactory.timeout',
131 132
132 133 mpi = 'MPI.use',
133 134
134 135 )
135 136 aliases.update(base_aliases)
136 137 aliases.update(session_aliases)
137 138 flags = {}
138 139 flags.update(base_flags)
139 140 flags.update(session_flags)
140 141
141 142 class IPEngineApp(BaseParallelApplication):
142 143
143 144 name = 'ipengine'
144 145 description = _description
145 146 examples = _examples
146 147 config_file_name = Unicode(default_config_file_name)
147 148 classes = List([ZMQInteractiveShell, ProfileDir, Session, EngineFactory, Kernel, MPI])
148 149
149 150 startup_script = Unicode(u'', config=True,
150 151 help='specify a script to be run at startup')
151 152 startup_command = Unicode('', config=True,
152 153 help='specify a command to be run at startup')
153 154
154 155 url_file = Unicode(u'', config=True,
155 156 help="""The full location of the file containing the connection information for
156 157 the controller. If this is not given, the file must be in the
157 158 security directory of the cluster directory. This location is
158 159 resolved using the `profile` or `profile_dir` options.""",
159 160 )
160 161 wait_for_url_file = Float(5, config=True,
161 162 help="""The maximum number of seconds to wait for url_file to exist.
162 163 This is useful for batch-systems and shared-filesystems where the
163 164 controller and engine are started at the same time and it
164 165 may take a moment for the controller to write the connector files.""")
165 166
166 167 url_file_name = Unicode(u'ipcontroller-engine.json', config=True)
167 168
168 169 def _cluster_id_changed(self, name, old, new):
169 170 if new:
170 171 base = 'ipcontroller-%s' % new
171 172 else:
172 173 base = 'ipcontroller'
173 174 self.url_file_name = "%s-engine.json" % base
174 175
175 176 log_url = Unicode('', config=True,
176 177 help="""The URL for the iploggerapp instance, for forwarding
177 178 logging to a central location.""")
178 179
179 180 # an IPKernelApp instance, used to setup listening for shell frontends
180 181 kernel_app = Instance(IPKernelApp)
181 182
182 183 aliases = Dict(aliases)
183 184 flags = Dict(flags)
184 185
185 186 @property
186 187 def kernel(self):
187 188 """allow access to the Kernel object, so I look like IPKernelApp"""
188 189 return self.engine.kernel
189 190
190 191 def find_url_file(self):
191 192 """Set the url file.
192 193
193 194 Here we don't try to actually see if it exists for is valid as that
194 195 is hadled by the connection logic.
195 196 """
196 197 config = self.config
197 198 # Find the actual controller key file
198 199 if not self.url_file:
199 200 self.url_file = os.path.join(
200 201 self.profile_dir.security_dir,
201 202 self.url_file_name
202 203 )
203 204
204 205 def load_connector_file(self):
205 206 """load config from a JSON connector file,
206 207 at a *lower* priority than command-line/config files.
207 208 """
208 209
209 210 self.log.info("Loading url_file %r", self.url_file)
210 211 config = self.config
211 212
212 213 with open(self.url_file) as f:
213 214 d = json.loads(f.read())
214 215
215 216 # allow hand-override of location for disambiguation
216 217 # and ssh-server
217 218 try:
218 219 config.EngineFactory.location
219 220 except AttributeError:
220 221 config.EngineFactory.location = d['location']
221 222
222 223 try:
223 224 config.EngineFactory.sshserver
224 225 except AttributeError:
225 226 config.EngineFactory.sshserver = d.get('ssh')
226 227
227 228 location = config.EngineFactory.location
228 229
229 230 proto, ip = d['interface'].split('://')
230 231 ip = disambiguate_ip_address(ip, location)
231 232 d['interface'] = '%s://%s' % (proto, ip)
232 233
233 234 # DO NOT allow override of basic URLs, serialization, or exec_key
234 235 # JSON file takes top priority there
235 236 config.Session.key = cast_bytes(d['exec_key'])
236 237
237 238 config.EngineFactory.url = d['interface'] + ':%i' % d['registration']
238 239
239 240 config.Session.packer = d['pack']
240 241 config.Session.unpacker = d['unpack']
241 242
242 243 self.log.debug("Config changed:")
243 244 self.log.debug("%r", config)
244 245 self.connection_info = d
245 246
246 247 def bind_kernel(self, **kwargs):
247 248 """Promote engine to listening kernel, accessible to frontends."""
248 249 if self.kernel_app is not None:
249 250 return
250 251
251 252 self.log.info("Opening ports for direct connections as an IPython kernel")
252 253
253 254 kernel = self.kernel
254 255
255 256 kwargs.setdefault('config', self.config)
256 257 kwargs.setdefault('log', self.log)
257 258 kwargs.setdefault('profile_dir', self.profile_dir)
258 259 kwargs.setdefault('session', self.engine.session)
259 260
260 261 app = self.kernel_app = IPKernelApp(**kwargs)
261 262
262 263 # allow IPKernelApp.instance():
263 264 IPKernelApp._instance = app
264 265
265 266 app.init_connection_file()
266 267 # relevant contents of init_sockets:
267 268
268 269 app.shell_port = app._bind_socket(kernel.shell_streams[0], app.shell_port)
269 270 app.log.debug("shell ROUTER Channel on port: %i", app.shell_port)
270 271
271 272 app.iopub_port = app._bind_socket(kernel.iopub_socket, app.iopub_port)
272 273 app.log.debug("iopub PUB Channel on port: %i", app.iopub_port)
273 274
274 275 kernel.stdin_socket = self.engine.context.socket(zmq.ROUTER)
275 276 app.stdin_port = app._bind_socket(kernel.stdin_socket, app.stdin_port)
276 277 app.log.debug("stdin ROUTER Channel on port: %i", app.stdin_port)
277 278
278 279 # start the heartbeat, and log connection info:
279 280
280 281 app.init_heartbeat()
281 282
282 283 app.log_connection_info()
283 284 app.write_connection_file()
284 285
285 286
286 287 def init_engine(self):
287 288 # This is the working dir by now.
288 289 sys.path.insert(0, '')
289 290 config = self.config
290 291 # print config
291 292 self.find_url_file()
292 293
293 294 # was the url manually specified?
294 295 keys = set(self.config.EngineFactory.keys())
295 296 keys = keys.union(set(self.config.RegistrationFactory.keys()))
296 297
297 298 if keys.intersection(set(['ip', 'url', 'port'])):
298 299 # Connection info was specified, don't wait for the file
299 300 url_specified = True
300 301 self.wait_for_url_file = 0
301 302 else:
302 303 url_specified = False
303 304
304 305 if self.wait_for_url_file and not os.path.exists(self.url_file):
305 306 self.log.warn("url_file %r not found", self.url_file)
306 307 self.log.warn("Waiting up to %.1f seconds for it to arrive.", self.wait_for_url_file)
307 308 tic = time.time()
308 309 while not os.path.exists(self.url_file) and (time.time()-tic < self.wait_for_url_file):
309 310 # wait for url_file to exist, or until time limit
310 311 time.sleep(0.1)
311 312
312 313 if os.path.exists(self.url_file):
313 314 self.load_connector_file()
314 315 elif not url_specified:
315 316 self.log.fatal("Fatal: url file never arrived: %s", self.url_file)
316 317 self.exit(1)
317 318
318 319
319 320 try:
320 321 exec_lines = config.IPKernelApp.exec_lines
321 322 except AttributeError:
322 323 try:
323 324 exec_lines = config.InteractiveShellApp.exec_lines
324 325 except AttributeError:
325 326 exec_lines = config.IPKernelApp.exec_lines = []
326 327 try:
327 328 exec_files = config.IPKernelApp.exec_files
328 329 except AttributeError:
329 330 try:
330 331 exec_files = config.InteractiveShellApp.exec_files
331 332 except AttributeError:
332 333 exec_files = config.IPKernelApp.exec_files = []
333 334
334 335 if self.startup_script:
335 336 exec_files.append(self.startup_script)
336 337 if self.startup_command:
337 338 exec_lines.append(self.startup_command)
338 339
339 340 # Create the underlying shell class and Engine
340 341 # shell_class = import_item(self.master_config.Global.shell_class)
341 342 # print self.config
342 343 try:
343 344 self.engine = EngineFactory(config=config, log=self.log,
344 345 connection_info=self.connection_info,
345 346 )
346 347 except:
347 348 self.log.error("Couldn't start the Engine", exc_info=True)
348 349 self.exit(1)
349 350
350 351 def forward_logging(self):
351 352 if self.log_url:
352 353 self.log.info("Forwarding logging to %s", self.log_url)
353 354 context = self.engine.context
354 355 lsock = context.socket(zmq.PUB)
355 356 lsock.connect(self.log_url)
356 357 handler = EnginePUBHandler(self.engine, lsock)
357 358 handler.setLevel(self.log_level)
358 359 self.log.addHandler(handler)
359 360
360 361 def init_mpi(self):
361 362 global mpi
362 363 self.mpi = MPI(config=self.config)
363 364
364 365 mpi_import_statement = self.mpi.init_script
365 366 if mpi_import_statement:
366 367 try:
367 368 self.log.info("Initializing MPI:")
368 369 self.log.info(mpi_import_statement)
369 370 exec mpi_import_statement in globals()
370 371 except:
371 372 mpi = None
372 373 else:
373 374 mpi = None
374 375
375 376 @catch_config_error
376 377 def initialize(self, argv=None):
377 378 super(IPEngineApp, self).initialize(argv)
378 379 self.init_mpi()
379 380 self.init_engine()
380 381 self.forward_logging()
381 382
382 383 def start(self):
383 384 self.engine.start()
384 385 try:
385 386 self.engine.loop.start()
386 387 except KeyboardInterrupt:
387 388 self.log.critical("Engine Interrupted, shutting down...\n")
388 389
389 390
390 391 def launch_new_instance():
391 392 """Create and run the IPython engine"""
392 393 app = IPEngineApp.instance()
393 394 app.initialize()
394 395 app.start()
395 396
396 397
397 398 if __name__ == '__main__':
398 399 launch_new_instance()
399 400
@@ -1,304 +1,305 b''
1 1 """A simple engine that talks to a controller over 0MQ.
2 2 it handles registration, etc. and launches a kernel
3 3 connected to the Controller's Schedulers.
4 4
5 5 Authors:
6 6
7 7 * Min RK
8 8 """
9 9 #-----------------------------------------------------------------------------
10 10 # Copyright (C) 2010-2011 The IPython Development Team
11 11 #
12 12 # Distributed under the terms of the BSD License. The full license is in
13 13 # the file COPYING, distributed as part of this software.
14 14 #-----------------------------------------------------------------------------
15 15
16 16 from __future__ import print_function
17 17
18 18 import sys
19 19 import time
20 20 from getpass import getpass
21 21
22 22 import zmq
23 23 from zmq.eventloop import ioloop, zmqstream
24 24
25 25 from IPython.external.ssh import tunnel
26 26 # internal
27 27 from IPython.utils.localinterfaces import LOCALHOST
28 28 from IPython.utils.traitlets import (
29 29 Instance, Dict, Integer, Type, Float, Integer, Unicode, CBytes, Bool
30 30 )
31 31 from IPython.utils.py3compat import cast_bytes
32 32
33 33 from IPython.parallel.controller.heartmonitor import Heart
34 34 from IPython.parallel.factory import RegistrationFactory
35 35 from IPython.parallel.util import disambiguate_url
36 36
37 37 from IPython.zmq.session import Message
38 from IPython.zmq.ipkernel import Kernel, IPKernelApp
38 from IPython.zmq.ipkernel import Kernel
39 from IPython.zmq.kernelapp import IPKernelApp
39 40
40 41 class EngineFactory(RegistrationFactory):
41 42 """IPython engine"""
42 43
43 44 # configurables:
44 45 out_stream_factory=Type('IPython.zmq.iostream.OutStream', config=True,
45 46 help="""The OutStream for handling stdout/err.
46 47 Typically 'IPython.zmq.iostream.OutStream'""")
47 48 display_hook_factory=Type('IPython.zmq.displayhook.ZMQDisplayHook', config=True,
48 49 help="""The class for handling displayhook.
49 50 Typically 'IPython.zmq.displayhook.ZMQDisplayHook'""")
50 51 location=Unicode(config=True,
51 52 help="""The location (an IP address) of the controller. This is
52 53 used for disambiguating URLs, to determine whether
53 54 loopback should be used to connect or the public address.""")
54 55 timeout=Float(5.0, config=True,
55 56 help="""The time (in seconds) to wait for the Controller to respond
56 57 to registration requests before giving up.""")
57 58 max_heartbeat_misses=Integer(50, config=True,
58 59 help="""The maximum number of times a check for the heartbeat ping of a
59 60 controller can be missed before shutting down the engine.
60 61
61 62 If set to 0, the check is disabled.""")
62 63 sshserver=Unicode(config=True,
63 64 help="""The SSH server to use for tunneling connections to the Controller.""")
64 65 sshkey=Unicode(config=True,
65 66 help="""The SSH private key file to use when tunneling connections to the Controller.""")
66 67 paramiko=Bool(sys.platform == 'win32', config=True,
67 68 help="""Whether to use paramiko instead of openssh for tunnels.""")
68 69
69 70
70 71 # not configurable:
71 72 connection_info = Dict()
72 73 user_ns = Dict()
73 74 id = Integer(allow_none=True)
74 75 registrar = Instance('zmq.eventloop.zmqstream.ZMQStream')
75 76 kernel = Instance(Kernel)
76 77 hb_check_period=Integer()
77 78
78 79 # States for the heartbeat monitoring
79 80 # Initial values for monitored and pinged must satisfy "monitored > pinged == False" so that
80 81 # during the first check no "missed" ping is reported. Must be floats for Python 3 compatibility.
81 82 _hb_last_pinged = 0.0
82 83 _hb_last_monitored = 0.0
83 84 _hb_missed_beats = 0
84 85 # The zmq Stream which receives the pings from the Heart
85 86 _hb_listener = None
86 87
87 88 bident = CBytes()
88 89 ident = Unicode()
89 90 def _ident_changed(self, name, old, new):
90 91 self.bident = cast_bytes(new)
91 92 using_ssh=Bool(False)
92 93
93 94
94 95 def __init__(self, **kwargs):
95 96 super(EngineFactory, self).__init__(**kwargs)
96 97 self.ident = self.session.session
97 98
98 99 def init_connector(self):
99 100 """construct connection function, which handles tunnels."""
100 101 self.using_ssh = bool(self.sshkey or self.sshserver)
101 102
102 103 if self.sshkey and not self.sshserver:
103 104 # We are using ssh directly to the controller, tunneling localhost to localhost
104 105 self.sshserver = self.url.split('://')[1].split(':')[0]
105 106
106 107 if self.using_ssh:
107 108 if tunnel.try_passwordless_ssh(self.sshserver, self.sshkey, self.paramiko):
108 109 password=False
109 110 else:
110 111 password = getpass("SSH Password for %s: "%self.sshserver)
111 112 else:
112 113 password = False
113 114
114 115 def connect(s, url):
115 116 url = disambiguate_url(url, self.location)
116 117 if self.using_ssh:
117 118 self.log.debug("Tunneling connection to %s via %s", url, self.sshserver)
118 119 return tunnel.tunnel_connection(s, url, self.sshserver,
119 120 keyfile=self.sshkey, paramiko=self.paramiko,
120 121 password=password,
121 122 )
122 123 else:
123 124 return s.connect(url)
124 125
125 126 def maybe_tunnel(url):
126 127 """like connect, but don't complete the connection (for use by heartbeat)"""
127 128 url = disambiguate_url(url, self.location)
128 129 if self.using_ssh:
129 130 self.log.debug("Tunneling connection to %s via %s", url, self.sshserver)
130 131 url,tunnelobj = tunnel.open_tunnel(url, self.sshserver,
131 132 keyfile=self.sshkey, paramiko=self.paramiko,
132 133 password=password,
133 134 )
134 135 return str(url)
135 136 return connect, maybe_tunnel
136 137
137 138 def register(self):
138 139 """send the registration_request"""
139 140
140 141 self.log.info("Registering with controller at %s"%self.url)
141 142 ctx = self.context
142 143 connect,maybe_tunnel = self.init_connector()
143 144 reg = ctx.socket(zmq.DEALER)
144 145 reg.setsockopt(zmq.IDENTITY, self.bident)
145 146 connect(reg, self.url)
146 147 self.registrar = zmqstream.ZMQStream(reg, self.loop)
147 148
148 149
149 150 content = dict(uuid=self.ident)
150 151 self.registrar.on_recv(lambda msg: self.complete_registration(msg, connect, maybe_tunnel))
151 152 # print (self.session.key)
152 153 self.session.send(self.registrar, "registration_request", content=content)
153 154
154 155 def _report_ping(self, msg):
155 156 """Callback for when the heartmonitor.Heart receives a ping"""
156 157 #self.log.debug("Received a ping: %s", msg)
157 158 self._hb_last_pinged = time.time()
158 159
159 160 def complete_registration(self, msg, connect, maybe_tunnel):
160 161 # print msg
161 162 self._abort_dc.stop()
162 163 ctx = self.context
163 164 loop = self.loop
164 165 identity = self.bident
165 166 idents,msg = self.session.feed_identities(msg)
166 167 msg = self.session.unserialize(msg)
167 168 content = msg['content']
168 169 info = self.connection_info
169 170
170 171 def url(key):
171 172 """get zmq url for given channel"""
172 173 return str(info["interface"] + ":%i" % info[key])
173 174
174 175 if content['status'] == 'ok':
175 176 self.id = int(content['id'])
176 177
177 178 # launch heartbeat
178 179 # possibly forward hb ports with tunnels
179 180 hb_ping = maybe_tunnel(url('hb_ping'))
180 181 hb_pong = maybe_tunnel(url('hb_pong'))
181 182
182 183 hb_monitor = None
183 184 if self.max_heartbeat_misses > 0:
184 185 # Add a monitor socket which will record the last time a ping was seen
185 186 mon = self.context.socket(zmq.SUB)
186 187 mport = mon.bind_to_random_port('tcp://%s' % LOCALHOST)
187 188 mon.setsockopt(zmq.SUBSCRIBE, b"")
188 189 self._hb_listener = zmqstream.ZMQStream(mon, self.loop)
189 190 self._hb_listener.on_recv(self._report_ping)
190 191
191 192
192 193 hb_monitor = "tcp://%s:%i" % (LOCALHOST, mport)
193 194
194 195 heart = Heart(hb_ping, hb_pong, hb_monitor , heart_id=identity)
195 196 heart.start()
196 197
197 198 # create Shell Connections (MUX, Task, etc.):
198 199 shell_addrs = url('mux'), url('task')
199 200
200 201 # Use only one shell stream for mux and tasks
201 202 stream = zmqstream.ZMQStream(ctx.socket(zmq.ROUTER), loop)
202 203 stream.setsockopt(zmq.IDENTITY, identity)
203 204 shell_streams = [stream]
204 205 for addr in shell_addrs:
205 206 connect(stream, addr)
206 207
207 208 # control stream:
208 209 control_addr = url('control')
209 210 control_stream = zmqstream.ZMQStream(ctx.socket(zmq.ROUTER), loop)
210 211 control_stream.setsockopt(zmq.IDENTITY, identity)
211 212 connect(control_stream, control_addr)
212 213
213 214 # create iopub stream:
214 215 iopub_addr = url('iopub')
215 216 iopub_socket = ctx.socket(zmq.PUB)
216 217 iopub_socket.setsockopt(zmq.IDENTITY, identity)
217 218 connect(iopub_socket, iopub_addr)
218 219
219 220 # disable history:
220 221 self.config.HistoryManager.hist_file = ':memory:'
221 222
222 223 # Redirect input streams and set a display hook.
223 224 if self.out_stream_factory:
224 225 sys.stdout = self.out_stream_factory(self.session, iopub_socket, u'stdout')
225 226 sys.stdout.topic = cast_bytes('engine.%i.stdout' % self.id)
226 227 sys.stderr = self.out_stream_factory(self.session, iopub_socket, u'stderr')
227 228 sys.stderr.topic = cast_bytes('engine.%i.stderr' % self.id)
228 229 if self.display_hook_factory:
229 230 sys.displayhook = self.display_hook_factory(self.session, iopub_socket)
230 231 sys.displayhook.topic = cast_bytes('engine.%i.pyout' % self.id)
231 232
232 233 self.kernel = Kernel(config=self.config, int_id=self.id, ident=self.ident, session=self.session,
233 234 control_stream=control_stream, shell_streams=shell_streams, iopub_socket=iopub_socket,
234 235 loop=loop, user_ns=self.user_ns, log=self.log)
235 236
236 237 self.kernel.shell.display_pub.topic = cast_bytes('engine.%i.displaypub' % self.id)
237 238
238 239
239 240 # periodically check the heartbeat pings of the controller
240 241 # Should be started here and not in "start()" so that the right period can be taken
241 242 # from the hubs HeartBeatMonitor.period
242 243 if self.max_heartbeat_misses > 0:
243 244 # Use a slightly bigger check period than the hub signal period to not warn unnecessary
244 245 self.hb_check_period = int(content['hb_period'])+10
245 246 self.log.info("Starting to monitor the heartbeat signal from the hub every %i ms." , self.hb_check_period)
246 247 self._hb_reporter = ioloop.PeriodicCallback(self._hb_monitor, self.hb_check_period, self.loop)
247 248 self._hb_reporter.start()
248 249 else:
249 250 self.log.info("Monitoring of the heartbeat signal from the hub is not enabled.")
250 251
251 252
252 253 # FIXME: This is a hack until IPKernelApp and IPEngineApp can be fully merged
253 254 app = IPKernelApp(config=self.config, shell=self.kernel.shell, kernel=self.kernel, log=self.log)
254 255 app.init_profile_dir()
255 256 app.init_code()
256 257
257 258 self.kernel.start()
258 259 else:
259 260 self.log.fatal("Registration Failed: %s"%msg)
260 261 raise Exception("Registration Failed: %s"%msg)
261 262
262 263 self.log.info("Completed registration with id %i"%self.id)
263 264
264 265
265 266 def abort(self):
266 267 self.log.fatal("Registration timed out after %.1f seconds"%self.timeout)
267 268 if self.url.startswith('127.'):
268 269 self.log.fatal("""
269 270 If the controller and engines are not on the same machine,
270 271 you will have to instruct the controller to listen on an external IP (in ipcontroller_config.py):
271 272 c.HubFactory.ip='*' # for all interfaces, internal and external
272 273 c.HubFactory.ip='192.168.1.101' # or any interface that the engines can see
273 274 or tunnel connections via ssh.
274 275 """)
275 276 self.session.send(self.registrar, "unregistration_request", content=dict(id=self.id))
276 277 time.sleep(1)
277 278 sys.exit(255)
278 279
279 280 def _hb_monitor(self):
280 281 """Callback to monitor the heartbeat from the controller"""
281 282 self._hb_listener.flush()
282 283 if self._hb_last_monitored > self._hb_last_pinged:
283 284 self._hb_missed_beats += 1
284 285 self.log.warn("No heartbeat in the last %s ms (%s time(s) in a row).", self.hb_check_period, self._hb_missed_beats)
285 286 else:
286 287 #self.log.debug("Heartbeat received (after missing %s beats).", self._hb_missed_beats)
287 288 self._hb_missed_beats = 0
288 289
289 290 if self._hb_missed_beats >= self.max_heartbeat_misses:
290 291 self.log.fatal("Maximum number of heartbeats misses reached (%s times %s ms), shutting down.",
291 292 self.max_heartbeat_misses, self.hb_check_period)
292 293 self.session.send(self.registrar, "unregistration_request", content=dict(id=self.id))
293 294 self.loop.stop()
294 295
295 296 self._hb_last_monitored = time.time()
296 297
297 298
298 299 def start(self):
299 300 dc = ioloop.DelayedCallback(self.register, 0, self.loop)
300 301 dc.start()
301 302 self._abort_dc = ioloop.DelayedCallback(self.abort, self.timeout*1000, self.loop)
302 303 self._abort_dc.start()
303 304
304 305
@@ -1,923 +1,779 b''
1 1 #!/usr/bin/env python
2 2 """A simple interactive kernel that talks to a frontend over 0MQ.
3 3
4 4 Things to do:
5 5
6 6 * Implement `set_parent` logic. Right before doing exec, the Kernel should
7 7 call set_parent on all the PUB objects with the message about to be executed.
8 8 * Implement random port and security key logic.
9 9 * Implement control messages.
10 10 * Implement event loop and poll version.
11 11 """
12 12
13 13 #-----------------------------------------------------------------------------
14 14 # Imports
15 15 #-----------------------------------------------------------------------------
16 16 from __future__ import print_function
17 17
18 18 # Standard library imports
19 19 import __builtin__
20 import atexit
21 20 import sys
22 21 import time
23 22 import traceback
24 23 import logging
25 24 import uuid
26 25
27 26 from datetime import datetime
28 27 from signal import (
29 28 signal, getsignal, default_int_handler, SIGINT, SIG_IGN
30 29 )
31 30
32 31 # System library imports
33 32 import zmq
34 33 from zmq.eventloop import ioloop
35 34 from zmq.eventloop.zmqstream import ZMQStream
36 35
37 36 # Local imports
38 37 from IPython.config.configurable import Configurable
39 from IPython.config.application import boolean_flag, catch_config_error
40 from IPython.core.application import ProfileDir
41 38 from IPython.core.error import StdinNotImplementedError
42 39 from IPython.core import release
43 from IPython.core.shellapp import (
44 InteractiveShellApp, shell_flags, shell_aliases
45 )
46 40 from IPython.utils import io
47 41 from IPython.utils import py3compat
48 from IPython.utils.frame import extract_module_locals
49 42 from IPython.utils.jsonutil import json_clean
50 43 from IPython.utils.traitlets import (
51 44 Any, Instance, Float, Dict, CaselessStrEnum, List, Set, Integer, Unicode,
52 45 Type
53 46 )
54 47
55 from kernelapp import KernelApp, kernel_flags, kernel_aliases
56 48 from serialize import serialize_object, unpack_apply_message
57 from session import Session, Message
49 from session import Session
58 50 from zmqshell import ZMQInteractiveShell
59 51
60 52
61 53 #-----------------------------------------------------------------------------
62 54 # Main kernel class
63 55 #-----------------------------------------------------------------------------
64 56
65 57 protocol_version = list(release.kernel_protocol_version_info)
66 58 ipython_version = list(release.version_info)
67 59 language_version = list(sys.version_info[:3])
68 60
69 61
70 62 class Kernel(Configurable):
71 63
72 64 #---------------------------------------------------------------------------
73 65 # Kernel interface
74 66 #---------------------------------------------------------------------------
75 67
76 68 # attribute to override with a GUI
77 69 eventloop = Any(None)
78 70 def _eventloop_changed(self, name, old, new):
79 71 """schedule call to eventloop from IOLoop"""
80 72 loop = ioloop.IOLoop.instance()
81 73 loop.add_timeout(time.time()+0.1, self.enter_eventloop)
82 74
83 75 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
84 76 shell_class = Type(ZMQInteractiveShell)
85 77
86 78 session = Instance(Session)
87 79 profile_dir = Instance('IPython.core.profiledir.ProfileDir')
88 80 shell_streams = List()
89 81 control_stream = Instance(ZMQStream)
90 82 iopub_socket = Instance(zmq.Socket)
91 83 stdin_socket = Instance(zmq.Socket)
92 84 log = Instance(logging.Logger)
93 85
94 86 user_module = Any()
95 87 def _user_module_changed(self, name, old, new):
96 88 if self.shell is not None:
97 89 self.shell.user_module = new
98 90
99 91 user_ns = Dict(default_value=None)
100 92 def _user_ns_changed(self, name, old, new):
101 93 if self.shell is not None:
102 94 self.shell.user_ns = new
103 95 self.shell.init_user_ns()
104 96
105 97 # identities:
106 98 int_id = Integer(-1)
107 99 ident = Unicode()
108 100
109 101 def _ident_default(self):
110 102 return unicode(uuid.uuid4())
111 103
112 104
113 105 # Private interface
114 106
115 107 # Time to sleep after flushing the stdout/err buffers in each execute
116 108 # cycle. While this introduces a hard limit on the minimal latency of the
117 109 # execute cycle, it helps prevent output synchronization problems for
118 110 # clients.
119 111 # Units are in seconds. The minimum zmq latency on local host is probably
120 112 # ~150 microseconds, set this to 500us for now. We may need to increase it
121 113 # a little if it's not enough after more interactive testing.
122 114 _execute_sleep = Float(0.0005, config=True)
123 115
124 116 # Frequency of the kernel's event loop.
125 117 # Units are in seconds, kernel subclasses for GUI toolkits may need to
126 118 # adapt to milliseconds.
127 119 _poll_interval = Float(0.05, config=True)
128 120
129 121 # If the shutdown was requested over the network, we leave here the
130 122 # necessary reply message so it can be sent by our registered atexit
131 123 # handler. This ensures that the reply is only sent to clients truly at
132 124 # the end of our shutdown process (which happens after the underlying
133 125 # IPython shell's own shutdown).
134 126 _shutdown_message = None
135 127
136 128 # This is a dict of port number that the kernel is listening on. It is set
137 129 # by record_ports and used by connect_request.
138 130 _recorded_ports = Dict()
139 131
140 132 # A reference to the Python builtin 'raw_input' function.
141 133 # (i.e., __builtin__.raw_input for Python 2.7, builtins.input for Python 3)
142 134 _sys_raw_input = Any()
143 135
144 136 # set of aborted msg_ids
145 137 aborted = Set()
146 138
147 139
148 140 def __init__(self, **kwargs):
149 141 super(Kernel, self).__init__(**kwargs)
150 142
151 143 # Initialize the InteractiveShell subclass
152 144 self.shell = self.shell_class.instance(config=self.config,
153 145 profile_dir = self.profile_dir,
154 146 user_module = self.user_module,
155 147 user_ns = self.user_ns,
156 148 )
157 149 self.shell.displayhook.session = self.session
158 150 self.shell.displayhook.pub_socket = self.iopub_socket
159 151 self.shell.displayhook.topic = self._topic('pyout')
160 152 self.shell.display_pub.session = self.session
161 153 self.shell.display_pub.pub_socket = self.iopub_socket
162 154 self.shell.data_pub.session = self.session
163 155 self.shell.data_pub.pub_socket = self.iopub_socket
164 156
165 157 # TMP - hack while developing
166 158 self.shell._reply_content = None
167 159
168 160 # Build dict of handlers for message types
169 161 msg_types = [ 'execute_request', 'complete_request',
170 162 'object_info_request', 'history_request',
171 163 'kernel_info_request',
172 164 'connect_request', 'shutdown_request',
173 165 'apply_request',
174 166 ]
175 167 self.shell_handlers = {}
176 168 for msg_type in msg_types:
177 169 self.shell_handlers[msg_type] = getattr(self, msg_type)
178 170
179 171 control_msg_types = msg_types + [ 'clear_request', 'abort_request' ]
180 172 self.control_handlers = {}
181 173 for msg_type in control_msg_types:
182 174 self.control_handlers[msg_type] = getattr(self, msg_type)
183 175
184 176 def dispatch_control(self, msg):
185 177 """dispatch control requests"""
186 178 idents,msg = self.session.feed_identities(msg, copy=False)
187 179 try:
188 180 msg = self.session.unserialize(msg, content=True, copy=False)
189 181 except:
190 182 self.log.error("Invalid Control Message", exc_info=True)
191 183 return
192 184
193 185 self.log.debug("Control received: %s", msg)
194 186
195 187 header = msg['header']
196 188 msg_id = header['msg_id']
197 189 msg_type = header['msg_type']
198 190
199 191 handler = self.control_handlers.get(msg_type, None)
200 192 if handler is None:
201 193 self.log.error("UNKNOWN CONTROL MESSAGE TYPE: %r", msg_type)
202 194 else:
203 195 try:
204 196 handler(self.control_stream, idents, msg)
205 197 except Exception:
206 198 self.log.error("Exception in control handler:", exc_info=True)
207 199
208 200 def dispatch_shell(self, stream, msg):
209 201 """dispatch shell requests"""
210 202 # flush control requests first
211 203 if self.control_stream:
212 204 self.control_stream.flush()
213 205
214 206 idents,msg = self.session.feed_identities(msg, copy=False)
215 207 try:
216 208 msg = self.session.unserialize(msg, content=True, copy=False)
217 209 except:
218 210 self.log.error("Invalid Message", exc_info=True)
219 211 return
220 212
221 213 header = msg['header']
222 214 msg_id = header['msg_id']
223 215 msg_type = msg['header']['msg_type']
224 216
225 217 # Print some info about this message and leave a '--->' marker, so it's
226 218 # easier to trace visually the message chain when debugging. Each
227 219 # handler prints its message at the end.
228 220 self.log.debug('\n*** MESSAGE TYPE:%s***', msg_type)
229 221 self.log.debug(' Content: %s\n --->\n ', msg['content'])
230 222
231 223 if msg_id in self.aborted:
232 224 self.aborted.remove(msg_id)
233 225 # is it safe to assume a msg_id will not be resubmitted?
234 226 reply_type = msg_type.split('_')[0] + '_reply'
235 227 status = {'status' : 'aborted'}
236 228 md = {'engine' : self.ident}
237 229 md.update(status)
238 230 reply_msg = self.session.send(stream, reply_type, metadata=md,
239 231 content=status, parent=msg, ident=idents)
240 232 return
241 233
242 234 handler = self.shell_handlers.get(msg_type, None)
243 235 if handler is None:
244 236 self.log.error("UNKNOWN MESSAGE TYPE: %r", msg_type)
245 237 else:
246 238 # ensure default_int_handler during handler call
247 239 sig = signal(SIGINT, default_int_handler)
248 240 try:
249 241 handler(stream, idents, msg)
250 242 except Exception:
251 243 self.log.error("Exception in message handler:", exc_info=True)
252 244 finally:
253 245 signal(SIGINT, sig)
254 246
255 247 def enter_eventloop(self):
256 248 """enter eventloop"""
257 249 self.log.info("entering eventloop")
258 250 # restore default_int_handler
259 251 signal(SIGINT, default_int_handler)
260 252 while self.eventloop is not None:
261 253 try:
262 254 self.eventloop(self)
263 255 except KeyboardInterrupt:
264 256 # Ctrl-C shouldn't crash the kernel
265 257 self.log.error("KeyboardInterrupt caught in kernel")
266 258 continue
267 259 else:
268 260 # eventloop exited cleanly, this means we should stop (right?)
269 261 self.eventloop = None
270 262 break
271 263 self.log.info("exiting eventloop")
272 264
273 265 def start(self):
274 266 """register dispatchers for streams"""
275 267 self.shell.exit_now = False
276 268 if self.control_stream:
277 269 self.control_stream.on_recv(self.dispatch_control, copy=False)
278 270
279 271 def make_dispatcher(stream):
280 272 def dispatcher(msg):
281 273 return self.dispatch_shell(stream, msg)
282 274 return dispatcher
283 275
284 276 for s in self.shell_streams:
285 277 s.on_recv(make_dispatcher(s), copy=False)
286 278
287 279 def do_one_iteration(self):
288 280 """step eventloop just once"""
289 281 if self.control_stream:
290 282 self.control_stream.flush()
291 283 for stream in self.shell_streams:
292 284 # handle at most one request per iteration
293 285 stream.flush(zmq.POLLIN, 1)
294 286 stream.flush(zmq.POLLOUT)
295 287
296 288
297 289 def record_ports(self, ports):
298 290 """Record the ports that this kernel is using.
299 291
300 292 The creator of the Kernel instance must call this methods if they
301 293 want the :meth:`connect_request` method to return the port numbers.
302 294 """
303 295 self._recorded_ports = ports
304 296
305 297 #---------------------------------------------------------------------------
306 298 # Kernel request handlers
307 299 #---------------------------------------------------------------------------
308 300
309 301 def _make_metadata(self, other=None):
310 302 """init metadata dict, for execute/apply_reply"""
311 303 new_md = {
312 304 'dependencies_met' : True,
313 305 'engine' : self.ident,
314 306 'started': datetime.now(),
315 307 }
316 308 if other:
317 309 new_md.update(other)
318 310 return new_md
319 311
320 312 def _publish_pyin(self, code, parent, execution_count):
321 313 """Publish the code request on the pyin stream."""
322 314
323 315 self.session.send(self.iopub_socket, u'pyin',
324 316 {u'code':code, u'execution_count': execution_count},
325 317 parent=parent, ident=self._topic('pyin')
326 318 )
327 319
328 320 def _publish_status(self, status, parent=None):
329 321 """send status (busy/idle) on IOPub"""
330 322 self.session.send(self.iopub_socket,
331 323 u'status',
332 324 {u'execution_state': status},
333 325 parent=parent,
334 326 ident=self._topic('status'),
335 327 )
336 328
337 329
338 330 def execute_request(self, stream, ident, parent):
339 331 """handle an execute_request"""
340 332
341 333 self._publish_status(u'busy', parent)
342 334
343 335 try:
344 336 content = parent[u'content']
345 337 code = content[u'code']
346 338 silent = content[u'silent']
347 339 store_history = content.get(u'store_history', not silent)
348 340 except:
349 341 self.log.error("Got bad msg: ")
350 342 self.log.error("%s", parent)
351 343 return
352 344
353 345 md = self._make_metadata(parent['metadata'])
354 346
355 347 shell = self.shell # we'll need this a lot here
356 348
357 349 # Replace raw_input. Note that is not sufficient to replace
358 350 # raw_input in the user namespace.
359 351 if content.get('allow_stdin', False):
360 352 raw_input = lambda prompt='': self._raw_input(prompt, ident, parent)
361 353 else:
362 354 raw_input = lambda prompt='' : self._no_raw_input()
363 355
364 356 if py3compat.PY3:
365 357 self._sys_raw_input = __builtin__.input
366 358 __builtin__.input = raw_input
367 359 else:
368 360 self._sys_raw_input = __builtin__.raw_input
369 361 __builtin__.raw_input = raw_input
370 362
371 363 # Set the parent message of the display hook and out streams.
372 364 shell.displayhook.set_parent(parent)
373 365 shell.display_pub.set_parent(parent)
374 366 shell.data_pub.set_parent(parent)
375 367 sys.stdout.set_parent(parent)
376 368 sys.stderr.set_parent(parent)
377 369
378 370 # Re-broadcast our input for the benefit of listening clients, and
379 371 # start computing output
380 372 if not silent:
381 373 self._publish_pyin(code, parent, shell.execution_count)
382 374
383 375 reply_content = {}
384 376 try:
385 377 # FIXME: the shell calls the exception handler itself.
386 378 shell.run_cell(code, store_history=store_history, silent=silent)
387 379 except:
388 380 status = u'error'
389 381 # FIXME: this code right now isn't being used yet by default,
390 382 # because the run_cell() call above directly fires off exception
391 383 # reporting. This code, therefore, is only active in the scenario
392 384 # where runlines itself has an unhandled exception. We need to
393 385 # uniformize this, for all exception construction to come from a
394 386 # single location in the codbase.
395 387 etype, evalue, tb = sys.exc_info()
396 388 tb_list = traceback.format_exception(etype, evalue, tb)
397 389 reply_content.update(shell._showtraceback(etype, evalue, tb_list))
398 390 else:
399 391 status = u'ok'
400 392 finally:
401 393 # Restore raw_input.
402 394 if py3compat.PY3:
403 395 __builtin__.input = self._sys_raw_input
404 396 else:
405 397 __builtin__.raw_input = self._sys_raw_input
406 398
407 399 reply_content[u'status'] = status
408 400
409 401 # Return the execution counter so clients can display prompts
410 402 reply_content['execution_count'] = shell.execution_count - 1
411 403
412 404 # FIXME - fish exception info out of shell, possibly left there by
413 405 # runlines. We'll need to clean up this logic later.
414 406 if shell._reply_content is not None:
415 407 reply_content.update(shell._reply_content)
416 408 e_info = dict(engine_uuid=self.ident, engine_id=self.int_id, method='execute')
417 409 reply_content['engine_info'] = e_info
418 410 # reset after use
419 411 shell._reply_content = None
420 412
421 413 if 'traceback' in reply_content:
422 414 self.log.info("Exception in execute request:\n%s", '\n'.join(reply_content['traceback']))
423 415
424 416
425 417 # At this point, we can tell whether the main code execution succeeded
426 418 # or not. If it did, we proceed to evaluate user_variables/expressions
427 419 if reply_content['status'] == 'ok':
428 420 reply_content[u'user_variables'] = \
429 421 shell.user_variables(content.get(u'user_variables', []))
430 422 reply_content[u'user_expressions'] = \
431 423 shell.user_expressions(content.get(u'user_expressions', {}))
432 424 else:
433 425 # If there was an error, don't even try to compute variables or
434 426 # expressions
435 427 reply_content[u'user_variables'] = {}
436 428 reply_content[u'user_expressions'] = {}
437 429
438 430 # Payloads should be retrieved regardless of outcome, so we can both
439 431 # recover partial output (that could have been generated early in a
440 432 # block, before an error) and clear the payload system always.
441 433 reply_content[u'payload'] = shell.payload_manager.read_payload()
442 434 # Be agressive about clearing the payload because we don't want
443 435 # it to sit in memory until the next execute_request comes in.
444 436 shell.payload_manager.clear_payload()
445 437
446 438 # Flush output before sending the reply.
447 439 sys.stdout.flush()
448 440 sys.stderr.flush()
449 441 # FIXME: on rare occasions, the flush doesn't seem to make it to the
450 442 # clients... This seems to mitigate the problem, but we definitely need
451 443 # to better understand what's going on.
452 444 if self._execute_sleep:
453 445 time.sleep(self._execute_sleep)
454 446
455 447 # Send the reply.
456 448 reply_content = json_clean(reply_content)
457 449
458 450 md['status'] = reply_content['status']
459 451 if reply_content['status'] == 'error' and \
460 452 reply_content['ename'] == 'UnmetDependency':
461 453 md['dependencies_met'] = False
462 454
463 455 reply_msg = self.session.send(stream, u'execute_reply',
464 456 reply_content, parent, metadata=md,
465 457 ident=ident)
466 458
467 459 self.log.debug("%s", reply_msg)
468 460
469 461 if not silent and reply_msg['content']['status'] == u'error':
470 462 self._abort_queues()
471 463
472 464 self._publish_status(u'idle', parent)
473 465
474 466 def complete_request(self, stream, ident, parent):
475 467 txt, matches = self._complete(parent)
476 468 matches = {'matches' : matches,
477 469 'matched_text' : txt,
478 470 'status' : 'ok'}
479 471 matches = json_clean(matches)
480 472 completion_msg = self.session.send(stream, 'complete_reply',
481 473 matches, parent, ident)
482 474 self.log.debug("%s", completion_msg)
483 475
484 476 def object_info_request(self, stream, ident, parent):
485 477 content = parent['content']
486 478 object_info = self.shell.object_inspect(content['oname'],
487 479 detail_level = content.get('detail_level', 0)
488 480 )
489 481 # Before we send this object over, we scrub it for JSON usage
490 482 oinfo = json_clean(object_info)
491 483 msg = self.session.send(stream, 'object_info_reply',
492 484 oinfo, parent, ident)
493 485 self.log.debug("%s", msg)
494 486
495 487 def history_request(self, stream, ident, parent):
496 488 # We need to pull these out, as passing **kwargs doesn't work with
497 489 # unicode keys before Python 2.6.5.
498 490 hist_access_type = parent['content']['hist_access_type']
499 491 raw = parent['content']['raw']
500 492 output = parent['content']['output']
501 493 if hist_access_type == 'tail':
502 494 n = parent['content']['n']
503 495 hist = self.shell.history_manager.get_tail(n, raw=raw, output=output,
504 496 include_latest=True)
505 497
506 498 elif hist_access_type == 'range':
507 499 session = parent['content']['session']
508 500 start = parent['content']['start']
509 501 stop = parent['content']['stop']
510 502 hist = self.shell.history_manager.get_range(session, start, stop,
511 503 raw=raw, output=output)
512 504
513 505 elif hist_access_type == 'search':
514 506 n = parent['content'].get('n')
515 507 pattern = parent['content']['pattern']
516 508 hist = self.shell.history_manager.search(pattern, raw=raw,
517 509 output=output, n=n)
518 510
519 511 else:
520 512 hist = []
521 513 hist = list(hist)
522 514 content = {'history' : hist}
523 515 content = json_clean(content)
524 516 msg = self.session.send(stream, 'history_reply',
525 517 content, parent, ident)
526 518 self.log.debug("Sending history reply with %i entries", len(hist))
527 519
528 520 def connect_request(self, stream, ident, parent):
529 521 if self._recorded_ports is not None:
530 522 content = self._recorded_ports.copy()
531 523 else:
532 524 content = {}
533 525 msg = self.session.send(stream, 'connect_reply',
534 526 content, parent, ident)
535 527 self.log.debug("%s", msg)
536 528
537 529 def kernel_info_request(self, stream, ident, parent):
538 530 vinfo = {
539 531 'protocol_version': protocol_version,
540 532 'ipython_version': ipython_version,
541 533 'language_version': language_version,
542 534 'language': 'python',
543 535 }
544 536 msg = self.session.send(stream, 'kernel_info_reply',
545 537 vinfo, parent, ident)
546 538 self.log.debug("%s", msg)
547 539
548 540 def shutdown_request(self, stream, ident, parent):
549 541 self.shell.exit_now = True
550 542 content = dict(status='ok')
551 543 content.update(parent['content'])
552 544 self.session.send(stream, u'shutdown_reply', content, parent, ident=ident)
553 545 # same content, but different msg_id for broadcasting on IOPub
554 546 self._shutdown_message = self.session.msg(u'shutdown_reply',
555 547 content, parent
556 548 )
557 549
558 550 self._at_shutdown()
559 551 # call sys.exit after a short delay
560 552 loop = ioloop.IOLoop.instance()
561 553 loop.add_timeout(time.time()+0.1, loop.stop)
562 554
563 555 #---------------------------------------------------------------------------
564 556 # Engine methods
565 557 #---------------------------------------------------------------------------
566 558
567 559 def apply_request(self, stream, ident, parent):
568 560 try:
569 561 content = parent[u'content']
570 562 bufs = parent[u'buffers']
571 563 msg_id = parent['header']['msg_id']
572 564 except:
573 565 self.log.error("Got bad msg: %s", parent, exc_info=True)
574 566 return
575 567
576 568 self._publish_status(u'busy', parent)
577 569
578 570 # Set the parent message of the display hook and out streams.
579 571 shell = self.shell
580 572 shell.displayhook.set_parent(parent)
581 573 shell.display_pub.set_parent(parent)
582 574 shell.data_pub.set_parent(parent)
583 575 sys.stdout.set_parent(parent)
584 576 sys.stderr.set_parent(parent)
585 577
586 578 # pyin_msg = self.session.msg(u'pyin',{u'code':code}, parent=parent)
587 579 # self.iopub_socket.send(pyin_msg)
588 580 # self.session.send(self.iopub_socket, u'pyin', {u'code':code},parent=parent)
589 581 md = self._make_metadata(parent['metadata'])
590 582 try:
591 583 working = shell.user_ns
592 584
593 585 prefix = "_"+str(msg_id).replace("-","")+"_"
594 586
595 587 f,args,kwargs = unpack_apply_message(bufs, working, copy=False)
596 588
597 589 fname = getattr(f, '__name__', 'f')
598 590
599 591 fname = prefix+"f"
600 592 argname = prefix+"args"
601 593 kwargname = prefix+"kwargs"
602 594 resultname = prefix+"result"
603 595
604 596 ns = { fname : f, argname : args, kwargname : kwargs , resultname : None }
605 597 # print ns
606 598 working.update(ns)
607 599 code = "%s = %s(*%s,**%s)" % (resultname, fname, argname, kwargname)
608 600 try:
609 601 exec code in shell.user_global_ns, shell.user_ns
610 602 result = working.get(resultname)
611 603 finally:
612 604 for key in ns.iterkeys():
613 605 working.pop(key)
614 606
615 607 result_buf = serialize_object(result,
616 608 buffer_threshold=self.session.buffer_threshold,
617 609 item_threshold=self.session.item_threshold,
618 610 )
619 611
620 612 except:
621 613 # invoke IPython traceback formatting
622 614 shell.showtraceback()
623 615 # FIXME - fish exception info out of shell, possibly left there by
624 616 # run_code. We'll need to clean up this logic later.
625 617 reply_content = {}
626 618 if shell._reply_content is not None:
627 619 reply_content.update(shell._reply_content)
628 620 e_info = dict(engine_uuid=self.ident, engine_id=self.int_id, method='apply')
629 621 reply_content['engine_info'] = e_info
630 622 # reset after use
631 623 shell._reply_content = None
632 624
633 625 self.session.send(self.iopub_socket, u'pyerr', reply_content, parent=parent,
634 626 ident=self._topic('pyerr'))
635 627 self.log.info("Exception in apply request:\n%s", '\n'.join(reply_content['traceback']))
636 628 result_buf = []
637 629
638 630 if reply_content['ename'] == 'UnmetDependency':
639 631 md['dependencies_met'] = False
640 632 else:
641 633 reply_content = {'status' : 'ok'}
642 634
643 635 # put 'ok'/'error' status in header, for scheduler introspection:
644 636 md['status'] = reply_content['status']
645 637
646 638 # flush i/o
647 639 sys.stdout.flush()
648 640 sys.stderr.flush()
649 641
650 642 reply_msg = self.session.send(stream, u'apply_reply', reply_content,
651 643 parent=parent, ident=ident,buffers=result_buf, metadata=md)
652 644
653 645 self._publish_status(u'idle', parent)
654 646
655 647 #---------------------------------------------------------------------------
656 648 # Control messages
657 649 #---------------------------------------------------------------------------
658 650
659 651 def abort_request(self, stream, ident, parent):
660 652 """abort a specifig msg by id"""
661 653 msg_ids = parent['content'].get('msg_ids', None)
662 654 if isinstance(msg_ids, basestring):
663 655 msg_ids = [msg_ids]
664 656 if not msg_ids:
665 657 self.abort_queues()
666 658 for mid in msg_ids:
667 659 self.aborted.add(str(mid))
668 660
669 661 content = dict(status='ok')
670 662 reply_msg = self.session.send(stream, 'abort_reply', content=content,
671 663 parent=parent, ident=ident)
672 664 self.log.debug("%s", reply_msg)
673 665
674 666 def clear_request(self, stream, idents, parent):
675 667 """Clear our namespace."""
676 668 self.shell.reset(False)
677 669 msg = self.session.send(stream, 'clear_reply', ident=idents, parent=parent,
678 670 content = dict(status='ok'))
679 671
680 672
681 673 #---------------------------------------------------------------------------
682 674 # Protected interface
683 675 #---------------------------------------------------------------------------
684 676
685 677 def _wrap_exception(self, method=None):
686 678 # import here, because _wrap_exception is only used in parallel,
687 679 # and parallel has higher min pyzmq version
688 680 from IPython.parallel.error import wrap_exception
689 681 e_info = dict(engine_uuid=self.ident, engine_id=self.int_id, method=method)
690 682 content = wrap_exception(e_info)
691 683 return content
692 684
693 685 def _topic(self, topic):
694 686 """prefixed topic for IOPub messages"""
695 687 if self.int_id >= 0:
696 688 base = "engine.%i" % self.int_id
697 689 else:
698 690 base = "kernel.%s" % self.ident
699 691
700 692 return py3compat.cast_bytes("%s.%s" % (base, topic))
701 693
702 694 def _abort_queues(self):
703 695 for stream in self.shell_streams:
704 696 if stream:
705 697 self._abort_queue(stream)
706 698
707 699 def _abort_queue(self, stream):
708 700 poller = zmq.Poller()
709 701 poller.register(stream.socket, zmq.POLLIN)
710 702 while True:
711 703 idents,msg = self.session.recv(stream, zmq.NOBLOCK, content=True)
712 704 if msg is None:
713 705 return
714 706
715 707 self.log.info("Aborting:")
716 708 self.log.info("%s", msg)
717 709 msg_type = msg['header']['msg_type']
718 710 reply_type = msg_type.split('_')[0] + '_reply'
719 711
720 712 status = {'status' : 'aborted'}
721 713 md = {'engine' : self.ident}
722 714 md.update(status)
723 715 reply_msg = self.session.send(stream, reply_type, metadata=md,
724 716 content=status, parent=msg, ident=idents)
725 717 self.log.debug("%s", reply_msg)
726 718 # We need to wait a bit for requests to come in. This can probably
727 719 # be set shorter for true asynchronous clients.
728 720 poller.poll(50)
729 721
730 722
731 723 def _no_raw_input(self):
732 724 """Raise StdinNotImplentedError if active frontend doesn't support
733 725 stdin."""
734 726 raise StdinNotImplementedError("raw_input was called, but this "
735 727 "frontend does not support stdin.")
736 728
737 729 def _raw_input(self, prompt, ident, parent):
738 730 # Flush output before making the request.
739 731 sys.stderr.flush()
740 732 sys.stdout.flush()
741 733
742 734 # Send the input request.
743 735 content = json_clean(dict(prompt=prompt))
744 736 self.session.send(self.stdin_socket, u'input_request', content, parent,
745 737 ident=ident)
746 738
747 739 # Await a response.
748 740 while True:
749 741 try:
750 742 ident, reply = self.session.recv(self.stdin_socket, 0)
751 743 except Exception:
752 744 self.log.warn("Invalid Message:", exc_info=True)
753 745 else:
754 746 break
755 747 try:
756 748 value = reply['content']['value']
757 749 except:
758 750 self.log.error("Got bad raw_input reply: ")
759 751 self.log.error("%s", parent)
760 752 value = ''
761 753 if value == '\x04':
762 754 # EOF
763 755 raise EOFError
764 756 return value
765 757
766 758 def _complete(self, msg):
767 759 c = msg['content']
768 760 try:
769 761 cpos = int(c['cursor_pos'])
770 762 except:
771 763 # If we don't get something that we can convert to an integer, at
772 764 # least attempt the completion guessing the cursor is at the end of
773 765 # the text, if there's any, and otherwise of the line
774 766 cpos = len(c['text'])
775 767 if cpos==0:
776 768 cpos = len(c['line'])
777 769 return self.shell.complete(c['text'], c['line'], cpos)
778 770
779 771 def _at_shutdown(self):
780 772 """Actions taken at shutdown by the kernel, called by python's atexit.
781 773 """
782 774 # io.rprint("Kernel at_shutdown") # dbg
783 775 if self._shutdown_message is not None:
784 776 self.session.send(self.iopub_socket, self._shutdown_message, ident=self._topic('shutdown'))
785 777 self.log.debug("%s", self._shutdown_message)
786 778 [ s.flush(zmq.POLLOUT) for s in self.shell_streams ]
787 779
788 #-----------------------------------------------------------------------------
789 # Aliases and Flags for the IPKernelApp
790 #-----------------------------------------------------------------------------
791
792 flags = dict(kernel_flags)
793 flags.update(shell_flags)
794
795 addflag = lambda *args: flags.update(boolean_flag(*args))
796
797 flags['pylab'] = (
798 {'IPKernelApp' : {'pylab' : 'auto'}},
799 """Pre-load matplotlib and numpy for interactive use with
800 the default matplotlib backend."""
801 )
802
803 aliases = dict(kernel_aliases)
804 aliases.update(shell_aliases)
805
806 #-----------------------------------------------------------------------------
807 # The IPKernelApp class
808 #-----------------------------------------------------------------------------
809
810 class IPKernelApp(KernelApp, InteractiveShellApp):
811 name = 'ipkernel'
812
813 aliases = Dict(aliases)
814 flags = Dict(flags)
815 classes = [Kernel, ZMQInteractiveShell, ProfileDir, Session]
816
817 @catch_config_error
818 def initialize(self, argv=None):
819 super(IPKernelApp, self).initialize(argv)
820 self.init_path()
821 self.init_shell()
822 self.init_gui_pylab()
823 self.init_extensions()
824 self.init_code()
825
826 def init_kernel(self):
827
828 shell_stream = ZMQStream(self.shell_socket)
829
830 kernel = Kernel(config=self.config, session=self.session,
831 shell_streams=[shell_stream],
832 iopub_socket=self.iopub_socket,
833 stdin_socket=self.stdin_socket,
834 log=self.log,
835 profile_dir=self.profile_dir,
836 )
837 self.kernel = kernel
838 kernel.record_ports(self.ports)
839 shell = kernel.shell
840
841 def init_gui_pylab(self):
842 """Enable GUI event loop integration, taking pylab into account."""
843
844 # Provide a wrapper for :meth:`InteractiveShellApp.init_gui_pylab`
845 # to ensure that any exception is printed straight to stderr.
846 # Normally _showtraceback associates the reply with an execution,
847 # which means frontends will never draw it, as this exception
848 # is not associated with any execute request.
849
850 shell = self.shell
851 _showtraceback = shell._showtraceback
852 try:
853 # replace pyerr-sending traceback with stderr
854 def print_tb(etype, evalue, stb):
855 print ("GUI event loop or pylab initialization failed",
856 file=io.stderr)
857 print (shell.InteractiveTB.stb2text(stb), file=io.stderr)
858 shell._showtraceback = print_tb
859 InteractiveShellApp.init_gui_pylab(self)
860 finally:
861 shell._showtraceback = _showtraceback
862
863 def init_shell(self):
864 self.shell = self.kernel.shell
865 self.shell.configurables.append(self)
866
867
868 #-----------------------------------------------------------------------------
869 # Kernel main and launch functions
870 #-----------------------------------------------------------------------------
871
872
873 def embed_kernel(module=None, local_ns=None, **kwargs):
874 """Embed and start an IPython kernel in a given scope.
875
876 Parameters
877 ----------
878 module : ModuleType, optional
879 The module to load into IPython globals (default: caller)
880 local_ns : dict, optional
881 The namespace to load into IPython user namespace (default: caller)
882
883 kwargs : various, optional
884 Further keyword args are relayed to the KernelApp constructor,
885 allowing configuration of the Kernel. Will only have an effect
886 on the first embed_kernel call for a given process.
887
888 """
889 # get the app if it exists, or set it up if it doesn't
890 if IPKernelApp.initialized():
891 app = IPKernelApp.instance()
892 else:
893 app = IPKernelApp.instance(**kwargs)
894 app.initialize([])
895 # Undo unnecessary sys module mangling from init_sys_modules.
896 # This would not be necessary if we could prevent it
897 # in the first place by using a different InteractiveShell
898 # subclass, as in the regular embed case.
899 main = app.kernel.shell._orig_sys_modules_main_mod
900 if main is not None:
901 sys.modules[app.kernel.shell._orig_sys_modules_main_name] = main
902
903 # load the calling scope if not given
904 (caller_module, caller_locals) = extract_module_locals(1)
905 if module is None:
906 module = caller_module
907 if local_ns is None:
908 local_ns = caller_locals
909
910 app.kernel.user_module = module
911 app.kernel.user_ns = local_ns
912 app.shell.set_completer_frame()
913 app.start()
914
915 def main():
916 """Run an IPKernel as an application"""
917 app = IPKernelApp.instance()
918 app.initialize()
919 app.start()
920
921
922 if __name__ == '__main__':
923 main()
@@ -1,372 +1,432 b''
1 1 """An Application for launching a kernel
2 2
3 3 Authors
4 4 -------
5 5 * MinRK
6 6 """
7 7 #-----------------------------------------------------------------------------
8 8 # Copyright (C) 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.txt, distributed as part of this software.
12 12 #-----------------------------------------------------------------------------
13 13
14 14 #-----------------------------------------------------------------------------
15 15 # Imports
16 16 #-----------------------------------------------------------------------------
17 17
18 from __future__ import print_function
19
18 20 # Standard library imports
19 21 import atexit
20 22 import json
21 23 import os
22 24 import sys
23 25 import signal
24 26
25 27 # System library imports
26 28 import zmq
27 29 from zmq.eventloop import ioloop
30 from zmq.eventloop.zmqstream import ZMQStream
28 31
29 32 # IPython imports
30 33 from IPython.core.ultratb import FormattedTB
31 34 from IPython.core.application import (
32 35 BaseIPythonApplication, base_flags, base_aliases, catch_config_error
33 36 )
37 from IPython.core.profiledir import ProfileDir
38 from IPython.core.shellapp import (
39 InteractiveShellApp, shell_flags, shell_aliases
40 )
34 41 from IPython.utils import io
35 42 from IPython.utils.localinterfaces import LOCALHOST
36 43 from IPython.utils.path import filefind
37 44 from IPython.utils.py3compat import str_to_bytes
38 45 from IPython.utils.traitlets import (
39 46 Any, Instance, Dict, Unicode, Integer, Bool, CaselessStrEnum,
40 47 DottedObjectName,
41 48 )
42 49 from IPython.utils.importstring import import_item
43 50 from IPython.kernel import write_connection_file
51
44 52 # local imports
45 from IPython.zmq.heartbeat import Heartbeat
46 from IPython.zmq.parentpoller import ParentPollerUnix, ParentPollerWindows
47 from IPython.zmq.session import (
53 from heartbeat import Heartbeat
54 from ipkernel import Kernel
55 from parentpoller import ParentPollerUnix, ParentPollerWindows
56 from session import (
48 57 Session, session_flags, session_aliases, default_secure,
49 58 )
50
59 from zmqshell import ZMQInteractiveShell
51 60
52 61 #-----------------------------------------------------------------------------
53 62 # Flags and Aliases
54 63 #-----------------------------------------------------------------------------
55 64
56 65 kernel_aliases = dict(base_aliases)
57 66 kernel_aliases.update({
58 'ip' : 'KernelApp.ip',
59 'hb' : 'KernelApp.hb_port',
60 'shell' : 'KernelApp.shell_port',
61 'iopub' : 'KernelApp.iopub_port',
62 'stdin' : 'KernelApp.stdin_port',
63 'f' : 'KernelApp.connection_file',
64 'parent': 'KernelApp.parent',
65 'transport': 'KernelApp.transport',
67 'ip' : 'IPKernelApp.ip',
68 'hb' : 'IPKernelApp.hb_port',
69 'shell' : 'IPKernelApp.shell_port',
70 'iopub' : 'IPKernelApp.iopub_port',
71 'stdin' : 'IPKernelApp.stdin_port',
72 'f' : 'IPKernelApp.connection_file',
73 'parent': 'IPKernelApp.parent',
74 'transport': 'IPKernelApp.transport',
66 75 })
67 76 if sys.platform.startswith('win'):
68 77 kernel_aliases['interrupt'] = 'KernelApp.interrupt'
69 78
70 79 kernel_flags = dict(base_flags)
71 80 kernel_flags.update({
72 81 'no-stdout' : (
73 {'KernelApp' : {'no_stdout' : True}},
82 {'IPKernelApp' : {'no_stdout' : True}},
74 83 "redirect stdout to the null device"),
75 84 'no-stderr' : (
76 {'KernelApp' : {'no_stderr' : True}},
85 {'IPKernelApp' : {'no_stderr' : True}},
77 86 "redirect stderr to the null device"),
87 'pylab' : (
88 {'IPKernelApp' : {'pylab' : 'auto'}},
89 """Pre-load matplotlib and numpy for interactive use with
90 the default matplotlib backend."""),
78 91 })
79 92
93 # inherit flags&aliases for any IPython shell apps
94 kernel_aliases.update(shell_aliases)
95 kernel_flags.update(shell_flags)
96
80 97 # inherit flags&aliases for Sessions
81 98 kernel_aliases.update(session_aliases)
82 99 kernel_flags.update(session_flags)
83 100
84
85
86 101 #-----------------------------------------------------------------------------
87 # Application class for starting a Kernel
102 # Application class for starting an IPython Kernel
88 103 #-----------------------------------------------------------------------------
89 104
90 class KernelApp(BaseIPythonApplication):
105 class IPKernelApp(BaseIPythonApplication, InteractiveShellApp):
91 106 name='ipkernel'
92 107 aliases = Dict(kernel_aliases)
93 108 flags = Dict(kernel_flags)
94 classes = [Session]
109 classes = [Kernel, ZMQInteractiveShell, ProfileDir, Session]
95 110 # the kernel class, as an importstring
96 111 kernel_class = DottedObjectName('IPython.zmq.ipkernel.Kernel')
97 112 kernel = Any()
98 113 poller = Any() # don't restrict this even though current pollers are all Threads
99 114 heartbeat = Instance(Heartbeat)
100 115 session = Instance('IPython.zmq.session.Session')
101 116 ports = Dict()
102 117
103 118 # inherit config file name from parent:
104 119 parent_appname = Unicode(config=True)
105 120 def _parent_appname_changed(self, name, old, new):
106 121 if self.config_file_specified:
107 122 # it was manually specified, ignore
108 123 return
109 124 self.config_file_name = new.replace('-','_') + u'_config.py'
110 125 # don't let this count as specifying the config file
111 126 self.config_file_specified = False
112 127
113 128 # connection info:
114 129 transport = CaselessStrEnum(['tcp', 'ipc'], default_value='tcp', config=True)
115 130 ip = Unicode(config=True,
116 131 help="Set the IP or interface on which the kernel will listen.")
117 132 def _ip_default(self):
118 133 if self.transport == 'ipc':
119 134 if self.connection_file:
120 135 return os.path.splitext(self.abs_connection_file)[0] + '-ipc'
121 136 else:
122 137 return 'kernel-ipc'
123 138 else:
124 139 return LOCALHOST
125 140 hb_port = Integer(0, config=True, help="set the heartbeat port [default: random]")
126 141 shell_port = Integer(0, config=True, help="set the shell (ROUTER) port [default: random]")
127 142 iopub_port = Integer(0, config=True, help="set the iopub (PUB) port [default: random]")
128 143 stdin_port = Integer(0, config=True, help="set the stdin (DEALER) port [default: random]")
129 144 connection_file = Unicode('', config=True,
130 145 help="""JSON file in which to store connection info [default: kernel-<pid>.json]
131 146
132 147 This file will contain the IP, ports, and authentication key needed to connect
133 148 clients to this kernel. By default, this file will be created in the security dir
134 149 of the current profile, but can be specified by absolute path.
135 150 """)
136 151 @property
137 152 def abs_connection_file(self):
138 153 if os.path.basename(self.connection_file) == self.connection_file:
139 154 return os.path.join(self.profile_dir.security_dir, self.connection_file)
140 155 else:
141 156 return self.connection_file
142 157
143 158
144 159 # streams, etc.
145 160 no_stdout = Bool(False, config=True, help="redirect stdout to the null device")
146 161 no_stderr = Bool(False, config=True, help="redirect stderr to the null device")
147 162 outstream_class = DottedObjectName('IPython.zmq.iostream.OutStream',
148 163 config=True, help="The importstring for the OutStream factory")
149 164 displayhook_class = DottedObjectName('IPython.zmq.displayhook.ZMQDisplayHook',
150 165 config=True, help="The importstring for the DisplayHook factory")
151 166
152 167 # polling
153 168 parent = Integer(0, config=True,
154 169 help="""kill this process if its parent dies. On Windows, the argument
155 170 specifies the HANDLE of the parent process, otherwise it is simply boolean.
156 171 """)
157 172 interrupt = Integer(0, config=True,
158 173 help="""ONLY USED ON WINDOWS
159 174 Interrupt this process when the parent is signaled.
160 175 """)
161 176
162 177 def init_crash_handler(self):
163 178 # Install minimal exception handling
164 179 sys.excepthook = FormattedTB(mode='Verbose', color_scheme='NoColor',
165 180 ostream=sys.__stdout__)
166 181
167 182 def init_poller(self):
168 183 if sys.platform == 'win32':
169 184 if self.interrupt or self.parent:
170 185 self.poller = ParentPollerWindows(self.interrupt, self.parent)
171 186 elif self.parent:
172 187 self.poller = ParentPollerUnix()
173 188
174 189 def _bind_socket(self, s, port):
175 190 iface = '%s://%s' % (self.transport, self.ip)
176 191 if self.transport == 'tcp':
177 192 if port <= 0:
178 193 port = s.bind_to_random_port(iface)
179 194 else:
180 195 s.bind("tcp://%s:%i" % (self.ip, port))
181 196 elif self.transport == 'ipc':
182 197 if port <= 0:
183 198 port = 1
184 199 path = "%s-%i" % (self.ip, port)
185 200 while os.path.exists(path):
186 201 port = port + 1
187 202 path = "%s-%i" % (self.ip, port)
188 203 else:
189 204 path = "%s-%i" % (self.ip, port)
190 205 s.bind("ipc://%s" % path)
191 206 return port
192 207
193 208 def load_connection_file(self):
194 209 """load ip/port/hmac config from JSON connection file"""
195 210 try:
196 211 fname = filefind(self.connection_file, ['.', self.profile_dir.security_dir])
197 212 except IOError:
198 213 self.log.debug("Connection file not found: %s", self.connection_file)
199 214 # This means I own it, so I will clean it up:
200 215 atexit.register(self.cleanup_connection_file)
201 216 return
202 217 self.log.debug(u"Loading connection file %s", fname)
203 218 with open(fname) as f:
204 219 s = f.read()
205 220 cfg = json.loads(s)
206 221 self.transport = cfg.get('transport', self.transport)
207 222 if self.ip == self._ip_default() and 'ip' in cfg:
208 223 # not overridden by config or cl_args
209 224 self.ip = cfg['ip']
210 225 for channel in ('hb', 'shell', 'iopub', 'stdin'):
211 226 name = channel + '_port'
212 227 if getattr(self, name) == 0 and name in cfg:
213 228 # not overridden by config or cl_args
214 229 setattr(self, name, cfg[name])
215 230 if 'key' in cfg:
216 231 self.config.Session.key = str_to_bytes(cfg['key'])
217 232
218 233 def write_connection_file(self):
219 234 """write connection info to JSON file"""
220 235 cf = self.abs_connection_file
221 236 self.log.debug("Writing connection file: %s", cf)
222 237 write_connection_file(cf, ip=self.ip, key=self.session.key, transport=self.transport,
223 238 shell_port=self.shell_port, stdin_port=self.stdin_port, hb_port=self.hb_port,
224 239 iopub_port=self.iopub_port)
225 240
226 241 def cleanup_connection_file(self):
227 242 cf = self.abs_connection_file
228 243 self.log.debug("Cleaning up connection file: %s", cf)
229 244 try:
230 245 os.remove(cf)
231 246 except (IOError, OSError):
232 247 pass
233 248
234 249 self.cleanup_ipc_files()
235 250
236 251 def cleanup_ipc_files(self):
237 252 """cleanup ipc files if we wrote them"""
238 253 if self.transport != 'ipc':
239 254 return
240 255 for port in (self.shell_port, self.iopub_port, self.stdin_port, self.hb_port):
241 256 ipcfile = "%s-%i" % (self.ip, port)
242 257 try:
243 258 os.remove(ipcfile)
244 259 except (IOError, OSError):
245 260 pass
246 261
247 262 def init_connection_file(self):
248 263 if not self.connection_file:
249 264 self.connection_file = "kernel-%s.json"%os.getpid()
250 265 try:
251 266 self.load_connection_file()
252 267 except Exception:
253 268 self.log.error("Failed to load connection file: %r", self.connection_file, exc_info=True)
254 269 self.exit(1)
255 270
256 271 def init_sockets(self):
257 272 # Create a context, a session, and the kernel sockets.
258 273 self.log.info("Starting the kernel at pid: %i", os.getpid())
259 274 context = zmq.Context.instance()
260 275 # Uncomment this to try closing the context.
261 276 # atexit.register(context.term)
262 277
263 278 self.shell_socket = context.socket(zmq.ROUTER)
264 279 self.shell_port = self._bind_socket(self.shell_socket, self.shell_port)
265 280 self.log.debug("shell ROUTER Channel on port: %i"%self.shell_port)
266 281
267 282 self.iopub_socket = context.socket(zmq.PUB)
268 283 self.iopub_port = self._bind_socket(self.iopub_socket, self.iopub_port)
269 284 self.log.debug("iopub PUB Channel on port: %i"%self.iopub_port)
270 285
271 286 self.stdin_socket = context.socket(zmq.ROUTER)
272 287 self.stdin_port = self._bind_socket(self.stdin_socket, self.stdin_port)
273 288 self.log.debug("stdin ROUTER Channel on port: %i"%self.stdin_port)
274 289
275 290 def init_heartbeat(self):
276 291 """start the heart beating"""
277 292 # heartbeat doesn't share context, because it mustn't be blocked
278 293 # by the GIL, which is accessed by libzmq when freeing zero-copy messages
279 294 hb_ctx = zmq.Context()
280 295 self.heartbeat = Heartbeat(hb_ctx, (self.transport, self.ip, self.hb_port))
281 296 self.hb_port = self.heartbeat.port
282 297 self.log.debug("Heartbeat REP Channel on port: %i"%self.hb_port)
283 298 self.heartbeat.start()
284 299
285 300 # Helper to make it easier to connect to an existing kernel.
286 301 # set log-level to critical, to make sure it is output
287 302 self.log.critical("To connect another client to this kernel, use:")
288 303
289 304 def log_connection_info(self):
290 305 """display connection info, and store ports"""
291 306 basename = os.path.basename(self.connection_file)
292 307 if basename == self.connection_file or \
293 308 os.path.dirname(self.connection_file) == self.profile_dir.security_dir:
294 309 # use shortname
295 310 tail = basename
296 311 if self.profile != 'default':
297 312 tail += " --profile %s" % self.profile
298 313 else:
299 314 tail = self.connection_file
300 315 self.log.critical("--existing %s", tail)
301 316
302 317
303 318 self.ports = dict(shell=self.shell_port, iopub=self.iopub_port,
304 319 stdin=self.stdin_port, hb=self.hb_port)
305 320
306 321 def init_session(self):
307 322 """create our session object"""
308 323 default_secure(self.config)
309 324 self.session = Session(config=self.config, username=u'kernel')
310 325
311 326 def init_blackhole(self):
312 327 """redirects stdout/stderr to devnull if necessary"""
313 328 if self.no_stdout or self.no_stderr:
314 329 blackhole = open(os.devnull, 'w')
315 330 if self.no_stdout:
316 331 sys.stdout = sys.__stdout__ = blackhole
317 332 if self.no_stderr:
318 333 sys.stderr = sys.__stderr__ = blackhole
319 334
320 335 def init_io(self):
321 336 """Redirect input streams and set a display hook."""
322 337 if self.outstream_class:
323 338 outstream_factory = import_item(str(self.outstream_class))
324 339 sys.stdout = outstream_factory(self.session, self.iopub_socket, u'stdout')
325 340 sys.stderr = outstream_factory(self.session, self.iopub_socket, u'stderr')
326 341 if self.displayhook_class:
327 342 displayhook_factory = import_item(str(self.displayhook_class))
328 343 sys.displayhook = displayhook_factory(self.session, self.iopub_socket)
329 344
330 345 def init_signal(self):
331 346 signal.signal(signal.SIGINT, signal.SIG_IGN)
332 347
333 348 def init_kernel(self):
334 349 """Create the Kernel object itself"""
335 kernel_factory = import_item(str(self.kernel_class))
336 self.kernel = kernel_factory(config=self.config, session=self.session,
337 shell_socket=self.shell_socket,
350 shell_stream = ZMQStream(self.shell_socket)
351
352 kernel = Kernel(config=self.config, session=self.session,
353 shell_streams=[shell_stream],
338 354 iopub_socket=self.iopub_socket,
339 355 stdin_socket=self.stdin_socket,
340 log=self.log
356 log=self.log,
357 profile_dir=self.profile_dir,
341 358 )
342 self.kernel.record_ports(self.ports)
359 kernel.record_ports(self.ports)
360 self.kernel = kernel
361
362 def init_gui_pylab(self):
363 """Enable GUI event loop integration, taking pylab into account."""
364
365 # Provide a wrapper for :meth:`InteractiveShellApp.init_gui_pylab`
366 # to ensure that any exception is printed straight to stderr.
367 # Normally _showtraceback associates the reply with an execution,
368 # which means frontends will never draw it, as this exception
369 # is not associated with any execute request.
370
371 shell = self.shell
372 _showtraceback = shell._showtraceback
373 try:
374 # replace pyerr-sending traceback with stderr
375 def print_tb(etype, evalue, stb):
376 print ("GUI event loop or pylab initialization failed",
377 file=io.stderr)
378 print (shell.InteractiveTB.stb2text(stb), file=io.stderr)
379 shell._showtraceback = print_tb
380 InteractiveShellApp.init_gui_pylab(self)
381 finally:
382 shell._showtraceback = _showtraceback
383
384 def init_shell(self):
385 self.shell = self.kernel.shell
386 self.shell.configurables.append(self)
343 387
344 388 @catch_config_error
345 389 def initialize(self, argv=None):
346 super(KernelApp, self).initialize(argv)
390 super(IPKernelApp, self).initialize(argv)
347 391 self.init_blackhole()
348 392 self.init_connection_file()
349 393 self.init_session()
350 394 self.init_poller()
351 395 self.init_sockets()
352 396 self.init_heartbeat()
353 397 # writing/displaying connection info must be *after* init_sockets/heartbeat
354 398 self.log_connection_info()
355 399 self.write_connection_file()
356 400 self.init_io()
357 401 self.init_signal()
358 402 self.init_kernel()
403 # shell init steps
404 self.init_path()
405 self.init_shell()
406 self.init_gui_pylab()
407 self.init_extensions()
408 self.init_code()
359 409 # flush stdout/stderr, so that anything written to these streams during
360 410 # initialization do not get associated with the first execution request
361 411 sys.stdout.flush()
362 412 sys.stderr.flush()
363 413
364 414 def start(self):
365 415 if self.poller is not None:
366 416 self.poller.start()
367 417 self.kernel.start()
368 418 try:
369 419 ioloop.IOLoop.instance().start()
370 420 except KeyboardInterrupt:
371 421 pass
372 422
423
424 def main():
425 """Run an IPKernel as an application"""
426 app = IPKernelApp.instance()
427 app.initialize()
428 app.start()
429
430
431 if __name__ == '__main__':
432 main()
@@ -1,1129 +1,1129 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 # Standard library imports.
19 19 import atexit
20 20 import errno
21 21 import json
22 22 from subprocess import Popen
23 23 import os
24 24 import signal
25 25 import sys
26 26 from threading import Thread
27 27 import time
28 28
29 29 # System library imports.
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.localinterfaces import LOCALHOST, LOCAL_IPS
39 39 from IPython.utils.traitlets import (
40 40 Any, Instance, Type, Unicode, List, Integer, Bool, CaselessStrEnum
41 41 )
42 42 from IPython.utils.py3compat import str_to_bytes
43 43 from IPython.kernel import (
44 44 write_connection_file,
45 45 make_ipkernel_cmd,
46 46 launch_kernel,
47 47 )
48 48 from session import Session
49 49 from IPython.kernel import (
50 50 ShellChannelABC, IOPubChannelABC,
51 51 HBChannelABC, StdInChannelABC,
52 52 KernelManagerABC
53 53 )
54 54
55 55
56 56 #-----------------------------------------------------------------------------
57 57 # Constants and exceptions
58 58 #-----------------------------------------------------------------------------
59 59
60 60 class InvalidPortNumber(Exception):
61 61 pass
62 62
63 63 #-----------------------------------------------------------------------------
64 64 # Utility functions
65 65 #-----------------------------------------------------------------------------
66 66
67 67 # some utilities to validate message structure, these might get moved elsewhere
68 68 # if they prove to have more generic utility
69 69
70 70 def validate_string_list(lst):
71 71 """Validate that the input is a list of strings.
72 72
73 73 Raises ValueError if not."""
74 74 if not isinstance(lst, list):
75 75 raise ValueError('input %r must be a list' % lst)
76 76 for x in lst:
77 77 if not isinstance(x, basestring):
78 78 raise ValueError('element %r in list must be a string' % x)
79 79
80 80
81 81 def validate_string_dict(dct):
82 82 """Validate that the input is a dict with string keys and values.
83 83
84 84 Raises ValueError if not."""
85 85 for k,v in dct.iteritems():
86 86 if not isinstance(k, basestring):
87 87 raise ValueError('key %r in dict must be a string' % k)
88 88 if not isinstance(v, basestring):
89 89 raise ValueError('value %r in dict must be a string' % v)
90 90
91 91
92 92 #-----------------------------------------------------------------------------
93 93 # ZMQ Socket Channel classes
94 94 #-----------------------------------------------------------------------------
95 95
96 96 class ZMQSocketChannel(Thread):
97 97 """The base class for the channels that use ZMQ sockets."""
98 98 context = None
99 99 session = None
100 100 socket = None
101 101 ioloop = None
102 102 stream = None
103 103 _address = None
104 104 _exiting = False
105 105
106 106 def __init__(self, context, session, address):
107 107 """Create a channel.
108 108
109 109 Parameters
110 110 ----------
111 111 context : :class:`zmq.Context`
112 112 The ZMQ context to use.
113 113 session : :class:`session.Session`
114 114 The session to use.
115 115 address : zmq url
116 116 Standard (ip, port) tuple that the kernel is listening on.
117 117 """
118 118 super(ZMQSocketChannel, self).__init__()
119 119 self.daemon = True
120 120
121 121 self.context = context
122 122 self.session = session
123 123 if isinstance(address, tuple):
124 124 if address[1] == 0:
125 125 message = 'The port number for a channel cannot be 0.'
126 126 raise InvalidPortNumber(message)
127 127 address = "tcp://%s:%i" % address
128 128 self._address = address
129 129 atexit.register(self._notice_exit)
130 130
131 131 def _notice_exit(self):
132 132 self._exiting = True
133 133
134 134 def _run_loop(self):
135 135 """Run my loop, ignoring EINTR events in the poller"""
136 136 while True:
137 137 try:
138 138 self.ioloop.start()
139 139 except ZMQError as e:
140 140 if e.errno == errno.EINTR:
141 141 continue
142 142 else:
143 143 raise
144 144 except Exception:
145 145 if self._exiting:
146 146 break
147 147 else:
148 148 raise
149 149 else:
150 150 break
151 151
152 152 def stop(self):
153 153 """Stop the channel's event loop and join its thread.
154 154
155 155 This calls :method:`Thread.join` and returns when the thread
156 156 terminates. :class:`RuntimeError` will be raised if
157 157 :method:`self.start` is called again.
158 158 """
159 159 self.join()
160 160
161 161 @property
162 162 def address(self):
163 163 """Get the channel's address as a zmq url string.
164 164
165 165 These URLS have the form: 'tcp://127.0.0.1:5555'.
166 166 """
167 167 return self._address
168 168
169 169 def _queue_send(self, msg):
170 170 """Queue a message to be sent from the IOLoop's thread.
171 171
172 172 Parameters
173 173 ----------
174 174 msg : message to send
175 175
176 176 This is threadsafe, as it uses IOLoop.add_callback to give the loop's
177 177 thread control of the action.
178 178 """
179 179 def thread_send():
180 180 self.session.send(self.stream, msg)
181 181 self.ioloop.add_callback(thread_send)
182 182
183 183 def _handle_recv(self, msg):
184 184 """Callback for stream.on_recv.
185 185
186 186 Unpacks message, and calls handlers with it.
187 187 """
188 188 ident,smsg = self.session.feed_identities(msg)
189 189 self.call_handlers(self.session.unserialize(smsg))
190 190
191 191
192 192
193 193 class ShellChannel(ZMQSocketChannel):
194 194 """The shell channel for issuing request/replies to the kernel."""
195 195
196 196 command_queue = None
197 197 # flag for whether execute requests should be allowed to call raw_input:
198 198 allow_stdin = True
199 199
200 200 def __init__(self, context, session, address):
201 201 super(ShellChannel, self).__init__(context, session, address)
202 202 self.ioloop = ioloop.IOLoop()
203 203
204 204 def run(self):
205 205 """The thread's main activity. Call start() instead."""
206 206 self.socket = self.context.socket(zmq.DEALER)
207 207 self.socket.setsockopt(zmq.IDENTITY, self.session.bsession)
208 208 self.socket.connect(self.address)
209 209 self.stream = zmqstream.ZMQStream(self.socket, self.ioloop)
210 210 self.stream.on_recv(self._handle_recv)
211 211 self._run_loop()
212 212 try:
213 213 self.socket.close()
214 214 except:
215 215 pass
216 216
217 217 def stop(self):
218 218 """Stop the channel's event loop and join its thread."""
219 219 self.ioloop.stop()
220 220 super(ShellChannel, self).stop()
221 221
222 222 def call_handlers(self, msg):
223 223 """This method is called in the ioloop thread when a message arrives.
224 224
225 225 Subclasses should override this method to handle incoming messages.
226 226 It is important to remember that this method is called in the thread
227 227 so that some logic must be done to ensure that the application leve
228 228 handlers are called in the application thread.
229 229 """
230 230 raise NotImplementedError('call_handlers must be defined in a subclass.')
231 231
232 232 def execute(self, code, silent=False, store_history=True,
233 233 user_variables=None, user_expressions=None, allow_stdin=None):
234 234 """Execute code in the kernel.
235 235
236 236 Parameters
237 237 ----------
238 238 code : str
239 239 A string of Python code.
240 240
241 241 silent : bool, optional (default False)
242 242 If set, the kernel will execute the code as quietly possible, and
243 243 will force store_history to be False.
244 244
245 245 store_history : bool, optional (default True)
246 246 If set, the kernel will store command history. This is forced
247 247 to be False if silent is True.
248 248
249 249 user_variables : list, optional
250 250 A list of variable names to pull from the user's namespace. They
251 251 will come back as a dict with these names as keys and their
252 252 :func:`repr` as values.
253 253
254 254 user_expressions : dict, optional
255 255 A dict mapping names to expressions to be evaluated in the user's
256 256 dict. The expression values are returned as strings formatted using
257 257 :func:`repr`.
258 258
259 259 allow_stdin : bool, optional (default self.allow_stdin)
260 260 Flag for whether the kernel can send stdin requests to frontends.
261 261
262 262 Some frontends (e.g. the Notebook) do not support stdin requests.
263 263 If raw_input is called from code executed from such a frontend, a
264 264 StdinNotImplementedError will be raised.
265 265
266 266 Returns
267 267 -------
268 268 The msg_id of the message sent.
269 269 """
270 270 if user_variables is None:
271 271 user_variables = []
272 272 if user_expressions is None:
273 273 user_expressions = {}
274 274 if allow_stdin is None:
275 275 allow_stdin = self.allow_stdin
276 276
277 277
278 278 # Don't waste network traffic if inputs are invalid
279 279 if not isinstance(code, basestring):
280 280 raise ValueError('code %r must be a string' % code)
281 281 validate_string_list(user_variables)
282 282 validate_string_dict(user_expressions)
283 283
284 284 # Create class for content/msg creation. Related to, but possibly
285 285 # not in Session.
286 286 content = dict(code=code, silent=silent, store_history=store_history,
287 287 user_variables=user_variables,
288 288 user_expressions=user_expressions,
289 289 allow_stdin=allow_stdin,
290 290 )
291 291 msg = self.session.msg('execute_request', content)
292 292 self._queue_send(msg)
293 293 return msg['header']['msg_id']
294 294
295 295 def complete(self, text, line, cursor_pos, block=None):
296 296 """Tab complete text in the kernel's namespace.
297 297
298 298 Parameters
299 299 ----------
300 300 text : str
301 301 The text to complete.
302 302 line : str
303 303 The full line of text that is the surrounding context for the
304 304 text to complete.
305 305 cursor_pos : int
306 306 The position of the cursor in the line where the completion was
307 307 requested.
308 308 block : str, optional
309 309 The full block of code in which the completion is being requested.
310 310
311 311 Returns
312 312 -------
313 313 The msg_id of the message sent.
314 314 """
315 315 content = dict(text=text, line=line, block=block, cursor_pos=cursor_pos)
316 316 msg = self.session.msg('complete_request', content)
317 317 self._queue_send(msg)
318 318 return msg['header']['msg_id']
319 319
320 320 def object_info(self, oname, detail_level=0):
321 321 """Get metadata information about an object in the kernel's namespace.
322 322
323 323 Parameters
324 324 ----------
325 325 oname : str
326 326 A string specifying the object name.
327 327 detail_level : int, optional
328 328 The level of detail for the introspection (0-2)
329 329
330 330 Returns
331 331 -------
332 332 The msg_id of the message sent.
333 333 """
334 334 content = dict(oname=oname, detail_level=detail_level)
335 335 msg = self.session.msg('object_info_request', content)
336 336 self._queue_send(msg)
337 337 return msg['header']['msg_id']
338 338
339 339 def history(self, raw=True, output=False, hist_access_type='range', **kwargs):
340 340 """Get entries from the kernel's history list.
341 341
342 342 Parameters
343 343 ----------
344 344 raw : bool
345 345 If True, return the raw input.
346 346 output : bool
347 347 If True, then return the output as well.
348 348 hist_access_type : str
349 349 'range' (fill in session, start and stop params), 'tail' (fill in n)
350 350 or 'search' (fill in pattern param).
351 351
352 352 session : int
353 353 For a range request, the session from which to get lines. Session
354 354 numbers are positive integers; negative ones count back from the
355 355 current session.
356 356 start : int
357 357 The first line number of a history range.
358 358 stop : int
359 359 The final (excluded) line number of a history range.
360 360
361 361 n : int
362 362 The number of lines of history to get for a tail request.
363 363
364 364 pattern : str
365 365 The glob-syntax pattern for a search request.
366 366
367 367 Returns
368 368 -------
369 369 The msg_id of the message sent.
370 370 """
371 371 content = dict(raw=raw, output=output, hist_access_type=hist_access_type,
372 372 **kwargs)
373 373 msg = self.session.msg('history_request', content)
374 374 self._queue_send(msg)
375 375 return msg['header']['msg_id']
376 376
377 377 def kernel_info(self):
378 378 """Request kernel info."""
379 379 msg = self.session.msg('kernel_info_request')
380 380 self._queue_send(msg)
381 381 return msg['header']['msg_id']
382 382
383 383 def shutdown(self, restart=False):
384 384 """Request an immediate kernel shutdown.
385 385
386 386 Upon receipt of the (empty) reply, client code can safely assume that
387 387 the kernel has shut down and it's safe to forcefully terminate it if
388 388 it's still alive.
389 389
390 390 The kernel will send the reply via a function registered with Python's
391 391 atexit module, ensuring it's truly done as the kernel is done with all
392 392 normal operation.
393 393 """
394 394 # Send quit message to kernel. Once we implement kernel-side setattr,
395 395 # this should probably be done that way, but for now this will do.
396 396 msg = self.session.msg('shutdown_request', {'restart':restart})
397 397 self._queue_send(msg)
398 398 return msg['header']['msg_id']
399 399
400 400
401 401
402 402 class IOPubChannel(ZMQSocketChannel):
403 403 """The iopub channel which listens for messages that the kernel publishes.
404 404
405 405 This channel is where all output is published to frontends.
406 406 """
407 407
408 408 def __init__(self, context, session, address):
409 409 super(IOPubChannel, self).__init__(context, session, address)
410 410 self.ioloop = ioloop.IOLoop()
411 411
412 412 def run(self):
413 413 """The thread's main activity. Call start() instead."""
414 414 self.socket = self.context.socket(zmq.SUB)
415 415 self.socket.setsockopt(zmq.SUBSCRIBE,b'')
416 416 self.socket.setsockopt(zmq.IDENTITY, self.session.bsession)
417 417 self.socket.connect(self.address)
418 418 self.stream = zmqstream.ZMQStream(self.socket, self.ioloop)
419 419 self.stream.on_recv(self._handle_recv)
420 420 self._run_loop()
421 421 try:
422 422 self.socket.close()
423 423 except:
424 424 pass
425 425
426 426 def stop(self):
427 427 """Stop the channel's event loop and join its thread."""
428 428 self.ioloop.stop()
429 429 super(IOPubChannel, self).stop()
430 430
431 431 def call_handlers(self, msg):
432 432 """This method is called in the ioloop thread when a message arrives.
433 433
434 434 Subclasses should override this method to handle incoming messages.
435 435 It is important to remember that this method is called in the thread
436 436 so that some logic must be done to ensure that the application leve
437 437 handlers are called in the application thread.
438 438 """
439 439 raise NotImplementedError('call_handlers must be defined in a subclass.')
440 440
441 441 def flush(self, timeout=1.0):
442 442 """Immediately processes all pending messages on the iopub channel.
443 443
444 444 Callers should use this method to ensure that :method:`call_handlers`
445 445 has been called for all messages that have been received on the
446 446 0MQ SUB socket of this channel.
447 447
448 448 This method is thread safe.
449 449
450 450 Parameters
451 451 ----------
452 452 timeout : float, optional
453 453 The maximum amount of time to spend flushing, in seconds. The
454 454 default is one second.
455 455 """
456 456 # We do the IOLoop callback process twice to ensure that the IOLoop
457 457 # gets to perform at least one full poll.
458 458 stop_time = time.time() + timeout
459 459 for i in xrange(2):
460 460 self._flushed = False
461 461 self.ioloop.add_callback(self._flush)
462 462 while not self._flushed and time.time() < stop_time:
463 463 time.sleep(0.01)
464 464
465 465 def _flush(self):
466 466 """Callback for :method:`self.flush`."""
467 467 self.stream.flush()
468 468 self._flushed = True
469 469
470 470
471 471 class StdInChannel(ZMQSocketChannel):
472 472 """The stdin channel to handle raw_input requests that the kernel makes."""
473 473
474 474 msg_queue = None
475 475
476 476 def __init__(self, context, session, address):
477 477 super(StdInChannel, self).__init__(context, session, address)
478 478 self.ioloop = ioloop.IOLoop()
479 479
480 480 def run(self):
481 481 """The thread's main activity. Call start() instead."""
482 482 self.socket = self.context.socket(zmq.DEALER)
483 483 self.socket.setsockopt(zmq.IDENTITY, self.session.bsession)
484 484 self.socket.connect(self.address)
485 485 self.stream = zmqstream.ZMQStream(self.socket, self.ioloop)
486 486 self.stream.on_recv(self._handle_recv)
487 487 self._run_loop()
488 488 try:
489 489 self.socket.close()
490 490 except:
491 491 pass
492 492
493 493 def stop(self):
494 494 """Stop the channel's event loop and join its thread."""
495 495 self.ioloop.stop()
496 496 super(StdInChannel, self).stop()
497 497
498 498 def call_handlers(self, msg):
499 499 """This method is called in the ioloop thread when a message arrives.
500 500
501 501 Subclasses should override this method to handle incoming messages.
502 502 It is important to remember that this method is called in the thread
503 503 so that some logic must be done to ensure that the application leve
504 504 handlers are called in the application thread.
505 505 """
506 506 raise NotImplementedError('call_handlers must be defined in a subclass.')
507 507
508 508 def input(self, string):
509 509 """Send a string of raw input to the kernel."""
510 510 content = dict(value=string)
511 511 msg = self.session.msg('input_reply', content)
512 512 self._queue_send(msg)
513 513
514 514
515 515 class HBChannel(ZMQSocketChannel):
516 516 """The heartbeat channel which monitors the kernel heartbeat.
517 517
518 518 Note that the heartbeat channel is paused by default. As long as you start
519 519 this channel, the kernel manager will ensure that it is paused and un-paused
520 520 as appropriate.
521 521 """
522 522
523 523 time_to_dead = 3.0
524 524 socket = None
525 525 poller = None
526 526 _running = None
527 527 _pause = None
528 528 _beating = None
529 529
530 530 def __init__(self, context, session, address):
531 531 super(HBChannel, self).__init__(context, session, address)
532 532 self._running = False
533 533 self._pause =True
534 534 self.poller = zmq.Poller()
535 535
536 536 def _create_socket(self):
537 537 if self.socket is not None:
538 538 # close previous socket, before opening a new one
539 539 self.poller.unregister(self.socket)
540 540 self.socket.close()
541 541 self.socket = self.context.socket(zmq.REQ)
542 542 self.socket.setsockopt(zmq.LINGER, 0)
543 543 self.socket.connect(self.address)
544 544
545 545 self.poller.register(self.socket, zmq.POLLIN)
546 546
547 547 def _poll(self, start_time):
548 548 """poll for heartbeat replies until we reach self.time_to_dead.
549 549
550 550 Ignores interrupts, and returns the result of poll(), which
551 551 will be an empty list if no messages arrived before the timeout,
552 552 or the event tuple if there is a message to receive.
553 553 """
554 554
555 555 until_dead = self.time_to_dead - (time.time() - start_time)
556 556 # ensure poll at least once
557 557 until_dead = max(until_dead, 1e-3)
558 558 events = []
559 559 while True:
560 560 try:
561 561 events = self.poller.poll(1000 * until_dead)
562 562 except ZMQError as e:
563 563 if e.errno == errno.EINTR:
564 564 # ignore interrupts during heartbeat
565 565 # this may never actually happen
566 566 until_dead = self.time_to_dead - (time.time() - start_time)
567 567 until_dead = max(until_dead, 1e-3)
568 568 pass
569 569 else:
570 570 raise
571 571 except Exception:
572 572 if self._exiting:
573 573 break
574 574 else:
575 575 raise
576 576 else:
577 577 break
578 578 return events
579 579
580 580 def run(self):
581 581 """The thread's main activity. Call start() instead."""
582 582 self._create_socket()
583 583 self._running = True
584 584 self._beating = True
585 585
586 586 while self._running:
587 587 if self._pause:
588 588 # just sleep, and skip the rest of the loop
589 589 time.sleep(self.time_to_dead)
590 590 continue
591 591
592 592 since_last_heartbeat = 0.0
593 593 # io.rprint('Ping from HB channel') # dbg
594 594 # no need to catch EFSM here, because the previous event was
595 595 # either a recv or connect, which cannot be followed by EFSM
596 596 self.socket.send(b'ping')
597 597 request_time = time.time()
598 598 ready = self._poll(request_time)
599 599 if ready:
600 600 self._beating = True
601 601 # the poll above guarantees we have something to recv
602 602 self.socket.recv()
603 603 # sleep the remainder of the cycle
604 604 remainder = self.time_to_dead - (time.time() - request_time)
605 605 if remainder > 0:
606 606 time.sleep(remainder)
607 607 continue
608 608 else:
609 609 # nothing was received within the time limit, signal heart failure
610 610 self._beating = False
611 611 since_last_heartbeat = time.time() - request_time
612 612 self.call_handlers(since_last_heartbeat)
613 613 # and close/reopen the socket, because the REQ/REP cycle has been broken
614 614 self._create_socket()
615 615 continue
616 616 try:
617 617 self.socket.close()
618 618 except:
619 619 pass
620 620
621 621 def pause(self):
622 622 """Pause the heartbeat."""
623 623 self._pause = True
624 624
625 625 def unpause(self):
626 626 """Unpause the heartbeat."""
627 627 self._pause = False
628 628
629 629 def is_beating(self):
630 630 """Is the heartbeat running and responsive (and not paused)."""
631 631 if self.is_alive() and not self._pause and self._beating:
632 632 return True
633 633 else:
634 634 return False
635 635
636 636 def stop(self):
637 637 """Stop the channel's event loop and join its thread."""
638 638 self._running = False
639 639 super(HBChannel, self).stop()
640 640
641 641 def call_handlers(self, since_last_heartbeat):
642 642 """This method is called in the ioloop thread when a message arrives.
643 643
644 644 Subclasses should override this method to handle incoming messages.
645 645 It is important to remember that this method is called in the thread
646 646 so that some logic must be done to ensure that the application level
647 647 handlers are called in the application thread.
648 648 """
649 649 raise NotImplementedError('call_handlers must be defined in a subclass.')
650 650
651 651
652 652 #-----------------------------------------------------------------------------
653 653 # Main kernel manager class
654 654 #-----------------------------------------------------------------------------
655 655
656 656 class KernelManager(Configurable):
657 657 """Manages a single kernel on this host along with its channels.
658 658
659 659 There are four channels associated with each kernel:
660 660
661 661 * shell: for request/reply calls to the kernel.
662 662 * iopub: for the kernel to publish results to frontends.
663 663 * hb: for monitoring the kernel's heartbeat.
664 664 * stdin: for frontends to reply to raw_input calls in the kernel.
665 665
666 666 The usage of the channels that this class manages is optional. It is
667 667 entirely possible to connect to the kernels directly using ZeroMQ
668 668 sockets. These channels are useful primarily for talking to a kernel
669 669 whose :class:`KernelManager` is in the same process.
670 670
671 671 This version manages kernels started using Popen.
672 672 """
673 673 # The PyZMQ Context to use for communication with the kernel.
674 674 context = Instance(zmq.Context)
675 675 def _context_default(self):
676 676 return zmq.Context.instance()
677 677
678 678 # The Session to use for communication with the kernel.
679 679 session = Instance(Session)
680 680 def _session_default(self):
681 681 return Session(config=self.config)
682 682
683 683 # The kernel process with which the KernelManager is communicating.
684 684 # generally a Popen instance
685 685 kernel = Any()
686 686
687 687 kernel_cmd = List(Unicode, config=True,
688 688 help="""The Popen Command to launch the kernel.
689 689 Override this if you have a custom
690 690 """
691 691 )
692 692 def _kernel_cmd_changed(self, name, old, new):
693 693 print 'kernel cmd changed', new
694 694 self.ipython_kernel = False
695 695
696 696 ipython_kernel = Bool(True)
697 697
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 def _ip_default(self):
711 711 if self.transport == 'ipc':
712 712 if self.connection_file:
713 713 return os.path.splitext(self.connection_file)[0] + '-ipc'
714 714 else:
715 715 return 'kernel-ipc'
716 716 else:
717 717 return LOCALHOST
718 718 def _ip_changed(self, name, old, new):
719 719 if new == '*':
720 720 self.ip = '0.0.0.0'
721 721 shell_port = Integer(0)
722 722 iopub_port = Integer(0)
723 723 stdin_port = Integer(0)
724 724 hb_port = Integer(0)
725 725
726 726 # The classes to use for the various channels.
727 727 shell_channel_class = Type(ShellChannel)
728 728 iopub_channel_class = Type(IOPubChannel)
729 729 stdin_channel_class = Type(StdInChannel)
730 730 hb_channel_class = Type(HBChannel)
731 731
732 732 # Protected traits.
733 733 _launch_args = Any
734 734 _shell_channel = Any
735 735 _iopub_channel = Any
736 736 _stdin_channel = Any
737 737 _hb_channel = Any
738 738 _connection_file_written=Bool(False)
739 739
740 740 def __del__(self):
741 741 self.cleanup_connection_file()
742 742
743 743 #--------------------------------------------------------------------------
744 744 # Channel management methods:
745 745 #--------------------------------------------------------------------------
746 746
747 747 def start_channels(self, shell=True, iopub=True, stdin=True, hb=True):
748 748 """Starts the channels for this kernel.
749 749
750 750 This will create the channels if they do not exist and then start
751 751 them (their activity runs in a thread). If port numbers of 0 are
752 752 being used (random ports) then you must first call
753 753 :method:`start_kernel`. If the channels have been stopped and you
754 754 call this, :class:`RuntimeError` will be raised.
755 755 """
756 756 if shell:
757 757 self.shell_channel.start()
758 758 if iopub:
759 759 self.iopub_channel.start()
760 760 if stdin:
761 761 self.stdin_channel.start()
762 762 self.shell_channel.allow_stdin = True
763 763 else:
764 764 self.shell_channel.allow_stdin = False
765 765 if hb:
766 766 self.hb_channel.start()
767 767
768 768 def stop_channels(self):
769 769 """Stops all the running channels for this kernel.
770 770
771 771 This stops their event loops and joins their threads.
772 772 """
773 773 if self.shell_channel.is_alive():
774 774 self.shell_channel.stop()
775 775 if self.iopub_channel.is_alive():
776 776 self.iopub_channel.stop()
777 777 if self.stdin_channel.is_alive():
778 778 self.stdin_channel.stop()
779 779 if self.hb_channel.is_alive():
780 780 self.hb_channel.stop()
781 781
782 782 @property
783 783 def channels_running(self):
784 784 """Are any of the channels created and running?"""
785 785 return (self.shell_channel.is_alive() or self.iopub_channel.is_alive() or
786 786 self.stdin_channel.is_alive() or self.hb_channel.is_alive())
787 787
788 788 def _make_url(self, port):
789 789 """Make a zmq url with a port.
790 790
791 791 There are two cases that this handles:
792 792
793 793 * tcp: tcp://ip:port
794 794 * ipc: ipc://ip-port
795 795 """
796 796 if self.transport == 'tcp':
797 797 return "tcp://%s:%i" % (self.ip, port)
798 798 else:
799 799 return "%s://%s-%s" % (self.transport, self.ip, port)
800 800
801 801 @property
802 802 def shell_channel(self):
803 803 """Get the shell channel object for this kernel."""
804 804 if self._shell_channel is None:
805 805 self._shell_channel = self.shell_channel_class(
806 806 self.context, self.session, self._make_url(self.shell_port)
807 807 )
808 808 return self._shell_channel
809 809
810 810 @property
811 811 def iopub_channel(self):
812 812 """Get the iopub channel object for this kernel."""
813 813 if self._iopub_channel is None:
814 814 self._iopub_channel = self.iopub_channel_class(
815 815 self.context, self.session, self._make_url(self.iopub_port)
816 816 )
817 817 return self._iopub_channel
818 818
819 819 @property
820 820 def stdin_channel(self):
821 821 """Get the stdin channel object for this kernel."""
822 822 if self._stdin_channel is None:
823 823 self._stdin_channel = self.stdin_channel_class(
824 824 self.context, self.session, self._make_url(self.stdin_port)
825 825 )
826 826 return self._stdin_channel
827 827
828 828 @property
829 829 def hb_channel(self):
830 830 """Get the hb channel object for this kernel."""
831 831 if self._hb_channel is None:
832 832 self._hb_channel = self.hb_channel_class(
833 833 self.context, self.session, self._make_url(self.hb_port)
834 834 )
835 835 return self._hb_channel
836 836
837 837 #--------------------------------------------------------------------------
838 838 # Connection and ipc file management
839 839 #--------------------------------------------------------------------------
840 840
841 841 def cleanup_connection_file(self):
842 842 """Cleanup connection file *if we wrote it*
843 843
844 844 Will not raise if the connection file was already removed somehow.
845 845 """
846 846 if self._connection_file_written:
847 847 # cleanup connection files on full shutdown of kernel we started
848 848 self._connection_file_written = False
849 849 try:
850 850 os.remove(self.connection_file)
851 851 except (IOError, OSError):
852 852 pass
853 853
854 854 def cleanup_ipc_files(self):
855 855 """Cleanup ipc files if we wrote them."""
856 856 if self.transport != 'ipc':
857 857 return
858 858 for port in (self.shell_port, self.iopub_port, self.stdin_port, self.hb_port):
859 859 ipcfile = "%s-%i" % (self.ip, port)
860 860 try:
861 861 os.remove(ipcfile)
862 862 except (IOError, OSError):
863 863 pass
864 864
865 865 def load_connection_file(self):
866 866 """Load connection info from JSON dict in self.connection_file."""
867 867 with open(self.connection_file) as f:
868 868 cfg = json.loads(f.read())
869 869
870 870 from pprint import pprint
871 871 pprint(cfg)
872 872 self.transport = cfg.get('transport', 'tcp')
873 873 self.ip = cfg['ip']
874 874 self.shell_port = cfg['shell_port']
875 875 self.stdin_port = cfg['stdin_port']
876 876 self.iopub_port = cfg['iopub_port']
877 877 self.hb_port = cfg['hb_port']
878 878 self.session.key = str_to_bytes(cfg['key'])
879 879
880 880 def write_connection_file(self):
881 881 """Write connection info to JSON dict in self.connection_file."""
882 882 if self._connection_file_written:
883 883 return
884 884 self.connection_file,cfg = write_connection_file(self.connection_file,
885 885 transport=self.transport, ip=self.ip, key=self.session.key,
886 886 stdin_port=self.stdin_port, iopub_port=self.iopub_port,
887 887 shell_port=self.shell_port, hb_port=self.hb_port)
888 888 # write_connection_file also sets default ports:
889 889 self.shell_port = cfg['shell_port']
890 890 self.stdin_port = cfg['stdin_port']
891 891 self.iopub_port = cfg['iopub_port']
892 892 self.hb_port = cfg['hb_port']
893 893
894 894 self._connection_file_written = True
895 895
896 896 #--------------------------------------------------------------------------
897 897 # Kernel management
898 898 #--------------------------------------------------------------------------
899 899
900 900 def format_kernel_cmd(self, **kw):
901 901 """format templated args (e.g. {connection_file})"""
902 902 if self.kernel_cmd:
903 903 cmd = self.kernel_cmd
904 904 else:
905 905 cmd = make_ipkernel_cmd(
906 'from IPython.zmq.ipkernel import main; main()',
906 'from IPython.zmq.kernelapp import main; main()',
907 907 **kw
908 908 )
909 909 ns = dict(connection_file=self.connection_file)
910 910 ns.update(self._launch_args)
911 911 return [ c.format(**ns) for c in cmd ]
912 912
913 913 def _launch_kernel(self, kernel_cmd, **kw):
914 914 """actually launch the kernel
915 915
916 916 override in a subclass to launch kernel subprocesses differently
917 917 """
918 918 return launch_kernel(kernel_cmd, **kw)
919 919
920 920 def start_kernel(self, **kw):
921 921 """Starts a kernel on this host in a separate process.
922 922
923 923 If random ports (port=0) are being used, this method must be called
924 924 before the channels are created.
925 925
926 926 Parameters:
927 927 -----------
928 928 **kw : optional
929 929 keyword arguments that are passed down to build the kernel_cmd
930 930 and launching the kernel (e.g. Popen kwargs).
931 931 """
932 932 if self.transport == 'tcp' and self.ip not in LOCAL_IPS:
933 933 raise RuntimeError("Can only launch a kernel on a local interface. "
934 934 "Make sure that the '*_address' attributes are "
935 935 "configured properly. "
936 936 "Currently valid addresses are: %s"%LOCAL_IPS
937 937 )
938 938
939 939 # write connection file / get default ports
940 940 self.write_connection_file()
941 941
942 942 # save kwargs for use in restart
943 943 self._launch_args = kw.copy()
944 944 # build the Popen cmd
945 945 kernel_cmd = self.format_kernel_cmd(**kw)
946 946 # launch the kernel subprocess
947 947 self.kernel = self._launch_kernel(kernel_cmd,
948 948 ipython_kernel=self.ipython_kernel,
949 949 **kw)
950 950
951 951 def shutdown_kernel(self, now=False, restart=False):
952 952 """Attempts to the stop the kernel process cleanly.
953 953
954 954 This attempts to shutdown the kernels cleanly by:
955 955
956 956 1. Sending it a shutdown message over the shell channel.
957 957 2. If that fails, the kernel is shutdown forcibly by sending it
958 958 a signal.
959 959
960 960 Parameters:
961 961 -----------
962 962 now : bool
963 963 Should the kernel be forcible killed *now*. This skips the
964 964 first, nice shutdown attempt.
965 965 restart: bool
966 966 Will this kernel be restarted after it is shutdown. When this
967 967 is True, connection files will not be cleaned up.
968 968 """
969 969 # FIXME: Shutdown does not work on Windows due to ZMQ errors!
970 970 if sys.platform == 'win32':
971 971 self._kill_kernel()
972 972 return
973 973
974 974 # Pause the heart beat channel if it exists.
975 975 if self._hb_channel is not None:
976 976 self._hb_channel.pause()
977 977
978 978 if now:
979 979 if self.has_kernel:
980 980 self._kill_kernel()
981 981 else:
982 982 # Don't send any additional kernel kill messages immediately, to give
983 983 # the kernel a chance to properly execute shutdown actions. Wait for at
984 984 # most 1s, checking every 0.1s.
985 985 self.shell_channel.shutdown(restart=restart)
986 986 for i in range(10):
987 987 if self.is_alive:
988 988 time.sleep(0.1)
989 989 else:
990 990 break
991 991 else:
992 992 # OK, we've waited long enough.
993 993 if self.has_kernel:
994 994 self._kill_kernel()
995 995
996 996 if not restart:
997 997 self.cleanup_connection_file()
998 998 self.cleanup_ipc_files()
999 999 else:
1000 1000 self.cleanup_ipc_files()
1001 1001
1002 1002 def restart_kernel(self, now=False, **kw):
1003 1003 """Restarts a kernel with the arguments that were used to launch it.
1004 1004
1005 1005 If the old kernel was launched with random ports, the same ports will be
1006 1006 used for the new kernel. The same connection file is used again.
1007 1007
1008 1008 Parameters
1009 1009 ----------
1010 1010 now : bool, optional
1011 1011 If True, the kernel is forcefully restarted *immediately*, without
1012 1012 having a chance to do any cleanup action. Otherwise the kernel is
1013 1013 given 1s to clean up before a forceful restart is issued.
1014 1014
1015 1015 In all cases the kernel is restarted, the only difference is whether
1016 1016 it is given a chance to perform a clean shutdown or not.
1017 1017
1018 1018 **kw : optional
1019 1019 Any options specified here will overwrite those used to launch the
1020 1020 kernel.
1021 1021 """
1022 1022 if self._launch_args is None:
1023 1023 raise RuntimeError("Cannot restart the kernel. "
1024 1024 "No previous call to 'start_kernel'.")
1025 1025 else:
1026 1026 # Stop currently running kernel.
1027 1027 self.shutdown_kernel(now=now, restart=True)
1028 1028
1029 1029 # Start new kernel.
1030 1030 self._launch_args.update(kw)
1031 1031 self.start_kernel(**self._launch_args)
1032 1032
1033 1033 # FIXME: Messages get dropped in Windows due to probable ZMQ bug
1034 1034 # unless there is some delay here.
1035 1035 if sys.platform == 'win32':
1036 1036 time.sleep(0.2)
1037 1037
1038 1038 @property
1039 1039 def has_kernel(self):
1040 1040 """Has a kernel been started that we are managing."""
1041 1041 return self.kernel is not None
1042 1042
1043 1043 def _kill_kernel(self):
1044 1044 """Kill the running kernel.
1045 1045
1046 1046 This is a private method, callers should use shutdown_kernel(now=True).
1047 1047 """
1048 1048 if self.has_kernel:
1049 1049 # Pause the heart beat channel if it exists.
1050 1050 if self._hb_channel is not None:
1051 1051 self._hb_channel.pause()
1052 1052
1053 1053 # Signal the kernel to terminate (sends SIGKILL on Unix and calls
1054 1054 # TerminateProcess() on Win32).
1055 1055 try:
1056 1056 self.kernel.kill()
1057 1057 except OSError as e:
1058 1058 # In Windows, we will get an Access Denied error if the process
1059 1059 # has already terminated. Ignore it.
1060 1060 if sys.platform == 'win32':
1061 1061 if e.winerror != 5:
1062 1062 raise
1063 1063 # On Unix, we may get an ESRCH error if the process has already
1064 1064 # terminated. Ignore it.
1065 1065 else:
1066 1066 from errno import ESRCH
1067 1067 if e.errno != ESRCH:
1068 1068 raise
1069 1069
1070 1070 # Block until the kernel terminates.
1071 1071 self.kernel.wait()
1072 1072 self.kernel = None
1073 1073 else:
1074 1074 raise RuntimeError("Cannot kill kernel. No kernel is running!")
1075 1075
1076 1076 def interrupt_kernel(self):
1077 1077 """Interrupts the kernel by sending it a signal.
1078 1078
1079 1079 Unlike ``signal_kernel``, this operation is well supported on all
1080 1080 platforms.
1081 1081 """
1082 1082 if self.has_kernel:
1083 1083 if sys.platform == 'win32':
1084 1084 from parentpoller import ParentPollerWindows as Poller
1085 1085 Poller.send_interrupt(self.kernel.win32_interrupt_event)
1086 1086 else:
1087 1087 self.kernel.send_signal(signal.SIGINT)
1088 1088 else:
1089 1089 raise RuntimeError("Cannot interrupt kernel. No kernel is running!")
1090 1090
1091 1091 def signal_kernel(self, signum):
1092 1092 """Sends a signal to the kernel.
1093 1093
1094 1094 Note that since only SIGTERM is supported on Windows, this function is
1095 1095 only useful on Unix systems.
1096 1096 """
1097 1097 if self.has_kernel:
1098 1098 self.kernel.send_signal(signum)
1099 1099 else:
1100 1100 raise RuntimeError("Cannot signal kernel. No kernel is running!")
1101 1101
1102 1102 @property
1103 1103 def is_alive(self):
1104 1104 """Is the kernel process still running?"""
1105 1105 if self.has_kernel:
1106 1106 if self.kernel.poll() is None:
1107 1107 return True
1108 1108 else:
1109 1109 return False
1110 1110 elif self._hb_channel is not None:
1111 1111 # We didn't start the kernel with this KernelManager so we
1112 1112 # use the heartbeat.
1113 1113 return self._hb_channel.is_beating()
1114 1114 else:
1115 1115 # no heartbeat and not local, we can't tell if it's running,
1116 1116 # so naively return True
1117 1117 return True
1118 1118
1119 1119
1120 1120 #-----------------------------------------------------------------------------
1121 1121 # ABC Registration
1122 1122 #-----------------------------------------------------------------------------
1123 1123
1124 1124 ShellChannelABC.register(ShellChannel)
1125 1125 IOPubChannelABC.register(IOPubChannel)
1126 1126 HBChannelABC.register(HBChannel)
1127 1127 StdInChannelABC.register(StdInChannel)
1128 1128 KernelManagerABC.register(KernelManager)
1129 1129
General Comments 0
You need to be logged in to leave comments. Login now