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