##// END OF EJS Templates
make logout_handler overridable
Min RK -
Show More
@@ -1,1038 +1,1044 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 # Copyright (c) IPython Development Team.
4 # Copyright (c) IPython Development Team.
5 # Distributed under the terms of the Modified BSD License.
5 # Distributed under the terms of the Modified BSD License.
6
6
7 from __future__ import print_function
7 from __future__ import print_function
8
8
9 import base64
9 import base64
10 import datetime
10 import datetime
11 import errno
11 import errno
12 import io
12 import io
13 import json
13 import json
14 import logging
14 import logging
15 import os
15 import os
16 import random
16 import random
17 import re
17 import re
18 import select
18 import select
19 import signal
19 import signal
20 import socket
20 import socket
21 import sys
21 import sys
22 import threading
22 import threading
23 import time
23 import time
24 import webbrowser
24 import webbrowser
25
25
26
26
27 # check for pyzmq 2.1.11
27 # check for pyzmq 2.1.11
28 from IPython.utils.zmqrelated import check_for_zmq
28 from IPython.utils.zmqrelated import check_for_zmq
29 check_for_zmq('2.1.11', 'IPython.html')
29 check_for_zmq('2.1.11', 'IPython.html')
30
30
31 from jinja2 import Environment, FileSystemLoader
31 from jinja2 import Environment, FileSystemLoader
32
32
33 # Install the pyzmq ioloop. This has to be done before anything else from
33 # Install the pyzmq ioloop. This has to be done before anything else from
34 # tornado is imported.
34 # tornado is imported.
35 from zmq.eventloop import ioloop
35 from zmq.eventloop import ioloop
36 ioloop.install()
36 ioloop.install()
37
37
38 # check for tornado 3.1.0
38 # check for tornado 3.1.0
39 msg = "The IPython Notebook requires tornado >= 4.0"
39 msg = "The IPython Notebook requires tornado >= 4.0"
40 try:
40 try:
41 import tornado
41 import tornado
42 except ImportError:
42 except ImportError:
43 raise ImportError(msg)
43 raise ImportError(msg)
44 try:
44 try:
45 version_info = tornado.version_info
45 version_info = tornado.version_info
46 except AttributeError:
46 except AttributeError:
47 raise ImportError(msg + ", but you have < 1.1.0")
47 raise ImportError(msg + ", but you have < 1.1.0")
48 if version_info < (4,0):
48 if version_info < (4,0):
49 raise ImportError(msg + ", but you have %s" % tornado.version)
49 raise ImportError(msg + ", but you have %s" % tornado.version)
50
50
51 from tornado import httpserver
51 from tornado import httpserver
52 from tornado import web
52 from tornado import web
53 from tornado.log import LogFormatter, app_log, access_log, gen_log
53 from tornado.log import LogFormatter, app_log, access_log, gen_log
54
54
55 from IPython.html import (
55 from IPython.html import (
56 DEFAULT_STATIC_FILES_PATH,
56 DEFAULT_STATIC_FILES_PATH,
57 DEFAULT_TEMPLATE_PATH_LIST,
57 DEFAULT_TEMPLATE_PATH_LIST,
58 )
58 )
59 from .base.handlers import Template404
59 from .base.handlers import Template404
60 from .log import log_request
60 from .log import log_request
61 from .services.kernels.kernelmanager import MappingKernelManager
61 from .services.kernels.kernelmanager import MappingKernelManager
62 from .services.contents.manager import ContentsManager
62 from .services.contents.manager import ContentsManager
63 from .services.contents.filemanager import FileContentsManager
63 from .services.contents.filemanager import FileContentsManager
64 from .services.clusters.clustermanager import ClusterManager
64 from .services.clusters.clustermanager import ClusterManager
65 from .services.sessions.sessionmanager import SessionManager
65 from .services.sessions.sessionmanager import SessionManager
66
66
67 from .base.handlers import AuthenticatedFileHandler, FileFindHandler
67 from .base.handlers import AuthenticatedFileHandler, FileFindHandler
68
68
69 from IPython.config import Config
69 from IPython.config import Config
70 from IPython.config.application import catch_config_error, boolean_flag
70 from IPython.config.application import catch_config_error, boolean_flag
71 from IPython.core.application import (
71 from IPython.core.application import (
72 BaseIPythonApplication, base_flags, base_aliases,
72 BaseIPythonApplication, base_flags, base_aliases,
73 )
73 )
74 from IPython.core.profiledir import ProfileDir
74 from IPython.core.profiledir import ProfileDir
75 from IPython.kernel import KernelManager
75 from IPython.kernel import KernelManager
76 from IPython.kernel.kernelspec import KernelSpecManager
76 from IPython.kernel.kernelspec import KernelSpecManager
77 from IPython.kernel.zmq.session import default_secure, Session
77 from IPython.kernel.zmq.session import default_secure, Session
78 from IPython.nbformat.sign import NotebookNotary
78 from IPython.nbformat.sign import NotebookNotary
79 from IPython.utils.importstring import import_item
79 from IPython.utils.importstring import import_item
80 from IPython.utils import submodule
80 from IPython.utils import submodule
81 from IPython.utils.process import check_pid
81 from IPython.utils.process import check_pid
82 from IPython.utils.traitlets import (
82 from IPython.utils.traitlets import (
83 Dict, Unicode, Integer, List, Bool, Bytes, Instance,
83 Dict, Unicode, Integer, List, Bool, Bytes, Instance,
84 DottedObjectName, TraitError,
84 DottedObjectName, TraitError,
85 )
85 )
86 from IPython.utils import py3compat
86 from IPython.utils import py3compat
87 from IPython.utils.path import filefind, get_ipython_dir
87 from IPython.utils.path import filefind, get_ipython_dir
88 from IPython.utils.sysinfo import get_sys_info
88 from IPython.utils.sysinfo import get_sys_info
89
89
90 from .utils import url_path_join
90 from .utils import url_path_join
91
91
92 #-----------------------------------------------------------------------------
92 #-----------------------------------------------------------------------------
93 # Module globals
93 # Module globals
94 #-----------------------------------------------------------------------------
94 #-----------------------------------------------------------------------------
95
95
96 _examples = """
96 _examples = """
97 ipython notebook # start the notebook
97 ipython notebook # start the notebook
98 ipython notebook --profile=sympy # use the sympy profile
98 ipython notebook --profile=sympy # use the sympy profile
99 ipython notebook --certfile=mycert.pem # use SSL/TLS certificate
99 ipython notebook --certfile=mycert.pem # use SSL/TLS certificate
100 """
100 """
101
101
102 #-----------------------------------------------------------------------------
102 #-----------------------------------------------------------------------------
103 # Helper functions
103 # Helper functions
104 #-----------------------------------------------------------------------------
104 #-----------------------------------------------------------------------------
105
105
106 def random_ports(port, n):
106 def random_ports(port, n):
107 """Generate a list of n random ports near the given port.
107 """Generate a list of n random ports near the given port.
108
108
109 The first 5 ports will be sequential, and the remaining n-5 will be
109 The first 5 ports will be sequential, and the remaining n-5 will be
110 randomly selected in the range [port-2*n, port+2*n].
110 randomly selected in the range [port-2*n, port+2*n].
111 """
111 """
112 for i in range(min(5, n)):
112 for i in range(min(5, n)):
113 yield port + i
113 yield port + i
114 for i in range(n-5):
114 for i in range(n-5):
115 yield max(1, port + random.randint(-2*n, 2*n))
115 yield max(1, port + random.randint(-2*n, 2*n))
116
116
117 def load_handlers(name):
117 def load_handlers(name):
118 """Load the (URL pattern, handler) tuples for each component."""
118 """Load the (URL pattern, handler) tuples for each component."""
119 name = 'IPython.html.' + name
119 name = 'IPython.html.' + name
120 mod = __import__(name, fromlist=['default_handlers'])
120 mod = __import__(name, fromlist=['default_handlers'])
121 return mod.default_handlers
121 return mod.default_handlers
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, contents_manager,
129 def __init__(self, ipython_app, kernel_manager, contents_manager,
130 cluster_manager, session_manager, kernel_spec_manager,
130 cluster_manager, session_manager, kernel_spec_manager,
131 config_manager, log,
131 config_manager, log,
132 base_url, default_url, settings_overrides, jinja_env_options):
132 base_url, default_url, settings_overrides, jinja_env_options):
133
133
134 settings = self.init_settings(
134 settings = self.init_settings(
135 ipython_app, kernel_manager, contents_manager, cluster_manager,
135 ipython_app, kernel_manager, contents_manager, cluster_manager,
136 session_manager, kernel_spec_manager, config_manager, log, base_url,
136 session_manager, kernel_spec_manager, config_manager, log, base_url,
137 default_url, settings_overrides, jinja_env_options)
137 default_url, settings_overrides, jinja_env_options)
138 handlers = self.init_handlers(settings)
138 handlers = self.init_handlers(settings)
139
139
140 super(NotebookWebApplication, self).__init__(handlers, **settings)
140 super(NotebookWebApplication, self).__init__(handlers, **settings)
141
141
142 def init_settings(self, ipython_app, kernel_manager, contents_manager,
142 def init_settings(self, ipython_app, kernel_manager, contents_manager,
143 cluster_manager, session_manager, kernel_spec_manager,
143 cluster_manager, session_manager, kernel_spec_manager,
144 config_manager,
144 config_manager,
145 log, base_url, default_url, settings_overrides,
145 log, base_url, default_url, settings_overrides,
146 jinja_env_options=None):
146 jinja_env_options=None):
147
147
148 _template_path = settings_overrides.get(
148 _template_path = settings_overrides.get(
149 "template_path",
149 "template_path",
150 ipython_app.template_file_path,
150 ipython_app.template_file_path,
151 )
151 )
152 if isinstance(_template_path, str):
152 if isinstance(_template_path, str):
153 _template_path = (_template_path,)
153 _template_path = (_template_path,)
154 template_path = [os.path.expanduser(path) for path in _template_path]
154 template_path = [os.path.expanduser(path) for path in _template_path]
155
155
156 jenv_opt = jinja_env_options if jinja_env_options else {}
156 jenv_opt = jinja_env_options if jinja_env_options else {}
157 env = Environment(loader=FileSystemLoader(template_path), **jenv_opt)
157 env = Environment(loader=FileSystemLoader(template_path), **jenv_opt)
158
158
159 sys_info = get_sys_info()
159 sys_info = get_sys_info()
160 if sys_info['commit_source'] == 'repository':
160 if sys_info['commit_source'] == 'repository':
161 # don't cache (rely on 304) when working from master
161 # don't cache (rely on 304) when working from master
162 version_hash = ''
162 version_hash = ''
163 else:
163 else:
164 # reset the cache on server restart
164 # reset the cache on server restart
165 version_hash = datetime.datetime.now().strftime("%Y%m%d%H%M%S")
165 version_hash = datetime.datetime.now().strftime("%Y%m%d%H%M%S")
166
166
167 settings = dict(
167 settings = dict(
168 # basics
168 # basics
169 log_function=log_request,
169 log_function=log_request,
170 base_url=base_url,
170 base_url=base_url,
171 default_url=default_url,
171 default_url=default_url,
172 template_path=template_path,
172 template_path=template_path,
173 static_path=ipython_app.static_file_path,
173 static_path=ipython_app.static_file_path,
174 static_handler_class = FileFindHandler,
174 static_handler_class = FileFindHandler,
175 static_url_prefix = url_path_join(base_url,'/static/'),
175 static_url_prefix = url_path_join(base_url,'/static/'),
176 static_handler_args = {
176 static_handler_args = {
177 # don't cache custom.js
177 # don't cache custom.js
178 'no_cache_paths': [url_path_join(base_url, 'static', 'custom')],
178 'no_cache_paths': [url_path_join(base_url, 'static', 'custom')],
179 },
179 },
180 version_hash=version_hash,
180 version_hash=version_hash,
181
181
182 # authentication
182 # authentication
183 cookie_secret=ipython_app.cookie_secret,
183 cookie_secret=ipython_app.cookie_secret,
184 login_url=url_path_join(base_url,'/login'),
184 login_url=url_path_join(base_url,'/login'),
185 login_handler_class=ipython_app.login_handler_class,
185 login_handler_class=ipython_app.login_handler_class,
186 logout_handler_class=ipython_app.logout_handler_class,
186 password=ipython_app.password,
187 password=ipython_app.password,
187
188
188 # managers
189 # managers
189 kernel_manager=kernel_manager,
190 kernel_manager=kernel_manager,
190 contents_manager=contents_manager,
191 contents_manager=contents_manager,
191 cluster_manager=cluster_manager,
192 cluster_manager=cluster_manager,
192 session_manager=session_manager,
193 session_manager=session_manager,
193 kernel_spec_manager=kernel_spec_manager,
194 kernel_spec_manager=kernel_spec_manager,
194 config_manager=config_manager,
195 config_manager=config_manager,
195
196
196 # IPython stuff
197 # IPython stuff
197 nbextensions_path=ipython_app.nbextensions_path,
198 nbextensions_path=ipython_app.nbextensions_path,
198 websocket_url=ipython_app.websocket_url,
199 websocket_url=ipython_app.websocket_url,
199 mathjax_url=ipython_app.mathjax_url,
200 mathjax_url=ipython_app.mathjax_url,
200 config=ipython_app.config,
201 config=ipython_app.config,
201 jinja2_env=env,
202 jinja2_env=env,
202 terminals_available=False, # Set later if terminals are available
203 terminals_available=False, # Set later if terminals are available
203 )
204 )
204
205
205 # allow custom overrides for the tornado web app.
206 # allow custom overrides for the tornado web app.
206 settings.update(settings_overrides)
207 settings.update(settings_overrides)
207 return settings
208 return settings
208
209
209 def init_handlers(self, settings):
210 def init_handlers(self, settings):
210 """Load the (URL pattern, handler) tuples for each component."""
211 """Load the (URL pattern, handler) tuples for each component."""
211
212
212 # Order matters. The first handler to match the URL will handle the request.
213 # Order matters. The first handler to match the URL will handle the request.
213 handlers = []
214 handlers = []
214 handlers.extend(load_handlers('tree.handlers'))
215 handlers.extend(load_handlers('tree.handlers'))
215 handlers.extend([(r"/login", settings['login_handler_class'])])
216 handlers.extend([(r"/login", settings['login_handler_class'])])
216 handlers.extend(load_handlers('auth.logout'))
217 handlers.extend([(r"/logout", settings['logout_handler_class'])])
217 handlers.extend(load_handlers('files.handlers'))
218 handlers.extend(load_handlers('files.handlers'))
218 handlers.extend(load_handlers('notebook.handlers'))
219 handlers.extend(load_handlers('notebook.handlers'))
219 handlers.extend(load_handlers('nbconvert.handlers'))
220 handlers.extend(load_handlers('nbconvert.handlers'))
220 handlers.extend(load_handlers('kernelspecs.handlers'))
221 handlers.extend(load_handlers('kernelspecs.handlers'))
221 handlers.extend(load_handlers('edit.handlers'))
222 handlers.extend(load_handlers('edit.handlers'))
222 handlers.extend(load_handlers('services.config.handlers'))
223 handlers.extend(load_handlers('services.config.handlers'))
223 handlers.extend(load_handlers('services.kernels.handlers'))
224 handlers.extend(load_handlers('services.kernels.handlers'))
224 handlers.extend(load_handlers('services.contents.handlers'))
225 handlers.extend(load_handlers('services.contents.handlers'))
225 handlers.extend(load_handlers('services.clusters.handlers'))
226 handlers.extend(load_handlers('services.clusters.handlers'))
226 handlers.extend(load_handlers('services.sessions.handlers'))
227 handlers.extend(load_handlers('services.sessions.handlers'))
227 handlers.extend(load_handlers('services.nbconvert.handlers'))
228 handlers.extend(load_handlers('services.nbconvert.handlers'))
228 handlers.extend(load_handlers('services.kernelspecs.handlers'))
229 handlers.extend(load_handlers('services.kernelspecs.handlers'))
229 handlers.extend(load_handlers('services.security.handlers'))
230 handlers.extend(load_handlers('services.security.handlers'))
230 handlers.append(
231 handlers.append(
231 (r"/nbextensions/(.*)", FileFindHandler, {
232 (r"/nbextensions/(.*)", FileFindHandler, {
232 'path': settings['nbextensions_path'],
233 'path': settings['nbextensions_path'],
233 'no_cache_paths': ['/'], # don't cache anything in nbextensions
234 'no_cache_paths': ['/'], # don't cache anything in nbextensions
234 }),
235 }),
235 )
236 )
236 # register base handlers last
237 # register base handlers last
237 handlers.extend(load_handlers('base.handlers'))
238 handlers.extend(load_handlers('base.handlers'))
238 # set the URL that will be redirected from `/`
239 # set the URL that will be redirected from `/`
239 handlers.append(
240 handlers.append(
240 (r'/?', web.RedirectHandler, {
241 (r'/?', web.RedirectHandler, {
241 'url' : url_path_join(settings['base_url'], settings['default_url']),
242 'url' : url_path_join(settings['base_url'], settings['default_url']),
242 'permanent': False, # want 302, not 301
243 'permanent': False, # want 302, not 301
243 })
244 })
244 )
245 )
245 # prepend base_url onto the patterns that we match
246 # prepend base_url onto the patterns that we match
246 new_handlers = []
247 new_handlers = []
247 for handler in handlers:
248 for handler in handlers:
248 pattern = url_path_join(settings['base_url'], handler[0])
249 pattern = url_path_join(settings['base_url'], handler[0])
249 new_handler = tuple([pattern] + list(handler[1:]))
250 new_handler = tuple([pattern] + list(handler[1:]))
250 new_handlers.append(new_handler)
251 new_handlers.append(new_handler)
251 # add 404 on the end, which will catch everything that falls through
252 # add 404 on the end, which will catch everything that falls through
252 new_handlers.append((r'(.*)', Template404))
253 new_handlers.append((r'(.*)', Template404))
253 return new_handlers
254 return new_handlers
254
255
255
256
256 class NbserverListApp(BaseIPythonApplication):
257 class NbserverListApp(BaseIPythonApplication):
257
258
258 description="List currently running notebook servers in this profile."
259 description="List currently running notebook servers in this profile."
259
260
260 flags = dict(
261 flags = dict(
261 json=({'NbserverListApp': {'json': True}},
262 json=({'NbserverListApp': {'json': True}},
262 "Produce machine-readable JSON output."),
263 "Produce machine-readable JSON output."),
263 )
264 )
264
265
265 json = Bool(False, config=True,
266 json = Bool(False, config=True,
266 help="If True, each line of output will be a JSON object with the "
267 help="If True, each line of output will be a JSON object with the "
267 "details from the server info file.")
268 "details from the server info file.")
268
269
269 def start(self):
270 def start(self):
270 if not self.json:
271 if not self.json:
271 print("Currently running servers:")
272 print("Currently running servers:")
272 for serverinfo in list_running_servers(self.profile):
273 for serverinfo in list_running_servers(self.profile):
273 if self.json:
274 if self.json:
274 print(json.dumps(serverinfo))
275 print(json.dumps(serverinfo))
275 else:
276 else:
276 print(serverinfo['url'], "::", serverinfo['notebook_dir'])
277 print(serverinfo['url'], "::", serverinfo['notebook_dir'])
277
278
278 #-----------------------------------------------------------------------------
279 #-----------------------------------------------------------------------------
279 # Aliases and Flags
280 # Aliases and Flags
280 #-----------------------------------------------------------------------------
281 #-----------------------------------------------------------------------------
281
282
282 flags = dict(base_flags)
283 flags = dict(base_flags)
283 flags['no-browser']=(
284 flags['no-browser']=(
284 {'NotebookApp' : {'open_browser' : False}},
285 {'NotebookApp' : {'open_browser' : False}},
285 "Don't open the notebook in a browser after startup."
286 "Don't open the notebook in a browser after startup."
286 )
287 )
287 flags['pylab']=(
288 flags['pylab']=(
288 {'NotebookApp' : {'pylab' : 'warn'}},
289 {'NotebookApp' : {'pylab' : 'warn'}},
289 "DISABLED: use %pylab or %matplotlib in the notebook to enable matplotlib."
290 "DISABLED: use %pylab or %matplotlib in the notebook to enable matplotlib."
290 )
291 )
291 flags['no-mathjax']=(
292 flags['no-mathjax']=(
292 {'NotebookApp' : {'enable_mathjax' : False}},
293 {'NotebookApp' : {'enable_mathjax' : False}},
293 """Disable MathJax
294 """Disable MathJax
294
295
295 MathJax is the javascript library IPython uses to render math/LaTeX. It is
296 MathJax is the javascript library IPython uses to render math/LaTeX. It is
296 very large, so you may want to disable it if you have a slow internet
297 very large, so you may want to disable it if you have a slow internet
297 connection, or for offline use of the notebook.
298 connection, or for offline use of the notebook.
298
299
299 When disabled, equations etc. will appear as their untransformed TeX source.
300 When disabled, equations etc. will appear as their untransformed TeX source.
300 """
301 """
301 )
302 )
302
303
303 # Add notebook manager flags
304 # Add notebook manager flags
304 flags.update(boolean_flag('script', 'FileContentsManager.save_script',
305 flags.update(boolean_flag('script', 'FileContentsManager.save_script',
305 'DEPRECATED, IGNORED',
306 'DEPRECATED, IGNORED',
306 'DEPRECATED, IGNORED'))
307 'DEPRECATED, IGNORED'))
307
308
308 aliases = dict(base_aliases)
309 aliases = dict(base_aliases)
309
310
310 aliases.update({
311 aliases.update({
311 'ip': 'NotebookApp.ip',
312 'ip': 'NotebookApp.ip',
312 'port': 'NotebookApp.port',
313 'port': 'NotebookApp.port',
313 'port-retries': 'NotebookApp.port_retries',
314 'port-retries': 'NotebookApp.port_retries',
314 'transport': 'KernelManager.transport',
315 'transport': 'KernelManager.transport',
315 'keyfile': 'NotebookApp.keyfile',
316 'keyfile': 'NotebookApp.keyfile',
316 'certfile': 'NotebookApp.certfile',
317 'certfile': 'NotebookApp.certfile',
317 'notebook-dir': 'NotebookApp.notebook_dir',
318 'notebook-dir': 'NotebookApp.notebook_dir',
318 'browser': 'NotebookApp.browser',
319 'browser': 'NotebookApp.browser',
319 'pylab': 'NotebookApp.pylab',
320 'pylab': 'NotebookApp.pylab',
320 })
321 })
321
322
322 #-----------------------------------------------------------------------------
323 #-----------------------------------------------------------------------------
323 # NotebookApp
324 # NotebookApp
324 #-----------------------------------------------------------------------------
325 #-----------------------------------------------------------------------------
325
326
326 class NotebookApp(BaseIPythonApplication):
327 class NotebookApp(BaseIPythonApplication):
327
328
328 name = 'ipython-notebook'
329 name = 'ipython-notebook'
329
330
330 description = """
331 description = """
331 The IPython HTML Notebook.
332 The IPython HTML Notebook.
332
333
333 This launches a Tornado based HTML Notebook Server that serves up an
334 This launches a Tornado based HTML Notebook Server that serves up an
334 HTML5/Javascript Notebook client.
335 HTML5/Javascript Notebook client.
335 """
336 """
336 examples = _examples
337 examples = _examples
337 aliases = aliases
338 aliases = aliases
338 flags = flags
339 flags = flags
339
340
340 classes = [
341 classes = [
341 KernelManager, ProfileDir, Session, MappingKernelManager,
342 KernelManager, ProfileDir, Session, MappingKernelManager,
342 ContentsManager, FileContentsManager, NotebookNotary,
343 ContentsManager, FileContentsManager, NotebookNotary,
343 ]
344 ]
344 flags = Dict(flags)
345 flags = Dict(flags)
345 aliases = Dict(aliases)
346 aliases = Dict(aliases)
346
347
347 subcommands = dict(
348 subcommands = dict(
348 list=(NbserverListApp, NbserverListApp.description.splitlines()[0]),
349 list=(NbserverListApp, NbserverListApp.description.splitlines()[0]),
349 )
350 )
350
351
351 ipython_kernel_argv = List(Unicode)
352 ipython_kernel_argv = List(Unicode)
352
353
353 _log_formatter_cls = LogFormatter
354 _log_formatter_cls = LogFormatter
354
355
355 def _log_level_default(self):
356 def _log_level_default(self):
356 return logging.INFO
357 return logging.INFO
357
358
358 def _log_datefmt_default(self):
359 def _log_datefmt_default(self):
359 """Exclude date from default date format"""
360 """Exclude date from default date format"""
360 return "%H:%M:%S"
361 return "%H:%M:%S"
361
362
362 def _log_format_default(self):
363 def _log_format_default(self):
363 """override default log format to include time"""
364 """override default log format to include time"""
364 return u"%(color)s[%(levelname)1.1s %(asctime)s.%(msecs).03d %(name)s]%(end_color)s %(message)s"
365 return u"%(color)s[%(levelname)1.1s %(asctime)s.%(msecs).03d %(name)s]%(end_color)s %(message)s"
365
366
366 # create requested profiles by default, if they don't exist:
367 # create requested profiles by default, if they don't exist:
367 auto_create = Bool(True)
368 auto_create = Bool(True)
368
369
369 # file to be opened in the notebook server
370 # file to be opened in the notebook server
370 file_to_run = Unicode('', config=True)
371 file_to_run = Unicode('', config=True)
371
372
372 # Network related information
373 # Network related information
373
374
374 allow_origin = Unicode('', config=True,
375 allow_origin = Unicode('', config=True,
375 help="""Set the Access-Control-Allow-Origin header
376 help="""Set the Access-Control-Allow-Origin header
376
377
377 Use '*' to allow any origin to access your server.
378 Use '*' to allow any origin to access your server.
378
379
379 Takes precedence over allow_origin_pat.
380 Takes precedence over allow_origin_pat.
380 """
381 """
381 )
382 )
382
383
383 allow_origin_pat = Unicode('', config=True,
384 allow_origin_pat = Unicode('', config=True,
384 help="""Use a regular expression for the Access-Control-Allow-Origin header
385 help="""Use a regular expression for the Access-Control-Allow-Origin header
385
386
386 Requests from an origin matching the expression will get replies with:
387 Requests from an origin matching the expression will get replies with:
387
388
388 Access-Control-Allow-Origin: origin
389 Access-Control-Allow-Origin: origin
389
390
390 where `origin` is the origin of the request.
391 where `origin` is the origin of the request.
391
392
392 Ignored if allow_origin is set.
393 Ignored if allow_origin is set.
393 """
394 """
394 )
395 )
395
396
396 allow_credentials = Bool(False, config=True,
397 allow_credentials = Bool(False, config=True,
397 help="Set the Access-Control-Allow-Credentials: true header"
398 help="Set the Access-Control-Allow-Credentials: true header"
398 )
399 )
399
400
400 default_url = Unicode('/tree', config=True,
401 default_url = Unicode('/tree', config=True,
401 help="The default URL to redirect to from `/`"
402 help="The default URL to redirect to from `/`"
402 )
403 )
403
404
404 ip = Unicode('localhost', config=True,
405 ip = Unicode('localhost', config=True,
405 help="The IP address the notebook server will listen on."
406 help="The IP address the notebook server will listen on."
406 )
407 )
407
408
408 def _ip_changed(self, name, old, new):
409 def _ip_changed(self, name, old, new):
409 if new == u'*': self.ip = u''
410 if new == u'*': self.ip = u''
410
411
411 port = Integer(8888, config=True,
412 port = Integer(8888, config=True,
412 help="The port the notebook server will listen on."
413 help="The port the notebook server will listen on."
413 )
414 )
414 port_retries = Integer(50, config=True,
415 port_retries = Integer(50, config=True,
415 help="The number of additional ports to try if the specified port is not available."
416 help="The number of additional ports to try if the specified port is not available."
416 )
417 )
417
418
418 certfile = Unicode(u'', config=True,
419 certfile = Unicode(u'', config=True,
419 help="""The full path to an SSL/TLS certificate file."""
420 help="""The full path to an SSL/TLS certificate file."""
420 )
421 )
421
422
422 keyfile = Unicode(u'', config=True,
423 keyfile = Unicode(u'', config=True,
423 help="""The full path to a private key file for usage with SSL/TLS."""
424 help="""The full path to a private key file for usage with SSL/TLS."""
424 )
425 )
425
426
426 cookie_secret_file = Unicode(config=True,
427 cookie_secret_file = Unicode(config=True,
427 help="""The file where the cookie secret is stored."""
428 help="""The file where the cookie secret is stored."""
428 )
429 )
429 def _cookie_secret_file_default(self):
430 def _cookie_secret_file_default(self):
430 if self.profile_dir is None:
431 if self.profile_dir is None:
431 return ''
432 return ''
432 return os.path.join(self.profile_dir.security_dir, 'notebook_cookie_secret')
433 return os.path.join(self.profile_dir.security_dir, 'notebook_cookie_secret')
433
434
434 cookie_secret = Bytes(b'', config=True,
435 cookie_secret = Bytes(b'', config=True,
435 help="""The random bytes used to secure cookies.
436 help="""The random bytes used to secure cookies.
436 By default this is a new random number every time you start the Notebook.
437 By default this is a new random number every time you start the Notebook.
437 Set it to a value in a config file to enable logins to persist across server sessions.
438 Set it to a value in a config file to enable logins to persist across server sessions.
438
439
439 Note: Cookie secrets should be kept private, do not share config files with
440 Note: Cookie secrets should be kept private, do not share config files with
440 cookie_secret stored in plaintext (you can read the value from a file).
441 cookie_secret stored in plaintext (you can read the value from a file).
441 """
442 """
442 )
443 )
443 def _cookie_secret_default(self):
444 def _cookie_secret_default(self):
444 if os.path.exists(self.cookie_secret_file):
445 if os.path.exists(self.cookie_secret_file):
445 with io.open(self.cookie_secret_file, 'rb') as f:
446 with io.open(self.cookie_secret_file, 'rb') as f:
446 return f.read()
447 return f.read()
447 else:
448 else:
448 secret = base64.encodestring(os.urandom(1024))
449 secret = base64.encodestring(os.urandom(1024))
449 self._write_cookie_secret_file(secret)
450 self._write_cookie_secret_file(secret)
450 return secret
451 return secret
451
452
452 def _write_cookie_secret_file(self, secret):
453 def _write_cookie_secret_file(self, secret):
453 """write my secret to my secret_file"""
454 """write my secret to my secret_file"""
454 self.log.info("Writing notebook server cookie secret to %s", self.cookie_secret_file)
455 self.log.info("Writing notebook server cookie secret to %s", self.cookie_secret_file)
455 with io.open(self.cookie_secret_file, 'wb') as f:
456 with io.open(self.cookie_secret_file, 'wb') as f:
456 f.write(secret)
457 f.write(secret)
457 try:
458 try:
458 os.chmod(self.cookie_secret_file, 0o600)
459 os.chmod(self.cookie_secret_file, 0o600)
459 except OSError:
460 except OSError:
460 self.log.warn(
461 self.log.warn(
461 "Could not set permissions on %s",
462 "Could not set permissions on %s",
462 self.cookie_secret_file
463 self.cookie_secret_file
463 )
464 )
464
465
465 password = Unicode(u'', config=True,
466 password = Unicode(u'', config=True,
466 help="""Hashed password to use for web authentication.
467 help="""Hashed password to use for web authentication.
467
468
468 To generate, type in a python/IPython shell:
469 To generate, type in a python/IPython shell:
469
470
470 from IPython.lib import passwd; passwd()
471 from IPython.lib import passwd; passwd()
471
472
472 The string should be of the form type:salt:hashed-password.
473 The string should be of the form type:salt:hashed-password.
473 """
474 """
474 )
475 )
475
476
476 open_browser = Bool(True, config=True,
477 open_browser = Bool(True, config=True,
477 help="""Whether to open in a browser after starting.
478 help="""Whether to open in a browser after starting.
478 The specific browser used is platform dependent and
479 The specific browser used is platform dependent and
479 determined by the python standard library `webbrowser`
480 determined by the python standard library `webbrowser`
480 module, unless it is overridden using the --browser
481 module, unless it is overridden using the --browser
481 (NotebookApp.browser) configuration option.
482 (NotebookApp.browser) configuration option.
482 """)
483 """)
483
484
484 browser = Unicode(u'', config=True,
485 browser = Unicode(u'', config=True,
485 help="""Specify what command to use to invoke a web
486 help="""Specify what command to use to invoke a web
486 browser when opening the notebook. If not specified, the
487 browser when opening the notebook. If not specified, the
487 default browser will be determined by the `webbrowser`
488 default browser will be determined by the `webbrowser`
488 standard library module, which allows setting of the
489 standard library module, which allows setting of the
489 BROWSER environment variable to override it.
490 BROWSER environment variable to override it.
490 """)
491 """)
491
492
492 webapp_settings = Dict(config=True,
493 webapp_settings = Dict(config=True,
493 help="DEPRECATED, use tornado_settings"
494 help="DEPRECATED, use tornado_settings"
494 )
495 )
495 def _webapp_settings_changed(self, name, old, new):
496 def _webapp_settings_changed(self, name, old, new):
496 self.log.warn("\n webapp_settings is deprecated, use tornado_settings.\n")
497 self.log.warn("\n webapp_settings is deprecated, use tornado_settings.\n")
497 self.tornado_settings = new
498 self.tornado_settings = new
498
499
499 tornado_settings = Dict(config=True,
500 tornado_settings = Dict(config=True,
500 help="Supply overrides for the tornado.web.Application that the "
501 help="Supply overrides for the tornado.web.Application that the "
501 "IPython notebook uses.")
502 "IPython notebook uses.")
502
503
503 jinja_environment_options = Dict(config=True,
504 jinja_environment_options = Dict(config=True,
504 help="Supply extra arguments that will be passed to Jinja environment.")
505 help="Supply extra arguments that will be passed to Jinja environment.")
505
506
506 enable_mathjax = Bool(True, config=True,
507 enable_mathjax = Bool(True, config=True,
507 help="""Whether to enable MathJax for typesetting math/TeX
508 help="""Whether to enable MathJax for typesetting math/TeX
508
509
509 MathJax is the javascript library IPython uses to render math/LaTeX. It is
510 MathJax is the javascript library IPython uses to render math/LaTeX. It is
510 very large, so you may want to disable it if you have a slow internet
511 very large, so you may want to disable it if you have a slow internet
511 connection, or for offline use of the notebook.
512 connection, or for offline use of the notebook.
512
513
513 When disabled, equations etc. will appear as their untransformed TeX source.
514 When disabled, equations etc. will appear as their untransformed TeX source.
514 """
515 """
515 )
516 )
516 def _enable_mathjax_changed(self, name, old, new):
517 def _enable_mathjax_changed(self, name, old, new):
517 """set mathjax url to empty if mathjax is disabled"""
518 """set mathjax url to empty if mathjax is disabled"""
518 if not new:
519 if not new:
519 self.mathjax_url = u''
520 self.mathjax_url = u''
520
521
521 base_url = Unicode('/', config=True,
522 base_url = Unicode('/', config=True,
522 help='''The base URL for the notebook server.
523 help='''The base URL for the notebook server.
523
524
524 Leading and trailing slashes can be omitted,
525 Leading and trailing slashes can be omitted,
525 and will automatically be added.
526 and will automatically be added.
526 ''')
527 ''')
527 def _base_url_changed(self, name, old, new):
528 def _base_url_changed(self, name, old, new):
528 if not new.startswith('/'):
529 if not new.startswith('/'):
529 self.base_url = '/'+new
530 self.base_url = '/'+new
530 elif not new.endswith('/'):
531 elif not new.endswith('/'):
531 self.base_url = new+'/'
532 self.base_url = new+'/'
532
533
533 base_project_url = Unicode('/', config=True, help="""DEPRECATED use base_url""")
534 base_project_url = Unicode('/', config=True, help="""DEPRECATED use base_url""")
534 def _base_project_url_changed(self, name, old, new):
535 def _base_project_url_changed(self, name, old, new):
535 self.log.warn("base_project_url is deprecated, use base_url")
536 self.log.warn("base_project_url is deprecated, use base_url")
536 self.base_url = new
537 self.base_url = new
537
538
538 extra_static_paths = List(Unicode, config=True,
539 extra_static_paths = List(Unicode, config=True,
539 help="""Extra paths to search for serving static files.
540 help="""Extra paths to search for serving static files.
540
541
541 This allows adding javascript/css to be available from the notebook server machine,
542 This allows adding javascript/css to be available from the notebook server machine,
542 or overriding individual files in the IPython"""
543 or overriding individual files in the IPython"""
543 )
544 )
544 def _extra_static_paths_default(self):
545 def _extra_static_paths_default(self):
545 return [os.path.join(self.profile_dir.location, 'static')]
546 return [os.path.join(self.profile_dir.location, 'static')]
546
547
547 @property
548 @property
548 def static_file_path(self):
549 def static_file_path(self):
549 """return extra paths + the default location"""
550 """return extra paths + the default location"""
550 return self.extra_static_paths + [DEFAULT_STATIC_FILES_PATH]
551 return self.extra_static_paths + [DEFAULT_STATIC_FILES_PATH]
551
552
552 extra_template_paths = List(Unicode, config=True,
553 extra_template_paths = List(Unicode, config=True,
553 help="""Extra paths to search for serving jinja templates.
554 help="""Extra paths to search for serving jinja templates.
554
555
555 Can be used to override templates from IPython.html.templates."""
556 Can be used to override templates from IPython.html.templates."""
556 )
557 )
557 def _extra_template_paths_default(self):
558 def _extra_template_paths_default(self):
558 return []
559 return []
559
560
560 @property
561 @property
561 def template_file_path(self):
562 def template_file_path(self):
562 """return extra paths + the default locations"""
563 """return extra paths + the default locations"""
563 return self.extra_template_paths + DEFAULT_TEMPLATE_PATH_LIST
564 return self.extra_template_paths + DEFAULT_TEMPLATE_PATH_LIST
564
565
565 nbextensions_path = List(Unicode, config=True,
566 nbextensions_path = List(Unicode, config=True,
566 help="""paths for Javascript extensions. By default, this is just IPYTHONDIR/nbextensions"""
567 help="""paths for Javascript extensions. By default, this is just IPYTHONDIR/nbextensions"""
567 )
568 )
568 def _nbextensions_path_default(self):
569 def _nbextensions_path_default(self):
569 return [os.path.join(get_ipython_dir(), 'nbextensions')]
570 return [os.path.join(get_ipython_dir(), 'nbextensions')]
570
571
571 websocket_url = Unicode("", config=True,
572 websocket_url = Unicode("", config=True,
572 help="""The base URL for websockets,
573 help="""The base URL for websockets,
573 if it differs from the HTTP server (hint: it almost certainly doesn't).
574 if it differs from the HTTP server (hint: it almost certainly doesn't).
574
575
575 Should be in the form of an HTTP origin: ws[s]://hostname[:port]
576 Should be in the form of an HTTP origin: ws[s]://hostname[:port]
576 """
577 """
577 )
578 )
578 mathjax_url = Unicode("", config=True,
579 mathjax_url = Unicode("", config=True,
579 help="""The url for MathJax.js."""
580 help="""The url for MathJax.js."""
580 )
581 )
581 def _mathjax_url_default(self):
582 def _mathjax_url_default(self):
582 if not self.enable_mathjax:
583 if not self.enable_mathjax:
583 return u''
584 return u''
584 static_url_prefix = self.tornado_settings.get("static_url_prefix",
585 static_url_prefix = self.tornado_settings.get("static_url_prefix",
585 url_path_join(self.base_url, "static")
586 url_path_join(self.base_url, "static")
586 )
587 )
587
588
588 # try local mathjax, either in nbextensions/mathjax or static/mathjax
589 # try local mathjax, either in nbextensions/mathjax or static/mathjax
589 for (url_prefix, search_path) in [
590 for (url_prefix, search_path) in [
590 (url_path_join(self.base_url, "nbextensions"), self.nbextensions_path),
591 (url_path_join(self.base_url, "nbextensions"), self.nbextensions_path),
591 (static_url_prefix, self.static_file_path),
592 (static_url_prefix, self.static_file_path),
592 ]:
593 ]:
593 self.log.debug("searching for local mathjax in %s", search_path)
594 self.log.debug("searching for local mathjax in %s", search_path)
594 try:
595 try:
595 mathjax = filefind(os.path.join('mathjax', 'MathJax.js'), search_path)
596 mathjax = filefind(os.path.join('mathjax', 'MathJax.js'), search_path)
596 except IOError:
597 except IOError:
597 continue
598 continue
598 else:
599 else:
599 url = url_path_join(url_prefix, u"mathjax/MathJax.js")
600 url = url_path_join(url_prefix, u"mathjax/MathJax.js")
600 self.log.info("Serving local MathJax from %s at %s", mathjax, url)
601 self.log.info("Serving local MathJax from %s at %s", mathjax, url)
601 return url
602 return url
602
603
603 # no local mathjax, serve from CDN
604 # no local mathjax, serve from CDN
604 url = u"https://cdn.mathjax.org/mathjax/latest/MathJax.js"
605 url = u"https://cdn.mathjax.org/mathjax/latest/MathJax.js"
605 self.log.info("Using MathJax from CDN: %s", url)
606 self.log.info("Using MathJax from CDN: %s", url)
606 return url
607 return url
607
608
608 def _mathjax_url_changed(self, name, old, new):
609 def _mathjax_url_changed(self, name, old, new):
609 if new and not self.enable_mathjax:
610 if new and not self.enable_mathjax:
610 # enable_mathjax=False overrides mathjax_url
611 # enable_mathjax=False overrides mathjax_url
611 self.mathjax_url = u''
612 self.mathjax_url = u''
612 else:
613 else:
613 self.log.info("Using MathJax: %s", new)
614 self.log.info("Using MathJax: %s", new)
614
615
615 contents_manager_class = DottedObjectName('IPython.html.services.contents.filemanager.FileContentsManager',
616 contents_manager_class = DottedObjectName('IPython.html.services.contents.filemanager.FileContentsManager',
616 config=True,
617 config=True,
617 help='The notebook manager class to use.'
618 help='The notebook manager class to use.'
618 )
619 )
619 kernel_manager_class = DottedObjectName('IPython.html.services.kernels.kernelmanager.MappingKernelManager',
620 kernel_manager_class = DottedObjectName('IPython.html.services.kernels.kernelmanager.MappingKernelManager',
620 config=True,
621 config=True,
621 help='The kernel manager class to use.'
622 help='The kernel manager class to use.'
622 )
623 )
623 session_manager_class = DottedObjectName('IPython.html.services.sessions.sessionmanager.SessionManager',
624 session_manager_class = DottedObjectName('IPython.html.services.sessions.sessionmanager.SessionManager',
624 config=True,
625 config=True,
625 help='The session manager class to use.'
626 help='The session manager class to use.'
626 )
627 )
627 cluster_manager_class = DottedObjectName('IPython.html.services.clusters.clustermanager.ClusterManager',
628 cluster_manager_class = DottedObjectName('IPython.html.services.clusters.clustermanager.ClusterManager',
628 config=True,
629 config=True,
629 help='The cluster manager class to use.'
630 help='The cluster manager class to use.'
630 )
631 )
631
632
632 config_manager_class = DottedObjectName('IPython.html.services.config.manager.ConfigManager',
633 config_manager_class = DottedObjectName('IPython.html.services.config.manager.ConfigManager',
633 config = True,
634 config = True,
634 help='The config manager class to use'
635 help='The config manager class to use'
635 )
636 )
636
637
637 kernel_spec_manager = Instance(KernelSpecManager)
638 kernel_spec_manager = Instance(KernelSpecManager)
638
639
639 def _kernel_spec_manager_default(self):
640 def _kernel_spec_manager_default(self):
640 return KernelSpecManager(ipython_dir=self.ipython_dir)
641 return KernelSpecManager(ipython_dir=self.ipython_dir)
641
642
642 kernel_spec_manager_class = DottedObjectName('IPython.kernel.kernelspec.KernelSpecManager',
643 kernel_spec_manager_class = DottedObjectName('IPython.kernel.kernelspec.KernelSpecManager',
643 config=True,
644 config=True,
644 help="""
645 help="""
645 The kernel spec manager class to use. Should be a subclass
646 The kernel spec manager class to use. Should be a subclass
646 of `IPython.kernel.kernelspec.KernelSpecManager`.
647 of `IPython.kernel.kernelspec.KernelSpecManager`.
647
648
648 The Api of KernelSpecManager is provisional and might change
649 The Api of KernelSpecManager is provisional and might change
649 without warning between this version of IPython and the next stable one.
650 without warning between this version of IPython and the next stable one.
650 """)
651 """)
651
652
652 login_handler = DottedObjectName('IPython.html.auth.login.LoginHandler',
653 login_handler = DottedObjectName('IPython.html.auth.login.LoginHandler',
653 config=True,
654 config=True,
654 help='The login handler class to use.')
655 help='The login handler class to use.')
655
656
657 logout_handler = DottedObjectName('IPython.html.auth.logout.LogoutHandler',
658 config=True,
659 help='The logout handler class to use.')
660
656 trust_xheaders = Bool(False, config=True,
661 trust_xheaders = Bool(False, config=True,
657 help=("Whether to trust or not X-Scheme/X-Forwarded-Proto and X-Real-Ip/X-Forwarded-For headers"
662 help=("Whether to trust or not X-Scheme/X-Forwarded-Proto and X-Real-Ip/X-Forwarded-For headers"
658 "sent by the upstream reverse proxy. Necessary if the proxy handles SSL")
663 "sent by the upstream reverse proxy. Necessary if the proxy handles SSL")
659 )
664 )
660
665
661 info_file = Unicode()
666 info_file = Unicode()
662
667
663 def _info_file_default(self):
668 def _info_file_default(self):
664 info_file = "nbserver-%s.json"%os.getpid()
669 info_file = "nbserver-%s.json"%os.getpid()
665 return os.path.join(self.profile_dir.security_dir, info_file)
670 return os.path.join(self.profile_dir.security_dir, info_file)
666
671
667 pylab = Unicode('disabled', config=True,
672 pylab = Unicode('disabled', config=True,
668 help="""
673 help="""
669 DISABLED: use %pylab or %matplotlib in the notebook to enable matplotlib.
674 DISABLED: use %pylab or %matplotlib in the notebook to enable matplotlib.
670 """
675 """
671 )
676 )
672 def _pylab_changed(self, name, old, new):
677 def _pylab_changed(self, name, old, new):
673 """when --pylab is specified, display a warning and exit"""
678 """when --pylab is specified, display a warning and exit"""
674 if new != 'warn':
679 if new != 'warn':
675 backend = ' %s' % new
680 backend = ' %s' % new
676 else:
681 else:
677 backend = ''
682 backend = ''
678 self.log.error("Support for specifying --pylab on the command line has been removed.")
683 self.log.error("Support for specifying --pylab on the command line has been removed.")
679 self.log.error(
684 self.log.error(
680 "Please use `%pylab{0}` or `%matplotlib{0}` in the notebook itself.".format(backend)
685 "Please use `%pylab{0}` or `%matplotlib{0}` in the notebook itself.".format(backend)
681 )
686 )
682 self.exit(1)
687 self.exit(1)
683
688
684 notebook_dir = Unicode(config=True,
689 notebook_dir = Unicode(config=True,
685 help="The directory to use for notebooks and kernels."
690 help="The directory to use for notebooks and kernels."
686 )
691 )
687
692
688 def _notebook_dir_default(self):
693 def _notebook_dir_default(self):
689 if self.file_to_run:
694 if self.file_to_run:
690 return os.path.dirname(os.path.abspath(self.file_to_run))
695 return os.path.dirname(os.path.abspath(self.file_to_run))
691 else:
696 else:
692 return py3compat.getcwd()
697 return py3compat.getcwd()
693
698
694 def _notebook_dir_changed(self, name, old, new):
699 def _notebook_dir_changed(self, name, old, new):
695 """Do a bit of validation of the notebook dir."""
700 """Do a bit of validation of the notebook dir."""
696 if not os.path.isabs(new):
701 if not os.path.isabs(new):
697 # If we receive a non-absolute path, make it absolute.
702 # If we receive a non-absolute path, make it absolute.
698 self.notebook_dir = os.path.abspath(new)
703 self.notebook_dir = os.path.abspath(new)
699 return
704 return
700 if not os.path.isdir(new):
705 if not os.path.isdir(new):
701 raise TraitError("No such notebook dir: %r" % new)
706 raise TraitError("No such notebook dir: %r" % new)
702
707
703 # setting App.notebook_dir implies setting notebook and kernel dirs as well
708 # setting App.notebook_dir implies setting notebook and kernel dirs as well
704 self.config.FileContentsManager.root_dir = new
709 self.config.FileContentsManager.root_dir = new
705 self.config.MappingKernelManager.root_dir = new
710 self.config.MappingKernelManager.root_dir = new
706
711
707 def parse_command_line(self, argv=None):
712 def parse_command_line(self, argv=None):
708 super(NotebookApp, self).parse_command_line(argv)
713 super(NotebookApp, self).parse_command_line(argv)
709
714
710 if self.extra_args:
715 if self.extra_args:
711 arg0 = self.extra_args[0]
716 arg0 = self.extra_args[0]
712 f = os.path.abspath(arg0)
717 f = os.path.abspath(arg0)
713 self.argv.remove(arg0)
718 self.argv.remove(arg0)
714 if not os.path.exists(f):
719 if not os.path.exists(f):
715 self.log.critical("No such file or directory: %s", f)
720 self.log.critical("No such file or directory: %s", f)
716 self.exit(1)
721 self.exit(1)
717
722
718 # Use config here, to ensure that it takes higher priority than
723 # Use config here, to ensure that it takes higher priority than
719 # anything that comes from the profile.
724 # anything that comes from the profile.
720 c = Config()
725 c = Config()
721 if os.path.isdir(f):
726 if os.path.isdir(f):
722 c.NotebookApp.notebook_dir = f
727 c.NotebookApp.notebook_dir = f
723 elif os.path.isfile(f):
728 elif os.path.isfile(f):
724 c.NotebookApp.file_to_run = f
729 c.NotebookApp.file_to_run = f
725 self.update_config(c)
730 self.update_config(c)
726
731
727 def init_kernel_argv(self):
732 def init_kernel_argv(self):
728 """add the profile-dir to arguments to be passed to IPython kernels"""
733 """add the profile-dir to arguments to be passed to IPython kernels"""
729 # FIXME: remove special treatment of IPython kernels
734 # FIXME: remove special treatment of IPython kernels
730 # Kernel should get *absolute* path to profile directory
735 # Kernel should get *absolute* path to profile directory
731 self.ipython_kernel_argv = ["--profile-dir", self.profile_dir.location]
736 self.ipython_kernel_argv = ["--profile-dir", self.profile_dir.location]
732
737
733 def init_configurables(self):
738 def init_configurables(self):
734 # force Session default to be secure
739 # force Session default to be secure
735 default_secure(self.config)
740 default_secure(self.config)
736 kls = import_item(self.kernel_spec_manager_class)
741 kls = import_item(self.kernel_spec_manager_class)
737 self.kernel_spec_manager = kls(ipython_dir=self.ipython_dir)
742 self.kernel_spec_manager = kls(ipython_dir=self.ipython_dir)
738
743
739 kls = import_item(self.kernel_manager_class)
744 kls = import_item(self.kernel_manager_class)
740 self.kernel_manager = kls(
745 self.kernel_manager = kls(
741 parent=self, log=self.log, ipython_kernel_argv=self.ipython_kernel_argv,
746 parent=self, log=self.log, ipython_kernel_argv=self.ipython_kernel_argv,
742 connection_dir = self.profile_dir.security_dir,
747 connection_dir = self.profile_dir.security_dir,
743 )
748 )
744 kls = import_item(self.contents_manager_class)
749 kls = import_item(self.contents_manager_class)
745 self.contents_manager = kls(parent=self, log=self.log)
750 self.contents_manager = kls(parent=self, log=self.log)
746 kls = import_item(self.session_manager_class)
751 kls = import_item(self.session_manager_class)
747 self.session_manager = kls(parent=self, log=self.log,
752 self.session_manager = kls(parent=self, log=self.log,
748 kernel_manager=self.kernel_manager,
753 kernel_manager=self.kernel_manager,
749 contents_manager=self.contents_manager)
754 contents_manager=self.contents_manager)
750 kls = import_item(self.cluster_manager_class)
755 kls = import_item(self.cluster_manager_class)
751 self.cluster_manager = kls(parent=self, log=self.log)
756 self.cluster_manager = kls(parent=self, log=self.log)
752 self.cluster_manager.update_profiles()
757 self.cluster_manager.update_profiles()
753 self.login_handler_class = import_item(self.login_handler)
758 self.login_handler_class = import_item(self.login_handler)
759 self.logout_handler_class = import_item(self.logout_handler)
754
760
755 kls = import_item(self.config_manager_class)
761 kls = import_item(self.config_manager_class)
756 self.config_manager = kls(parent=self, log=self.log,
762 self.config_manager = kls(parent=self, log=self.log,
757 profile_dir=self.profile_dir.location)
763 profile_dir=self.profile_dir.location)
758
764
759 def init_logging(self):
765 def init_logging(self):
760 # This prevents double log messages because tornado use a root logger that
766 # This prevents double log messages because tornado use a root logger that
761 # self.log is a child of. The logging module dipatches log messages to a log
767 # self.log is a child of. The logging module dipatches log messages to a log
762 # and all of its ancenstors until propagate is set to False.
768 # and all of its ancenstors until propagate is set to False.
763 self.log.propagate = False
769 self.log.propagate = False
764
770
765 for log in app_log, access_log, gen_log:
771 for log in app_log, access_log, gen_log:
766 # consistent log output name (NotebookApp instead of tornado.access, etc.)
772 # consistent log output name (NotebookApp instead of tornado.access, etc.)
767 log.name = self.log.name
773 log.name = self.log.name
768 # hook up tornado 3's loggers to our app handlers
774 # hook up tornado 3's loggers to our app handlers
769 logger = logging.getLogger('tornado')
775 logger = logging.getLogger('tornado')
770 logger.propagate = True
776 logger.propagate = True
771 logger.parent = self.log
777 logger.parent = self.log
772 logger.setLevel(self.log.level)
778 logger.setLevel(self.log.level)
773
779
774 def init_webapp(self):
780 def init_webapp(self):
775 """initialize tornado webapp and httpserver"""
781 """initialize tornado webapp and httpserver"""
776 self.tornado_settings['allow_origin'] = self.allow_origin
782 self.tornado_settings['allow_origin'] = self.allow_origin
777 if self.allow_origin_pat:
783 if self.allow_origin_pat:
778 self.tornado_settings['allow_origin_pat'] = re.compile(self.allow_origin_pat)
784 self.tornado_settings['allow_origin_pat'] = re.compile(self.allow_origin_pat)
779 self.tornado_settings['allow_credentials'] = self.allow_credentials
785 self.tornado_settings['allow_credentials'] = self.allow_credentials
780
786
781 self.web_app = NotebookWebApplication(
787 self.web_app = NotebookWebApplication(
782 self, self.kernel_manager, self.contents_manager,
788 self, self.kernel_manager, self.contents_manager,
783 self.cluster_manager, self.session_manager, self.kernel_spec_manager,
789 self.cluster_manager, self.session_manager, self.kernel_spec_manager,
784 self.config_manager,
790 self.config_manager,
785 self.log, self.base_url, self.default_url, self.tornado_settings,
791 self.log, self.base_url, self.default_url, self.tornado_settings,
786 self.jinja_environment_options
792 self.jinja_environment_options
787 )
793 )
788 if self.certfile:
794 if self.certfile:
789 ssl_options = dict(certfile=self.certfile)
795 ssl_options = dict(certfile=self.certfile)
790 if self.keyfile:
796 if self.keyfile:
791 ssl_options['keyfile'] = self.keyfile
797 ssl_options['keyfile'] = self.keyfile
792 else:
798 else:
793 ssl_options = None
799 ssl_options = None
794 self.login_handler_class.validate_notebook_app_security(self, ssl_options=ssl_options)
800 self.login_handler_class.validate_notebook_app_security(self, ssl_options=ssl_options)
795 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options,
801 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options,
796 xheaders=self.trust_xheaders)
802 xheaders=self.trust_xheaders)
797
803
798 success = None
804 success = None
799 for port in random_ports(self.port, self.port_retries+1):
805 for port in random_ports(self.port, self.port_retries+1):
800 try:
806 try:
801 self.http_server.listen(port, self.ip)
807 self.http_server.listen(port, self.ip)
802 except socket.error as e:
808 except socket.error as e:
803 if e.errno == errno.EADDRINUSE:
809 if e.errno == errno.EADDRINUSE:
804 self.log.info('The port %i is already in use, trying another random port.' % port)
810 self.log.info('The port %i is already in use, trying another random port.' % port)
805 continue
811 continue
806 elif e.errno in (errno.EACCES, getattr(errno, 'WSAEACCES', errno.EACCES)):
812 elif e.errno in (errno.EACCES, getattr(errno, 'WSAEACCES', errno.EACCES)):
807 self.log.warn("Permission to listen on port %i denied" % port)
813 self.log.warn("Permission to listen on port %i denied" % port)
808 continue
814 continue
809 else:
815 else:
810 raise
816 raise
811 else:
817 else:
812 self.port = port
818 self.port = port
813 success = True
819 success = True
814 break
820 break
815 if not success:
821 if not success:
816 self.log.critical('ERROR: the notebook server could not be started because '
822 self.log.critical('ERROR: the notebook server could not be started because '
817 'no available port could be found.')
823 'no available port could be found.')
818 self.exit(1)
824 self.exit(1)
819
825
820 @property
826 @property
821 def display_url(self):
827 def display_url(self):
822 ip = self.ip if self.ip else '[all ip addresses on your system]'
828 ip = self.ip if self.ip else '[all ip addresses on your system]'
823 return self._url(ip)
829 return self._url(ip)
824
830
825 @property
831 @property
826 def connection_url(self):
832 def connection_url(self):
827 ip = self.ip if self.ip else 'localhost'
833 ip = self.ip if self.ip else 'localhost'
828 return self._url(ip)
834 return self._url(ip)
829
835
830 def _url(self, ip):
836 def _url(self, ip):
831 proto = 'https' if self.certfile else 'http'
837 proto = 'https' if self.certfile else 'http'
832 return "%s://%s:%i%s" % (proto, ip, self.port, self.base_url)
838 return "%s://%s:%i%s" % (proto, ip, self.port, self.base_url)
833
839
834 def init_terminals(self):
840 def init_terminals(self):
835 try:
841 try:
836 from .terminal import initialize
842 from .terminal import initialize
837 initialize(self.web_app)
843 initialize(self.web_app)
838 self.web_app.settings['terminals_available'] = True
844 self.web_app.settings['terminals_available'] = True
839 except ImportError as e:
845 except ImportError as e:
840 self.log.info("Terminals not available (error was %s)", e)
846 self.log.info("Terminals not available (error was %s)", e)
841
847
842 def init_signal(self):
848 def init_signal(self):
843 if not sys.platform.startswith('win'):
849 if not sys.platform.startswith('win'):
844 signal.signal(signal.SIGINT, self._handle_sigint)
850 signal.signal(signal.SIGINT, self._handle_sigint)
845 signal.signal(signal.SIGTERM, self._signal_stop)
851 signal.signal(signal.SIGTERM, self._signal_stop)
846 if hasattr(signal, 'SIGUSR1'):
852 if hasattr(signal, 'SIGUSR1'):
847 # Windows doesn't support SIGUSR1
853 # Windows doesn't support SIGUSR1
848 signal.signal(signal.SIGUSR1, self._signal_info)
854 signal.signal(signal.SIGUSR1, self._signal_info)
849 if hasattr(signal, 'SIGINFO'):
855 if hasattr(signal, 'SIGINFO'):
850 # only on BSD-based systems
856 # only on BSD-based systems
851 signal.signal(signal.SIGINFO, self._signal_info)
857 signal.signal(signal.SIGINFO, self._signal_info)
852
858
853 def _handle_sigint(self, sig, frame):
859 def _handle_sigint(self, sig, frame):
854 """SIGINT handler spawns confirmation dialog"""
860 """SIGINT handler spawns confirmation dialog"""
855 # register more forceful signal handler for ^C^C case
861 # register more forceful signal handler for ^C^C case
856 signal.signal(signal.SIGINT, self._signal_stop)
862 signal.signal(signal.SIGINT, self._signal_stop)
857 # request confirmation dialog in bg thread, to avoid
863 # request confirmation dialog in bg thread, to avoid
858 # blocking the App
864 # blocking the App
859 thread = threading.Thread(target=self._confirm_exit)
865 thread = threading.Thread(target=self._confirm_exit)
860 thread.daemon = True
866 thread.daemon = True
861 thread.start()
867 thread.start()
862
868
863 def _restore_sigint_handler(self):
869 def _restore_sigint_handler(self):
864 """callback for restoring original SIGINT handler"""
870 """callback for restoring original SIGINT handler"""
865 signal.signal(signal.SIGINT, self._handle_sigint)
871 signal.signal(signal.SIGINT, self._handle_sigint)
866
872
867 def _confirm_exit(self):
873 def _confirm_exit(self):
868 """confirm shutdown on ^C
874 """confirm shutdown on ^C
869
875
870 A second ^C, or answering 'y' within 5s will cause shutdown,
876 A second ^C, or answering 'y' within 5s will cause shutdown,
871 otherwise original SIGINT handler will be restored.
877 otherwise original SIGINT handler will be restored.
872
878
873 This doesn't work on Windows.
879 This doesn't work on Windows.
874 """
880 """
875 info = self.log.info
881 info = self.log.info
876 info('interrupted')
882 info('interrupted')
877 print(self.notebook_info())
883 print(self.notebook_info())
878 sys.stdout.write("Shutdown this notebook server (y/[n])? ")
884 sys.stdout.write("Shutdown this notebook server (y/[n])? ")
879 sys.stdout.flush()
885 sys.stdout.flush()
880 r,w,x = select.select([sys.stdin], [], [], 5)
886 r,w,x = select.select([sys.stdin], [], [], 5)
881 if r:
887 if r:
882 line = sys.stdin.readline()
888 line = sys.stdin.readline()
883 if line.lower().startswith('y') and 'n' not in line.lower():
889 if line.lower().startswith('y') and 'n' not in line.lower():
884 self.log.critical("Shutdown confirmed")
890 self.log.critical("Shutdown confirmed")
885 ioloop.IOLoop.instance().stop()
891 ioloop.IOLoop.instance().stop()
886 return
892 return
887 else:
893 else:
888 print("No answer for 5s:", end=' ')
894 print("No answer for 5s:", end=' ')
889 print("resuming operation...")
895 print("resuming operation...")
890 # no answer, or answer is no:
896 # no answer, or answer is no:
891 # set it back to original SIGINT handler
897 # set it back to original SIGINT handler
892 # use IOLoop.add_callback because signal.signal must be called
898 # use IOLoop.add_callback because signal.signal must be called
893 # from main thread
899 # from main thread
894 ioloop.IOLoop.instance().add_callback(self._restore_sigint_handler)
900 ioloop.IOLoop.instance().add_callback(self._restore_sigint_handler)
895
901
896 def _signal_stop(self, sig, frame):
902 def _signal_stop(self, sig, frame):
897 self.log.critical("received signal %s, stopping", sig)
903 self.log.critical("received signal %s, stopping", sig)
898 ioloop.IOLoop.instance().stop()
904 ioloop.IOLoop.instance().stop()
899
905
900 def _signal_info(self, sig, frame):
906 def _signal_info(self, sig, frame):
901 print(self.notebook_info())
907 print(self.notebook_info())
902
908
903 def init_components(self):
909 def init_components(self):
904 """Check the components submodule, and warn if it's unclean"""
910 """Check the components submodule, and warn if it's unclean"""
905 status = submodule.check_submodule_status()
911 status = submodule.check_submodule_status()
906 if status == 'missing':
912 if status == 'missing':
907 self.log.warn("components submodule missing, running `git submodule update`")
913 self.log.warn("components submodule missing, running `git submodule update`")
908 submodule.update_submodules(submodule.ipython_parent())
914 submodule.update_submodules(submodule.ipython_parent())
909 elif status == 'unclean':
915 elif status == 'unclean':
910 self.log.warn("components submodule unclean, you may see 404s on static/components")
916 self.log.warn("components submodule unclean, you may see 404s on static/components")
911 self.log.warn("run `setup.py submodule` or `git submodule update` to update")
917 self.log.warn("run `setup.py submodule` or `git submodule update` to update")
912
918
913 @catch_config_error
919 @catch_config_error
914 def initialize(self, argv=None):
920 def initialize(self, argv=None):
915 super(NotebookApp, self).initialize(argv)
921 super(NotebookApp, self).initialize(argv)
916 self.init_logging()
922 self.init_logging()
917 self.init_kernel_argv()
923 self.init_kernel_argv()
918 self.init_configurables()
924 self.init_configurables()
919 self.init_components()
925 self.init_components()
920 self.init_webapp()
926 self.init_webapp()
921 self.init_terminals()
927 self.init_terminals()
922 self.init_signal()
928 self.init_signal()
923
929
924 def cleanup_kernels(self):
930 def cleanup_kernels(self):
925 """Shutdown all kernels.
931 """Shutdown all kernels.
926
932
927 The kernels will shutdown themselves when this process no longer exists,
933 The kernels will shutdown themselves when this process no longer exists,
928 but explicit shutdown allows the KernelManagers to cleanup the connection files.
934 but explicit shutdown allows the KernelManagers to cleanup the connection files.
929 """
935 """
930 self.log.info('Shutting down kernels')
936 self.log.info('Shutting down kernels')
931 self.kernel_manager.shutdown_all()
937 self.kernel_manager.shutdown_all()
932
938
933 def notebook_info(self):
939 def notebook_info(self):
934 "Return the current working directory and the server url information"
940 "Return the current working directory and the server url information"
935 info = self.contents_manager.info_string() + "\n"
941 info = self.contents_manager.info_string() + "\n"
936 info += "%d active kernels \n" % len(self.kernel_manager._kernels)
942 info += "%d active kernels \n" % len(self.kernel_manager._kernels)
937 return info + "The IPython Notebook is running at: %s" % self.display_url
943 return info + "The IPython Notebook is running at: %s" % self.display_url
938
944
939 def server_info(self):
945 def server_info(self):
940 """Return a JSONable dict of information about this server."""
946 """Return a JSONable dict of information about this server."""
941 return {'url': self.connection_url,
947 return {'url': self.connection_url,
942 'hostname': self.ip if self.ip else 'localhost',
948 'hostname': self.ip if self.ip else 'localhost',
943 'port': self.port,
949 'port': self.port,
944 'secure': bool(self.certfile),
950 'secure': bool(self.certfile),
945 'base_url': self.base_url,
951 'base_url': self.base_url,
946 'notebook_dir': os.path.abspath(self.notebook_dir),
952 'notebook_dir': os.path.abspath(self.notebook_dir),
947 'pid': os.getpid()
953 'pid': os.getpid()
948 }
954 }
949
955
950 def write_server_info_file(self):
956 def write_server_info_file(self):
951 """Write the result of server_info() to the JSON file info_file."""
957 """Write the result of server_info() to the JSON file info_file."""
952 with open(self.info_file, 'w') as f:
958 with open(self.info_file, 'w') as f:
953 json.dump(self.server_info(), f, indent=2)
959 json.dump(self.server_info(), f, indent=2)
954
960
955 def remove_server_info_file(self):
961 def remove_server_info_file(self):
956 """Remove the nbserver-<pid>.json file created for this server.
962 """Remove the nbserver-<pid>.json file created for this server.
957
963
958 Ignores the error raised when the file has already been removed.
964 Ignores the error raised when the file has already been removed.
959 """
965 """
960 try:
966 try:
961 os.unlink(self.info_file)
967 os.unlink(self.info_file)
962 except OSError as e:
968 except OSError as e:
963 if e.errno != errno.ENOENT:
969 if e.errno != errno.ENOENT:
964 raise
970 raise
965
971
966 def start(self):
972 def start(self):
967 """ Start the IPython Notebook server app, after initialization
973 """ Start the IPython Notebook server app, after initialization
968
974
969 This method takes no arguments so all configuration and initialization
975 This method takes no arguments so all configuration and initialization
970 must be done prior to calling this method."""
976 must be done prior to calling this method."""
971 if self.subapp is not None:
977 if self.subapp is not None:
972 return self.subapp.start()
978 return self.subapp.start()
973
979
974 info = self.log.info
980 info = self.log.info
975 for line in self.notebook_info().split("\n"):
981 for line in self.notebook_info().split("\n"):
976 info(line)
982 info(line)
977 info("Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).")
983 info("Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).")
978
984
979 self.write_server_info_file()
985 self.write_server_info_file()
980
986
981 if self.open_browser or self.file_to_run:
987 if self.open_browser or self.file_to_run:
982 try:
988 try:
983 browser = webbrowser.get(self.browser or None)
989 browser = webbrowser.get(self.browser or None)
984 except webbrowser.Error as e:
990 except webbrowser.Error as e:
985 self.log.warn('No web browser found: %s.' % e)
991 self.log.warn('No web browser found: %s.' % e)
986 browser = None
992 browser = None
987
993
988 if self.file_to_run:
994 if self.file_to_run:
989 if not os.path.exists(self.file_to_run):
995 if not os.path.exists(self.file_to_run):
990 self.log.critical("%s does not exist" % self.file_to_run)
996 self.log.critical("%s does not exist" % self.file_to_run)
991 self.exit(1)
997 self.exit(1)
992
998
993 relpath = os.path.relpath(self.file_to_run, self.notebook_dir)
999 relpath = os.path.relpath(self.file_to_run, self.notebook_dir)
994 uri = url_path_join('notebooks', *relpath.split(os.sep))
1000 uri = url_path_join('notebooks', *relpath.split(os.sep))
995 else:
1001 else:
996 uri = 'tree'
1002 uri = 'tree'
997 if browser:
1003 if browser:
998 b = lambda : browser.open(url_path_join(self.connection_url, uri),
1004 b = lambda : browser.open(url_path_join(self.connection_url, uri),
999 new=2)
1005 new=2)
1000 threading.Thread(target=b).start()
1006 threading.Thread(target=b).start()
1001 try:
1007 try:
1002 ioloop.IOLoop.instance().start()
1008 ioloop.IOLoop.instance().start()
1003 except KeyboardInterrupt:
1009 except KeyboardInterrupt:
1004 info("Interrupted...")
1010 info("Interrupted...")
1005 finally:
1011 finally:
1006 self.cleanup_kernels()
1012 self.cleanup_kernels()
1007 self.remove_server_info_file()
1013 self.remove_server_info_file()
1008
1014
1009
1015
1010 def list_running_servers(profile='default'):
1016 def list_running_servers(profile='default'):
1011 """Iterate over the server info files of running notebook servers.
1017 """Iterate over the server info files of running notebook servers.
1012
1018
1013 Given a profile name, find nbserver-* files in the security directory of
1019 Given a profile name, find nbserver-* files in the security directory of
1014 that profile, and yield dicts of their information, each one pertaining to
1020 that profile, and yield dicts of their information, each one pertaining to
1015 a currently running notebook server instance.
1021 a currently running notebook server instance.
1016 """
1022 """
1017 pd = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), name=profile)
1023 pd = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), name=profile)
1018 for file in os.listdir(pd.security_dir):
1024 for file in os.listdir(pd.security_dir):
1019 if file.startswith('nbserver-'):
1025 if file.startswith('nbserver-'):
1020 with io.open(os.path.join(pd.security_dir, file), encoding='utf-8') as f:
1026 with io.open(os.path.join(pd.security_dir, file), encoding='utf-8') as f:
1021 info = json.load(f)
1027 info = json.load(f)
1022
1028
1023 # Simple check whether that process is really still running
1029 # Simple check whether that process is really still running
1024 # Also remove leftover files from IPython 2.x without a pid field
1030 # Also remove leftover files from IPython 2.x without a pid field
1025 if ('pid' in info) and check_pid(info['pid']):
1031 if ('pid' in info) and check_pid(info['pid']):
1026 yield info
1032 yield info
1027 else:
1033 else:
1028 # If the process has died, try to delete its info file
1034 # If the process has died, try to delete its info file
1029 try:
1035 try:
1030 os.unlink(file)
1036 os.unlink(file)
1031 except OSError:
1037 except OSError:
1032 pass # TODO: This should warn or log or something
1038 pass # TODO: This should warn or log or something
1033 #-----------------------------------------------------------------------------
1039 #-----------------------------------------------------------------------------
1034 # Main entry point
1040 # Main entry point
1035 #-----------------------------------------------------------------------------
1041 #-----------------------------------------------------------------------------
1036
1042
1037 launch_new_instance = NotebookApp.launch_instance
1043 launch_new_instance = NotebookApp.launch_instance
1038
1044
General Comments 0
You need to be logged in to leave comments. Login now