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