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