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