##// END OF EJS Templates
add `ipython notebook trust` subcommand
MinRK -
Show More
@@ -1,837 +1,874 b''
1 # coding: utf-8
1 # coding: utf-8
2 """A tornado based IPython notebook server.
2 """A tornado based IPython notebook server.
3
3
4 Authors:
4 Authors:
5
5
6 * Brian Granger
6 * Brian Granger
7 """
7 """
8 from __future__ import print_function
8 from __future__ import print_function
9 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10 # Copyright (C) 2013 The IPython Development Team
10 # Copyright (C) 2013 The IPython Development Team
11 #
11 #
12 # Distributed under the terms of the BSD License. The full license is in
12 # Distributed under the terms of the BSD License. The full license is in
13 # the file COPYING, distributed as part of this software.
13 # the file COPYING, distributed as part of this software.
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15
15
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17 # Imports
17 # Imports
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19
19
20 # stdlib
20 # stdlib
21 import errno
21 import errno
22 import io
22 import io
23 import json
23 import json
24 import logging
24 import logging
25 import os
25 import os
26 import random
26 import random
27 import select
27 import select
28 import signal
28 import signal
29 import socket
29 import socket
30 import sys
30 import sys
31 import threading
31 import threading
32 import time
32 import time
33 import webbrowser
33 import webbrowser
34
34
35
35
36 # Third party
36 # Third party
37 # check for pyzmq 2.1.11
37 # check for pyzmq 2.1.11
38 from IPython.utils.zmqrelated import check_for_zmq
38 from IPython.utils.zmqrelated import check_for_zmq
39 check_for_zmq('2.1.11', 'IPython.html')
39 check_for_zmq('2.1.11', 'IPython.html')
40
40
41 from jinja2 import Environment, FileSystemLoader
41 from jinja2 import Environment, FileSystemLoader
42
42
43 # Install the pyzmq ioloop. This has to be done before anything else from
43 # Install the pyzmq ioloop. This has to be done before anything else from
44 # tornado is imported.
44 # tornado is imported.
45 from zmq.eventloop import ioloop
45 from zmq.eventloop import ioloop
46 ioloop.install()
46 ioloop.install()
47
47
48 # check for tornado 3.1.0
48 # check for tornado 3.1.0
49 msg = "The IPython Notebook requires tornado >= 3.1.0"
49 msg = "The IPython Notebook requires tornado >= 3.1.0"
50 try:
50 try:
51 import tornado
51 import tornado
52 except ImportError:
52 except ImportError:
53 raise ImportError(msg)
53 raise ImportError(msg)
54 try:
54 try:
55 version_info = tornado.version_info
55 version_info = tornado.version_info
56 except AttributeError:
56 except AttributeError:
57 raise ImportError(msg + ", but you have < 1.1.0")
57 raise ImportError(msg + ", but you have < 1.1.0")
58 if version_info < (3,1,0):
58 if version_info < (3,1,0):
59 raise ImportError(msg + ", but you have %s" % tornado.version)
59 raise ImportError(msg + ", but you have %s" % tornado.version)
60
60
61 from tornado import httpserver
61 from tornado import httpserver
62 from tornado import web
62 from tornado import web
63
63
64 # Our own libraries
64 # Our own libraries
65 from IPython.html import DEFAULT_STATIC_FILES_PATH
65 from IPython.html import DEFAULT_STATIC_FILES_PATH
66 from .base.handlers import Template404
66 from .base.handlers import Template404
67 from .log import log_request
67 from .log import log_request
68 from .services.kernels.kernelmanager import MappingKernelManager
68 from .services.kernels.kernelmanager import MappingKernelManager
69 from .services.notebooks.nbmanager import NotebookManager
69 from .services.notebooks.nbmanager import NotebookManager
70 from .services.notebooks.filenbmanager import FileNotebookManager
70 from .services.notebooks.filenbmanager import FileNotebookManager
71 from .services.clusters.clustermanager import ClusterManager
71 from .services.clusters.clustermanager import ClusterManager
72 from .services.sessions.sessionmanager import SessionManager
72 from .services.sessions.sessionmanager import SessionManager
73
73
74 from .base.handlers import AuthenticatedFileHandler, FileFindHandler
74 from .base.handlers import AuthenticatedFileHandler, FileFindHandler
75
75
76 from IPython.config.application import catch_config_error, boolean_flag
76 from IPython.config.application import catch_config_error, boolean_flag
77 from IPython.core.application import BaseIPythonApplication
77 from IPython.core.application import BaseIPythonApplication
78 from IPython.core.profiledir import ProfileDir
78 from IPython.core.profiledir import ProfileDir
79 from IPython.consoleapp import IPythonConsoleApp
79 from IPython.consoleapp import IPythonConsoleApp
80 from IPython.kernel import swallow_argv
80 from IPython.kernel import swallow_argv
81 from IPython.kernel.zmq.session import default_secure
81 from IPython.kernel.zmq.session import default_secure
82 from IPython.kernel.zmq.kernelapp import (
82 from IPython.kernel.zmq.kernelapp import (
83 kernel_flags,
83 kernel_flags,
84 kernel_aliases,
84 kernel_aliases,
85 )
85 )
86 from IPython.nbformat import current, sign
86 from IPython.utils.importstring import import_item
87 from IPython.utils.importstring import import_item
87 from IPython.utils.localinterfaces import localhost
88 from IPython.utils.localinterfaces import localhost
88 from IPython.utils import submodule
89 from IPython.utils import submodule
89 from IPython.utils.traitlets import (
90 from IPython.utils.traitlets import (
90 Dict, Unicode, Integer, List, Bool, Bytes,
91 Dict, Unicode, Integer, List, Bool, Bytes,
91 DottedObjectName
92 DottedObjectName, Instance,
92 )
93 )
93 from IPython.utils import py3compat
94 from IPython.utils import py3compat
94 from IPython.utils.path import filefind, get_ipython_dir
95 from IPython.utils.path import filefind, get_ipython_dir
95
96
96 from .utils import url_path_join
97 from .utils import url_path_join
97
98
98 #-----------------------------------------------------------------------------
99 #-----------------------------------------------------------------------------
99 # Module globals
100 # Module globals
100 #-----------------------------------------------------------------------------
101 #-----------------------------------------------------------------------------
101
102
102 _examples = """
103 _examples = """
103 ipython notebook # start the notebook
104 ipython notebook # start the notebook
104 ipython notebook --profile=sympy # use the sympy profile
105 ipython notebook --profile=sympy # use the sympy profile
105 ipython notebook --certfile=mycert.pem # use SSL/TLS certificate
106 ipython notebook --certfile=mycert.pem # use SSL/TLS certificate
106 """
107 """
107
108
108 #-----------------------------------------------------------------------------
109 #-----------------------------------------------------------------------------
109 # Helper functions
110 # Helper functions
110 #-----------------------------------------------------------------------------
111 #-----------------------------------------------------------------------------
111
112
112 def random_ports(port, n):
113 def random_ports(port, n):
113 """Generate a list of n random ports near the given port.
114 """Generate a list of n random ports near the given port.
114
115
115 The first 5 ports will be sequential, and the remaining n-5 will be
116 The first 5 ports will be sequential, and the remaining n-5 will be
116 randomly selected in the range [port-2*n, port+2*n].
117 randomly selected in the range [port-2*n, port+2*n].
117 """
118 """
118 for i in range(min(5, n)):
119 for i in range(min(5, n)):
119 yield port + i
120 yield port + i
120 for i in range(n-5):
121 for i in range(n-5):
121 yield max(1, port + random.randint(-2*n, 2*n))
122 yield max(1, port + random.randint(-2*n, 2*n))
122
123
123 def load_handlers(name):
124 def load_handlers(name):
124 """Load the (URL pattern, handler) tuples for each component."""
125 """Load the (URL pattern, handler) tuples for each component."""
125 name = 'IPython.html.' + name
126 name = 'IPython.html.' + name
126 mod = __import__(name, fromlist=['default_handlers'])
127 mod = __import__(name, fromlist=['default_handlers'])
127 return mod.default_handlers
128 return mod.default_handlers
128
129
129 #-----------------------------------------------------------------------------
130 #-----------------------------------------------------------------------------
130 # The Tornado web application
131 # The Tornado web application
131 #-----------------------------------------------------------------------------
132 #-----------------------------------------------------------------------------
132
133
133 class NotebookWebApplication(web.Application):
134 class NotebookWebApplication(web.Application):
134
135
135 def __init__(self, ipython_app, kernel_manager, notebook_manager,
136 def __init__(self, ipython_app, kernel_manager, notebook_manager,
136 cluster_manager, session_manager, log, base_project_url,
137 cluster_manager, session_manager, log, base_project_url,
137 settings_overrides):
138 settings_overrides):
138
139
139 settings = self.init_settings(
140 settings = self.init_settings(
140 ipython_app, kernel_manager, notebook_manager, cluster_manager,
141 ipython_app, kernel_manager, notebook_manager, cluster_manager,
141 session_manager, log, base_project_url, settings_overrides)
142 session_manager, log, base_project_url, settings_overrides)
142 handlers = self.init_handlers(settings)
143 handlers = self.init_handlers(settings)
143
144
144 super(NotebookWebApplication, self).__init__(handlers, **settings)
145 super(NotebookWebApplication, self).__init__(handlers, **settings)
145
146
146 def init_settings(self, ipython_app, kernel_manager, notebook_manager,
147 def init_settings(self, ipython_app, kernel_manager, notebook_manager,
147 cluster_manager, session_manager, log, base_project_url,
148 cluster_manager, session_manager, log, base_project_url,
148 settings_overrides):
149 settings_overrides):
149 # Python < 2.6.5 doesn't accept unicode keys in f(**kwargs), and
150 # Python < 2.6.5 doesn't accept unicode keys in f(**kwargs), and
150 # base_project_url will always be unicode, which will in turn
151 # base_project_url will always be unicode, which will in turn
151 # make the patterns unicode, and ultimately result in unicode
152 # make the patterns unicode, and ultimately result in unicode
152 # keys in kwargs to handler._execute(**kwargs) in tornado.
153 # keys in kwargs to handler._execute(**kwargs) in tornado.
153 # This enforces that base_project_url be ascii in that situation.
154 # This enforces that base_project_url be ascii in that situation.
154 #
155 #
155 # Note that the URLs these patterns check against are escaped,
156 # Note that the URLs these patterns check against are escaped,
156 # and thus guaranteed to be ASCII: 'hΓ©llo' is really 'h%C3%A9llo'.
157 # and thus guaranteed to be ASCII: 'hΓ©llo' is really 'h%C3%A9llo'.
157 base_project_url = py3compat.unicode_to_str(base_project_url, 'ascii')
158 base_project_url = py3compat.unicode_to_str(base_project_url, 'ascii')
158 template_path = settings_overrides.get("template_path", os.path.join(os.path.dirname(__file__), "templates"))
159 template_path = settings_overrides.get("template_path", os.path.join(os.path.dirname(__file__), "templates"))
159 settings = dict(
160 settings = dict(
160 # basics
161 # basics
161 log_function=log_request,
162 log_function=log_request,
162 base_project_url=base_project_url,
163 base_project_url=base_project_url,
163 base_kernel_url=ipython_app.base_kernel_url,
164 base_kernel_url=ipython_app.base_kernel_url,
164 template_path=template_path,
165 template_path=template_path,
165 static_path=ipython_app.static_file_path,
166 static_path=ipython_app.static_file_path,
166 static_handler_class = FileFindHandler,
167 static_handler_class = FileFindHandler,
167 static_url_prefix = url_path_join(base_project_url,'/static/'),
168 static_url_prefix = url_path_join(base_project_url,'/static/'),
168
169
169 # authentication
170 # authentication
170 cookie_secret=ipython_app.cookie_secret,
171 cookie_secret=ipython_app.cookie_secret,
171 login_url=url_path_join(base_project_url,'/login'),
172 login_url=url_path_join(base_project_url,'/login'),
172 password=ipython_app.password,
173 password=ipython_app.password,
173
174
174 # managers
175 # managers
175 kernel_manager=kernel_manager,
176 kernel_manager=kernel_manager,
176 notebook_manager=notebook_manager,
177 notebook_manager=notebook_manager,
177 cluster_manager=cluster_manager,
178 cluster_manager=cluster_manager,
178 session_manager=session_manager,
179 session_manager=session_manager,
179
180
180 # IPython stuff
181 # IPython stuff
181 nbextensions_path = ipython_app.nbextensions_path,
182 nbextensions_path = ipython_app.nbextensions_path,
182 mathjax_url=ipython_app.mathjax_url,
183 mathjax_url=ipython_app.mathjax_url,
183 config=ipython_app.config,
184 config=ipython_app.config,
184 jinja2_env=Environment(loader=FileSystemLoader(template_path)),
185 jinja2_env=Environment(loader=FileSystemLoader(template_path)),
185 )
186 )
186
187
187 # allow custom overrides for the tornado web app.
188 # allow custom overrides for the tornado web app.
188 settings.update(settings_overrides)
189 settings.update(settings_overrides)
189 return settings
190 return settings
190
191
191 def init_handlers(self, settings):
192 def init_handlers(self, settings):
192 # Load the (URL pattern, handler) tuples for each component.
193 # Load the (URL pattern, handler) tuples for each component.
193 handlers = []
194 handlers = []
194 handlers.extend(load_handlers('base.handlers'))
195 handlers.extend(load_handlers('base.handlers'))
195 handlers.extend(load_handlers('tree.handlers'))
196 handlers.extend(load_handlers('tree.handlers'))
196 handlers.extend(load_handlers('auth.login'))
197 handlers.extend(load_handlers('auth.login'))
197 handlers.extend(load_handlers('auth.logout'))
198 handlers.extend(load_handlers('auth.logout'))
198 handlers.extend(load_handlers('notebook.handlers'))
199 handlers.extend(load_handlers('notebook.handlers'))
199 handlers.extend(load_handlers('nbconvert.handlers'))
200 handlers.extend(load_handlers('nbconvert.handlers'))
200 handlers.extend(load_handlers('services.kernels.handlers'))
201 handlers.extend(load_handlers('services.kernels.handlers'))
201 handlers.extend(load_handlers('services.notebooks.handlers'))
202 handlers.extend(load_handlers('services.notebooks.handlers'))
202 handlers.extend(load_handlers('services.clusters.handlers'))
203 handlers.extend(load_handlers('services.clusters.handlers'))
203 handlers.extend(load_handlers('services.sessions.handlers'))
204 handlers.extend(load_handlers('services.sessions.handlers'))
204 handlers.extend(load_handlers('services.nbconvert.handlers'))
205 handlers.extend(load_handlers('services.nbconvert.handlers'))
205 handlers.extend([
206 handlers.extend([
206 (r"/files/(.*)", AuthenticatedFileHandler, {'path' : settings['notebook_manager'].notebook_dir}),
207 (r"/files/(.*)", AuthenticatedFileHandler, {'path' : settings['notebook_manager'].notebook_dir}),
207 (r"/nbextensions/(.*)", FileFindHandler, {'path' : settings['nbextensions_path']}),
208 (r"/nbextensions/(.*)", FileFindHandler, {'path' : settings['nbextensions_path']}),
208 ])
209 ])
209 # prepend base_project_url onto the patterns that we match
210 # prepend base_project_url onto the patterns that we match
210 new_handlers = []
211 new_handlers = []
211 for handler in handlers:
212 for handler in handlers:
212 pattern = url_path_join(settings['base_project_url'], handler[0])
213 pattern = url_path_join(settings['base_project_url'], handler[0])
213 new_handler = tuple([pattern] + list(handler[1:]))
214 new_handler = tuple([pattern] + list(handler[1:]))
214 new_handlers.append(new_handler)
215 new_handlers.append(new_handler)
215 # add 404 on the end, which will catch everything that falls through
216 # add 404 on the end, which will catch everything that falls through
216 new_handlers.append((r'(.*)', Template404))
217 new_handlers.append((r'(.*)', Template404))
217 return new_handlers
218 return new_handlers
218
219
219
220
221 #-----------------------------------------------------------------------------
222 # Subcommands
223 #-----------------------------------------------------------------------------
224
220 class NbserverListApp(BaseIPythonApplication):
225 class NbserverListApp(BaseIPythonApplication):
221
226
222 description="List currently running notebook servers in this profile."
227 description="List currently running notebook servers in this profile."
223
228
224 flags = dict(
229 flags = dict(
225 json=({'NbserverListApp': {'json': True}},
230 json=({'NbserverListApp': {'json': True}},
226 "Produce machine-readable JSON output."),
231 "Produce machine-readable JSON output."),
227 )
232 )
228
233
229 json = Bool(False, config=True,
234 json = Bool(False, config=True,
230 help="If True, each line of output will be a JSON object with the "
235 help="If True, each line of output will be a JSON object with the "
231 "details from the server info file.")
236 "details from the server info file.")
232
237
233 def start(self):
238 def start(self):
234 if not self.json:
239 if not self.json:
235 print("Currently running servers:")
240 print("Currently running servers:")
236 for serverinfo in list_running_servers(self.profile):
241 for serverinfo in list_running_servers(self.profile):
237 if self.json:
242 if self.json:
238 print(json.dumps(serverinfo))
243 print(json.dumps(serverinfo))
239 else:
244 else:
240 print(serverinfo['url'], "::", serverinfo['notebook_dir'])
245 print(serverinfo['url'], "::", serverinfo['notebook_dir'])
241
246
247
248 class NotebookTrustApp(BaseIPythonApplication):
249
250 description="""Sign one or more IPython notebooks with your key,
251 to trust their dynamic (HTML, Javascript) output."""
252
253 examples="""ipython notebook trust mynotebook.ipynb"""
254
255 notary = Instance(sign.NotebookNotary)
256 def _notary_default(self):
257 return sign.NotebookNotary(parent=self, profile_dir=self.profile_dir)
258
259 def sign_notebook(self, notebook_path):
260 if not os.path.exists(notebook_path):
261 self.log.error("Notebook missing: %s" % notebook_path)
262 self.exit(1)
263 with io.open(notebook_path, encoding='utf8') as f:
264 nb = current.read(f, 'json')
265 sign.trust_notebook(nb, self.notary.secret, self.notary.signature_scheme)
266 with io.open(notebook_path, 'w', encoding='utf8') as f:
267 current.write(nb, f, 'json')
268
269 def start(self):
270 if not self.extra_args:
271 self.log.critical("Specify at least one notebook to sign.")
272 self.exit(1)
273
274 for notebook_path in self.extra_args:
275 print("Signing notebook: %s" % notebook_path)
276 self.sign_notebook(notebook_path)
277
242 #-----------------------------------------------------------------------------
278 #-----------------------------------------------------------------------------
243 # Aliases and Flags
279 # Aliases and Flags
244 #-----------------------------------------------------------------------------
280 #-----------------------------------------------------------------------------
245
281
246 flags = dict(kernel_flags)
282 flags = dict(kernel_flags)
247 flags['no-browser']=(
283 flags['no-browser']=(
248 {'NotebookApp' : {'open_browser' : False}},
284 {'NotebookApp' : {'open_browser' : False}},
249 "Don't open the notebook in a browser after startup."
285 "Don't open the notebook in a browser after startup."
250 )
286 )
251 flags['no-mathjax']=(
287 flags['no-mathjax']=(
252 {'NotebookApp' : {'enable_mathjax' : False}},
288 {'NotebookApp' : {'enable_mathjax' : False}},
253 """Disable MathJax
289 """Disable MathJax
254
290
255 MathJax is the javascript library IPython uses to render math/LaTeX. It is
291 MathJax is the javascript library IPython uses to render math/LaTeX. It is
256 very large, so you may want to disable it if you have a slow internet
292 very large, so you may want to disable it if you have a slow internet
257 connection, or for offline use of the notebook.
293 connection, or for offline use of the notebook.
258
294
259 When disabled, equations etc. will appear as their untransformed TeX source.
295 When disabled, equations etc. will appear as their untransformed TeX source.
260 """
296 """
261 )
297 )
262
298
263 # Add notebook manager flags
299 # Add notebook manager flags
264 flags.update(boolean_flag('script', 'FileNotebookManager.save_script',
300 flags.update(boolean_flag('script', 'FileNotebookManager.save_script',
265 'Auto-save a .py script everytime the .ipynb notebook is saved',
301 'Auto-save a .py script everytime the .ipynb notebook is saved',
266 'Do not auto-save .py scripts for every notebook'))
302 'Do not auto-save .py scripts for every notebook'))
267
303
268 # the flags that are specific to the frontend
304 # the flags that are specific to the frontend
269 # these must be scrubbed before being passed to the kernel,
305 # these must be scrubbed before being passed to the kernel,
270 # or it will raise an error on unrecognized flags
306 # or it will raise an error on unrecognized flags
271 notebook_flags = ['no-browser', 'no-mathjax', 'script', 'no-script']
307 notebook_flags = ['no-browser', 'no-mathjax', 'script', 'no-script']
272
308
273 aliases = dict(kernel_aliases)
309 aliases = dict(kernel_aliases)
274
310
275 aliases.update({
311 aliases.update({
276 'ip': 'NotebookApp.ip',
312 'ip': 'NotebookApp.ip',
277 'port': 'NotebookApp.port',
313 'port': 'NotebookApp.port',
278 'port-retries': 'NotebookApp.port_retries',
314 'port-retries': 'NotebookApp.port_retries',
279 'transport': 'KernelManager.transport',
315 'transport': 'KernelManager.transport',
280 'keyfile': 'NotebookApp.keyfile',
316 'keyfile': 'NotebookApp.keyfile',
281 'certfile': 'NotebookApp.certfile',
317 'certfile': 'NotebookApp.certfile',
282 'notebook-dir': 'NotebookManager.notebook_dir',
318 'notebook-dir': 'NotebookManager.notebook_dir',
283 'browser': 'NotebookApp.browser',
319 'browser': 'NotebookApp.browser',
284 })
320 })
285
321
286 # remove ipkernel flags that are singletons, and don't make sense in
322 # remove ipkernel flags that are singletons, and don't make sense in
287 # multi-kernel evironment:
323 # multi-kernel evironment:
288 aliases.pop('f', None)
324 aliases.pop('f', None)
289
325
290 notebook_aliases = [u'port', u'port-retries', u'ip', u'keyfile', u'certfile',
326 notebook_aliases = [u'port', u'port-retries', u'ip', u'keyfile', u'certfile',
291 u'notebook-dir', u'profile', u'profile-dir']
327 u'notebook-dir', u'profile', u'profile-dir']
292
328
293 #-----------------------------------------------------------------------------
329 #-----------------------------------------------------------------------------
294 # NotebookApp
330 # NotebookApp
295 #-----------------------------------------------------------------------------
331 #-----------------------------------------------------------------------------
296
332
297 class NotebookApp(BaseIPythonApplication):
333 class NotebookApp(BaseIPythonApplication):
298
334
299 name = 'ipython-notebook'
335 name = 'ipython-notebook'
300
336
301 description = """
337 description = """
302 The IPython HTML Notebook.
338 The IPython HTML Notebook.
303
339
304 This launches a Tornado based HTML Notebook Server that serves up an
340 This launches a Tornado based HTML Notebook Server that serves up an
305 HTML5/Javascript Notebook client.
341 HTML5/Javascript Notebook client.
306 """
342 """
307 examples = _examples
343 examples = _examples
308
344
309 classes = IPythonConsoleApp.classes + [MappingKernelManager, NotebookManager,
345 classes = IPythonConsoleApp.classes + [MappingKernelManager, NotebookManager,
310 FileNotebookManager]
346 FileNotebookManager]
311 flags = Dict(flags)
347 flags = Dict(flags)
312 aliases = Dict(aliases)
348 aliases = Dict(aliases)
313
349
314 subcommands = dict(
350 subcommands = dict(
315 list=(NbserverListApp, NbserverListApp.description.splitlines()[0]),
351 list=(NbserverListApp, NbserverListApp.description.splitlines()[0]),
352 trust=(NotebookTrustApp, NotebookTrustApp.description),
316 )
353 )
317
354
318 kernel_argv = List(Unicode)
355 kernel_argv = List(Unicode)
319
356
320 def _log_level_default(self):
357 def _log_level_default(self):
321 return logging.INFO
358 return logging.INFO
322
359
323 def _log_format_default(self):
360 def _log_format_default(self):
324 """override default log format to include time"""
361 """override default log format to include time"""
325 return u"%(asctime)s.%(msecs).03d [%(name)s]%(highlevel)s %(message)s"
362 return u"%(asctime)s.%(msecs).03d [%(name)s]%(highlevel)s %(message)s"
326
363
327 # create requested profiles by default, if they don't exist:
364 # create requested profiles by default, if they don't exist:
328 auto_create = Bool(True)
365 auto_create = Bool(True)
329
366
330 # file to be opened in the notebook server
367 # file to be opened in the notebook server
331 file_to_run = Unicode('')
368 file_to_run = Unicode('')
332
369
333 # Network related information.
370 # Network related information.
334
371
335 ip = Unicode(config=True,
372 ip = Unicode(config=True,
336 help="The IP address the notebook server will listen on."
373 help="The IP address the notebook server will listen on."
337 )
374 )
338 def _ip_default(self):
375 def _ip_default(self):
339 return localhost()
376 return localhost()
340
377
341 def _ip_changed(self, name, old, new):
378 def _ip_changed(self, name, old, new):
342 if new == u'*': self.ip = u''
379 if new == u'*': self.ip = u''
343
380
344 port = Integer(8888, config=True,
381 port = Integer(8888, config=True,
345 help="The port the notebook server will listen on."
382 help="The port the notebook server will listen on."
346 )
383 )
347 port_retries = Integer(50, config=True,
384 port_retries = Integer(50, config=True,
348 help="The number of additional ports to try if the specified port is not available."
385 help="The number of additional ports to try if the specified port is not available."
349 )
386 )
350
387
351 certfile = Unicode(u'', config=True,
388 certfile = Unicode(u'', config=True,
352 help="""The full path to an SSL/TLS certificate file."""
389 help="""The full path to an SSL/TLS certificate file."""
353 )
390 )
354
391
355 keyfile = Unicode(u'', config=True,
392 keyfile = Unicode(u'', config=True,
356 help="""The full path to a private key file for usage with SSL/TLS."""
393 help="""The full path to a private key file for usage with SSL/TLS."""
357 )
394 )
358
395
359 cookie_secret = Bytes(b'', config=True,
396 cookie_secret = Bytes(b'', config=True,
360 help="""The random bytes used to secure cookies.
397 help="""The random bytes used to secure cookies.
361 By default this is a new random number every time you start the Notebook.
398 By default this is a new random number every time you start the Notebook.
362 Set it to a value in a config file to enable logins to persist across server sessions.
399 Set it to a value in a config file to enable logins to persist across server sessions.
363
400
364 Note: Cookie secrets should be kept private, do not share config files with
401 Note: Cookie secrets should be kept private, do not share config files with
365 cookie_secret stored in plaintext (you can read the value from a file).
402 cookie_secret stored in plaintext (you can read the value from a file).
366 """
403 """
367 )
404 )
368 def _cookie_secret_default(self):
405 def _cookie_secret_default(self):
369 return os.urandom(1024)
406 return os.urandom(1024)
370
407
371 password = Unicode(u'', config=True,
408 password = Unicode(u'', config=True,
372 help="""Hashed password to use for web authentication.
409 help="""Hashed password to use for web authentication.
373
410
374 To generate, type in a python/IPython shell:
411 To generate, type in a python/IPython shell:
375
412
376 from IPython.lib import passwd; passwd()
413 from IPython.lib import passwd; passwd()
377
414
378 The string should be of the form type:salt:hashed-password.
415 The string should be of the form type:salt:hashed-password.
379 """
416 """
380 )
417 )
381
418
382 open_browser = Bool(True, config=True,
419 open_browser = Bool(True, config=True,
383 help="""Whether to open in a browser after starting.
420 help="""Whether to open in a browser after starting.
384 The specific browser used is platform dependent and
421 The specific browser used is platform dependent and
385 determined by the python standard library `webbrowser`
422 determined by the python standard library `webbrowser`
386 module, unless it is overridden using the --browser
423 module, unless it is overridden using the --browser
387 (NotebookApp.browser) configuration option.
424 (NotebookApp.browser) configuration option.
388 """)
425 """)
389
426
390 browser = Unicode(u'', config=True,
427 browser = Unicode(u'', config=True,
391 help="""Specify what command to use to invoke a web
428 help="""Specify what command to use to invoke a web
392 browser when opening the notebook. If not specified, the
429 browser when opening the notebook. If not specified, the
393 default browser will be determined by the `webbrowser`
430 default browser will be determined by the `webbrowser`
394 standard library module, which allows setting of the
431 standard library module, which allows setting of the
395 BROWSER environment variable to override it.
432 BROWSER environment variable to override it.
396 """)
433 """)
397
434
398 webapp_settings = Dict(config=True,
435 webapp_settings = Dict(config=True,
399 help="Supply overrides for the tornado.web.Application that the "
436 help="Supply overrides for the tornado.web.Application that the "
400 "IPython notebook uses.")
437 "IPython notebook uses.")
401
438
402 enable_mathjax = Bool(True, config=True,
439 enable_mathjax = Bool(True, config=True,
403 help="""Whether to enable MathJax for typesetting math/TeX
440 help="""Whether to enable MathJax for typesetting math/TeX
404
441
405 MathJax is the javascript library IPython uses to render math/LaTeX. It is
442 MathJax is the javascript library IPython uses to render math/LaTeX. It is
406 very large, so you may want to disable it if you have a slow internet
443 very large, so you may want to disable it if you have a slow internet
407 connection, or for offline use of the notebook.
444 connection, or for offline use of the notebook.
408
445
409 When disabled, equations etc. will appear as their untransformed TeX source.
446 When disabled, equations etc. will appear as their untransformed TeX source.
410 """
447 """
411 )
448 )
412 def _enable_mathjax_changed(self, name, old, new):
449 def _enable_mathjax_changed(self, name, old, new):
413 """set mathjax url to empty if mathjax is disabled"""
450 """set mathjax url to empty if mathjax is disabled"""
414 if not new:
451 if not new:
415 self.mathjax_url = u''
452 self.mathjax_url = u''
416
453
417 base_project_url = Unicode('/', config=True,
454 base_project_url = Unicode('/', config=True,
418 help='''The base URL for the notebook server.
455 help='''The base URL for the notebook server.
419
456
420 Leading and trailing slashes can be omitted,
457 Leading and trailing slashes can be omitted,
421 and will automatically be added.
458 and will automatically be added.
422 ''')
459 ''')
423 def _base_project_url_changed(self, name, old, new):
460 def _base_project_url_changed(self, name, old, new):
424 if not new.startswith('/'):
461 if not new.startswith('/'):
425 self.base_project_url = '/'+new
462 self.base_project_url = '/'+new
426 elif not new.endswith('/'):
463 elif not new.endswith('/'):
427 self.base_project_url = new+'/'
464 self.base_project_url = new+'/'
428
465
429 base_kernel_url = Unicode('/', config=True,
466 base_kernel_url = Unicode('/', config=True,
430 help='''The base URL for the kernel server
467 help='''The base URL for the kernel server
431
468
432 Leading and trailing slashes can be omitted,
469 Leading and trailing slashes can be omitted,
433 and will automatically be added.
470 and will automatically be added.
434 ''')
471 ''')
435 def _base_kernel_url_changed(self, name, old, new):
472 def _base_kernel_url_changed(self, name, old, new):
436 if not new.startswith('/'):
473 if not new.startswith('/'):
437 self.base_kernel_url = '/'+new
474 self.base_kernel_url = '/'+new
438 elif not new.endswith('/'):
475 elif not new.endswith('/'):
439 self.base_kernel_url = new+'/'
476 self.base_kernel_url = new+'/'
440
477
441 websocket_url = Unicode("", config=True,
478 websocket_url = Unicode("", config=True,
442 help="""The base URL for the websocket server,
479 help="""The base URL for the websocket server,
443 if it differs from the HTTP server (hint: it almost certainly doesn't).
480 if it differs from the HTTP server (hint: it almost certainly doesn't).
444
481
445 Should be in the form of an HTTP origin: ws[s]://hostname[:port]
482 Should be in the form of an HTTP origin: ws[s]://hostname[:port]
446 """
483 """
447 )
484 )
448
485
449 extra_static_paths = List(Unicode, config=True,
486 extra_static_paths = List(Unicode, config=True,
450 help="""Extra paths to search for serving static files.
487 help="""Extra paths to search for serving static files.
451
488
452 This allows adding javascript/css to be available from the notebook server machine,
489 This allows adding javascript/css to be available from the notebook server machine,
453 or overriding individual files in the IPython"""
490 or overriding individual files in the IPython"""
454 )
491 )
455 def _extra_static_paths_default(self):
492 def _extra_static_paths_default(self):
456 return [os.path.join(self.profile_dir.location, 'static')]
493 return [os.path.join(self.profile_dir.location, 'static')]
457
494
458 @property
495 @property
459 def static_file_path(self):
496 def static_file_path(self):
460 """return extra paths + the default location"""
497 """return extra paths + the default location"""
461 return self.extra_static_paths + [DEFAULT_STATIC_FILES_PATH]
498 return self.extra_static_paths + [DEFAULT_STATIC_FILES_PATH]
462
499
463 nbextensions_path = List(Unicode, config=True,
500 nbextensions_path = List(Unicode, config=True,
464 help="""paths for Javascript extensions. By default, this is just IPYTHONDIR/nbextensions"""
501 help="""paths for Javascript extensions. By default, this is just IPYTHONDIR/nbextensions"""
465 )
502 )
466 def _nbextensions_path_default(self):
503 def _nbextensions_path_default(self):
467 return [os.path.join(get_ipython_dir(), 'nbextensions')]
504 return [os.path.join(get_ipython_dir(), 'nbextensions')]
468
505
469 mathjax_url = Unicode("", config=True,
506 mathjax_url = Unicode("", config=True,
470 help="""The url for MathJax.js."""
507 help="""The url for MathJax.js."""
471 )
508 )
472 def _mathjax_url_default(self):
509 def _mathjax_url_default(self):
473 if not self.enable_mathjax:
510 if not self.enable_mathjax:
474 return u''
511 return u''
475 static_url_prefix = self.webapp_settings.get("static_url_prefix",
512 static_url_prefix = self.webapp_settings.get("static_url_prefix",
476 url_path_join(self.base_project_url, "static")
513 url_path_join(self.base_project_url, "static")
477 )
514 )
478
515
479 # try local mathjax, either in nbextensions/mathjax or static/mathjax
516 # try local mathjax, either in nbextensions/mathjax or static/mathjax
480 for (url_prefix, search_path) in [
517 for (url_prefix, search_path) in [
481 (url_path_join(self.base_project_url, "nbextensions"), self.nbextensions_path),
518 (url_path_join(self.base_project_url, "nbextensions"), self.nbextensions_path),
482 (static_url_prefix, self.static_file_path),
519 (static_url_prefix, self.static_file_path),
483 ]:
520 ]:
484 self.log.debug("searching for local mathjax in %s", search_path)
521 self.log.debug("searching for local mathjax in %s", search_path)
485 try:
522 try:
486 mathjax = filefind(os.path.join('mathjax', 'MathJax.js'), search_path)
523 mathjax = filefind(os.path.join('mathjax', 'MathJax.js'), search_path)
487 except IOError:
524 except IOError:
488 continue
525 continue
489 else:
526 else:
490 url = url_path_join(url_prefix, u"mathjax/MathJax.js")
527 url = url_path_join(url_prefix, u"mathjax/MathJax.js")
491 self.log.info("Serving local MathJax from %s at %s", mathjax, url)
528 self.log.info("Serving local MathJax from %s at %s", mathjax, url)
492 return url
529 return url
493
530
494 # no local mathjax, serve from CDN
531 # no local mathjax, serve from CDN
495 if self.certfile:
532 if self.certfile:
496 # HTTPS: load from Rackspace CDN, because SSL certificate requires it
533 # HTTPS: load from Rackspace CDN, because SSL certificate requires it
497 host = u"https://c328740.ssl.cf1.rackcdn.com"
534 host = u"https://c328740.ssl.cf1.rackcdn.com"
498 else:
535 else:
499 host = u"http://cdn.mathjax.org"
536 host = u"http://cdn.mathjax.org"
500
537
501 url = host + u"/mathjax/latest/MathJax.js"
538 url = host + u"/mathjax/latest/MathJax.js"
502 self.log.info("Using MathJax from CDN: %s", url)
539 self.log.info("Using MathJax from CDN: %s", url)
503 return url
540 return url
504
541
505 def _mathjax_url_changed(self, name, old, new):
542 def _mathjax_url_changed(self, name, old, new):
506 if new and not self.enable_mathjax:
543 if new and not self.enable_mathjax:
507 # enable_mathjax=False overrides mathjax_url
544 # enable_mathjax=False overrides mathjax_url
508 self.mathjax_url = u''
545 self.mathjax_url = u''
509 else:
546 else:
510 self.log.info("Using MathJax: %s", new)
547 self.log.info("Using MathJax: %s", new)
511
548
512 notebook_manager_class = DottedObjectName('IPython.html.services.notebooks.filenbmanager.FileNotebookManager',
549 notebook_manager_class = DottedObjectName('IPython.html.services.notebooks.filenbmanager.FileNotebookManager',
513 config=True,
550 config=True,
514 help='The notebook manager class to use.')
551 help='The notebook manager class to use.')
515
552
516 trust_xheaders = Bool(False, config=True,
553 trust_xheaders = Bool(False, config=True,
517 help=("Whether to trust or not X-Scheme/X-Forwarded-Proto and X-Real-Ip/X-Forwarded-For headers"
554 help=("Whether to trust or not X-Scheme/X-Forwarded-Proto and X-Real-Ip/X-Forwarded-For headers"
518 "sent by the upstream reverse proxy. Necessary if the proxy handles SSL")
555 "sent by the upstream reverse proxy. Necessary if the proxy handles SSL")
519 )
556 )
520
557
521 info_file = Unicode()
558 info_file = Unicode()
522
559
523 def _info_file_default(self):
560 def _info_file_default(self):
524 info_file = "nbserver-%s.json"%os.getpid()
561 info_file = "nbserver-%s.json"%os.getpid()
525 return os.path.join(self.profile_dir.security_dir, info_file)
562 return os.path.join(self.profile_dir.security_dir, info_file)
526
563
527 def parse_command_line(self, argv=None):
564 def parse_command_line(self, argv=None):
528 super(NotebookApp, self).parse_command_line(argv)
565 super(NotebookApp, self).parse_command_line(argv)
529
566
530 if self.extra_args:
567 if self.extra_args:
531 arg0 = self.extra_args[0]
568 arg0 = self.extra_args[0]
532 f = os.path.abspath(arg0)
569 f = os.path.abspath(arg0)
533 self.argv.remove(arg0)
570 self.argv.remove(arg0)
534 if not os.path.exists(f):
571 if not os.path.exists(f):
535 self.log.critical("No such file or directory: %s", f)
572 self.log.critical("No such file or directory: %s", f)
536 self.exit(1)
573 self.exit(1)
537 if os.path.isdir(f):
574 if os.path.isdir(f):
538 self.config.FileNotebookManager.notebook_dir = f
575 self.config.FileNotebookManager.notebook_dir = f
539 elif os.path.isfile(f):
576 elif os.path.isfile(f):
540 self.file_to_run = f
577 self.file_to_run = f
541
578
542 def init_kernel_argv(self):
579 def init_kernel_argv(self):
543 """construct the kernel arguments"""
580 """construct the kernel arguments"""
544 # Scrub frontend-specific flags
581 # Scrub frontend-specific flags
545 self.kernel_argv = swallow_argv(self.argv, notebook_aliases, notebook_flags)
582 self.kernel_argv = swallow_argv(self.argv, notebook_aliases, notebook_flags)
546 if any(arg.startswith(u'--pylab') for arg in self.kernel_argv):
583 if any(arg.startswith(u'--pylab') for arg in self.kernel_argv):
547 self.log.warn('\n '.join([
584 self.log.warn('\n '.join([
548 "Starting all kernels in pylab mode is not recommended,",
585 "Starting all kernels in pylab mode is not recommended,",
549 "and will be disabled in a future release.",
586 "and will be disabled in a future release.",
550 "Please use the %matplotlib magic to enable matplotlib instead.",
587 "Please use the %matplotlib magic to enable matplotlib instead.",
551 "pylab implies many imports, which can have confusing side effects",
588 "pylab implies many imports, which can have confusing side effects",
552 "and harm the reproducibility of your notebooks.",
589 "and harm the reproducibility of your notebooks.",
553 ]))
590 ]))
554 # Kernel should inherit default config file from frontend
591 # Kernel should inherit default config file from frontend
555 self.kernel_argv.append("--IPKernelApp.parent_appname='%s'" % self.name)
592 self.kernel_argv.append("--IPKernelApp.parent_appname='%s'" % self.name)
556 # Kernel should get *absolute* path to profile directory
593 # Kernel should get *absolute* path to profile directory
557 self.kernel_argv.extend(["--profile-dir", self.profile_dir.location])
594 self.kernel_argv.extend(["--profile-dir", self.profile_dir.location])
558
595
559 def init_configurables(self):
596 def init_configurables(self):
560 # force Session default to be secure
597 # force Session default to be secure
561 default_secure(self.config)
598 default_secure(self.config)
562 self.kernel_manager = MappingKernelManager(
599 self.kernel_manager = MappingKernelManager(
563 parent=self, log=self.log, kernel_argv=self.kernel_argv,
600 parent=self, log=self.log, kernel_argv=self.kernel_argv,
564 connection_dir = self.profile_dir.security_dir,
601 connection_dir = self.profile_dir.security_dir,
565 )
602 )
566 kls = import_item(self.notebook_manager_class)
603 kls = import_item(self.notebook_manager_class)
567 self.notebook_manager = kls(parent=self, log=self.log)
604 self.notebook_manager = kls(parent=self, log=self.log)
568 self.session_manager = SessionManager(parent=self, log=self.log)
605 self.session_manager = SessionManager(parent=self, log=self.log)
569 self.cluster_manager = ClusterManager(parent=self, log=self.log)
606 self.cluster_manager = ClusterManager(parent=self, log=self.log)
570 self.cluster_manager.update_profiles()
607 self.cluster_manager.update_profiles()
571
608
572 def init_logging(self):
609 def init_logging(self):
573 # This prevents double log messages because tornado use a root logger that
610 # This prevents double log messages because tornado use a root logger that
574 # self.log is a child of. The logging module dipatches log messages to a log
611 # self.log is a child of. The logging module dipatches log messages to a log
575 # and all of its ancenstors until propagate is set to False.
612 # and all of its ancenstors until propagate is set to False.
576 self.log.propagate = False
613 self.log.propagate = False
577
614
578 # hook up tornado 3's loggers to our app handlers
615 # hook up tornado 3's loggers to our app handlers
579 for name in ('access', 'application', 'general'):
616 for name in ('access', 'application', 'general'):
580 logger = logging.getLogger('tornado.%s' % name)
617 logger = logging.getLogger('tornado.%s' % name)
581 logger.parent = self.log
618 logger.parent = self.log
582 logger.setLevel(self.log.level)
619 logger.setLevel(self.log.level)
583
620
584 def init_webapp(self):
621 def init_webapp(self):
585 """initialize tornado webapp and httpserver"""
622 """initialize tornado webapp and httpserver"""
586 self.web_app = NotebookWebApplication(
623 self.web_app = NotebookWebApplication(
587 self, self.kernel_manager, self.notebook_manager,
624 self, self.kernel_manager, self.notebook_manager,
588 self.cluster_manager, self.session_manager,
625 self.cluster_manager, self.session_manager,
589 self.log, self.base_project_url, self.webapp_settings
626 self.log, self.base_project_url, self.webapp_settings
590 )
627 )
591 if self.certfile:
628 if self.certfile:
592 ssl_options = dict(certfile=self.certfile)
629 ssl_options = dict(certfile=self.certfile)
593 if self.keyfile:
630 if self.keyfile:
594 ssl_options['keyfile'] = self.keyfile
631 ssl_options['keyfile'] = self.keyfile
595 else:
632 else:
596 ssl_options = None
633 ssl_options = None
597 self.web_app.password = self.password
634 self.web_app.password = self.password
598 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options,
635 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options,
599 xheaders=self.trust_xheaders)
636 xheaders=self.trust_xheaders)
600 if not self.ip:
637 if not self.ip:
601 warning = "WARNING: The notebook server is listening on all IP addresses"
638 warning = "WARNING: The notebook server is listening on all IP addresses"
602 if ssl_options is None:
639 if ssl_options is None:
603 self.log.critical(warning + " and not using encryption. This "
640 self.log.critical(warning + " and not using encryption. This "
604 "is not recommended.")
641 "is not recommended.")
605 if not self.password:
642 if not self.password:
606 self.log.critical(warning + " and not using authentication. "
643 self.log.critical(warning + " and not using authentication. "
607 "This is highly insecure and not recommended.")
644 "This is highly insecure and not recommended.")
608 success = None
645 success = None
609 for port in random_ports(self.port, self.port_retries+1):
646 for port in random_ports(self.port, self.port_retries+1):
610 try:
647 try:
611 self.http_server.listen(port, self.ip)
648 self.http_server.listen(port, self.ip)
612 except socket.error as e:
649 except socket.error as e:
613 if e.errno == errno.EADDRINUSE:
650 if e.errno == errno.EADDRINUSE:
614 self.log.info('The port %i is already in use, trying another random port.' % port)
651 self.log.info('The port %i is already in use, trying another random port.' % port)
615 continue
652 continue
616 elif e.errno in (errno.EACCES, getattr(errno, 'WSAEACCES', errno.EACCES)):
653 elif e.errno in (errno.EACCES, getattr(errno, 'WSAEACCES', errno.EACCES)):
617 self.log.warn("Permission to listen on port %i denied" % port)
654 self.log.warn("Permission to listen on port %i denied" % port)
618 continue
655 continue
619 else:
656 else:
620 raise
657 raise
621 else:
658 else:
622 self.port = port
659 self.port = port
623 success = True
660 success = True
624 break
661 break
625 if not success:
662 if not success:
626 self.log.critical('ERROR: the notebook server could not be started because '
663 self.log.critical('ERROR: the notebook server could not be started because '
627 'no available port could be found.')
664 'no available port could be found.')
628 self.exit(1)
665 self.exit(1)
629
666
630 @property
667 @property
631 def display_url(self):
668 def display_url(self):
632 ip = self.ip if self.ip else '[all ip addresses on your system]'
669 ip = self.ip if self.ip else '[all ip addresses on your system]'
633 return self._url(ip)
670 return self._url(ip)
634
671
635 @property
672 @property
636 def connection_url(self):
673 def connection_url(self):
637 ip = self.ip if self.ip else localhost()
674 ip = self.ip if self.ip else localhost()
638 return self._url(ip)
675 return self._url(ip)
639
676
640 def _url(self, ip):
677 def _url(self, ip):
641 proto = 'https' if self.certfile else 'http'
678 proto = 'https' if self.certfile else 'http'
642 return "%s://%s:%i%s" % (proto, ip, self.port, self.base_project_url)
679 return "%s://%s:%i%s" % (proto, ip, self.port, self.base_project_url)
643
680
644 def init_signal(self):
681 def init_signal(self):
645 if not sys.platform.startswith('win'):
682 if not sys.platform.startswith('win'):
646 signal.signal(signal.SIGINT, self._handle_sigint)
683 signal.signal(signal.SIGINT, self._handle_sigint)
647 signal.signal(signal.SIGTERM, self._signal_stop)
684 signal.signal(signal.SIGTERM, self._signal_stop)
648 if hasattr(signal, 'SIGUSR1'):
685 if hasattr(signal, 'SIGUSR1'):
649 # Windows doesn't support SIGUSR1
686 # Windows doesn't support SIGUSR1
650 signal.signal(signal.SIGUSR1, self._signal_info)
687 signal.signal(signal.SIGUSR1, self._signal_info)
651 if hasattr(signal, 'SIGINFO'):
688 if hasattr(signal, 'SIGINFO'):
652 # only on BSD-based systems
689 # only on BSD-based systems
653 signal.signal(signal.SIGINFO, self._signal_info)
690 signal.signal(signal.SIGINFO, self._signal_info)
654
691
655 def _handle_sigint(self, sig, frame):
692 def _handle_sigint(self, sig, frame):
656 """SIGINT handler spawns confirmation dialog"""
693 """SIGINT handler spawns confirmation dialog"""
657 # register more forceful signal handler for ^C^C case
694 # register more forceful signal handler for ^C^C case
658 signal.signal(signal.SIGINT, self._signal_stop)
695 signal.signal(signal.SIGINT, self._signal_stop)
659 # request confirmation dialog in bg thread, to avoid
696 # request confirmation dialog in bg thread, to avoid
660 # blocking the App
697 # blocking the App
661 thread = threading.Thread(target=self._confirm_exit)
698 thread = threading.Thread(target=self._confirm_exit)
662 thread.daemon = True
699 thread.daemon = True
663 thread.start()
700 thread.start()
664
701
665 def _restore_sigint_handler(self):
702 def _restore_sigint_handler(self):
666 """callback for restoring original SIGINT handler"""
703 """callback for restoring original SIGINT handler"""
667 signal.signal(signal.SIGINT, self._handle_sigint)
704 signal.signal(signal.SIGINT, self._handle_sigint)
668
705
669 def _confirm_exit(self):
706 def _confirm_exit(self):
670 """confirm shutdown on ^C
707 """confirm shutdown on ^C
671
708
672 A second ^C, or answering 'y' within 5s will cause shutdown,
709 A second ^C, or answering 'y' within 5s will cause shutdown,
673 otherwise original SIGINT handler will be restored.
710 otherwise original SIGINT handler will be restored.
674
711
675 This doesn't work on Windows.
712 This doesn't work on Windows.
676 """
713 """
677 # FIXME: remove this delay when pyzmq dependency is >= 2.1.11
714 # FIXME: remove this delay when pyzmq dependency is >= 2.1.11
678 time.sleep(0.1)
715 time.sleep(0.1)
679 info = self.log.info
716 info = self.log.info
680 info('interrupted')
717 info('interrupted')
681 print(self.notebook_info())
718 print(self.notebook_info())
682 sys.stdout.write("Shutdown this notebook server (y/[n])? ")
719 sys.stdout.write("Shutdown this notebook server (y/[n])? ")
683 sys.stdout.flush()
720 sys.stdout.flush()
684 r,w,x = select.select([sys.stdin], [], [], 5)
721 r,w,x = select.select([sys.stdin], [], [], 5)
685 if r:
722 if r:
686 line = sys.stdin.readline()
723 line = sys.stdin.readline()
687 if line.lower().startswith('y'):
724 if line.lower().startswith('y'):
688 self.log.critical("Shutdown confirmed")
725 self.log.critical("Shutdown confirmed")
689 ioloop.IOLoop.instance().stop()
726 ioloop.IOLoop.instance().stop()
690 return
727 return
691 else:
728 else:
692 print("No answer for 5s:", end=' ')
729 print("No answer for 5s:", end=' ')
693 print("resuming operation...")
730 print("resuming operation...")
694 # no answer, or answer is no:
731 # no answer, or answer is no:
695 # set it back to original SIGINT handler
732 # set it back to original SIGINT handler
696 # use IOLoop.add_callback because signal.signal must be called
733 # use IOLoop.add_callback because signal.signal must be called
697 # from main thread
734 # from main thread
698 ioloop.IOLoop.instance().add_callback(self._restore_sigint_handler)
735 ioloop.IOLoop.instance().add_callback(self._restore_sigint_handler)
699
736
700 def _signal_stop(self, sig, frame):
737 def _signal_stop(self, sig, frame):
701 self.log.critical("received signal %s, stopping", sig)
738 self.log.critical("received signal %s, stopping", sig)
702 ioloop.IOLoop.instance().stop()
739 ioloop.IOLoop.instance().stop()
703
740
704 def _signal_info(self, sig, frame):
741 def _signal_info(self, sig, frame):
705 print(self.notebook_info())
742 print(self.notebook_info())
706
743
707 def init_components(self):
744 def init_components(self):
708 """Check the components submodule, and warn if it's unclean"""
745 """Check the components submodule, and warn if it's unclean"""
709 status = submodule.check_submodule_status()
746 status = submodule.check_submodule_status()
710 if status == 'missing':
747 if status == 'missing':
711 self.log.warn("components submodule missing, running `git submodule update`")
748 self.log.warn("components submodule missing, running `git submodule update`")
712 submodule.update_submodules(submodule.ipython_parent())
749 submodule.update_submodules(submodule.ipython_parent())
713 elif status == 'unclean':
750 elif status == 'unclean':
714 self.log.warn("components submodule unclean, you may see 404s on static/components")
751 self.log.warn("components submodule unclean, you may see 404s on static/components")
715 self.log.warn("run `setup.py submodule` or `git submodule update` to update")
752 self.log.warn("run `setup.py submodule` or `git submodule update` to update")
716
753
717 @catch_config_error
754 @catch_config_error
718 def initialize(self, argv=None):
755 def initialize(self, argv=None):
719 super(NotebookApp, self).initialize(argv)
756 super(NotebookApp, self).initialize(argv)
720 self.init_logging()
757 self.init_logging()
721 self.init_kernel_argv()
758 self.init_kernel_argv()
722 self.init_configurables()
759 self.init_configurables()
723 self.init_components()
760 self.init_components()
724 self.init_webapp()
761 self.init_webapp()
725 self.init_signal()
762 self.init_signal()
726
763
727 def cleanup_kernels(self):
764 def cleanup_kernels(self):
728 """Shutdown all kernels.
765 """Shutdown all kernels.
729
766
730 The kernels will shutdown themselves when this process no longer exists,
767 The kernels will shutdown themselves when this process no longer exists,
731 but explicit shutdown allows the KernelManagers to cleanup the connection files.
768 but explicit shutdown allows the KernelManagers to cleanup the connection files.
732 """
769 """
733 self.log.info('Shutting down kernels')
770 self.log.info('Shutting down kernels')
734 self.kernel_manager.shutdown_all()
771 self.kernel_manager.shutdown_all()
735
772
736 def notebook_info(self):
773 def notebook_info(self):
737 "Return the current working directory and the server url information"
774 "Return the current working directory and the server url information"
738 info = self.notebook_manager.info_string() + "\n"
775 info = self.notebook_manager.info_string() + "\n"
739 info += "%d active kernels \n" % len(self.kernel_manager._kernels)
776 info += "%d active kernels \n" % len(self.kernel_manager._kernels)
740 return info + "The IPython Notebook is running at: %s" % self.display_url
777 return info + "The IPython Notebook is running at: %s" % self.display_url
741
778
742 def server_info(self):
779 def server_info(self):
743 """Return a JSONable dict of information about this server."""
780 """Return a JSONable dict of information about this server."""
744 return {'url': self.connection_url,
781 return {'url': self.connection_url,
745 'hostname': self.ip if self.ip else 'localhost',
782 'hostname': self.ip if self.ip else 'localhost',
746 'port': self.port,
783 'port': self.port,
747 'secure': bool(self.certfile),
784 'secure': bool(self.certfile),
748 'base_project_url': self.base_project_url,
785 'base_project_url': self.base_project_url,
749 'notebook_dir': os.path.abspath(self.notebook_manager.notebook_dir),
786 'notebook_dir': os.path.abspath(self.notebook_manager.notebook_dir),
750 }
787 }
751
788
752 def write_server_info_file(self):
789 def write_server_info_file(self):
753 """Write the result of server_info() to the JSON file info_file."""
790 """Write the result of server_info() to the JSON file info_file."""
754 with open(self.info_file, 'w') as f:
791 with open(self.info_file, 'w') as f:
755 json.dump(self.server_info(), f, indent=2)
792 json.dump(self.server_info(), f, indent=2)
756
793
757 def remove_server_info_file(self):
794 def remove_server_info_file(self):
758 """Remove the nbserver-<pid>.json file created for this server.
795 """Remove the nbserver-<pid>.json file created for this server.
759
796
760 Ignores the error raised when the file has already been removed.
797 Ignores the error raised when the file has already been removed.
761 """
798 """
762 try:
799 try:
763 os.unlink(self.info_file)
800 os.unlink(self.info_file)
764 except OSError as e:
801 except OSError as e:
765 if e.errno != errno.ENOENT:
802 if e.errno != errno.ENOENT:
766 raise
803 raise
767
804
768 def start(self):
805 def start(self):
769 """ Start the IPython Notebook server app, after initialization
806 """ Start the IPython Notebook server app, after initialization
770
807
771 This method takes no arguments so all configuration and initialization
808 This method takes no arguments so all configuration and initialization
772 must be done prior to calling this method."""
809 must be done prior to calling this method."""
773 if self.subapp is not None:
810 if self.subapp is not None:
774 return self.subapp.start()
811 return self.subapp.start()
775
812
776 info = self.log.info
813 info = self.log.info
777 for line in self.notebook_info().split("\n"):
814 for line in self.notebook_info().split("\n"):
778 info(line)
815 info(line)
779 info("Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).")
816 info("Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).")
780
817
781 self.write_server_info_file()
818 self.write_server_info_file()
782
819
783 if self.open_browser or self.file_to_run:
820 if self.open_browser or self.file_to_run:
784 try:
821 try:
785 browser = webbrowser.get(self.browser or None)
822 browser = webbrowser.get(self.browser or None)
786 except webbrowser.Error as e:
823 except webbrowser.Error as e:
787 self.log.warn('No web browser found: %s.' % e)
824 self.log.warn('No web browser found: %s.' % e)
788 browser = None
825 browser = None
789
826
790 f = self.file_to_run
827 f = self.file_to_run
791 if f:
828 if f:
792 nbdir = os.path.abspath(self.notebook_manager.notebook_dir)
829 nbdir = os.path.abspath(self.notebook_manager.notebook_dir)
793 if f.startswith(nbdir):
830 if f.startswith(nbdir):
794 f = f[len(nbdir):]
831 f = f[len(nbdir):]
795 else:
832 else:
796 self.log.warn(
833 self.log.warn(
797 "Probably won't be able to open notebook %s "
834 "Probably won't be able to open notebook %s "
798 "because it is not in notebook_dir %s",
835 "because it is not in notebook_dir %s",
799 f, nbdir,
836 f, nbdir,
800 )
837 )
801
838
802 if os.path.isfile(self.file_to_run):
839 if os.path.isfile(self.file_to_run):
803 url = url_path_join('notebooks', f)
840 url = url_path_join('notebooks', f)
804 else:
841 else:
805 url = url_path_join('tree', f)
842 url = url_path_join('tree', f)
806 if browser:
843 if browser:
807 b = lambda : browser.open("%s%s" % (self.connection_url, url),
844 b = lambda : browser.open("%s%s" % (self.connection_url, url),
808 new=2)
845 new=2)
809 threading.Thread(target=b).start()
846 threading.Thread(target=b).start()
810 try:
847 try:
811 ioloop.IOLoop.instance().start()
848 ioloop.IOLoop.instance().start()
812 except KeyboardInterrupt:
849 except KeyboardInterrupt:
813 info("Interrupted...")
850 info("Interrupted...")
814 finally:
851 finally:
815 self.cleanup_kernels()
852 self.cleanup_kernels()
816 self.remove_server_info_file()
853 self.remove_server_info_file()
817
854
818
855
819 def list_running_servers(profile='default'):
856 def list_running_servers(profile='default'):
820 """Iterate over the server info files of running notebook servers.
857 """Iterate over the server info files of running notebook servers.
821
858
822 Given a profile name, find nbserver-* files in the security directory of
859 Given a profile name, find nbserver-* files in the security directory of
823 that profile, and yield dicts of their information, each one pertaining to
860 that profile, and yield dicts of their information, each one pertaining to
824 a currently running notebook server instance.
861 a currently running notebook server instance.
825 """
862 """
826 pd = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), name=profile)
863 pd = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), name=profile)
827 for file in os.listdir(pd.security_dir):
864 for file in os.listdir(pd.security_dir):
828 if file.startswith('nbserver-'):
865 if file.startswith('nbserver-'):
829 with io.open(os.path.join(pd.security_dir, file), encoding='utf-8') as f:
866 with io.open(os.path.join(pd.security_dir, file), encoding='utf-8') as f:
830 yield json.load(f)
867 yield json.load(f)
831
868
832 #-----------------------------------------------------------------------------
869 #-----------------------------------------------------------------------------
833 # Main entry point
870 # Main entry point
834 #-----------------------------------------------------------------------------
871 #-----------------------------------------------------------------------------
835
872
836 launch_new_instance = NotebookApp.launch_instance
873 launch_new_instance = NotebookApp.launch_instance
837
874
General Comments 0
You need to be logged in to leave comments. Login now