##// END OF EJS Templates
remove redundant parts of kernelapp...
Paul Ivanov -
Show More
@@ -1,558 +1,564 b''
1 """Utilities for connecting to kernels
1 """Utilities for connecting to kernels
2
2
3 Notable contents:
3 Notable contents:
4 - ConnectionFileMixin class
4 - ConnectionFileMixin class
5 encapsulates the logic related to writing and reading connections files.
5 encapsulates the logic related to writing and reading connections files.
6 """
6 """
7 # Copyright (c) IPython Development Team.
7 # Copyright (c) IPython Development Team.
8 # Distributed under the terms of the Modified BSD License.
8 # Distributed under the terms of the Modified BSD License.
9
9
10 #-----------------------------------------------------------------------------
10 #-----------------------------------------------------------------------------
11 # Imports
11 # Imports
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13
13
14 from __future__ import absolute_import
14 from __future__ import absolute_import
15
15
16 import glob
16 import glob
17 import json
17 import json
18 import os
18 import os
19 import socket
19 import socket
20 import sys
20 import sys
21 from getpass import getpass
21 from getpass import getpass
22 from subprocess import Popen, PIPE
22 from subprocess import Popen, PIPE
23 import tempfile
23 import tempfile
24
24
25 import zmq
25 import zmq
26
26
27 # external imports
27 # external imports
28 from IPython.external.ssh import tunnel
28 from IPython.external.ssh import tunnel
29
29
30 # IPython imports
30 # IPython imports
31 from IPython.config import Configurable
31 from IPython.config import Configurable
32 from IPython.core.profiledir import ProfileDir
32 from IPython.core.profiledir import ProfileDir
33 from IPython.utils.localinterfaces import localhost
33 from IPython.utils.localinterfaces import localhost
34 from IPython.utils.path import filefind, get_ipython_dir
34 from IPython.utils.path import filefind, get_ipython_dir
35 from IPython.utils.py3compat import (str_to_bytes, bytes_to_str, cast_bytes_py2,
35 from IPython.utils.py3compat import (str_to_bytes, bytes_to_str, cast_bytes_py2,
36 string_types)
36 string_types)
37 from IPython.utils.traitlets import (
37 from IPython.utils.traitlets import (
38 Bool, Integer, Unicode, CaselessStrEnum,
38 Bool, Integer, Unicode, CaselessStrEnum,
39 )
39 )
40
40
41
41
42 #-----------------------------------------------------------------------------
42 #-----------------------------------------------------------------------------
43 # Working with Connection Files
43 # Working with Connection Files
44 #-----------------------------------------------------------------------------
44 #-----------------------------------------------------------------------------
45
45
46 def write_connection_file(fname=None, shell_port=0, iopub_port=0, stdin_port=0, hb_port=0,
46 def write_connection_file(fname=None, shell_port=0, iopub_port=0, stdin_port=0, hb_port=0,
47 control_port=0, ip='', key=b'', transport='tcp',
47 control_port=0, ip='', key=b'', transport='tcp',
48 signature_scheme='hmac-sha256',
48 signature_scheme='hmac-sha256',
49 ):
49 ):
50 """Generates a JSON config file, including the selection of random ports.
50 """Generates a JSON config file, including the selection of random ports.
51
51
52 Parameters
52 Parameters
53 ----------
53 ----------
54
54
55 fname : unicode
55 fname : unicode
56 The path to the file to write
56 The path to the file to write
57
57
58 shell_port : int, optional
58 shell_port : int, optional
59 The port to use for ROUTER (shell) channel.
59 The port to use for ROUTER (shell) channel.
60
60
61 iopub_port : int, optional
61 iopub_port : int, optional
62 The port to use for the SUB channel.
62 The port to use for the SUB channel.
63
63
64 stdin_port : int, optional
64 stdin_port : int, optional
65 The port to use for the ROUTER (raw input) channel.
65 The port to use for the ROUTER (raw input) channel.
66
66
67 control_port : int, optional
67 control_port : int, optional
68 The port to use for the ROUTER (control) channel.
68 The port to use for the ROUTER (control) channel.
69
69
70 hb_port : int, optional
70 hb_port : int, optional
71 The port to use for the heartbeat REP channel.
71 The port to use for the heartbeat REP channel.
72
72
73 ip : str, optional
73 ip : str, optional
74 The ip address the kernel will bind to.
74 The ip address the kernel will bind to.
75
75
76 key : str, optional
76 key : str, optional
77 The Session key used for message authentication.
77 The Session key used for message authentication.
78
78
79 signature_scheme : str, optional
79 signature_scheme : str, optional
80 The scheme used for message authentication.
80 The scheme used for message authentication.
81 This has the form 'digest-hash', where 'digest'
81 This has the form 'digest-hash', where 'digest'
82 is the scheme used for digests, and 'hash' is the name of the hash function
82 is the scheme used for digests, and 'hash' is the name of the hash function
83 used by the digest scheme.
83 used by the digest scheme.
84 Currently, 'hmac' is the only supported digest scheme,
84 Currently, 'hmac' is the only supported digest scheme,
85 and 'sha256' is the default hash function.
85 and 'sha256' is the default hash function.
86
86
87 """
87 """
88 if not ip:
88 if not ip:
89 ip = localhost()
89 ip = localhost()
90 # default to temporary connector file
90 # default to temporary connector file
91 if not fname:
91 if not fname:
92 fd, fname = tempfile.mkstemp('.json')
92 fd, fname = tempfile.mkstemp('.json')
93 os.close(fd)
93 os.close(fd)
94
94
95 # Find open ports as necessary.
95 # Find open ports as necessary.
96
96
97 ports = []
97 ports = []
98 ports_needed = int(shell_port <= 0) + \
98 ports_needed = int(shell_port <= 0) + \
99 int(iopub_port <= 0) + \
99 int(iopub_port <= 0) + \
100 int(stdin_port <= 0) + \
100 int(stdin_port <= 0) + \
101 int(control_port <= 0) + \
101 int(control_port <= 0) + \
102 int(hb_port <= 0)
102 int(hb_port <= 0)
103 if transport == 'tcp':
103 if transport == 'tcp':
104 for i in range(ports_needed):
104 for i in range(ports_needed):
105 sock = socket.socket()
105 sock = socket.socket()
106 # struct.pack('ii', (0,0)) is 8 null bytes
106 # struct.pack('ii', (0,0)) is 8 null bytes
107 sock.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER, b'\0' * 8)
107 sock.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER, b'\0' * 8)
108 sock.bind(('', 0))
108 sock.bind(('', 0))
109 ports.append(sock)
109 ports.append(sock)
110 for i, sock in enumerate(ports):
110 for i, sock in enumerate(ports):
111 port = sock.getsockname()[1]
111 port = sock.getsockname()[1]
112 sock.close()
112 sock.close()
113 ports[i] = port
113 ports[i] = port
114 else:
114 else:
115 N = 1
115 N = 1
116 for i in range(ports_needed):
116 for i in range(ports_needed):
117 while os.path.exists("%s-%s" % (ip, str(N))):
117 while os.path.exists("%s-%s" % (ip, str(N))):
118 N += 1
118 N += 1
119 ports.append(N)
119 ports.append(N)
120 N += 1
120 N += 1
121 if shell_port <= 0:
121 if shell_port <= 0:
122 shell_port = ports.pop(0)
122 shell_port = ports.pop(0)
123 if iopub_port <= 0:
123 if iopub_port <= 0:
124 iopub_port = ports.pop(0)
124 iopub_port = ports.pop(0)
125 if stdin_port <= 0:
125 if stdin_port <= 0:
126 stdin_port = ports.pop(0)
126 stdin_port = ports.pop(0)
127 if control_port <= 0:
127 if control_port <= 0:
128 control_port = ports.pop(0)
128 control_port = ports.pop(0)
129 if hb_port <= 0:
129 if hb_port <= 0:
130 hb_port = ports.pop(0)
130 hb_port = ports.pop(0)
131
131
132 cfg = dict( shell_port=shell_port,
132 cfg = dict( shell_port=shell_port,
133 iopub_port=iopub_port,
133 iopub_port=iopub_port,
134 stdin_port=stdin_port,
134 stdin_port=stdin_port,
135 control_port=control_port,
135 control_port=control_port,
136 hb_port=hb_port,
136 hb_port=hb_port,
137 )
137 )
138 cfg['ip'] = ip
138 cfg['ip'] = ip
139 cfg['key'] = bytes_to_str(key)
139 cfg['key'] = bytes_to_str(key)
140 cfg['transport'] = transport
140 cfg['transport'] = transport
141 cfg['signature_scheme'] = signature_scheme
141 cfg['signature_scheme'] = signature_scheme
142
142
143 with open(fname, 'w') as f:
143 with open(fname, 'w') as f:
144 f.write(json.dumps(cfg, indent=2))
144 f.write(json.dumps(cfg, indent=2))
145
145
146 return fname, cfg
146 return fname, cfg
147
147
148
148
149 def get_connection_file(app=None):
149 def get_connection_file(app=None):
150 """Return the path to the connection file of an app
150 """Return the path to the connection file of an app
151
151
152 Parameters
152 Parameters
153 ----------
153 ----------
154 app : IPKernelApp instance [optional]
154 app : IPKernelApp instance [optional]
155 If unspecified, the currently running app will be used
155 If unspecified, the currently running app will be used
156 """
156 """
157 if app is None:
157 if app is None:
158 from IPython.kernel.zmq.kernelapp import IPKernelApp
158 from IPython.kernel.zmq.kernelapp import IPKernelApp
159 if not IPKernelApp.initialized():
159 if not IPKernelApp.initialized():
160 raise RuntimeError("app not specified, and not in a running Kernel")
160 raise RuntimeError("app not specified, and not in a running Kernel")
161
161
162 app = IPKernelApp.instance()
162 app = IPKernelApp.instance()
163 return filefind(app.connection_file, ['.', app.profile_dir.security_dir])
163 return filefind(app.connection_file, ['.', app.profile_dir.security_dir])
164
164
165
165
166 def find_connection_file(filename, profile=None):
166 def find_connection_file(filename, profile=None):
167 """find a connection file, and return its absolute path.
167 """find a connection file, and return its absolute path.
168
168
169 The current working directory and the profile's security
169 The current working directory and the profile's security
170 directory will be searched for the file if it is not given by
170 directory will be searched for the file if it is not given by
171 absolute path.
171 absolute path.
172
172
173 If profile is unspecified, then the current running application's
173 If profile is unspecified, then the current running application's
174 profile will be used, or 'default', if not run from IPython.
174 profile will be used, or 'default', if not run from IPython.
175
175
176 If the argument does not match an existing file, it will be interpreted as a
176 If the argument does not match an existing file, it will be interpreted as a
177 fileglob, and the matching file in the profile's security dir with
177 fileglob, and the matching file in the profile's security dir with
178 the latest access time will be used.
178 the latest access time will be used.
179
179
180 Parameters
180 Parameters
181 ----------
181 ----------
182 filename : str
182 filename : str
183 The connection file or fileglob to search for.
183 The connection file or fileglob to search for.
184 profile : str [optional]
184 profile : str [optional]
185 The name of the profile to use when searching for the connection file,
185 The name of the profile to use when searching for the connection file,
186 if different from the current IPython session or 'default'.
186 if different from the current IPython session or 'default'.
187
187
188 Returns
188 Returns
189 -------
189 -------
190 str : The absolute path of the connection file.
190 str : The absolute path of the connection file.
191 """
191 """
192 from IPython.core.application import BaseIPythonApplication as IPApp
192 from IPython.core.application import BaseIPythonApplication as IPApp
193 try:
193 try:
194 # quick check for absolute path, before going through logic
194 # quick check for absolute path, before going through logic
195 return filefind(filename)
195 return filefind(filename)
196 except IOError:
196 except IOError:
197 pass
197 pass
198
198
199 if profile is None:
199 if profile is None:
200 # profile unspecified, check if running from an IPython app
200 # profile unspecified, check if running from an IPython app
201 if IPApp.initialized():
201 if IPApp.initialized():
202 app = IPApp.instance()
202 app = IPApp.instance()
203 profile_dir = app.profile_dir
203 profile_dir = app.profile_dir
204 else:
204 else:
205 # not running in IPython, use default profile
205 # not running in IPython, use default profile
206 profile_dir = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), 'default')
206 profile_dir = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), 'default')
207 else:
207 else:
208 # find profiledir by profile name:
208 # find profiledir by profile name:
209 profile_dir = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), profile)
209 profile_dir = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), profile)
210 security_dir = profile_dir.security_dir
210 security_dir = profile_dir.security_dir
211
211
212 try:
212 try:
213 # first, try explicit name
213 # first, try explicit name
214 return filefind(filename, ['.', security_dir])
214 return filefind(filename, ['.', security_dir])
215 except IOError:
215 except IOError:
216 pass
216 pass
217
217
218 # not found by full name
218 # not found by full name
219
219
220 if '*' in filename:
220 if '*' in filename:
221 # given as a glob already
221 # given as a glob already
222 pat = filename
222 pat = filename
223 else:
223 else:
224 # accept any substring match
224 # accept any substring match
225 pat = '*%s*' % filename
225 pat = '*%s*' % filename
226 matches = glob.glob( os.path.join(security_dir, pat) )
226 matches = glob.glob( os.path.join(security_dir, pat) )
227 if not matches:
227 if not matches:
228 raise IOError("Could not find %r in %r" % (filename, security_dir))
228 raise IOError("Could not find %r in %r" % (filename, security_dir))
229 elif len(matches) == 1:
229 elif len(matches) == 1:
230 return matches[0]
230 return matches[0]
231 else:
231 else:
232 # get most recent match, by access time:
232 # get most recent match, by access time:
233 return sorted(matches, key=lambda f: os.stat(f).st_atime)[-1]
233 return sorted(matches, key=lambda f: os.stat(f).st_atime)[-1]
234
234
235
235
236 def get_connection_info(connection_file=None, unpack=False, profile=None):
236 def get_connection_info(connection_file=None, unpack=False, profile=None):
237 """Return the connection information for the current Kernel.
237 """Return the connection information for the current Kernel.
238
238
239 Parameters
239 Parameters
240 ----------
240 ----------
241 connection_file : str [optional]
241 connection_file : str [optional]
242 The connection file to be used. Can be given by absolute path, or
242 The connection file to be used. Can be given by absolute path, or
243 IPython will search in the security directory of a given profile.
243 IPython will search in the security directory of a given profile.
244 If run from IPython,
244 If run from IPython,
245
245
246 If unspecified, the connection file for the currently running
246 If unspecified, the connection file for the currently running
247 IPython Kernel will be used, which is only allowed from inside a kernel.
247 IPython Kernel will be used, which is only allowed from inside a kernel.
248 unpack : bool [default: False]
248 unpack : bool [default: False]
249 if True, return the unpacked dict, otherwise just the string contents
249 if True, return the unpacked dict, otherwise just the string contents
250 of the file.
250 of the file.
251 profile : str [optional]
251 profile : str [optional]
252 The name of the profile to use when searching for the connection file,
252 The name of the profile to use when searching for the connection file,
253 if different from the current IPython session or 'default'.
253 if different from the current IPython session or 'default'.
254
254
255
255
256 Returns
256 Returns
257 -------
257 -------
258 The connection dictionary of the current kernel, as string or dict,
258 The connection dictionary of the current kernel, as string or dict,
259 depending on `unpack`.
259 depending on `unpack`.
260 """
260 """
261 if connection_file is None:
261 if connection_file is None:
262 # get connection file from current kernel
262 # get connection file from current kernel
263 cf = get_connection_file()
263 cf = get_connection_file()
264 else:
264 else:
265 # connection file specified, allow shortnames:
265 # connection file specified, allow shortnames:
266 cf = find_connection_file(connection_file, profile=profile)
266 cf = find_connection_file(connection_file, profile=profile)
267
267
268 with open(cf) as f:
268 with open(cf) as f:
269 info = f.read()
269 info = f.read()
270
270
271 if unpack:
271 if unpack:
272 info = json.loads(info)
272 info = json.loads(info)
273 # ensure key is bytes:
273 # ensure key is bytes:
274 info['key'] = str_to_bytes(info.get('key', ''))
274 info['key'] = str_to_bytes(info.get('key', ''))
275 return info
275 return info
276
276
277
277
278 def connect_qtconsole(connection_file=None, argv=None, profile=None):
278 def connect_qtconsole(connection_file=None, argv=None, profile=None):
279 """Connect a qtconsole to the current kernel.
279 """Connect a qtconsole to the current kernel.
280
280
281 This is useful for connecting a second qtconsole to a kernel, or to a
281 This is useful for connecting a second qtconsole to a kernel, or to a
282 local notebook.
282 local notebook.
283
283
284 Parameters
284 Parameters
285 ----------
285 ----------
286 connection_file : str [optional]
286 connection_file : str [optional]
287 The connection file to be used. Can be given by absolute path, or
287 The connection file to be used. Can be given by absolute path, or
288 IPython will search in the security directory of a given profile.
288 IPython will search in the security directory of a given profile.
289 If run from IPython,
289 If run from IPython,
290
290
291 If unspecified, the connection file for the currently running
291 If unspecified, the connection file for the currently running
292 IPython Kernel will be used, which is only allowed from inside a kernel.
292 IPython Kernel will be used, which is only allowed from inside a kernel.
293 argv : list [optional]
293 argv : list [optional]
294 Any extra args to be passed to the console.
294 Any extra args to be passed to the console.
295 profile : str [optional]
295 profile : str [optional]
296 The name of the profile to use when searching for the connection file,
296 The name of the profile to use when searching for the connection file,
297 if different from the current IPython session or 'default'.
297 if different from the current IPython session or 'default'.
298
298
299
299
300 Returns
300 Returns
301 -------
301 -------
302 subprocess.Popen instance running the qtconsole frontend
302 subprocess.Popen instance running the qtconsole frontend
303 """
303 """
304 argv = [] if argv is None else argv
304 argv = [] if argv is None else argv
305
305
306 if connection_file is None:
306 if connection_file is None:
307 # get connection file from current kernel
307 # get connection file from current kernel
308 cf = get_connection_file()
308 cf = get_connection_file()
309 else:
309 else:
310 cf = find_connection_file(connection_file, profile=profile)
310 cf = find_connection_file(connection_file, profile=profile)
311
311
312 cmd = ';'.join([
312 cmd = ';'.join([
313 "from IPython.qt.console import qtconsoleapp",
313 "from IPython.qt.console import qtconsoleapp",
314 "qtconsoleapp.main()"
314 "qtconsoleapp.main()"
315 ])
315 ])
316
316
317 return Popen([sys.executable, '-c', cmd, '--existing', cf] + argv,
317 return Popen([sys.executable, '-c', cmd, '--existing', cf] + argv,
318 stdout=PIPE, stderr=PIPE, close_fds=(sys.platform != 'win32'),
318 stdout=PIPE, stderr=PIPE, close_fds=(sys.platform != 'win32'),
319 )
319 )
320
320
321
321
322 def tunnel_to_kernel(connection_info, sshserver, sshkey=None):
322 def tunnel_to_kernel(connection_info, sshserver, sshkey=None):
323 """tunnel connections to a kernel via ssh
323 """tunnel connections to a kernel via ssh
324
324
325 This will open four SSH tunnels from localhost on this machine to the
325 This will open four SSH tunnels from localhost on this machine to the
326 ports associated with the kernel. They can be either direct
326 ports associated with the kernel. They can be either direct
327 localhost-localhost tunnels, or if an intermediate server is necessary,
327 localhost-localhost tunnels, or if an intermediate server is necessary,
328 the kernel must be listening on a public IP.
328 the kernel must be listening on a public IP.
329
329
330 Parameters
330 Parameters
331 ----------
331 ----------
332 connection_info : dict or str (path)
332 connection_info : dict or str (path)
333 Either a connection dict, or the path to a JSON connection file
333 Either a connection dict, or the path to a JSON connection file
334 sshserver : str
334 sshserver : str
335 The ssh sever to use to tunnel to the kernel. Can be a full
335 The ssh sever to use to tunnel to the kernel. Can be a full
336 `user@server:port` string. ssh config aliases are respected.
336 `user@server:port` string. ssh config aliases are respected.
337 sshkey : str [optional]
337 sshkey : str [optional]
338 Path to file containing ssh key to use for authentication.
338 Path to file containing ssh key to use for authentication.
339 Only necessary if your ssh config does not already associate
339 Only necessary if your ssh config does not already associate
340 a keyfile with the host.
340 a keyfile with the host.
341
341
342 Returns
342 Returns
343 -------
343 -------
344
344
345 (shell, iopub, stdin, hb) : ints
345 (shell, iopub, stdin, hb) : ints
346 The four ports on localhost that have been forwarded to the kernel.
346 The four ports on localhost that have been forwarded to the kernel.
347 """
347 """
348 if isinstance(connection_info, string_types):
348 if isinstance(connection_info, string_types):
349 # it's a path, unpack it
349 # it's a path, unpack it
350 with open(connection_info) as f:
350 with open(connection_info) as f:
351 connection_info = json.loads(f.read())
351 connection_info = json.loads(f.read())
352
352
353 cf = connection_info
353 cf = connection_info
354
354
355 lports = tunnel.select_random_ports(4)
355 lports = tunnel.select_random_ports(4)
356 rports = cf['shell_port'], cf['iopub_port'], cf['stdin_port'], cf['hb_port']
356 rports = cf['shell_port'], cf['iopub_port'], cf['stdin_port'], cf['hb_port']
357
357
358 remote_ip = cf['ip']
358 remote_ip = cf['ip']
359
359
360 if tunnel.try_passwordless_ssh(sshserver, sshkey):
360 if tunnel.try_passwordless_ssh(sshserver, sshkey):
361 password=False
361 password=False
362 else:
362 else:
363 password = getpass("SSH Password for %s: " % cast_bytes_py2(sshserver))
363 password = getpass("SSH Password for %s: " % cast_bytes_py2(sshserver))
364
364
365 for lp,rp in zip(lports, rports):
365 for lp,rp in zip(lports, rports):
366 tunnel.ssh_tunnel(lp, rp, sshserver, remote_ip, sshkey, password)
366 tunnel.ssh_tunnel(lp, rp, sshserver, remote_ip, sshkey, password)
367
367
368 return tuple(lports)
368 return tuple(lports)
369
369
370
370
371 #-----------------------------------------------------------------------------
371 #-----------------------------------------------------------------------------
372 # Mixin for classes that work with connection files
372 # Mixin for classes that work with connection files
373 #-----------------------------------------------------------------------------
373 #-----------------------------------------------------------------------------
374
374
375 channel_socket_types = {
375 channel_socket_types = {
376 'hb' : zmq.REQ,
376 'hb' : zmq.REQ,
377 'shell' : zmq.DEALER,
377 'shell' : zmq.DEALER,
378 'iopub' : zmq.SUB,
378 'iopub' : zmq.SUB,
379 'stdin' : zmq.DEALER,
379 'stdin' : zmq.DEALER,
380 'control': zmq.DEALER,
380 'control': zmq.DEALER,
381 }
381 }
382
382
383 port_names = [ "%s_port" % channel for channel in ('shell', 'stdin', 'iopub', 'hb', 'control')]
383 port_names = [ "%s_port" % channel for channel in ('shell', 'stdin', 'iopub', 'hb', 'control')]
384
384
385 class ConnectionFileMixin(Configurable):
385 class ConnectionFileMixin(Configurable):
386 """Mixin for configurable classes that work with connection files"""
386 """Mixin for configurable classes that work with connection files"""
387
387
388 # The addresses for the communication channels
388 # The addresses for the communication channels
389 connection_file = Unicode('')
389 connection_file = Unicode('', config=True,
390 help="""JSON file in which to store connection info [default: kernel-<pid>.json]
391
392 This file will contain the IP, ports, and authentication key needed to connect
393 clients to this kernel. By default, this file will be created in the security dir
394 of the current profile, but can be specified by absolute path.
395 """)
390 _connection_file_written = Bool(False)
396 _connection_file_written = Bool(False)
391
397
392 transport = CaselessStrEnum(['tcp', 'ipc'], default_value='tcp', config=True)
398 transport = CaselessStrEnum(['tcp', 'ipc'], default_value='tcp', config=True)
393
399
394 ip = Unicode(config=True,
400 ip = Unicode(config=True,
395 help="""Set the kernel\'s IP address [default localhost].
401 help="""Set the kernel\'s IP address [default localhost].
396 If the IP address is something other than localhost, then
402 If the IP address is something other than localhost, then
397 Consoles on other machines will be able to connect
403 Consoles on other machines will be able to connect
398 to the Kernel, so be careful!"""
404 to the Kernel, so be careful!"""
399 )
405 )
400
406
401 def _ip_default(self):
407 def _ip_default(self):
402 if self.transport == 'ipc':
408 if self.transport == 'ipc':
403 if self.connection_file:
409 if self.connection_file:
404 return os.path.splitext(self.connection_file)[0] + '-ipc'
410 return os.path.splitext(self.connection_file)[0] + '-ipc'
405 else:
411 else:
406 return 'kernel-ipc'
412 return 'kernel-ipc'
407 else:
413 else:
408 return localhost()
414 return localhost()
409
415
410 def _ip_changed(self, name, old, new):
416 def _ip_changed(self, name, old, new):
411 if new == '*':
417 if new == '*':
412 self.ip = '0.0.0.0'
418 self.ip = '0.0.0.0'
413
419
414 # protected traits
420 # protected traits
415
421
416 shell_port = Integer(0)
422 hb_port = Integer(0, config=True, help="set the heartbeat port [default: random]")
417 iopub_port = Integer(0)
423 shell_port = Integer(0, config=True, help="set the shell (ROUTER) port [default: random]")
418 stdin_port = Integer(0)
424 iopub_port = Integer(0, config=True, help="set the iopub (PUB) port [default: random]")
419 control_port = Integer(0)
425 stdin_port = Integer(0, config=True, help="set the stdin (ROUTER) port [default: random]")
420 hb_port = Integer(0)
426 control_port = Integer(0, config=True, help="set the control (ROUTER) port [default: random]")
421
427
422 @property
428 @property
423 def ports(self):
429 def ports(self):
424 return [ getattr(self, name) for name in port_names ]
430 return [ getattr(self, name) for name in port_names ]
425
431
426 #--------------------------------------------------------------------------
432 #--------------------------------------------------------------------------
427 # Connection and ipc file management
433 # Connection and ipc file management
428 #--------------------------------------------------------------------------
434 #--------------------------------------------------------------------------
429
435
430 def get_connection_info(self):
436 def get_connection_info(self):
431 """return the connection info as a dict"""
437 """return the connection info as a dict"""
432 return dict(
438 return dict(
433 transport=self.transport,
439 transport=self.transport,
434 ip=self.ip,
440 ip=self.ip,
435 shell_port=self.shell_port,
441 shell_port=self.shell_port,
436 iopub_port=self.iopub_port,
442 iopub_port=self.iopub_port,
437 stdin_port=self.stdin_port,
443 stdin_port=self.stdin_port,
438 hb_port=self.hb_port,
444 hb_port=self.hb_port,
439 control_port=self.control_port,
445 control_port=self.control_port,
440 signature_scheme=self.session.signature_scheme,
446 signature_scheme=self.session.signature_scheme,
441 key=self.session.key,
447 key=self.session.key,
442 )
448 )
443
449
444 def cleanup_connection_file(self):
450 def cleanup_connection_file(self):
445 """Cleanup connection file *if we wrote it*
451 """Cleanup connection file *if we wrote it*
446
452
447 Will not raise if the connection file was already removed somehow.
453 Will not raise if the connection file was already removed somehow.
448 """
454 """
449 if self._connection_file_written:
455 if self._connection_file_written:
450 # cleanup connection files on full shutdown of kernel we started
456 # cleanup connection files on full shutdown of kernel we started
451 self._connection_file_written = False
457 self._connection_file_written = False
452 try:
458 try:
453 os.remove(self.connection_file)
459 os.remove(self.connection_file)
454 except (IOError, OSError, AttributeError):
460 except (IOError, OSError, AttributeError):
455 pass
461 pass
456
462
457 def cleanup_ipc_files(self):
463 def cleanup_ipc_files(self):
458 """Cleanup ipc files if we wrote them."""
464 """Cleanup ipc files if we wrote them."""
459 if self.transport != 'ipc':
465 if self.transport != 'ipc':
460 return
466 return
461 for port in self.ports:
467 for port in self.ports:
462 ipcfile = "%s-%i" % (self.ip, port)
468 ipcfile = "%s-%i" % (self.ip, port)
463 try:
469 try:
464 os.remove(ipcfile)
470 os.remove(ipcfile)
465 except (IOError, OSError):
471 except (IOError, OSError):
466 pass
472 pass
467
473
468 def write_connection_file(self):
474 def write_connection_file(self):
469 """Write connection info to JSON dict in self.connection_file."""
475 """Write connection info to JSON dict in self.connection_file."""
470 if self._connection_file_written and os.path.exists(self.connection_file):
476 if self._connection_file_written and os.path.exists(self.connection_file):
471 return
477 return
472
478
473 self.connection_file, cfg = write_connection_file(self.connection_file,
479 self.connection_file, cfg = write_connection_file(self.connection_file,
474 transport=self.transport, ip=self.ip, key=self.session.key,
480 transport=self.transport, ip=self.ip, key=self.session.key,
475 stdin_port=self.stdin_port, iopub_port=self.iopub_port,
481 stdin_port=self.stdin_port, iopub_port=self.iopub_port,
476 shell_port=self.shell_port, hb_port=self.hb_port,
482 shell_port=self.shell_port, hb_port=self.hb_port,
477 control_port=self.control_port,
483 control_port=self.control_port,
478 signature_scheme=self.session.signature_scheme,
484 signature_scheme=self.session.signature_scheme,
479 )
485 )
480 # write_connection_file also sets default ports:
486 # write_connection_file also sets default ports:
481 for name in port_names:
487 for name in port_names:
482 setattr(self, name, cfg[name])
488 setattr(self, name, cfg[name])
483
489
484 self._connection_file_written = True
490 self._connection_file_written = True
485
491
486 def load_connection_file(self):
492 def load_connection_file(self):
487 """Load connection info from JSON dict in self.connection_file."""
493 """Load connection info from JSON dict in self.connection_file."""
488 self.log.debug(u"Loading connection file %s", self.connection_file)
494 self.log.debug(u"Loading connection file %s", self.connection_file)
489 with open(self.connection_file) as f:
495 with open(self.connection_file) as f:
490 cfg = json.load(f)
496 cfg = json.load(f)
491 self.transport = cfg.get('transport', self.transport)
497 self.transport = cfg.get('transport', self.transport)
492 self.ip = cfg.get('ip', self._ip_default())
498 self.ip = cfg.get('ip', self._ip_default())
493
499
494 for name in port_names:
500 for name in port_names:
495 if getattr(self, name) == 0 and name in cfg:
501 if getattr(self, name) == 0 and name in cfg:
496 # not overridden by config or cl_args
502 # not overridden by config or cl_args
497 setattr(self, name, cfg[name])
503 setattr(self, name, cfg[name])
498 if 'key' in cfg:
504 if 'key' in cfg:
499 self.config.Session.key = str_to_bytes(cfg['key'])
505 self.config.Session.key = str_to_bytes(cfg['key'])
500 if 'signature_scheme' in cfg:
506 if 'signature_scheme' in cfg:
501 self.config.Session.signature_scheme = cfg['signature_scheme']
507 self.config.Session.signature_scheme = cfg['signature_scheme']
502 #--------------------------------------------------------------------------
508 #--------------------------------------------------------------------------
503 # Creating connected sockets
509 # Creating connected sockets
504 #--------------------------------------------------------------------------
510 #--------------------------------------------------------------------------
505
511
506 def _make_url(self, channel):
512 def _make_url(self, channel):
507 """Make a ZeroMQ URL for a given channel."""
513 """Make a ZeroMQ URL for a given channel."""
508 transport = self.transport
514 transport = self.transport
509 ip = self.ip
515 ip = self.ip
510 port = getattr(self, '%s_port' % channel)
516 port = getattr(self, '%s_port' % channel)
511
517
512 if transport == 'tcp':
518 if transport == 'tcp':
513 return "tcp://%s:%i" % (ip, port)
519 return "tcp://%s:%i" % (ip, port)
514 else:
520 else:
515 return "%s://%s-%s" % (transport, ip, port)
521 return "%s://%s-%s" % (transport, ip, port)
516
522
517 def _create_connected_socket(self, channel, identity=None):
523 def _create_connected_socket(self, channel, identity=None):
518 """Create a zmq Socket and connect it to the kernel."""
524 """Create a zmq Socket and connect it to the kernel."""
519 url = self._make_url(channel)
525 url = self._make_url(channel)
520 socket_type = channel_socket_types[channel]
526 socket_type = channel_socket_types[channel]
521 self.log.debug("Connecting to: %s" % url)
527 self.log.debug("Connecting to: %s" % url)
522 sock = self.context.socket(socket_type)
528 sock = self.context.socket(socket_type)
523 if identity:
529 if identity:
524 sock.identity = identity
530 sock.identity = identity
525 sock.connect(url)
531 sock.connect(url)
526 return sock
532 return sock
527
533
528 def connect_iopub(self, identity=None):
534 def connect_iopub(self, identity=None):
529 """return zmq Socket connected to the IOPub channel"""
535 """return zmq Socket connected to the IOPub channel"""
530 sock = self._create_connected_socket('iopub', identity=identity)
536 sock = self._create_connected_socket('iopub', identity=identity)
531 sock.setsockopt(zmq.SUBSCRIBE, b'')
537 sock.setsockopt(zmq.SUBSCRIBE, b'')
532 return sock
538 return sock
533
539
534 def connect_shell(self, identity=None):
540 def connect_shell(self, identity=None):
535 """return zmq Socket connected to the Shell channel"""
541 """return zmq Socket connected to the Shell channel"""
536 return self._create_connected_socket('shell', identity=identity)
542 return self._create_connected_socket('shell', identity=identity)
537
543
538 def connect_stdin(self, identity=None):
544 def connect_stdin(self, identity=None):
539 """return zmq Socket connected to the StdIn channel"""
545 """return zmq Socket connected to the StdIn channel"""
540 return self._create_connected_socket('stdin', identity=identity)
546 return self._create_connected_socket('stdin', identity=identity)
541
547
542 def connect_hb(self, identity=None):
548 def connect_hb(self, identity=None):
543 """return zmq Socket connected to the Heartbeat channel"""
549 """return zmq Socket connected to the Heartbeat channel"""
544 return self._create_connected_socket('hb', identity=identity)
550 return self._create_connected_socket('hb', identity=identity)
545
551
546 def connect_control(self, identity=None):
552 def connect_control(self, identity=None):
547 """return zmq Socket connected to the Heartbeat channel"""
553 """return zmq Socket connected to the Heartbeat channel"""
548 return self._create_connected_socket('control', identity=identity)
554 return self._create_connected_socket('control', identity=identity)
549
555
550
556
551 __all__ = [
557 __all__ = [
552 'write_connection_file',
558 'write_connection_file',
553 'get_connection_file',
559 'get_connection_file',
554 'find_connection_file',
560 'find_connection_file',
555 'get_connection_info',
561 'get_connection_info',
556 'connect_qtconsole',
562 'connect_qtconsole',
557 'tunnel_to_kernel',
563 'tunnel_to_kernel',
558 ]
564 ]
@@ -1,447 +1,411 b''
1 """An Application for launching a kernel
1 """An Application for launching a kernel
2 """
2 """
3 # Copyright (c) IPython Development Team.
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
4 # Distributed under the terms of the Modified BSD License.
5
5
6 #-----------------------------------------------------------------------------
6 #-----------------------------------------------------------------------------
7 # Imports
7 # Imports
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9
9
10 from __future__ import print_function
10 from __future__ import print_function
11
11
12 # Standard library imports
12 # Standard library imports
13 import atexit
13 import atexit
14 import os
14 import os
15 import sys
15 import sys
16 import signal
16 import signal
17
17
18 # System library imports
18 # System library imports
19 import zmq
19 import zmq
20 from zmq.eventloop import ioloop
20 from zmq.eventloop import ioloop
21 from zmq.eventloop.zmqstream import ZMQStream
21 from zmq.eventloop.zmqstream import ZMQStream
22
22
23 # IPython imports
23 # IPython imports
24 from IPython.core.ultratb import FormattedTB
24 from IPython.core.ultratb import FormattedTB
25 from IPython.core.application import (
25 from IPython.core.application import (
26 BaseIPythonApplication, base_flags, base_aliases, catch_config_error
26 BaseIPythonApplication, base_flags, base_aliases, catch_config_error
27 )
27 )
28 from IPython.core.profiledir import ProfileDir
28 from IPython.core.profiledir import ProfileDir
29 from IPython.core.shellapp import (
29 from IPython.core.shellapp import (
30 InteractiveShellApp, shell_flags, shell_aliases
30 InteractiveShellApp, shell_flags, shell_aliases
31 )
31 )
32 from IPython.utils import io
32 from IPython.utils import io
33 from IPython.utils.localinterfaces import localhost
34 from IPython.utils.path import filefind
33 from IPython.utils.path import filefind
35 from IPython.utils.traitlets import (
34 from IPython.utils.traitlets import (
36 Any, Instance, Dict, Unicode, Integer, Bool, CaselessStrEnum,
35 Any, Instance, Dict, Unicode, Integer, Bool, DottedObjectName,
37 DottedObjectName,
38 )
36 )
39 from IPython.utils.importstring import import_item
37 from IPython.utils.importstring import import_item
40 from IPython.kernel import write_connection_file
38 from IPython.kernel import write_connection_file
41 from IPython.kernel.connect import ConnectionFileMixin
39 from IPython.kernel.connect import ConnectionFileMixin
42
40
43 # local imports
41 # local imports
44 from .heartbeat import Heartbeat
42 from .heartbeat import Heartbeat
45 from .ipkernel import Kernel
43 from .ipkernel import Kernel
46 from .parentpoller import ParentPollerUnix, ParentPollerWindows
44 from .parentpoller import ParentPollerUnix, ParentPollerWindows
47 from .session import (
45 from .session import (
48 Session, session_flags, session_aliases, default_secure,
46 Session, session_flags, session_aliases, default_secure,
49 )
47 )
50 from .zmqshell import ZMQInteractiveShell
48 from .zmqshell import ZMQInteractiveShell
51
49
52 #-----------------------------------------------------------------------------
50 #-----------------------------------------------------------------------------
53 # Flags and Aliases
51 # Flags and Aliases
54 #-----------------------------------------------------------------------------
52 #-----------------------------------------------------------------------------
55
53
56 kernel_aliases = dict(base_aliases)
54 kernel_aliases = dict(base_aliases)
57 kernel_aliases.update({
55 kernel_aliases.update({
58 'ip' : 'IPKernelApp.ip',
56 'ip' : 'IPKernelApp.ip',
59 'hb' : 'IPKernelApp.hb_port',
57 'hb' : 'IPKernelApp.hb_port',
60 'shell' : 'IPKernelApp.shell_port',
58 'shell' : 'IPKernelApp.shell_port',
61 'iopub' : 'IPKernelApp.iopub_port',
59 'iopub' : 'IPKernelApp.iopub_port',
62 'stdin' : 'IPKernelApp.stdin_port',
60 'stdin' : 'IPKernelApp.stdin_port',
63 'control' : 'IPKernelApp.control_port',
61 'control' : 'IPKernelApp.control_port',
64 'f' : 'IPKernelApp.connection_file',
62 'f' : 'IPKernelApp.connection_file',
65 'parent': 'IPKernelApp.parent_handle',
63 'parent': 'IPKernelApp.parent_handle',
66 'transport': 'IPKernelApp.transport',
64 'transport': 'IPKernelApp.transport',
67 })
65 })
68 if sys.platform.startswith('win'):
66 if sys.platform.startswith('win'):
69 kernel_aliases['interrupt'] = 'IPKernelApp.interrupt'
67 kernel_aliases['interrupt'] = 'IPKernelApp.interrupt'
70
68
71 kernel_flags = dict(base_flags)
69 kernel_flags = dict(base_flags)
72 kernel_flags.update({
70 kernel_flags.update({
73 'no-stdout' : (
71 'no-stdout' : (
74 {'IPKernelApp' : {'no_stdout' : True}},
72 {'IPKernelApp' : {'no_stdout' : True}},
75 "redirect stdout to the null device"),
73 "redirect stdout to the null device"),
76 'no-stderr' : (
74 'no-stderr' : (
77 {'IPKernelApp' : {'no_stderr' : True}},
75 {'IPKernelApp' : {'no_stderr' : True}},
78 "redirect stderr to the null device"),
76 "redirect stderr to the null device"),
79 'pylab' : (
77 'pylab' : (
80 {'IPKernelApp' : {'pylab' : 'auto'}},
78 {'IPKernelApp' : {'pylab' : 'auto'}},
81 """Pre-load matplotlib and numpy for interactive use with
79 """Pre-load matplotlib and numpy for interactive use with
82 the default matplotlib backend."""),
80 the default matplotlib backend."""),
83 })
81 })
84
82
85 # inherit flags&aliases for any IPython shell apps
83 # inherit flags&aliases for any IPython shell apps
86 kernel_aliases.update(shell_aliases)
84 kernel_aliases.update(shell_aliases)
87 kernel_flags.update(shell_flags)
85 kernel_flags.update(shell_flags)
88
86
89 # inherit flags&aliases for Sessions
87 # inherit flags&aliases for Sessions
90 kernel_aliases.update(session_aliases)
88 kernel_aliases.update(session_aliases)
91 kernel_flags.update(session_flags)
89 kernel_flags.update(session_flags)
92
90
93 _ctrl_c_message = """\
91 _ctrl_c_message = """\
94 NOTE: When using the `ipython kernel` entry point, Ctrl-C will not work.
92 NOTE: When using the `ipython kernel` entry point, Ctrl-C will not work.
95
93
96 To exit, you will have to explicitly quit this process, by either sending
94 To exit, you will have to explicitly quit this process, by either sending
97 "quit" from a client, or using Ctrl-\\ in UNIX-like environments.
95 "quit" from a client, or using Ctrl-\\ in UNIX-like environments.
98
96
99 To read more about this, see https://github.com/ipython/ipython/issues/2049
97 To read more about this, see https://github.com/ipython/ipython/issues/2049
100
98
101 """
99 """
102
100
103 #-----------------------------------------------------------------------------
101 #-----------------------------------------------------------------------------
104 # Application class for starting an IPython Kernel
102 # Application class for starting an IPython Kernel
105 #-----------------------------------------------------------------------------
103 #-----------------------------------------------------------------------------
106
104
107 class IPKernelApp(BaseIPythonApplication, InteractiveShellApp,
105 class IPKernelApp(BaseIPythonApplication, InteractiveShellApp,
108 ConnectionFileMixin):
106 ConnectionFileMixin):
109 name='ipkernel'
107 name='ipkernel'
110 aliases = Dict(kernel_aliases)
108 aliases = Dict(kernel_aliases)
111 flags = Dict(kernel_flags)
109 flags = Dict(kernel_flags)
112 classes = [Kernel, ZMQInteractiveShell, ProfileDir, Session]
110 classes = [Kernel, ZMQInteractiveShell, ProfileDir, Session]
113 # the kernel class, as an importstring
111 # the kernel class, as an importstring
114 kernel_class = DottedObjectName('IPython.kernel.zmq.ipkernel.Kernel', config=True,
112 kernel_class = DottedObjectName('IPython.kernel.zmq.ipkernel.Kernel', config=True,
115 help="""The Kernel subclass to be used.
113 help="""The Kernel subclass to be used.
116
114
117 This should allow easy re-use of the IPKernelApp entry point
115 This should allow easy re-use of the IPKernelApp entry point
118 to configure and launch kernels other than IPython's own.
116 to configure and launch kernels other than IPython's own.
119 """)
117 """)
120 kernel = Any()
118 kernel = Any()
121 poller = Any() # don't restrict this even though current pollers are all Threads
119 poller = Any() # don't restrict this even though current pollers are all Threads
122 heartbeat = Instance(Heartbeat)
120 heartbeat = Instance(Heartbeat)
123 session = Instance('IPython.kernel.zmq.session.Session')
121 session = Instance('IPython.kernel.zmq.session.Session')
124 ports = Dict()
122 ports = Dict()
125
123
126 # ipkernel doesn't get its own config file
124 # ipkernel doesn't get its own config file
127 def _config_file_name_default(self):
125 def _config_file_name_default(self):
128 return 'ipython_config.py'
126 return 'ipython_config.py'
129
127
130 # inherit config file name from parent:
128 # inherit config file name from parent:
131 parent_appname = Unicode(config=True)
129 parent_appname = Unicode(config=True)
132 def _parent_appname_changed(self, name, old, new):
130 def _parent_appname_changed(self, name, old, new):
133 if self.config_file_specified:
131 if self.config_file_specified:
134 # it was manually specified, ignore
132 # it was manually specified, ignore
135 return
133 return
136 self.config_file_name = new.replace('-','_') + u'_config.py'
134 self.config_file_name = new.replace('-','_') + u'_config.py'
137 # don't let this count as specifying the config file
135 # don't let this count as specifying the config file
138 self.config_file_specified.remove(self.config_file_name)
136 self.config_file_specified.remove(self.config_file_name)
139
137
140 # connection info:
138 # connection info:
141 transport = CaselessStrEnum(['tcp', 'ipc'], default_value='tcp', config=True)
142 ip = Unicode(config=True,
143 help="Set the IP or interface on which the kernel will listen.")
144 def _ip_default(self):
145 if self.transport == 'ipc':
146 if self.connection_file:
147 return os.path.splitext(self.abs_connection_file)[0] + '-ipc'
148 else:
149 return 'kernel-ipc'
150 else:
151 return localhost()
152
153 hb_port = Integer(0, config=True, help="set the heartbeat port [default: random]")
154 shell_port = Integer(0, config=True, help="set the shell (ROUTER) port [default: random]")
155 iopub_port = Integer(0, config=True, help="set the iopub (PUB) port [default: random]")
156 stdin_port = Integer(0, config=True, help="set the stdin (ROUTER) port [default: random]")
157 control_port = Integer(0, config=True, help="set the control (ROUTER) port [default: random]")
158 connection_file = Unicode('', config=True,
159 help="""JSON file in which to store connection info [default: kernel-<pid>.json]
160
139
161 This file will contain the IP, ports, and authentication key needed to connect
162 clients to this kernel. By default, this file will be created in the security dir
163 of the current profile, but can be specified by absolute path.
164 """)
165 @property
140 @property
166 def abs_connection_file(self):
141 def abs_connection_file(self):
167 if os.path.basename(self.connection_file) == self.connection_file:
142 if os.path.basename(self.connection_file) == self.connection_file:
168 return os.path.join(self.profile_dir.security_dir, self.connection_file)
143 return os.path.join(self.profile_dir.security_dir, self.connection_file)
169 else:
144 else:
170 return self.connection_file
145 return self.connection_file
171
146
172
147
173 # streams, etc.
148 # streams, etc.
174 no_stdout = Bool(False, config=True, help="redirect stdout to the null device")
149 no_stdout = Bool(False, config=True, help="redirect stdout to the null device")
175 no_stderr = Bool(False, config=True, help="redirect stderr to the null device")
150 no_stderr = Bool(False, config=True, help="redirect stderr to the null device")
176 outstream_class = DottedObjectName('IPython.kernel.zmq.iostream.OutStream',
151 outstream_class = DottedObjectName('IPython.kernel.zmq.iostream.OutStream',
177 config=True, help="The importstring for the OutStream factory")
152 config=True, help="The importstring for the OutStream factory")
178 displayhook_class = DottedObjectName('IPython.kernel.zmq.displayhook.ZMQDisplayHook',
153 displayhook_class = DottedObjectName('IPython.kernel.zmq.displayhook.ZMQDisplayHook',
179 config=True, help="The importstring for the DisplayHook factory")
154 config=True, help="The importstring for the DisplayHook factory")
180
155
181 # polling
156 # polling
182 parent_handle = Integer(0, config=True,
157 parent_handle = Integer(0, config=True,
183 help="""kill this process if its parent dies. On Windows, the argument
158 help="""kill this process if its parent dies. On Windows, the argument
184 specifies the HANDLE of the parent process, otherwise it is simply boolean.
159 specifies the HANDLE of the parent process, otherwise it is simply boolean.
185 """)
160 """)
186 interrupt = Integer(0, config=True,
161 interrupt = Integer(0, config=True,
187 help="""ONLY USED ON WINDOWS
162 help="""ONLY USED ON WINDOWS
188 Interrupt this process when the parent is signaled.
163 Interrupt this process when the parent is signaled.
189 """)
164 """)
190
165
191 def init_crash_handler(self):
166 def init_crash_handler(self):
192 # Install minimal exception handling
167 # Install minimal exception handling
193 sys.excepthook = FormattedTB(mode='Verbose', color_scheme='NoColor',
168 sys.excepthook = FormattedTB(mode='Verbose', color_scheme='NoColor',
194 ostream=sys.__stdout__)
169 ostream=sys.__stdout__)
195
170
196 def init_poller(self):
171 def init_poller(self):
197 if sys.platform == 'win32':
172 if sys.platform == 'win32':
198 if self.interrupt or self.parent_handle:
173 if self.interrupt or self.parent_handle:
199 self.poller = ParentPollerWindows(self.interrupt, self.parent_handle)
174 self.poller = ParentPollerWindows(self.interrupt, self.parent_handle)
200 elif self.parent_handle:
175 elif self.parent_handle:
201 self.poller = ParentPollerUnix()
176 self.poller = ParentPollerUnix()
202
177
203 def _bind_socket(self, s, port):
178 def _bind_socket(self, s, port):
204 iface = '%s://%s' % (self.transport, self.ip)
179 iface = '%s://%s' % (self.transport, self.ip)
205 if self.transport == 'tcp':
180 if self.transport == 'tcp':
206 if port <= 0:
181 if port <= 0:
207 port = s.bind_to_random_port(iface)
182 port = s.bind_to_random_port(iface)
208 else:
183 else:
209 s.bind("tcp://%s:%i" % (self.ip, port))
184 s.bind("tcp://%s:%i" % (self.ip, port))
210 elif self.transport == 'ipc':
185 elif self.transport == 'ipc':
211 if port <= 0:
186 if port <= 0:
212 port = 1
187 port = 1
213 path = "%s-%i" % (self.ip, port)
188 path = "%s-%i" % (self.ip, port)
214 while os.path.exists(path):
189 while os.path.exists(path):
215 port = port + 1
190 port = port + 1
216 path = "%s-%i" % (self.ip, port)
191 path = "%s-%i" % (self.ip, port)
217 else:
192 else:
218 path = "%s-%i" % (self.ip, port)
193 path = "%s-%i" % (self.ip, port)
219 s.bind("ipc://%s" % path)
194 s.bind("ipc://%s" % path)
220 return port
195 return port
221
196
222 def write_connection_file(self):
197 def write_connection_file(self):
223 """write connection info to JSON file"""
198 """write connection info to JSON file"""
224 cf = self.abs_connection_file
199 cf = self.abs_connection_file
225 self.log.debug("Writing connection file: %s", cf)
200 self.log.debug("Writing connection file: %s", cf)
226 write_connection_file(cf, ip=self.ip, key=self.session.key, transport=self.transport,
201 write_connection_file(cf, ip=self.ip, key=self.session.key, transport=self.transport,
227 shell_port=self.shell_port, stdin_port=self.stdin_port, hb_port=self.hb_port,
202 shell_port=self.shell_port, stdin_port=self.stdin_port, hb_port=self.hb_port,
228 iopub_port=self.iopub_port, control_port=self.control_port)
203 iopub_port=self.iopub_port, control_port=self.control_port)
229
204
230 def cleanup_connection_file(self):
205 def cleanup_connection_file(self):
231 cf = self.abs_connection_file
206 cf = self.abs_connection_file
232 self.log.debug("Cleaning up connection file: %s", cf)
207 self.log.debug("Cleaning up connection file: %s", cf)
233 try:
208 try:
234 os.remove(cf)
209 os.remove(cf)
235 except (IOError, OSError):
210 except (IOError, OSError):
236 pass
211 pass
237
212
238 self.cleanup_ipc_files()
213 self.cleanup_ipc_files()
239
214
240 def cleanup_ipc_files(self):
241 """cleanup ipc files if we wrote them"""
242 if self.transport != 'ipc':
243 return
244 for port in (self.shell_port, self.iopub_port, self.stdin_port, self.hb_port, self.control_port):
245 ipcfile = "%s-%i" % (self.ip, port)
246 try:
247 os.remove(ipcfile)
248 except (IOError, OSError):
249 pass
250
251 def init_connection_file(self):
215 def init_connection_file(self):
252 if not self.connection_file:
216 if not self.connection_file:
253 self.connection_file = "kernel-%s.json"%os.getpid()
217 self.connection_file = "kernel-%s.json"%os.getpid()
254 try:
218 try:
255 self.connection_file = filefind(self.connection_file, ['.', self.profile_dir.security_dir])
219 self.connection_file = filefind(self.connection_file, ['.', self.profile_dir.security_dir])
256 except IOError:
220 except IOError:
257 self.log.debug("Connection file not found: %s", self.connection_file)
221 self.log.debug("Connection file not found: %s", self.connection_file)
258 # This means I own it, so I will clean it up:
222 # This means I own it, so I will clean it up:
259 atexit.register(self.cleanup_connection_file)
223 atexit.register(self.cleanup_connection_file)
260 return
224 return
261 try:
225 try:
262 self.load_connection_file()
226 self.load_connection_file()
263 except Exception:
227 except Exception:
264 self.log.error("Failed to load connection file: %r", self.connection_file, exc_info=True)
228 self.log.error("Failed to load connection file: %r", self.connection_file, exc_info=True)
265 self.exit(1)
229 self.exit(1)
266
230
267 def init_sockets(self):
231 def init_sockets(self):
268 # Create a context, a session, and the kernel sockets.
232 # Create a context, a session, and the kernel sockets.
269 self.log.info("Starting the kernel at pid: %i", os.getpid())
233 self.log.info("Starting the kernel at pid: %i", os.getpid())
270 context = zmq.Context.instance()
234 context = zmq.Context.instance()
271 # Uncomment this to try closing the context.
235 # Uncomment this to try closing the context.
272 # atexit.register(context.term)
236 # atexit.register(context.term)
273
237
274 self.shell_socket = context.socket(zmq.ROUTER)
238 self.shell_socket = context.socket(zmq.ROUTER)
275 self.shell_port = self._bind_socket(self.shell_socket, self.shell_port)
239 self.shell_port = self._bind_socket(self.shell_socket, self.shell_port)
276 self.log.debug("shell ROUTER Channel on port: %i" % self.shell_port)
240 self.log.debug("shell ROUTER Channel on port: %i" % self.shell_port)
277
241
278 self.iopub_socket = context.socket(zmq.PUB)
242 self.iopub_socket = context.socket(zmq.PUB)
279 self.iopub_port = self._bind_socket(self.iopub_socket, self.iopub_port)
243 self.iopub_port = self._bind_socket(self.iopub_socket, self.iopub_port)
280 self.log.debug("iopub PUB Channel on port: %i" % self.iopub_port)
244 self.log.debug("iopub PUB Channel on port: %i" % self.iopub_port)
281
245
282 self.stdin_socket = context.socket(zmq.ROUTER)
246 self.stdin_socket = context.socket(zmq.ROUTER)
283 self.stdin_port = self._bind_socket(self.stdin_socket, self.stdin_port)
247 self.stdin_port = self._bind_socket(self.stdin_socket, self.stdin_port)
284 self.log.debug("stdin ROUTER Channel on port: %i" % self.stdin_port)
248 self.log.debug("stdin ROUTER Channel on port: %i" % self.stdin_port)
285
249
286 self.control_socket = context.socket(zmq.ROUTER)
250 self.control_socket = context.socket(zmq.ROUTER)
287 self.control_port = self._bind_socket(self.control_socket, self.control_port)
251 self.control_port = self._bind_socket(self.control_socket, self.control_port)
288 self.log.debug("control ROUTER Channel on port: %i" % self.control_port)
252 self.log.debug("control ROUTER Channel on port: %i" % self.control_port)
289
253
290 def init_heartbeat(self):
254 def init_heartbeat(self):
291 """start the heart beating"""
255 """start the heart beating"""
292 # heartbeat doesn't share context, because it mustn't be blocked
256 # heartbeat doesn't share context, because it mustn't be blocked
293 # by the GIL, which is accessed by libzmq when freeing zero-copy messages
257 # by the GIL, which is accessed by libzmq when freeing zero-copy messages
294 hb_ctx = zmq.Context()
258 hb_ctx = zmq.Context()
295 self.heartbeat = Heartbeat(hb_ctx, (self.transport, self.ip, self.hb_port))
259 self.heartbeat = Heartbeat(hb_ctx, (self.transport, self.ip, self.hb_port))
296 self.hb_port = self.heartbeat.port
260 self.hb_port = self.heartbeat.port
297 self.log.debug("Heartbeat REP Channel on port: %i" % self.hb_port)
261 self.log.debug("Heartbeat REP Channel on port: %i" % self.hb_port)
298 self.heartbeat.start()
262 self.heartbeat.start()
299
263
300 def log_connection_info(self):
264 def log_connection_info(self):
301 """display connection info, and store ports"""
265 """display connection info, and store ports"""
302 basename = os.path.basename(self.connection_file)
266 basename = os.path.basename(self.connection_file)
303 if basename == self.connection_file or \
267 if basename == self.connection_file or \
304 os.path.dirname(self.connection_file) == self.profile_dir.security_dir:
268 os.path.dirname(self.connection_file) == self.profile_dir.security_dir:
305 # use shortname
269 # use shortname
306 tail = basename
270 tail = basename
307 if self.profile != 'default':
271 if self.profile != 'default':
308 tail += " --profile %s" % self.profile
272 tail += " --profile %s" % self.profile
309 else:
273 else:
310 tail = self.connection_file
274 tail = self.connection_file
311 lines = [
275 lines = [
312 "To connect another client to this kernel, use:",
276 "To connect another client to this kernel, use:",
313 " --existing %s" % tail,
277 " --existing %s" % tail,
314 ]
278 ]
315 # log connection info
279 # log connection info
316 # info-level, so often not shown.
280 # info-level, so often not shown.
317 # frontends should use the %connect_info magic
281 # frontends should use the %connect_info magic
318 # to see the connection info
282 # to see the connection info
319 for line in lines:
283 for line in lines:
320 self.log.info(line)
284 self.log.info(line)
321 # also raw print to the terminal if no parent_handle (`ipython kernel`)
285 # also raw print to the terminal if no parent_handle (`ipython kernel`)
322 if not self.parent_handle:
286 if not self.parent_handle:
323 io.rprint(_ctrl_c_message)
287 io.rprint(_ctrl_c_message)
324 for line in lines:
288 for line in lines:
325 io.rprint(line)
289 io.rprint(line)
326
290
327 self.ports = dict(shell=self.shell_port, iopub=self.iopub_port,
291 self.ports = dict(shell=self.shell_port, iopub=self.iopub_port,
328 stdin=self.stdin_port, hb=self.hb_port,
292 stdin=self.stdin_port, hb=self.hb_port,
329 control=self.control_port)
293 control=self.control_port)
330
294
331 def init_session(self):
295 def init_session(self):
332 """create our session object"""
296 """create our session object"""
333 default_secure(self.config)
297 default_secure(self.config)
334 self.session = Session(parent=self, username=u'kernel')
298 self.session = Session(parent=self, username=u'kernel')
335
299
336 def init_blackhole(self):
300 def init_blackhole(self):
337 """redirects stdout/stderr to devnull if necessary"""
301 """redirects stdout/stderr to devnull if necessary"""
338 if self.no_stdout or self.no_stderr:
302 if self.no_stdout or self.no_stderr:
339 blackhole = open(os.devnull, 'w')
303 blackhole = open(os.devnull, 'w')
340 if self.no_stdout:
304 if self.no_stdout:
341 sys.stdout = sys.__stdout__ = blackhole
305 sys.stdout = sys.__stdout__ = blackhole
342 if self.no_stderr:
306 if self.no_stderr:
343 sys.stderr = sys.__stderr__ = blackhole
307 sys.stderr = sys.__stderr__ = blackhole
344
308
345 def init_io(self):
309 def init_io(self):
346 """Redirect input streams and set a display hook."""
310 """Redirect input streams and set a display hook."""
347 if self.outstream_class:
311 if self.outstream_class:
348 outstream_factory = import_item(str(self.outstream_class))
312 outstream_factory = import_item(str(self.outstream_class))
349 sys.stdout = outstream_factory(self.session, self.iopub_socket, u'stdout')
313 sys.stdout = outstream_factory(self.session, self.iopub_socket, u'stdout')
350 sys.stderr = outstream_factory(self.session, self.iopub_socket, u'stderr')
314 sys.stderr = outstream_factory(self.session, self.iopub_socket, u'stderr')
351 if self.displayhook_class:
315 if self.displayhook_class:
352 displayhook_factory = import_item(str(self.displayhook_class))
316 displayhook_factory = import_item(str(self.displayhook_class))
353 sys.displayhook = displayhook_factory(self.session, self.iopub_socket)
317 sys.displayhook = displayhook_factory(self.session, self.iopub_socket)
354
318
355 def init_signal(self):
319 def init_signal(self):
356 signal.signal(signal.SIGINT, signal.SIG_IGN)
320 signal.signal(signal.SIGINT, signal.SIG_IGN)
357
321
358 def init_kernel(self):
322 def init_kernel(self):
359 """Create the Kernel object itself"""
323 """Create the Kernel object itself"""
360 shell_stream = ZMQStream(self.shell_socket)
324 shell_stream = ZMQStream(self.shell_socket)
361 control_stream = ZMQStream(self.control_socket)
325 control_stream = ZMQStream(self.control_socket)
362
326
363 kernel_factory = import_item(str(self.kernel_class))
327 kernel_factory = import_item(str(self.kernel_class))
364
328
365 kernel = kernel_factory(parent=self, session=self.session,
329 kernel = kernel_factory(parent=self, session=self.session,
366 shell_streams=[shell_stream, control_stream],
330 shell_streams=[shell_stream, control_stream],
367 iopub_socket=self.iopub_socket,
331 iopub_socket=self.iopub_socket,
368 stdin_socket=self.stdin_socket,
332 stdin_socket=self.stdin_socket,
369 log=self.log,
333 log=self.log,
370 profile_dir=self.profile_dir,
334 profile_dir=self.profile_dir,
371 user_ns=self.user_ns,
335 user_ns=self.user_ns,
372 )
336 )
373 kernel.record_ports(self.ports)
337 kernel.record_ports(self.ports)
374 self.kernel = kernel
338 self.kernel = kernel
375
339
376 def init_gui_pylab(self):
340 def init_gui_pylab(self):
377 """Enable GUI event loop integration, taking pylab into account."""
341 """Enable GUI event loop integration, taking pylab into account."""
378
342
379 # Provide a wrapper for :meth:`InteractiveShellApp.init_gui_pylab`
343 # Provide a wrapper for :meth:`InteractiveShellApp.init_gui_pylab`
380 # to ensure that any exception is printed straight to stderr.
344 # to ensure that any exception is printed straight to stderr.
381 # Normally _showtraceback associates the reply with an execution,
345 # Normally _showtraceback associates the reply with an execution,
382 # which means frontends will never draw it, as this exception
346 # which means frontends will never draw it, as this exception
383 # is not associated with any execute request.
347 # is not associated with any execute request.
384
348
385 shell = self.shell
349 shell = self.shell
386 _showtraceback = shell._showtraceback
350 _showtraceback = shell._showtraceback
387 try:
351 try:
388 # replace pyerr-sending traceback with stderr
352 # replace pyerr-sending traceback with stderr
389 def print_tb(etype, evalue, stb):
353 def print_tb(etype, evalue, stb):
390 print ("GUI event loop or pylab initialization failed",
354 print ("GUI event loop or pylab initialization failed",
391 file=io.stderr)
355 file=io.stderr)
392 print (shell.InteractiveTB.stb2text(stb), file=io.stderr)
356 print (shell.InteractiveTB.stb2text(stb), file=io.stderr)
393 shell._showtraceback = print_tb
357 shell._showtraceback = print_tb
394 InteractiveShellApp.init_gui_pylab(self)
358 InteractiveShellApp.init_gui_pylab(self)
395 finally:
359 finally:
396 shell._showtraceback = _showtraceback
360 shell._showtraceback = _showtraceback
397
361
398 def init_shell(self):
362 def init_shell(self):
399 self.shell = self.kernel.shell
363 self.shell = self.kernel.shell
400 self.shell.configurables.append(self)
364 self.shell.configurables.append(self)
401
365
402 @catch_config_error
366 @catch_config_error
403 def initialize(self, argv=None):
367 def initialize(self, argv=None):
404 super(IPKernelApp, self).initialize(argv)
368 super(IPKernelApp, self).initialize(argv)
405 self.init_blackhole()
369 self.init_blackhole()
406 self.init_connection_file()
370 self.init_connection_file()
407 self.init_session()
371 self.init_session()
408 self.init_poller()
372 self.init_poller()
409 self.init_sockets()
373 self.init_sockets()
410 self.init_heartbeat()
374 self.init_heartbeat()
411 # writing/displaying connection info must be *after* init_sockets/heartbeat
375 # writing/displaying connection info must be *after* init_sockets/heartbeat
412 self.log_connection_info()
376 self.log_connection_info()
413 self.write_connection_file()
377 self.write_connection_file()
414 self.init_io()
378 self.init_io()
415 self.init_signal()
379 self.init_signal()
416 self.init_kernel()
380 self.init_kernel()
417 # shell init steps
381 # shell init steps
418 self.init_path()
382 self.init_path()
419 self.init_shell()
383 self.init_shell()
420 self.init_gui_pylab()
384 self.init_gui_pylab()
421 self.init_extensions()
385 self.init_extensions()
422 self.init_code()
386 self.init_code()
423 # flush stdout/stderr, so that anything written to these streams during
387 # flush stdout/stderr, so that anything written to these streams during
424 # initialization do not get associated with the first execution request
388 # initialization do not get associated with the first execution request
425 sys.stdout.flush()
389 sys.stdout.flush()
426 sys.stderr.flush()
390 sys.stderr.flush()
427
391
428 def start(self):
392 def start(self):
429 if self.poller is not None:
393 if self.poller is not None:
430 self.poller.start()
394 self.poller.start()
431 self.kernel.start()
395 self.kernel.start()
432 try:
396 try:
433 ioloop.IOLoop.instance().start()
397 ioloop.IOLoop.instance().start()
434 except KeyboardInterrupt:
398 except KeyboardInterrupt:
435 pass
399 pass
436
400
437 launch_new_instance = IPKernelApp.launch_instance
401 launch_new_instance = IPKernelApp.launch_instance
438
402
439 def main():
403 def main():
440 """Run an IPKernel as an application"""
404 """Run an IPKernel as an application"""
441 app = IPKernelApp.instance()
405 app = IPKernelApp.instance()
442 app.initialize()
406 app.initialize()
443 app.start()
407 app.start()
444
408
445
409
446 if __name__ == '__main__':
410 if __name__ == '__main__':
447 main()
411 main()
General Comments 0
You need to be logged in to leave comments. Login now