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