##// END OF EJS Templates
move IPython.lib.kernel to IPython.utils.kernel...
MinRK -
Show More
@@ -0,0 +1,315 b''
1 """Utilities for connecting to kernels
2
3 Authors:
4
5 * Min Ragan-Kelley
6
7 """
8
9 #-----------------------------------------------------------------------------
10 # Copyright (C) 2011 The IPython Development Team
11 #
12 # Distributed under the terms of the BSD License. The full license is in
13 # the file COPYING, distributed as part of this software.
14 #-----------------------------------------------------------------------------
15
16 #-----------------------------------------------------------------------------
17 # Imports
18 #-----------------------------------------------------------------------------
19
20 import glob
21 import json
22 import os
23 import sys
24 from getpass import getpass
25 from subprocess import Popen, PIPE
26
27 # external imports
28 from IPython.external.ssh import tunnel
29
30 # IPython imports
31 from IPython.core.profiledir import ProfileDir
32 from IPython.utils.path import filefind, get_ipython_dir
33 from IPython.utils.py3compat import str_to_bytes
34
35
36 #-----------------------------------------------------------------------------
37 # Functions
38 #-----------------------------------------------------------------------------
39
40 def get_connection_file(app=None):
41 """Return the path to the connection file of an app
42
43 Parameters
44 ----------
45 app : KernelApp instance [optional]
46 If unspecified, the currently running app will be used
47 """
48 if app is None:
49 from IPython.zmq.ipkernel import IPKernelApp
50 if not IPKernelApp.initialized():
51 raise RuntimeError("app not specified, and not in a running Kernel")
52
53 app = IPKernelApp.instance()
54 return filefind(app.connection_file, ['.', app.profile_dir.security_dir])
55
56 def find_connection_file(filename, profile=None):
57 """find a connection file, and return its absolute path.
58
59 The current working directory and the profile's security
60 directory will be searched for the file if it is not given by
61 absolute path.
62
63 If profile is unspecified, then the current running application's
64 profile will be used, or 'default', if not run from IPython.
65
66 If the argument does not match an existing file, it will be interpreted as a
67 fileglob, and the matching file in the profile's security dir with
68 the latest access time will be used.
69
70 Parameters
71 ----------
72 filename : str
73 The connection file or fileglob to search for.
74 profile : str [optional]
75 The name of the profile to use when searching for the connection file,
76 if different from the current IPython session or 'default'.
77
78 Returns
79 -------
80 str : The absolute path of the connection file.
81 """
82 from IPython.core.application import BaseIPythonApplication as IPApp
83 try:
84 # quick check for absolute path, before going through logic
85 return filefind(filename)
86 except IOError:
87 pass
88
89 if profile is None:
90 # profile unspecified, check if running from an IPython app
91 if IPApp.initialized():
92 app = IPApp.instance()
93 profile_dir = app.profile_dir
94 else:
95 # not running in IPython, use default profile
96 profile_dir = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), 'default')
97 else:
98 # find profiledir by profile name:
99 profile_dir = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), profile)
100 security_dir = profile_dir.security_dir
101
102 try:
103 # first, try explicit name
104 return filefind(filename, ['.', security_dir])
105 except IOError:
106 pass
107
108 # not found by full name
109
110 if '*' in filename:
111 # given as a glob already
112 pat = filename
113 else:
114 # accept any substring match
115 pat = '*%s*' % filename
116 matches = glob.glob( os.path.join(security_dir, pat) )
117 if not matches:
118 raise IOError("Could not find %r in %r" % (filename, security_dir))
119 elif len(matches) == 1:
120 return matches[0]
121 else:
122 # get most recent match, by access time:
123 return sorted(matches, key=lambda f: os.stat(f).st_atime)[-1]
124
125 def get_connection_info(connection_file=None, unpack=False, profile=None):
126 """Return the connection information for the current Kernel.
127
128 Parameters
129 ----------
130 connection_file : str [optional]
131 The connection file to be used. Can be given by absolute path, or
132 IPython will search in the security directory of a given profile.
133 If run from IPython,
134
135 If unspecified, the connection file for the currently running
136 IPython Kernel will be used, which is only allowed from inside a kernel.
137 unpack : bool [default: False]
138 if True, return the unpacked dict, otherwise just the string contents
139 of the file.
140 profile : str [optional]
141 The name of the profile to use when searching for the connection file,
142 if different from the current IPython session or 'default'.
143
144
145 Returns
146 -------
147 The connection dictionary of the current kernel, as string or dict,
148 depending on `unpack`.
149 """
150 if connection_file is None:
151 # get connection file from current kernel
152 cf = get_connection_file()
153 else:
154 # connection file specified, allow shortnames:
155 cf = find_connection_file(connection_file, profile=profile)
156
157 with open(cf) as f:
158 info = f.read()
159
160 if unpack:
161 info = json.loads(info)
162 # ensure key is bytes:
163 info['key'] = str_to_bytes(info.get('key', ''))
164 return info
165
166 def connect_qtconsole(connection_file=None, argv=None, profile=None):
167 """Connect a qtconsole to the current kernel.
168
169 This is useful for connecting a second qtconsole to a kernel, or to a
170 local notebook.
171
172 Parameters
173 ----------
174 connection_file : str [optional]
175 The connection file to be used. Can be given by absolute path, or
176 IPython will search in the security directory of a given profile.
177 If run from IPython,
178
179 If unspecified, the connection file for the currently running
180 IPython Kernel will be used, which is only allowed from inside a kernel.
181 argv : list [optional]
182 Any extra args to be passed to the console.
183 profile : str [optional]
184 The name of the profile to use when searching for the connection file,
185 if different from the current IPython session or 'default'.
186
187
188 Returns
189 -------
190 subprocess.Popen instance running the qtconsole frontend
191 """
192 argv = [] if argv is None else argv
193
194 if connection_file is None:
195 # get connection file from current kernel
196 cf = get_connection_file()
197 else:
198 cf = find_connection_file(connection_file, profile=profile)
199
200 cmd = ';'.join([
201 "from IPython.frontend.qt.console import qtconsoleapp",
202 "qtconsoleapp.main()"
203 ])
204
205 return Popen([sys.executable, '-c', cmd, '--existing', cf] + argv, stdout=PIPE, stderr=PIPE)
206
207 def tunnel_to_kernel(connection_info, sshserver, sshkey=None):
208 """tunnel connections to a kernel via ssh
209
210 This will open four SSH tunnels from localhost on this machine to the
211 ports associated with the kernel. They can be either direct
212 localhost-localhost tunnels, or if an intermediate server is necessary,
213 the kernel must be listening on a public IP.
214
215 Parameters
216 ----------
217 connection_info : dict or str (path)
218 Either a connection dict, or the path to a JSON connection file
219 sshserver : str
220 The ssh sever to use to tunnel to the kernel. Can be a full
221 `user@server:port` string. ssh config aliases are respected.
222 sshkey : str [optional]
223 Path to file containing ssh key to use for authentication.
224 Only necessary if your ssh config does not already associate
225 a keyfile with the host.
226
227 Returns
228 -------
229
230 (shell, iopub, stdin, hb) : ints
231 The four ports on localhost that have been forwarded to the kernel.
232 """
233 if isinstance(connection_info, basestring):
234 # it's a path, unpack it
235 with open(connection_info) as f:
236 connection_info = json.loads(f.read())
237
238 cf = connection_info
239
240 lports = tunnel.select_random_ports(4)
241 rports = cf['shell_port'], cf['iopub_port'], cf['stdin_port'], cf['hb_port']
242
243 remote_ip = cf['ip']
244
245 if tunnel.try_passwordless_ssh(sshserver, sshkey):
246 password=False
247 else:
248 password = getpass("SSH Password for %s: "%sshserver)
249
250 for lp,rp in zip(lports, rports):
251 tunnel.ssh_tunnel(lp, rp, sshserver, remote_ip, sshkey, password)
252
253 return tuple(lports)
254
255
256 def swallow_argv(argv, aliases=None, flags=None):
257 """strip frontend-specific aliases and flags from an argument list
258
259 For use primarily in frontend apps that want to pass a subset of command-line
260 arguments through to a subprocess, where frontend-specific flags and aliases
261 should be removed from the list.
262
263 Parameters
264 ----------
265
266 argv : list(str)
267 The starting argv, to be filtered
268 aliases : container of aliases (dict, list, set, etc.)
269 The frontend-specific aliases to be removed
270 flags : container of flags (dict, list, set, etc.)
271 The frontend-specific flags to be removed
272
273 Returns
274 -------
275
276 argv : list(str)
277 The argv list, excluding flags and aliases that have been stripped
278 """
279
280 if aliases is None:
281 aliases = set()
282 if flags is None:
283 flags = set()
284
285 stripped = list(argv) # copy
286
287 swallow_next = False
288 was_flag = False
289 for a in argv:
290 if swallow_next:
291 swallow_next = False
292 # last arg was an alias, remove the next one
293 # *unless* the last alias has a no-arg flag version, in which
294 # case, don't swallow the next arg if it's also a flag:
295 if not (was_flag and a.startswith('-')):
296 stripped.remove(a)
297 continue
298 if a.startswith('-'):
299 split = a.lstrip('-').split('=')
300 alias = split[0]
301 if alias in aliases:
302 stripped.remove(a)
303 if len(split) == 1:
304 # alias passed with arg via space
305 swallow_next = True
306 # could have been a flag that matches an alias, e.g. `existing`
307 # in which case, we might not swallow the next arg
308 was_flag = alias in flags
309 elif alias in flags and len(split) == 1:
310 # strip flag, but don't swallow next, as flags don't take args
311 stripped.remove(a)
312
313 # return shortened list
314 return stripped
315
@@ -1,362 +1,362 b''
1 1 """ A minimal application base mixin for all ZMQ based IPython frontends.
2 2
3 3 This is not a complete console app, as subprocess will not be able to receive
4 4 input, there is no real readline support, among other limitations. This is a
5 5 refactoring of what used to be the IPython/frontend/qt/console/qtconsoleapp.py
6 6
7 7 Authors:
8 8
9 9 * Evan Patterson
10 10 * Min RK
11 11 * Erik Tollerud
12 12 * Fernando Perez
13 13 * Bussonnier Matthias
14 14 * Thomas Kluyver
15 15 * Paul Ivanov
16 16
17 17 """
18 18
19 19 #-----------------------------------------------------------------------------
20 20 # Imports
21 21 #-----------------------------------------------------------------------------
22 22
23 23 # stdlib imports
24 24 import atexit
25 25 import json
26 26 import os
27 27 import shutil
28 28 import signal
29 29 import sys
30 30 import uuid
31 31
32 32
33 33 # Local imports
34 34 from IPython.config.application import boolean_flag
35 35 from IPython.config.configurable import Configurable
36 36 from IPython.core.profiledir import ProfileDir
37 from IPython.lib.kernel import tunnel_to_kernel, find_connection_file, swallow_argv
38 37 from IPython.zmq.blockingkernelmanager import BlockingKernelManager
39 38 from IPython.zmq.kernelmanager import KernelManager
39 from IPython.utils.kernel import tunnel_to_kernel, find_connection_file, swallow_argv
40 40 from IPython.utils.path import filefind
41 41 from IPython.utils.py3compat import str_to_bytes
42 42 from IPython.utils.traitlets import (
43 43 Dict, List, Unicode, CUnicode, Int, CBool, Any, CaselessStrEnum
44 44 )
45 45 from IPython.zmq.ipkernel import (
46 46 flags as ipkernel_flags,
47 47 aliases as ipkernel_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 68 flags = dict(ipkernel_flags)
69 69
70 70 # the flags that are specific to the frontend
71 71 # these must be scrubbed before being passed to the kernel,
72 72 # or it will raise an error on unrecognized flags
73 73 app_flags = {
74 74 'existing' : ({'IPythonConsoleApp' : {'existing' : 'kernel*.json'}},
75 75 "Connect to an existing kernel. If no argument specified, guess most recent"),
76 76 }
77 77 app_flags.update(boolean_flag(
78 78 'confirm-exit', 'IPythonConsoleApp.confirm_exit',
79 79 """Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
80 80 to force a direct exit without any confirmation.
81 81 """,
82 82 """Don't prompt the user when exiting. This will terminate the kernel
83 83 if it is owned by the frontend, and leave it alive if it is external.
84 84 """
85 85 ))
86 86 flags.update(app_flags)
87 87
88 88 aliases = dict(ipkernel_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 from IPython.lib.kernel import swallow_argv
65 64 from IPython.zmq.session import Session, default_secure
66 65 from IPython.zmq.zmqshell import ZMQInteractiveShell
67 66 from IPython.zmq.ipkernel import (
68 67 flags as ipkernel_flags,
69 68 aliases as ipkernel_aliases,
70 69 IPKernelApp
71 70 )
72 71 from IPython.utils.importstring import import_item
73 72 from IPython.utils.localinterfaces import LOCALHOST
73 from IPython.utils.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 198 flags = dict(ipkernel_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 237 aliases = dict(ipkernel_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 from IPython.lib.kernel import tunnel_to_kernel, find_connection_file
63 62 from IPython.frontend.qt.console.frontend_widget import FrontendWidget
64 63 from IPython.frontend.qt.console.ipython_widget import IPythonWidget
65 64 from IPython.frontend.qt.console.rich_ipython_widget import RichIPythonWidget
66 65 from IPython.frontend.qt.console import styles
67 66 from IPython.frontend.qt.console.mainwindow import MainWindow
68 67 from IPython.frontend.qt.kernelmanager import QtKernelManager
68 from IPython.utils.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 74 from IPython.zmq.ipkernel 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,315 +1,12 b''
1 """Utilities for connecting to kernels
2
3 Authors:
4
5 * Min Ragan-Kelley
1 """[DEPRECATED] Utilities for connecting to kernels
6 2
3 Moved to IPython.utils.kernel, where it always belonged.
7 4 """
8 5
9 #-----------------------------------------------------------------------------
10 # Copyright (C) 2011 The IPython Development Team
11 #
12 # Distributed under the terms of the BSD License. The full license is in
13 # the file COPYING, distributed as part of this software.
14 #-----------------------------------------------------------------------------
15
16 #-----------------------------------------------------------------------------
17 # Imports
18 #-----------------------------------------------------------------------------
19
20 import glob
21 import json
22 import os
23 import sys
24 from getpass import getpass
25 from subprocess import Popen, PIPE
26
27 # external imports
28 from IPython.external.ssh import tunnel
29
30 # IPython imports
31 from IPython.core.profiledir import ProfileDir
32 from IPython.utils.path import filefind, get_ipython_dir
33 from IPython.utils.py3compat import str_to_bytes
34
35
36 #-----------------------------------------------------------------------------
37 # Functions
38 #-----------------------------------------------------------------------------
39
40 def get_connection_file(app=None):
41 """Return the path to the connection file of an app
42
43 Parameters
44 ----------
45 app : KernelApp instance [optional]
46 If unspecified, the currently running app will be used
47 """
48 if app is None:
49 from IPython.zmq.ipkernel import IPKernelApp
50 if not IPKernelApp.initialized():
51 raise RuntimeError("app not specified, and not in a running Kernel")
52
53 app = IPKernelApp.instance()
54 return filefind(app.connection_file, ['.', app.profile_dir.security_dir])
55
56 def find_connection_file(filename, profile=None):
57 """find a connection file, and return its absolute path.
58
59 The current working directory and the profile's security
60 directory will be searched for the file if it is not given by
61 absolute path.
62
63 If profile is unspecified, then the current running application's
64 profile will be used, or 'default', if not run from IPython.
65
66 If the argument does not match an existing file, it will be interpreted as a
67 fileglob, and the matching file in the profile's security dir with
68 the latest access time will be used.
69
70 Parameters
71 ----------
72 filename : str
73 The connection file or fileglob to search for.
74 profile : str [optional]
75 The name of the profile to use when searching for the connection file,
76 if different from the current IPython session or 'default'.
77
78 Returns
79 -------
80 str : The absolute path of the connection file.
81 """
82 from IPython.core.application import BaseIPythonApplication as IPApp
83 try:
84 # quick check for absolute path, before going through logic
85 return filefind(filename)
86 except IOError:
87 pass
88
89 if profile is None:
90 # profile unspecified, check if running from an IPython app
91 if IPApp.initialized():
92 app = IPApp.instance()
93 profile_dir = app.profile_dir
94 else:
95 # not running in IPython, use default profile
96 profile_dir = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), 'default')
97 else:
98 # find profiledir by profile name:
99 profile_dir = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), profile)
100 security_dir = profile_dir.security_dir
101
102 try:
103 # first, try explicit name
104 return filefind(filename, ['.', security_dir])
105 except IOError:
106 pass
107
108 # not found by full name
109
110 if '*' in filename:
111 # given as a glob already
112 pat = filename
113 else:
114 # accept any substring match
115 pat = '*%s*' % filename
116 matches = glob.glob( os.path.join(security_dir, pat) )
117 if not matches:
118 raise IOError("Could not find %r in %r" % (filename, security_dir))
119 elif len(matches) == 1:
120 return matches[0]
121 else:
122 # get most recent match, by access time:
123 return sorted(matches, key=lambda f: os.stat(f).st_atime)[-1]
124
125 def get_connection_info(connection_file=None, unpack=False, profile=None):
126 """Return the connection information for the current Kernel.
127
128 Parameters
129 ----------
130 connection_file : str [optional]
131 The connection file to be used. Can be given by absolute path, or
132 IPython will search in the security directory of a given profile.
133 If run from IPython,
134
135 If unspecified, the connection file for the currently running
136 IPython Kernel will be used, which is only allowed from inside a kernel.
137 unpack : bool [default: False]
138 if True, return the unpacked dict, otherwise just the string contents
139 of the file.
140 profile : str [optional]
141 The name of the profile to use when searching for the connection file,
142 if different from the current IPython session or 'default'.
143
144
145 Returns
146 -------
147 The connection dictionary of the current kernel, as string or dict,
148 depending on `unpack`.
149 """
150 if connection_file is None:
151 # get connection file from current kernel
152 cf = get_connection_file()
153 else:
154 # connection file specified, allow shortnames:
155 cf = find_connection_file(connection_file, profile=profile)
156
157 with open(cf) as f:
158 info = f.read()
159
160 if unpack:
161 info = json.loads(info)
162 # ensure key is bytes:
163 info['key'] = str_to_bytes(info.get('key', ''))
164 return info
165
166 def connect_qtconsole(connection_file=None, argv=None, profile=None):
167 """Connect a qtconsole to the current kernel.
168
169 This is useful for connecting a second qtconsole to a kernel, or to a
170 local notebook.
171
172 Parameters
173 ----------
174 connection_file : str [optional]
175 The connection file to be used. Can be given by absolute path, or
176 IPython will search in the security directory of a given profile.
177 If run from IPython,
178
179 If unspecified, the connection file for the currently running
180 IPython Kernel will be used, which is only allowed from inside a kernel.
181 argv : list [optional]
182 Any extra args to be passed to the console.
183 profile : str [optional]
184 The name of the profile to use when searching for the connection file,
185 if different from the current IPython session or 'default'.
186
187
188 Returns
189 -------
190 subprocess.Popen instance running the qtconsole frontend
191 """
192 argv = [] if argv is None else argv
193
194 if connection_file is None:
195 # get connection file from current kernel
196 cf = get_connection_file()
197 else:
198 cf = find_connection_file(connection_file, profile=profile)
199
200 cmd = ';'.join([
201 "from IPython.frontend.qt.console import qtconsoleapp",
202 "qtconsoleapp.main()"
203 ])
204
205 return Popen([sys.executable, '-c', cmd, '--existing', cf] + argv, stdout=PIPE, stderr=PIPE)
206
207 def tunnel_to_kernel(connection_info, sshserver, sshkey=None):
208 """tunnel connections to a kernel via ssh
209
210 This will open four SSH tunnels from localhost on this machine to the
211 ports associated with the kernel. They can be either direct
212 localhost-localhost tunnels, or if an intermediate server is necessary,
213 the kernel must be listening on a public IP.
214
215 Parameters
216 ----------
217 connection_info : dict or str (path)
218 Either a connection dict, or the path to a JSON connection file
219 sshserver : str
220 The ssh sever to use to tunnel to the kernel. Can be a full
221 `user@server:port` string. ssh config aliases are respected.
222 sshkey : str [optional]
223 Path to file containing ssh key to use for authentication.
224 Only necessary if your ssh config does not already associate
225 a keyfile with the host.
226
227 Returns
228 -------
229
230 (shell, iopub, stdin, hb) : ints
231 The four ports on localhost that have been forwarded to the kernel.
232 """
233 if isinstance(connection_info, basestring):
234 # it's a path, unpack it
235 with open(connection_info) as f:
236 connection_info = json.loads(f.read())
237
238 cf = connection_info
239
240 lports = tunnel.select_random_ports(4)
241 rports = cf['shell_port'], cf['iopub_port'], cf['stdin_port'], cf['hb_port']
242
243 remote_ip = cf['ip']
244
245 if tunnel.try_passwordless_ssh(sshserver, sshkey):
246 password=False
247 else:
248 password = getpass("SSH Password for %s: "%sshserver)
249
250 for lp,rp in zip(lports, rports):
251 tunnel.ssh_tunnel(lp, rp, sshserver, remote_ip, sshkey, password)
252
253 return tuple(lports)
254
6 import warnings
7 warnings.warn("IPython.lib.kernel moved to IPython.utils.kernel in IPython 0.14",
8 DeprecationWarning
9 )
255 10
256 def swallow_argv(argv, aliases=None, flags=None):
257 """strip frontend-specific aliases and flags from an argument list
258
259 For use primarily in frontend apps that want to pass a subset of command-line
260 arguments through to a subprocess, where frontend-specific flags and aliases
261 should be removed from the list.
262
263 Parameters
264 ----------
265
266 argv : list(str)
267 The starting argv, to be filtered
268 aliases : container of aliases (dict, list, set, etc.)
269 The frontend-specific aliases to be removed
270 flags : container of flags (dict, list, set, etc.)
271 The frontend-specific flags to be removed
272
273 Returns
274 -------
275
276 argv : list(str)
277 The argv list, excluding flags and aliases that have been stripped
278 """
279
280 if aliases is None:
281 aliases = set()
282 if flags is None:
283 flags = set()
284
285 stripped = list(argv) # copy
286
287 swallow_next = False
288 was_flag = False
289 for a in argv:
290 if swallow_next:
291 swallow_next = False
292 # last arg was an alias, remove the next one
293 # *unless* the last alias has a no-arg flag version, in which
294 # case, don't swallow the next arg if it's also a flag:
295 if not (was_flag and a.startswith('-')):
296 stripped.remove(a)
297 continue
298 if a.startswith('-'):
299 split = a.lstrip('-').split('=')
300 alias = split[0]
301 if alias in aliases:
302 stripped.remove(a)
303 if len(split) == 1:
304 # alias passed with arg via space
305 swallow_next = True
306 # could have been a flag that matches an alias, e.g. `existing`
307 # in which case, we might not swallow the next arg
308 was_flag = alias in flags
309 elif alias in flags and len(split) == 1:
310 # strip flag, but don't swallow next, as flags don't take args
311 stripped.remove(a)
312
313 # return shortened list
314 return stripped
11 from IPython.utils.kernel import *
315 12
@@ -1,63 +1,63 b''
1 1 """Tests for kernel utility functions
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 Modified BSD License.
11 11 #
12 12 # The full license is in the file COPYING.txt, distributed with this software.
13 13 #-----------------------------------------------------------------------------
14 14
15 15 #-----------------------------------------------------------------------------
16 16 # Imports
17 17 #-----------------------------------------------------------------------------
18 18
19 19 # Stdlib imports
20 20 from unittest import TestCase
21 21
22 22 # Third-party imports
23 23 import nose.tools as nt
24 24
25 25 # Our own imports
26 26 from IPython.testing import decorators as dec
27 from IPython.lib import kernel
27 from IPython.utils import kernel
28 28
29 29 #-----------------------------------------------------------------------------
30 30 # Classes and functions
31 31 #-----------------------------------------------------------------------------
32 32
33 33 @dec.parametric
34 34 def test_swallow_argv():
35 35 tests = [
36 36 # expected , argv , aliases, flags
37 37 (['-a', '5'], ['-a', '5'], None, None),
38 38 (['5'], ['-a', '5'], None, ['a']),
39 39 ([], ['-a', '5'], ['a'], None),
40 40 ([], ['-a', '5'], ['a'], ['a']),
41 41 ([], ['--foo'], None, ['foo']),
42 42 (['--foo'], ['--foo'], ['foobar'], []),
43 43 ([], ['--foo', '5'], ['foo'], []),
44 44 ([], ['--foo=5'], ['foo'], []),
45 45 (['--foo=5'], ['--foo=5'], [], ['foo']),
46 46 (['5'], ['--foo', '5'], [], ['foo']),
47 47 (['bar'], ['--foo', '5', 'bar'], ['foo'], ['foo']),
48 48 (['bar'], ['--foo=5', 'bar'], ['foo'], ['foo']),
49 49 (['5','bar'], ['--foo', '5', 'bar'], None, ['foo']),
50 50 (['bar'], ['--foo', '5', 'bar'], ['foo'], None),
51 51 (['bar'], ['--foo=5', 'bar'], ['foo'], None),
52 52 ]
53 53 for expected, argv, aliases, flags in tests:
54 54 stripped = kernel.swallow_argv(argv, aliases=aliases, flags=flags)
55 55 message = '\n'.join(['',
56 56 "argv: %r" % argv,
57 57 "aliases: %r" % aliases,
58 58 "flags : %r" % flags,
59 59 "expected : %r" % expected,
60 60 "returned : %r" % stripped,
61 61 ])
62 62 yield nt.assert_equal(expected, stripped, message)
63 63
@@ -1,591 +1,591 b''
1 1 """A ZMQ-based subclass of InteractiveShell.
2 2
3 3 This code is meant to ease the refactoring of the base InteractiveShell into
4 4 something with a cleaner architecture for 2-process use, without actually
5 5 breaking InteractiveShell itself. So we're doing something a bit ugly, where
6 6 we subclass and override what we want to fix. Once this is working well, we
7 7 can go back to the base class and refactor the code for a cleaner inheritance
8 8 implementation that doesn't rely on so much monkeypatching.
9 9
10 10 But this lets us maintain a fully working IPython as we develop the new
11 11 machinery. This should thus be thought of as scaffolding.
12 12 """
13 13 #-----------------------------------------------------------------------------
14 14 # Imports
15 15 #-----------------------------------------------------------------------------
16 16 from __future__ import print_function
17 17
18 18 # Stdlib
19 19 import os
20 20 import sys
21 21 import time
22 22
23 23 # System library imports
24 24 from zmq.eventloop import ioloop
25 25
26 26 # Our own
27 27 from IPython.core.interactiveshell import (
28 28 InteractiveShell, InteractiveShellABC
29 29 )
30 30 from IPython.core import page
31 31 from IPython.core.autocall import ZMQExitAutocall
32 32 from IPython.core.displaypub import DisplayPublisher
33 33 from IPython.core.error import UsageError
34 34 from IPython.core.magics import MacroToEdit, CodeMagics
35 35 from IPython.core.magic import magics_class, line_magic, Magics
36 36 from IPython.core.payloadpage import install_payload_page
37 37 from IPython.inprocess.socket import SocketABC
38 from IPython.lib.kernel import (
38 from IPython.utils.kernel import (
39 39 get_connection_file, get_connection_info, connect_qtconsole
40 40 )
41 41 from IPython.testing.skipdoctest import skip_doctest
42 42 from IPython.utils import io, openpy
43 43 from IPython.utils.jsonutil import json_clean, encode_images
44 44 from IPython.utils.process import arg_split
45 45 from IPython.utils import py3compat
46 46 from IPython.utils.traitlets import Instance, Type, Dict, CBool, CBytes
47 47 from IPython.utils.warn import warn, error
48 48 from IPython.zmq.displayhook import ZMQShellDisplayHook
49 49 from IPython.zmq.datapub import ZMQDataPublisher
50 50 from IPython.zmq.session import extract_header
51 51 from session import Session
52 52
53 53 #-----------------------------------------------------------------------------
54 54 # Functions and classes
55 55 #-----------------------------------------------------------------------------
56 56
57 57 class ZMQDisplayPublisher(DisplayPublisher):
58 58 """A display publisher that publishes data using a ZeroMQ PUB socket."""
59 59
60 60 session = Instance(Session)
61 61 pub_socket = Instance(SocketABC)
62 62 parent_header = Dict({})
63 63 topic = CBytes(b'displaypub')
64 64
65 65 def set_parent(self, parent):
66 66 """Set the parent for outbound messages."""
67 67 self.parent_header = extract_header(parent)
68 68
69 69 def _flush_streams(self):
70 70 """flush IO Streams prior to display"""
71 71 sys.stdout.flush()
72 72 sys.stderr.flush()
73 73
74 74 def publish(self, source, data, metadata=None):
75 75 self._flush_streams()
76 76 if metadata is None:
77 77 metadata = {}
78 78 self._validate_data(source, data, metadata)
79 79 content = {}
80 80 content['source'] = source
81 81 content['data'] = encode_images(data)
82 82 content['metadata'] = metadata
83 83 self.session.send(
84 84 self.pub_socket, u'display_data', json_clean(content),
85 85 parent=self.parent_header, ident=self.topic,
86 86 )
87 87
88 88 def clear_output(self, stdout=True, stderr=True, other=True):
89 89 content = dict(stdout=stdout, stderr=stderr, other=other)
90 90
91 91 if stdout:
92 92 print('\r', file=sys.stdout, end='')
93 93 if stderr:
94 94 print('\r', file=sys.stderr, end='')
95 95
96 96 self._flush_streams()
97 97
98 98 self.session.send(
99 99 self.pub_socket, u'clear_output', content,
100 100 parent=self.parent_header, ident=self.topic,
101 101 )
102 102
103 103 @magics_class
104 104 class KernelMagics(Magics):
105 105 #------------------------------------------------------------------------
106 106 # Magic overrides
107 107 #------------------------------------------------------------------------
108 108 # Once the base class stops inheriting from magic, this code needs to be
109 109 # moved into a separate machinery as well. For now, at least isolate here
110 110 # the magics which this class needs to implement differently from the base
111 111 # class, or that are unique to it.
112 112
113 113 @line_magic
114 114 def doctest_mode(self, parameter_s=''):
115 115 """Toggle doctest mode on and off.
116 116
117 117 This mode is intended to make IPython behave as much as possible like a
118 118 plain Python shell, from the perspective of how its prompts, exceptions
119 119 and output look. This makes it easy to copy and paste parts of a
120 120 session into doctests. It does so by:
121 121
122 122 - Changing the prompts to the classic ``>>>`` ones.
123 123 - Changing the exception reporting mode to 'Plain'.
124 124 - Disabling pretty-printing of output.
125 125
126 126 Note that IPython also supports the pasting of code snippets that have
127 127 leading '>>>' and '...' prompts in them. This means that you can paste
128 128 doctests from files or docstrings (even if they have leading
129 129 whitespace), and the code will execute correctly. You can then use
130 130 '%history -t' to see the translated history; this will give you the
131 131 input after removal of all the leading prompts and whitespace, which
132 132 can be pasted back into an editor.
133 133
134 134 With these features, you can switch into this mode easily whenever you
135 135 need to do testing and changes to doctests, without having to leave
136 136 your existing IPython session.
137 137 """
138 138
139 139 from IPython.utils.ipstruct import Struct
140 140
141 141 # Shorthands
142 142 shell = self.shell
143 143 disp_formatter = self.shell.display_formatter
144 144 ptformatter = disp_formatter.formatters['text/plain']
145 145 # dstore is a data store kept in the instance metadata bag to track any
146 146 # changes we make, so we can undo them later.
147 147 dstore = shell.meta.setdefault('doctest_mode', Struct())
148 148 save_dstore = dstore.setdefault
149 149
150 150 # save a few values we'll need to recover later
151 151 mode = save_dstore('mode', False)
152 152 save_dstore('rc_pprint', ptformatter.pprint)
153 153 save_dstore('rc_plain_text_only',disp_formatter.plain_text_only)
154 154 save_dstore('xmode', shell.InteractiveTB.mode)
155 155
156 156 if mode == False:
157 157 # turn on
158 158 ptformatter.pprint = False
159 159 disp_formatter.plain_text_only = True
160 160 shell.magic('xmode Plain')
161 161 else:
162 162 # turn off
163 163 ptformatter.pprint = dstore.rc_pprint
164 164 disp_formatter.plain_text_only = dstore.rc_plain_text_only
165 165 shell.magic("xmode " + dstore.xmode)
166 166
167 167 # Store new mode and inform on console
168 168 dstore.mode = bool(1-int(mode))
169 169 mode_label = ['OFF','ON'][dstore.mode]
170 170 print('Doctest mode is:', mode_label)
171 171
172 172 # Send the payload back so that clients can modify their prompt display
173 173 payload = dict(
174 174 source='IPython.zmq.zmqshell.ZMQInteractiveShell.doctest_mode',
175 175 mode=dstore.mode)
176 176 shell.payload_manager.write_payload(payload)
177 177
178 178
179 179 _find_edit_target = CodeMagics._find_edit_target
180 180
181 181 @skip_doctest
182 182 @line_magic
183 183 def edit(self, parameter_s='', last_call=['','']):
184 184 """Bring up an editor and execute the resulting code.
185 185
186 186 Usage:
187 187 %edit [options] [args]
188 188
189 189 %edit runs an external text editor. You will need to set the command for
190 190 this editor via the ``TerminalInteractiveShell.editor`` option in your
191 191 configuration file before it will work.
192 192
193 193 This command allows you to conveniently edit multi-line code right in
194 194 your IPython session.
195 195
196 196 If called without arguments, %edit opens up an empty editor with a
197 197 temporary file and will execute the contents of this file when you
198 198 close it (don't forget to save it!).
199 199
200 200
201 201 Options:
202 202
203 203 -n <number>: open the editor at a specified line number. By default,
204 204 the IPython editor hook uses the unix syntax 'editor +N filename', but
205 205 you can configure this by providing your own modified hook if your
206 206 favorite editor supports line-number specifications with a different
207 207 syntax.
208 208
209 209 -p: this will call the editor with the same data as the previous time
210 210 it was used, regardless of how long ago (in your current session) it
211 211 was.
212 212
213 213 -r: use 'raw' input. This option only applies to input taken from the
214 214 user's history. By default, the 'processed' history is used, so that
215 215 magics are loaded in their transformed version to valid Python. If
216 216 this option is given, the raw input as typed as the command line is
217 217 used instead. When you exit the editor, it will be executed by
218 218 IPython's own processor.
219 219
220 220 -x: do not execute the edited code immediately upon exit. This is
221 221 mainly useful if you are editing programs which need to be called with
222 222 command line arguments, which you can then do using %run.
223 223
224 224
225 225 Arguments:
226 226
227 227 If arguments are given, the following possibilites exist:
228 228
229 229 - The arguments are numbers or pairs of colon-separated numbers (like
230 230 1 4:8 9). These are interpreted as lines of previous input to be
231 231 loaded into the editor. The syntax is the same of the %macro command.
232 232
233 233 - If the argument doesn't start with a number, it is evaluated as a
234 234 variable and its contents loaded into the editor. You can thus edit
235 235 any string which contains python code (including the result of
236 236 previous edits).
237 237
238 238 - If the argument is the name of an object (other than a string),
239 239 IPython will try to locate the file where it was defined and open the
240 240 editor at the point where it is defined. You can use `%edit function`
241 241 to load an editor exactly at the point where 'function' is defined,
242 242 edit it and have the file be executed automatically.
243 243
244 244 If the object is a macro (see %macro for details), this opens up your
245 245 specified editor with a temporary file containing the macro's data.
246 246 Upon exit, the macro is reloaded with the contents of the file.
247 247
248 248 Note: opening at an exact line is only supported under Unix, and some
249 249 editors (like kedit and gedit up to Gnome 2.8) do not understand the
250 250 '+NUMBER' parameter necessary for this feature. Good editors like
251 251 (X)Emacs, vi, jed, pico and joe all do.
252 252
253 253 - If the argument is not found as a variable, IPython will look for a
254 254 file with that name (adding .py if necessary) and load it into the
255 255 editor. It will execute its contents with execfile() when you exit,
256 256 loading any code in the file into your interactive namespace.
257 257
258 258 After executing your code, %edit will return as output the code you
259 259 typed in the editor (except when it was an existing file). This way
260 260 you can reload the code in further invocations of %edit as a variable,
261 261 via _<NUMBER> or Out[<NUMBER>], where <NUMBER> is the prompt number of
262 262 the output.
263 263
264 264 Note that %edit is also available through the alias %ed.
265 265
266 266 This is an example of creating a simple function inside the editor and
267 267 then modifying it. First, start up the editor:
268 268
269 269 In [1]: ed
270 270 Editing... done. Executing edited code...
271 271 Out[1]: 'def foo():n print "foo() was defined in an editing session"n'
272 272
273 273 We can then call the function foo():
274 274
275 275 In [2]: foo()
276 276 foo() was defined in an editing session
277 277
278 278 Now we edit foo. IPython automatically loads the editor with the
279 279 (temporary) file where foo() was previously defined:
280 280
281 281 In [3]: ed foo
282 282 Editing... done. Executing edited code...
283 283
284 284 And if we call foo() again we get the modified version:
285 285
286 286 In [4]: foo()
287 287 foo() has now been changed!
288 288
289 289 Here is an example of how to edit a code snippet successive
290 290 times. First we call the editor:
291 291
292 292 In [5]: ed
293 293 Editing... done. Executing edited code...
294 294 hello
295 295 Out[5]: "print 'hello'n"
296 296
297 297 Now we call it again with the previous output (stored in _):
298 298
299 299 In [6]: ed _
300 300 Editing... done. Executing edited code...
301 301 hello world
302 302 Out[6]: "print 'hello world'n"
303 303
304 304 Now we call it with the output #8 (stored in _8, also as Out[8]):
305 305
306 306 In [7]: ed _8
307 307 Editing... done. Executing edited code...
308 308 hello again
309 309 Out[7]: "print 'hello again'n"
310 310 """
311 311
312 312 opts,args = self.parse_options(parameter_s,'prn:')
313 313
314 314 try:
315 315 filename, lineno, _ = CodeMagics._find_edit_target(self.shell, args, opts, last_call)
316 316 except MacroToEdit as e:
317 317 # TODO: Implement macro editing over 2 processes.
318 318 print("Macro editing not yet implemented in 2-process model.")
319 319 return
320 320
321 321 # Make sure we send to the client an absolute path, in case the working
322 322 # directory of client and kernel don't match
323 323 filename = os.path.abspath(filename)
324 324
325 325 payload = {
326 326 'source' : 'IPython.zmq.zmqshell.ZMQInteractiveShell.edit_magic',
327 327 'filename' : filename,
328 328 'line_number' : lineno
329 329 }
330 330 self.shell.payload_manager.write_payload(payload)
331 331
332 332 # A few magics that are adapted to the specifics of using pexpect and a
333 333 # remote terminal
334 334
335 335 @line_magic
336 336 def clear(self, arg_s):
337 337 """Clear the terminal."""
338 338 if os.name == 'posix':
339 339 self.shell.system("clear")
340 340 else:
341 341 self.shell.system("cls")
342 342
343 343 if os.name == 'nt':
344 344 # This is the usual name in windows
345 345 cls = line_magic('cls')(clear)
346 346
347 347 # Terminal pagers won't work over pexpect, but we do have our own pager
348 348
349 349 @line_magic
350 350 def less(self, arg_s):
351 351 """Show a file through the pager.
352 352
353 353 Files ending in .py are syntax-highlighted."""
354 354 if not arg_s:
355 355 raise UsageError('Missing filename.')
356 356
357 357 cont = open(arg_s).read()
358 358 if arg_s.endswith('.py'):
359 359 cont = self.shell.pycolorize(openpy.read_py_file(arg_s, skip_encoding_cookie=False))
360 360 else:
361 361 cont = open(arg_s).read()
362 362 page.page(cont)
363 363
364 364 more = line_magic('more')(less)
365 365
366 366 # Man calls a pager, so we also need to redefine it
367 367 if os.name == 'posix':
368 368 @line_magic
369 369 def man(self, arg_s):
370 370 """Find the man page for the given command and display in pager."""
371 371 page.page(self.shell.getoutput('man %s | col -b' % arg_s,
372 372 split=False))
373 373
374 374 @line_magic
375 375 def connect_info(self, arg_s):
376 376 """Print information for connecting other clients to this kernel
377 377
378 378 It will print the contents of this session's connection file, as well as
379 379 shortcuts for local clients.
380 380
381 381 In the simplest case, when called from the most recently launched kernel,
382 382 secondary clients can be connected, simply with:
383 383
384 384 $> ipython <app> --existing
385 385
386 386 """
387 387
388 388 from IPython.core.application import BaseIPythonApplication as BaseIPApp
389 389
390 390 if BaseIPApp.initialized():
391 391 app = BaseIPApp.instance()
392 392 security_dir = app.profile_dir.security_dir
393 393 profile = app.profile
394 394 else:
395 395 profile = 'default'
396 396 security_dir = ''
397 397
398 398 try:
399 399 connection_file = get_connection_file()
400 400 info = get_connection_info(unpack=False)
401 401 except Exception as e:
402 402 error("Could not get connection info: %r" % e)
403 403 return
404 404
405 405 # add profile flag for non-default profile
406 406 profile_flag = "--profile %s" % profile if profile != 'default' else ""
407 407
408 408 # if it's in the security dir, truncate to basename
409 409 if security_dir == os.path.dirname(connection_file):
410 410 connection_file = os.path.basename(connection_file)
411 411
412 412
413 413 print (info + '\n')
414 414 print ("Paste the above JSON into a file, and connect with:\n"
415 415 " $> ipython <app> --existing <file>\n"
416 416 "or, if you are local, you can connect with just:\n"
417 417 " $> ipython <app> --existing {0} {1}\n"
418 418 "or even just:\n"
419 419 " $> ipython <app> --existing {1}\n"
420 420 "if this is the most recent IPython session you have started.".format(
421 421 connection_file, profile_flag
422 422 )
423 423 )
424 424
425 425 @line_magic
426 426 def qtconsole(self, arg_s):
427 427 """Open a qtconsole connected to this kernel.
428 428
429 429 Useful for connecting a qtconsole to running notebooks, for better
430 430 debugging.
431 431 """
432 432
433 433 # %qtconsole should imply bind_kernel for engines:
434 434 try:
435 435 from IPython.parallel import bind_kernel
436 436 except ImportError:
437 437 # technically possible, because parallel has higher pyzmq min-version
438 438 pass
439 439 else:
440 440 bind_kernel()
441 441
442 442 try:
443 443 p = connect_qtconsole(argv=arg_split(arg_s, os.name=='posix'))
444 444 except Exception as e:
445 445 error("Could not start qtconsole: %r" % e)
446 446 return
447 447
448 448 def safe_unicode(e):
449 449 """unicode(e) with various fallbacks. Used for exceptions, which may not be
450 450 safe to call unicode() on.
451 451 """
452 452 try:
453 453 return unicode(e)
454 454 except UnicodeError:
455 455 pass
456 456
457 457 try:
458 458 return py3compat.str_to_unicode(str(e))
459 459 except UnicodeError:
460 460 pass
461 461
462 462 try:
463 463 return py3compat.str_to_unicode(repr(e))
464 464 except UnicodeError:
465 465 pass
466 466
467 467 return u'Unrecoverably corrupt evalue'
468 468
469 469
470 470 class ZMQInteractiveShell(InteractiveShell):
471 471 """A subclass of InteractiveShell for ZMQ."""
472 472
473 473 displayhook_class = Type(ZMQShellDisplayHook)
474 474 display_pub_class = Type(ZMQDisplayPublisher)
475 475 data_pub_class = Type(ZMQDataPublisher)
476 476
477 477 # Override the traitlet in the parent class, because there's no point using
478 478 # readline for the kernel. Can be removed when the readline code is moved
479 479 # to the terminal frontend.
480 480 colors_force = CBool(True)
481 481 readline_use = CBool(False)
482 482 # autoindent has no meaning in a zmqshell, and attempting to enable it
483 483 # will print a warning in the absence of readline.
484 484 autoindent = CBool(False)
485 485
486 486 exiter = Instance(ZMQExitAutocall)
487 487 def _exiter_default(self):
488 488 return ZMQExitAutocall(self)
489 489
490 490 def _exit_now_changed(self, name, old, new):
491 491 """stop eventloop when exit_now fires"""
492 492 if new:
493 493 loop = ioloop.IOLoop.instance()
494 494 loop.add_timeout(time.time()+0.1, loop.stop)
495 495
496 496 keepkernel_on_exit = None
497 497
498 498 # Over ZeroMQ, GUI control isn't done with PyOS_InputHook as there is no
499 499 # interactive input being read; we provide event loop support in ipkernel
500 500 from .eventloops import enable_gui
501 501 enable_gui = staticmethod(enable_gui)
502 502
503 503 def init_environment(self):
504 504 """Configure the user's environment.
505 505
506 506 """
507 507 env = os.environ
508 508 # These two ensure 'ls' produces nice coloring on BSD-derived systems
509 509 env['TERM'] = 'xterm-color'
510 510 env['CLICOLOR'] = '1'
511 511 # Since normal pagers don't work at all (over pexpect we don't have
512 512 # single-key control of the subprocess), try to disable paging in
513 513 # subprocesses as much as possible.
514 514 env['PAGER'] = 'cat'
515 515 env['GIT_PAGER'] = 'cat'
516 516
517 517 # And install the payload version of page.
518 518 install_payload_page()
519 519
520 520 def auto_rewrite_input(self, cmd):
521 521 """Called to show the auto-rewritten input for autocall and friends.
522 522
523 523 FIXME: this payload is currently not correctly processed by the
524 524 frontend.
525 525 """
526 526 new = self.prompt_manager.render('rewrite') + cmd
527 527 payload = dict(
528 528 source='IPython.zmq.zmqshell.ZMQInteractiveShell.auto_rewrite_input',
529 529 transformed_input=new,
530 530 )
531 531 self.payload_manager.write_payload(payload)
532 532
533 533 def ask_exit(self):
534 534 """Engage the exit actions."""
535 535 self.exit_now = True
536 536 payload = dict(
537 537 source='IPython.zmq.zmqshell.ZMQInteractiveShell.ask_exit',
538 538 exit=True,
539 539 keepkernel=self.keepkernel_on_exit,
540 540 )
541 541 self.payload_manager.write_payload(payload)
542 542
543 543 def _showtraceback(self, etype, evalue, stb):
544 544
545 545 exc_content = {
546 546 u'traceback' : stb,
547 547 u'ename' : unicode(etype.__name__),
548 548 u'evalue' : safe_unicode(evalue)
549 549 }
550 550
551 551 dh = self.displayhook
552 552 # Send exception info over pub socket for other clients than the caller
553 553 # to pick up
554 554 topic = None
555 555 if dh.topic:
556 556 topic = dh.topic.replace(b'pyout', b'pyerr')
557 557
558 558 exc_msg = dh.session.send(dh.pub_socket, u'pyerr', json_clean(exc_content), dh.parent_header, ident=topic)
559 559
560 560 # FIXME - Hack: store exception info in shell object. Right now, the
561 561 # caller is reading this info after the fact, we need to fix this logic
562 562 # to remove this hack. Even uglier, we need to store the error status
563 563 # here, because in the main loop, the logic that sets it is being
564 564 # skipped because runlines swallows the exceptions.
565 565 exc_content[u'status'] = u'error'
566 566 self._reply_content = exc_content
567 567 # /FIXME
568 568
569 569 return exc_content
570 570
571 571 def set_next_input(self, text):
572 572 """Send the specified text to the frontend to be presented at the next
573 573 input cell."""
574 574 payload = dict(
575 575 source='IPython.zmq.zmqshell.ZMQInteractiveShell.set_next_input',
576 576 text=text
577 577 )
578 578 self.payload_manager.write_payload(payload)
579 579
580 580 #-------------------------------------------------------------------------
581 581 # Things related to magics
582 582 #-------------------------------------------------------------------------
583 583
584 584 def init_magics(self):
585 585 super(ZMQInteractiveShell, self).init_magics()
586 586 self.register_magics(KernelMagics)
587 587 self.magics_manager.register_alias('ed', 'edit')
588 588
589 589
590 590
591 591 InteractiveShellABC.register(ZMQInteractiveShell)
General Comments 0
You need to be logged in to leave comments. Login now