##// END OF EJS Templates
move utils.kernel (formerly entry_point and lib.kernel) to kernel.util
MinRK -
Show More
@@ -1,362 +1,362 b''
1 """ A minimal application base mixin for all ZMQ based IPython frontends.
1 """ A minimal application base mixin for all ZMQ based IPython frontends.
2
2
3 This is not a complete console app, as subprocess will not be able to receive
3 This is not a complete console app, as subprocess will not be able to receive
4 input, there is no real readline support, among other limitations. This is a
4 input, there is no real readline support, among other limitations. This is a
5 refactoring of what used to be the IPython/frontend/qt/console/qtconsoleapp.py
5 refactoring of what used to be the IPython/frontend/qt/console/qtconsoleapp.py
6
6
7 Authors:
7 Authors:
8
8
9 * Evan Patterson
9 * Evan Patterson
10 * Min RK
10 * Min RK
11 * Erik Tollerud
11 * Erik Tollerud
12 * Fernando Perez
12 * Fernando Perez
13 * Bussonnier Matthias
13 * Bussonnier Matthias
14 * Thomas Kluyver
14 * Thomas Kluyver
15 * Paul Ivanov
15 * Paul Ivanov
16
16
17 """
17 """
18
18
19 #-----------------------------------------------------------------------------
19 #-----------------------------------------------------------------------------
20 # Imports
20 # Imports
21 #-----------------------------------------------------------------------------
21 #-----------------------------------------------------------------------------
22
22
23 # stdlib imports
23 # stdlib imports
24 import atexit
24 import atexit
25 import json
25 import json
26 import os
26 import os
27 import shutil
27 import shutil
28 import signal
28 import signal
29 import sys
29 import sys
30 import uuid
30 import uuid
31
31
32
32
33 # Local imports
33 # Local imports
34 from IPython.config.application import boolean_flag
34 from IPython.config.application import boolean_flag
35 from IPython.config.configurable import Configurable
35 from IPython.config.configurable import Configurable
36 from IPython.core.profiledir import ProfileDir
36 from IPython.core.profiledir import ProfileDir
37 from IPython.zmq.blockingkernelmanager import BlockingKernelManager
37 from IPython.zmq.blockingkernelmanager import BlockingKernelManager
38 from IPython.zmq.kernelmanager import KernelManager
38 from IPython.zmq.kernelmanager import KernelManager
39 from IPython.utils.kernel import tunnel_to_kernel, find_connection_file, swallow_argv
39 from IPython.kernel import tunnel_to_kernel, find_connection_file, swallow_argv
40 from IPython.utils.path import filefind
40 from IPython.utils.path import filefind
41 from IPython.utils.py3compat import str_to_bytes
41 from IPython.utils.py3compat import str_to_bytes
42 from IPython.utils.traitlets import (
42 from IPython.utils.traitlets import (
43 Dict, List, Unicode, CUnicode, Int, CBool, Any, CaselessStrEnum
43 Dict, List, Unicode, CUnicode, Int, CBool, Any, CaselessStrEnum
44 )
44 )
45 from IPython.zmq.ipkernel import (
45 from IPython.zmq.ipkernel import (
46 flags as ipkernel_flags,
46 flags as ipkernel_flags,
47 aliases as ipkernel_aliases,
47 aliases as ipkernel_aliases,
48 IPKernelApp
48 IPKernelApp
49 )
49 )
50 from IPython.zmq.session import Session, default_secure
50 from IPython.zmq.session import Session, default_secure
51 from IPython.zmq.zmqshell import ZMQInteractiveShell
51 from IPython.zmq.zmqshell import ZMQInteractiveShell
52
52
53 #-----------------------------------------------------------------------------
53 #-----------------------------------------------------------------------------
54 # Network Constants
54 # Network Constants
55 #-----------------------------------------------------------------------------
55 #-----------------------------------------------------------------------------
56
56
57 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
57 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
58
58
59 #-----------------------------------------------------------------------------
59 #-----------------------------------------------------------------------------
60 # Globals
60 # Globals
61 #-----------------------------------------------------------------------------
61 #-----------------------------------------------------------------------------
62
62
63
63
64 #-----------------------------------------------------------------------------
64 #-----------------------------------------------------------------------------
65 # Aliases and Flags
65 # Aliases and Flags
66 #-----------------------------------------------------------------------------
66 #-----------------------------------------------------------------------------
67
67
68 flags = dict(ipkernel_flags)
68 flags = dict(ipkernel_flags)
69
69
70 # the flags that are specific to the frontend
70 # the flags that are specific to the frontend
71 # these must be scrubbed before being passed to the kernel,
71 # these must be scrubbed before being passed to the kernel,
72 # or it will raise an error on unrecognized flags
72 # or it will raise an error on unrecognized flags
73 app_flags = {
73 app_flags = {
74 'existing' : ({'IPythonConsoleApp' : {'existing' : 'kernel*.json'}},
74 'existing' : ({'IPythonConsoleApp' : {'existing' : 'kernel*.json'}},
75 "Connect to an existing kernel. If no argument specified, guess most recent"),
75 "Connect to an existing kernel. If no argument specified, guess most recent"),
76 }
76 }
77 app_flags.update(boolean_flag(
77 app_flags.update(boolean_flag(
78 'confirm-exit', 'IPythonConsoleApp.confirm_exit',
78 'confirm-exit', 'IPythonConsoleApp.confirm_exit',
79 """Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
79 """Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
80 to force a direct exit without any confirmation.
80 to force a direct exit without any confirmation.
81 """,
81 """,
82 """Don't prompt the user when exiting. This will terminate the kernel
82 """Don't prompt the user when exiting. This will terminate the kernel
83 if it is owned by the frontend, and leave it alive if it is external.
83 if it is owned by the frontend, and leave it alive if it is external.
84 """
84 """
85 ))
85 ))
86 flags.update(app_flags)
86 flags.update(app_flags)
87
87
88 aliases = dict(ipkernel_aliases)
88 aliases = dict(ipkernel_aliases)
89
89
90 # also scrub aliases from the frontend
90 # also scrub aliases from the frontend
91 app_aliases = dict(
91 app_aliases = dict(
92 ip = 'KernelManager.ip',
92 ip = 'KernelManager.ip',
93 transport = 'KernelManager.transport',
93 transport = 'KernelManager.transport',
94 hb = 'IPythonConsoleApp.hb_port',
94 hb = 'IPythonConsoleApp.hb_port',
95 shell = 'IPythonConsoleApp.shell_port',
95 shell = 'IPythonConsoleApp.shell_port',
96 iopub = 'IPythonConsoleApp.iopub_port',
96 iopub = 'IPythonConsoleApp.iopub_port',
97 stdin = 'IPythonConsoleApp.stdin_port',
97 stdin = 'IPythonConsoleApp.stdin_port',
98 existing = 'IPythonConsoleApp.existing',
98 existing = 'IPythonConsoleApp.existing',
99 f = 'IPythonConsoleApp.connection_file',
99 f = 'IPythonConsoleApp.connection_file',
100
100
101
101
102 ssh = 'IPythonConsoleApp.sshserver',
102 ssh = 'IPythonConsoleApp.sshserver',
103 )
103 )
104 aliases.update(app_aliases)
104 aliases.update(app_aliases)
105
105
106 #-----------------------------------------------------------------------------
106 #-----------------------------------------------------------------------------
107 # Classes
107 # Classes
108 #-----------------------------------------------------------------------------
108 #-----------------------------------------------------------------------------
109
109
110 #-----------------------------------------------------------------------------
110 #-----------------------------------------------------------------------------
111 # IPythonConsole
111 # IPythonConsole
112 #-----------------------------------------------------------------------------
112 #-----------------------------------------------------------------------------
113
113
114 classes = [IPKernelApp, ZMQInteractiveShell, KernelManager, ProfileDir, Session]
114 classes = [IPKernelApp, ZMQInteractiveShell, KernelManager, ProfileDir, Session]
115
115
116 try:
116 try:
117 from IPython.zmq.pylab.backend_inline import InlineBackend
117 from IPython.zmq.pylab.backend_inline import InlineBackend
118 except ImportError:
118 except ImportError:
119 pass
119 pass
120 else:
120 else:
121 classes.append(InlineBackend)
121 classes.append(InlineBackend)
122
122
123 class IPythonConsoleApp(Configurable):
123 class IPythonConsoleApp(Configurable):
124 name = 'ipython-console-mixin'
124 name = 'ipython-console-mixin'
125 default_config_file_name='ipython_config.py'
125 default_config_file_name='ipython_config.py'
126
126
127 description = """
127 description = """
128 The IPython Mixin Console.
128 The IPython Mixin Console.
129
129
130 This class contains the common portions of console client (QtConsole,
130 This class contains the common portions of console client (QtConsole,
131 ZMQ-based terminal console, etc). It is not a full console, in that
131 ZMQ-based terminal console, etc). It is not a full console, in that
132 launched terminal subprocesses will not be able to accept input.
132 launched terminal subprocesses will not be able to accept input.
133
133
134 The Console using this mixing supports various extra features beyond
134 The Console using this mixing supports various extra features beyond
135 the single-process Terminal IPython shell, such as connecting to
135 the single-process Terminal IPython shell, such as connecting to
136 existing kernel, via:
136 existing kernel, via:
137
137
138 ipython <appname> --existing
138 ipython <appname> --existing
139
139
140 as well as tunnel via SSH
140 as well as tunnel via SSH
141
141
142 """
142 """
143
143
144 classes = classes
144 classes = classes
145 flags = Dict(flags)
145 flags = Dict(flags)
146 aliases = Dict(aliases)
146 aliases = Dict(aliases)
147 kernel_manager_class = BlockingKernelManager
147 kernel_manager_class = BlockingKernelManager
148
148
149 kernel_argv = List(Unicode)
149 kernel_argv = List(Unicode)
150 # frontend flags&aliases to be stripped when building kernel_argv
150 # frontend flags&aliases to be stripped when building kernel_argv
151 frontend_flags = Any(app_flags)
151 frontend_flags = Any(app_flags)
152 frontend_aliases = Any(app_aliases)
152 frontend_aliases = Any(app_aliases)
153
153
154 # create requested profiles by default, if they don't exist:
154 # create requested profiles by default, if they don't exist:
155 auto_create = CBool(True)
155 auto_create = CBool(True)
156 # connection info:
156 # connection info:
157
157
158 sshserver = Unicode('', config=True,
158 sshserver = Unicode('', config=True,
159 help="""The SSH server to use to connect to the kernel.""")
159 help="""The SSH server to use to connect to the kernel.""")
160 sshkey = Unicode('', config=True,
160 sshkey = Unicode('', config=True,
161 help="""Path to the ssh key to use for logging in to the ssh server.""")
161 help="""Path to the ssh key to use for logging in to the ssh server.""")
162
162
163 hb_port = Int(0, config=True,
163 hb_port = Int(0, config=True,
164 help="set the heartbeat port [default: random]")
164 help="set the heartbeat port [default: random]")
165 shell_port = Int(0, config=True,
165 shell_port = Int(0, config=True,
166 help="set the shell (ROUTER) port [default: random]")
166 help="set the shell (ROUTER) port [default: random]")
167 iopub_port = Int(0, config=True,
167 iopub_port = Int(0, config=True,
168 help="set the iopub (PUB) port [default: random]")
168 help="set the iopub (PUB) port [default: random]")
169 stdin_port = Int(0, config=True,
169 stdin_port = Int(0, config=True,
170 help="set the stdin (DEALER) port [default: random]")
170 help="set the stdin (DEALER) port [default: random]")
171 connection_file = Unicode('', config=True,
171 connection_file = Unicode('', config=True,
172 help="""JSON file in which to store connection info [default: kernel-<pid>.json]
172 help="""JSON file in which to store connection info [default: kernel-<pid>.json]
173
173
174 This file will contain the IP, ports, and authentication key needed to connect
174 This file will contain the IP, ports, and authentication key needed to connect
175 clients to this kernel. By default, this file will be created in the security-dir
175 clients to this kernel. By default, this file will be created in the security-dir
176 of the current profile, but can be specified by absolute path.
176 of the current profile, but can be specified by absolute path.
177 """)
177 """)
178 def _connection_file_default(self):
178 def _connection_file_default(self):
179 return 'kernel-%i.json' % os.getpid()
179 return 'kernel-%i.json' % os.getpid()
180
180
181 existing = CUnicode('', config=True,
181 existing = CUnicode('', config=True,
182 help="""Connect to an already running kernel""")
182 help="""Connect to an already running kernel""")
183
183
184 confirm_exit = CBool(True, config=True,
184 confirm_exit = CBool(True, config=True,
185 help="""
185 help="""
186 Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
186 Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
187 to force a direct exit without any confirmation.""",
187 to force a direct exit without any confirmation.""",
188 )
188 )
189
189
190
190
191 def build_kernel_argv(self, argv=None):
191 def build_kernel_argv(self, argv=None):
192 """build argv to be passed to kernel subprocess"""
192 """build argv to be passed to kernel subprocess"""
193 if argv is None:
193 if argv is None:
194 argv = sys.argv[1:]
194 argv = sys.argv[1:]
195 self.kernel_argv = swallow_argv(argv, self.frontend_aliases, self.frontend_flags)
195 self.kernel_argv = swallow_argv(argv, self.frontend_aliases, self.frontend_flags)
196 # kernel should inherit default config file from frontend
196 # kernel should inherit default config file from frontend
197 self.kernel_argv.append("--KernelApp.parent_appname='%s'"%self.name)
197 self.kernel_argv.append("--KernelApp.parent_appname='%s'"%self.name)
198
198
199 def init_connection_file(self):
199 def init_connection_file(self):
200 """find the connection file, and load the info if found.
200 """find the connection file, and load the info if found.
201
201
202 The current working directory and the current profile's security
202 The current working directory and the current profile's security
203 directory will be searched for the file if it is not given by
203 directory will be searched for the file if it is not given by
204 absolute path.
204 absolute path.
205
205
206 When attempting to connect to an existing kernel and the `--existing`
206 When attempting to connect to an existing kernel and the `--existing`
207 argument does not match an existing file, it will be interpreted as a
207 argument does not match an existing file, it will be interpreted as a
208 fileglob, and the matching file in the current profile's security dir
208 fileglob, and the matching file in the current profile's security dir
209 with the latest access time will be used.
209 with the latest access time will be used.
210
210
211 After this method is called, self.connection_file contains the *full path*
211 After this method is called, self.connection_file contains the *full path*
212 to the connection file, never just its name.
212 to the connection file, never just its name.
213 """
213 """
214 if self.existing:
214 if self.existing:
215 try:
215 try:
216 cf = find_connection_file(self.existing)
216 cf = find_connection_file(self.existing)
217 except Exception:
217 except Exception:
218 self.log.critical("Could not find existing kernel connection file %s", self.existing)
218 self.log.critical("Could not find existing kernel connection file %s", self.existing)
219 self.exit(1)
219 self.exit(1)
220 self.log.info("Connecting to existing kernel: %s" % cf)
220 self.log.info("Connecting to existing kernel: %s" % cf)
221 self.connection_file = cf
221 self.connection_file = cf
222 else:
222 else:
223 # not existing, check if we are going to write the file
223 # not existing, check if we are going to write the file
224 # and ensure that self.connection_file is a full path, not just the shortname
224 # and ensure that self.connection_file is a full path, not just the shortname
225 try:
225 try:
226 cf = find_connection_file(self.connection_file)
226 cf = find_connection_file(self.connection_file)
227 except Exception:
227 except Exception:
228 # file might not exist
228 # file might not exist
229 if self.connection_file == os.path.basename(self.connection_file):
229 if self.connection_file == os.path.basename(self.connection_file):
230 # just shortname, put it in security dir
230 # just shortname, put it in security dir
231 cf = os.path.join(self.profile_dir.security_dir, self.connection_file)
231 cf = os.path.join(self.profile_dir.security_dir, self.connection_file)
232 else:
232 else:
233 cf = self.connection_file
233 cf = self.connection_file
234 self.connection_file = cf
234 self.connection_file = cf
235
235
236 # should load_connection_file only be used for existing?
236 # should load_connection_file only be used for existing?
237 # as it is now, this allows reusing ports if an existing
237 # as it is now, this allows reusing ports if an existing
238 # file is requested
238 # file is requested
239 try:
239 try:
240 self.load_connection_file()
240 self.load_connection_file()
241 except Exception:
241 except Exception:
242 self.log.error("Failed to load connection file: %r", self.connection_file, exc_info=True)
242 self.log.error("Failed to load connection file: %r", self.connection_file, exc_info=True)
243 self.exit(1)
243 self.exit(1)
244
244
245 def load_connection_file(self):
245 def load_connection_file(self):
246 """load ip/port/hmac config from JSON connection file"""
246 """load ip/port/hmac config from JSON connection file"""
247 # this is identical to KernelApp.load_connection_file
247 # this is identical to KernelApp.load_connection_file
248 # perhaps it can be centralized somewhere?
248 # perhaps it can be centralized somewhere?
249 try:
249 try:
250 fname = filefind(self.connection_file, ['.', self.profile_dir.security_dir])
250 fname = filefind(self.connection_file, ['.', self.profile_dir.security_dir])
251 except IOError:
251 except IOError:
252 self.log.debug("Connection File not found: %s", self.connection_file)
252 self.log.debug("Connection File not found: %s", self.connection_file)
253 return
253 return
254 self.log.debug(u"Loading connection file %s", fname)
254 self.log.debug(u"Loading connection file %s", fname)
255 with open(fname) as f:
255 with open(fname) as f:
256 cfg = json.load(f)
256 cfg = json.load(f)
257
257
258 self.config.KernelManager.transport = cfg.get('transport', 'tcp')
258 self.config.KernelManager.transport = cfg.get('transport', 'tcp')
259 self.config.KernelManager.ip = cfg.get('ip', LOCALHOST)
259 self.config.KernelManager.ip = cfg.get('ip', LOCALHOST)
260
260
261 for channel in ('hb', 'shell', 'iopub', 'stdin'):
261 for channel in ('hb', 'shell', 'iopub', 'stdin'):
262 name = channel + '_port'
262 name = channel + '_port'
263 if getattr(self, name) == 0 and name in cfg:
263 if getattr(self, name) == 0 and name in cfg:
264 # not overridden by config or cl_args
264 # not overridden by config or cl_args
265 setattr(self, name, cfg[name])
265 setattr(self, name, cfg[name])
266 if 'key' in cfg:
266 if 'key' in cfg:
267 self.config.Session.key = str_to_bytes(cfg['key'])
267 self.config.Session.key = str_to_bytes(cfg['key'])
268
268
269 def init_ssh(self):
269 def init_ssh(self):
270 """set up ssh tunnels, if needed."""
270 """set up ssh tunnels, if needed."""
271 if not self.existing or (not self.sshserver and not self.sshkey):
271 if not self.existing or (not self.sshserver and not self.sshkey):
272 return
272 return
273
273
274 self.load_connection_file()
274 self.load_connection_file()
275
275
276 transport = self.config.KernelManager.transport
276 transport = self.config.KernelManager.transport
277 ip = self.config.KernelManager.ip
277 ip = self.config.KernelManager.ip
278
278
279 if transport != 'tcp':
279 if transport != 'tcp':
280 self.log.error("Can only use ssh tunnels with TCP sockets, not %s", transport)
280 self.log.error("Can only use ssh tunnels with TCP sockets, not %s", transport)
281 sys.exit(-1)
281 sys.exit(-1)
282
282
283 if self.sshkey and not self.sshserver:
283 if self.sshkey and not self.sshserver:
284 # specifying just the key implies that we are connecting directly
284 # specifying just the key implies that we are connecting directly
285 self.sshserver = ip
285 self.sshserver = ip
286 ip = LOCALHOST
286 ip = LOCALHOST
287
287
288 # build connection dict for tunnels:
288 # build connection dict for tunnels:
289 info = dict(ip=ip,
289 info = dict(ip=ip,
290 shell_port=self.shell_port,
290 shell_port=self.shell_port,
291 iopub_port=self.iopub_port,
291 iopub_port=self.iopub_port,
292 stdin_port=self.stdin_port,
292 stdin_port=self.stdin_port,
293 hb_port=self.hb_port
293 hb_port=self.hb_port
294 )
294 )
295
295
296 self.log.info("Forwarding connections to %s via %s"%(ip, self.sshserver))
296 self.log.info("Forwarding connections to %s via %s"%(ip, self.sshserver))
297
297
298 # tunnels return a new set of ports, which will be on localhost:
298 # tunnels return a new set of ports, which will be on localhost:
299 self.config.KernelManager.ip = LOCALHOST
299 self.config.KernelManager.ip = LOCALHOST
300 try:
300 try:
301 newports = tunnel_to_kernel(info, self.sshserver, self.sshkey)
301 newports = tunnel_to_kernel(info, self.sshserver, self.sshkey)
302 except:
302 except:
303 # even catch KeyboardInterrupt
303 # even catch KeyboardInterrupt
304 self.log.error("Could not setup tunnels", exc_info=True)
304 self.log.error("Could not setup tunnels", exc_info=True)
305 self.exit(1)
305 self.exit(1)
306
306
307 self.shell_port, self.iopub_port, self.stdin_port, self.hb_port = newports
307 self.shell_port, self.iopub_port, self.stdin_port, self.hb_port = newports
308
308
309 cf = self.connection_file
309 cf = self.connection_file
310 base,ext = os.path.splitext(cf)
310 base,ext = os.path.splitext(cf)
311 base = os.path.basename(base)
311 base = os.path.basename(base)
312 self.connection_file = os.path.basename(base)+'-ssh'+ext
312 self.connection_file = os.path.basename(base)+'-ssh'+ext
313 self.log.critical("To connect another client via this tunnel, use:")
313 self.log.critical("To connect another client via this tunnel, use:")
314 self.log.critical("--existing %s" % self.connection_file)
314 self.log.critical("--existing %s" % self.connection_file)
315
315
316 def _new_connection_file(self):
316 def _new_connection_file(self):
317 cf = ''
317 cf = ''
318 while not cf:
318 while not cf:
319 # we don't need a 128b id to distinguish kernels, use more readable
319 # we don't need a 128b id to distinguish kernels, use more readable
320 # 48b node segment (12 hex chars). Users running more than 32k simultaneous
320 # 48b node segment (12 hex chars). Users running more than 32k simultaneous
321 # kernels can subclass.
321 # kernels can subclass.
322 ident = str(uuid.uuid4()).split('-')[-1]
322 ident = str(uuid.uuid4()).split('-')[-1]
323 cf = os.path.join(self.profile_dir.security_dir, 'kernel-%s.json' % ident)
323 cf = os.path.join(self.profile_dir.security_dir, 'kernel-%s.json' % ident)
324 # only keep if it's actually new. Protect against unlikely collision
324 # only keep if it's actually new. Protect against unlikely collision
325 # in 48b random search space
325 # in 48b random search space
326 cf = cf if not os.path.exists(cf) else ''
326 cf = cf if not os.path.exists(cf) else ''
327 return cf
327 return cf
328
328
329 def init_kernel_manager(self):
329 def init_kernel_manager(self):
330 # Don't let Qt or ZMQ swallow KeyboardInterupts.
330 # Don't let Qt or ZMQ swallow KeyboardInterupts.
331 signal.signal(signal.SIGINT, signal.SIG_DFL)
331 signal.signal(signal.SIGINT, signal.SIG_DFL)
332
332
333 # Create a KernelManager and start a kernel.
333 # Create a KernelManager and start a kernel.
334 self.kernel_manager = self.kernel_manager_class(
334 self.kernel_manager = self.kernel_manager_class(
335 shell_port=self.shell_port,
335 shell_port=self.shell_port,
336 iopub_port=self.iopub_port,
336 iopub_port=self.iopub_port,
337 stdin_port=self.stdin_port,
337 stdin_port=self.stdin_port,
338 hb_port=self.hb_port,
338 hb_port=self.hb_port,
339 connection_file=self.connection_file,
339 connection_file=self.connection_file,
340 config=self.config,
340 config=self.config,
341 )
341 )
342 # start the kernel
342 # start the kernel
343 if not self.existing:
343 if not self.existing:
344 self.kernel_manager.start_kernel(extra_arguments=self.kernel_argv)
344 self.kernel_manager.start_kernel(extra_arguments=self.kernel_argv)
345 atexit.register(self.kernel_manager.cleanup_ipc_files)
345 atexit.register(self.kernel_manager.cleanup_ipc_files)
346 elif self.sshserver:
346 elif self.sshserver:
347 # ssh, write new connection file
347 # ssh, write new connection file
348 self.kernel_manager.write_connection_file()
348 self.kernel_manager.write_connection_file()
349 atexit.register(self.kernel_manager.cleanup_connection_file)
349 atexit.register(self.kernel_manager.cleanup_connection_file)
350 self.kernel_manager.start_channels()
350 self.kernel_manager.start_channels()
351
351
352
352
353 def initialize(self, argv=None):
353 def initialize(self, argv=None):
354 """
354 """
355 Classes which mix this class in should call:
355 Classes which mix this class in should call:
356 IPythonConsoleApp.initialize(self,argv)
356 IPythonConsoleApp.initialize(self,argv)
357 """
357 """
358 self.init_connection_file()
358 self.init_connection_file()
359 default_secure(self.config)
359 default_secure(self.config)
360 self.init_ssh()
360 self.init_ssh()
361 self.init_kernel_manager()
361 self.init_kernel_manager()
362
362
@@ -1,643 +1,643 b''
1 # coding: utf-8
1 # coding: utf-8
2 """A tornado based IPython notebook server.
2 """A tornado based IPython notebook server.
3
3
4 Authors:
4 Authors:
5
5
6 * Brian Granger
6 * Brian Granger
7 """
7 """
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2008-2011 The IPython Development Team
9 # Copyright (C) 2008-2011 The IPython Development Team
10 #
10 #
11 # Distributed under the terms of the BSD License. The full license is in
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
12 # the file COPYING, distributed as part of this software.
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 # Imports
16 # Imports
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18
18
19 # stdlib
19 # stdlib
20 import errno
20 import errno
21 import logging
21 import logging
22 import os
22 import os
23 import random
23 import random
24 import re
24 import re
25 import select
25 import select
26 import signal
26 import signal
27 import socket
27 import socket
28 import sys
28 import sys
29 import threading
29 import threading
30 import time
30 import time
31 import uuid
31 import uuid
32 import webbrowser
32 import webbrowser
33
33
34 # Third party
34 # Third party
35 import zmq
35 import zmq
36 from jinja2 import Environment, FileSystemLoader
36 from jinja2 import Environment, FileSystemLoader
37
37
38 # Install the pyzmq ioloop. This has to be done before anything else from
38 # Install the pyzmq ioloop. This has to be done before anything else from
39 # tornado is imported.
39 # tornado is imported.
40 from zmq.eventloop import ioloop
40 from zmq.eventloop import ioloop
41 ioloop.install()
41 ioloop.install()
42
42
43 from tornado import httpserver
43 from tornado import httpserver
44 from tornado import web
44 from tornado import web
45
45
46 # Our own libraries
46 # Our own libraries
47 from .kernelmanager import MappingKernelManager
47 from .kernelmanager import MappingKernelManager
48 from .handlers import (LoginHandler, LogoutHandler,
48 from .handlers import (LoginHandler, LogoutHandler,
49 ProjectDashboardHandler, NewHandler, NamedNotebookHandler,
49 ProjectDashboardHandler, NewHandler, NamedNotebookHandler,
50 MainKernelHandler, KernelHandler, KernelActionHandler, IOPubHandler,
50 MainKernelHandler, KernelHandler, KernelActionHandler, IOPubHandler,
51 ShellHandler, NotebookRootHandler, NotebookHandler, NotebookCopyHandler,
51 ShellHandler, NotebookRootHandler, NotebookHandler, NotebookCopyHandler,
52 RSTHandler, AuthenticatedFileHandler, PrintNotebookHandler,
52 RSTHandler, AuthenticatedFileHandler, PrintNotebookHandler,
53 MainClusterHandler, ClusterProfileHandler, ClusterActionHandler,
53 MainClusterHandler, ClusterProfileHandler, ClusterActionHandler,
54 FileFindHandler,
54 FileFindHandler,
55 )
55 )
56 from .nbmanager import NotebookManager
56 from .nbmanager import NotebookManager
57 from .filenbmanager import FileNotebookManager
57 from .filenbmanager import FileNotebookManager
58 from .clustermanager import ClusterManager
58 from .clustermanager import ClusterManager
59
59
60 from IPython.config.application import catch_config_error, boolean_flag
60 from IPython.config.application import catch_config_error, boolean_flag
61 from IPython.core.application import BaseIPythonApplication
61 from IPython.core.application import BaseIPythonApplication
62 from IPython.core.profiledir import ProfileDir
62 from IPython.core.profiledir import ProfileDir
63 from IPython.frontend.consoleapp import IPythonConsoleApp
63 from IPython.frontend.consoleapp import IPythonConsoleApp
64 from IPython.zmq.session import Session, default_secure
64 from IPython.zmq.session import Session, default_secure
65 from IPython.zmq.zmqshell import ZMQInteractiveShell
65 from IPython.zmq.zmqshell import ZMQInteractiveShell
66 from IPython.zmq.ipkernel import (
66 from IPython.zmq.ipkernel import (
67 flags as ipkernel_flags,
67 flags as ipkernel_flags,
68 aliases as ipkernel_aliases,
68 aliases as ipkernel_aliases,
69 IPKernelApp
69 IPKernelApp
70 )
70 )
71 from IPython.utils.importstring import import_item
71 from IPython.utils.importstring import import_item
72 from IPython.utils.localinterfaces import LOCALHOST
72 from IPython.utils.localinterfaces import LOCALHOST
73 from IPython.utils.kernel import swallow_argv
73 from IPython.kernel import swallow_argv
74 from IPython.utils.traitlets import (
74 from IPython.utils.traitlets import (
75 Dict, Unicode, Integer, List, Enum, Bool,
75 Dict, Unicode, Integer, List, Enum, Bool,
76 DottedObjectName
76 DottedObjectName
77 )
77 )
78 from IPython.utils import py3compat
78 from IPython.utils import py3compat
79 from IPython.utils.path import filefind
79 from IPython.utils.path import filefind
80
80
81 #-----------------------------------------------------------------------------
81 #-----------------------------------------------------------------------------
82 # Module globals
82 # Module globals
83 #-----------------------------------------------------------------------------
83 #-----------------------------------------------------------------------------
84
84
85 _kernel_id_regex = r"(?P<kernel_id>\w+-\w+-\w+-\w+-\w+)"
85 _kernel_id_regex = r"(?P<kernel_id>\w+-\w+-\w+-\w+-\w+)"
86 _kernel_action_regex = r"(?P<action>restart|interrupt)"
86 _kernel_action_regex = r"(?P<action>restart|interrupt)"
87 _notebook_id_regex = r"(?P<notebook_id>\w+-\w+-\w+-\w+-\w+)"
87 _notebook_id_regex = r"(?P<notebook_id>\w+-\w+-\w+-\w+-\w+)"
88 _profile_regex = r"(?P<profile>[^\/]+)" # there is almost no text that is invalid
88 _profile_regex = r"(?P<profile>[^\/]+)" # there is almost no text that is invalid
89 _cluster_action_regex = r"(?P<action>start|stop)"
89 _cluster_action_regex = r"(?P<action>start|stop)"
90
90
91 _examples = """
91 _examples = """
92 ipython notebook # start the notebook
92 ipython notebook # start the notebook
93 ipython notebook --profile=sympy # use the sympy profile
93 ipython notebook --profile=sympy # use the sympy profile
94 ipython notebook --pylab=inline # pylab in inline plotting mode
94 ipython notebook --pylab=inline # pylab in inline plotting mode
95 ipython notebook --certfile=mycert.pem # use SSL/TLS certificate
95 ipython notebook --certfile=mycert.pem # use SSL/TLS certificate
96 ipython notebook --port=5555 --ip=* # Listen on port 5555, all interfaces
96 ipython notebook --port=5555 --ip=* # Listen on port 5555, all interfaces
97 """
97 """
98
98
99 #-----------------------------------------------------------------------------
99 #-----------------------------------------------------------------------------
100 # Helper functions
100 # Helper functions
101 #-----------------------------------------------------------------------------
101 #-----------------------------------------------------------------------------
102
102
103 def url_path_join(a,b):
103 def url_path_join(a,b):
104 if a.endswith('/') and b.startswith('/'):
104 if a.endswith('/') and b.startswith('/'):
105 return a[:-1]+b
105 return a[:-1]+b
106 else:
106 else:
107 return a+b
107 return a+b
108
108
109 def random_ports(port, n):
109 def random_ports(port, n):
110 """Generate a list of n random ports near the given port.
110 """Generate a list of n random ports near the given port.
111
111
112 The first 5 ports will be sequential, and the remaining n-5 will be
112 The first 5 ports will be sequential, and the remaining n-5 will be
113 randomly selected in the range [port-2*n, port+2*n].
113 randomly selected in the range [port-2*n, port+2*n].
114 """
114 """
115 for i in range(min(5, n)):
115 for i in range(min(5, n)):
116 yield port + i
116 yield port + i
117 for i in range(n-5):
117 for i in range(n-5):
118 yield port + random.randint(-2*n, 2*n)
118 yield port + random.randint(-2*n, 2*n)
119
119
120 #-----------------------------------------------------------------------------
120 #-----------------------------------------------------------------------------
121 # The Tornado web application
121 # The Tornado web application
122 #-----------------------------------------------------------------------------
122 #-----------------------------------------------------------------------------
123
123
124 class NotebookWebApplication(web.Application):
124 class NotebookWebApplication(web.Application):
125
125
126 def __init__(self, ipython_app, kernel_manager, notebook_manager,
126 def __init__(self, ipython_app, kernel_manager, notebook_manager,
127 cluster_manager, log,
127 cluster_manager, log,
128 base_project_url, settings_overrides):
128 base_project_url, settings_overrides):
129 handlers = [
129 handlers = [
130 (r"/", ProjectDashboardHandler),
130 (r"/", ProjectDashboardHandler),
131 (r"/login", LoginHandler),
131 (r"/login", LoginHandler),
132 (r"/logout", LogoutHandler),
132 (r"/logout", LogoutHandler),
133 (r"/new", NewHandler),
133 (r"/new", NewHandler),
134 (r"/%s" % _notebook_id_regex, NamedNotebookHandler),
134 (r"/%s" % _notebook_id_regex, NamedNotebookHandler),
135 (r"/%s/copy" % _notebook_id_regex, NotebookCopyHandler),
135 (r"/%s/copy" % _notebook_id_regex, NotebookCopyHandler),
136 (r"/%s/print" % _notebook_id_regex, PrintNotebookHandler),
136 (r"/%s/print" % _notebook_id_regex, PrintNotebookHandler),
137 (r"/kernels", MainKernelHandler),
137 (r"/kernels", MainKernelHandler),
138 (r"/kernels/%s" % _kernel_id_regex, KernelHandler),
138 (r"/kernels/%s" % _kernel_id_regex, KernelHandler),
139 (r"/kernels/%s/%s" % (_kernel_id_regex, _kernel_action_regex), KernelActionHandler),
139 (r"/kernels/%s/%s" % (_kernel_id_regex, _kernel_action_regex), KernelActionHandler),
140 (r"/kernels/%s/iopub" % _kernel_id_regex, IOPubHandler),
140 (r"/kernels/%s/iopub" % _kernel_id_regex, IOPubHandler),
141 (r"/kernels/%s/shell" % _kernel_id_regex, ShellHandler),
141 (r"/kernels/%s/shell" % _kernel_id_regex, ShellHandler),
142 (r"/notebooks", NotebookRootHandler),
142 (r"/notebooks", NotebookRootHandler),
143 (r"/notebooks/%s" % _notebook_id_regex, NotebookHandler),
143 (r"/notebooks/%s" % _notebook_id_regex, NotebookHandler),
144 (r"/rstservice/render", RSTHandler),
144 (r"/rstservice/render", RSTHandler),
145 (r"/files/(.*)", AuthenticatedFileHandler, {'path' : notebook_manager.notebook_dir}),
145 (r"/files/(.*)", AuthenticatedFileHandler, {'path' : notebook_manager.notebook_dir}),
146 (r"/clusters", MainClusterHandler),
146 (r"/clusters", MainClusterHandler),
147 (r"/clusters/%s/%s" % (_profile_regex, _cluster_action_regex), ClusterActionHandler),
147 (r"/clusters/%s/%s" % (_profile_regex, _cluster_action_regex), ClusterActionHandler),
148 (r"/clusters/%s" % _profile_regex, ClusterProfileHandler),
148 (r"/clusters/%s" % _profile_regex, ClusterProfileHandler),
149 ]
149 ]
150
150
151 # Python < 2.6.5 doesn't accept unicode keys in f(**kwargs), and
151 # Python < 2.6.5 doesn't accept unicode keys in f(**kwargs), and
152 # base_project_url will always be unicode, which will in turn
152 # base_project_url will always be unicode, which will in turn
153 # make the patterns unicode, and ultimately result in unicode
153 # make the patterns unicode, and ultimately result in unicode
154 # keys in kwargs to handler._execute(**kwargs) in tornado.
154 # keys in kwargs to handler._execute(**kwargs) in tornado.
155 # This enforces that base_project_url be ascii in that situation.
155 # This enforces that base_project_url be ascii in that situation.
156 #
156 #
157 # Note that the URLs these patterns check against are escaped,
157 # Note that the URLs these patterns check against are escaped,
158 # and thus guaranteed to be ASCII: 'hΓ©llo' is really 'h%C3%A9llo'.
158 # and thus guaranteed to be ASCII: 'hΓ©llo' is really 'h%C3%A9llo'.
159 base_project_url = py3compat.unicode_to_str(base_project_url, 'ascii')
159 base_project_url = py3compat.unicode_to_str(base_project_url, 'ascii')
160
160
161 settings = dict(
161 settings = dict(
162 template_path=os.path.join(os.path.dirname(__file__), "templates"),
162 template_path=os.path.join(os.path.dirname(__file__), "templates"),
163 static_path=ipython_app.static_file_path,
163 static_path=ipython_app.static_file_path,
164 static_handler_class = FileFindHandler,
164 static_handler_class = FileFindHandler,
165 static_url_prefix = url_path_join(base_project_url,'/static/'),
165 static_url_prefix = url_path_join(base_project_url,'/static/'),
166 cookie_secret=os.urandom(1024),
166 cookie_secret=os.urandom(1024),
167 login_url=url_path_join(base_project_url,'/login'),
167 login_url=url_path_join(base_project_url,'/login'),
168 cookie_name='username-%s' % uuid.uuid4(),
168 cookie_name='username-%s' % uuid.uuid4(),
169 )
169 )
170
170
171 # allow custom overrides for the tornado web app.
171 # allow custom overrides for the tornado web app.
172 settings.update(settings_overrides)
172 settings.update(settings_overrides)
173
173
174 # prepend base_project_url onto the patterns that we match
174 # prepend base_project_url onto the patterns that we match
175 new_handlers = []
175 new_handlers = []
176 for handler in handlers:
176 for handler in handlers:
177 pattern = url_path_join(base_project_url, handler[0])
177 pattern = url_path_join(base_project_url, handler[0])
178 new_handler = tuple([pattern]+list(handler[1:]))
178 new_handler = tuple([pattern]+list(handler[1:]))
179 new_handlers.append( new_handler )
179 new_handlers.append( new_handler )
180
180
181 super(NotebookWebApplication, self).__init__(new_handlers, **settings)
181 super(NotebookWebApplication, self).__init__(new_handlers, **settings)
182
182
183 self.kernel_manager = kernel_manager
183 self.kernel_manager = kernel_manager
184 self.notebook_manager = notebook_manager
184 self.notebook_manager = notebook_manager
185 self.cluster_manager = cluster_manager
185 self.cluster_manager = cluster_manager
186 self.ipython_app = ipython_app
186 self.ipython_app = ipython_app
187 self.read_only = self.ipython_app.read_only
187 self.read_only = self.ipython_app.read_only
188 self.config = self.ipython_app.config
188 self.config = self.ipython_app.config
189 self.log = log
189 self.log = log
190 self.jinja2_env = Environment(loader=FileSystemLoader(os.path.join(os.path.dirname(__file__), "templates")))
190 self.jinja2_env = Environment(loader=FileSystemLoader(os.path.join(os.path.dirname(__file__), "templates")))
191
191
192
192
193
193
194 #-----------------------------------------------------------------------------
194 #-----------------------------------------------------------------------------
195 # Aliases and Flags
195 # Aliases and Flags
196 #-----------------------------------------------------------------------------
196 #-----------------------------------------------------------------------------
197
197
198 flags = dict(ipkernel_flags)
198 flags = dict(ipkernel_flags)
199 flags['no-browser']=(
199 flags['no-browser']=(
200 {'NotebookApp' : {'open_browser' : False}},
200 {'NotebookApp' : {'open_browser' : False}},
201 "Don't open the notebook in a browser after startup."
201 "Don't open the notebook in a browser after startup."
202 )
202 )
203 flags['no-mathjax']=(
203 flags['no-mathjax']=(
204 {'NotebookApp' : {'enable_mathjax' : False}},
204 {'NotebookApp' : {'enable_mathjax' : False}},
205 """Disable MathJax
205 """Disable MathJax
206
206
207 MathJax is the javascript library IPython uses to render math/LaTeX. It is
207 MathJax is the javascript library IPython uses to render math/LaTeX. It is
208 very large, so you may want to disable it if you have a slow internet
208 very large, so you may want to disable it if you have a slow internet
209 connection, or for offline use of the notebook.
209 connection, or for offline use of the notebook.
210
210
211 When disabled, equations etc. will appear as their untransformed TeX source.
211 When disabled, equations etc. will appear as their untransformed TeX source.
212 """
212 """
213 )
213 )
214 flags['read-only'] = (
214 flags['read-only'] = (
215 {'NotebookApp' : {'read_only' : True}},
215 {'NotebookApp' : {'read_only' : True}},
216 """Allow read-only access to notebooks.
216 """Allow read-only access to notebooks.
217
217
218 When using a password to protect the notebook server, this flag
218 When using a password to protect the notebook server, this flag
219 allows unauthenticated clients to view the notebook list, and
219 allows unauthenticated clients to view the notebook list, and
220 individual notebooks, but not edit them, start kernels, or run
220 individual notebooks, but not edit them, start kernels, or run
221 code.
221 code.
222
222
223 If no password is set, the server will be entirely read-only.
223 If no password is set, the server will be entirely read-only.
224 """
224 """
225 )
225 )
226
226
227 # Add notebook manager flags
227 # Add notebook manager flags
228 flags.update(boolean_flag('script', 'FileNotebookManager.save_script',
228 flags.update(boolean_flag('script', 'FileNotebookManager.save_script',
229 'Auto-save a .py script everytime the .ipynb notebook is saved',
229 'Auto-save a .py script everytime the .ipynb notebook is saved',
230 'Do not auto-save .py scripts for every notebook'))
230 'Do not auto-save .py scripts for every notebook'))
231
231
232 # the flags that are specific to the frontend
232 # the flags that are specific to the frontend
233 # these must be scrubbed before being passed to the kernel,
233 # these must be scrubbed before being passed to the kernel,
234 # or it will raise an error on unrecognized flags
234 # or it will raise an error on unrecognized flags
235 notebook_flags = ['no-browser', 'no-mathjax', 'read-only', 'script', 'no-script']
235 notebook_flags = ['no-browser', 'no-mathjax', 'read-only', 'script', 'no-script']
236
236
237 aliases = dict(ipkernel_aliases)
237 aliases = dict(ipkernel_aliases)
238
238
239 aliases.update({
239 aliases.update({
240 'ip': 'NotebookApp.ip',
240 'ip': 'NotebookApp.ip',
241 'port': 'NotebookApp.port',
241 'port': 'NotebookApp.port',
242 'port-retries': 'NotebookApp.port_retries',
242 'port-retries': 'NotebookApp.port_retries',
243 'transport': 'KernelManager.transport',
243 'transport': 'KernelManager.transport',
244 'keyfile': 'NotebookApp.keyfile',
244 'keyfile': 'NotebookApp.keyfile',
245 'certfile': 'NotebookApp.certfile',
245 'certfile': 'NotebookApp.certfile',
246 'notebook-dir': 'NotebookManager.notebook_dir',
246 'notebook-dir': 'NotebookManager.notebook_dir',
247 'browser': 'NotebookApp.browser',
247 'browser': 'NotebookApp.browser',
248 })
248 })
249
249
250 # remove ipkernel flags that are singletons, and don't make sense in
250 # remove ipkernel flags that are singletons, and don't make sense in
251 # multi-kernel evironment:
251 # multi-kernel evironment:
252 aliases.pop('f', None)
252 aliases.pop('f', None)
253
253
254 notebook_aliases = [u'port', u'port-retries', u'ip', u'keyfile', u'certfile',
254 notebook_aliases = [u'port', u'port-retries', u'ip', u'keyfile', u'certfile',
255 u'notebook-dir']
255 u'notebook-dir']
256
256
257 #-----------------------------------------------------------------------------
257 #-----------------------------------------------------------------------------
258 # NotebookApp
258 # NotebookApp
259 #-----------------------------------------------------------------------------
259 #-----------------------------------------------------------------------------
260
260
261 class NotebookApp(BaseIPythonApplication):
261 class NotebookApp(BaseIPythonApplication):
262
262
263 name = 'ipython-notebook'
263 name = 'ipython-notebook'
264 default_config_file_name='ipython_notebook_config.py'
264 default_config_file_name='ipython_notebook_config.py'
265
265
266 description = """
266 description = """
267 The IPython HTML Notebook.
267 The IPython HTML Notebook.
268
268
269 This launches a Tornado based HTML Notebook Server that serves up an
269 This launches a Tornado based HTML Notebook Server that serves up an
270 HTML5/Javascript Notebook client.
270 HTML5/Javascript Notebook client.
271 """
271 """
272 examples = _examples
272 examples = _examples
273
273
274 classes = IPythonConsoleApp.classes + [MappingKernelManager, NotebookManager,
274 classes = IPythonConsoleApp.classes + [MappingKernelManager, NotebookManager,
275 FileNotebookManager]
275 FileNotebookManager]
276 flags = Dict(flags)
276 flags = Dict(flags)
277 aliases = Dict(aliases)
277 aliases = Dict(aliases)
278
278
279 kernel_argv = List(Unicode)
279 kernel_argv = List(Unicode)
280
280
281 log_level = Enum((0,10,20,30,40,50,'DEBUG','INFO','WARN','ERROR','CRITICAL'),
281 log_level = Enum((0,10,20,30,40,50,'DEBUG','INFO','WARN','ERROR','CRITICAL'),
282 default_value=logging.INFO,
282 default_value=logging.INFO,
283 config=True,
283 config=True,
284 help="Set the log level by value or name.")
284 help="Set the log level by value or name.")
285
285
286 # create requested profiles by default, if they don't exist:
286 # create requested profiles by default, if they don't exist:
287 auto_create = Bool(True)
287 auto_create = Bool(True)
288
288
289 # file to be opened in the notebook server
289 # file to be opened in the notebook server
290 file_to_run = Unicode('')
290 file_to_run = Unicode('')
291
291
292 # Network related information.
292 # Network related information.
293
293
294 ip = Unicode(LOCALHOST, config=True,
294 ip = Unicode(LOCALHOST, config=True,
295 help="The IP address the notebook server will listen on."
295 help="The IP address the notebook server will listen on."
296 )
296 )
297
297
298 def _ip_changed(self, name, old, new):
298 def _ip_changed(self, name, old, new):
299 if new == u'*': self.ip = u''
299 if new == u'*': self.ip = u''
300
300
301 port = Integer(8888, config=True,
301 port = Integer(8888, config=True,
302 help="The port the notebook server will listen on."
302 help="The port the notebook server will listen on."
303 )
303 )
304 port_retries = Integer(50, config=True,
304 port_retries = Integer(50, config=True,
305 help="The number of additional ports to try if the specified port is not available."
305 help="The number of additional ports to try if the specified port is not available."
306 )
306 )
307
307
308 certfile = Unicode(u'', config=True,
308 certfile = Unicode(u'', config=True,
309 help="""The full path to an SSL/TLS certificate file."""
309 help="""The full path to an SSL/TLS certificate file."""
310 )
310 )
311
311
312 keyfile = Unicode(u'', config=True,
312 keyfile = Unicode(u'', config=True,
313 help="""The full path to a private key file for usage with SSL/TLS."""
313 help="""The full path to a private key file for usage with SSL/TLS."""
314 )
314 )
315
315
316 password = Unicode(u'', config=True,
316 password = Unicode(u'', config=True,
317 help="""Hashed password to use for web authentication.
317 help="""Hashed password to use for web authentication.
318
318
319 To generate, type in a python/IPython shell:
319 To generate, type in a python/IPython shell:
320
320
321 from IPython.lib import passwd; passwd()
321 from IPython.lib import passwd; passwd()
322
322
323 The string should be of the form type:salt:hashed-password.
323 The string should be of the form type:salt:hashed-password.
324 """
324 """
325 )
325 )
326
326
327 open_browser = Bool(True, config=True,
327 open_browser = Bool(True, config=True,
328 help="""Whether to open in a browser after starting.
328 help="""Whether to open in a browser after starting.
329 The specific browser used is platform dependent and
329 The specific browser used is platform dependent and
330 determined by the python standard library `webbrowser`
330 determined by the python standard library `webbrowser`
331 module, unless it is overridden using the --browser
331 module, unless it is overridden using the --browser
332 (NotebookApp.browser) configuration option.
332 (NotebookApp.browser) configuration option.
333 """)
333 """)
334
334
335 browser = Unicode(u'', config=True,
335 browser = Unicode(u'', config=True,
336 help="""Specify what command to use to invoke a web
336 help="""Specify what command to use to invoke a web
337 browser when opening the notebook. If not specified, the
337 browser when opening the notebook. If not specified, the
338 default browser will be determined by the `webbrowser`
338 default browser will be determined by the `webbrowser`
339 standard library module, which allows setting of the
339 standard library module, which allows setting of the
340 BROWSER environment variable to override it.
340 BROWSER environment variable to override it.
341 """)
341 """)
342
342
343 read_only = Bool(False, config=True,
343 read_only = Bool(False, config=True,
344 help="Whether to prevent editing/execution of notebooks."
344 help="Whether to prevent editing/execution of notebooks."
345 )
345 )
346
346
347 webapp_settings = Dict(config=True,
347 webapp_settings = Dict(config=True,
348 help="Supply overrides for the tornado.web.Application that the "
348 help="Supply overrides for the tornado.web.Application that the "
349 "IPython notebook uses.")
349 "IPython notebook uses.")
350
350
351 enable_mathjax = Bool(True, config=True,
351 enable_mathjax = Bool(True, config=True,
352 help="""Whether to enable MathJax for typesetting math/TeX
352 help="""Whether to enable MathJax for typesetting math/TeX
353
353
354 MathJax is the javascript library IPython uses to render math/LaTeX. It is
354 MathJax is the javascript library IPython uses to render math/LaTeX. It is
355 very large, so you may want to disable it if you have a slow internet
355 very large, so you may want to disable it if you have a slow internet
356 connection, or for offline use of the notebook.
356 connection, or for offline use of the notebook.
357
357
358 When disabled, equations etc. will appear as their untransformed TeX source.
358 When disabled, equations etc. will appear as their untransformed TeX source.
359 """
359 """
360 )
360 )
361 def _enable_mathjax_changed(self, name, old, new):
361 def _enable_mathjax_changed(self, name, old, new):
362 """set mathjax url to empty if mathjax is disabled"""
362 """set mathjax url to empty if mathjax is disabled"""
363 if not new:
363 if not new:
364 self.mathjax_url = u''
364 self.mathjax_url = u''
365
365
366 base_project_url = Unicode('/', config=True,
366 base_project_url = Unicode('/', config=True,
367 help='''The base URL for the notebook server.
367 help='''The base URL for the notebook server.
368
368
369 Leading and trailing slashes can be omitted,
369 Leading and trailing slashes can be omitted,
370 and will automatically be added.
370 and will automatically be added.
371 ''')
371 ''')
372 def _base_project_url_changed(self, name, old, new):
372 def _base_project_url_changed(self, name, old, new):
373 if not new.startswith('/'):
373 if not new.startswith('/'):
374 self.base_project_url = '/'+new
374 self.base_project_url = '/'+new
375 elif not new.endswith('/'):
375 elif not new.endswith('/'):
376 self.base_project_url = new+'/'
376 self.base_project_url = new+'/'
377
377
378 base_kernel_url = Unicode('/', config=True,
378 base_kernel_url = Unicode('/', config=True,
379 help='''The base URL for the kernel server
379 help='''The base URL for the kernel server
380
380
381 Leading and trailing slashes can be omitted,
381 Leading and trailing slashes can be omitted,
382 and will automatically be added.
382 and will automatically be added.
383 ''')
383 ''')
384 def _base_kernel_url_changed(self, name, old, new):
384 def _base_kernel_url_changed(self, name, old, new):
385 if not new.startswith('/'):
385 if not new.startswith('/'):
386 self.base_kernel_url = '/'+new
386 self.base_kernel_url = '/'+new
387 elif not new.endswith('/'):
387 elif not new.endswith('/'):
388 self.base_kernel_url = new+'/'
388 self.base_kernel_url = new+'/'
389
389
390 websocket_host = Unicode("", config=True,
390 websocket_host = Unicode("", config=True,
391 help="""The hostname for the websocket server."""
391 help="""The hostname for the websocket server."""
392 )
392 )
393
393
394 extra_static_paths = List(Unicode, config=True,
394 extra_static_paths = List(Unicode, config=True,
395 help="""Extra paths to search for serving static files.
395 help="""Extra paths to search for serving static files.
396
396
397 This allows adding javascript/css to be available from the notebook server machine,
397 This allows adding javascript/css to be available from the notebook server machine,
398 or overriding individual files in the IPython"""
398 or overriding individual files in the IPython"""
399 )
399 )
400 def _extra_static_paths_default(self):
400 def _extra_static_paths_default(self):
401 return [os.path.join(self.profile_dir.location, 'static')]
401 return [os.path.join(self.profile_dir.location, 'static')]
402
402
403 @property
403 @property
404 def static_file_path(self):
404 def static_file_path(self):
405 """return extra paths + the default location"""
405 """return extra paths + the default location"""
406 return self.extra_static_paths + [os.path.join(os.path.dirname(__file__), "static")]
406 return self.extra_static_paths + [os.path.join(os.path.dirname(__file__), "static")]
407
407
408 mathjax_url = Unicode("", config=True,
408 mathjax_url = Unicode("", config=True,
409 help="""The url for MathJax.js."""
409 help="""The url for MathJax.js."""
410 )
410 )
411 def _mathjax_url_default(self):
411 def _mathjax_url_default(self):
412 if not self.enable_mathjax:
412 if not self.enable_mathjax:
413 return u''
413 return u''
414 static_url_prefix = self.webapp_settings.get("static_url_prefix",
414 static_url_prefix = self.webapp_settings.get("static_url_prefix",
415 "/static/")
415 "/static/")
416 try:
416 try:
417 mathjax = filefind(os.path.join('mathjax', 'MathJax.js'), self.static_file_path)
417 mathjax = filefind(os.path.join('mathjax', 'MathJax.js'), self.static_file_path)
418 except IOError:
418 except IOError:
419 if self.certfile:
419 if self.certfile:
420 # HTTPS: load from Rackspace CDN, because SSL certificate requires it
420 # HTTPS: load from Rackspace CDN, because SSL certificate requires it
421 base = u"https://c328740.ssl.cf1.rackcdn.com"
421 base = u"https://c328740.ssl.cf1.rackcdn.com"
422 else:
422 else:
423 base = u"http://cdn.mathjax.org"
423 base = u"http://cdn.mathjax.org"
424
424
425 url = base + u"/mathjax/latest/MathJax.js"
425 url = base + u"/mathjax/latest/MathJax.js"
426 self.log.info("Using MathJax from CDN: %s", url)
426 self.log.info("Using MathJax from CDN: %s", url)
427 return url
427 return url
428 else:
428 else:
429 self.log.info("Using local MathJax from %s" % mathjax)
429 self.log.info("Using local MathJax from %s" % mathjax)
430 return static_url_prefix+u"mathjax/MathJax.js"
430 return static_url_prefix+u"mathjax/MathJax.js"
431
431
432 def _mathjax_url_changed(self, name, old, new):
432 def _mathjax_url_changed(self, name, old, new):
433 if new and not self.enable_mathjax:
433 if new and not self.enable_mathjax:
434 # enable_mathjax=False overrides mathjax_url
434 # enable_mathjax=False overrides mathjax_url
435 self.mathjax_url = u''
435 self.mathjax_url = u''
436 else:
436 else:
437 self.log.info("Using MathJax: %s", new)
437 self.log.info("Using MathJax: %s", new)
438
438
439 notebook_manager_class = DottedObjectName('IPython.frontend.html.notebook.filenbmanager.FileNotebookManager',
439 notebook_manager_class = DottedObjectName('IPython.frontend.html.notebook.filenbmanager.FileNotebookManager',
440 config=True,
440 config=True,
441 help='The notebook manager class to use.')
441 help='The notebook manager class to use.')
442
442
443 def parse_command_line(self, argv=None):
443 def parse_command_line(self, argv=None):
444 super(NotebookApp, self).parse_command_line(argv)
444 super(NotebookApp, self).parse_command_line(argv)
445 if argv is None:
445 if argv is None:
446 argv = sys.argv[1:]
446 argv = sys.argv[1:]
447
447
448 # Scrub frontend-specific flags
448 # Scrub frontend-specific flags
449 self.kernel_argv = swallow_argv(argv, notebook_aliases, notebook_flags)
449 self.kernel_argv = swallow_argv(argv, notebook_aliases, notebook_flags)
450 # Kernel should inherit default config file from frontend
450 # Kernel should inherit default config file from frontend
451 self.kernel_argv.append("--KernelApp.parent_appname='%s'"%self.name)
451 self.kernel_argv.append("--KernelApp.parent_appname='%s'"%self.name)
452
452
453 if self.extra_args:
453 if self.extra_args:
454 f = os.path.abspath(self.extra_args[0])
454 f = os.path.abspath(self.extra_args[0])
455 if os.path.isdir(f):
455 if os.path.isdir(f):
456 nbdir = f
456 nbdir = f
457 else:
457 else:
458 self.file_to_run = f
458 self.file_to_run = f
459 nbdir = os.path.dirname(f)
459 nbdir = os.path.dirname(f)
460 self.config.NotebookManager.notebook_dir = nbdir
460 self.config.NotebookManager.notebook_dir = nbdir
461
461
462 def init_configurables(self):
462 def init_configurables(self):
463 # force Session default to be secure
463 # force Session default to be secure
464 default_secure(self.config)
464 default_secure(self.config)
465 self.kernel_manager = MappingKernelManager(
465 self.kernel_manager = MappingKernelManager(
466 config=self.config, log=self.log, kernel_argv=self.kernel_argv,
466 config=self.config, log=self.log, kernel_argv=self.kernel_argv,
467 connection_dir = self.profile_dir.security_dir,
467 connection_dir = self.profile_dir.security_dir,
468 )
468 )
469 kls = import_item(self.notebook_manager_class)
469 kls = import_item(self.notebook_manager_class)
470 self.notebook_manager = kls(config=self.config, log=self.log)
470 self.notebook_manager = kls(config=self.config, log=self.log)
471 self.notebook_manager.log_info()
471 self.notebook_manager.log_info()
472 self.notebook_manager.load_notebook_names()
472 self.notebook_manager.load_notebook_names()
473 self.cluster_manager = ClusterManager(config=self.config, log=self.log)
473 self.cluster_manager = ClusterManager(config=self.config, log=self.log)
474 self.cluster_manager.update_profiles()
474 self.cluster_manager.update_profiles()
475
475
476 def init_logging(self):
476 def init_logging(self):
477 # This prevents double log messages because tornado use a root logger that
477 # This prevents double log messages because tornado use a root logger that
478 # self.log is a child of. The logging module dipatches log messages to a log
478 # self.log is a child of. The logging module dipatches log messages to a log
479 # and all of its ancenstors until propagate is set to False.
479 # and all of its ancenstors until propagate is set to False.
480 self.log.propagate = False
480 self.log.propagate = False
481
481
482 def init_webapp(self):
482 def init_webapp(self):
483 """initialize tornado webapp and httpserver"""
483 """initialize tornado webapp and httpserver"""
484 self.web_app = NotebookWebApplication(
484 self.web_app = NotebookWebApplication(
485 self, self.kernel_manager, self.notebook_manager,
485 self, self.kernel_manager, self.notebook_manager,
486 self.cluster_manager, self.log,
486 self.cluster_manager, self.log,
487 self.base_project_url, self.webapp_settings
487 self.base_project_url, self.webapp_settings
488 )
488 )
489 if self.certfile:
489 if self.certfile:
490 ssl_options = dict(certfile=self.certfile)
490 ssl_options = dict(certfile=self.certfile)
491 if self.keyfile:
491 if self.keyfile:
492 ssl_options['keyfile'] = self.keyfile
492 ssl_options['keyfile'] = self.keyfile
493 else:
493 else:
494 ssl_options = None
494 ssl_options = None
495 self.web_app.password = self.password
495 self.web_app.password = self.password
496 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options)
496 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options)
497 if not self.ip:
497 if not self.ip:
498 warning = "WARNING: The notebook server is listening on all IP addresses"
498 warning = "WARNING: The notebook server is listening on all IP addresses"
499 if ssl_options is None:
499 if ssl_options is None:
500 self.log.critical(warning + " and not using encryption. This"
500 self.log.critical(warning + " and not using encryption. This"
501 "is not recommended.")
501 "is not recommended.")
502 if not self.password and not self.read_only:
502 if not self.password and not self.read_only:
503 self.log.critical(warning + "and not using authentication."
503 self.log.critical(warning + "and not using authentication."
504 "This is highly insecure and not recommended.")
504 "This is highly insecure and not recommended.")
505 success = None
505 success = None
506 for port in random_ports(self.port, self.port_retries+1):
506 for port in random_ports(self.port, self.port_retries+1):
507 try:
507 try:
508 self.http_server.listen(port, self.ip)
508 self.http_server.listen(port, self.ip)
509 except socket.error as e:
509 except socket.error as e:
510 if e.errno != errno.EADDRINUSE:
510 if e.errno != errno.EADDRINUSE:
511 raise
511 raise
512 self.log.info('The port %i is already in use, trying another random port.' % port)
512 self.log.info('The port %i is already in use, trying another random port.' % port)
513 else:
513 else:
514 self.port = port
514 self.port = port
515 success = True
515 success = True
516 break
516 break
517 if not success:
517 if not success:
518 self.log.critical('ERROR: the notebook server could not be started because '
518 self.log.critical('ERROR: the notebook server could not be started because '
519 'no available port could be found.')
519 'no available port could be found.')
520 self.exit(1)
520 self.exit(1)
521
521
522 def init_signal(self):
522 def init_signal(self):
523 # FIXME: remove this check when pyzmq dependency is >= 2.1.11
523 # FIXME: remove this check when pyzmq dependency is >= 2.1.11
524 # safely extract zmq version info:
524 # safely extract zmq version info:
525 try:
525 try:
526 zmq_v = zmq.pyzmq_version_info()
526 zmq_v = zmq.pyzmq_version_info()
527 except AttributeError:
527 except AttributeError:
528 zmq_v = [ int(n) for n in re.findall(r'\d+', zmq.__version__) ]
528 zmq_v = [ int(n) for n in re.findall(r'\d+', zmq.__version__) ]
529 if 'dev' in zmq.__version__:
529 if 'dev' in zmq.__version__:
530 zmq_v.append(999)
530 zmq_v.append(999)
531 zmq_v = tuple(zmq_v)
531 zmq_v = tuple(zmq_v)
532 if zmq_v >= (2,1,9) and not sys.platform.startswith('win'):
532 if zmq_v >= (2,1,9) and not sys.platform.startswith('win'):
533 # This won't work with 2.1.7 and
533 # This won't work with 2.1.7 and
534 # 2.1.9-10 will log ugly 'Interrupted system call' messages,
534 # 2.1.9-10 will log ugly 'Interrupted system call' messages,
535 # but it will work
535 # but it will work
536 signal.signal(signal.SIGINT, self._handle_sigint)
536 signal.signal(signal.SIGINT, self._handle_sigint)
537 signal.signal(signal.SIGTERM, self._signal_stop)
537 signal.signal(signal.SIGTERM, self._signal_stop)
538
538
539 def _handle_sigint(self, sig, frame):
539 def _handle_sigint(self, sig, frame):
540 """SIGINT handler spawns confirmation dialog"""
540 """SIGINT handler spawns confirmation dialog"""
541 # register more forceful signal handler for ^C^C case
541 # register more forceful signal handler for ^C^C case
542 signal.signal(signal.SIGINT, self._signal_stop)
542 signal.signal(signal.SIGINT, self._signal_stop)
543 # request confirmation dialog in bg thread, to avoid
543 # request confirmation dialog in bg thread, to avoid
544 # blocking the App
544 # blocking the App
545 thread = threading.Thread(target=self._confirm_exit)
545 thread = threading.Thread(target=self._confirm_exit)
546 thread.daemon = True
546 thread.daemon = True
547 thread.start()
547 thread.start()
548
548
549 def _restore_sigint_handler(self):
549 def _restore_sigint_handler(self):
550 """callback for restoring original SIGINT handler"""
550 """callback for restoring original SIGINT handler"""
551 signal.signal(signal.SIGINT, self._handle_sigint)
551 signal.signal(signal.SIGINT, self._handle_sigint)
552
552
553 def _confirm_exit(self):
553 def _confirm_exit(self):
554 """confirm shutdown on ^C
554 """confirm shutdown on ^C
555
555
556 A second ^C, or answering 'y' within 5s will cause shutdown,
556 A second ^C, or answering 'y' within 5s will cause shutdown,
557 otherwise original SIGINT handler will be restored.
557 otherwise original SIGINT handler will be restored.
558
558
559 This doesn't work on Windows.
559 This doesn't work on Windows.
560 """
560 """
561 # FIXME: remove this delay when pyzmq dependency is >= 2.1.11
561 # FIXME: remove this delay when pyzmq dependency is >= 2.1.11
562 time.sleep(0.1)
562 time.sleep(0.1)
563 sys.stdout.write("Shutdown Notebook Server (y/[n])? ")
563 sys.stdout.write("Shutdown Notebook Server (y/[n])? ")
564 sys.stdout.flush()
564 sys.stdout.flush()
565 r,w,x = select.select([sys.stdin], [], [], 5)
565 r,w,x = select.select([sys.stdin], [], [], 5)
566 if r:
566 if r:
567 line = sys.stdin.readline()
567 line = sys.stdin.readline()
568 if line.lower().startswith('y'):
568 if line.lower().startswith('y'):
569 self.log.critical("Shutdown confirmed")
569 self.log.critical("Shutdown confirmed")
570 ioloop.IOLoop.instance().stop()
570 ioloop.IOLoop.instance().stop()
571 return
571 return
572 else:
572 else:
573 print "No answer for 5s:",
573 print "No answer for 5s:",
574 print "resuming operation..."
574 print "resuming operation..."
575 # no answer, or answer is no:
575 # no answer, or answer is no:
576 # set it back to original SIGINT handler
576 # set it back to original SIGINT handler
577 # use IOLoop.add_callback because signal.signal must be called
577 # use IOLoop.add_callback because signal.signal must be called
578 # from main thread
578 # from main thread
579 ioloop.IOLoop.instance().add_callback(self._restore_sigint_handler)
579 ioloop.IOLoop.instance().add_callback(self._restore_sigint_handler)
580
580
581 def _signal_stop(self, sig, frame):
581 def _signal_stop(self, sig, frame):
582 self.log.critical("received signal %s, stopping", sig)
582 self.log.critical("received signal %s, stopping", sig)
583 ioloop.IOLoop.instance().stop()
583 ioloop.IOLoop.instance().stop()
584
584
585 @catch_config_error
585 @catch_config_error
586 def initialize(self, argv=None):
586 def initialize(self, argv=None):
587 self.init_logging()
587 self.init_logging()
588 super(NotebookApp, self).initialize(argv)
588 super(NotebookApp, self).initialize(argv)
589 self.init_configurables()
589 self.init_configurables()
590 self.init_webapp()
590 self.init_webapp()
591 self.init_signal()
591 self.init_signal()
592
592
593 def cleanup_kernels(self):
593 def cleanup_kernels(self):
594 """Shutdown all kernels.
594 """Shutdown all kernels.
595
595
596 The kernels will shutdown themselves when this process no longer exists,
596 The kernels will shutdown themselves when this process no longer exists,
597 but explicit shutdown allows the KernelManagers to cleanup the connection files.
597 but explicit shutdown allows the KernelManagers to cleanup the connection files.
598 """
598 """
599 self.log.info('Shutting down kernels')
599 self.log.info('Shutting down kernels')
600 self.kernel_manager.shutdown_all()
600 self.kernel_manager.shutdown_all()
601
601
602 def start(self):
602 def start(self):
603 ip = self.ip if self.ip else '[all ip addresses on your system]'
603 ip = self.ip if self.ip else '[all ip addresses on your system]'
604 proto = 'https' if self.certfile else 'http'
604 proto = 'https' if self.certfile else 'http'
605 info = self.log.info
605 info = self.log.info
606 info("The IPython Notebook is running at: %s://%s:%i%s" %
606 info("The IPython Notebook is running at: %s://%s:%i%s" %
607 (proto, ip, self.port,self.base_project_url) )
607 (proto, ip, self.port,self.base_project_url) )
608 info("Use Control-C to stop this server and shut down all kernels.")
608 info("Use Control-C to stop this server and shut down all kernels.")
609
609
610 if self.open_browser or self.file_to_run:
610 if self.open_browser or self.file_to_run:
611 ip = self.ip or LOCALHOST
611 ip = self.ip or LOCALHOST
612 try:
612 try:
613 browser = webbrowser.get(self.browser or None)
613 browser = webbrowser.get(self.browser or None)
614 except webbrowser.Error as e:
614 except webbrowser.Error as e:
615 self.log.warn('No web browser found: %s.' % e)
615 self.log.warn('No web browser found: %s.' % e)
616 browser = None
616 browser = None
617
617
618 if self.file_to_run:
618 if self.file_to_run:
619 name, _ = os.path.splitext(os.path.basename(self.file_to_run))
619 name, _ = os.path.splitext(os.path.basename(self.file_to_run))
620 url = self.notebook_manager.rev_mapping.get(name, '')
620 url = self.notebook_manager.rev_mapping.get(name, '')
621 else:
621 else:
622 url = ''
622 url = ''
623 if browser:
623 if browser:
624 b = lambda : browser.open("%s://%s:%i%s%s" % (proto, ip,
624 b = lambda : browser.open("%s://%s:%i%s%s" % (proto, ip,
625 self.port, self.base_project_url, url), new=2)
625 self.port, self.base_project_url, url), new=2)
626 threading.Thread(target=b).start()
626 threading.Thread(target=b).start()
627 try:
627 try:
628 ioloop.IOLoop.instance().start()
628 ioloop.IOLoop.instance().start()
629 except KeyboardInterrupt:
629 except KeyboardInterrupt:
630 info("Interrupted...")
630 info("Interrupted...")
631 finally:
631 finally:
632 self.cleanup_kernels()
632 self.cleanup_kernels()
633
633
634
634
635 #-----------------------------------------------------------------------------
635 #-----------------------------------------------------------------------------
636 # Main entry point
636 # Main entry point
637 #-----------------------------------------------------------------------------
637 #-----------------------------------------------------------------------------
638
638
639 def launch_new_instance():
639 def launch_new_instance():
640 app = NotebookApp.instance()
640 app = NotebookApp.instance()
641 app.initialize()
641 app.initialize()
642 app.start()
642 app.start()
643
643
@@ -1,370 +1,370 b''
1 """ A minimal application using the Qt console-style IPython frontend.
1 """ A minimal application using the Qt console-style IPython frontend.
2
2
3 This is not a complete console app, as subprocess will not be able to receive
3 This is not a complete console app, as subprocess will not be able to receive
4 input, there is no real readline support, among other limitations.
4 input, there is no real readline support, among other limitations.
5
5
6 Authors:
6 Authors:
7
7
8 * Evan Patterson
8 * Evan Patterson
9 * Min RK
9 * Min RK
10 * Erik Tollerud
10 * Erik Tollerud
11 * Fernando Perez
11 * Fernando Perez
12 * Bussonnier Matthias
12 * Bussonnier Matthias
13 * Thomas Kluyver
13 * Thomas Kluyver
14 * Paul Ivanov
14 * Paul Ivanov
15
15
16 """
16 """
17
17
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19 # Imports
19 # Imports
20 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
21
21
22 # stdlib imports
22 # stdlib imports
23 import json
23 import json
24 import os
24 import os
25 import signal
25 import signal
26 import sys
26 import sys
27 import uuid
27 import uuid
28
28
29 # If run on Windows, install an exception hook which pops up a
29 # If run on Windows, install an exception hook which pops up a
30 # message box. Pythonw.exe hides the console, so without this
30 # message box. Pythonw.exe hides the console, so without this
31 # the application silently fails to load.
31 # the application silently fails to load.
32 #
32 #
33 # We always install this handler, because the expectation is for
33 # We always install this handler, because the expectation is for
34 # qtconsole to bring up a GUI even if called from the console.
34 # qtconsole to bring up a GUI even if called from the console.
35 # The old handler is called, so the exception is printed as well.
35 # The old handler is called, so the exception is printed as well.
36 # If desired, check for pythonw with an additional condition
36 # If desired, check for pythonw with an additional condition
37 # (sys.executable.lower().find('pythonw.exe') >= 0).
37 # (sys.executable.lower().find('pythonw.exe') >= 0).
38 if os.name == 'nt':
38 if os.name == 'nt':
39 old_excepthook = sys.excepthook
39 old_excepthook = sys.excepthook
40
40
41 def gui_excepthook(exctype, value, tb):
41 def gui_excepthook(exctype, value, tb):
42 try:
42 try:
43 import ctypes, traceback
43 import ctypes, traceback
44 MB_ICONERROR = 0x00000010L
44 MB_ICONERROR = 0x00000010L
45 title = u'Error starting IPython QtConsole'
45 title = u'Error starting IPython QtConsole'
46 msg = u''.join(traceback.format_exception(exctype, value, tb))
46 msg = u''.join(traceback.format_exception(exctype, value, tb))
47 ctypes.windll.user32.MessageBoxW(0, msg, title, MB_ICONERROR)
47 ctypes.windll.user32.MessageBoxW(0, msg, title, MB_ICONERROR)
48 finally:
48 finally:
49 # Also call the old exception hook to let it do
49 # Also call the old exception hook to let it do
50 # its thing too.
50 # its thing too.
51 old_excepthook(exctype, value, tb)
51 old_excepthook(exctype, value, tb)
52
52
53 sys.excepthook = gui_excepthook
53 sys.excepthook = gui_excepthook
54
54
55 # System library imports
55 # System library imports
56 from IPython.external.qt import QtCore, QtGui
56 from IPython.external.qt import QtCore, QtGui
57
57
58 # Local imports
58 # Local imports
59 from IPython.config.application import boolean_flag, catch_config_error
59 from IPython.config.application import boolean_flag, catch_config_error
60 from IPython.core.application import BaseIPythonApplication
60 from IPython.core.application import BaseIPythonApplication
61 from IPython.core.profiledir import ProfileDir
61 from IPython.core.profiledir import ProfileDir
62 from IPython.frontend.qt.console.frontend_widget import FrontendWidget
62 from IPython.frontend.qt.console.frontend_widget import FrontendWidget
63 from IPython.frontend.qt.console.ipython_widget import IPythonWidget
63 from IPython.frontend.qt.console.ipython_widget import IPythonWidget
64 from IPython.frontend.qt.console.rich_ipython_widget import RichIPythonWidget
64 from IPython.frontend.qt.console.rich_ipython_widget import RichIPythonWidget
65 from IPython.frontend.qt.console import styles
65 from IPython.frontend.qt.console import styles
66 from IPython.frontend.qt.console.mainwindow import MainWindow
66 from IPython.frontend.qt.console.mainwindow import MainWindow
67 from IPython.frontend.qt.kernelmanager import QtKernelManager
67 from IPython.frontend.qt.kernelmanager import QtKernelManager
68 from IPython.utils.kernel import tunnel_to_kernel, find_connection_file
68 from IPython.kernel import tunnel_to_kernel, find_connection_file
69 from IPython.utils.path import filefind
69 from IPython.utils.path import filefind
70 from IPython.utils.py3compat import str_to_bytes
70 from IPython.utils.py3compat import str_to_bytes
71 from IPython.utils.traitlets import (
71 from IPython.utils.traitlets import (
72 Dict, List, Unicode, Integer, CaselessStrEnum, CBool, Any
72 Dict, List, Unicode, Integer, CaselessStrEnum, CBool, Any
73 )
73 )
74 from IPython.zmq.ipkernel import IPKernelApp
74 from IPython.zmq.ipkernel import IPKernelApp
75 from IPython.zmq.session import Session, default_secure
75 from IPython.zmq.session import Session, default_secure
76 from IPython.zmq.zmqshell import ZMQInteractiveShell
76 from IPython.zmq.zmqshell import ZMQInteractiveShell
77
77
78 from IPython.frontend.consoleapp import (
78 from IPython.frontend.consoleapp import (
79 IPythonConsoleApp, app_aliases, app_flags, flags, aliases
79 IPythonConsoleApp, app_aliases, app_flags, flags, aliases
80 )
80 )
81
81
82 #-----------------------------------------------------------------------------
82 #-----------------------------------------------------------------------------
83 # Network Constants
83 # Network Constants
84 #-----------------------------------------------------------------------------
84 #-----------------------------------------------------------------------------
85
85
86 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
86 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
87
87
88 #-----------------------------------------------------------------------------
88 #-----------------------------------------------------------------------------
89 # Globals
89 # Globals
90 #-----------------------------------------------------------------------------
90 #-----------------------------------------------------------------------------
91
91
92 _examples = """
92 _examples = """
93 ipython qtconsole # start the qtconsole
93 ipython qtconsole # start the qtconsole
94 ipython qtconsole --pylab=inline # start with pylab in inline plotting mode
94 ipython qtconsole --pylab=inline # start with pylab in inline plotting mode
95 """
95 """
96
96
97 #-----------------------------------------------------------------------------
97 #-----------------------------------------------------------------------------
98 # Aliases and Flags
98 # Aliases and Flags
99 #-----------------------------------------------------------------------------
99 #-----------------------------------------------------------------------------
100
100
101 # start with copy of flags
101 # start with copy of flags
102 flags = dict(flags)
102 flags = dict(flags)
103 qt_flags = {
103 qt_flags = {
104 'plain' : ({'IPythonQtConsoleApp' : {'plain' : True}},
104 'plain' : ({'IPythonQtConsoleApp' : {'plain' : True}},
105 "Disable rich text support."),
105 "Disable rich text support."),
106 }
106 }
107
107
108 # and app_flags from the Console Mixin
108 # and app_flags from the Console Mixin
109 qt_flags.update(app_flags)
109 qt_flags.update(app_flags)
110 # add frontend flags to the full set
110 # add frontend flags to the full set
111 flags.update(qt_flags)
111 flags.update(qt_flags)
112
112
113 # start with copy of front&backend aliases list
113 # start with copy of front&backend aliases list
114 aliases = dict(aliases)
114 aliases = dict(aliases)
115 qt_aliases = dict(
115 qt_aliases = dict(
116 style = 'IPythonWidget.syntax_style',
116 style = 'IPythonWidget.syntax_style',
117 stylesheet = 'IPythonQtConsoleApp.stylesheet',
117 stylesheet = 'IPythonQtConsoleApp.stylesheet',
118 colors = 'ZMQInteractiveShell.colors',
118 colors = 'ZMQInteractiveShell.colors',
119
119
120 editor = 'IPythonWidget.editor',
120 editor = 'IPythonWidget.editor',
121 paging = 'ConsoleWidget.paging',
121 paging = 'ConsoleWidget.paging',
122 )
122 )
123 # and app_aliases from the Console Mixin
123 # and app_aliases from the Console Mixin
124 qt_aliases.update(app_aliases)
124 qt_aliases.update(app_aliases)
125 qt_aliases.update({'gui-completion':'ConsoleWidget.gui_completion'})
125 qt_aliases.update({'gui-completion':'ConsoleWidget.gui_completion'})
126 # add frontend aliases to the full set
126 # add frontend aliases to the full set
127 aliases.update(qt_aliases)
127 aliases.update(qt_aliases)
128
128
129 # get flags&aliases into sets, and remove a couple that
129 # get flags&aliases into sets, and remove a couple that
130 # shouldn't be scrubbed from backend flags:
130 # shouldn't be scrubbed from backend flags:
131 qt_aliases = set(qt_aliases.keys())
131 qt_aliases = set(qt_aliases.keys())
132 qt_aliases.remove('colors')
132 qt_aliases.remove('colors')
133 qt_flags = set(qt_flags.keys())
133 qt_flags = set(qt_flags.keys())
134
134
135 #-----------------------------------------------------------------------------
135 #-----------------------------------------------------------------------------
136 # Classes
136 # Classes
137 #-----------------------------------------------------------------------------
137 #-----------------------------------------------------------------------------
138
138
139 #-----------------------------------------------------------------------------
139 #-----------------------------------------------------------------------------
140 # IPythonQtConsole
140 # IPythonQtConsole
141 #-----------------------------------------------------------------------------
141 #-----------------------------------------------------------------------------
142
142
143
143
144 class IPythonQtConsoleApp(BaseIPythonApplication, IPythonConsoleApp):
144 class IPythonQtConsoleApp(BaseIPythonApplication, IPythonConsoleApp):
145 name = 'ipython-qtconsole'
145 name = 'ipython-qtconsole'
146
146
147 description = """
147 description = """
148 The IPython QtConsole.
148 The IPython QtConsole.
149
149
150 This launches a Console-style application using Qt. It is not a full
150 This launches a Console-style application using Qt. It is not a full
151 console, in that launched terminal subprocesses will not be able to accept
151 console, in that launched terminal subprocesses will not be able to accept
152 input.
152 input.
153
153
154 The QtConsole supports various extra features beyond the Terminal IPython
154 The QtConsole supports various extra features beyond the Terminal IPython
155 shell, such as inline plotting with matplotlib, via:
155 shell, such as inline plotting with matplotlib, via:
156
156
157 ipython qtconsole --pylab=inline
157 ipython qtconsole --pylab=inline
158
158
159 as well as saving your session as HTML, and printing the output.
159 as well as saving your session as HTML, and printing the output.
160
160
161 """
161 """
162 examples = _examples
162 examples = _examples
163
163
164 classes = [IPythonWidget] + IPythonConsoleApp.classes
164 classes = [IPythonWidget] + IPythonConsoleApp.classes
165 flags = Dict(flags)
165 flags = Dict(flags)
166 aliases = Dict(aliases)
166 aliases = Dict(aliases)
167 frontend_flags = Any(qt_flags)
167 frontend_flags = Any(qt_flags)
168 frontend_aliases = Any(qt_aliases)
168 frontend_aliases = Any(qt_aliases)
169 kernel_manager_class = QtKernelManager
169 kernel_manager_class = QtKernelManager
170
170
171 stylesheet = Unicode('', config=True,
171 stylesheet = Unicode('', config=True,
172 help="path to a custom CSS stylesheet")
172 help="path to a custom CSS stylesheet")
173
173
174 plain = CBool(False, config=True,
174 plain = CBool(False, config=True,
175 help="Use a plaintext widget instead of rich text (plain can't print/save).")
175 help="Use a plaintext widget instead of rich text (plain can't print/save).")
176
176
177 def _plain_changed(self, name, old, new):
177 def _plain_changed(self, name, old, new):
178 kind = 'plain' if new else 'rich'
178 kind = 'plain' if new else 'rich'
179 self.config.ConsoleWidget.kind = kind
179 self.config.ConsoleWidget.kind = kind
180 if new:
180 if new:
181 self.widget_factory = IPythonWidget
181 self.widget_factory = IPythonWidget
182 else:
182 else:
183 self.widget_factory = RichIPythonWidget
183 self.widget_factory = RichIPythonWidget
184
184
185 # the factory for creating a widget
185 # the factory for creating a widget
186 widget_factory = Any(RichIPythonWidget)
186 widget_factory = Any(RichIPythonWidget)
187
187
188 def parse_command_line(self, argv=None):
188 def parse_command_line(self, argv=None):
189 super(IPythonQtConsoleApp, self).parse_command_line(argv)
189 super(IPythonQtConsoleApp, self).parse_command_line(argv)
190 self.build_kernel_argv(argv)
190 self.build_kernel_argv(argv)
191
191
192
192
193 def new_frontend_master(self):
193 def new_frontend_master(self):
194 """ Create and return new frontend attached to new kernel, launched on localhost.
194 """ Create and return new frontend attached to new kernel, launched on localhost.
195 """
195 """
196 kernel_manager = self.kernel_manager_class(
196 kernel_manager = self.kernel_manager_class(
197 connection_file=self._new_connection_file(),
197 connection_file=self._new_connection_file(),
198 config=self.config,
198 config=self.config,
199 )
199 )
200 # start the kernel
200 # start the kernel
201 kwargs = dict()
201 kwargs = dict()
202 kwargs['extra_arguments'] = self.kernel_argv
202 kwargs['extra_arguments'] = self.kernel_argv
203 kernel_manager.start_kernel(**kwargs)
203 kernel_manager.start_kernel(**kwargs)
204 kernel_manager.start_channels()
204 kernel_manager.start_channels()
205 widget = self.widget_factory(config=self.config,
205 widget = self.widget_factory(config=self.config,
206 local_kernel=True)
206 local_kernel=True)
207 self.init_colors(widget)
207 self.init_colors(widget)
208 widget.kernel_manager = kernel_manager
208 widget.kernel_manager = kernel_manager
209 widget._existing = False
209 widget._existing = False
210 widget._may_close = True
210 widget._may_close = True
211 widget._confirm_exit = self.confirm_exit
211 widget._confirm_exit = self.confirm_exit
212 return widget
212 return widget
213
213
214 def new_frontend_slave(self, current_widget):
214 def new_frontend_slave(self, current_widget):
215 """Create and return a new frontend attached to an existing kernel.
215 """Create and return a new frontend attached to an existing kernel.
216
216
217 Parameters
217 Parameters
218 ----------
218 ----------
219 current_widget : IPythonWidget
219 current_widget : IPythonWidget
220 The IPythonWidget whose kernel this frontend is to share
220 The IPythonWidget whose kernel this frontend is to share
221 """
221 """
222 kernel_manager = self.kernel_manager_class(
222 kernel_manager = self.kernel_manager_class(
223 connection_file=current_widget.kernel_manager.connection_file,
223 connection_file=current_widget.kernel_manager.connection_file,
224 config = self.config,
224 config = self.config,
225 )
225 )
226 kernel_manager.load_connection_file()
226 kernel_manager.load_connection_file()
227 kernel_manager.start_channels()
227 kernel_manager.start_channels()
228 widget = self.widget_factory(config=self.config,
228 widget = self.widget_factory(config=self.config,
229 local_kernel=False)
229 local_kernel=False)
230 self.init_colors(widget)
230 self.init_colors(widget)
231 widget._existing = True
231 widget._existing = True
232 widget._may_close = False
232 widget._may_close = False
233 widget._confirm_exit = False
233 widget._confirm_exit = False
234 widget.kernel_manager = kernel_manager
234 widget.kernel_manager = kernel_manager
235 return widget
235 return widget
236
236
237 def init_qt_elements(self):
237 def init_qt_elements(self):
238 # Create the widget.
238 # Create the widget.
239 self.app = QtGui.QApplication([])
239 self.app = QtGui.QApplication([])
240
240
241 base_path = os.path.abspath(os.path.dirname(__file__))
241 base_path = os.path.abspath(os.path.dirname(__file__))
242 icon_path = os.path.join(base_path, 'resources', 'icon', 'IPythonConsole.svg')
242 icon_path = os.path.join(base_path, 'resources', 'icon', 'IPythonConsole.svg')
243 self.app.icon = QtGui.QIcon(icon_path)
243 self.app.icon = QtGui.QIcon(icon_path)
244 QtGui.QApplication.setWindowIcon(self.app.icon)
244 QtGui.QApplication.setWindowIcon(self.app.icon)
245
245
246 try:
246 try:
247 ip = self.config.KernelManager.ip
247 ip = self.config.KernelManager.ip
248 except AttributeError:
248 except AttributeError:
249 ip = LOCALHOST
249 ip = LOCALHOST
250 local_kernel = (not self.existing) or ip in LOCAL_IPS
250 local_kernel = (not self.existing) or ip in LOCAL_IPS
251 self.widget = self.widget_factory(config=self.config,
251 self.widget = self.widget_factory(config=self.config,
252 local_kernel=local_kernel)
252 local_kernel=local_kernel)
253 self.init_colors(self.widget)
253 self.init_colors(self.widget)
254 self.widget._existing = self.existing
254 self.widget._existing = self.existing
255 self.widget._may_close = not self.existing
255 self.widget._may_close = not self.existing
256 self.widget._confirm_exit = self.confirm_exit
256 self.widget._confirm_exit = self.confirm_exit
257
257
258 self.widget.kernel_manager = self.kernel_manager
258 self.widget.kernel_manager = self.kernel_manager
259 self.window = MainWindow(self.app,
259 self.window = MainWindow(self.app,
260 confirm_exit=self.confirm_exit,
260 confirm_exit=self.confirm_exit,
261 new_frontend_factory=self.new_frontend_master,
261 new_frontend_factory=self.new_frontend_master,
262 slave_frontend_factory=self.new_frontend_slave,
262 slave_frontend_factory=self.new_frontend_slave,
263 )
263 )
264 self.window.log = self.log
264 self.window.log = self.log
265 self.window.add_tab_with_frontend(self.widget)
265 self.window.add_tab_with_frontend(self.widget)
266 self.window.init_menu_bar()
266 self.window.init_menu_bar()
267
267
268 self.window.setWindowTitle('IPython')
268 self.window.setWindowTitle('IPython')
269
269
270 def init_colors(self, widget):
270 def init_colors(self, widget):
271 """Configure the coloring of the widget"""
271 """Configure the coloring of the widget"""
272 # Note: This will be dramatically simplified when colors
272 # Note: This will be dramatically simplified when colors
273 # are removed from the backend.
273 # are removed from the backend.
274
274
275 # parse the colors arg down to current known labels
275 # parse the colors arg down to current known labels
276 try:
276 try:
277 colors = self.config.ZMQInteractiveShell.colors
277 colors = self.config.ZMQInteractiveShell.colors
278 except AttributeError:
278 except AttributeError:
279 colors = None
279 colors = None
280 try:
280 try:
281 style = self.config.IPythonWidget.syntax_style
281 style = self.config.IPythonWidget.syntax_style
282 except AttributeError:
282 except AttributeError:
283 style = None
283 style = None
284 try:
284 try:
285 sheet = self.config.IPythonWidget.style_sheet
285 sheet = self.config.IPythonWidget.style_sheet
286 except AttributeError:
286 except AttributeError:
287 sheet = None
287 sheet = None
288
288
289 # find the value for colors:
289 # find the value for colors:
290 if colors:
290 if colors:
291 colors=colors.lower()
291 colors=colors.lower()
292 if colors in ('lightbg', 'light'):
292 if colors in ('lightbg', 'light'):
293 colors='lightbg'
293 colors='lightbg'
294 elif colors in ('dark', 'linux'):
294 elif colors in ('dark', 'linux'):
295 colors='linux'
295 colors='linux'
296 else:
296 else:
297 colors='nocolor'
297 colors='nocolor'
298 elif style:
298 elif style:
299 if style=='bw':
299 if style=='bw':
300 colors='nocolor'
300 colors='nocolor'
301 elif styles.dark_style(style):
301 elif styles.dark_style(style):
302 colors='linux'
302 colors='linux'
303 else:
303 else:
304 colors='lightbg'
304 colors='lightbg'
305 else:
305 else:
306 colors=None
306 colors=None
307
307
308 # Configure the style
308 # Configure the style
309 if style:
309 if style:
310 widget.style_sheet = styles.sheet_from_template(style, colors)
310 widget.style_sheet = styles.sheet_from_template(style, colors)
311 widget.syntax_style = style
311 widget.syntax_style = style
312 widget._syntax_style_changed()
312 widget._syntax_style_changed()
313 widget._style_sheet_changed()
313 widget._style_sheet_changed()
314 elif colors:
314 elif colors:
315 # use a default dark/light/bw style
315 # use a default dark/light/bw style
316 widget.set_default_style(colors=colors)
316 widget.set_default_style(colors=colors)
317
317
318 if self.stylesheet:
318 if self.stylesheet:
319 # we got an explicit stylesheet
319 # we got an explicit stylesheet
320 if os.path.isfile(self.stylesheet):
320 if os.path.isfile(self.stylesheet):
321 with open(self.stylesheet) as f:
321 with open(self.stylesheet) as f:
322 sheet = f.read()
322 sheet = f.read()
323 else:
323 else:
324 raise IOError("Stylesheet %r not found." % self.stylesheet)
324 raise IOError("Stylesheet %r not found." % self.stylesheet)
325 if sheet:
325 if sheet:
326 widget.style_sheet = sheet
326 widget.style_sheet = sheet
327 widget._style_sheet_changed()
327 widget._style_sheet_changed()
328
328
329
329
330 def init_signal(self):
330 def init_signal(self):
331 """allow clean shutdown on sigint"""
331 """allow clean shutdown on sigint"""
332 signal.signal(signal.SIGINT, lambda sig, frame: self.exit(-2))
332 signal.signal(signal.SIGINT, lambda sig, frame: self.exit(-2))
333 # need a timer, so that QApplication doesn't block until a real
333 # need a timer, so that QApplication doesn't block until a real
334 # Qt event fires (can require mouse movement)
334 # Qt event fires (can require mouse movement)
335 # timer trick from http://stackoverflow.com/q/4938723/938949
335 # timer trick from http://stackoverflow.com/q/4938723/938949
336 timer = QtCore.QTimer()
336 timer = QtCore.QTimer()
337 # Let the interpreter run each 200 ms:
337 # Let the interpreter run each 200 ms:
338 timer.timeout.connect(lambda: None)
338 timer.timeout.connect(lambda: None)
339 timer.start(200)
339 timer.start(200)
340 # hold onto ref, so the timer doesn't get cleaned up
340 # hold onto ref, so the timer doesn't get cleaned up
341 self._sigint_timer = timer
341 self._sigint_timer = timer
342
342
343 @catch_config_error
343 @catch_config_error
344 def initialize(self, argv=None):
344 def initialize(self, argv=None):
345 super(IPythonQtConsoleApp, self).initialize(argv)
345 super(IPythonQtConsoleApp, self).initialize(argv)
346 IPythonConsoleApp.initialize(self,argv)
346 IPythonConsoleApp.initialize(self,argv)
347 self.init_qt_elements()
347 self.init_qt_elements()
348 self.init_signal()
348 self.init_signal()
349
349
350 def start(self):
350 def start(self):
351
351
352 # draw the window
352 # draw the window
353 self.window.show()
353 self.window.show()
354 self.window.raise_()
354 self.window.raise_()
355
355
356 # Start the application main loop.
356 # Start the application main loop.
357 self.app.exec_()
357 self.app.exec_()
358
358
359 #-----------------------------------------------------------------------------
359 #-----------------------------------------------------------------------------
360 # Main entry point
360 # Main entry point
361 #-----------------------------------------------------------------------------
361 #-----------------------------------------------------------------------------
362
362
363 def main():
363 def main():
364 app = IPythonQtConsoleApp()
364 app = IPythonQtConsoleApp()
365 app.initialize()
365 app.initialize()
366 app.start()
366 app.start()
367
367
368
368
369 if __name__ == '__main__':
369 if __name__ == '__main__':
370 main()
370 main()
@@ -1,10 +1,3 b''
1 """IPython.kernel has been replaced by IPython.parallel.
1 """IPython kernel bases"""
2
2
3 The previous version of IPython's parallel library was located at this
3 from .util import *
4 location (IPython.kernel). It has been moved to the IPython.parallel
5 subpackage and has been refactored to use zeromq/pyzmq instead of twisted.
6
7 Please see INSERT URL for further details.
8 """
9
10 raise ImportError(__doc__)
1 NO CONTENT: file renamed from IPython/utils/tests/test_kernel.py to IPython/kernel/tests/test_util.py
NO CONTENT: file renamed from IPython/utils/tests/test_kernel.py to IPython/kernel/tests/test_util.py
1 NO CONTENT: file renamed from IPython/utils/kernel.py to IPython/kernel/util.py
NO CONTENT: file renamed from IPython/utils/kernel.py to IPython/kernel/util.py
@@ -1,12 +1,12 b''
1 """[DEPRECATED] Utilities for connecting to kernels
1 """[DEPRECATED] Utilities for connecting to kernels
2
2
3 Moved to IPython.utils.kernel, where it always belonged.
3 Moved to IPython.kernel.util
4 """
4 """
5
5
6 import warnings
6 import warnings
7 warnings.warn("IPython.lib.kernel moved to IPython.utils.kernel in IPython 0.14",
7 warnings.warn("IPython.lib.kernel moved to IPython.kernel in IPython 0.14",
8 DeprecationWarning
8 DeprecationWarning
9 )
9 )
10
10
11 from IPython.utils.kernel import *
11 from IPython.kernel.util import *
12
12
@@ -1,372 +1,372 b''
1 """An Application for launching a kernel
1 """An Application for launching a kernel
2
2
3 Authors
3 Authors
4 -------
4 -------
5 * MinRK
5 * MinRK
6 """
6 """
7 #-----------------------------------------------------------------------------
7 #-----------------------------------------------------------------------------
8 # Copyright (C) 2011 The IPython Development Team
8 # Copyright (C) 2011 The IPython Development Team
9 #
9 #
10 # Distributed under the terms of the BSD License. The full license is in
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING.txt, distributed as part of this software.
11 # the file COPYING.txt, distributed as part of this software.
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13
13
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15 # Imports
15 # Imports
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17
17
18 # Standard library imports
18 # Standard library imports
19 import atexit
19 import atexit
20 import json
20 import json
21 import os
21 import os
22 import sys
22 import sys
23 import signal
23 import signal
24
24
25 # System library imports
25 # System library imports
26 import zmq
26 import zmq
27 from zmq.eventloop import ioloop
27 from zmq.eventloop import ioloop
28
28
29 # IPython imports
29 # IPython imports
30 from IPython.core.ultratb import FormattedTB
30 from IPython.core.ultratb import FormattedTB
31 from IPython.core.application import (
31 from IPython.core.application import (
32 BaseIPythonApplication, base_flags, base_aliases, catch_config_error
32 BaseIPythonApplication, base_flags, base_aliases, catch_config_error
33 )
33 )
34 from IPython.utils import io
34 from IPython.utils import io
35 from IPython.utils.localinterfaces import LOCALHOST
35 from IPython.utils.localinterfaces import LOCALHOST
36 from IPython.utils.path import filefind
36 from IPython.utils.path import filefind
37 from IPython.utils.py3compat import str_to_bytes
37 from IPython.utils.py3compat import str_to_bytes
38 from IPython.utils.traitlets import (
38 from IPython.utils.traitlets import (
39 Any, Instance, Dict, Unicode, Integer, Bool, CaselessStrEnum,
39 Any, Instance, Dict, Unicode, Integer, Bool, CaselessStrEnum,
40 DottedObjectName,
40 DottedObjectName,
41 )
41 )
42 from IPython.utils.importstring import import_item
42 from IPython.utils.importstring import import_item
43 from IPython.utils.kernel import write_connection_file
43 from IPython.kernel import write_connection_file
44 # local imports
44 # local imports
45 from IPython.zmq.heartbeat import Heartbeat
45 from IPython.zmq.heartbeat import Heartbeat
46 from IPython.zmq.parentpoller import ParentPollerUnix, ParentPollerWindows
46 from IPython.zmq.parentpoller import ParentPollerUnix, ParentPollerWindows
47 from IPython.zmq.session import (
47 from IPython.zmq.session import (
48 Session, session_flags, session_aliases, default_secure,
48 Session, session_flags, session_aliases, default_secure,
49 )
49 )
50
50
51
51
52 #-----------------------------------------------------------------------------
52 #-----------------------------------------------------------------------------
53 # Flags and Aliases
53 # Flags and Aliases
54 #-----------------------------------------------------------------------------
54 #-----------------------------------------------------------------------------
55
55
56 kernel_aliases = dict(base_aliases)
56 kernel_aliases = dict(base_aliases)
57 kernel_aliases.update({
57 kernel_aliases.update({
58 'ip' : 'KernelApp.ip',
58 'ip' : 'KernelApp.ip',
59 'hb' : 'KernelApp.hb_port',
59 'hb' : 'KernelApp.hb_port',
60 'shell' : 'KernelApp.shell_port',
60 'shell' : 'KernelApp.shell_port',
61 'iopub' : 'KernelApp.iopub_port',
61 'iopub' : 'KernelApp.iopub_port',
62 'stdin' : 'KernelApp.stdin_port',
62 'stdin' : 'KernelApp.stdin_port',
63 'f' : 'KernelApp.connection_file',
63 'f' : 'KernelApp.connection_file',
64 'parent': 'KernelApp.parent',
64 'parent': 'KernelApp.parent',
65 'transport': 'KernelApp.transport',
65 'transport': 'KernelApp.transport',
66 })
66 })
67 if sys.platform.startswith('win'):
67 if sys.platform.startswith('win'):
68 kernel_aliases['interrupt'] = 'KernelApp.interrupt'
68 kernel_aliases['interrupt'] = 'KernelApp.interrupt'
69
69
70 kernel_flags = dict(base_flags)
70 kernel_flags = dict(base_flags)
71 kernel_flags.update({
71 kernel_flags.update({
72 'no-stdout' : (
72 'no-stdout' : (
73 {'KernelApp' : {'no_stdout' : True}},
73 {'KernelApp' : {'no_stdout' : True}},
74 "redirect stdout to the null device"),
74 "redirect stdout to the null device"),
75 'no-stderr' : (
75 'no-stderr' : (
76 {'KernelApp' : {'no_stderr' : True}},
76 {'KernelApp' : {'no_stderr' : True}},
77 "redirect stderr to the null device"),
77 "redirect stderr to the null device"),
78 })
78 })
79
79
80 # inherit flags&aliases for Sessions
80 # inherit flags&aliases for Sessions
81 kernel_aliases.update(session_aliases)
81 kernel_aliases.update(session_aliases)
82 kernel_flags.update(session_flags)
82 kernel_flags.update(session_flags)
83
83
84
84
85
85
86 #-----------------------------------------------------------------------------
86 #-----------------------------------------------------------------------------
87 # Application class for starting a Kernel
87 # Application class for starting a Kernel
88 #-----------------------------------------------------------------------------
88 #-----------------------------------------------------------------------------
89
89
90 class KernelApp(BaseIPythonApplication):
90 class KernelApp(BaseIPythonApplication):
91 name='ipkernel'
91 name='ipkernel'
92 aliases = Dict(kernel_aliases)
92 aliases = Dict(kernel_aliases)
93 flags = Dict(kernel_flags)
93 flags = Dict(kernel_flags)
94 classes = [Session]
94 classes = [Session]
95 # the kernel class, as an importstring
95 # the kernel class, as an importstring
96 kernel_class = DottedObjectName('IPython.zmq.ipkernel.Kernel')
96 kernel_class = DottedObjectName('IPython.zmq.ipkernel.Kernel')
97 kernel = Any()
97 kernel = Any()
98 poller = Any() # don't restrict this even though current pollers are all Threads
98 poller = Any() # don't restrict this even though current pollers are all Threads
99 heartbeat = Instance(Heartbeat)
99 heartbeat = Instance(Heartbeat)
100 session = Instance('IPython.zmq.session.Session')
100 session = Instance('IPython.zmq.session.Session')
101 ports = Dict()
101 ports = Dict()
102
102
103 # inherit config file name from parent:
103 # inherit config file name from parent:
104 parent_appname = Unicode(config=True)
104 parent_appname = Unicode(config=True)
105 def _parent_appname_changed(self, name, old, new):
105 def _parent_appname_changed(self, name, old, new):
106 if self.config_file_specified:
106 if self.config_file_specified:
107 # it was manually specified, ignore
107 # it was manually specified, ignore
108 return
108 return
109 self.config_file_name = new.replace('-','_') + u'_config.py'
109 self.config_file_name = new.replace('-','_') + u'_config.py'
110 # don't let this count as specifying the config file
110 # don't let this count as specifying the config file
111 self.config_file_specified = False
111 self.config_file_specified = False
112
112
113 # connection info:
113 # connection info:
114 transport = CaselessStrEnum(['tcp', 'ipc'], default_value='tcp', config=True)
114 transport = CaselessStrEnum(['tcp', 'ipc'], default_value='tcp', config=True)
115 ip = Unicode(config=True,
115 ip = Unicode(config=True,
116 help="Set the IP or interface on which the kernel will listen.")
116 help="Set the IP or interface on which the kernel will listen.")
117 def _ip_default(self):
117 def _ip_default(self):
118 if self.transport == 'ipc':
118 if self.transport == 'ipc':
119 if self.connection_file:
119 if self.connection_file:
120 return os.path.splitext(self.abs_connection_file)[0] + '-ipc'
120 return os.path.splitext(self.abs_connection_file)[0] + '-ipc'
121 else:
121 else:
122 return 'kernel-ipc'
122 return 'kernel-ipc'
123 else:
123 else:
124 return LOCALHOST
124 return LOCALHOST
125 hb_port = Integer(0, config=True, help="set the heartbeat port [default: random]")
125 hb_port = Integer(0, config=True, help="set the heartbeat port [default: random]")
126 shell_port = Integer(0, config=True, help="set the shell (ROUTER) port [default: random]")
126 shell_port = Integer(0, config=True, help="set the shell (ROUTER) port [default: random]")
127 iopub_port = Integer(0, config=True, help="set the iopub (PUB) port [default: random]")
127 iopub_port = Integer(0, config=True, help="set the iopub (PUB) port [default: random]")
128 stdin_port = Integer(0, config=True, help="set the stdin (DEALER) port [default: random]")
128 stdin_port = Integer(0, config=True, help="set the stdin (DEALER) port [default: random]")
129 connection_file = Unicode('', config=True,
129 connection_file = Unicode('', config=True,
130 help="""JSON file in which to store connection info [default: kernel-<pid>.json]
130 help="""JSON file in which to store connection info [default: kernel-<pid>.json]
131
131
132 This file will contain the IP, ports, and authentication key needed to connect
132 This file will contain the IP, ports, and authentication key needed to connect
133 clients to this kernel. By default, this file will be created in the security dir
133 clients to this kernel. By default, this file will be created in the security dir
134 of the current profile, but can be specified by absolute path.
134 of the current profile, but can be specified by absolute path.
135 """)
135 """)
136 @property
136 @property
137 def abs_connection_file(self):
137 def abs_connection_file(self):
138 if os.path.basename(self.connection_file) == self.connection_file:
138 if os.path.basename(self.connection_file) == self.connection_file:
139 return os.path.join(self.profile_dir.security_dir, self.connection_file)
139 return os.path.join(self.profile_dir.security_dir, self.connection_file)
140 else:
140 else:
141 return self.connection_file
141 return self.connection_file
142
142
143
143
144 # streams, etc.
144 # streams, etc.
145 no_stdout = Bool(False, config=True, help="redirect stdout to the null device")
145 no_stdout = Bool(False, config=True, help="redirect stdout to the null device")
146 no_stderr = Bool(False, config=True, help="redirect stderr to the null device")
146 no_stderr = Bool(False, config=True, help="redirect stderr to the null device")
147 outstream_class = DottedObjectName('IPython.zmq.iostream.OutStream',
147 outstream_class = DottedObjectName('IPython.zmq.iostream.OutStream',
148 config=True, help="The importstring for the OutStream factory")
148 config=True, help="The importstring for the OutStream factory")
149 displayhook_class = DottedObjectName('IPython.zmq.displayhook.ZMQDisplayHook',
149 displayhook_class = DottedObjectName('IPython.zmq.displayhook.ZMQDisplayHook',
150 config=True, help="The importstring for the DisplayHook factory")
150 config=True, help="The importstring for the DisplayHook factory")
151
151
152 # polling
152 # polling
153 parent = Integer(0, config=True,
153 parent = Integer(0, config=True,
154 help="""kill this process if its parent dies. On Windows, the argument
154 help="""kill this process if its parent dies. On Windows, the argument
155 specifies the HANDLE of the parent process, otherwise it is simply boolean.
155 specifies the HANDLE of the parent process, otherwise it is simply boolean.
156 """)
156 """)
157 interrupt = Integer(0, config=True,
157 interrupt = Integer(0, config=True,
158 help="""ONLY USED ON WINDOWS
158 help="""ONLY USED ON WINDOWS
159 Interrupt this process when the parent is signaled.
159 Interrupt this process when the parent is signaled.
160 """)
160 """)
161
161
162 def init_crash_handler(self):
162 def init_crash_handler(self):
163 # Install minimal exception handling
163 # Install minimal exception handling
164 sys.excepthook = FormattedTB(mode='Verbose', color_scheme='NoColor',
164 sys.excepthook = FormattedTB(mode='Verbose', color_scheme='NoColor',
165 ostream=sys.__stdout__)
165 ostream=sys.__stdout__)
166
166
167 def init_poller(self):
167 def init_poller(self):
168 if sys.platform == 'win32':
168 if sys.platform == 'win32':
169 if self.interrupt or self.parent:
169 if self.interrupt or self.parent:
170 self.poller = ParentPollerWindows(self.interrupt, self.parent)
170 self.poller = ParentPollerWindows(self.interrupt, self.parent)
171 elif self.parent:
171 elif self.parent:
172 self.poller = ParentPollerUnix()
172 self.poller = ParentPollerUnix()
173
173
174 def _bind_socket(self, s, port):
174 def _bind_socket(self, s, port):
175 iface = '%s://%s' % (self.transport, self.ip)
175 iface = '%s://%s' % (self.transport, self.ip)
176 if self.transport == 'tcp':
176 if self.transport == 'tcp':
177 if port <= 0:
177 if port <= 0:
178 port = s.bind_to_random_port(iface)
178 port = s.bind_to_random_port(iface)
179 else:
179 else:
180 s.bind("tcp://%s:%i" % (self.ip, port))
180 s.bind("tcp://%s:%i" % (self.ip, port))
181 elif self.transport == 'ipc':
181 elif self.transport == 'ipc':
182 if port <= 0:
182 if port <= 0:
183 port = 1
183 port = 1
184 path = "%s-%i" % (self.ip, port)
184 path = "%s-%i" % (self.ip, port)
185 while os.path.exists(path):
185 while os.path.exists(path):
186 port = port + 1
186 port = port + 1
187 path = "%s-%i" % (self.ip, port)
187 path = "%s-%i" % (self.ip, port)
188 else:
188 else:
189 path = "%s-%i" % (self.ip, port)
189 path = "%s-%i" % (self.ip, port)
190 s.bind("ipc://%s" % path)
190 s.bind("ipc://%s" % path)
191 return port
191 return port
192
192
193 def load_connection_file(self):
193 def load_connection_file(self):
194 """load ip/port/hmac config from JSON connection file"""
194 """load ip/port/hmac config from JSON connection file"""
195 try:
195 try:
196 fname = filefind(self.connection_file, ['.', self.profile_dir.security_dir])
196 fname = filefind(self.connection_file, ['.', self.profile_dir.security_dir])
197 except IOError:
197 except IOError:
198 self.log.debug("Connection file not found: %s", self.connection_file)
198 self.log.debug("Connection file not found: %s", self.connection_file)
199 # This means I own it, so I will clean it up:
199 # This means I own it, so I will clean it up:
200 atexit.register(self.cleanup_connection_file)
200 atexit.register(self.cleanup_connection_file)
201 return
201 return
202 self.log.debug(u"Loading connection file %s", fname)
202 self.log.debug(u"Loading connection file %s", fname)
203 with open(fname) as f:
203 with open(fname) as f:
204 s = f.read()
204 s = f.read()
205 cfg = json.loads(s)
205 cfg = json.loads(s)
206 self.transport = cfg.get('transport', self.transport)
206 self.transport = cfg.get('transport', self.transport)
207 if self.ip == self._ip_default() and 'ip' in cfg:
207 if self.ip == self._ip_default() and 'ip' in cfg:
208 # not overridden by config or cl_args
208 # not overridden by config or cl_args
209 self.ip = cfg['ip']
209 self.ip = cfg['ip']
210 for channel in ('hb', 'shell', 'iopub', 'stdin'):
210 for channel in ('hb', 'shell', 'iopub', 'stdin'):
211 name = channel + '_port'
211 name = channel + '_port'
212 if getattr(self, name) == 0 and name in cfg:
212 if getattr(self, name) == 0 and name in cfg:
213 # not overridden by config or cl_args
213 # not overridden by config or cl_args
214 setattr(self, name, cfg[name])
214 setattr(self, name, cfg[name])
215 if 'key' in cfg:
215 if 'key' in cfg:
216 self.config.Session.key = str_to_bytes(cfg['key'])
216 self.config.Session.key = str_to_bytes(cfg['key'])
217
217
218 def write_connection_file(self):
218 def write_connection_file(self):
219 """write connection info to JSON file"""
219 """write connection info to JSON file"""
220 cf = self.abs_connection_file
220 cf = self.abs_connection_file
221 self.log.debug("Writing connection file: %s", cf)
221 self.log.debug("Writing connection file: %s", cf)
222 write_connection_file(cf, ip=self.ip, key=self.session.key, transport=self.transport,
222 write_connection_file(cf, ip=self.ip, key=self.session.key, transport=self.transport,
223 shell_port=self.shell_port, stdin_port=self.stdin_port, hb_port=self.hb_port,
223 shell_port=self.shell_port, stdin_port=self.stdin_port, hb_port=self.hb_port,
224 iopub_port=self.iopub_port)
224 iopub_port=self.iopub_port)
225
225
226 def cleanup_connection_file(self):
226 def cleanup_connection_file(self):
227 cf = self.abs_connection_file
227 cf = self.abs_connection_file
228 self.log.debug("Cleaning up connection file: %s", cf)
228 self.log.debug("Cleaning up connection file: %s", cf)
229 try:
229 try:
230 os.remove(cf)
230 os.remove(cf)
231 except (IOError, OSError):
231 except (IOError, OSError):
232 pass
232 pass
233
233
234 self.cleanup_ipc_files()
234 self.cleanup_ipc_files()
235
235
236 def cleanup_ipc_files(self):
236 def cleanup_ipc_files(self):
237 """cleanup ipc files if we wrote them"""
237 """cleanup ipc files if we wrote them"""
238 if self.transport != 'ipc':
238 if self.transport != 'ipc':
239 return
239 return
240 for port in (self.shell_port, self.iopub_port, self.stdin_port, self.hb_port):
240 for port in (self.shell_port, self.iopub_port, self.stdin_port, self.hb_port):
241 ipcfile = "%s-%i" % (self.ip, port)
241 ipcfile = "%s-%i" % (self.ip, port)
242 try:
242 try:
243 os.remove(ipcfile)
243 os.remove(ipcfile)
244 except (IOError, OSError):
244 except (IOError, OSError):
245 pass
245 pass
246
246
247 def init_connection_file(self):
247 def init_connection_file(self):
248 if not self.connection_file:
248 if not self.connection_file:
249 self.connection_file = "kernel-%s.json"%os.getpid()
249 self.connection_file = "kernel-%s.json"%os.getpid()
250 try:
250 try:
251 self.load_connection_file()
251 self.load_connection_file()
252 except Exception:
252 except Exception:
253 self.log.error("Failed to load connection file: %r", self.connection_file, exc_info=True)
253 self.log.error("Failed to load connection file: %r", self.connection_file, exc_info=True)
254 self.exit(1)
254 self.exit(1)
255
255
256 def init_sockets(self):
256 def init_sockets(self):
257 # Create a context, a session, and the kernel sockets.
257 # Create a context, a session, and the kernel sockets.
258 self.log.info("Starting the kernel at pid: %i", os.getpid())
258 self.log.info("Starting the kernel at pid: %i", os.getpid())
259 context = zmq.Context.instance()
259 context = zmq.Context.instance()
260 # Uncomment this to try closing the context.
260 # Uncomment this to try closing the context.
261 # atexit.register(context.term)
261 # atexit.register(context.term)
262
262
263 self.shell_socket = context.socket(zmq.ROUTER)
263 self.shell_socket = context.socket(zmq.ROUTER)
264 self.shell_port = self._bind_socket(self.shell_socket, self.shell_port)
264 self.shell_port = self._bind_socket(self.shell_socket, self.shell_port)
265 self.log.debug("shell ROUTER Channel on port: %i"%self.shell_port)
265 self.log.debug("shell ROUTER Channel on port: %i"%self.shell_port)
266
266
267 self.iopub_socket = context.socket(zmq.PUB)
267 self.iopub_socket = context.socket(zmq.PUB)
268 self.iopub_port = self._bind_socket(self.iopub_socket, self.iopub_port)
268 self.iopub_port = self._bind_socket(self.iopub_socket, self.iopub_port)
269 self.log.debug("iopub PUB Channel on port: %i"%self.iopub_port)
269 self.log.debug("iopub PUB Channel on port: %i"%self.iopub_port)
270
270
271 self.stdin_socket = context.socket(zmq.ROUTER)
271 self.stdin_socket = context.socket(zmq.ROUTER)
272 self.stdin_port = self._bind_socket(self.stdin_socket, self.stdin_port)
272 self.stdin_port = self._bind_socket(self.stdin_socket, self.stdin_port)
273 self.log.debug("stdin ROUTER Channel on port: %i"%self.stdin_port)
273 self.log.debug("stdin ROUTER Channel on port: %i"%self.stdin_port)
274
274
275 def init_heartbeat(self):
275 def init_heartbeat(self):
276 """start the heart beating"""
276 """start the heart beating"""
277 # heartbeat doesn't share context, because it mustn't be blocked
277 # heartbeat doesn't share context, because it mustn't be blocked
278 # by the GIL, which is accessed by libzmq when freeing zero-copy messages
278 # by the GIL, which is accessed by libzmq when freeing zero-copy messages
279 hb_ctx = zmq.Context()
279 hb_ctx = zmq.Context()
280 self.heartbeat = Heartbeat(hb_ctx, (self.transport, self.ip, self.hb_port))
280 self.heartbeat = Heartbeat(hb_ctx, (self.transport, self.ip, self.hb_port))
281 self.hb_port = self.heartbeat.port
281 self.hb_port = self.heartbeat.port
282 self.log.debug("Heartbeat REP Channel on port: %i"%self.hb_port)
282 self.log.debug("Heartbeat REP Channel on port: %i"%self.hb_port)
283 self.heartbeat.start()
283 self.heartbeat.start()
284
284
285 # Helper to make it easier to connect to an existing kernel.
285 # Helper to make it easier to connect to an existing kernel.
286 # set log-level to critical, to make sure it is output
286 # set log-level to critical, to make sure it is output
287 self.log.critical("To connect another client to this kernel, use:")
287 self.log.critical("To connect another client to this kernel, use:")
288
288
289 def log_connection_info(self):
289 def log_connection_info(self):
290 """display connection info, and store ports"""
290 """display connection info, and store ports"""
291 basename = os.path.basename(self.connection_file)
291 basename = os.path.basename(self.connection_file)
292 if basename == self.connection_file or \
292 if basename == self.connection_file or \
293 os.path.dirname(self.connection_file) == self.profile_dir.security_dir:
293 os.path.dirname(self.connection_file) == self.profile_dir.security_dir:
294 # use shortname
294 # use shortname
295 tail = basename
295 tail = basename
296 if self.profile != 'default':
296 if self.profile != 'default':
297 tail += " --profile %s" % self.profile
297 tail += " --profile %s" % self.profile
298 else:
298 else:
299 tail = self.connection_file
299 tail = self.connection_file
300 self.log.critical("--existing %s", tail)
300 self.log.critical("--existing %s", tail)
301
301
302
302
303 self.ports = dict(shell=self.shell_port, iopub=self.iopub_port,
303 self.ports = dict(shell=self.shell_port, iopub=self.iopub_port,
304 stdin=self.stdin_port, hb=self.hb_port)
304 stdin=self.stdin_port, hb=self.hb_port)
305
305
306 def init_session(self):
306 def init_session(self):
307 """create our session object"""
307 """create our session object"""
308 default_secure(self.config)
308 default_secure(self.config)
309 self.session = Session(config=self.config, username=u'kernel')
309 self.session = Session(config=self.config, username=u'kernel')
310
310
311 def init_blackhole(self):
311 def init_blackhole(self):
312 """redirects stdout/stderr to devnull if necessary"""
312 """redirects stdout/stderr to devnull if necessary"""
313 if self.no_stdout or self.no_stderr:
313 if self.no_stdout or self.no_stderr:
314 blackhole = open(os.devnull, 'w')
314 blackhole = open(os.devnull, 'w')
315 if self.no_stdout:
315 if self.no_stdout:
316 sys.stdout = sys.__stdout__ = blackhole
316 sys.stdout = sys.__stdout__ = blackhole
317 if self.no_stderr:
317 if self.no_stderr:
318 sys.stderr = sys.__stderr__ = blackhole
318 sys.stderr = sys.__stderr__ = blackhole
319
319
320 def init_io(self):
320 def init_io(self):
321 """Redirect input streams and set a display hook."""
321 """Redirect input streams and set a display hook."""
322 if self.outstream_class:
322 if self.outstream_class:
323 outstream_factory = import_item(str(self.outstream_class))
323 outstream_factory = import_item(str(self.outstream_class))
324 sys.stdout = outstream_factory(self.session, self.iopub_socket, u'stdout')
324 sys.stdout = outstream_factory(self.session, self.iopub_socket, u'stdout')
325 sys.stderr = outstream_factory(self.session, self.iopub_socket, u'stderr')
325 sys.stderr = outstream_factory(self.session, self.iopub_socket, u'stderr')
326 if self.displayhook_class:
326 if self.displayhook_class:
327 displayhook_factory = import_item(str(self.displayhook_class))
327 displayhook_factory = import_item(str(self.displayhook_class))
328 sys.displayhook = displayhook_factory(self.session, self.iopub_socket)
328 sys.displayhook = displayhook_factory(self.session, self.iopub_socket)
329
329
330 def init_signal(self):
330 def init_signal(self):
331 signal.signal(signal.SIGINT, signal.SIG_IGN)
331 signal.signal(signal.SIGINT, signal.SIG_IGN)
332
332
333 def init_kernel(self):
333 def init_kernel(self):
334 """Create the Kernel object itself"""
334 """Create the Kernel object itself"""
335 kernel_factory = import_item(str(self.kernel_class))
335 kernel_factory = import_item(str(self.kernel_class))
336 self.kernel = kernel_factory(config=self.config, session=self.session,
336 self.kernel = kernel_factory(config=self.config, session=self.session,
337 shell_socket=self.shell_socket,
337 shell_socket=self.shell_socket,
338 iopub_socket=self.iopub_socket,
338 iopub_socket=self.iopub_socket,
339 stdin_socket=self.stdin_socket,
339 stdin_socket=self.stdin_socket,
340 log=self.log
340 log=self.log
341 )
341 )
342 self.kernel.record_ports(self.ports)
342 self.kernel.record_ports(self.ports)
343
343
344 @catch_config_error
344 @catch_config_error
345 def initialize(self, argv=None):
345 def initialize(self, argv=None):
346 super(KernelApp, self).initialize(argv)
346 super(KernelApp, self).initialize(argv)
347 self.init_blackhole()
347 self.init_blackhole()
348 self.init_connection_file()
348 self.init_connection_file()
349 self.init_session()
349 self.init_session()
350 self.init_poller()
350 self.init_poller()
351 self.init_sockets()
351 self.init_sockets()
352 self.init_heartbeat()
352 self.init_heartbeat()
353 # writing/displaying connection info must be *after* init_sockets/heartbeat
353 # writing/displaying connection info must be *after* init_sockets/heartbeat
354 self.log_connection_info()
354 self.log_connection_info()
355 self.write_connection_file()
355 self.write_connection_file()
356 self.init_io()
356 self.init_io()
357 self.init_signal()
357 self.init_signal()
358 self.init_kernel()
358 self.init_kernel()
359 # flush stdout/stderr, so that anything written to these streams during
359 # flush stdout/stderr, so that anything written to these streams during
360 # initialization do not get associated with the first execution request
360 # initialization do not get associated with the first execution request
361 sys.stdout.flush()
361 sys.stdout.flush()
362 sys.stderr.flush()
362 sys.stderr.flush()
363
363
364 def start(self):
364 def start(self):
365 if self.poller is not None:
365 if self.poller is not None:
366 self.poller.start()
366 self.poller.start()
367 self.kernel.start()
367 self.kernel.start()
368 try:
368 try:
369 ioloop.IOLoop.instance().start()
369 ioloop.IOLoop.instance().start()
370 except KeyboardInterrupt:
370 except KeyboardInterrupt:
371 pass
371 pass
372
372
@@ -1,1129 +1,1129 b''
1 """Base classes to manage the interaction with a running kernel.
1 """Base classes to manage the interaction with a running kernel.
2
2
3 TODO
3 TODO
4 * Create logger to handle debugging and console messages.
4 * Create logger to handle debugging and console messages.
5 """
5 """
6
6
7 #-----------------------------------------------------------------------------
7 #-----------------------------------------------------------------------------
8 # Copyright (C) 2008-2011 The IPython Development Team
8 # Copyright (C) 2008-2011 The IPython Development Team
9 #
9 #
10 # Distributed under the terms of the BSD License. The full license is in
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
11 # the file COPYING, distributed as part of this software.
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13
13
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15 # Imports
15 # Imports
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17
17
18 # Standard library imports.
18 # Standard library imports.
19 import atexit
19 import atexit
20 import errno
20 import errno
21 import json
21 import json
22 from subprocess import Popen
22 from subprocess import Popen
23 import os
23 import os
24 import signal
24 import signal
25 import sys
25 import sys
26 from threading import Thread
26 from threading import Thread
27 import time
27 import time
28
28
29 # System library imports.
29 # System library imports.
30 import zmq
30 import zmq
31 # import ZMQError in top-level namespace, to avoid ugly attribute-error messages
31 # import ZMQError in top-level namespace, to avoid ugly attribute-error messages
32 # during garbage collection of threads at exit:
32 # during garbage collection of threads at exit:
33 from zmq import ZMQError
33 from zmq import ZMQError
34 from zmq.eventloop import ioloop, zmqstream
34 from zmq.eventloop import ioloop, zmqstream
35
35
36 # Local imports.
36 # Local imports.
37 from IPython.config.configurable import Configurable
37 from IPython.config.configurable import Configurable
38 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
38 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
39 from IPython.utils.traitlets import (
39 from IPython.utils.traitlets import (
40 Any, Instance, Type, Unicode, List, Integer, Bool, CaselessStrEnum
40 Any, Instance, Type, Unicode, List, Integer, Bool, CaselessStrEnum
41 )
41 )
42 from IPython.utils.py3compat import str_to_bytes
42 from IPython.utils.py3compat import str_to_bytes
43 from IPython.utils.kernel import (
43 from IPython.kernel import (
44 write_connection_file,
44 write_connection_file,
45 make_ipkernel_cmd,
45 make_ipkernel_cmd,
46 launch_kernel,
46 launch_kernel,
47 )
47 )
48 from session import Session
48 from session import Session
49 from IPython.zmq.kernelmanagerabc import (
49 from IPython.zmq.kernelmanagerabc import (
50 ShellChannelABC, IOPubChannelABC,
50 ShellChannelABC, IOPubChannelABC,
51 HBChannelABC, StdInChannelABC,
51 HBChannelABC, StdInChannelABC,
52 KernelManagerABC
52 KernelManagerABC
53 )
53 )
54
54
55
55
56 #-----------------------------------------------------------------------------
56 #-----------------------------------------------------------------------------
57 # Constants and exceptions
57 # Constants and exceptions
58 #-----------------------------------------------------------------------------
58 #-----------------------------------------------------------------------------
59
59
60 class InvalidPortNumber(Exception):
60 class InvalidPortNumber(Exception):
61 pass
61 pass
62
62
63 #-----------------------------------------------------------------------------
63 #-----------------------------------------------------------------------------
64 # Utility functions
64 # Utility functions
65 #-----------------------------------------------------------------------------
65 #-----------------------------------------------------------------------------
66
66
67 # some utilities to validate message structure, these might get moved elsewhere
67 # some utilities to validate message structure, these might get moved elsewhere
68 # if they prove to have more generic utility
68 # if they prove to have more generic utility
69
69
70 def validate_string_list(lst):
70 def validate_string_list(lst):
71 """Validate that the input is a list of strings.
71 """Validate that the input is a list of strings.
72
72
73 Raises ValueError if not."""
73 Raises ValueError if not."""
74 if not isinstance(lst, list):
74 if not isinstance(lst, list):
75 raise ValueError('input %r must be a list' % lst)
75 raise ValueError('input %r must be a list' % lst)
76 for x in lst:
76 for x in lst:
77 if not isinstance(x, basestring):
77 if not isinstance(x, basestring):
78 raise ValueError('element %r in list must be a string' % x)
78 raise ValueError('element %r in list must be a string' % x)
79
79
80
80
81 def validate_string_dict(dct):
81 def validate_string_dict(dct):
82 """Validate that the input is a dict with string keys and values.
82 """Validate that the input is a dict with string keys and values.
83
83
84 Raises ValueError if not."""
84 Raises ValueError if not."""
85 for k,v in dct.iteritems():
85 for k,v in dct.iteritems():
86 if not isinstance(k, basestring):
86 if not isinstance(k, basestring):
87 raise ValueError('key %r in dict must be a string' % k)
87 raise ValueError('key %r in dict must be a string' % k)
88 if not isinstance(v, basestring):
88 if not isinstance(v, basestring):
89 raise ValueError('value %r in dict must be a string' % v)
89 raise ValueError('value %r in dict must be a string' % v)
90
90
91
91
92 #-----------------------------------------------------------------------------
92 #-----------------------------------------------------------------------------
93 # ZMQ Socket Channel classes
93 # ZMQ Socket Channel classes
94 #-----------------------------------------------------------------------------
94 #-----------------------------------------------------------------------------
95
95
96 class ZMQSocketChannel(Thread):
96 class ZMQSocketChannel(Thread):
97 """The base class for the channels that use ZMQ sockets."""
97 """The base class for the channels that use ZMQ sockets."""
98 context = None
98 context = None
99 session = None
99 session = None
100 socket = None
100 socket = None
101 ioloop = None
101 ioloop = None
102 stream = None
102 stream = None
103 _address = None
103 _address = None
104 _exiting = False
104 _exiting = False
105
105
106 def __init__(self, context, session, address):
106 def __init__(self, context, session, address):
107 """Create a channel.
107 """Create a channel.
108
108
109 Parameters
109 Parameters
110 ----------
110 ----------
111 context : :class:`zmq.Context`
111 context : :class:`zmq.Context`
112 The ZMQ context to use.
112 The ZMQ context to use.
113 session : :class:`session.Session`
113 session : :class:`session.Session`
114 The session to use.
114 The session to use.
115 address : zmq url
115 address : zmq url
116 Standard (ip, port) tuple that the kernel is listening on.
116 Standard (ip, port) tuple that the kernel is listening on.
117 """
117 """
118 super(ZMQSocketChannel, self).__init__()
118 super(ZMQSocketChannel, self).__init__()
119 self.daemon = True
119 self.daemon = True
120
120
121 self.context = context
121 self.context = context
122 self.session = session
122 self.session = session
123 if isinstance(address, tuple):
123 if isinstance(address, tuple):
124 if address[1] == 0:
124 if address[1] == 0:
125 message = 'The port number for a channel cannot be 0.'
125 message = 'The port number for a channel cannot be 0.'
126 raise InvalidPortNumber(message)
126 raise InvalidPortNumber(message)
127 address = "tcp://%s:%i" % address
127 address = "tcp://%s:%i" % address
128 self._address = address
128 self._address = address
129 atexit.register(self._notice_exit)
129 atexit.register(self._notice_exit)
130
130
131 def _notice_exit(self):
131 def _notice_exit(self):
132 self._exiting = True
132 self._exiting = True
133
133
134 def _run_loop(self):
134 def _run_loop(self):
135 """Run my loop, ignoring EINTR events in the poller"""
135 """Run my loop, ignoring EINTR events in the poller"""
136 while True:
136 while True:
137 try:
137 try:
138 self.ioloop.start()
138 self.ioloop.start()
139 except ZMQError as e:
139 except ZMQError as e:
140 if e.errno == errno.EINTR:
140 if e.errno == errno.EINTR:
141 continue
141 continue
142 else:
142 else:
143 raise
143 raise
144 except Exception:
144 except Exception:
145 if self._exiting:
145 if self._exiting:
146 break
146 break
147 else:
147 else:
148 raise
148 raise
149 else:
149 else:
150 break
150 break
151
151
152 def stop(self):
152 def stop(self):
153 """Stop the channel's event loop and join its thread.
153 """Stop the channel's event loop and join its thread.
154
154
155 This calls :method:`Thread.join` and returns when the thread
155 This calls :method:`Thread.join` and returns when the thread
156 terminates. :class:`RuntimeError` will be raised if
156 terminates. :class:`RuntimeError` will be raised if
157 :method:`self.start` is called again.
157 :method:`self.start` is called again.
158 """
158 """
159 self.join()
159 self.join()
160
160
161 @property
161 @property
162 def address(self):
162 def address(self):
163 """Get the channel's address as a zmq url string.
163 """Get the channel's address as a zmq url string.
164
164
165 These URLS have the form: 'tcp://127.0.0.1:5555'.
165 These URLS have the form: 'tcp://127.0.0.1:5555'.
166 """
166 """
167 return self._address
167 return self._address
168
168
169 def _queue_send(self, msg):
169 def _queue_send(self, msg):
170 """Queue a message to be sent from the IOLoop's thread.
170 """Queue a message to be sent from the IOLoop's thread.
171
171
172 Parameters
172 Parameters
173 ----------
173 ----------
174 msg : message to send
174 msg : message to send
175
175
176 This is threadsafe, as it uses IOLoop.add_callback to give the loop's
176 This is threadsafe, as it uses IOLoop.add_callback to give the loop's
177 thread control of the action.
177 thread control of the action.
178 """
178 """
179 def thread_send():
179 def thread_send():
180 self.session.send(self.stream, msg)
180 self.session.send(self.stream, msg)
181 self.ioloop.add_callback(thread_send)
181 self.ioloop.add_callback(thread_send)
182
182
183 def _handle_recv(self, msg):
183 def _handle_recv(self, msg):
184 """Callback for stream.on_recv.
184 """Callback for stream.on_recv.
185
185
186 Unpacks message, and calls handlers with it.
186 Unpacks message, and calls handlers with it.
187 """
187 """
188 ident,smsg = self.session.feed_identities(msg)
188 ident,smsg = self.session.feed_identities(msg)
189 self.call_handlers(self.session.unserialize(smsg))
189 self.call_handlers(self.session.unserialize(smsg))
190
190
191
191
192
192
193 class ShellChannel(ZMQSocketChannel):
193 class ShellChannel(ZMQSocketChannel):
194 """The shell channel for issuing request/replies to the kernel."""
194 """The shell channel for issuing request/replies to the kernel."""
195
195
196 command_queue = None
196 command_queue = None
197 # flag for whether execute requests should be allowed to call raw_input:
197 # flag for whether execute requests should be allowed to call raw_input:
198 allow_stdin = True
198 allow_stdin = True
199
199
200 def __init__(self, context, session, address):
200 def __init__(self, context, session, address):
201 super(ShellChannel, self).__init__(context, session, address)
201 super(ShellChannel, self).__init__(context, session, address)
202 self.ioloop = ioloop.IOLoop()
202 self.ioloop = ioloop.IOLoop()
203
203
204 def run(self):
204 def run(self):
205 """The thread's main activity. Call start() instead."""
205 """The thread's main activity. Call start() instead."""
206 self.socket = self.context.socket(zmq.DEALER)
206 self.socket = self.context.socket(zmq.DEALER)
207 self.socket.setsockopt(zmq.IDENTITY, self.session.bsession)
207 self.socket.setsockopt(zmq.IDENTITY, self.session.bsession)
208 self.socket.connect(self.address)
208 self.socket.connect(self.address)
209 self.stream = zmqstream.ZMQStream(self.socket, self.ioloop)
209 self.stream = zmqstream.ZMQStream(self.socket, self.ioloop)
210 self.stream.on_recv(self._handle_recv)
210 self.stream.on_recv(self._handle_recv)
211 self._run_loop()
211 self._run_loop()
212 try:
212 try:
213 self.socket.close()
213 self.socket.close()
214 except:
214 except:
215 pass
215 pass
216
216
217 def stop(self):
217 def stop(self):
218 """Stop the channel's event loop and join its thread."""
218 """Stop the channel's event loop and join its thread."""
219 self.ioloop.stop()
219 self.ioloop.stop()
220 super(ShellChannel, self).stop()
220 super(ShellChannel, self).stop()
221
221
222 def call_handlers(self, msg):
222 def call_handlers(self, msg):
223 """This method is called in the ioloop thread when a message arrives.
223 """This method is called in the ioloop thread when a message arrives.
224
224
225 Subclasses should override this method to handle incoming messages.
225 Subclasses should override this method to handle incoming messages.
226 It is important to remember that this method is called in the thread
226 It is important to remember that this method is called in the thread
227 so that some logic must be done to ensure that the application leve
227 so that some logic must be done to ensure that the application leve
228 handlers are called in the application thread.
228 handlers are called in the application thread.
229 """
229 """
230 raise NotImplementedError('call_handlers must be defined in a subclass.')
230 raise NotImplementedError('call_handlers must be defined in a subclass.')
231
231
232 def execute(self, code, silent=False, store_history=True,
232 def execute(self, code, silent=False, store_history=True,
233 user_variables=None, user_expressions=None, allow_stdin=None):
233 user_variables=None, user_expressions=None, allow_stdin=None):
234 """Execute code in the kernel.
234 """Execute code in the kernel.
235
235
236 Parameters
236 Parameters
237 ----------
237 ----------
238 code : str
238 code : str
239 A string of Python code.
239 A string of Python code.
240
240
241 silent : bool, optional (default False)
241 silent : bool, optional (default False)
242 If set, the kernel will execute the code as quietly possible, and
242 If set, the kernel will execute the code as quietly possible, and
243 will force store_history to be False.
243 will force store_history to be False.
244
244
245 store_history : bool, optional (default True)
245 store_history : bool, optional (default True)
246 If set, the kernel will store command history. This is forced
246 If set, the kernel will store command history. This is forced
247 to be False if silent is True.
247 to be False if silent is True.
248
248
249 user_variables : list, optional
249 user_variables : list, optional
250 A list of variable names to pull from the user's namespace. They
250 A list of variable names to pull from the user's namespace. They
251 will come back as a dict with these names as keys and their
251 will come back as a dict with these names as keys and their
252 :func:`repr` as values.
252 :func:`repr` as values.
253
253
254 user_expressions : dict, optional
254 user_expressions : dict, optional
255 A dict mapping names to expressions to be evaluated in the user's
255 A dict mapping names to expressions to be evaluated in the user's
256 dict. The expression values are returned as strings formatted using
256 dict. The expression values are returned as strings formatted using
257 :func:`repr`.
257 :func:`repr`.
258
258
259 allow_stdin : bool, optional (default self.allow_stdin)
259 allow_stdin : bool, optional (default self.allow_stdin)
260 Flag for whether the kernel can send stdin requests to frontends.
260 Flag for whether the kernel can send stdin requests to frontends.
261
261
262 Some frontends (e.g. the Notebook) do not support stdin requests.
262 Some frontends (e.g. the Notebook) do not support stdin requests.
263 If raw_input is called from code executed from such a frontend, a
263 If raw_input is called from code executed from such a frontend, a
264 StdinNotImplementedError will be raised.
264 StdinNotImplementedError will be raised.
265
265
266 Returns
266 Returns
267 -------
267 -------
268 The msg_id of the message sent.
268 The msg_id of the message sent.
269 """
269 """
270 if user_variables is None:
270 if user_variables is None:
271 user_variables = []
271 user_variables = []
272 if user_expressions is None:
272 if user_expressions is None:
273 user_expressions = {}
273 user_expressions = {}
274 if allow_stdin is None:
274 if allow_stdin is None:
275 allow_stdin = self.allow_stdin
275 allow_stdin = self.allow_stdin
276
276
277
277
278 # Don't waste network traffic if inputs are invalid
278 # Don't waste network traffic if inputs are invalid
279 if not isinstance(code, basestring):
279 if not isinstance(code, basestring):
280 raise ValueError('code %r must be a string' % code)
280 raise ValueError('code %r must be a string' % code)
281 validate_string_list(user_variables)
281 validate_string_list(user_variables)
282 validate_string_dict(user_expressions)
282 validate_string_dict(user_expressions)
283
283
284 # Create class for content/msg creation. Related to, but possibly
284 # Create class for content/msg creation. Related to, but possibly
285 # not in Session.
285 # not in Session.
286 content = dict(code=code, silent=silent, store_history=store_history,
286 content = dict(code=code, silent=silent, store_history=store_history,
287 user_variables=user_variables,
287 user_variables=user_variables,
288 user_expressions=user_expressions,
288 user_expressions=user_expressions,
289 allow_stdin=allow_stdin,
289 allow_stdin=allow_stdin,
290 )
290 )
291 msg = self.session.msg('execute_request', content)
291 msg = self.session.msg('execute_request', content)
292 self._queue_send(msg)
292 self._queue_send(msg)
293 return msg['header']['msg_id']
293 return msg['header']['msg_id']
294
294
295 def complete(self, text, line, cursor_pos, block=None):
295 def complete(self, text, line, cursor_pos, block=None):
296 """Tab complete text in the kernel's namespace.
296 """Tab complete text in the kernel's namespace.
297
297
298 Parameters
298 Parameters
299 ----------
299 ----------
300 text : str
300 text : str
301 The text to complete.
301 The text to complete.
302 line : str
302 line : str
303 The full line of text that is the surrounding context for the
303 The full line of text that is the surrounding context for the
304 text to complete.
304 text to complete.
305 cursor_pos : int
305 cursor_pos : int
306 The position of the cursor in the line where the completion was
306 The position of the cursor in the line where the completion was
307 requested.
307 requested.
308 block : str, optional
308 block : str, optional
309 The full block of code in which the completion is being requested.
309 The full block of code in which the completion is being requested.
310
310
311 Returns
311 Returns
312 -------
312 -------
313 The msg_id of the message sent.
313 The msg_id of the message sent.
314 """
314 """
315 content = dict(text=text, line=line, block=block, cursor_pos=cursor_pos)
315 content = dict(text=text, line=line, block=block, cursor_pos=cursor_pos)
316 msg = self.session.msg('complete_request', content)
316 msg = self.session.msg('complete_request', content)
317 self._queue_send(msg)
317 self._queue_send(msg)
318 return msg['header']['msg_id']
318 return msg['header']['msg_id']
319
319
320 def object_info(self, oname, detail_level=0):
320 def object_info(self, oname, detail_level=0):
321 """Get metadata information about an object in the kernel's namespace.
321 """Get metadata information about an object in the kernel's namespace.
322
322
323 Parameters
323 Parameters
324 ----------
324 ----------
325 oname : str
325 oname : str
326 A string specifying the object name.
326 A string specifying the object name.
327 detail_level : int, optional
327 detail_level : int, optional
328 The level of detail for the introspection (0-2)
328 The level of detail for the introspection (0-2)
329
329
330 Returns
330 Returns
331 -------
331 -------
332 The msg_id of the message sent.
332 The msg_id of the message sent.
333 """
333 """
334 content = dict(oname=oname, detail_level=detail_level)
334 content = dict(oname=oname, detail_level=detail_level)
335 msg = self.session.msg('object_info_request', content)
335 msg = self.session.msg('object_info_request', content)
336 self._queue_send(msg)
336 self._queue_send(msg)
337 return msg['header']['msg_id']
337 return msg['header']['msg_id']
338
338
339 def history(self, raw=True, output=False, hist_access_type='range', **kwargs):
339 def history(self, raw=True, output=False, hist_access_type='range', **kwargs):
340 """Get entries from the kernel's history list.
340 """Get entries from the kernel's history list.
341
341
342 Parameters
342 Parameters
343 ----------
343 ----------
344 raw : bool
344 raw : bool
345 If True, return the raw input.
345 If True, return the raw input.
346 output : bool
346 output : bool
347 If True, then return the output as well.
347 If True, then return the output as well.
348 hist_access_type : str
348 hist_access_type : str
349 'range' (fill in session, start and stop params), 'tail' (fill in n)
349 'range' (fill in session, start and stop params), 'tail' (fill in n)
350 or 'search' (fill in pattern param).
350 or 'search' (fill in pattern param).
351
351
352 session : int
352 session : int
353 For a range request, the session from which to get lines. Session
353 For a range request, the session from which to get lines. Session
354 numbers are positive integers; negative ones count back from the
354 numbers are positive integers; negative ones count back from the
355 current session.
355 current session.
356 start : int
356 start : int
357 The first line number of a history range.
357 The first line number of a history range.
358 stop : int
358 stop : int
359 The final (excluded) line number of a history range.
359 The final (excluded) line number of a history range.
360
360
361 n : int
361 n : int
362 The number of lines of history to get for a tail request.
362 The number of lines of history to get for a tail request.
363
363
364 pattern : str
364 pattern : str
365 The glob-syntax pattern for a search request.
365 The glob-syntax pattern for a search request.
366
366
367 Returns
367 Returns
368 -------
368 -------
369 The msg_id of the message sent.
369 The msg_id of the message sent.
370 """
370 """
371 content = dict(raw=raw, output=output, hist_access_type=hist_access_type,
371 content = dict(raw=raw, output=output, hist_access_type=hist_access_type,
372 **kwargs)
372 **kwargs)
373 msg = self.session.msg('history_request', content)
373 msg = self.session.msg('history_request', content)
374 self._queue_send(msg)
374 self._queue_send(msg)
375 return msg['header']['msg_id']
375 return msg['header']['msg_id']
376
376
377 def kernel_info(self):
377 def kernel_info(self):
378 """Request kernel info."""
378 """Request kernel info."""
379 msg = self.session.msg('kernel_info_request')
379 msg = self.session.msg('kernel_info_request')
380 self._queue_send(msg)
380 self._queue_send(msg)
381 return msg['header']['msg_id']
381 return msg['header']['msg_id']
382
382
383 def shutdown(self, restart=False):
383 def shutdown(self, restart=False):
384 """Request an immediate kernel shutdown.
384 """Request an immediate kernel shutdown.
385
385
386 Upon receipt of the (empty) reply, client code can safely assume that
386 Upon receipt of the (empty) reply, client code can safely assume that
387 the kernel has shut down and it's safe to forcefully terminate it if
387 the kernel has shut down and it's safe to forcefully terminate it if
388 it's still alive.
388 it's still alive.
389
389
390 The kernel will send the reply via a function registered with Python's
390 The kernel will send the reply via a function registered with Python's
391 atexit module, ensuring it's truly done as the kernel is done with all
391 atexit module, ensuring it's truly done as the kernel is done with all
392 normal operation.
392 normal operation.
393 """
393 """
394 # Send quit message to kernel. Once we implement kernel-side setattr,
394 # Send quit message to kernel. Once we implement kernel-side setattr,
395 # this should probably be done that way, but for now this will do.
395 # this should probably be done that way, but for now this will do.
396 msg = self.session.msg('shutdown_request', {'restart':restart})
396 msg = self.session.msg('shutdown_request', {'restart':restart})
397 self._queue_send(msg)
397 self._queue_send(msg)
398 return msg['header']['msg_id']
398 return msg['header']['msg_id']
399
399
400
400
401
401
402 class IOPubChannel(ZMQSocketChannel):
402 class IOPubChannel(ZMQSocketChannel):
403 """The iopub channel which listens for messages that the kernel publishes.
403 """The iopub channel which listens for messages that the kernel publishes.
404
404
405 This channel is where all output is published to frontends.
405 This channel is where all output is published to frontends.
406 """
406 """
407
407
408 def __init__(self, context, session, address):
408 def __init__(self, context, session, address):
409 super(IOPubChannel, self).__init__(context, session, address)
409 super(IOPubChannel, self).__init__(context, session, address)
410 self.ioloop = ioloop.IOLoop()
410 self.ioloop = ioloop.IOLoop()
411
411
412 def run(self):
412 def run(self):
413 """The thread's main activity. Call start() instead."""
413 """The thread's main activity. Call start() instead."""
414 self.socket = self.context.socket(zmq.SUB)
414 self.socket = self.context.socket(zmq.SUB)
415 self.socket.setsockopt(zmq.SUBSCRIBE,b'')
415 self.socket.setsockopt(zmq.SUBSCRIBE,b'')
416 self.socket.setsockopt(zmq.IDENTITY, self.session.bsession)
416 self.socket.setsockopt(zmq.IDENTITY, self.session.bsession)
417 self.socket.connect(self.address)
417 self.socket.connect(self.address)
418 self.stream = zmqstream.ZMQStream(self.socket, self.ioloop)
418 self.stream = zmqstream.ZMQStream(self.socket, self.ioloop)
419 self.stream.on_recv(self._handle_recv)
419 self.stream.on_recv(self._handle_recv)
420 self._run_loop()
420 self._run_loop()
421 try:
421 try:
422 self.socket.close()
422 self.socket.close()
423 except:
423 except:
424 pass
424 pass
425
425
426 def stop(self):
426 def stop(self):
427 """Stop the channel's event loop and join its thread."""
427 """Stop the channel's event loop and join its thread."""
428 self.ioloop.stop()
428 self.ioloop.stop()
429 super(IOPubChannel, self).stop()
429 super(IOPubChannel, self).stop()
430
430
431 def call_handlers(self, msg):
431 def call_handlers(self, msg):
432 """This method is called in the ioloop thread when a message arrives.
432 """This method is called in the ioloop thread when a message arrives.
433
433
434 Subclasses should override this method to handle incoming messages.
434 Subclasses should override this method to handle incoming messages.
435 It is important to remember that this method is called in the thread
435 It is important to remember that this method is called in the thread
436 so that some logic must be done to ensure that the application leve
436 so that some logic must be done to ensure that the application leve
437 handlers are called in the application thread.
437 handlers are called in the application thread.
438 """
438 """
439 raise NotImplementedError('call_handlers must be defined in a subclass.')
439 raise NotImplementedError('call_handlers must be defined in a subclass.')
440
440
441 def flush(self, timeout=1.0):
441 def flush(self, timeout=1.0):
442 """Immediately processes all pending messages on the iopub channel.
442 """Immediately processes all pending messages on the iopub channel.
443
443
444 Callers should use this method to ensure that :method:`call_handlers`
444 Callers should use this method to ensure that :method:`call_handlers`
445 has been called for all messages that have been received on the
445 has been called for all messages that have been received on the
446 0MQ SUB socket of this channel.
446 0MQ SUB socket of this channel.
447
447
448 This method is thread safe.
448 This method is thread safe.
449
449
450 Parameters
450 Parameters
451 ----------
451 ----------
452 timeout : float, optional
452 timeout : float, optional
453 The maximum amount of time to spend flushing, in seconds. The
453 The maximum amount of time to spend flushing, in seconds. The
454 default is one second.
454 default is one second.
455 """
455 """
456 # We do the IOLoop callback process twice to ensure that the IOLoop
456 # We do the IOLoop callback process twice to ensure that the IOLoop
457 # gets to perform at least one full poll.
457 # gets to perform at least one full poll.
458 stop_time = time.time() + timeout
458 stop_time = time.time() + timeout
459 for i in xrange(2):
459 for i in xrange(2):
460 self._flushed = False
460 self._flushed = False
461 self.ioloop.add_callback(self._flush)
461 self.ioloop.add_callback(self._flush)
462 while not self._flushed and time.time() < stop_time:
462 while not self._flushed and time.time() < stop_time:
463 time.sleep(0.01)
463 time.sleep(0.01)
464
464
465 def _flush(self):
465 def _flush(self):
466 """Callback for :method:`self.flush`."""
466 """Callback for :method:`self.flush`."""
467 self.stream.flush()
467 self.stream.flush()
468 self._flushed = True
468 self._flushed = True
469
469
470
470
471 class StdInChannel(ZMQSocketChannel):
471 class StdInChannel(ZMQSocketChannel):
472 """The stdin channel to handle raw_input requests that the kernel makes."""
472 """The stdin channel to handle raw_input requests that the kernel makes."""
473
473
474 msg_queue = None
474 msg_queue = None
475
475
476 def __init__(self, context, session, address):
476 def __init__(self, context, session, address):
477 super(StdInChannel, self).__init__(context, session, address)
477 super(StdInChannel, self).__init__(context, session, address)
478 self.ioloop = ioloop.IOLoop()
478 self.ioloop = ioloop.IOLoop()
479
479
480 def run(self):
480 def run(self):
481 """The thread's main activity. Call start() instead."""
481 """The thread's main activity. Call start() instead."""
482 self.socket = self.context.socket(zmq.DEALER)
482 self.socket = self.context.socket(zmq.DEALER)
483 self.socket.setsockopt(zmq.IDENTITY, self.session.bsession)
483 self.socket.setsockopt(zmq.IDENTITY, self.session.bsession)
484 self.socket.connect(self.address)
484 self.socket.connect(self.address)
485 self.stream = zmqstream.ZMQStream(self.socket, self.ioloop)
485 self.stream = zmqstream.ZMQStream(self.socket, self.ioloop)
486 self.stream.on_recv(self._handle_recv)
486 self.stream.on_recv(self._handle_recv)
487 self._run_loop()
487 self._run_loop()
488 try:
488 try:
489 self.socket.close()
489 self.socket.close()
490 except:
490 except:
491 pass
491 pass
492
492
493 def stop(self):
493 def stop(self):
494 """Stop the channel's event loop and join its thread."""
494 """Stop the channel's event loop and join its thread."""
495 self.ioloop.stop()
495 self.ioloop.stop()
496 super(StdInChannel, self).stop()
496 super(StdInChannel, self).stop()
497
497
498 def call_handlers(self, msg):
498 def call_handlers(self, msg):
499 """This method is called in the ioloop thread when a message arrives.
499 """This method is called in the ioloop thread when a message arrives.
500
500
501 Subclasses should override this method to handle incoming messages.
501 Subclasses should override this method to handle incoming messages.
502 It is important to remember that this method is called in the thread
502 It is important to remember that this method is called in the thread
503 so that some logic must be done to ensure that the application leve
503 so that some logic must be done to ensure that the application leve
504 handlers are called in the application thread.
504 handlers are called in the application thread.
505 """
505 """
506 raise NotImplementedError('call_handlers must be defined in a subclass.')
506 raise NotImplementedError('call_handlers must be defined in a subclass.')
507
507
508 def input(self, string):
508 def input(self, string):
509 """Send a string of raw input to the kernel."""
509 """Send a string of raw input to the kernel."""
510 content = dict(value=string)
510 content = dict(value=string)
511 msg = self.session.msg('input_reply', content)
511 msg = self.session.msg('input_reply', content)
512 self._queue_send(msg)
512 self._queue_send(msg)
513
513
514
514
515 class HBChannel(ZMQSocketChannel):
515 class HBChannel(ZMQSocketChannel):
516 """The heartbeat channel which monitors the kernel heartbeat.
516 """The heartbeat channel which monitors the kernel heartbeat.
517
517
518 Note that the heartbeat channel is paused by default. As long as you start
518 Note that the heartbeat channel is paused by default. As long as you start
519 this channel, the kernel manager will ensure that it is paused and un-paused
519 this channel, the kernel manager will ensure that it is paused and un-paused
520 as appropriate.
520 as appropriate.
521 """
521 """
522
522
523 time_to_dead = 3.0
523 time_to_dead = 3.0
524 socket = None
524 socket = None
525 poller = None
525 poller = None
526 _running = None
526 _running = None
527 _pause = None
527 _pause = None
528 _beating = None
528 _beating = None
529
529
530 def __init__(self, context, session, address):
530 def __init__(self, context, session, address):
531 super(HBChannel, self).__init__(context, session, address)
531 super(HBChannel, self).__init__(context, session, address)
532 self._running = False
532 self._running = False
533 self._pause =True
533 self._pause =True
534 self.poller = zmq.Poller()
534 self.poller = zmq.Poller()
535
535
536 def _create_socket(self):
536 def _create_socket(self):
537 if self.socket is not None:
537 if self.socket is not None:
538 # close previous socket, before opening a new one
538 # close previous socket, before opening a new one
539 self.poller.unregister(self.socket)
539 self.poller.unregister(self.socket)
540 self.socket.close()
540 self.socket.close()
541 self.socket = self.context.socket(zmq.REQ)
541 self.socket = self.context.socket(zmq.REQ)
542 self.socket.setsockopt(zmq.LINGER, 0)
542 self.socket.setsockopt(zmq.LINGER, 0)
543 self.socket.connect(self.address)
543 self.socket.connect(self.address)
544
544
545 self.poller.register(self.socket, zmq.POLLIN)
545 self.poller.register(self.socket, zmq.POLLIN)
546
546
547 def _poll(self, start_time):
547 def _poll(self, start_time):
548 """poll for heartbeat replies until we reach self.time_to_dead.
548 """poll for heartbeat replies until we reach self.time_to_dead.
549
549
550 Ignores interrupts, and returns the result of poll(), which
550 Ignores interrupts, and returns the result of poll(), which
551 will be an empty list if no messages arrived before the timeout,
551 will be an empty list if no messages arrived before the timeout,
552 or the event tuple if there is a message to receive.
552 or the event tuple if there is a message to receive.
553 """
553 """
554
554
555 until_dead = self.time_to_dead - (time.time() - start_time)
555 until_dead = self.time_to_dead - (time.time() - start_time)
556 # ensure poll at least once
556 # ensure poll at least once
557 until_dead = max(until_dead, 1e-3)
557 until_dead = max(until_dead, 1e-3)
558 events = []
558 events = []
559 while True:
559 while True:
560 try:
560 try:
561 events = self.poller.poll(1000 * until_dead)
561 events = self.poller.poll(1000 * until_dead)
562 except ZMQError as e:
562 except ZMQError as e:
563 if e.errno == errno.EINTR:
563 if e.errno == errno.EINTR:
564 # ignore interrupts during heartbeat
564 # ignore interrupts during heartbeat
565 # this may never actually happen
565 # this may never actually happen
566 until_dead = self.time_to_dead - (time.time() - start_time)
566 until_dead = self.time_to_dead - (time.time() - start_time)
567 until_dead = max(until_dead, 1e-3)
567 until_dead = max(until_dead, 1e-3)
568 pass
568 pass
569 else:
569 else:
570 raise
570 raise
571 except Exception:
571 except Exception:
572 if self._exiting:
572 if self._exiting:
573 break
573 break
574 else:
574 else:
575 raise
575 raise
576 else:
576 else:
577 break
577 break
578 return events
578 return events
579
579
580 def run(self):
580 def run(self):
581 """The thread's main activity. Call start() instead."""
581 """The thread's main activity. Call start() instead."""
582 self._create_socket()
582 self._create_socket()
583 self._running = True
583 self._running = True
584 self._beating = True
584 self._beating = True
585
585
586 while self._running:
586 while self._running:
587 if self._pause:
587 if self._pause:
588 # just sleep, and skip the rest of the loop
588 # just sleep, and skip the rest of the loop
589 time.sleep(self.time_to_dead)
589 time.sleep(self.time_to_dead)
590 continue
590 continue
591
591
592 since_last_heartbeat = 0.0
592 since_last_heartbeat = 0.0
593 # io.rprint('Ping from HB channel') # dbg
593 # io.rprint('Ping from HB channel') # dbg
594 # no need to catch EFSM here, because the previous event was
594 # no need to catch EFSM here, because the previous event was
595 # either a recv or connect, which cannot be followed by EFSM
595 # either a recv or connect, which cannot be followed by EFSM
596 self.socket.send(b'ping')
596 self.socket.send(b'ping')
597 request_time = time.time()
597 request_time = time.time()
598 ready = self._poll(request_time)
598 ready = self._poll(request_time)
599 if ready:
599 if ready:
600 self._beating = True
600 self._beating = True
601 # the poll above guarantees we have something to recv
601 # the poll above guarantees we have something to recv
602 self.socket.recv()
602 self.socket.recv()
603 # sleep the remainder of the cycle
603 # sleep the remainder of the cycle
604 remainder = self.time_to_dead - (time.time() - request_time)
604 remainder = self.time_to_dead - (time.time() - request_time)
605 if remainder > 0:
605 if remainder > 0:
606 time.sleep(remainder)
606 time.sleep(remainder)
607 continue
607 continue
608 else:
608 else:
609 # nothing was received within the time limit, signal heart failure
609 # nothing was received within the time limit, signal heart failure
610 self._beating = False
610 self._beating = False
611 since_last_heartbeat = time.time() - request_time
611 since_last_heartbeat = time.time() - request_time
612 self.call_handlers(since_last_heartbeat)
612 self.call_handlers(since_last_heartbeat)
613 # and close/reopen the socket, because the REQ/REP cycle has been broken
613 # and close/reopen the socket, because the REQ/REP cycle has been broken
614 self._create_socket()
614 self._create_socket()
615 continue
615 continue
616 try:
616 try:
617 self.socket.close()
617 self.socket.close()
618 except:
618 except:
619 pass
619 pass
620
620
621 def pause(self):
621 def pause(self):
622 """Pause the heartbeat."""
622 """Pause the heartbeat."""
623 self._pause = True
623 self._pause = True
624
624
625 def unpause(self):
625 def unpause(self):
626 """Unpause the heartbeat."""
626 """Unpause the heartbeat."""
627 self._pause = False
627 self._pause = False
628
628
629 def is_beating(self):
629 def is_beating(self):
630 """Is the heartbeat running and responsive (and not paused)."""
630 """Is the heartbeat running and responsive (and not paused)."""
631 if self.is_alive() and not self._pause and self._beating:
631 if self.is_alive() and not self._pause and self._beating:
632 return True
632 return True
633 else:
633 else:
634 return False
634 return False
635
635
636 def stop(self):
636 def stop(self):
637 """Stop the channel's event loop and join its thread."""
637 """Stop the channel's event loop and join its thread."""
638 self._running = False
638 self._running = False
639 super(HBChannel, self).stop()
639 super(HBChannel, self).stop()
640
640
641 def call_handlers(self, since_last_heartbeat):
641 def call_handlers(self, since_last_heartbeat):
642 """This method is called in the ioloop thread when a message arrives.
642 """This method is called in the ioloop thread when a message arrives.
643
643
644 Subclasses should override this method to handle incoming messages.
644 Subclasses should override this method to handle incoming messages.
645 It is important to remember that this method is called in the thread
645 It is important to remember that this method is called in the thread
646 so that some logic must be done to ensure that the application level
646 so that some logic must be done to ensure that the application level
647 handlers are called in the application thread.
647 handlers are called in the application thread.
648 """
648 """
649 raise NotImplementedError('call_handlers must be defined in a subclass.')
649 raise NotImplementedError('call_handlers must be defined in a subclass.')
650
650
651
651
652 #-----------------------------------------------------------------------------
652 #-----------------------------------------------------------------------------
653 # Main kernel manager class
653 # Main kernel manager class
654 #-----------------------------------------------------------------------------
654 #-----------------------------------------------------------------------------
655
655
656 class KernelManager(Configurable):
656 class KernelManager(Configurable):
657 """Manages a single kernel on this host along with its channels.
657 """Manages a single kernel on this host along with its channels.
658
658
659 There are four channels associated with each kernel:
659 There are four channels associated with each kernel:
660
660
661 * shell: for request/reply calls to the kernel.
661 * shell: for request/reply calls to the kernel.
662 * iopub: for the kernel to publish results to frontends.
662 * iopub: for the kernel to publish results to frontends.
663 * hb: for monitoring the kernel's heartbeat.
663 * hb: for monitoring the kernel's heartbeat.
664 * stdin: for frontends to reply to raw_input calls in the kernel.
664 * stdin: for frontends to reply to raw_input calls in the kernel.
665
665
666 The usage of the channels that this class manages is optional. It is
666 The usage of the channels that this class manages is optional. It is
667 entirely possible to connect to the kernels directly using ZeroMQ
667 entirely possible to connect to the kernels directly using ZeroMQ
668 sockets. These channels are useful primarily for talking to a kernel
668 sockets. These channels are useful primarily for talking to a kernel
669 whose :class:`KernelManager` is in the same process.
669 whose :class:`KernelManager` is in the same process.
670
670
671 This version manages kernels started using Popen.
671 This version manages kernels started using Popen.
672 """
672 """
673 # The PyZMQ Context to use for communication with the kernel.
673 # The PyZMQ Context to use for communication with the kernel.
674 context = Instance(zmq.Context)
674 context = Instance(zmq.Context)
675 def _context_default(self):
675 def _context_default(self):
676 return zmq.Context.instance()
676 return zmq.Context.instance()
677
677
678 # The Session to use for communication with the kernel.
678 # The Session to use for communication with the kernel.
679 session = Instance(Session)
679 session = Instance(Session)
680 def _session_default(self):
680 def _session_default(self):
681 return Session(config=self.config)
681 return Session(config=self.config)
682
682
683 # The kernel process with which the KernelManager is communicating.
683 # The kernel process with which the KernelManager is communicating.
684 # generally a Popen instance
684 # generally a Popen instance
685 kernel = Any()
685 kernel = Any()
686
686
687 kernel_cmd = List(Unicode, config=True,
687 kernel_cmd = List(Unicode, config=True,
688 help="""The Popen Command to launch the kernel.
688 help="""The Popen Command to launch the kernel.
689 Override this if you have a custom
689 Override this if you have a custom
690 """
690 """
691 )
691 )
692 def _kernel_cmd_changed(self, name, old, new):
692 def _kernel_cmd_changed(self, name, old, new):
693 print 'kernel cmd changed', new
693 print 'kernel cmd changed', new
694 self.ipython_kernel = False
694 self.ipython_kernel = False
695
695
696 ipython_kernel = Bool(True)
696 ipython_kernel = Bool(True)
697
697
698
698
699 # The addresses for the communication channels.
699 # The addresses for the communication channels.
700 connection_file = Unicode('')
700 connection_file = Unicode('')
701
701
702 transport = CaselessStrEnum(['tcp', 'ipc'], default_value='tcp', config=True)
702 transport = CaselessStrEnum(['tcp', 'ipc'], default_value='tcp', config=True)
703
703
704 ip = Unicode(LOCALHOST, config=True,
704 ip = Unicode(LOCALHOST, config=True,
705 help="""Set the kernel\'s IP address [default localhost].
705 help="""Set the kernel\'s IP address [default localhost].
706 If the IP address is something other than localhost, then
706 If the IP address is something other than localhost, then
707 Consoles on other machines will be able to connect
707 Consoles on other machines will be able to connect
708 to the Kernel, so be careful!"""
708 to the Kernel, so be careful!"""
709 )
709 )
710 def _ip_default(self):
710 def _ip_default(self):
711 if self.transport == 'ipc':
711 if self.transport == 'ipc':
712 if self.connection_file:
712 if self.connection_file:
713 return os.path.splitext(self.connection_file)[0] + '-ipc'
713 return os.path.splitext(self.connection_file)[0] + '-ipc'
714 else:
714 else:
715 return 'kernel-ipc'
715 return 'kernel-ipc'
716 else:
716 else:
717 return LOCALHOST
717 return LOCALHOST
718 def _ip_changed(self, name, old, new):
718 def _ip_changed(self, name, old, new):
719 if new == '*':
719 if new == '*':
720 self.ip = '0.0.0.0'
720 self.ip = '0.0.0.0'
721 shell_port = Integer(0)
721 shell_port = Integer(0)
722 iopub_port = Integer(0)
722 iopub_port = Integer(0)
723 stdin_port = Integer(0)
723 stdin_port = Integer(0)
724 hb_port = Integer(0)
724 hb_port = Integer(0)
725
725
726 # The classes to use for the various channels.
726 # The classes to use for the various channels.
727 shell_channel_class = Type(ShellChannel)
727 shell_channel_class = Type(ShellChannel)
728 iopub_channel_class = Type(IOPubChannel)
728 iopub_channel_class = Type(IOPubChannel)
729 stdin_channel_class = Type(StdInChannel)
729 stdin_channel_class = Type(StdInChannel)
730 hb_channel_class = Type(HBChannel)
730 hb_channel_class = Type(HBChannel)
731
731
732 # Protected traits.
732 # Protected traits.
733 _launch_args = Any
733 _launch_args = Any
734 _shell_channel = Any
734 _shell_channel = Any
735 _iopub_channel = Any
735 _iopub_channel = Any
736 _stdin_channel = Any
736 _stdin_channel = Any
737 _hb_channel = Any
737 _hb_channel = Any
738 _connection_file_written=Bool(False)
738 _connection_file_written=Bool(False)
739
739
740 def __del__(self):
740 def __del__(self):
741 self.cleanup_connection_file()
741 self.cleanup_connection_file()
742
742
743 #--------------------------------------------------------------------------
743 #--------------------------------------------------------------------------
744 # Channel management methods:
744 # Channel management methods:
745 #--------------------------------------------------------------------------
745 #--------------------------------------------------------------------------
746
746
747 def start_channels(self, shell=True, iopub=True, stdin=True, hb=True):
747 def start_channels(self, shell=True, iopub=True, stdin=True, hb=True):
748 """Starts the channels for this kernel.
748 """Starts the channels for this kernel.
749
749
750 This will create the channels if they do not exist and then start
750 This will create the channels if they do not exist and then start
751 them (their activity runs in a thread). If port numbers of 0 are
751 them (their activity runs in a thread). If port numbers of 0 are
752 being used (random ports) then you must first call
752 being used (random ports) then you must first call
753 :method:`start_kernel`. If the channels have been stopped and you
753 :method:`start_kernel`. If the channels have been stopped and you
754 call this, :class:`RuntimeError` will be raised.
754 call this, :class:`RuntimeError` will be raised.
755 """
755 """
756 if shell:
756 if shell:
757 self.shell_channel.start()
757 self.shell_channel.start()
758 if iopub:
758 if iopub:
759 self.iopub_channel.start()
759 self.iopub_channel.start()
760 if stdin:
760 if stdin:
761 self.stdin_channel.start()
761 self.stdin_channel.start()
762 self.shell_channel.allow_stdin = True
762 self.shell_channel.allow_stdin = True
763 else:
763 else:
764 self.shell_channel.allow_stdin = False
764 self.shell_channel.allow_stdin = False
765 if hb:
765 if hb:
766 self.hb_channel.start()
766 self.hb_channel.start()
767
767
768 def stop_channels(self):
768 def stop_channels(self):
769 """Stops all the running channels for this kernel.
769 """Stops all the running channels for this kernel.
770
770
771 This stops their event loops and joins their threads.
771 This stops their event loops and joins their threads.
772 """
772 """
773 if self.shell_channel.is_alive():
773 if self.shell_channel.is_alive():
774 self.shell_channel.stop()
774 self.shell_channel.stop()
775 if self.iopub_channel.is_alive():
775 if self.iopub_channel.is_alive():
776 self.iopub_channel.stop()
776 self.iopub_channel.stop()
777 if self.stdin_channel.is_alive():
777 if self.stdin_channel.is_alive():
778 self.stdin_channel.stop()
778 self.stdin_channel.stop()
779 if self.hb_channel.is_alive():
779 if self.hb_channel.is_alive():
780 self.hb_channel.stop()
780 self.hb_channel.stop()
781
781
782 @property
782 @property
783 def channels_running(self):
783 def channels_running(self):
784 """Are any of the channels created and running?"""
784 """Are any of the channels created and running?"""
785 return (self.shell_channel.is_alive() or self.iopub_channel.is_alive() or
785 return (self.shell_channel.is_alive() or self.iopub_channel.is_alive() or
786 self.stdin_channel.is_alive() or self.hb_channel.is_alive())
786 self.stdin_channel.is_alive() or self.hb_channel.is_alive())
787
787
788 def _make_url(self, port):
788 def _make_url(self, port):
789 """Make a zmq url with a port.
789 """Make a zmq url with a port.
790
790
791 There are two cases that this handles:
791 There are two cases that this handles:
792
792
793 * tcp: tcp://ip:port
793 * tcp: tcp://ip:port
794 * ipc: ipc://ip-port
794 * ipc: ipc://ip-port
795 """
795 """
796 if self.transport == 'tcp':
796 if self.transport == 'tcp':
797 return "tcp://%s:%i" % (self.ip, port)
797 return "tcp://%s:%i" % (self.ip, port)
798 else:
798 else:
799 return "%s://%s-%s" % (self.transport, self.ip, port)
799 return "%s://%s-%s" % (self.transport, self.ip, port)
800
800
801 @property
801 @property
802 def shell_channel(self):
802 def shell_channel(self):
803 """Get the shell channel object for this kernel."""
803 """Get the shell channel object for this kernel."""
804 if self._shell_channel is None:
804 if self._shell_channel is None:
805 self._shell_channel = self.shell_channel_class(
805 self._shell_channel = self.shell_channel_class(
806 self.context, self.session, self._make_url(self.shell_port)
806 self.context, self.session, self._make_url(self.shell_port)
807 )
807 )
808 return self._shell_channel
808 return self._shell_channel
809
809
810 @property
810 @property
811 def iopub_channel(self):
811 def iopub_channel(self):
812 """Get the iopub channel object for this kernel."""
812 """Get the iopub channel object for this kernel."""
813 if self._iopub_channel is None:
813 if self._iopub_channel is None:
814 self._iopub_channel = self.iopub_channel_class(
814 self._iopub_channel = self.iopub_channel_class(
815 self.context, self.session, self._make_url(self.iopub_port)
815 self.context, self.session, self._make_url(self.iopub_port)
816 )
816 )
817 return self._iopub_channel
817 return self._iopub_channel
818
818
819 @property
819 @property
820 def stdin_channel(self):
820 def stdin_channel(self):
821 """Get the stdin channel object for this kernel."""
821 """Get the stdin channel object for this kernel."""
822 if self._stdin_channel is None:
822 if self._stdin_channel is None:
823 self._stdin_channel = self.stdin_channel_class(
823 self._stdin_channel = self.stdin_channel_class(
824 self.context, self.session, self._make_url(self.stdin_port)
824 self.context, self.session, self._make_url(self.stdin_port)
825 )
825 )
826 return self._stdin_channel
826 return self._stdin_channel
827
827
828 @property
828 @property
829 def hb_channel(self):
829 def hb_channel(self):
830 """Get the hb channel object for this kernel."""
830 """Get the hb channel object for this kernel."""
831 if self._hb_channel is None:
831 if self._hb_channel is None:
832 self._hb_channel = self.hb_channel_class(
832 self._hb_channel = self.hb_channel_class(
833 self.context, self.session, self._make_url(self.hb_port)
833 self.context, self.session, self._make_url(self.hb_port)
834 )
834 )
835 return self._hb_channel
835 return self._hb_channel
836
836
837 #--------------------------------------------------------------------------
837 #--------------------------------------------------------------------------
838 # Connection and ipc file management
838 # Connection and ipc file management
839 #--------------------------------------------------------------------------
839 #--------------------------------------------------------------------------
840
840
841 def cleanup_connection_file(self):
841 def cleanup_connection_file(self):
842 """Cleanup connection file *if we wrote it*
842 """Cleanup connection file *if we wrote it*
843
843
844 Will not raise if the connection file was already removed somehow.
844 Will not raise if the connection file was already removed somehow.
845 """
845 """
846 if self._connection_file_written:
846 if self._connection_file_written:
847 # cleanup connection files on full shutdown of kernel we started
847 # cleanup connection files on full shutdown of kernel we started
848 self._connection_file_written = False
848 self._connection_file_written = False
849 try:
849 try:
850 os.remove(self.connection_file)
850 os.remove(self.connection_file)
851 except (IOError, OSError):
851 except (IOError, OSError):
852 pass
852 pass
853
853
854 def cleanup_ipc_files(self):
854 def cleanup_ipc_files(self):
855 """Cleanup ipc files if we wrote them."""
855 """Cleanup ipc files if we wrote them."""
856 if self.transport != 'ipc':
856 if self.transport != 'ipc':
857 return
857 return
858 for port in (self.shell_port, self.iopub_port, self.stdin_port, self.hb_port):
858 for port in (self.shell_port, self.iopub_port, self.stdin_port, self.hb_port):
859 ipcfile = "%s-%i" % (self.ip, port)
859 ipcfile = "%s-%i" % (self.ip, port)
860 try:
860 try:
861 os.remove(ipcfile)
861 os.remove(ipcfile)
862 except (IOError, OSError):
862 except (IOError, OSError):
863 pass
863 pass
864
864
865 def load_connection_file(self):
865 def load_connection_file(self):
866 """Load connection info from JSON dict in self.connection_file."""
866 """Load connection info from JSON dict in self.connection_file."""
867 with open(self.connection_file) as f:
867 with open(self.connection_file) as f:
868 cfg = json.loads(f.read())
868 cfg = json.loads(f.read())
869
869
870 from pprint import pprint
870 from pprint import pprint
871 pprint(cfg)
871 pprint(cfg)
872 self.transport = cfg.get('transport', 'tcp')
872 self.transport = cfg.get('transport', 'tcp')
873 self.ip = cfg['ip']
873 self.ip = cfg['ip']
874 self.shell_port = cfg['shell_port']
874 self.shell_port = cfg['shell_port']
875 self.stdin_port = cfg['stdin_port']
875 self.stdin_port = cfg['stdin_port']
876 self.iopub_port = cfg['iopub_port']
876 self.iopub_port = cfg['iopub_port']
877 self.hb_port = cfg['hb_port']
877 self.hb_port = cfg['hb_port']
878 self.session.key = str_to_bytes(cfg['key'])
878 self.session.key = str_to_bytes(cfg['key'])
879
879
880 def write_connection_file(self):
880 def write_connection_file(self):
881 """Write connection info to JSON dict in self.connection_file."""
881 """Write connection info to JSON dict in self.connection_file."""
882 if self._connection_file_written:
882 if self._connection_file_written:
883 return
883 return
884 self.connection_file,cfg = write_connection_file(self.connection_file,
884 self.connection_file,cfg = write_connection_file(self.connection_file,
885 transport=self.transport, ip=self.ip, key=self.session.key,
885 transport=self.transport, ip=self.ip, key=self.session.key,
886 stdin_port=self.stdin_port, iopub_port=self.iopub_port,
886 stdin_port=self.stdin_port, iopub_port=self.iopub_port,
887 shell_port=self.shell_port, hb_port=self.hb_port)
887 shell_port=self.shell_port, hb_port=self.hb_port)
888 # write_connection_file also sets default ports:
888 # write_connection_file also sets default ports:
889 self.shell_port = cfg['shell_port']
889 self.shell_port = cfg['shell_port']
890 self.stdin_port = cfg['stdin_port']
890 self.stdin_port = cfg['stdin_port']
891 self.iopub_port = cfg['iopub_port']
891 self.iopub_port = cfg['iopub_port']
892 self.hb_port = cfg['hb_port']
892 self.hb_port = cfg['hb_port']
893
893
894 self._connection_file_written = True
894 self._connection_file_written = True
895
895
896 #--------------------------------------------------------------------------
896 #--------------------------------------------------------------------------
897 # Kernel management
897 # Kernel management
898 #--------------------------------------------------------------------------
898 #--------------------------------------------------------------------------
899
899
900 def format_kernel_cmd(self, **kw):
900 def format_kernel_cmd(self, **kw):
901 """format templated args (e.g. {connection_file})"""
901 """format templated args (e.g. {connection_file})"""
902 if self.kernel_cmd:
902 if self.kernel_cmd:
903 cmd = self.kernel_cmd
903 cmd = self.kernel_cmd
904 else:
904 else:
905 cmd = make_ipkernel_cmd(
905 cmd = make_ipkernel_cmd(
906 'from IPython.zmq.ipkernel import main; main()',
906 'from IPython.zmq.ipkernel import main; main()',
907 **kw
907 **kw
908 )
908 )
909 ns = dict(connection_file=self.connection_file)
909 ns = dict(connection_file=self.connection_file)
910 ns.update(self._launch_args)
910 ns.update(self._launch_args)
911 return [ c.format(**ns) for c in cmd ]
911 return [ c.format(**ns) for c in cmd ]
912
912
913 def _launch_kernel(self, kernel_cmd, **kw):
913 def _launch_kernel(self, kernel_cmd, **kw):
914 """actually launch the kernel
914 """actually launch the kernel
915
915
916 override in a subclass to launch kernel subprocesses differently
916 override in a subclass to launch kernel subprocesses differently
917 """
917 """
918 return launch_kernel(kernel_cmd, **kw)
918 return launch_kernel(kernel_cmd, **kw)
919
919
920 def start_kernel(self, **kw):
920 def start_kernel(self, **kw):
921 """Starts a kernel on this host in a separate process.
921 """Starts a kernel on this host in a separate process.
922
922
923 If random ports (port=0) are being used, this method must be called
923 If random ports (port=0) are being used, this method must be called
924 before the channels are created.
924 before the channels are created.
925
925
926 Parameters:
926 Parameters:
927 -----------
927 -----------
928 **kw : optional
928 **kw : optional
929 keyword arguments that are passed down to build the kernel_cmd
929 keyword arguments that are passed down to build the kernel_cmd
930 and launching the kernel (e.g. Popen kwargs).
930 and launching the kernel (e.g. Popen kwargs).
931 """
931 """
932 if self.transport == 'tcp' and self.ip not in LOCAL_IPS:
932 if self.transport == 'tcp' and self.ip not in LOCAL_IPS:
933 raise RuntimeError("Can only launch a kernel on a local interface. "
933 raise RuntimeError("Can only launch a kernel on a local interface. "
934 "Make sure that the '*_address' attributes are "
934 "Make sure that the '*_address' attributes are "
935 "configured properly. "
935 "configured properly. "
936 "Currently valid addresses are: %s"%LOCAL_IPS
936 "Currently valid addresses are: %s"%LOCAL_IPS
937 )
937 )
938
938
939 # write connection file / get default ports
939 # write connection file / get default ports
940 self.write_connection_file()
940 self.write_connection_file()
941
941
942 # save kwargs for use in restart
942 # save kwargs for use in restart
943 self._launch_args = kw.copy()
943 self._launch_args = kw.copy()
944 # build the Popen cmd
944 # build the Popen cmd
945 kernel_cmd = self.format_kernel_cmd(**kw)
945 kernel_cmd = self.format_kernel_cmd(**kw)
946 # launch the kernel subprocess
946 # launch the kernel subprocess
947 self.kernel = self._launch_kernel(kernel_cmd,
947 self.kernel = self._launch_kernel(kernel_cmd,
948 ipython_kernel=self.ipython_kernel,
948 ipython_kernel=self.ipython_kernel,
949 **kw)
949 **kw)
950
950
951 def shutdown_kernel(self, now=False, restart=False):
951 def shutdown_kernel(self, now=False, restart=False):
952 """Attempts to the stop the kernel process cleanly.
952 """Attempts to the stop the kernel process cleanly.
953
953
954 This attempts to shutdown the kernels cleanly by:
954 This attempts to shutdown the kernels cleanly by:
955
955
956 1. Sending it a shutdown message over the shell channel.
956 1. Sending it a shutdown message over the shell channel.
957 2. If that fails, the kernel is shutdown forcibly by sending it
957 2. If that fails, the kernel is shutdown forcibly by sending it
958 a signal.
958 a signal.
959
959
960 Parameters:
960 Parameters:
961 -----------
961 -----------
962 now : bool
962 now : bool
963 Should the kernel be forcible killed *now*. This skips the
963 Should the kernel be forcible killed *now*. This skips the
964 first, nice shutdown attempt.
964 first, nice shutdown attempt.
965 restart: bool
965 restart: bool
966 Will this kernel be restarted after it is shutdown. When this
966 Will this kernel be restarted after it is shutdown. When this
967 is True, connection files will not be cleaned up.
967 is True, connection files will not be cleaned up.
968 """
968 """
969 # FIXME: Shutdown does not work on Windows due to ZMQ errors!
969 # FIXME: Shutdown does not work on Windows due to ZMQ errors!
970 if sys.platform == 'win32':
970 if sys.platform == 'win32':
971 self._kill_kernel()
971 self._kill_kernel()
972 return
972 return
973
973
974 # Pause the heart beat channel if it exists.
974 # Pause the heart beat channel if it exists.
975 if self._hb_channel is not None:
975 if self._hb_channel is not None:
976 self._hb_channel.pause()
976 self._hb_channel.pause()
977
977
978 if now:
978 if now:
979 if self.has_kernel:
979 if self.has_kernel:
980 self._kill_kernel()
980 self._kill_kernel()
981 else:
981 else:
982 # Don't send any additional kernel kill messages immediately, to give
982 # Don't send any additional kernel kill messages immediately, to give
983 # the kernel a chance to properly execute shutdown actions. Wait for at
983 # the kernel a chance to properly execute shutdown actions. Wait for at
984 # most 1s, checking every 0.1s.
984 # most 1s, checking every 0.1s.
985 self.shell_channel.shutdown(restart=restart)
985 self.shell_channel.shutdown(restart=restart)
986 for i in range(10):
986 for i in range(10):
987 if self.is_alive:
987 if self.is_alive:
988 time.sleep(0.1)
988 time.sleep(0.1)
989 else:
989 else:
990 break
990 break
991 else:
991 else:
992 # OK, we've waited long enough.
992 # OK, we've waited long enough.
993 if self.has_kernel:
993 if self.has_kernel:
994 self._kill_kernel()
994 self._kill_kernel()
995
995
996 if not restart:
996 if not restart:
997 self.cleanup_connection_file()
997 self.cleanup_connection_file()
998 self.cleanup_ipc_files()
998 self.cleanup_ipc_files()
999 else:
999 else:
1000 self.cleanup_ipc_files()
1000 self.cleanup_ipc_files()
1001
1001
1002 def restart_kernel(self, now=False, **kw):
1002 def restart_kernel(self, now=False, **kw):
1003 """Restarts a kernel with the arguments that were used to launch it.
1003 """Restarts a kernel with the arguments that were used to launch it.
1004
1004
1005 If the old kernel was launched with random ports, the same ports will be
1005 If the old kernel was launched with random ports, the same ports will be
1006 used for the new kernel. The same connection file is used again.
1006 used for the new kernel. The same connection file is used again.
1007
1007
1008 Parameters
1008 Parameters
1009 ----------
1009 ----------
1010 now : bool, optional
1010 now : bool, optional
1011 If True, the kernel is forcefully restarted *immediately*, without
1011 If True, the kernel is forcefully restarted *immediately*, without
1012 having a chance to do any cleanup action. Otherwise the kernel is
1012 having a chance to do any cleanup action. Otherwise the kernel is
1013 given 1s to clean up before a forceful restart is issued.
1013 given 1s to clean up before a forceful restart is issued.
1014
1014
1015 In all cases the kernel is restarted, the only difference is whether
1015 In all cases the kernel is restarted, the only difference is whether
1016 it is given a chance to perform a clean shutdown or not.
1016 it is given a chance to perform a clean shutdown or not.
1017
1017
1018 **kw : optional
1018 **kw : optional
1019 Any options specified here will overwrite those used to launch the
1019 Any options specified here will overwrite those used to launch the
1020 kernel.
1020 kernel.
1021 """
1021 """
1022 if self._launch_args is None:
1022 if self._launch_args is None:
1023 raise RuntimeError("Cannot restart the kernel. "
1023 raise RuntimeError("Cannot restart the kernel. "
1024 "No previous call to 'start_kernel'.")
1024 "No previous call to 'start_kernel'.")
1025 else:
1025 else:
1026 # Stop currently running kernel.
1026 # Stop currently running kernel.
1027 self.shutdown_kernel(now=now, restart=True)
1027 self.shutdown_kernel(now=now, restart=True)
1028
1028
1029 # Start new kernel.
1029 # Start new kernel.
1030 self._launch_args.update(kw)
1030 self._launch_args.update(kw)
1031 self.start_kernel(**self._launch_args)
1031 self.start_kernel(**self._launch_args)
1032
1032
1033 # FIXME: Messages get dropped in Windows due to probable ZMQ bug
1033 # FIXME: Messages get dropped in Windows due to probable ZMQ bug
1034 # unless there is some delay here.
1034 # unless there is some delay here.
1035 if sys.platform == 'win32':
1035 if sys.platform == 'win32':
1036 time.sleep(0.2)
1036 time.sleep(0.2)
1037
1037
1038 @property
1038 @property
1039 def has_kernel(self):
1039 def has_kernel(self):
1040 """Has a kernel been started that we are managing."""
1040 """Has a kernel been started that we are managing."""
1041 return self.kernel is not None
1041 return self.kernel is not None
1042
1042
1043 def _kill_kernel(self):
1043 def _kill_kernel(self):
1044 """Kill the running kernel.
1044 """Kill the running kernel.
1045
1045
1046 This is a private method, callers should use shutdown_kernel(now=True).
1046 This is a private method, callers should use shutdown_kernel(now=True).
1047 """
1047 """
1048 if self.has_kernel:
1048 if self.has_kernel:
1049 # Pause the heart beat channel if it exists.
1049 # Pause the heart beat channel if it exists.
1050 if self._hb_channel is not None:
1050 if self._hb_channel is not None:
1051 self._hb_channel.pause()
1051 self._hb_channel.pause()
1052
1052
1053 # Signal the kernel to terminate (sends SIGKILL on Unix and calls
1053 # Signal the kernel to terminate (sends SIGKILL on Unix and calls
1054 # TerminateProcess() on Win32).
1054 # TerminateProcess() on Win32).
1055 try:
1055 try:
1056 self.kernel.kill()
1056 self.kernel.kill()
1057 except OSError as e:
1057 except OSError as e:
1058 # In Windows, we will get an Access Denied error if the process
1058 # In Windows, we will get an Access Denied error if the process
1059 # has already terminated. Ignore it.
1059 # has already terminated. Ignore it.
1060 if sys.platform == 'win32':
1060 if sys.platform == 'win32':
1061 if e.winerror != 5:
1061 if e.winerror != 5:
1062 raise
1062 raise
1063 # On Unix, we may get an ESRCH error if the process has already
1063 # On Unix, we may get an ESRCH error if the process has already
1064 # terminated. Ignore it.
1064 # terminated. Ignore it.
1065 else:
1065 else:
1066 from errno import ESRCH
1066 from errno import ESRCH
1067 if e.errno != ESRCH:
1067 if e.errno != ESRCH:
1068 raise
1068 raise
1069
1069
1070 # Block until the kernel terminates.
1070 # Block until the kernel terminates.
1071 self.kernel.wait()
1071 self.kernel.wait()
1072 self.kernel = None
1072 self.kernel = None
1073 else:
1073 else:
1074 raise RuntimeError("Cannot kill kernel. No kernel is running!")
1074 raise RuntimeError("Cannot kill kernel. No kernel is running!")
1075
1075
1076 def interrupt_kernel(self):
1076 def interrupt_kernel(self):
1077 """Interrupts the kernel by sending it a signal.
1077 """Interrupts the kernel by sending it a signal.
1078
1078
1079 Unlike ``signal_kernel``, this operation is well supported on all
1079 Unlike ``signal_kernel``, this operation is well supported on all
1080 platforms.
1080 platforms.
1081 """
1081 """
1082 if self.has_kernel:
1082 if self.has_kernel:
1083 if sys.platform == 'win32':
1083 if sys.platform == 'win32':
1084 from parentpoller import ParentPollerWindows as Poller
1084 from parentpoller import ParentPollerWindows as Poller
1085 Poller.send_interrupt(self.kernel.win32_interrupt_event)
1085 Poller.send_interrupt(self.kernel.win32_interrupt_event)
1086 else:
1086 else:
1087 self.kernel.send_signal(signal.SIGINT)
1087 self.kernel.send_signal(signal.SIGINT)
1088 else:
1088 else:
1089 raise RuntimeError("Cannot interrupt kernel. No kernel is running!")
1089 raise RuntimeError("Cannot interrupt kernel. No kernel is running!")
1090
1090
1091 def signal_kernel(self, signum):
1091 def signal_kernel(self, signum):
1092 """Sends a signal to the kernel.
1092 """Sends a signal to the kernel.
1093
1093
1094 Note that since only SIGTERM is supported on Windows, this function is
1094 Note that since only SIGTERM is supported on Windows, this function is
1095 only useful on Unix systems.
1095 only useful on Unix systems.
1096 """
1096 """
1097 if self.has_kernel:
1097 if self.has_kernel:
1098 self.kernel.send_signal(signum)
1098 self.kernel.send_signal(signum)
1099 else:
1099 else:
1100 raise RuntimeError("Cannot signal kernel. No kernel is running!")
1100 raise RuntimeError("Cannot signal kernel. No kernel is running!")
1101
1101
1102 @property
1102 @property
1103 def is_alive(self):
1103 def is_alive(self):
1104 """Is the kernel process still running?"""
1104 """Is the kernel process still running?"""
1105 if self.has_kernel:
1105 if self.has_kernel:
1106 if self.kernel.poll() is None:
1106 if self.kernel.poll() is None:
1107 return True
1107 return True
1108 else:
1108 else:
1109 return False
1109 return False
1110 elif self._hb_channel is not None:
1110 elif self._hb_channel is not None:
1111 # We didn't start the kernel with this KernelManager so we
1111 # We didn't start the kernel with this KernelManager so we
1112 # use the heartbeat.
1112 # use the heartbeat.
1113 return self._hb_channel.is_beating()
1113 return self._hb_channel.is_beating()
1114 else:
1114 else:
1115 # no heartbeat and not local, we can't tell if it's running,
1115 # no heartbeat and not local, we can't tell if it's running,
1116 # so naively return True
1116 # so naively return True
1117 return True
1117 return True
1118
1118
1119
1119
1120 #-----------------------------------------------------------------------------
1120 #-----------------------------------------------------------------------------
1121 # ABC Registration
1121 # ABC Registration
1122 #-----------------------------------------------------------------------------
1122 #-----------------------------------------------------------------------------
1123
1123
1124 ShellChannelABC.register(ShellChannel)
1124 ShellChannelABC.register(ShellChannel)
1125 IOPubChannelABC.register(IOPubChannel)
1125 IOPubChannelABC.register(IOPubChannel)
1126 HBChannelABC.register(HBChannel)
1126 HBChannelABC.register(HBChannel)
1127 StdInChannelABC.register(StdInChannel)
1127 StdInChannelABC.register(StdInChannel)
1128 KernelManagerABC.register(KernelManager)
1128 KernelManagerABC.register(KernelManager)
1129
1129
@@ -1,591 +1,591 b''
1 """A ZMQ-based subclass of InteractiveShell.
1 """A ZMQ-based subclass of InteractiveShell.
2
2
3 This code is meant to ease the refactoring of the base InteractiveShell into
3 This code is meant to ease the refactoring of the base InteractiveShell into
4 something with a cleaner architecture for 2-process use, without actually
4 something with a cleaner architecture for 2-process use, without actually
5 breaking InteractiveShell itself. So we're doing something a bit ugly, where
5 breaking InteractiveShell itself. So we're doing something a bit ugly, where
6 we subclass and override what we want to fix. Once this is working well, we
6 we subclass and override what we want to fix. Once this is working well, we
7 can go back to the base class and refactor the code for a cleaner inheritance
7 can go back to the base class and refactor the code for a cleaner inheritance
8 implementation that doesn't rely on so much monkeypatching.
8 implementation that doesn't rely on so much monkeypatching.
9
9
10 But this lets us maintain a fully working IPython as we develop the new
10 But this lets us maintain a fully working IPython as we develop the new
11 machinery. This should thus be thought of as scaffolding.
11 machinery. This should thus be thought of as scaffolding.
12 """
12 """
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14 # Imports
14 # Imports
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 from __future__ import print_function
16 from __future__ import print_function
17
17
18 # Stdlib
18 # Stdlib
19 import os
19 import os
20 import sys
20 import sys
21 import time
21 import time
22
22
23 # System library imports
23 # System library imports
24 from zmq.eventloop import ioloop
24 from zmq.eventloop import ioloop
25
25
26 # Our own
26 # Our own
27 from IPython.core.interactiveshell import (
27 from IPython.core.interactiveshell import (
28 InteractiveShell, InteractiveShellABC
28 InteractiveShell, InteractiveShellABC
29 )
29 )
30 from IPython.core import page
30 from IPython.core import page
31 from IPython.core.autocall import ZMQExitAutocall
31 from IPython.core.autocall import ZMQExitAutocall
32 from IPython.core.displaypub import DisplayPublisher
32 from IPython.core.displaypub import DisplayPublisher
33 from IPython.core.error import UsageError
33 from IPython.core.error import UsageError
34 from IPython.core.magics import MacroToEdit, CodeMagics
34 from IPython.core.magics import MacroToEdit, CodeMagics
35 from IPython.core.magic import magics_class, line_magic, Magics
35 from IPython.core.magic import magics_class, line_magic, Magics
36 from IPython.core.payloadpage import install_payload_page
36 from IPython.core.payloadpage import install_payload_page
37 from IPython.inprocess.socket import SocketABC
37 from IPython.inprocess.socket import SocketABC
38 from IPython.utils.kernel import (
38 from IPython.kernel import (
39 get_connection_file, get_connection_info, connect_qtconsole
39 get_connection_file, get_connection_info, connect_qtconsole
40 )
40 )
41 from IPython.testing.skipdoctest import skip_doctest
41 from IPython.testing.skipdoctest import skip_doctest
42 from IPython.utils import io, openpy
42 from IPython.utils import io, openpy
43 from IPython.utils.jsonutil import json_clean, encode_images
43 from IPython.utils.jsonutil import json_clean, encode_images
44 from IPython.utils.process import arg_split
44 from IPython.utils.process import arg_split
45 from IPython.utils import py3compat
45 from IPython.utils import py3compat
46 from IPython.utils.traitlets import Instance, Type, Dict, CBool, CBytes
46 from IPython.utils.traitlets import Instance, Type, Dict, CBool, CBytes
47 from IPython.utils.warn import warn, error
47 from IPython.utils.warn import warn, error
48 from IPython.zmq.displayhook import ZMQShellDisplayHook
48 from IPython.zmq.displayhook import ZMQShellDisplayHook
49 from IPython.zmq.datapub import ZMQDataPublisher
49 from IPython.zmq.datapub import ZMQDataPublisher
50 from IPython.zmq.session import extract_header
50 from IPython.zmq.session import extract_header
51 from session import Session
51 from session import Session
52
52
53 #-----------------------------------------------------------------------------
53 #-----------------------------------------------------------------------------
54 # Functions and classes
54 # Functions and classes
55 #-----------------------------------------------------------------------------
55 #-----------------------------------------------------------------------------
56
56
57 class ZMQDisplayPublisher(DisplayPublisher):
57 class ZMQDisplayPublisher(DisplayPublisher):
58 """A display publisher that publishes data using a ZeroMQ PUB socket."""
58 """A display publisher that publishes data using a ZeroMQ PUB socket."""
59
59
60 session = Instance(Session)
60 session = Instance(Session)
61 pub_socket = Instance(SocketABC)
61 pub_socket = Instance(SocketABC)
62 parent_header = Dict({})
62 parent_header = Dict({})
63 topic = CBytes(b'displaypub')
63 topic = CBytes(b'displaypub')
64
64
65 def set_parent(self, parent):
65 def set_parent(self, parent):
66 """Set the parent for outbound messages."""
66 """Set the parent for outbound messages."""
67 self.parent_header = extract_header(parent)
67 self.parent_header = extract_header(parent)
68
68
69 def _flush_streams(self):
69 def _flush_streams(self):
70 """flush IO Streams prior to display"""
70 """flush IO Streams prior to display"""
71 sys.stdout.flush()
71 sys.stdout.flush()
72 sys.stderr.flush()
72 sys.stderr.flush()
73
73
74 def publish(self, source, data, metadata=None):
74 def publish(self, source, data, metadata=None):
75 self._flush_streams()
75 self._flush_streams()
76 if metadata is None:
76 if metadata is None:
77 metadata = {}
77 metadata = {}
78 self._validate_data(source, data, metadata)
78 self._validate_data(source, data, metadata)
79 content = {}
79 content = {}
80 content['source'] = source
80 content['source'] = source
81 content['data'] = encode_images(data)
81 content['data'] = encode_images(data)
82 content['metadata'] = metadata
82 content['metadata'] = metadata
83 self.session.send(
83 self.session.send(
84 self.pub_socket, u'display_data', json_clean(content),
84 self.pub_socket, u'display_data', json_clean(content),
85 parent=self.parent_header, ident=self.topic,
85 parent=self.parent_header, ident=self.topic,
86 )
86 )
87
87
88 def clear_output(self, stdout=True, stderr=True, other=True):
88 def clear_output(self, stdout=True, stderr=True, other=True):
89 content = dict(stdout=stdout, stderr=stderr, other=other)
89 content = dict(stdout=stdout, stderr=stderr, other=other)
90
90
91 if stdout:
91 if stdout:
92 print('\r', file=sys.stdout, end='')
92 print('\r', file=sys.stdout, end='')
93 if stderr:
93 if stderr:
94 print('\r', file=sys.stderr, end='')
94 print('\r', file=sys.stderr, end='')
95
95
96 self._flush_streams()
96 self._flush_streams()
97
97
98 self.session.send(
98 self.session.send(
99 self.pub_socket, u'clear_output', content,
99 self.pub_socket, u'clear_output', content,
100 parent=self.parent_header, ident=self.topic,
100 parent=self.parent_header, ident=self.topic,
101 )
101 )
102
102
103 @magics_class
103 @magics_class
104 class KernelMagics(Magics):
104 class KernelMagics(Magics):
105 #------------------------------------------------------------------------
105 #------------------------------------------------------------------------
106 # Magic overrides
106 # Magic overrides
107 #------------------------------------------------------------------------
107 #------------------------------------------------------------------------
108 # Once the base class stops inheriting from magic, this code needs to be
108 # Once the base class stops inheriting from magic, this code needs to be
109 # moved into a separate machinery as well. For now, at least isolate here
109 # moved into a separate machinery as well. For now, at least isolate here
110 # the magics which this class needs to implement differently from the base
110 # the magics which this class needs to implement differently from the base
111 # class, or that are unique to it.
111 # class, or that are unique to it.
112
112
113 @line_magic
113 @line_magic
114 def doctest_mode(self, parameter_s=''):
114 def doctest_mode(self, parameter_s=''):
115 """Toggle doctest mode on and off.
115 """Toggle doctest mode on and off.
116
116
117 This mode is intended to make IPython behave as much as possible like a
117 This mode is intended to make IPython behave as much as possible like a
118 plain Python shell, from the perspective of how its prompts, exceptions
118 plain Python shell, from the perspective of how its prompts, exceptions
119 and output look. This makes it easy to copy and paste parts of a
119 and output look. This makes it easy to copy and paste parts of a
120 session into doctests. It does so by:
120 session into doctests. It does so by:
121
121
122 - Changing the prompts to the classic ``>>>`` ones.
122 - Changing the prompts to the classic ``>>>`` ones.
123 - Changing the exception reporting mode to 'Plain'.
123 - Changing the exception reporting mode to 'Plain'.
124 - Disabling pretty-printing of output.
124 - Disabling pretty-printing of output.
125
125
126 Note that IPython also supports the pasting of code snippets that have
126 Note that IPython also supports the pasting of code snippets that have
127 leading '>>>' and '...' prompts in them. This means that you can paste
127 leading '>>>' and '...' prompts in them. This means that you can paste
128 doctests from files or docstrings (even if they have leading
128 doctests from files or docstrings (even if they have leading
129 whitespace), and the code will execute correctly. You can then use
129 whitespace), and the code will execute correctly. You can then use
130 '%history -t' to see the translated history; this will give you the
130 '%history -t' to see the translated history; this will give you the
131 input after removal of all the leading prompts and whitespace, which
131 input after removal of all the leading prompts and whitespace, which
132 can be pasted back into an editor.
132 can be pasted back into an editor.
133
133
134 With these features, you can switch into this mode easily whenever you
134 With these features, you can switch into this mode easily whenever you
135 need to do testing and changes to doctests, without having to leave
135 need to do testing and changes to doctests, without having to leave
136 your existing IPython session.
136 your existing IPython session.
137 """
137 """
138
138
139 from IPython.utils.ipstruct import Struct
139 from IPython.utils.ipstruct import Struct
140
140
141 # Shorthands
141 # Shorthands
142 shell = self.shell
142 shell = self.shell
143 disp_formatter = self.shell.display_formatter
143 disp_formatter = self.shell.display_formatter
144 ptformatter = disp_formatter.formatters['text/plain']
144 ptformatter = disp_formatter.formatters['text/plain']
145 # dstore is a data store kept in the instance metadata bag to track any
145 # dstore is a data store kept in the instance metadata bag to track any
146 # changes we make, so we can undo them later.
146 # changes we make, so we can undo them later.
147 dstore = shell.meta.setdefault('doctest_mode', Struct())
147 dstore = shell.meta.setdefault('doctest_mode', Struct())
148 save_dstore = dstore.setdefault
148 save_dstore = dstore.setdefault
149
149
150 # save a few values we'll need to recover later
150 # save a few values we'll need to recover later
151 mode = save_dstore('mode', False)
151 mode = save_dstore('mode', False)
152 save_dstore('rc_pprint', ptformatter.pprint)
152 save_dstore('rc_pprint', ptformatter.pprint)
153 save_dstore('rc_plain_text_only',disp_formatter.plain_text_only)
153 save_dstore('rc_plain_text_only',disp_formatter.plain_text_only)
154 save_dstore('xmode', shell.InteractiveTB.mode)
154 save_dstore('xmode', shell.InteractiveTB.mode)
155
155
156 if mode == False:
156 if mode == False:
157 # turn on
157 # turn on
158 ptformatter.pprint = False
158 ptformatter.pprint = False
159 disp_formatter.plain_text_only = True
159 disp_formatter.plain_text_only = True
160 shell.magic('xmode Plain')
160 shell.magic('xmode Plain')
161 else:
161 else:
162 # turn off
162 # turn off
163 ptformatter.pprint = dstore.rc_pprint
163 ptformatter.pprint = dstore.rc_pprint
164 disp_formatter.plain_text_only = dstore.rc_plain_text_only
164 disp_formatter.plain_text_only = dstore.rc_plain_text_only
165 shell.magic("xmode " + dstore.xmode)
165 shell.magic("xmode " + dstore.xmode)
166
166
167 # Store new mode and inform on console
167 # Store new mode and inform on console
168 dstore.mode = bool(1-int(mode))
168 dstore.mode = bool(1-int(mode))
169 mode_label = ['OFF','ON'][dstore.mode]
169 mode_label = ['OFF','ON'][dstore.mode]
170 print('Doctest mode is:', mode_label)
170 print('Doctest mode is:', mode_label)
171
171
172 # Send the payload back so that clients can modify their prompt display
172 # Send the payload back so that clients can modify their prompt display
173 payload = dict(
173 payload = dict(
174 source='IPython.zmq.zmqshell.ZMQInteractiveShell.doctest_mode',
174 source='IPython.zmq.zmqshell.ZMQInteractiveShell.doctest_mode',
175 mode=dstore.mode)
175 mode=dstore.mode)
176 shell.payload_manager.write_payload(payload)
176 shell.payload_manager.write_payload(payload)
177
177
178
178
179 _find_edit_target = CodeMagics._find_edit_target
179 _find_edit_target = CodeMagics._find_edit_target
180
180
181 @skip_doctest
181 @skip_doctest
182 @line_magic
182 @line_magic
183 def edit(self, parameter_s='', last_call=['','']):
183 def edit(self, parameter_s='', last_call=['','']):
184 """Bring up an editor and execute the resulting code.
184 """Bring up an editor and execute the resulting code.
185
185
186 Usage:
186 Usage:
187 %edit [options] [args]
187 %edit [options] [args]
188
188
189 %edit runs an external text editor. You will need to set the command for
189 %edit runs an external text editor. You will need to set the command for
190 this editor via the ``TerminalInteractiveShell.editor`` option in your
190 this editor via the ``TerminalInteractiveShell.editor`` option in your
191 configuration file before it will work.
191 configuration file before it will work.
192
192
193 This command allows you to conveniently edit multi-line code right in
193 This command allows you to conveniently edit multi-line code right in
194 your IPython session.
194 your IPython session.
195
195
196 If called without arguments, %edit opens up an empty editor with a
196 If called without arguments, %edit opens up an empty editor with a
197 temporary file and will execute the contents of this file when you
197 temporary file and will execute the contents of this file when you
198 close it (don't forget to save it!).
198 close it (don't forget to save it!).
199
199
200
200
201 Options:
201 Options:
202
202
203 -n <number>: open the editor at a specified line number. By default,
203 -n <number>: open the editor at a specified line number. By default,
204 the IPython editor hook uses the unix syntax 'editor +N filename', but
204 the IPython editor hook uses the unix syntax 'editor +N filename', but
205 you can configure this by providing your own modified hook if your
205 you can configure this by providing your own modified hook if your
206 favorite editor supports line-number specifications with a different
206 favorite editor supports line-number specifications with a different
207 syntax.
207 syntax.
208
208
209 -p: this will call the editor with the same data as the previous time
209 -p: this will call the editor with the same data as the previous time
210 it was used, regardless of how long ago (in your current session) it
210 it was used, regardless of how long ago (in your current session) it
211 was.
211 was.
212
212
213 -r: use 'raw' input. This option only applies to input taken from the
213 -r: use 'raw' input. This option only applies to input taken from the
214 user's history. By default, the 'processed' history is used, so that
214 user's history. By default, the 'processed' history is used, so that
215 magics are loaded in their transformed version to valid Python. If
215 magics are loaded in their transformed version to valid Python. If
216 this option is given, the raw input as typed as the command line is
216 this option is given, the raw input as typed as the command line is
217 used instead. When you exit the editor, it will be executed by
217 used instead. When you exit the editor, it will be executed by
218 IPython's own processor.
218 IPython's own processor.
219
219
220 -x: do not execute the edited code immediately upon exit. This is
220 -x: do not execute the edited code immediately upon exit. This is
221 mainly useful if you are editing programs which need to be called with
221 mainly useful if you are editing programs which need to be called with
222 command line arguments, which you can then do using %run.
222 command line arguments, which you can then do using %run.
223
223
224
224
225 Arguments:
225 Arguments:
226
226
227 If arguments are given, the following possibilites exist:
227 If arguments are given, the following possibilites exist:
228
228
229 - The arguments are numbers or pairs of colon-separated numbers (like
229 - The arguments are numbers or pairs of colon-separated numbers (like
230 1 4:8 9). These are interpreted as lines of previous input to be
230 1 4:8 9). These are interpreted as lines of previous input to be
231 loaded into the editor. The syntax is the same of the %macro command.
231 loaded into the editor. The syntax is the same of the %macro command.
232
232
233 - If the argument doesn't start with a number, it is evaluated as a
233 - If the argument doesn't start with a number, it is evaluated as a
234 variable and its contents loaded into the editor. You can thus edit
234 variable and its contents loaded into the editor. You can thus edit
235 any string which contains python code (including the result of
235 any string which contains python code (including the result of
236 previous edits).
236 previous edits).
237
237
238 - If the argument is the name of an object (other than a string),
238 - If the argument is the name of an object (other than a string),
239 IPython will try to locate the file where it was defined and open the
239 IPython will try to locate the file where it was defined and open the
240 editor at the point where it is defined. You can use `%edit function`
240 editor at the point where it is defined. You can use `%edit function`
241 to load an editor exactly at the point where 'function' is defined,
241 to load an editor exactly at the point where 'function' is defined,
242 edit it and have the file be executed automatically.
242 edit it and have the file be executed automatically.
243
243
244 If the object is a macro (see %macro for details), this opens up your
244 If the object is a macro (see %macro for details), this opens up your
245 specified editor with a temporary file containing the macro's data.
245 specified editor with a temporary file containing the macro's data.
246 Upon exit, the macro is reloaded with the contents of the file.
246 Upon exit, the macro is reloaded with the contents of the file.
247
247
248 Note: opening at an exact line is only supported under Unix, and some
248 Note: opening at an exact line is only supported under Unix, and some
249 editors (like kedit and gedit up to Gnome 2.8) do not understand the
249 editors (like kedit and gedit up to Gnome 2.8) do not understand the
250 '+NUMBER' parameter necessary for this feature. Good editors like
250 '+NUMBER' parameter necessary for this feature. Good editors like
251 (X)Emacs, vi, jed, pico and joe all do.
251 (X)Emacs, vi, jed, pico and joe all do.
252
252
253 - If the argument is not found as a variable, IPython will look for a
253 - If the argument is not found as a variable, IPython will look for a
254 file with that name (adding .py if necessary) and load it into the
254 file with that name (adding .py if necessary) and load it into the
255 editor. It will execute its contents with execfile() when you exit,
255 editor. It will execute its contents with execfile() when you exit,
256 loading any code in the file into your interactive namespace.
256 loading any code in the file into your interactive namespace.
257
257
258 After executing your code, %edit will return as output the code you
258 After executing your code, %edit will return as output the code you
259 typed in the editor (except when it was an existing file). This way
259 typed in the editor (except when it was an existing file). This way
260 you can reload the code in further invocations of %edit as a variable,
260 you can reload the code in further invocations of %edit as a variable,
261 via _<NUMBER> or Out[<NUMBER>], where <NUMBER> is the prompt number of
261 via _<NUMBER> or Out[<NUMBER>], where <NUMBER> is the prompt number of
262 the output.
262 the output.
263
263
264 Note that %edit is also available through the alias %ed.
264 Note that %edit is also available through the alias %ed.
265
265
266 This is an example of creating a simple function inside the editor and
266 This is an example of creating a simple function inside the editor and
267 then modifying it. First, start up the editor:
267 then modifying it. First, start up the editor:
268
268
269 In [1]: ed
269 In [1]: ed
270 Editing... done. Executing edited code...
270 Editing... done. Executing edited code...
271 Out[1]: 'def foo():n print "foo() was defined in an editing session"n'
271 Out[1]: 'def foo():n print "foo() was defined in an editing session"n'
272
272
273 We can then call the function foo():
273 We can then call the function foo():
274
274
275 In [2]: foo()
275 In [2]: foo()
276 foo() was defined in an editing session
276 foo() was defined in an editing session
277
277
278 Now we edit foo. IPython automatically loads the editor with the
278 Now we edit foo. IPython automatically loads the editor with the
279 (temporary) file where foo() was previously defined:
279 (temporary) file where foo() was previously defined:
280
280
281 In [3]: ed foo
281 In [3]: ed foo
282 Editing... done. Executing edited code...
282 Editing... done. Executing edited code...
283
283
284 And if we call foo() again we get the modified version:
284 And if we call foo() again we get the modified version:
285
285
286 In [4]: foo()
286 In [4]: foo()
287 foo() has now been changed!
287 foo() has now been changed!
288
288
289 Here is an example of how to edit a code snippet successive
289 Here is an example of how to edit a code snippet successive
290 times. First we call the editor:
290 times. First we call the editor:
291
291
292 In [5]: ed
292 In [5]: ed
293 Editing... done. Executing edited code...
293 Editing... done. Executing edited code...
294 hello
294 hello
295 Out[5]: "print 'hello'n"
295 Out[5]: "print 'hello'n"
296
296
297 Now we call it again with the previous output (stored in _):
297 Now we call it again with the previous output (stored in _):
298
298
299 In [6]: ed _
299 In [6]: ed _
300 Editing... done. Executing edited code...
300 Editing... done. Executing edited code...
301 hello world
301 hello world
302 Out[6]: "print 'hello world'n"
302 Out[6]: "print 'hello world'n"
303
303
304 Now we call it with the output #8 (stored in _8, also as Out[8]):
304 Now we call it with the output #8 (stored in _8, also as Out[8]):
305
305
306 In [7]: ed _8
306 In [7]: ed _8
307 Editing... done. Executing edited code...
307 Editing... done. Executing edited code...
308 hello again
308 hello again
309 Out[7]: "print 'hello again'n"
309 Out[7]: "print 'hello again'n"
310 """
310 """
311
311
312 opts,args = self.parse_options(parameter_s,'prn:')
312 opts,args = self.parse_options(parameter_s,'prn:')
313
313
314 try:
314 try:
315 filename, lineno, _ = CodeMagics._find_edit_target(self.shell, args, opts, last_call)
315 filename, lineno, _ = CodeMagics._find_edit_target(self.shell, args, opts, last_call)
316 except MacroToEdit as e:
316 except MacroToEdit as e:
317 # TODO: Implement macro editing over 2 processes.
317 # TODO: Implement macro editing over 2 processes.
318 print("Macro editing not yet implemented in 2-process model.")
318 print("Macro editing not yet implemented in 2-process model.")
319 return
319 return
320
320
321 # Make sure we send to the client an absolute path, in case the working
321 # Make sure we send to the client an absolute path, in case the working
322 # directory of client and kernel don't match
322 # directory of client and kernel don't match
323 filename = os.path.abspath(filename)
323 filename = os.path.abspath(filename)
324
324
325 payload = {
325 payload = {
326 'source' : 'IPython.zmq.zmqshell.ZMQInteractiveShell.edit_magic',
326 'source' : 'IPython.zmq.zmqshell.ZMQInteractiveShell.edit_magic',
327 'filename' : filename,
327 'filename' : filename,
328 'line_number' : lineno
328 'line_number' : lineno
329 }
329 }
330 self.shell.payload_manager.write_payload(payload)
330 self.shell.payload_manager.write_payload(payload)
331
331
332 # A few magics that are adapted to the specifics of using pexpect and a
332 # A few magics that are adapted to the specifics of using pexpect and a
333 # remote terminal
333 # remote terminal
334
334
335 @line_magic
335 @line_magic
336 def clear(self, arg_s):
336 def clear(self, arg_s):
337 """Clear the terminal."""
337 """Clear the terminal."""
338 if os.name == 'posix':
338 if os.name == 'posix':
339 self.shell.system("clear")
339 self.shell.system("clear")
340 else:
340 else:
341 self.shell.system("cls")
341 self.shell.system("cls")
342
342
343 if os.name == 'nt':
343 if os.name == 'nt':
344 # This is the usual name in windows
344 # This is the usual name in windows
345 cls = line_magic('cls')(clear)
345 cls = line_magic('cls')(clear)
346
346
347 # Terminal pagers won't work over pexpect, but we do have our own pager
347 # Terminal pagers won't work over pexpect, but we do have our own pager
348
348
349 @line_magic
349 @line_magic
350 def less(self, arg_s):
350 def less(self, arg_s):
351 """Show a file through the pager.
351 """Show a file through the pager.
352
352
353 Files ending in .py are syntax-highlighted."""
353 Files ending in .py are syntax-highlighted."""
354 if not arg_s:
354 if not arg_s:
355 raise UsageError('Missing filename.')
355 raise UsageError('Missing filename.')
356
356
357 cont = open(arg_s).read()
357 cont = open(arg_s).read()
358 if arg_s.endswith('.py'):
358 if arg_s.endswith('.py'):
359 cont = self.shell.pycolorize(openpy.read_py_file(arg_s, skip_encoding_cookie=False))
359 cont = self.shell.pycolorize(openpy.read_py_file(arg_s, skip_encoding_cookie=False))
360 else:
360 else:
361 cont = open(arg_s).read()
361 cont = open(arg_s).read()
362 page.page(cont)
362 page.page(cont)
363
363
364 more = line_magic('more')(less)
364 more = line_magic('more')(less)
365
365
366 # Man calls a pager, so we also need to redefine it
366 # Man calls a pager, so we also need to redefine it
367 if os.name == 'posix':
367 if os.name == 'posix':
368 @line_magic
368 @line_magic
369 def man(self, arg_s):
369 def man(self, arg_s):
370 """Find the man page for the given command and display in pager."""
370 """Find the man page for the given command and display in pager."""
371 page.page(self.shell.getoutput('man %s | col -b' % arg_s,
371 page.page(self.shell.getoutput('man %s | col -b' % arg_s,
372 split=False))
372 split=False))
373
373
374 @line_magic
374 @line_magic
375 def connect_info(self, arg_s):
375 def connect_info(self, arg_s):
376 """Print information for connecting other clients to this kernel
376 """Print information for connecting other clients to this kernel
377
377
378 It will print the contents of this session's connection file, as well as
378 It will print the contents of this session's connection file, as well as
379 shortcuts for local clients.
379 shortcuts for local clients.
380
380
381 In the simplest case, when called from the most recently launched kernel,
381 In the simplest case, when called from the most recently launched kernel,
382 secondary clients can be connected, simply with:
382 secondary clients can be connected, simply with:
383
383
384 $> ipython <app> --existing
384 $> ipython <app> --existing
385
385
386 """
386 """
387
387
388 from IPython.core.application import BaseIPythonApplication as BaseIPApp
388 from IPython.core.application import BaseIPythonApplication as BaseIPApp
389
389
390 if BaseIPApp.initialized():
390 if BaseIPApp.initialized():
391 app = BaseIPApp.instance()
391 app = BaseIPApp.instance()
392 security_dir = app.profile_dir.security_dir
392 security_dir = app.profile_dir.security_dir
393 profile = app.profile
393 profile = app.profile
394 else:
394 else:
395 profile = 'default'
395 profile = 'default'
396 security_dir = ''
396 security_dir = ''
397
397
398 try:
398 try:
399 connection_file = get_connection_file()
399 connection_file = get_connection_file()
400 info = get_connection_info(unpack=False)
400 info = get_connection_info(unpack=False)
401 except Exception as e:
401 except Exception as e:
402 error("Could not get connection info: %r" % e)
402 error("Could not get connection info: %r" % e)
403 return
403 return
404
404
405 # add profile flag for non-default profile
405 # add profile flag for non-default profile
406 profile_flag = "--profile %s" % profile if profile != 'default' else ""
406 profile_flag = "--profile %s" % profile if profile != 'default' else ""
407
407
408 # if it's in the security dir, truncate to basename
408 # if it's in the security dir, truncate to basename
409 if security_dir == os.path.dirname(connection_file):
409 if security_dir == os.path.dirname(connection_file):
410 connection_file = os.path.basename(connection_file)
410 connection_file = os.path.basename(connection_file)
411
411
412
412
413 print (info + '\n')
413 print (info + '\n')
414 print ("Paste the above JSON into a file, and connect with:\n"
414 print ("Paste the above JSON into a file, and connect with:\n"
415 " $> ipython <app> --existing <file>\n"
415 " $> ipython <app> --existing <file>\n"
416 "or, if you are local, you can connect with just:\n"
416 "or, if you are local, you can connect with just:\n"
417 " $> ipython <app> --existing {0} {1}\n"
417 " $> ipython <app> --existing {0} {1}\n"
418 "or even just:\n"
418 "or even just:\n"
419 " $> ipython <app> --existing {1}\n"
419 " $> ipython <app> --existing {1}\n"
420 "if this is the most recent IPython session you have started.".format(
420 "if this is the most recent IPython session you have started.".format(
421 connection_file, profile_flag
421 connection_file, profile_flag
422 )
422 )
423 )
423 )
424
424
425 @line_magic
425 @line_magic
426 def qtconsole(self, arg_s):
426 def qtconsole(self, arg_s):
427 """Open a qtconsole connected to this kernel.
427 """Open a qtconsole connected to this kernel.
428
428
429 Useful for connecting a qtconsole to running notebooks, for better
429 Useful for connecting a qtconsole to running notebooks, for better
430 debugging.
430 debugging.
431 """
431 """
432
432
433 # %qtconsole should imply bind_kernel for engines:
433 # %qtconsole should imply bind_kernel for engines:
434 try:
434 try:
435 from IPython.parallel import bind_kernel
435 from IPython.parallel import bind_kernel
436 except ImportError:
436 except ImportError:
437 # technically possible, because parallel has higher pyzmq min-version
437 # technically possible, because parallel has higher pyzmq min-version
438 pass
438 pass
439 else:
439 else:
440 bind_kernel()
440 bind_kernel()
441
441
442 try:
442 try:
443 p = connect_qtconsole(argv=arg_split(arg_s, os.name=='posix'))
443 p = connect_qtconsole(argv=arg_split(arg_s, os.name=='posix'))
444 except Exception as e:
444 except Exception as e:
445 error("Could not start qtconsole: %r" % e)
445 error("Could not start qtconsole: %r" % e)
446 return
446 return
447
447
448 def safe_unicode(e):
448 def safe_unicode(e):
449 """unicode(e) with various fallbacks. Used for exceptions, which may not be
449 """unicode(e) with various fallbacks. Used for exceptions, which may not be
450 safe to call unicode() on.
450 safe to call unicode() on.
451 """
451 """
452 try:
452 try:
453 return unicode(e)
453 return unicode(e)
454 except UnicodeError:
454 except UnicodeError:
455 pass
455 pass
456
456
457 try:
457 try:
458 return py3compat.str_to_unicode(str(e))
458 return py3compat.str_to_unicode(str(e))
459 except UnicodeError:
459 except UnicodeError:
460 pass
460 pass
461
461
462 try:
462 try:
463 return py3compat.str_to_unicode(repr(e))
463 return py3compat.str_to_unicode(repr(e))
464 except UnicodeError:
464 except UnicodeError:
465 pass
465 pass
466
466
467 return u'Unrecoverably corrupt evalue'
467 return u'Unrecoverably corrupt evalue'
468
468
469
469
470 class ZMQInteractiveShell(InteractiveShell):
470 class ZMQInteractiveShell(InteractiveShell):
471 """A subclass of InteractiveShell for ZMQ."""
471 """A subclass of InteractiveShell for ZMQ."""
472
472
473 displayhook_class = Type(ZMQShellDisplayHook)
473 displayhook_class = Type(ZMQShellDisplayHook)
474 display_pub_class = Type(ZMQDisplayPublisher)
474 display_pub_class = Type(ZMQDisplayPublisher)
475 data_pub_class = Type(ZMQDataPublisher)
475 data_pub_class = Type(ZMQDataPublisher)
476
476
477 # Override the traitlet in the parent class, because there's no point using
477 # Override the traitlet in the parent class, because there's no point using
478 # readline for the kernel. Can be removed when the readline code is moved
478 # readline for the kernel. Can be removed when the readline code is moved
479 # to the terminal frontend.
479 # to the terminal frontend.
480 colors_force = CBool(True)
480 colors_force = CBool(True)
481 readline_use = CBool(False)
481 readline_use = CBool(False)
482 # autoindent has no meaning in a zmqshell, and attempting to enable it
482 # autoindent has no meaning in a zmqshell, and attempting to enable it
483 # will print a warning in the absence of readline.
483 # will print a warning in the absence of readline.
484 autoindent = CBool(False)
484 autoindent = CBool(False)
485
485
486 exiter = Instance(ZMQExitAutocall)
486 exiter = Instance(ZMQExitAutocall)
487 def _exiter_default(self):
487 def _exiter_default(self):
488 return ZMQExitAutocall(self)
488 return ZMQExitAutocall(self)
489
489
490 def _exit_now_changed(self, name, old, new):
490 def _exit_now_changed(self, name, old, new):
491 """stop eventloop when exit_now fires"""
491 """stop eventloop when exit_now fires"""
492 if new:
492 if new:
493 loop = ioloop.IOLoop.instance()
493 loop = ioloop.IOLoop.instance()
494 loop.add_timeout(time.time()+0.1, loop.stop)
494 loop.add_timeout(time.time()+0.1, loop.stop)
495
495
496 keepkernel_on_exit = None
496 keepkernel_on_exit = None
497
497
498 # Over ZeroMQ, GUI control isn't done with PyOS_InputHook as there is no
498 # Over ZeroMQ, GUI control isn't done with PyOS_InputHook as there is no
499 # interactive input being read; we provide event loop support in ipkernel
499 # interactive input being read; we provide event loop support in ipkernel
500 from .eventloops import enable_gui
500 from .eventloops import enable_gui
501 enable_gui = staticmethod(enable_gui)
501 enable_gui = staticmethod(enable_gui)
502
502
503 def init_environment(self):
503 def init_environment(self):
504 """Configure the user's environment.
504 """Configure the user's environment.
505
505
506 """
506 """
507 env = os.environ
507 env = os.environ
508 # These two ensure 'ls' produces nice coloring on BSD-derived systems
508 # These two ensure 'ls' produces nice coloring on BSD-derived systems
509 env['TERM'] = 'xterm-color'
509 env['TERM'] = 'xterm-color'
510 env['CLICOLOR'] = '1'
510 env['CLICOLOR'] = '1'
511 # Since normal pagers don't work at all (over pexpect we don't have
511 # Since normal pagers don't work at all (over pexpect we don't have
512 # single-key control of the subprocess), try to disable paging in
512 # single-key control of the subprocess), try to disable paging in
513 # subprocesses as much as possible.
513 # subprocesses as much as possible.
514 env['PAGER'] = 'cat'
514 env['PAGER'] = 'cat'
515 env['GIT_PAGER'] = 'cat'
515 env['GIT_PAGER'] = 'cat'
516
516
517 # And install the payload version of page.
517 # And install the payload version of page.
518 install_payload_page()
518 install_payload_page()
519
519
520 def auto_rewrite_input(self, cmd):
520 def auto_rewrite_input(self, cmd):
521 """Called to show the auto-rewritten input for autocall and friends.
521 """Called to show the auto-rewritten input for autocall and friends.
522
522
523 FIXME: this payload is currently not correctly processed by the
523 FIXME: this payload is currently not correctly processed by the
524 frontend.
524 frontend.
525 """
525 """
526 new = self.prompt_manager.render('rewrite') + cmd
526 new = self.prompt_manager.render('rewrite') + cmd
527 payload = dict(
527 payload = dict(
528 source='IPython.zmq.zmqshell.ZMQInteractiveShell.auto_rewrite_input',
528 source='IPython.zmq.zmqshell.ZMQInteractiveShell.auto_rewrite_input',
529 transformed_input=new,
529 transformed_input=new,
530 )
530 )
531 self.payload_manager.write_payload(payload)
531 self.payload_manager.write_payload(payload)
532
532
533 def ask_exit(self):
533 def ask_exit(self):
534 """Engage the exit actions."""
534 """Engage the exit actions."""
535 self.exit_now = True
535 self.exit_now = True
536 payload = dict(
536 payload = dict(
537 source='IPython.zmq.zmqshell.ZMQInteractiveShell.ask_exit',
537 source='IPython.zmq.zmqshell.ZMQInteractiveShell.ask_exit',
538 exit=True,
538 exit=True,
539 keepkernel=self.keepkernel_on_exit,
539 keepkernel=self.keepkernel_on_exit,
540 )
540 )
541 self.payload_manager.write_payload(payload)
541 self.payload_manager.write_payload(payload)
542
542
543 def _showtraceback(self, etype, evalue, stb):
543 def _showtraceback(self, etype, evalue, stb):
544
544
545 exc_content = {
545 exc_content = {
546 u'traceback' : stb,
546 u'traceback' : stb,
547 u'ename' : unicode(etype.__name__),
547 u'ename' : unicode(etype.__name__),
548 u'evalue' : safe_unicode(evalue)
548 u'evalue' : safe_unicode(evalue)
549 }
549 }
550
550
551 dh = self.displayhook
551 dh = self.displayhook
552 # Send exception info over pub socket for other clients than the caller
552 # Send exception info over pub socket for other clients than the caller
553 # to pick up
553 # to pick up
554 topic = None
554 topic = None
555 if dh.topic:
555 if dh.topic:
556 topic = dh.topic.replace(b'pyout', b'pyerr')
556 topic = dh.topic.replace(b'pyout', b'pyerr')
557
557
558 exc_msg = dh.session.send(dh.pub_socket, u'pyerr', json_clean(exc_content), dh.parent_header, ident=topic)
558 exc_msg = dh.session.send(dh.pub_socket, u'pyerr', json_clean(exc_content), dh.parent_header, ident=topic)
559
559
560 # FIXME - Hack: store exception info in shell object. Right now, the
560 # FIXME - Hack: store exception info in shell object. Right now, the
561 # caller is reading this info after the fact, we need to fix this logic
561 # caller is reading this info after the fact, we need to fix this logic
562 # to remove this hack. Even uglier, we need to store the error status
562 # to remove this hack. Even uglier, we need to store the error status
563 # here, because in the main loop, the logic that sets it is being
563 # here, because in the main loop, the logic that sets it is being
564 # skipped because runlines swallows the exceptions.
564 # skipped because runlines swallows the exceptions.
565 exc_content[u'status'] = u'error'
565 exc_content[u'status'] = u'error'
566 self._reply_content = exc_content
566 self._reply_content = exc_content
567 # /FIXME
567 # /FIXME
568
568
569 return exc_content
569 return exc_content
570
570
571 def set_next_input(self, text):
571 def set_next_input(self, text):
572 """Send the specified text to the frontend to be presented at the next
572 """Send the specified text to the frontend to be presented at the next
573 input cell."""
573 input cell."""
574 payload = dict(
574 payload = dict(
575 source='IPython.zmq.zmqshell.ZMQInteractiveShell.set_next_input',
575 source='IPython.zmq.zmqshell.ZMQInteractiveShell.set_next_input',
576 text=text
576 text=text
577 )
577 )
578 self.payload_manager.write_payload(payload)
578 self.payload_manager.write_payload(payload)
579
579
580 #-------------------------------------------------------------------------
580 #-------------------------------------------------------------------------
581 # Things related to magics
581 # Things related to magics
582 #-------------------------------------------------------------------------
582 #-------------------------------------------------------------------------
583
583
584 def init_magics(self):
584 def init_magics(self):
585 super(ZMQInteractiveShell, self).init_magics()
585 super(ZMQInteractiveShell, self).init_magics()
586 self.register_magics(KernelMagics)
586 self.register_magics(KernelMagics)
587 self.magics_manager.register_alias('ed', 'edit')
587 self.magics_manager.register_alias('ed', 'edit')
588
588
589
589
590
590
591 InteractiveShellABC.register(ZMQInteractiveShell)
591 InteractiveShellABC.register(ZMQInteractiveShell)
General Comments 0
You need to be logged in to leave comments. Login now