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