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