##// END OF EJS Templates
Merge pull request #4980 from minrk/remove-parent-app...
Thomas Kluyver -
r17102:c94cad18 merge
parent child Browse files
Show More
@@ -1,353 +1,351 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/qt/console/qtconsoleapp.py
5 refactoring of what used to be the IPython/qt/console/qtconsoleapp.py
6 """
6 """
7 # Copyright (c) IPython Development Team.
7 # Copyright (c) IPython Development Team.
8 # Distributed under the terms of the Modified BSD License.
8 # Distributed under the terms of the Modified BSD License.
9
9
10 #-----------------------------------------------------------------------------
10 #-----------------------------------------------------------------------------
11 # Imports
11 # Imports
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13
13
14 # stdlib imports
14 # stdlib imports
15 import atexit
15 import atexit
16 import os
16 import os
17 import signal
17 import signal
18 import sys
18 import sys
19 import uuid
19 import uuid
20
20
21
21
22 # Local imports
22 # Local imports
23 from IPython.config.application import boolean_flag
23 from IPython.config.application import boolean_flag
24 from IPython.core.profiledir import ProfileDir
24 from IPython.core.profiledir import ProfileDir
25 from IPython.kernel.blocking import BlockingKernelClient
25 from IPython.kernel.blocking import BlockingKernelClient
26 from IPython.kernel import KernelManager
26 from IPython.kernel import KernelManager
27 from IPython.kernel import tunnel_to_kernel, find_connection_file, swallow_argv
27 from IPython.kernel import tunnel_to_kernel, find_connection_file, swallow_argv
28 from IPython.kernel.kernelspec import NoSuchKernel
28 from IPython.kernel.kernelspec import NoSuchKernel
29 from IPython.utils.path import filefind
29 from IPython.utils.path import filefind
30 from IPython.utils.traitlets import (
30 from IPython.utils.traitlets import (
31 Dict, List, Unicode, CUnicode, CBool, Any
31 Dict, List, Unicode, CUnicode, CBool, Any
32 )
32 )
33 from IPython.kernel.zmq.kernelapp import (
33 from IPython.kernel.zmq.kernelapp import (
34 kernel_flags,
34 kernel_flags,
35 kernel_aliases,
35 kernel_aliases,
36 IPKernelApp
36 IPKernelApp
37 )
37 )
38 from IPython.kernel.zmq.pylab.config import InlineBackend
38 from IPython.kernel.zmq.pylab.config import InlineBackend
39 from IPython.kernel.zmq.session import Session, default_secure
39 from IPython.kernel.zmq.session import Session, default_secure
40 from IPython.kernel.zmq.zmqshell import ZMQInteractiveShell
40 from IPython.kernel.zmq.zmqshell import ZMQInteractiveShell
41 from IPython.kernel.connect import ConnectionFileMixin
41 from IPython.kernel.connect import ConnectionFileMixin
42
42
43 #-----------------------------------------------------------------------------
43 #-----------------------------------------------------------------------------
44 # Network Constants
44 # Network Constants
45 #-----------------------------------------------------------------------------
45 #-----------------------------------------------------------------------------
46
46
47 from IPython.utils.localinterfaces import localhost
47 from IPython.utils.localinterfaces import localhost
48
48
49 #-----------------------------------------------------------------------------
49 #-----------------------------------------------------------------------------
50 # Globals
50 # Globals
51 #-----------------------------------------------------------------------------
51 #-----------------------------------------------------------------------------
52
52
53
53
54 #-----------------------------------------------------------------------------
54 #-----------------------------------------------------------------------------
55 # Aliases and Flags
55 # Aliases and Flags
56 #-----------------------------------------------------------------------------
56 #-----------------------------------------------------------------------------
57
57
58 flags = dict(kernel_flags)
58 flags = dict(kernel_flags)
59
59
60 # the flags that are specific to the frontend
60 # the flags that are specific to the frontend
61 # these must be scrubbed before being passed to the kernel,
61 # these must be scrubbed before being passed to the kernel,
62 # or it will raise an error on unrecognized flags
62 # or it will raise an error on unrecognized flags
63 app_flags = {
63 app_flags = {
64 'existing' : ({'IPythonConsoleApp' : {'existing' : 'kernel*.json'}},
64 'existing' : ({'IPythonConsoleApp' : {'existing' : 'kernel*.json'}},
65 "Connect to an existing kernel. If no argument specified, guess most recent"),
65 "Connect to an existing kernel. If no argument specified, guess most recent"),
66 }
66 }
67 app_flags.update(boolean_flag(
67 app_flags.update(boolean_flag(
68 'confirm-exit', 'IPythonConsoleApp.confirm_exit',
68 'confirm-exit', 'IPythonConsoleApp.confirm_exit',
69 """Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
69 """Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
70 to force a direct exit without any confirmation.
70 to force a direct exit without any confirmation.
71 """,
71 """,
72 """Don't prompt the user when exiting. This will terminate the kernel
72 """Don't prompt the user when exiting. This will terminate the kernel
73 if it is owned by the frontend, and leave it alive if it is external.
73 if it is owned by the frontend, and leave it alive if it is external.
74 """
74 """
75 ))
75 ))
76 flags.update(app_flags)
76 flags.update(app_flags)
77
77
78 aliases = dict(kernel_aliases)
78 aliases = dict(kernel_aliases)
79
79
80 # also scrub aliases from the frontend
80 # also scrub aliases from the frontend
81 app_aliases = dict(
81 app_aliases = dict(
82 ip = 'IPythonConsoleApp.ip',
82 ip = 'IPythonConsoleApp.ip',
83 transport = 'IPythonConsoleApp.transport',
83 transport = 'IPythonConsoleApp.transport',
84 hb = 'IPythonConsoleApp.hb_port',
84 hb = 'IPythonConsoleApp.hb_port',
85 shell = 'IPythonConsoleApp.shell_port',
85 shell = 'IPythonConsoleApp.shell_port',
86 iopub = 'IPythonConsoleApp.iopub_port',
86 iopub = 'IPythonConsoleApp.iopub_port',
87 stdin = 'IPythonConsoleApp.stdin_port',
87 stdin = 'IPythonConsoleApp.stdin_port',
88 existing = 'IPythonConsoleApp.existing',
88 existing = 'IPythonConsoleApp.existing',
89 f = 'IPythonConsoleApp.connection_file',
89 f = 'IPythonConsoleApp.connection_file',
90
90
91 kernel = 'IPythonConsoleApp.kernel_name',
91 kernel = 'IPythonConsoleApp.kernel_name',
92
92
93 ssh = 'IPythonConsoleApp.sshserver',
93 ssh = 'IPythonConsoleApp.sshserver',
94 )
94 )
95 aliases.update(app_aliases)
95 aliases.update(app_aliases)
96
96
97 #-----------------------------------------------------------------------------
97 #-----------------------------------------------------------------------------
98 # Classes
98 # Classes
99 #-----------------------------------------------------------------------------
99 #-----------------------------------------------------------------------------
100
100
101 #-----------------------------------------------------------------------------
101 #-----------------------------------------------------------------------------
102 # IPythonConsole
102 # IPythonConsole
103 #-----------------------------------------------------------------------------
103 #-----------------------------------------------------------------------------
104
104
105 classes = [IPKernelApp, ZMQInteractiveShell, KernelManager, ProfileDir, Session, InlineBackend]
105 classes = [IPKernelApp, ZMQInteractiveShell, KernelManager, ProfileDir, Session, InlineBackend]
106
106
107 class IPythonConsoleApp(ConnectionFileMixin):
107 class IPythonConsoleApp(ConnectionFileMixin):
108 name = 'ipython-console-mixin'
108 name = 'ipython-console-mixin'
109
109
110 description = """
110 description = """
111 The IPython Mixin Console.
111 The IPython Mixin Console.
112
112
113 This class contains the common portions of console client (QtConsole,
113 This class contains the common portions of console client (QtConsole,
114 ZMQ-based terminal console, etc). It is not a full console, in that
114 ZMQ-based terminal console, etc). It is not a full console, in that
115 launched terminal subprocesses will not be able to accept input.
115 launched terminal subprocesses will not be able to accept input.
116
116
117 The Console using this mixing supports various extra features beyond
117 The Console using this mixing supports various extra features beyond
118 the single-process Terminal IPython shell, such as connecting to
118 the single-process Terminal IPython shell, such as connecting to
119 existing kernel, via:
119 existing kernel, via:
120
120
121 ipython <appname> --existing
121 ipython <appname> --existing
122
122
123 as well as tunnel via SSH
123 as well as tunnel via SSH
124
124
125 """
125 """
126
126
127 classes = classes
127 classes = classes
128 flags = Dict(flags)
128 flags = Dict(flags)
129 aliases = Dict(aliases)
129 aliases = Dict(aliases)
130 kernel_manager_class = KernelManager
130 kernel_manager_class = KernelManager
131 kernel_client_class = BlockingKernelClient
131 kernel_client_class = BlockingKernelClient
132
132
133 kernel_argv = List(Unicode)
133 kernel_argv = List(Unicode)
134 # frontend flags&aliases to be stripped when building kernel_argv
134 # frontend flags&aliases to be stripped when building kernel_argv
135 frontend_flags = Any(app_flags)
135 frontend_flags = Any(app_flags)
136 frontend_aliases = Any(app_aliases)
136 frontend_aliases = Any(app_aliases)
137
137
138 # create requested profiles by default, if they don't exist:
138 # create requested profiles by default, if they don't exist:
139 auto_create = CBool(True)
139 auto_create = CBool(True)
140 # connection info:
140 # connection info:
141
141
142 sshserver = Unicode('', config=True,
142 sshserver = Unicode('', config=True,
143 help="""The SSH server to use to connect to the kernel.""")
143 help="""The SSH server to use to connect to the kernel.""")
144 sshkey = Unicode('', config=True,
144 sshkey = Unicode('', config=True,
145 help="""Path to the ssh key to use for logging in to the ssh server.""")
145 help="""Path to the ssh key to use for logging in to the ssh server.""")
146
146
147 def _connection_file_default(self):
147 def _connection_file_default(self):
148 return 'kernel-%i.json' % os.getpid()
148 return 'kernel-%i.json' % os.getpid()
149
149
150 existing = CUnicode('', config=True,
150 existing = CUnicode('', config=True,
151 help="""Connect to an already running kernel""")
151 help="""Connect to an already running kernel""")
152
152
153 kernel_name = Unicode('python', config=True,
153 kernel_name = Unicode('python', config=True,
154 help="""The name of the default kernel to start.""")
154 help="""The name of the default kernel to start.""")
155
155
156 confirm_exit = CBool(True, config=True,
156 confirm_exit = CBool(True, config=True,
157 help="""
157 help="""
158 Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
158 Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
159 to force a direct exit without any confirmation.""",
159 to force a direct exit without any confirmation.""",
160 )
160 )
161
161
162
162
163 def build_kernel_argv(self, argv=None):
163 def build_kernel_argv(self, argv=None):
164 """build argv to be passed to kernel subprocess"""
164 """build argv to be passed to kernel subprocess"""
165 if argv is None:
165 if argv is None:
166 argv = sys.argv[1:]
166 argv = sys.argv[1:]
167 self.kernel_argv = swallow_argv(argv, self.frontend_aliases, self.frontend_flags)
167 self.kernel_argv = swallow_argv(argv, self.frontend_aliases, self.frontend_flags)
168 # kernel should inherit default config file from frontend
169 self.kernel_argv.append("--IPKernelApp.parent_appname='%s'" % self.name)
170
168
171 def init_connection_file(self):
169 def init_connection_file(self):
172 """find the connection file, and load the info if found.
170 """find the connection file, and load the info if found.
173
171
174 The current working directory and the current profile's security
172 The current working directory and the current profile's security
175 directory will be searched for the file if it is not given by
173 directory will be searched for the file if it is not given by
176 absolute path.
174 absolute path.
177
175
178 When attempting to connect to an existing kernel and the `--existing`
176 When attempting to connect to an existing kernel and the `--existing`
179 argument does not match an existing file, it will be interpreted as a
177 argument does not match an existing file, it will be interpreted as a
180 fileglob, and the matching file in the current profile's security dir
178 fileglob, and the matching file in the current profile's security dir
181 with the latest access time will be used.
179 with the latest access time will be used.
182
180
183 After this method is called, self.connection_file contains the *full path*
181 After this method is called, self.connection_file contains the *full path*
184 to the connection file, never just its name.
182 to the connection file, never just its name.
185 """
183 """
186 if self.existing:
184 if self.existing:
187 try:
185 try:
188 cf = find_connection_file(self.existing)
186 cf = find_connection_file(self.existing)
189 except Exception:
187 except Exception:
190 self.log.critical("Could not find existing kernel connection file %s", self.existing)
188 self.log.critical("Could not find existing kernel connection file %s", self.existing)
191 self.exit(1)
189 self.exit(1)
192 self.log.debug("Connecting to existing kernel: %s" % cf)
190 self.log.debug("Connecting to existing kernel: %s" % cf)
193 self.connection_file = cf
191 self.connection_file = cf
194 else:
192 else:
195 # not existing, check if we are going to write the file
193 # not existing, check if we are going to write the file
196 # and ensure that self.connection_file is a full path, not just the shortname
194 # and ensure that self.connection_file is a full path, not just the shortname
197 try:
195 try:
198 cf = find_connection_file(self.connection_file)
196 cf = find_connection_file(self.connection_file)
199 except Exception:
197 except Exception:
200 # file might not exist
198 # file might not exist
201 if self.connection_file == os.path.basename(self.connection_file):
199 if self.connection_file == os.path.basename(self.connection_file):
202 # just shortname, put it in security dir
200 # just shortname, put it in security dir
203 cf = os.path.join(self.profile_dir.security_dir, self.connection_file)
201 cf = os.path.join(self.profile_dir.security_dir, self.connection_file)
204 else:
202 else:
205 cf = self.connection_file
203 cf = self.connection_file
206 self.connection_file = cf
204 self.connection_file = cf
207 try:
205 try:
208 self.connection_file = filefind(self.connection_file, ['.', self.profile_dir.security_dir])
206 self.connection_file = filefind(self.connection_file, ['.', self.profile_dir.security_dir])
209 except IOError:
207 except IOError:
210 self.log.debug("Connection File not found: %s", self.connection_file)
208 self.log.debug("Connection File not found: %s", self.connection_file)
211 return
209 return
212
210
213 # should load_connection_file only be used for existing?
211 # should load_connection_file only be used for existing?
214 # as it is now, this allows reusing ports if an existing
212 # as it is now, this allows reusing ports if an existing
215 # file is requested
213 # file is requested
216 try:
214 try:
217 self.load_connection_file()
215 self.load_connection_file()
218 except Exception:
216 except Exception:
219 self.log.error("Failed to load connection file: %r", self.connection_file, exc_info=True)
217 self.log.error("Failed to load connection file: %r", self.connection_file, exc_info=True)
220 self.exit(1)
218 self.exit(1)
221
219
222 def init_ssh(self):
220 def init_ssh(self):
223 """set up ssh tunnels, if needed."""
221 """set up ssh tunnels, if needed."""
224 if not self.existing or (not self.sshserver and not self.sshkey):
222 if not self.existing or (not self.sshserver and not self.sshkey):
225 return
223 return
226 self.load_connection_file()
224 self.load_connection_file()
227
225
228 transport = self.transport
226 transport = self.transport
229 ip = self.ip
227 ip = self.ip
230
228
231 if transport != 'tcp':
229 if transport != 'tcp':
232 self.log.error("Can only use ssh tunnels with TCP sockets, not %s", transport)
230 self.log.error("Can only use ssh tunnels with TCP sockets, not %s", transport)
233 sys.exit(-1)
231 sys.exit(-1)
234
232
235 if self.sshkey and not self.sshserver:
233 if self.sshkey and not self.sshserver:
236 # specifying just the key implies that we are connecting directly
234 # specifying just the key implies that we are connecting directly
237 self.sshserver = ip
235 self.sshserver = ip
238 ip = localhost()
236 ip = localhost()
239
237
240 # build connection dict for tunnels:
238 # build connection dict for tunnels:
241 info = dict(ip=ip,
239 info = dict(ip=ip,
242 shell_port=self.shell_port,
240 shell_port=self.shell_port,
243 iopub_port=self.iopub_port,
241 iopub_port=self.iopub_port,
244 stdin_port=self.stdin_port,
242 stdin_port=self.stdin_port,
245 hb_port=self.hb_port
243 hb_port=self.hb_port
246 )
244 )
247
245
248 self.log.info("Forwarding connections to %s via %s"%(ip, self.sshserver))
246 self.log.info("Forwarding connections to %s via %s"%(ip, self.sshserver))
249
247
250 # tunnels return a new set of ports, which will be on localhost:
248 # tunnels return a new set of ports, which will be on localhost:
251 self.ip = localhost()
249 self.ip = localhost()
252 try:
250 try:
253 newports = tunnel_to_kernel(info, self.sshserver, self.sshkey)
251 newports = tunnel_to_kernel(info, self.sshserver, self.sshkey)
254 except:
252 except:
255 # even catch KeyboardInterrupt
253 # even catch KeyboardInterrupt
256 self.log.error("Could not setup tunnels", exc_info=True)
254 self.log.error("Could not setup tunnels", exc_info=True)
257 self.exit(1)
255 self.exit(1)
258
256
259 self.shell_port, self.iopub_port, self.stdin_port, self.hb_port = newports
257 self.shell_port, self.iopub_port, self.stdin_port, self.hb_port = newports
260
258
261 cf = self.connection_file
259 cf = self.connection_file
262 base,ext = os.path.splitext(cf)
260 base,ext = os.path.splitext(cf)
263 base = os.path.basename(base)
261 base = os.path.basename(base)
264 self.connection_file = os.path.basename(base)+'-ssh'+ext
262 self.connection_file = os.path.basename(base)+'-ssh'+ext
265 self.log.info("To connect another client via this tunnel, use:")
263 self.log.info("To connect another client via this tunnel, use:")
266 self.log.info("--existing %s" % self.connection_file)
264 self.log.info("--existing %s" % self.connection_file)
267
265
268 def _new_connection_file(self):
266 def _new_connection_file(self):
269 cf = ''
267 cf = ''
270 while not cf:
268 while not cf:
271 # we don't need a 128b id to distinguish kernels, use more readable
269 # we don't need a 128b id to distinguish kernels, use more readable
272 # 48b node segment (12 hex chars). Users running more than 32k simultaneous
270 # 48b node segment (12 hex chars). Users running more than 32k simultaneous
273 # kernels can subclass.
271 # kernels can subclass.
274 ident = str(uuid.uuid4()).split('-')[-1]
272 ident = str(uuid.uuid4()).split('-')[-1]
275 cf = os.path.join(self.profile_dir.security_dir, 'kernel-%s.json' % ident)
273 cf = os.path.join(self.profile_dir.security_dir, 'kernel-%s.json' % ident)
276 # only keep if it's actually new. Protect against unlikely collision
274 # only keep if it's actually new. Protect against unlikely collision
277 # in 48b random search space
275 # in 48b random search space
278 cf = cf if not os.path.exists(cf) else ''
276 cf = cf if not os.path.exists(cf) else ''
279 return cf
277 return cf
280
278
281 def init_kernel_manager(self):
279 def init_kernel_manager(self):
282 # Don't let Qt or ZMQ swallow KeyboardInterupts.
280 # Don't let Qt or ZMQ swallow KeyboardInterupts.
283 if self.existing:
281 if self.existing:
284 self.kernel_manager = None
282 self.kernel_manager = None
285 return
283 return
286 signal.signal(signal.SIGINT, signal.SIG_DFL)
284 signal.signal(signal.SIGINT, signal.SIG_DFL)
287
285
288 # Create a KernelManager and start a kernel.
286 # Create a KernelManager and start a kernel.
289 try:
287 try:
290 self.kernel_manager = self.kernel_manager_class(
288 self.kernel_manager = self.kernel_manager_class(
291 ip=self.ip,
289 ip=self.ip,
292 transport=self.transport,
290 transport=self.transport,
293 shell_port=self.shell_port,
291 shell_port=self.shell_port,
294 iopub_port=self.iopub_port,
292 iopub_port=self.iopub_port,
295 stdin_port=self.stdin_port,
293 stdin_port=self.stdin_port,
296 hb_port=self.hb_port,
294 hb_port=self.hb_port,
297 connection_file=self.connection_file,
295 connection_file=self.connection_file,
298 kernel_name=self.kernel_name,
296 kernel_name=self.kernel_name,
299 parent=self,
297 parent=self,
300 ipython_dir=self.ipython_dir,
298 ipython_dir=self.ipython_dir,
301 )
299 )
302 except NoSuchKernel:
300 except NoSuchKernel:
303 self.log.critical("Could not find kernel %s", self.kernel_name)
301 self.log.critical("Could not find kernel %s", self.kernel_name)
304 self.exit(1)
302 self.exit(1)
305
303
306 self.kernel_manager.client_factory = self.kernel_client_class
304 self.kernel_manager.client_factory = self.kernel_client_class
307 self.kernel_manager.start_kernel(extra_arguments=self.kernel_argv)
305 self.kernel_manager.start_kernel(extra_arguments=self.kernel_argv)
308 atexit.register(self.kernel_manager.cleanup_ipc_files)
306 atexit.register(self.kernel_manager.cleanup_ipc_files)
309
307
310 if self.sshserver:
308 if self.sshserver:
311 # ssh, write new connection file
309 # ssh, write new connection file
312 self.kernel_manager.write_connection_file()
310 self.kernel_manager.write_connection_file()
313
311
314 # in case KM defaults / ssh writing changes things:
312 # in case KM defaults / ssh writing changes things:
315 km = self.kernel_manager
313 km = self.kernel_manager
316 self.shell_port=km.shell_port
314 self.shell_port=km.shell_port
317 self.iopub_port=km.iopub_port
315 self.iopub_port=km.iopub_port
318 self.stdin_port=km.stdin_port
316 self.stdin_port=km.stdin_port
319 self.hb_port=km.hb_port
317 self.hb_port=km.hb_port
320 self.connection_file = km.connection_file
318 self.connection_file = km.connection_file
321
319
322 atexit.register(self.kernel_manager.cleanup_connection_file)
320 atexit.register(self.kernel_manager.cleanup_connection_file)
323
321
324 def init_kernel_client(self):
322 def init_kernel_client(self):
325 if self.kernel_manager is not None:
323 if self.kernel_manager is not None:
326 self.kernel_client = self.kernel_manager.client()
324 self.kernel_client = self.kernel_manager.client()
327 else:
325 else:
328 self.kernel_client = self.kernel_client_class(
326 self.kernel_client = self.kernel_client_class(
329 ip=self.ip,
327 ip=self.ip,
330 transport=self.transport,
328 transport=self.transport,
331 shell_port=self.shell_port,
329 shell_port=self.shell_port,
332 iopub_port=self.iopub_port,
330 iopub_port=self.iopub_port,
333 stdin_port=self.stdin_port,
331 stdin_port=self.stdin_port,
334 hb_port=self.hb_port,
332 hb_port=self.hb_port,
335 connection_file=self.connection_file,
333 connection_file=self.connection_file,
336 parent=self,
334 parent=self,
337 )
335 )
338
336
339 self.kernel_client.start_channels()
337 self.kernel_client.start_channels()
340
338
341
339
342
340
343 def initialize(self, argv=None):
341 def initialize(self, argv=None):
344 """
342 """
345 Classes which mix this class in should call:
343 Classes which mix this class in should call:
346 IPythonConsoleApp.initialize(self,argv)
344 IPythonConsoleApp.initialize(self,argv)
347 """
345 """
348 self.init_connection_file()
346 self.init_connection_file()
349 default_secure(self.config)
347 default_secure(self.config)
350 self.init_ssh()
348 self.init_ssh()
351 self.init_kernel_manager()
349 self.init_kernel_manager()
352 self.init_kernel_client()
350 self.init_kernel_client()
353
351
@@ -1,873 +1,870 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 # Copyright (c) IPython Development Team.
4 # Copyright (c) IPython Development Team.
5 # Distributed under the terms of the Modified BSD License.
5 # Distributed under the terms of the Modified BSD License.
6
6
7 from __future__ import print_function
7 from __future__ import print_function
8
8
9 import errno
9 import errno
10 import io
10 import io
11 import json
11 import json
12 import logging
12 import logging
13 import os
13 import os
14 import random
14 import random
15 import select
15 import select
16 import signal
16 import signal
17 import socket
17 import socket
18 import sys
18 import sys
19 import threading
19 import threading
20 import time
20 import time
21 import webbrowser
21 import webbrowser
22
22
23
23
24 # check for pyzmq 2.1.11
24 # check for pyzmq 2.1.11
25 from IPython.utils.zmqrelated import check_for_zmq
25 from IPython.utils.zmqrelated import check_for_zmq
26 check_for_zmq('2.1.11', 'IPython.html')
26 check_for_zmq('2.1.11', 'IPython.html')
27
27
28 from jinja2 import Environment, FileSystemLoader
28 from jinja2 import Environment, FileSystemLoader
29
29
30 # Install the pyzmq ioloop. This has to be done before anything else from
30 # Install the pyzmq ioloop. This has to be done before anything else from
31 # tornado is imported.
31 # tornado is imported.
32 from zmq.eventloop import ioloop
32 from zmq.eventloop import ioloop
33 ioloop.install()
33 ioloop.install()
34
34
35 # check for tornado 3.1.0
35 # check for tornado 3.1.0
36 msg = "The IPython Notebook requires tornado >= 3.1.0"
36 msg = "The IPython Notebook requires tornado >= 3.1.0"
37 try:
37 try:
38 import tornado
38 import tornado
39 except ImportError:
39 except ImportError:
40 raise ImportError(msg)
40 raise ImportError(msg)
41 try:
41 try:
42 version_info = tornado.version_info
42 version_info = tornado.version_info
43 except AttributeError:
43 except AttributeError:
44 raise ImportError(msg + ", but you have < 1.1.0")
44 raise ImportError(msg + ", but you have < 1.1.0")
45 if version_info < (3,1,0):
45 if version_info < (3,1,0):
46 raise ImportError(msg + ", but you have %s" % tornado.version)
46 raise ImportError(msg + ", but you have %s" % tornado.version)
47
47
48 from tornado import httpserver
48 from tornado import httpserver
49 from tornado import web
49 from tornado import web
50 from tornado.log import LogFormatter
50 from tornado.log import LogFormatter
51
51
52 from IPython.html import DEFAULT_STATIC_FILES_PATH
52 from IPython.html import DEFAULT_STATIC_FILES_PATH
53 from .base.handlers import Template404
53 from .base.handlers import Template404
54 from .log import log_request
54 from .log import log_request
55 from .services.kernels.kernelmanager import MappingKernelManager
55 from .services.kernels.kernelmanager import MappingKernelManager
56 from .services.notebooks.nbmanager import NotebookManager
56 from .services.notebooks.nbmanager import NotebookManager
57 from .services.notebooks.filenbmanager import FileNotebookManager
57 from .services.notebooks.filenbmanager import FileNotebookManager
58 from .services.clusters.clustermanager import ClusterManager
58 from .services.clusters.clustermanager import ClusterManager
59 from .services.sessions.sessionmanager import SessionManager
59 from .services.sessions.sessionmanager import SessionManager
60
60
61 from .base.handlers import AuthenticatedFileHandler, FileFindHandler
61 from .base.handlers import AuthenticatedFileHandler, FileFindHandler
62
62
63 from IPython.config import Config
63 from IPython.config import Config
64 from IPython.config.application import catch_config_error, boolean_flag
64 from IPython.config.application import catch_config_error, boolean_flag
65 from IPython.core.application import (
65 from IPython.core.application import (
66 BaseIPythonApplication, base_flags, base_aliases,
66 BaseIPythonApplication, base_flags, base_aliases,
67 )
67 )
68 from IPython.core.profiledir import ProfileDir
68 from IPython.core.profiledir import ProfileDir
69 from IPython.kernel import KernelManager
69 from IPython.kernel import KernelManager
70 from IPython.kernel.kernelspec import KernelSpecManager
70 from IPython.kernel.kernelspec import KernelSpecManager
71 from IPython.kernel.zmq.session import default_secure, Session
71 from IPython.kernel.zmq.session import default_secure, Session
72 from IPython.nbformat.sign import NotebookNotary
72 from IPython.nbformat.sign import NotebookNotary
73 from IPython.utils.importstring import import_item
73 from IPython.utils.importstring import import_item
74 from IPython.utils import submodule
74 from IPython.utils import submodule
75 from IPython.utils.traitlets import (
75 from IPython.utils.traitlets import (
76 Dict, Unicode, Integer, List, Bool, Bytes, Instance,
76 Dict, Unicode, Integer, List, Bool, Bytes, Instance,
77 DottedObjectName, TraitError,
77 DottedObjectName, TraitError,
78 )
78 )
79 from IPython.utils import py3compat
79 from IPython.utils import py3compat
80 from IPython.utils.path import filefind, get_ipython_dir
80 from IPython.utils.path import filefind, get_ipython_dir
81
81
82 from .utils import url_path_join
82 from .utils import url_path_join
83
83
84 #-----------------------------------------------------------------------------
84 #-----------------------------------------------------------------------------
85 # Module globals
85 # Module globals
86 #-----------------------------------------------------------------------------
86 #-----------------------------------------------------------------------------
87
87
88 _examples = """
88 _examples = """
89 ipython notebook # start the notebook
89 ipython notebook # start the notebook
90 ipython notebook --profile=sympy # use the sympy profile
90 ipython notebook --profile=sympy # use the sympy profile
91 ipython notebook --certfile=mycert.pem # use SSL/TLS certificate
91 ipython notebook --certfile=mycert.pem # use SSL/TLS certificate
92 """
92 """
93
93
94 #-----------------------------------------------------------------------------
94 #-----------------------------------------------------------------------------
95 # Helper functions
95 # Helper functions
96 #-----------------------------------------------------------------------------
96 #-----------------------------------------------------------------------------
97
97
98 def random_ports(port, n):
98 def random_ports(port, n):
99 """Generate a list of n random ports near the given port.
99 """Generate a list of n random ports near the given port.
100
100
101 The first 5 ports will be sequential, and the remaining n-5 will be
101 The first 5 ports will be sequential, and the remaining n-5 will be
102 randomly selected in the range [port-2*n, port+2*n].
102 randomly selected in the range [port-2*n, port+2*n].
103 """
103 """
104 for i in range(min(5, n)):
104 for i in range(min(5, n)):
105 yield port + i
105 yield port + i
106 for i in range(n-5):
106 for i in range(n-5):
107 yield max(1, port + random.randint(-2*n, 2*n))
107 yield max(1, port + random.randint(-2*n, 2*n))
108
108
109 def load_handlers(name):
109 def load_handlers(name):
110 """Load the (URL pattern, handler) tuples for each component."""
110 """Load the (URL pattern, handler) tuples for each component."""
111 name = 'IPython.html.' + name
111 name = 'IPython.html.' + name
112 mod = __import__(name, fromlist=['default_handlers'])
112 mod = __import__(name, fromlist=['default_handlers'])
113 return mod.default_handlers
113 return mod.default_handlers
114
114
115 #-----------------------------------------------------------------------------
115 #-----------------------------------------------------------------------------
116 # The Tornado web application
116 # The Tornado web application
117 #-----------------------------------------------------------------------------
117 #-----------------------------------------------------------------------------
118
118
119 class NotebookWebApplication(web.Application):
119 class NotebookWebApplication(web.Application):
120
120
121 def __init__(self, ipython_app, kernel_manager, notebook_manager,
121 def __init__(self, ipython_app, kernel_manager, notebook_manager,
122 cluster_manager, session_manager, kernel_spec_manager, log,
122 cluster_manager, session_manager, kernel_spec_manager, log,
123 base_url, settings_overrides, jinja_env_options):
123 base_url, settings_overrides, jinja_env_options):
124
124
125 settings = self.init_settings(
125 settings = self.init_settings(
126 ipython_app, kernel_manager, notebook_manager, cluster_manager,
126 ipython_app, kernel_manager, notebook_manager, cluster_manager,
127 session_manager, kernel_spec_manager, log, base_url,
127 session_manager, kernel_spec_manager, log, base_url,
128 settings_overrides, jinja_env_options)
128 settings_overrides, jinja_env_options)
129 handlers = self.init_handlers(settings)
129 handlers = self.init_handlers(settings)
130
130
131 super(NotebookWebApplication, self).__init__(handlers, **settings)
131 super(NotebookWebApplication, self).__init__(handlers, **settings)
132
132
133 def init_settings(self, ipython_app, kernel_manager, notebook_manager,
133 def init_settings(self, ipython_app, kernel_manager, notebook_manager,
134 cluster_manager, session_manager, kernel_spec_manager,
134 cluster_manager, session_manager, kernel_spec_manager,
135 log, base_url, settings_overrides,
135 log, base_url, settings_overrides,
136 jinja_env_options=None):
136 jinja_env_options=None):
137 # Python < 2.6.5 doesn't accept unicode keys in f(**kwargs), and
137 # Python < 2.6.5 doesn't accept unicode keys in f(**kwargs), and
138 # base_url will always be unicode, which will in turn
138 # base_url will always be unicode, which will in turn
139 # make the patterns unicode, and ultimately result in unicode
139 # make the patterns unicode, and ultimately result in unicode
140 # keys in kwargs to handler._execute(**kwargs) in tornado.
140 # keys in kwargs to handler._execute(**kwargs) in tornado.
141 # This enforces that base_url be ascii in that situation.
141 # This enforces that base_url be ascii in that situation.
142 #
142 #
143 # Note that the URLs these patterns check against are escaped,
143 # Note that the URLs these patterns check against are escaped,
144 # and thus guaranteed to be ASCII: 'hΓ©llo' is really 'h%C3%A9llo'.
144 # and thus guaranteed to be ASCII: 'hΓ©llo' is really 'h%C3%A9llo'.
145 base_url = py3compat.unicode_to_str(base_url, 'ascii')
145 base_url = py3compat.unicode_to_str(base_url, 'ascii')
146 template_path = settings_overrides.get("template_path", os.path.join(os.path.dirname(__file__), "templates"))
146 template_path = settings_overrides.get("template_path", os.path.join(os.path.dirname(__file__), "templates"))
147 jenv_opt = jinja_env_options if jinja_env_options else {}
147 jenv_opt = jinja_env_options if jinja_env_options else {}
148 env = Environment(loader=FileSystemLoader(template_path),**jenv_opt )
148 env = Environment(loader=FileSystemLoader(template_path),**jenv_opt )
149 settings = dict(
149 settings = dict(
150 # basics
150 # basics
151 log_function=log_request,
151 log_function=log_request,
152 base_url=base_url,
152 base_url=base_url,
153 template_path=template_path,
153 template_path=template_path,
154 static_path=ipython_app.static_file_path,
154 static_path=ipython_app.static_file_path,
155 static_handler_class = FileFindHandler,
155 static_handler_class = FileFindHandler,
156 static_url_prefix = url_path_join(base_url,'/static/'),
156 static_url_prefix = url_path_join(base_url,'/static/'),
157
157
158 # authentication
158 # authentication
159 cookie_secret=ipython_app.cookie_secret,
159 cookie_secret=ipython_app.cookie_secret,
160 login_url=url_path_join(base_url,'/login'),
160 login_url=url_path_join(base_url,'/login'),
161 password=ipython_app.password,
161 password=ipython_app.password,
162
162
163 # managers
163 # managers
164 kernel_manager=kernel_manager,
164 kernel_manager=kernel_manager,
165 notebook_manager=notebook_manager,
165 notebook_manager=notebook_manager,
166 cluster_manager=cluster_manager,
166 cluster_manager=cluster_manager,
167 session_manager=session_manager,
167 session_manager=session_manager,
168 kernel_spec_manager=kernel_spec_manager,
168 kernel_spec_manager=kernel_spec_manager,
169
169
170 # IPython stuff
170 # IPython stuff
171 nbextensions_path = ipython_app.nbextensions_path,
171 nbextensions_path = ipython_app.nbextensions_path,
172 mathjax_url=ipython_app.mathjax_url,
172 mathjax_url=ipython_app.mathjax_url,
173 config=ipython_app.config,
173 config=ipython_app.config,
174 jinja2_env=env,
174 jinja2_env=env,
175 )
175 )
176
176
177 # allow custom overrides for the tornado web app.
177 # allow custom overrides for the tornado web app.
178 settings.update(settings_overrides)
178 settings.update(settings_overrides)
179 return settings
179 return settings
180
180
181 def init_handlers(self, settings):
181 def init_handlers(self, settings):
182 # Load the (URL pattern, handler) tuples for each component.
182 # Load the (URL pattern, handler) tuples for each component.
183 handlers = []
183 handlers = []
184 handlers.extend(load_handlers('base.handlers'))
184 handlers.extend(load_handlers('base.handlers'))
185 handlers.extend(load_handlers('tree.handlers'))
185 handlers.extend(load_handlers('tree.handlers'))
186 handlers.extend(load_handlers('auth.login'))
186 handlers.extend(load_handlers('auth.login'))
187 handlers.extend(load_handlers('auth.logout'))
187 handlers.extend(load_handlers('auth.logout'))
188 handlers.extend(load_handlers('notebook.handlers'))
188 handlers.extend(load_handlers('notebook.handlers'))
189 handlers.extend(load_handlers('nbconvert.handlers'))
189 handlers.extend(load_handlers('nbconvert.handlers'))
190 handlers.extend(load_handlers('kernelspecs.handlers'))
190 handlers.extend(load_handlers('kernelspecs.handlers'))
191 handlers.extend(load_handlers('services.kernels.handlers'))
191 handlers.extend(load_handlers('services.kernels.handlers'))
192 handlers.extend(load_handlers('services.notebooks.handlers'))
192 handlers.extend(load_handlers('services.notebooks.handlers'))
193 handlers.extend(load_handlers('services.clusters.handlers'))
193 handlers.extend(load_handlers('services.clusters.handlers'))
194 handlers.extend(load_handlers('services.sessions.handlers'))
194 handlers.extend(load_handlers('services.sessions.handlers'))
195 handlers.extend(load_handlers('services.nbconvert.handlers'))
195 handlers.extend(load_handlers('services.nbconvert.handlers'))
196 handlers.extend(load_handlers('services.kernelspecs.handlers'))
196 handlers.extend(load_handlers('services.kernelspecs.handlers'))
197 # FIXME: /files/ should be handled by the Contents service when it exists
197 # FIXME: /files/ should be handled by the Contents service when it exists
198 nbm = settings['notebook_manager']
198 nbm = settings['notebook_manager']
199 if hasattr(nbm, 'notebook_dir'):
199 if hasattr(nbm, 'notebook_dir'):
200 handlers.extend([
200 handlers.extend([
201 (r"/files/(.*)", AuthenticatedFileHandler, {'path' : nbm.notebook_dir}),
201 (r"/files/(.*)", AuthenticatedFileHandler, {'path' : nbm.notebook_dir}),
202 (r"/nbextensions/(.*)", FileFindHandler, {'path' : settings['nbextensions_path']}),
202 (r"/nbextensions/(.*)", FileFindHandler, {'path' : settings['nbextensions_path']}),
203 ])
203 ])
204 # prepend base_url onto the patterns that we match
204 # prepend base_url onto the patterns that we match
205 new_handlers = []
205 new_handlers = []
206 for handler in handlers:
206 for handler in handlers:
207 pattern = url_path_join(settings['base_url'], handler[0])
207 pattern = url_path_join(settings['base_url'], handler[0])
208 new_handler = tuple([pattern] + list(handler[1:]))
208 new_handler = tuple([pattern] + list(handler[1:]))
209 new_handlers.append(new_handler)
209 new_handlers.append(new_handler)
210 # add 404 on the end, which will catch everything that falls through
210 # add 404 on the end, which will catch everything that falls through
211 new_handlers.append((r'(.*)', Template404))
211 new_handlers.append((r'(.*)', Template404))
212 return new_handlers
212 return new_handlers
213
213
214
214
215 class NbserverListApp(BaseIPythonApplication):
215 class NbserverListApp(BaseIPythonApplication):
216
216
217 description="List currently running notebook servers in this profile."
217 description="List currently running notebook servers in this profile."
218
218
219 flags = dict(
219 flags = dict(
220 json=({'NbserverListApp': {'json': True}},
220 json=({'NbserverListApp': {'json': True}},
221 "Produce machine-readable JSON output."),
221 "Produce machine-readable JSON output."),
222 )
222 )
223
223
224 json = Bool(False, config=True,
224 json = Bool(False, config=True,
225 help="If True, each line of output will be a JSON object with the "
225 help="If True, each line of output will be a JSON object with the "
226 "details from the server info file.")
226 "details from the server info file.")
227
227
228 def start(self):
228 def start(self):
229 if not self.json:
229 if not self.json:
230 print("Currently running servers:")
230 print("Currently running servers:")
231 for serverinfo in list_running_servers(self.profile):
231 for serverinfo in list_running_servers(self.profile):
232 if self.json:
232 if self.json:
233 print(json.dumps(serverinfo))
233 print(json.dumps(serverinfo))
234 else:
234 else:
235 print(serverinfo['url'], "::", serverinfo['notebook_dir'])
235 print(serverinfo['url'], "::", serverinfo['notebook_dir'])
236
236
237 #-----------------------------------------------------------------------------
237 #-----------------------------------------------------------------------------
238 # Aliases and Flags
238 # Aliases and Flags
239 #-----------------------------------------------------------------------------
239 #-----------------------------------------------------------------------------
240
240
241 flags = dict(base_flags)
241 flags = dict(base_flags)
242 flags['no-browser']=(
242 flags['no-browser']=(
243 {'NotebookApp' : {'open_browser' : False}},
243 {'NotebookApp' : {'open_browser' : False}},
244 "Don't open the notebook in a browser after startup."
244 "Don't open the notebook in a browser after startup."
245 )
245 )
246 flags['pylab']=(
246 flags['pylab']=(
247 {'NotebookApp' : {'pylab' : 'warn'}},
247 {'NotebookApp' : {'pylab' : 'warn'}},
248 "DISABLED: use %pylab or %matplotlib in the notebook to enable matplotlib."
248 "DISABLED: use %pylab or %matplotlib in the notebook to enable matplotlib."
249 )
249 )
250 flags['no-mathjax']=(
250 flags['no-mathjax']=(
251 {'NotebookApp' : {'enable_mathjax' : False}},
251 {'NotebookApp' : {'enable_mathjax' : False}},
252 """Disable MathJax
252 """Disable MathJax
253
253
254 MathJax is the javascript library IPython uses to render math/LaTeX. It is
254 MathJax is the javascript library IPython uses to render math/LaTeX. It is
255 very large, so you may want to disable it if you have a slow internet
255 very large, so you may want to disable it if you have a slow internet
256 connection, or for offline use of the notebook.
256 connection, or for offline use of the notebook.
257
257
258 When disabled, equations etc. will appear as their untransformed TeX source.
258 When disabled, equations etc. will appear as their untransformed TeX source.
259 """
259 """
260 )
260 )
261
261
262 # Add notebook manager flags
262 # Add notebook manager flags
263 flags.update(boolean_flag('script', 'FileNotebookManager.save_script',
263 flags.update(boolean_flag('script', 'FileNotebookManager.save_script',
264 'Auto-save a .py script everytime the .ipynb notebook is saved',
264 'Auto-save a .py script everytime the .ipynb notebook is saved',
265 'Do not auto-save .py scripts for every notebook'))
265 'Do not auto-save .py scripts for every notebook'))
266
266
267 aliases = dict(base_aliases)
267 aliases = dict(base_aliases)
268
268
269 aliases.update({
269 aliases.update({
270 'ip': 'NotebookApp.ip',
270 'ip': 'NotebookApp.ip',
271 'port': 'NotebookApp.port',
271 'port': 'NotebookApp.port',
272 'port-retries': 'NotebookApp.port_retries',
272 'port-retries': 'NotebookApp.port_retries',
273 'transport': 'KernelManager.transport',
273 'transport': 'KernelManager.transport',
274 'keyfile': 'NotebookApp.keyfile',
274 'keyfile': 'NotebookApp.keyfile',
275 'certfile': 'NotebookApp.certfile',
275 'certfile': 'NotebookApp.certfile',
276 'notebook-dir': 'NotebookApp.notebook_dir',
276 'notebook-dir': 'NotebookApp.notebook_dir',
277 'browser': 'NotebookApp.browser',
277 'browser': 'NotebookApp.browser',
278 'pylab': 'NotebookApp.pylab',
278 'pylab': 'NotebookApp.pylab',
279 })
279 })
280
280
281 #-----------------------------------------------------------------------------
281 #-----------------------------------------------------------------------------
282 # NotebookApp
282 # NotebookApp
283 #-----------------------------------------------------------------------------
283 #-----------------------------------------------------------------------------
284
284
285 class NotebookApp(BaseIPythonApplication):
285 class NotebookApp(BaseIPythonApplication):
286
286
287 name = 'ipython-notebook'
287 name = 'ipython-notebook'
288
288
289 description = """
289 description = """
290 The IPython HTML Notebook.
290 The IPython HTML Notebook.
291
291
292 This launches a Tornado based HTML Notebook Server that serves up an
292 This launches a Tornado based HTML Notebook Server that serves up an
293 HTML5/Javascript Notebook client.
293 HTML5/Javascript Notebook client.
294 """
294 """
295 examples = _examples
295 examples = _examples
296 aliases = aliases
296 aliases = aliases
297 flags = flags
297 flags = flags
298
298
299 classes = [
299 classes = [
300 KernelManager, ProfileDir, Session, MappingKernelManager,
300 KernelManager, ProfileDir, Session, MappingKernelManager,
301 NotebookManager, FileNotebookManager, NotebookNotary,
301 NotebookManager, FileNotebookManager, NotebookNotary,
302 ]
302 ]
303 flags = Dict(flags)
303 flags = Dict(flags)
304 aliases = Dict(aliases)
304 aliases = Dict(aliases)
305
305
306 subcommands = dict(
306 subcommands = dict(
307 list=(NbserverListApp, NbserverListApp.description.splitlines()[0]),
307 list=(NbserverListApp, NbserverListApp.description.splitlines()[0]),
308 )
308 )
309
309
310 kernel_argv = List(Unicode)
310 kernel_argv = List(Unicode)
311
311
312 _log_formatter_cls = LogFormatter
312 _log_formatter_cls = LogFormatter
313
313
314 def _log_level_default(self):
314 def _log_level_default(self):
315 return logging.INFO
315 return logging.INFO
316
316
317 def _log_datefmt_default(self):
317 def _log_datefmt_default(self):
318 """Exclude date from default date format"""
318 """Exclude date from default date format"""
319 return "%H:%M:%S"
319 return "%H:%M:%S"
320
320
321 def _log_format_default(self):
321 def _log_format_default(self):
322 """override default log format to include time"""
322 """override default log format to include time"""
323 return u"%(color)s[%(levelname)1.1s %(asctime)s.%(msecs).03d %(name)s]%(end_color)s %(message)s"
323 return u"%(color)s[%(levelname)1.1s %(asctime)s.%(msecs).03d %(name)s]%(end_color)s %(message)s"
324
324
325 # create requested profiles by default, if they don't exist:
325 # create requested profiles by default, if they don't exist:
326 auto_create = Bool(True)
326 auto_create = Bool(True)
327
327
328 # file to be opened in the notebook server
328 # file to be opened in the notebook server
329 file_to_run = Unicode('', config=True)
329 file_to_run = Unicode('', config=True)
330 def _file_to_run_changed(self, name, old, new):
330 def _file_to_run_changed(self, name, old, new):
331 path, base = os.path.split(new)
331 path, base = os.path.split(new)
332 if path:
332 if path:
333 self.file_to_run = base
333 self.file_to_run = base
334 self.notebook_dir = path
334 self.notebook_dir = path
335
335
336 # Network related information.
336 # Network related information.
337
337
338 ip = Unicode('localhost', config=True,
338 ip = Unicode('localhost', config=True,
339 help="The IP address the notebook server will listen on."
339 help="The IP address the notebook server will listen on."
340 )
340 )
341
341
342 def _ip_changed(self, name, old, new):
342 def _ip_changed(self, name, old, new):
343 if new == u'*': self.ip = u''
343 if new == u'*': self.ip = u''
344
344
345 port = Integer(8888, config=True,
345 port = Integer(8888, config=True,
346 help="The port the notebook server will listen on."
346 help="The port the notebook server will listen on."
347 )
347 )
348 port_retries = Integer(50, config=True,
348 port_retries = Integer(50, config=True,
349 help="The number of additional ports to try if the specified port is not available."
349 help="The number of additional ports to try if the specified port is not available."
350 )
350 )
351
351
352 certfile = Unicode(u'', config=True,
352 certfile = Unicode(u'', config=True,
353 help="""The full path to an SSL/TLS certificate file."""
353 help="""The full path to an SSL/TLS certificate file."""
354 )
354 )
355
355
356 keyfile = Unicode(u'', config=True,
356 keyfile = Unicode(u'', config=True,
357 help="""The full path to a private key file for usage with SSL/TLS."""
357 help="""The full path to a private key file for usage with SSL/TLS."""
358 )
358 )
359
359
360 cookie_secret = Bytes(b'', config=True,
360 cookie_secret = Bytes(b'', config=True,
361 help="""The random bytes used to secure cookies.
361 help="""The random bytes used to secure cookies.
362 By default this is a new random number every time you start the Notebook.
362 By default this is a new random number every time you start the Notebook.
363 Set it to a value in a config file to enable logins to persist across server sessions.
363 Set it to a value in a config file to enable logins to persist across server sessions.
364
364
365 Note: Cookie secrets should be kept private, do not share config files with
365 Note: Cookie secrets should be kept private, do not share config files with
366 cookie_secret stored in plaintext (you can read the value from a file).
366 cookie_secret stored in plaintext (you can read the value from a file).
367 """
367 """
368 )
368 )
369 def _cookie_secret_default(self):
369 def _cookie_secret_default(self):
370 return os.urandom(1024)
370 return os.urandom(1024)
371
371
372 password = Unicode(u'', config=True,
372 password = Unicode(u'', config=True,
373 help="""Hashed password to use for web authentication.
373 help="""Hashed password to use for web authentication.
374
374
375 To generate, type in a python/IPython shell:
375 To generate, type in a python/IPython shell:
376
376
377 from IPython.lib import passwd; passwd()
377 from IPython.lib import passwd; passwd()
378
378
379 The string should be of the form type:salt:hashed-password.
379 The string should be of the form type:salt:hashed-password.
380 """
380 """
381 )
381 )
382
382
383 open_browser = Bool(True, config=True,
383 open_browser = Bool(True, config=True,
384 help="""Whether to open in a browser after starting.
384 help="""Whether to open in a browser after starting.
385 The specific browser used is platform dependent and
385 The specific browser used is platform dependent and
386 determined by the python standard library `webbrowser`
386 determined by the python standard library `webbrowser`
387 module, unless it is overridden using the --browser
387 module, unless it is overridden using the --browser
388 (NotebookApp.browser) configuration option.
388 (NotebookApp.browser) configuration option.
389 """)
389 """)
390
390
391 browser = Unicode(u'', config=True,
391 browser = Unicode(u'', config=True,
392 help="""Specify what command to use to invoke a web
392 help="""Specify what command to use to invoke a web
393 browser when opening the notebook. If not specified, the
393 browser when opening the notebook. If not specified, the
394 default browser will be determined by the `webbrowser`
394 default browser will be determined by the `webbrowser`
395 standard library module, which allows setting of the
395 standard library module, which allows setting of the
396 BROWSER environment variable to override it.
396 BROWSER environment variable to override it.
397 """)
397 """)
398
398
399 webapp_settings = Dict(config=True,
399 webapp_settings = Dict(config=True,
400 help="Supply overrides for the tornado.web.Application that the "
400 help="Supply overrides for the tornado.web.Application that the "
401 "IPython notebook uses.")
401 "IPython notebook uses.")
402
402
403 jinja_environment_options = Dict(config=True,
403 jinja_environment_options = Dict(config=True,
404 help="Supply extra arguments that will be passed to Jinja environment.")
404 help="Supply extra arguments that will be passed to Jinja environment.")
405
405
406
406
407 enable_mathjax = Bool(True, config=True,
407 enable_mathjax = Bool(True, config=True,
408 help="""Whether to enable MathJax for typesetting math/TeX
408 help="""Whether to enable MathJax for typesetting math/TeX
409
409
410 MathJax is the javascript library IPython uses to render math/LaTeX. It is
410 MathJax is the javascript library IPython uses to render math/LaTeX. It is
411 very large, so you may want to disable it if you have a slow internet
411 very large, so you may want to disable it if you have a slow internet
412 connection, or for offline use of the notebook.
412 connection, or for offline use of the notebook.
413
413
414 When disabled, equations etc. will appear as their untransformed TeX source.
414 When disabled, equations etc. will appear as their untransformed TeX source.
415 """
415 """
416 )
416 )
417 def _enable_mathjax_changed(self, name, old, new):
417 def _enable_mathjax_changed(self, name, old, new):
418 """set mathjax url to empty if mathjax is disabled"""
418 """set mathjax url to empty if mathjax is disabled"""
419 if not new:
419 if not new:
420 self.mathjax_url = u''
420 self.mathjax_url = u''
421
421
422 base_url = Unicode('/', config=True,
422 base_url = Unicode('/', config=True,
423 help='''The base URL for the notebook server.
423 help='''The base URL for the notebook server.
424
424
425 Leading and trailing slashes can be omitted,
425 Leading and trailing slashes can be omitted,
426 and will automatically be added.
426 and will automatically be added.
427 ''')
427 ''')
428 def _base_url_changed(self, name, old, new):
428 def _base_url_changed(self, name, old, new):
429 if not new.startswith('/'):
429 if not new.startswith('/'):
430 self.base_url = '/'+new
430 self.base_url = '/'+new
431 elif not new.endswith('/'):
431 elif not new.endswith('/'):
432 self.base_url = new+'/'
432 self.base_url = new+'/'
433
433
434 base_project_url = Unicode('/', config=True, help="""DEPRECATED use base_url""")
434 base_project_url = Unicode('/', config=True, help="""DEPRECATED use base_url""")
435 def _base_project_url_changed(self, name, old, new):
435 def _base_project_url_changed(self, name, old, new):
436 self.log.warn("base_project_url is deprecated, use base_url")
436 self.log.warn("base_project_url is deprecated, use base_url")
437 self.base_url = new
437 self.base_url = new
438
438
439 extra_static_paths = List(Unicode, config=True,
439 extra_static_paths = List(Unicode, config=True,
440 help="""Extra paths to search for serving static files.
440 help="""Extra paths to search for serving static files.
441
441
442 This allows adding javascript/css to be available from the notebook server machine,
442 This allows adding javascript/css to be available from the notebook server machine,
443 or overriding individual files in the IPython"""
443 or overriding individual files in the IPython"""
444 )
444 )
445 def _extra_static_paths_default(self):
445 def _extra_static_paths_default(self):
446 return [os.path.join(self.profile_dir.location, 'static')]
446 return [os.path.join(self.profile_dir.location, 'static')]
447
447
448 @property
448 @property
449 def static_file_path(self):
449 def static_file_path(self):
450 """return extra paths + the default location"""
450 """return extra paths + the default location"""
451 return self.extra_static_paths + [DEFAULT_STATIC_FILES_PATH]
451 return self.extra_static_paths + [DEFAULT_STATIC_FILES_PATH]
452
452
453 nbextensions_path = List(Unicode, config=True,
453 nbextensions_path = List(Unicode, config=True,
454 help="""paths for Javascript extensions. By default, this is just IPYTHONDIR/nbextensions"""
454 help="""paths for Javascript extensions. By default, this is just IPYTHONDIR/nbextensions"""
455 )
455 )
456 def _nbextensions_path_default(self):
456 def _nbextensions_path_default(self):
457 return [os.path.join(get_ipython_dir(), 'nbextensions')]
457 return [os.path.join(get_ipython_dir(), 'nbextensions')]
458
458
459 mathjax_url = Unicode("", config=True,
459 mathjax_url = Unicode("", config=True,
460 help="""The url for MathJax.js."""
460 help="""The url for MathJax.js."""
461 )
461 )
462 def _mathjax_url_default(self):
462 def _mathjax_url_default(self):
463 if not self.enable_mathjax:
463 if not self.enable_mathjax:
464 return u''
464 return u''
465 static_url_prefix = self.webapp_settings.get("static_url_prefix",
465 static_url_prefix = self.webapp_settings.get("static_url_prefix",
466 url_path_join(self.base_url, "static")
466 url_path_join(self.base_url, "static")
467 )
467 )
468
468
469 # try local mathjax, either in nbextensions/mathjax or static/mathjax
469 # try local mathjax, either in nbextensions/mathjax or static/mathjax
470 for (url_prefix, search_path) in [
470 for (url_prefix, search_path) in [
471 (url_path_join(self.base_url, "nbextensions"), self.nbextensions_path),
471 (url_path_join(self.base_url, "nbextensions"), self.nbextensions_path),
472 (static_url_prefix, self.static_file_path),
472 (static_url_prefix, self.static_file_path),
473 ]:
473 ]:
474 self.log.debug("searching for local mathjax in %s", search_path)
474 self.log.debug("searching for local mathjax in %s", search_path)
475 try:
475 try:
476 mathjax = filefind(os.path.join('mathjax', 'MathJax.js'), search_path)
476 mathjax = filefind(os.path.join('mathjax', 'MathJax.js'), search_path)
477 except IOError:
477 except IOError:
478 continue
478 continue
479 else:
479 else:
480 url = url_path_join(url_prefix, u"mathjax/MathJax.js")
480 url = url_path_join(url_prefix, u"mathjax/MathJax.js")
481 self.log.info("Serving local MathJax from %s at %s", mathjax, url)
481 self.log.info("Serving local MathJax from %s at %s", mathjax, url)
482 return url
482 return url
483
483
484 # no local mathjax, serve from CDN
484 # no local mathjax, serve from CDN
485 if self.certfile:
485 if self.certfile:
486 # HTTPS: load from Rackspace CDN, because SSL certificate requires it
486 # HTTPS: load from Rackspace CDN, because SSL certificate requires it
487 host = u"https://c328740.ssl.cf1.rackcdn.com"
487 host = u"https://c328740.ssl.cf1.rackcdn.com"
488 else:
488 else:
489 host = u"http://cdn.mathjax.org"
489 host = u"http://cdn.mathjax.org"
490
490
491 url = host + u"/mathjax/latest/MathJax.js"
491 url = host + u"/mathjax/latest/MathJax.js"
492 self.log.info("Using MathJax from CDN: %s", url)
492 self.log.info("Using MathJax from CDN: %s", url)
493 return url
493 return url
494
494
495 def _mathjax_url_changed(self, name, old, new):
495 def _mathjax_url_changed(self, name, old, new):
496 if new and not self.enable_mathjax:
496 if new and not self.enable_mathjax:
497 # enable_mathjax=False overrides mathjax_url
497 # enable_mathjax=False overrides mathjax_url
498 self.mathjax_url = u''
498 self.mathjax_url = u''
499 else:
499 else:
500 self.log.info("Using MathJax: %s", new)
500 self.log.info("Using MathJax: %s", new)
501
501
502 notebook_manager_class = DottedObjectName('IPython.html.services.notebooks.filenbmanager.FileNotebookManager',
502 notebook_manager_class = DottedObjectName('IPython.html.services.notebooks.filenbmanager.FileNotebookManager',
503 config=True,
503 config=True,
504 help='The notebook manager class to use.'
504 help='The notebook manager class to use.'
505 )
505 )
506 kernel_manager_class = DottedObjectName('IPython.html.services.kernels.kernelmanager.MappingKernelManager',
506 kernel_manager_class = DottedObjectName('IPython.html.services.kernels.kernelmanager.MappingKernelManager',
507 config=True,
507 config=True,
508 help='The kernel manager class to use.'
508 help='The kernel manager class to use.'
509 )
509 )
510 session_manager_class = DottedObjectName('IPython.html.services.sessions.sessionmanager.SessionManager',
510 session_manager_class = DottedObjectName('IPython.html.services.sessions.sessionmanager.SessionManager',
511 config=True,
511 config=True,
512 help='The session manager class to use.'
512 help='The session manager class to use.'
513 )
513 )
514 cluster_manager_class = DottedObjectName('IPython.html.services.clusters.clustermanager.ClusterManager',
514 cluster_manager_class = DottedObjectName('IPython.html.services.clusters.clustermanager.ClusterManager',
515 config=True,
515 config=True,
516 help='The cluster manager class to use.'
516 help='The cluster manager class to use.'
517 )
517 )
518
518
519 kernel_spec_manager = Instance(KernelSpecManager)
519 kernel_spec_manager = Instance(KernelSpecManager)
520
520
521 def _kernel_spec_manager_default(self):
521 def _kernel_spec_manager_default(self):
522 return KernelSpecManager(ipython_dir=self.ipython_dir)
522 return KernelSpecManager(ipython_dir=self.ipython_dir)
523
523
524 trust_xheaders = Bool(False, config=True,
524 trust_xheaders = Bool(False, config=True,
525 help=("Whether to trust or not X-Scheme/X-Forwarded-Proto and X-Real-Ip/X-Forwarded-For headers"
525 help=("Whether to trust or not X-Scheme/X-Forwarded-Proto and X-Real-Ip/X-Forwarded-For headers"
526 "sent by the upstream reverse proxy. Necessary if the proxy handles SSL")
526 "sent by the upstream reverse proxy. Necessary if the proxy handles SSL")
527 )
527 )
528
528
529 info_file = Unicode()
529 info_file = Unicode()
530
530
531 def _info_file_default(self):
531 def _info_file_default(self):
532 info_file = "nbserver-%s.json"%os.getpid()
532 info_file = "nbserver-%s.json"%os.getpid()
533 return os.path.join(self.profile_dir.security_dir, info_file)
533 return os.path.join(self.profile_dir.security_dir, info_file)
534
534
535 notebook_dir = Unicode(py3compat.getcwd(), config=True,
535 notebook_dir = Unicode(py3compat.getcwd(), config=True,
536 help="The directory to use for notebooks and kernels."
536 help="The directory to use for notebooks and kernels."
537 )
537 )
538
538
539 pylab = Unicode('disabled', config=True,
539 pylab = Unicode('disabled', config=True,
540 help="""
540 help="""
541 DISABLED: use %pylab or %matplotlib in the notebook to enable matplotlib.
541 DISABLED: use %pylab or %matplotlib in the notebook to enable matplotlib.
542 """
542 """
543 )
543 )
544 def _pylab_changed(self, name, old, new):
544 def _pylab_changed(self, name, old, new):
545 """when --pylab is specified, display a warning and exit"""
545 """when --pylab is specified, display a warning and exit"""
546 if new != 'warn':
546 if new != 'warn':
547 backend = ' %s' % new
547 backend = ' %s' % new
548 else:
548 else:
549 backend = ''
549 backend = ''
550 self.log.error("Support for specifying --pylab on the command line has been removed.")
550 self.log.error("Support for specifying --pylab on the command line has been removed.")
551 self.log.error(
551 self.log.error(
552 "Please use `%pylab{0}` or `%matplotlib{0}` in the notebook itself.".format(backend)
552 "Please use `%pylab{0}` or `%matplotlib{0}` in the notebook itself.".format(backend)
553 )
553 )
554 self.exit(1)
554 self.exit(1)
555
555
556 def _notebook_dir_changed(self, name, old, new):
556 def _notebook_dir_changed(self, name, old, new):
557 """Do a bit of validation of the notebook dir."""
557 """Do a bit of validation of the notebook dir."""
558 if not os.path.isabs(new):
558 if not os.path.isabs(new):
559 # If we receive a non-absolute path, make it absolute.
559 # If we receive a non-absolute path, make it absolute.
560 self.notebook_dir = os.path.abspath(new)
560 self.notebook_dir = os.path.abspath(new)
561 return
561 return
562 if not os.path.isdir(new):
562 if not os.path.isdir(new):
563 raise TraitError("No such notebook dir: %r" % new)
563 raise TraitError("No such notebook dir: %r" % new)
564
564
565 # setting App.notebook_dir implies setting notebook and kernel dirs as well
565 # setting App.notebook_dir implies setting notebook and kernel dirs as well
566 self.config.FileNotebookManager.notebook_dir = new
566 self.config.FileNotebookManager.notebook_dir = new
567 self.config.MappingKernelManager.root_dir = new
567 self.config.MappingKernelManager.root_dir = new
568
568
569
569
570 def parse_command_line(self, argv=None):
570 def parse_command_line(self, argv=None):
571 super(NotebookApp, self).parse_command_line(argv)
571 super(NotebookApp, self).parse_command_line(argv)
572
572
573 if self.extra_args:
573 if self.extra_args:
574 arg0 = self.extra_args[0]
574 arg0 = self.extra_args[0]
575 f = os.path.abspath(arg0)
575 f = os.path.abspath(arg0)
576 self.argv.remove(arg0)
576 self.argv.remove(arg0)
577 if not os.path.exists(f):
577 if not os.path.exists(f):
578 self.log.critical("No such file or directory: %s", f)
578 self.log.critical("No such file or directory: %s", f)
579 self.exit(1)
579 self.exit(1)
580
580
581 # Use config here, to ensure that it takes higher priority than
581 # Use config here, to ensure that it takes higher priority than
582 # anything that comes from the profile.
582 # anything that comes from the profile.
583 c = Config()
583 c = Config()
584 if os.path.isdir(f):
584 if os.path.isdir(f):
585 c.NotebookApp.notebook_dir = f
585 c.NotebookApp.notebook_dir = f
586 elif os.path.isfile(f):
586 elif os.path.isfile(f):
587 c.NotebookApp.file_to_run = f
587 c.NotebookApp.file_to_run = f
588 self.update_config(c)
588 self.update_config(c)
589
589
590 def init_kernel_argv(self):
590 def init_kernel_argv(self):
591 """construct the kernel arguments"""
591 """construct the kernel arguments"""
592 self.kernel_argv = []
593 # Kernel should inherit default config file from frontend
594 self.kernel_argv.append("--IPKernelApp.parent_appname='%s'" % self.name)
595 # Kernel should get *absolute* path to profile directory
592 # Kernel should get *absolute* path to profile directory
596 self.kernel_argv.extend(["--profile-dir", self.profile_dir.location])
593 self.kernel_argv = ["--profile-dir", self.profile_dir.location]
597
594
598 def init_configurables(self):
595 def init_configurables(self):
599 # force Session default to be secure
596 # force Session default to be secure
600 default_secure(self.config)
597 default_secure(self.config)
601 kls = import_item(self.kernel_manager_class)
598 kls = import_item(self.kernel_manager_class)
602 self.kernel_manager = kls(
599 self.kernel_manager = kls(
603 parent=self, log=self.log, kernel_argv=self.kernel_argv,
600 parent=self, log=self.log, kernel_argv=self.kernel_argv,
604 connection_dir = self.profile_dir.security_dir,
601 connection_dir = self.profile_dir.security_dir,
605 )
602 )
606 kls = import_item(self.notebook_manager_class)
603 kls = import_item(self.notebook_manager_class)
607 self.notebook_manager = kls(parent=self, log=self.log)
604 self.notebook_manager = kls(parent=self, log=self.log)
608 kls = import_item(self.session_manager_class)
605 kls = import_item(self.session_manager_class)
609 self.session_manager = kls(parent=self, log=self.log)
606 self.session_manager = kls(parent=self, log=self.log)
610 kls = import_item(self.cluster_manager_class)
607 kls = import_item(self.cluster_manager_class)
611 self.cluster_manager = kls(parent=self, log=self.log)
608 self.cluster_manager = kls(parent=self, log=self.log)
612 self.cluster_manager.update_profiles()
609 self.cluster_manager.update_profiles()
613
610
614 def init_logging(self):
611 def init_logging(self):
615 # This prevents double log messages because tornado use a root logger that
612 # This prevents double log messages because tornado use a root logger that
616 # self.log is a child of. The logging module dipatches log messages to a log
613 # self.log is a child of. The logging module dipatches log messages to a log
617 # and all of its ancenstors until propagate is set to False.
614 # and all of its ancenstors until propagate is set to False.
618 self.log.propagate = False
615 self.log.propagate = False
619
616
620 # hook up tornado 3's loggers to our app handlers
617 # hook up tornado 3's loggers to our app handlers
621 logger = logging.getLogger('tornado')
618 logger = logging.getLogger('tornado')
622 logger.propagate = True
619 logger.propagate = True
623 logger.parent = self.log
620 logger.parent = self.log
624 logger.setLevel(self.log.level)
621 logger.setLevel(self.log.level)
625
622
626 def init_webapp(self):
623 def init_webapp(self):
627 """initialize tornado webapp and httpserver"""
624 """initialize tornado webapp and httpserver"""
628 self.web_app = NotebookWebApplication(
625 self.web_app = NotebookWebApplication(
629 self, self.kernel_manager, self.notebook_manager,
626 self, self.kernel_manager, self.notebook_manager,
630 self.cluster_manager, self.session_manager, self.kernel_spec_manager,
627 self.cluster_manager, self.session_manager, self.kernel_spec_manager,
631 self.log, self.base_url, self.webapp_settings,
628 self.log, self.base_url, self.webapp_settings,
632 self.jinja_environment_options
629 self.jinja_environment_options
633 )
630 )
634 if self.certfile:
631 if self.certfile:
635 ssl_options = dict(certfile=self.certfile)
632 ssl_options = dict(certfile=self.certfile)
636 if self.keyfile:
633 if self.keyfile:
637 ssl_options['keyfile'] = self.keyfile
634 ssl_options['keyfile'] = self.keyfile
638 else:
635 else:
639 ssl_options = None
636 ssl_options = None
640 self.web_app.password = self.password
637 self.web_app.password = self.password
641 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options,
638 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options,
642 xheaders=self.trust_xheaders)
639 xheaders=self.trust_xheaders)
643 if not self.ip:
640 if not self.ip:
644 warning = "WARNING: The notebook server is listening on all IP addresses"
641 warning = "WARNING: The notebook server is listening on all IP addresses"
645 if ssl_options is None:
642 if ssl_options is None:
646 self.log.critical(warning + " and not using encryption. This "
643 self.log.critical(warning + " and not using encryption. This "
647 "is not recommended.")
644 "is not recommended.")
648 if not self.password:
645 if not self.password:
649 self.log.critical(warning + " and not using authentication. "
646 self.log.critical(warning + " and not using authentication. "
650 "This is highly insecure and not recommended.")
647 "This is highly insecure and not recommended.")
651 success = None
648 success = None
652 for port in random_ports(self.port, self.port_retries+1):
649 for port in random_ports(self.port, self.port_retries+1):
653 try:
650 try:
654 self.http_server.listen(port, self.ip)
651 self.http_server.listen(port, self.ip)
655 except socket.error as e:
652 except socket.error as e:
656 if e.errno == errno.EADDRINUSE:
653 if e.errno == errno.EADDRINUSE:
657 self.log.info('The port %i is already in use, trying another random port.' % port)
654 self.log.info('The port %i is already in use, trying another random port.' % port)
658 continue
655 continue
659 elif e.errno in (errno.EACCES, getattr(errno, 'WSAEACCES', errno.EACCES)):
656 elif e.errno in (errno.EACCES, getattr(errno, 'WSAEACCES', errno.EACCES)):
660 self.log.warn("Permission to listen on port %i denied" % port)
657 self.log.warn("Permission to listen on port %i denied" % port)
661 continue
658 continue
662 else:
659 else:
663 raise
660 raise
664 else:
661 else:
665 self.port = port
662 self.port = port
666 success = True
663 success = True
667 break
664 break
668 if not success:
665 if not success:
669 self.log.critical('ERROR: the notebook server could not be started because '
666 self.log.critical('ERROR: the notebook server could not be started because '
670 'no available port could be found.')
667 'no available port could be found.')
671 self.exit(1)
668 self.exit(1)
672
669
673 @property
670 @property
674 def display_url(self):
671 def display_url(self):
675 ip = self.ip if self.ip else '[all ip addresses on your system]'
672 ip = self.ip if self.ip else '[all ip addresses on your system]'
676 return self._url(ip)
673 return self._url(ip)
677
674
678 @property
675 @property
679 def connection_url(self):
676 def connection_url(self):
680 ip = self.ip if self.ip else 'localhost'
677 ip = self.ip if self.ip else 'localhost'
681 return self._url(ip)
678 return self._url(ip)
682
679
683 def _url(self, ip):
680 def _url(self, ip):
684 proto = 'https' if self.certfile else 'http'
681 proto = 'https' if self.certfile else 'http'
685 return "%s://%s:%i%s" % (proto, ip, self.port, self.base_url)
682 return "%s://%s:%i%s" % (proto, ip, self.port, self.base_url)
686
683
687 def init_signal(self):
684 def init_signal(self):
688 if not sys.platform.startswith('win'):
685 if not sys.platform.startswith('win'):
689 signal.signal(signal.SIGINT, self._handle_sigint)
686 signal.signal(signal.SIGINT, self._handle_sigint)
690 signal.signal(signal.SIGTERM, self._signal_stop)
687 signal.signal(signal.SIGTERM, self._signal_stop)
691 if hasattr(signal, 'SIGUSR1'):
688 if hasattr(signal, 'SIGUSR1'):
692 # Windows doesn't support SIGUSR1
689 # Windows doesn't support SIGUSR1
693 signal.signal(signal.SIGUSR1, self._signal_info)
690 signal.signal(signal.SIGUSR1, self._signal_info)
694 if hasattr(signal, 'SIGINFO'):
691 if hasattr(signal, 'SIGINFO'):
695 # only on BSD-based systems
692 # only on BSD-based systems
696 signal.signal(signal.SIGINFO, self._signal_info)
693 signal.signal(signal.SIGINFO, self._signal_info)
697
694
698 def _handle_sigint(self, sig, frame):
695 def _handle_sigint(self, sig, frame):
699 """SIGINT handler spawns confirmation dialog"""
696 """SIGINT handler spawns confirmation dialog"""
700 # register more forceful signal handler for ^C^C case
697 # register more forceful signal handler for ^C^C case
701 signal.signal(signal.SIGINT, self._signal_stop)
698 signal.signal(signal.SIGINT, self._signal_stop)
702 # request confirmation dialog in bg thread, to avoid
699 # request confirmation dialog in bg thread, to avoid
703 # blocking the App
700 # blocking the App
704 thread = threading.Thread(target=self._confirm_exit)
701 thread = threading.Thread(target=self._confirm_exit)
705 thread.daemon = True
702 thread.daemon = True
706 thread.start()
703 thread.start()
707
704
708 def _restore_sigint_handler(self):
705 def _restore_sigint_handler(self):
709 """callback for restoring original SIGINT handler"""
706 """callback for restoring original SIGINT handler"""
710 signal.signal(signal.SIGINT, self._handle_sigint)
707 signal.signal(signal.SIGINT, self._handle_sigint)
711
708
712 def _confirm_exit(self):
709 def _confirm_exit(self):
713 """confirm shutdown on ^C
710 """confirm shutdown on ^C
714
711
715 A second ^C, or answering 'y' within 5s will cause shutdown,
712 A second ^C, or answering 'y' within 5s will cause shutdown,
716 otherwise original SIGINT handler will be restored.
713 otherwise original SIGINT handler will be restored.
717
714
718 This doesn't work on Windows.
715 This doesn't work on Windows.
719 """
716 """
720 # FIXME: remove this delay when pyzmq dependency is >= 2.1.11
717 # FIXME: remove this delay when pyzmq dependency is >= 2.1.11
721 time.sleep(0.1)
718 time.sleep(0.1)
722 info = self.log.info
719 info = self.log.info
723 info('interrupted')
720 info('interrupted')
724 print(self.notebook_info())
721 print(self.notebook_info())
725 sys.stdout.write("Shutdown this notebook server (y/[n])? ")
722 sys.stdout.write("Shutdown this notebook server (y/[n])? ")
726 sys.stdout.flush()
723 sys.stdout.flush()
727 r,w,x = select.select([sys.stdin], [], [], 5)
724 r,w,x = select.select([sys.stdin], [], [], 5)
728 if r:
725 if r:
729 line = sys.stdin.readline()
726 line = sys.stdin.readline()
730 if line.lower().startswith('y') and 'n' not in line.lower():
727 if line.lower().startswith('y') and 'n' not in line.lower():
731 self.log.critical("Shutdown confirmed")
728 self.log.critical("Shutdown confirmed")
732 ioloop.IOLoop.instance().stop()
729 ioloop.IOLoop.instance().stop()
733 return
730 return
734 else:
731 else:
735 print("No answer for 5s:", end=' ')
732 print("No answer for 5s:", end=' ')
736 print("resuming operation...")
733 print("resuming operation...")
737 # no answer, or answer is no:
734 # no answer, or answer is no:
738 # set it back to original SIGINT handler
735 # set it back to original SIGINT handler
739 # use IOLoop.add_callback because signal.signal must be called
736 # use IOLoop.add_callback because signal.signal must be called
740 # from main thread
737 # from main thread
741 ioloop.IOLoop.instance().add_callback(self._restore_sigint_handler)
738 ioloop.IOLoop.instance().add_callback(self._restore_sigint_handler)
742
739
743 def _signal_stop(self, sig, frame):
740 def _signal_stop(self, sig, frame):
744 self.log.critical("received signal %s, stopping", sig)
741 self.log.critical("received signal %s, stopping", sig)
745 ioloop.IOLoop.instance().stop()
742 ioloop.IOLoop.instance().stop()
746
743
747 def _signal_info(self, sig, frame):
744 def _signal_info(self, sig, frame):
748 print(self.notebook_info())
745 print(self.notebook_info())
749
746
750 def init_components(self):
747 def init_components(self):
751 """Check the components submodule, and warn if it's unclean"""
748 """Check the components submodule, and warn if it's unclean"""
752 status = submodule.check_submodule_status()
749 status = submodule.check_submodule_status()
753 if status == 'missing':
750 if status == 'missing':
754 self.log.warn("components submodule missing, running `git submodule update`")
751 self.log.warn("components submodule missing, running `git submodule update`")
755 submodule.update_submodules(submodule.ipython_parent())
752 submodule.update_submodules(submodule.ipython_parent())
756 elif status == 'unclean':
753 elif status == 'unclean':
757 self.log.warn("components submodule unclean, you may see 404s on static/components")
754 self.log.warn("components submodule unclean, you may see 404s on static/components")
758 self.log.warn("run `setup.py submodule` or `git submodule update` to update")
755 self.log.warn("run `setup.py submodule` or `git submodule update` to update")
759
756
760 @catch_config_error
757 @catch_config_error
761 def initialize(self, argv=None):
758 def initialize(self, argv=None):
762 super(NotebookApp, self).initialize(argv)
759 super(NotebookApp, self).initialize(argv)
763 self.init_logging()
760 self.init_logging()
764 self.init_kernel_argv()
761 self.init_kernel_argv()
765 self.init_configurables()
762 self.init_configurables()
766 self.init_components()
763 self.init_components()
767 self.init_webapp()
764 self.init_webapp()
768 self.init_signal()
765 self.init_signal()
769
766
770 def cleanup_kernels(self):
767 def cleanup_kernels(self):
771 """Shutdown all kernels.
768 """Shutdown all kernels.
772
769
773 The kernels will shutdown themselves when this process no longer exists,
770 The kernels will shutdown themselves when this process no longer exists,
774 but explicit shutdown allows the KernelManagers to cleanup the connection files.
771 but explicit shutdown allows the KernelManagers to cleanup the connection files.
775 """
772 """
776 self.log.info('Shutting down kernels')
773 self.log.info('Shutting down kernels')
777 self.kernel_manager.shutdown_all()
774 self.kernel_manager.shutdown_all()
778
775
779 def notebook_info(self):
776 def notebook_info(self):
780 "Return the current working directory and the server url information"
777 "Return the current working directory and the server url information"
781 info = self.notebook_manager.info_string() + "\n"
778 info = self.notebook_manager.info_string() + "\n"
782 info += "%d active kernels \n" % len(self.kernel_manager._kernels)
779 info += "%d active kernels \n" % len(self.kernel_manager._kernels)
783 return info + "The IPython Notebook is running at: %s" % self.display_url
780 return info + "The IPython Notebook is running at: %s" % self.display_url
784
781
785 def server_info(self):
782 def server_info(self):
786 """Return a JSONable dict of information about this server."""
783 """Return a JSONable dict of information about this server."""
787 return {'url': self.connection_url,
784 return {'url': self.connection_url,
788 'hostname': self.ip if self.ip else 'localhost',
785 'hostname': self.ip if self.ip else 'localhost',
789 'port': self.port,
786 'port': self.port,
790 'secure': bool(self.certfile),
787 'secure': bool(self.certfile),
791 'base_url': self.base_url,
788 'base_url': self.base_url,
792 'notebook_dir': os.path.abspath(self.notebook_dir),
789 'notebook_dir': os.path.abspath(self.notebook_dir),
793 }
790 }
794
791
795 def write_server_info_file(self):
792 def write_server_info_file(self):
796 """Write the result of server_info() to the JSON file info_file."""
793 """Write the result of server_info() to the JSON file info_file."""
797 with open(self.info_file, 'w') as f:
794 with open(self.info_file, 'w') as f:
798 json.dump(self.server_info(), f, indent=2)
795 json.dump(self.server_info(), f, indent=2)
799
796
800 def remove_server_info_file(self):
797 def remove_server_info_file(self):
801 """Remove the nbserver-<pid>.json file created for this server.
798 """Remove the nbserver-<pid>.json file created for this server.
802
799
803 Ignores the error raised when the file has already been removed.
800 Ignores the error raised when the file has already been removed.
804 """
801 """
805 try:
802 try:
806 os.unlink(self.info_file)
803 os.unlink(self.info_file)
807 except OSError as e:
804 except OSError as e:
808 if e.errno != errno.ENOENT:
805 if e.errno != errno.ENOENT:
809 raise
806 raise
810
807
811 def start(self):
808 def start(self):
812 """ Start the IPython Notebook server app, after initialization
809 """ Start the IPython Notebook server app, after initialization
813
810
814 This method takes no arguments so all configuration and initialization
811 This method takes no arguments so all configuration and initialization
815 must be done prior to calling this method."""
812 must be done prior to calling this method."""
816 if self.subapp is not None:
813 if self.subapp is not None:
817 return self.subapp.start()
814 return self.subapp.start()
818
815
819 info = self.log.info
816 info = self.log.info
820 for line in self.notebook_info().split("\n"):
817 for line in self.notebook_info().split("\n"):
821 info(line)
818 info(line)
822 info("Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).")
819 info("Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).")
823
820
824 self.write_server_info_file()
821 self.write_server_info_file()
825
822
826 if self.open_browser or self.file_to_run:
823 if self.open_browser or self.file_to_run:
827 try:
824 try:
828 browser = webbrowser.get(self.browser or None)
825 browser = webbrowser.get(self.browser or None)
829 except webbrowser.Error as e:
826 except webbrowser.Error as e:
830 self.log.warn('No web browser found: %s.' % e)
827 self.log.warn('No web browser found: %s.' % e)
831 browser = None
828 browser = None
832
829
833 if self.file_to_run:
830 if self.file_to_run:
834 fullpath = os.path.join(self.notebook_dir, self.file_to_run)
831 fullpath = os.path.join(self.notebook_dir, self.file_to_run)
835 if not os.path.exists(fullpath):
832 if not os.path.exists(fullpath):
836 self.log.critical("%s does not exist" % fullpath)
833 self.log.critical("%s does not exist" % fullpath)
837 self.exit(1)
834 self.exit(1)
838
835
839 uri = url_path_join('notebooks', self.file_to_run)
836 uri = url_path_join('notebooks', self.file_to_run)
840 else:
837 else:
841 uri = 'tree'
838 uri = 'tree'
842 if browser:
839 if browser:
843 b = lambda : browser.open(url_path_join(self.connection_url, uri),
840 b = lambda : browser.open(url_path_join(self.connection_url, uri),
844 new=2)
841 new=2)
845 threading.Thread(target=b).start()
842 threading.Thread(target=b).start()
846 try:
843 try:
847 ioloop.IOLoop.instance().start()
844 ioloop.IOLoop.instance().start()
848 except KeyboardInterrupt:
845 except KeyboardInterrupt:
849 info("Interrupted...")
846 info("Interrupted...")
850 finally:
847 finally:
851 self.cleanup_kernels()
848 self.cleanup_kernels()
852 self.remove_server_info_file()
849 self.remove_server_info_file()
853
850
854
851
855 def list_running_servers(profile='default'):
852 def list_running_servers(profile='default'):
856 """Iterate over the server info files of running notebook servers.
853 """Iterate over the server info files of running notebook servers.
857
854
858 Given a profile name, find nbserver-* files in the security directory of
855 Given a profile name, find nbserver-* files in the security directory of
859 that profile, and yield dicts of their information, each one pertaining to
856 that profile, and yield dicts of their information, each one pertaining to
860 a currently running notebook server instance.
857 a currently running notebook server instance.
861 """
858 """
862 pd = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), name=profile)
859 pd = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), name=profile)
863 for file in os.listdir(pd.security_dir):
860 for file in os.listdir(pd.security_dir):
864 if file.startswith('nbserver-'):
861 if file.startswith('nbserver-'):
865 with io.open(os.path.join(pd.security_dir, file), encoding='utf-8') as f:
862 with io.open(os.path.join(pd.security_dir, file), encoding='utf-8') as f:
866 yield json.load(f)
863 yield json.load(f)
867
864
868 #-----------------------------------------------------------------------------
865 #-----------------------------------------------------------------------------
869 # Main entry point
866 # Main entry point
870 #-----------------------------------------------------------------------------
867 #-----------------------------------------------------------------------------
871
868
872 launch_new_instance = NotebookApp.launch_instance
869 launch_new_instance = NotebookApp.launch_instance
873
870
@@ -1,405 +1,395 b''
1 """An Application for launching a kernel"""
1 """An Application for launching a kernel"""
2
2
3 # Copyright (c) IPython Development Team.
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
4 # Distributed under the terms of the Modified BSD License.
5
5
6 from __future__ import print_function
6 from __future__ import print_function
7
7
8 import atexit
8 import atexit
9 import os
9 import os
10 import sys
10 import sys
11 import signal
11 import signal
12
12
13 import zmq
13 import zmq
14 from zmq.eventloop import ioloop
14 from zmq.eventloop import ioloop
15 from zmq.eventloop.zmqstream import ZMQStream
15 from zmq.eventloop.zmqstream import ZMQStream
16
16
17 from IPython.core.ultratb import FormattedTB
17 from IPython.core.ultratb import FormattedTB
18 from IPython.core.application import (
18 from IPython.core.application import (
19 BaseIPythonApplication, base_flags, base_aliases, catch_config_error
19 BaseIPythonApplication, base_flags, base_aliases, catch_config_error
20 )
20 )
21 from IPython.core.profiledir import ProfileDir
21 from IPython.core.profiledir import ProfileDir
22 from IPython.core.shellapp import (
22 from IPython.core.shellapp import (
23 InteractiveShellApp, shell_flags, shell_aliases
23 InteractiveShellApp, shell_flags, shell_aliases
24 )
24 )
25 from IPython.utils import io
25 from IPython.utils import io
26 from IPython.utils.path import filefind
26 from IPython.utils.path import filefind
27 from IPython.utils.traitlets import (
27 from IPython.utils.traitlets import (
28 Any, Instance, Dict, Unicode, Integer, Bool, DottedObjectName, Type,
28 Any, Instance, Dict, Unicode, Integer, Bool, DottedObjectName, Type,
29 )
29 )
30 from IPython.utils.importstring import import_item
30 from IPython.utils.importstring import import_item
31 from IPython.kernel import write_connection_file
31 from IPython.kernel import write_connection_file
32 from IPython.kernel.connect import ConnectionFileMixin
32 from IPython.kernel.connect import ConnectionFileMixin
33
33
34 # local imports
34 # local imports
35 from .heartbeat import Heartbeat
35 from .heartbeat import Heartbeat
36 from .ipkernel import IPythonKernel
36 from .ipkernel import IPythonKernel
37 from .parentpoller import ParentPollerUnix, ParentPollerWindows
37 from .parentpoller import ParentPollerUnix, ParentPollerWindows
38 from .session import (
38 from .session import (
39 Session, session_flags, session_aliases, default_secure,
39 Session, session_flags, session_aliases, default_secure,
40 )
40 )
41 from .zmqshell import ZMQInteractiveShell
41 from .zmqshell import ZMQInteractiveShell
42
42
43 #-----------------------------------------------------------------------------
43 #-----------------------------------------------------------------------------
44 # Flags and Aliases
44 # Flags and Aliases
45 #-----------------------------------------------------------------------------
45 #-----------------------------------------------------------------------------
46
46
47 kernel_aliases = dict(base_aliases)
47 kernel_aliases = dict(base_aliases)
48 kernel_aliases.update({
48 kernel_aliases.update({
49 'ip' : 'IPKernelApp.ip',
49 'ip' : 'IPKernelApp.ip',
50 'hb' : 'IPKernelApp.hb_port',
50 'hb' : 'IPKernelApp.hb_port',
51 'shell' : 'IPKernelApp.shell_port',
51 'shell' : 'IPKernelApp.shell_port',
52 'iopub' : 'IPKernelApp.iopub_port',
52 'iopub' : 'IPKernelApp.iopub_port',
53 'stdin' : 'IPKernelApp.stdin_port',
53 'stdin' : 'IPKernelApp.stdin_port',
54 'control' : 'IPKernelApp.control_port',
54 'control' : 'IPKernelApp.control_port',
55 'f' : 'IPKernelApp.connection_file',
55 'f' : 'IPKernelApp.connection_file',
56 'parent': 'IPKernelApp.parent_handle',
56 'parent': 'IPKernelApp.parent_handle',
57 'transport': 'IPKernelApp.transport',
57 'transport': 'IPKernelApp.transport',
58 })
58 })
59 if sys.platform.startswith('win'):
59 if sys.platform.startswith('win'):
60 kernel_aliases['interrupt'] = 'IPKernelApp.interrupt'
60 kernel_aliases['interrupt'] = 'IPKernelApp.interrupt'
61
61
62 kernel_flags = dict(base_flags)
62 kernel_flags = dict(base_flags)
63 kernel_flags.update({
63 kernel_flags.update({
64 'no-stdout' : (
64 'no-stdout' : (
65 {'IPKernelApp' : {'no_stdout' : True}},
65 {'IPKernelApp' : {'no_stdout' : True}},
66 "redirect stdout to the null device"),
66 "redirect stdout to the null device"),
67 'no-stderr' : (
67 'no-stderr' : (
68 {'IPKernelApp' : {'no_stderr' : True}},
68 {'IPKernelApp' : {'no_stderr' : True}},
69 "redirect stderr to the null device"),
69 "redirect stderr to the null device"),
70 'pylab' : (
70 'pylab' : (
71 {'IPKernelApp' : {'pylab' : 'auto'}},
71 {'IPKernelApp' : {'pylab' : 'auto'}},
72 """Pre-load matplotlib and numpy for interactive use with
72 """Pre-load matplotlib and numpy for interactive use with
73 the default matplotlib backend."""),
73 the default matplotlib backend."""),
74 })
74 })
75
75
76 # inherit flags&aliases for any IPython shell apps
76 # inherit flags&aliases for any IPython shell apps
77 kernel_aliases.update(shell_aliases)
77 kernel_aliases.update(shell_aliases)
78 kernel_flags.update(shell_flags)
78 kernel_flags.update(shell_flags)
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 _ctrl_c_message = """\
84 _ctrl_c_message = """\
85 NOTE: When using the `ipython kernel` entry point, Ctrl-C will not work.
85 NOTE: When using the `ipython kernel` entry point, Ctrl-C will not work.
86
86
87 To exit, you will have to explicitly quit this process, by either sending
87 To exit, you will have to explicitly quit this process, by either sending
88 "quit" from a client, or using Ctrl-\\ in UNIX-like environments.
88 "quit" from a client, or using Ctrl-\\ in UNIX-like environments.
89
89
90 To read more about this, see https://github.com/ipython/ipython/issues/2049
90 To read more about this, see https://github.com/ipython/ipython/issues/2049
91
91
92 """
92 """
93
93
94 #-----------------------------------------------------------------------------
94 #-----------------------------------------------------------------------------
95 # Application class for starting an IPython Kernel
95 # Application class for starting an IPython Kernel
96 #-----------------------------------------------------------------------------
96 #-----------------------------------------------------------------------------
97
97
98 class IPKernelApp(BaseIPythonApplication, InteractiveShellApp,
98 class IPKernelApp(BaseIPythonApplication, InteractiveShellApp,
99 ConnectionFileMixin):
99 ConnectionFileMixin):
100 name='ipkernel'
100 name='ipkernel'
101 aliases = Dict(kernel_aliases)
101 aliases = Dict(kernel_aliases)
102 flags = Dict(kernel_flags)
102 flags = Dict(kernel_flags)
103 classes = [IPythonKernel, ZMQInteractiveShell, ProfileDir, Session]
103 classes = [IPythonKernel, ZMQInteractiveShell, ProfileDir, Session]
104 # the kernel class, as an importstring
104 # the kernel class, as an importstring
105 kernel_class = Type('IPython.kernel.zmq.ipkernel.IPythonKernel', config=True,
105 kernel_class = Type('IPython.kernel.zmq.ipkernel.IPythonKernel', config=True,
106 klass='IPython.kernel.zmq.kernelbase.Kernel',
106 klass='IPython.kernel.zmq.kernelbase.Kernel',
107 help="""The Kernel subclass to be used.
107 help="""The Kernel subclass to be used.
108
108
109 This should allow easy re-use of the IPKernelApp entry point
109 This should allow easy re-use of the IPKernelApp entry point
110 to configure and launch kernels other than IPython's own.
110 to configure and launch kernels other than IPython's own.
111 """)
111 """)
112 kernel = Any()
112 kernel = Any()
113 poller = Any() # don't restrict this even though current pollers are all Threads
113 poller = Any() # don't restrict this even though current pollers are all Threads
114 heartbeat = Instance(Heartbeat)
114 heartbeat = Instance(Heartbeat)
115 ports = Dict()
115 ports = Dict()
116
116
117 # ipkernel doesn't get its own config file
117 # ipkernel doesn't get its own config file
118 def _config_file_name_default(self):
118 def _config_file_name_default(self):
119 return 'ipython_config.py'
119 return 'ipython_config.py'
120
120
121 # inherit config file name from parent:
122 parent_appname = Unicode(config=True)
123 def _parent_appname_changed(self, name, old, new):
124 if self.config_file_specified:
125 # it was manually specified, ignore
126 return
127 self.config_file_name = new.replace('-','_') + u'_config.py'
128 # don't let this count as specifying the config file
129 self.config_file_specified.remove(self.config_file_name)
130
131 # connection info:
121 # connection info:
132
122
133 @property
123 @property
134 def abs_connection_file(self):
124 def abs_connection_file(self):
135 if os.path.basename(self.connection_file) == self.connection_file:
125 if os.path.basename(self.connection_file) == self.connection_file:
136 return os.path.join(self.profile_dir.security_dir, self.connection_file)
126 return os.path.join(self.profile_dir.security_dir, self.connection_file)
137 else:
127 else:
138 return self.connection_file
128 return self.connection_file
139
129
140
130
141 # streams, etc.
131 # streams, etc.
142 no_stdout = Bool(False, config=True, help="redirect stdout to the null device")
132 no_stdout = Bool(False, config=True, help="redirect stdout to the null device")
143 no_stderr = Bool(False, config=True, help="redirect stderr to the null device")
133 no_stderr = Bool(False, config=True, help="redirect stderr to the null device")
144 outstream_class = DottedObjectName('IPython.kernel.zmq.iostream.OutStream',
134 outstream_class = DottedObjectName('IPython.kernel.zmq.iostream.OutStream',
145 config=True, help="The importstring for the OutStream factory")
135 config=True, help="The importstring for the OutStream factory")
146 displayhook_class = DottedObjectName('IPython.kernel.zmq.displayhook.ZMQDisplayHook',
136 displayhook_class = DottedObjectName('IPython.kernel.zmq.displayhook.ZMQDisplayHook',
147 config=True, help="The importstring for the DisplayHook factory")
137 config=True, help="The importstring for the DisplayHook factory")
148
138
149 # polling
139 # polling
150 parent_handle = Integer(0, config=True,
140 parent_handle = Integer(0, config=True,
151 help="""kill this process if its parent dies. On Windows, the argument
141 help="""kill this process if its parent dies. On Windows, the argument
152 specifies the HANDLE of the parent process, otherwise it is simply boolean.
142 specifies the HANDLE of the parent process, otherwise it is simply boolean.
153 """)
143 """)
154 interrupt = Integer(0, config=True,
144 interrupt = Integer(0, config=True,
155 help="""ONLY USED ON WINDOWS
145 help="""ONLY USED ON WINDOWS
156 Interrupt this process when the parent is signaled.
146 Interrupt this process when the parent is signaled.
157 """)
147 """)
158
148
159 def init_crash_handler(self):
149 def init_crash_handler(self):
160 # Install minimal exception handling
150 # Install minimal exception handling
161 sys.excepthook = FormattedTB(mode='Verbose', color_scheme='NoColor',
151 sys.excepthook = FormattedTB(mode='Verbose', color_scheme='NoColor',
162 ostream=sys.__stdout__)
152 ostream=sys.__stdout__)
163
153
164 def init_poller(self):
154 def init_poller(self):
165 if sys.platform == 'win32':
155 if sys.platform == 'win32':
166 if self.interrupt or self.parent_handle:
156 if self.interrupt or self.parent_handle:
167 self.poller = ParentPollerWindows(self.interrupt, self.parent_handle)
157 self.poller = ParentPollerWindows(self.interrupt, self.parent_handle)
168 elif self.parent_handle:
158 elif self.parent_handle:
169 self.poller = ParentPollerUnix()
159 self.poller = ParentPollerUnix()
170
160
171 def _bind_socket(self, s, port):
161 def _bind_socket(self, s, port):
172 iface = '%s://%s' % (self.transport, self.ip)
162 iface = '%s://%s' % (self.transport, self.ip)
173 if self.transport == 'tcp':
163 if self.transport == 'tcp':
174 if port <= 0:
164 if port <= 0:
175 port = s.bind_to_random_port(iface)
165 port = s.bind_to_random_port(iface)
176 else:
166 else:
177 s.bind("tcp://%s:%i" % (self.ip, port))
167 s.bind("tcp://%s:%i" % (self.ip, port))
178 elif self.transport == 'ipc':
168 elif self.transport == 'ipc':
179 if port <= 0:
169 if port <= 0:
180 port = 1
170 port = 1
181 path = "%s-%i" % (self.ip, port)
171 path = "%s-%i" % (self.ip, port)
182 while os.path.exists(path):
172 while os.path.exists(path):
183 port = port + 1
173 port = port + 1
184 path = "%s-%i" % (self.ip, port)
174 path = "%s-%i" % (self.ip, port)
185 else:
175 else:
186 path = "%s-%i" % (self.ip, port)
176 path = "%s-%i" % (self.ip, port)
187 s.bind("ipc://%s" % path)
177 s.bind("ipc://%s" % path)
188 return port
178 return port
189
179
190 def write_connection_file(self):
180 def write_connection_file(self):
191 """write connection info to JSON file"""
181 """write connection info to JSON file"""
192 cf = self.abs_connection_file
182 cf = self.abs_connection_file
193 self.log.debug("Writing connection file: %s", cf)
183 self.log.debug("Writing connection file: %s", cf)
194 write_connection_file(cf, ip=self.ip, key=self.session.key, transport=self.transport,
184 write_connection_file(cf, ip=self.ip, key=self.session.key, transport=self.transport,
195 shell_port=self.shell_port, stdin_port=self.stdin_port, hb_port=self.hb_port,
185 shell_port=self.shell_port, stdin_port=self.stdin_port, hb_port=self.hb_port,
196 iopub_port=self.iopub_port, control_port=self.control_port)
186 iopub_port=self.iopub_port, control_port=self.control_port)
197
187
198 def cleanup_connection_file(self):
188 def cleanup_connection_file(self):
199 cf = self.abs_connection_file
189 cf = self.abs_connection_file
200 self.log.debug("Cleaning up connection file: %s", cf)
190 self.log.debug("Cleaning up connection file: %s", cf)
201 try:
191 try:
202 os.remove(cf)
192 os.remove(cf)
203 except (IOError, OSError):
193 except (IOError, OSError):
204 pass
194 pass
205
195
206 self.cleanup_ipc_files()
196 self.cleanup_ipc_files()
207
197
208 def init_connection_file(self):
198 def init_connection_file(self):
209 if not self.connection_file:
199 if not self.connection_file:
210 self.connection_file = "kernel-%s.json"%os.getpid()
200 self.connection_file = "kernel-%s.json"%os.getpid()
211 try:
201 try:
212 self.connection_file = filefind(self.connection_file, ['.', self.profile_dir.security_dir])
202 self.connection_file = filefind(self.connection_file, ['.', self.profile_dir.security_dir])
213 except IOError:
203 except IOError:
214 self.log.debug("Connection file not found: %s", self.connection_file)
204 self.log.debug("Connection file not found: %s", self.connection_file)
215 # This means I own it, so I will clean it up:
205 # This means I own it, so I will clean it up:
216 atexit.register(self.cleanup_connection_file)
206 atexit.register(self.cleanup_connection_file)
217 return
207 return
218 try:
208 try:
219 self.load_connection_file()
209 self.load_connection_file()
220 except Exception:
210 except Exception:
221 self.log.error("Failed to load connection file: %r", self.connection_file, exc_info=True)
211 self.log.error("Failed to load connection file: %r", self.connection_file, exc_info=True)
222 self.exit(1)
212 self.exit(1)
223
213
224 def init_sockets(self):
214 def init_sockets(self):
225 # Create a context, a session, and the kernel sockets.
215 # Create a context, a session, and the kernel sockets.
226 self.log.info("Starting the kernel at pid: %i", os.getpid())
216 self.log.info("Starting the kernel at pid: %i", os.getpid())
227 context = zmq.Context.instance()
217 context = zmq.Context.instance()
228 # Uncomment this to try closing the context.
218 # Uncomment this to try closing the context.
229 # atexit.register(context.term)
219 # atexit.register(context.term)
230
220
231 self.shell_socket = context.socket(zmq.ROUTER)
221 self.shell_socket = context.socket(zmq.ROUTER)
232 self.shell_socket.linger = 1000
222 self.shell_socket.linger = 1000
233 self.shell_port = self._bind_socket(self.shell_socket, self.shell_port)
223 self.shell_port = self._bind_socket(self.shell_socket, self.shell_port)
234 self.log.debug("shell ROUTER Channel on port: %i" % self.shell_port)
224 self.log.debug("shell ROUTER Channel on port: %i" % self.shell_port)
235
225
236 self.iopub_socket = context.socket(zmq.PUB)
226 self.iopub_socket = context.socket(zmq.PUB)
237 self.iopub_socket.linger = 1000
227 self.iopub_socket.linger = 1000
238 self.iopub_port = self._bind_socket(self.iopub_socket, self.iopub_port)
228 self.iopub_port = self._bind_socket(self.iopub_socket, self.iopub_port)
239 self.log.debug("iopub PUB Channel on port: %i" % self.iopub_port)
229 self.log.debug("iopub PUB Channel on port: %i" % self.iopub_port)
240
230
241 self.stdin_socket = context.socket(zmq.ROUTER)
231 self.stdin_socket = context.socket(zmq.ROUTER)
242 self.stdin_socket.linger = 1000
232 self.stdin_socket.linger = 1000
243 self.stdin_port = self._bind_socket(self.stdin_socket, self.stdin_port)
233 self.stdin_port = self._bind_socket(self.stdin_socket, self.stdin_port)
244 self.log.debug("stdin ROUTER Channel on port: %i" % self.stdin_port)
234 self.log.debug("stdin ROUTER Channel on port: %i" % self.stdin_port)
245
235
246 self.control_socket = context.socket(zmq.ROUTER)
236 self.control_socket = context.socket(zmq.ROUTER)
247 self.control_socket.linger = 1000
237 self.control_socket.linger = 1000
248 self.control_port = self._bind_socket(self.control_socket, self.control_port)
238 self.control_port = self._bind_socket(self.control_socket, self.control_port)
249 self.log.debug("control ROUTER Channel on port: %i" % self.control_port)
239 self.log.debug("control ROUTER Channel on port: %i" % self.control_port)
250
240
251 def init_heartbeat(self):
241 def init_heartbeat(self):
252 """start the heart beating"""
242 """start the heart beating"""
253 # heartbeat doesn't share context, because it mustn't be blocked
243 # heartbeat doesn't share context, because it mustn't be blocked
254 # by the GIL, which is accessed by libzmq when freeing zero-copy messages
244 # by the GIL, which is accessed by libzmq when freeing zero-copy messages
255 hb_ctx = zmq.Context()
245 hb_ctx = zmq.Context()
256 self.heartbeat = Heartbeat(hb_ctx, (self.transport, self.ip, self.hb_port))
246 self.heartbeat = Heartbeat(hb_ctx, (self.transport, self.ip, self.hb_port))
257 self.hb_port = self.heartbeat.port
247 self.hb_port = self.heartbeat.port
258 self.log.debug("Heartbeat REP Channel on port: %i" % self.hb_port)
248 self.log.debug("Heartbeat REP Channel on port: %i" % self.hb_port)
259 self.heartbeat.start()
249 self.heartbeat.start()
260
250
261 def log_connection_info(self):
251 def log_connection_info(self):
262 """display connection info, and store ports"""
252 """display connection info, and store ports"""
263 basename = os.path.basename(self.connection_file)
253 basename = os.path.basename(self.connection_file)
264 if basename == self.connection_file or \
254 if basename == self.connection_file or \
265 os.path.dirname(self.connection_file) == self.profile_dir.security_dir:
255 os.path.dirname(self.connection_file) == self.profile_dir.security_dir:
266 # use shortname
256 # use shortname
267 tail = basename
257 tail = basename
268 if self.profile != 'default':
258 if self.profile != 'default':
269 tail += " --profile %s" % self.profile
259 tail += " --profile %s" % self.profile
270 else:
260 else:
271 tail = self.connection_file
261 tail = self.connection_file
272 lines = [
262 lines = [
273 "To connect another client to this kernel, use:",
263 "To connect another client to this kernel, use:",
274 " --existing %s" % tail,
264 " --existing %s" % tail,
275 ]
265 ]
276 # log connection info
266 # log connection info
277 # info-level, so often not shown.
267 # info-level, so often not shown.
278 # frontends should use the %connect_info magic
268 # frontends should use the %connect_info magic
279 # to see the connection info
269 # to see the connection info
280 for line in lines:
270 for line in lines:
281 self.log.info(line)
271 self.log.info(line)
282 # also raw print to the terminal if no parent_handle (`ipython kernel`)
272 # also raw print to the terminal if no parent_handle (`ipython kernel`)
283 if not self.parent_handle:
273 if not self.parent_handle:
284 io.rprint(_ctrl_c_message)
274 io.rprint(_ctrl_c_message)
285 for line in lines:
275 for line in lines:
286 io.rprint(line)
276 io.rprint(line)
287
277
288 self.ports = dict(shell=self.shell_port, iopub=self.iopub_port,
278 self.ports = dict(shell=self.shell_port, iopub=self.iopub_port,
289 stdin=self.stdin_port, hb=self.hb_port,
279 stdin=self.stdin_port, hb=self.hb_port,
290 control=self.control_port)
280 control=self.control_port)
291
281
292 def init_blackhole(self):
282 def init_blackhole(self):
293 """redirects stdout/stderr to devnull if necessary"""
283 """redirects stdout/stderr to devnull if necessary"""
294 if self.no_stdout or self.no_stderr:
284 if self.no_stdout or self.no_stderr:
295 blackhole = open(os.devnull, 'w')
285 blackhole = open(os.devnull, 'w')
296 if self.no_stdout:
286 if self.no_stdout:
297 sys.stdout = sys.__stdout__ = blackhole
287 sys.stdout = sys.__stdout__ = blackhole
298 if self.no_stderr:
288 if self.no_stderr:
299 sys.stderr = sys.__stderr__ = blackhole
289 sys.stderr = sys.__stderr__ = blackhole
300
290
301 def init_io(self):
291 def init_io(self):
302 """Redirect input streams and set a display hook."""
292 """Redirect input streams and set a display hook."""
303 if self.outstream_class:
293 if self.outstream_class:
304 outstream_factory = import_item(str(self.outstream_class))
294 outstream_factory = import_item(str(self.outstream_class))
305 sys.stdout = outstream_factory(self.session, self.iopub_socket, u'stdout')
295 sys.stdout = outstream_factory(self.session, self.iopub_socket, u'stdout')
306 sys.stderr = outstream_factory(self.session, self.iopub_socket, u'stderr')
296 sys.stderr = outstream_factory(self.session, self.iopub_socket, u'stderr')
307 if self.displayhook_class:
297 if self.displayhook_class:
308 displayhook_factory = import_item(str(self.displayhook_class))
298 displayhook_factory = import_item(str(self.displayhook_class))
309 sys.displayhook = displayhook_factory(self.session, self.iopub_socket)
299 sys.displayhook = displayhook_factory(self.session, self.iopub_socket)
310
300
311 def init_signal(self):
301 def init_signal(self):
312 signal.signal(signal.SIGINT, signal.SIG_IGN)
302 signal.signal(signal.SIGINT, signal.SIG_IGN)
313
303
314 def init_kernel(self):
304 def init_kernel(self):
315 """Create the Kernel object itself"""
305 """Create the Kernel object itself"""
316 shell_stream = ZMQStream(self.shell_socket)
306 shell_stream = ZMQStream(self.shell_socket)
317 control_stream = ZMQStream(self.control_socket)
307 control_stream = ZMQStream(self.control_socket)
318
308
319 kernel_factory = self.kernel_class
309 kernel_factory = self.kernel_class
320
310
321 kernel = kernel_factory(parent=self, session=self.session,
311 kernel = kernel_factory(parent=self, session=self.session,
322 shell_streams=[shell_stream, control_stream],
312 shell_streams=[shell_stream, control_stream],
323 iopub_socket=self.iopub_socket,
313 iopub_socket=self.iopub_socket,
324 stdin_socket=self.stdin_socket,
314 stdin_socket=self.stdin_socket,
325 log=self.log,
315 log=self.log,
326 profile_dir=self.profile_dir,
316 profile_dir=self.profile_dir,
327 user_ns=self.user_ns,
317 user_ns=self.user_ns,
328 )
318 )
329 kernel.record_ports(self.ports)
319 kernel.record_ports(self.ports)
330 self.kernel = kernel
320 self.kernel = kernel
331
321
332 def init_gui_pylab(self):
322 def init_gui_pylab(self):
333 """Enable GUI event loop integration, taking pylab into account."""
323 """Enable GUI event loop integration, taking pylab into account."""
334
324
335 # Provide a wrapper for :meth:`InteractiveShellApp.init_gui_pylab`
325 # Provide a wrapper for :meth:`InteractiveShellApp.init_gui_pylab`
336 # to ensure that any exception is printed straight to stderr.
326 # to ensure that any exception is printed straight to stderr.
337 # Normally _showtraceback associates the reply with an execution,
327 # Normally _showtraceback associates the reply with an execution,
338 # which means frontends will never draw it, as this exception
328 # which means frontends will never draw it, as this exception
339 # is not associated with any execute request.
329 # is not associated with any execute request.
340
330
341 shell = self.shell
331 shell = self.shell
342 _showtraceback = shell._showtraceback
332 _showtraceback = shell._showtraceback
343 try:
333 try:
344 # replace error-sending traceback with stderr
334 # replace error-sending traceback with stderr
345 def print_tb(etype, evalue, stb):
335 def print_tb(etype, evalue, stb):
346 print ("GUI event loop or pylab initialization failed",
336 print ("GUI event loop or pylab initialization failed",
347 file=io.stderr)
337 file=io.stderr)
348 print (shell.InteractiveTB.stb2text(stb), file=io.stderr)
338 print (shell.InteractiveTB.stb2text(stb), file=io.stderr)
349 shell._showtraceback = print_tb
339 shell._showtraceback = print_tb
350 InteractiveShellApp.init_gui_pylab(self)
340 InteractiveShellApp.init_gui_pylab(self)
351 finally:
341 finally:
352 shell._showtraceback = _showtraceback
342 shell._showtraceback = _showtraceback
353
343
354 def init_shell(self):
344 def init_shell(self):
355 self.shell = getattr(self.kernel, 'shell', None)
345 self.shell = getattr(self.kernel, 'shell', None)
356 if self.shell:
346 if self.shell:
357 self.shell.configurables.append(self)
347 self.shell.configurables.append(self)
358
348
359 @catch_config_error
349 @catch_config_error
360 def initialize(self, argv=None):
350 def initialize(self, argv=None):
361 super(IPKernelApp, self).initialize(argv)
351 super(IPKernelApp, self).initialize(argv)
362 default_secure(self.config)
352 default_secure(self.config)
363 self.init_blackhole()
353 self.init_blackhole()
364 self.init_connection_file()
354 self.init_connection_file()
365 self.init_poller()
355 self.init_poller()
366 self.init_sockets()
356 self.init_sockets()
367 self.init_heartbeat()
357 self.init_heartbeat()
368 # writing/displaying connection info must be *after* init_sockets/heartbeat
358 # writing/displaying connection info must be *after* init_sockets/heartbeat
369 self.log_connection_info()
359 self.log_connection_info()
370 self.write_connection_file()
360 self.write_connection_file()
371 self.init_io()
361 self.init_io()
372 self.init_signal()
362 self.init_signal()
373 self.init_kernel()
363 self.init_kernel()
374 # shell init steps
364 # shell init steps
375 self.init_path()
365 self.init_path()
376 self.init_shell()
366 self.init_shell()
377 if self.shell:
367 if self.shell:
378 self.init_gui_pylab()
368 self.init_gui_pylab()
379 self.init_extensions()
369 self.init_extensions()
380 self.init_code()
370 self.init_code()
381 # flush stdout/stderr, so that anything written to these streams during
371 # flush stdout/stderr, so that anything written to these streams during
382 # initialization do not get associated with the first execution request
372 # initialization do not get associated with the first execution request
383 sys.stdout.flush()
373 sys.stdout.flush()
384 sys.stderr.flush()
374 sys.stderr.flush()
385
375
386 def start(self):
376 def start(self):
387 if self.poller is not None:
377 if self.poller is not None:
388 self.poller.start()
378 self.poller.start()
389 self.kernel.start()
379 self.kernel.start()
390 try:
380 try:
391 ioloop.IOLoop.instance().start()
381 ioloop.IOLoop.instance().start()
392 except KeyboardInterrupt:
382 except KeyboardInterrupt:
393 pass
383 pass
394
384
395 launch_new_instance = IPKernelApp.launch_instance
385 launch_new_instance = IPKernelApp.launch_instance
396
386
397 def main():
387 def main():
398 """Run an IPKernel as an application"""
388 """Run an IPKernel as an application"""
399 app = IPKernelApp.instance()
389 app = IPKernelApp.instance()
400 app.initialize()
390 app.initialize()
401 app.start()
391 app.start()
402
392
403
393
404 if __name__ == '__main__':
394 if __name__ == '__main__':
405 main()
395 main()
General Comments 0
You need to be logged in to leave comments. Login now