##// END OF EJS Templates
Make KernelSpecManager configurable...
Bussonnier Matthias -
Show More
@@ -1,992 +1,1006 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 kernel_argv = List(Unicode)
327 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 def _file_to_run_changed(self, name, old, new):
347 def _file_to_run_changed(self, name, old, new):
348 path, base = os.path.split(new)
348 path, base = os.path.split(new)
349 if path:
349 if path:
350 self.file_to_run = base
350 self.file_to_run = base
351 self.notebook_dir = path
351 self.notebook_dir = path
352
352
353 # Network related information
353 # Network related information
354
354
355 allow_origin = Unicode('', config=True,
355 allow_origin = Unicode('', config=True,
356 help="""Set the Access-Control-Allow-Origin header
356 help="""Set the Access-Control-Allow-Origin header
357
357
358 Use '*' to allow any origin to access your server.
358 Use '*' to allow any origin to access your server.
359
359
360 Takes precedence over allow_origin_pat.
360 Takes precedence over allow_origin_pat.
361 """
361 """
362 )
362 )
363
363
364 allow_origin_pat = Unicode('', config=True,
364 allow_origin_pat = Unicode('', config=True,
365 help="""Use a regular expression for the Access-Control-Allow-Origin header
365 help="""Use a regular expression for the Access-Control-Allow-Origin header
366
366
367 Requests from an origin matching the expression will get replies with:
367 Requests from an origin matching the expression will get replies with:
368
368
369 Access-Control-Allow-Origin: origin
369 Access-Control-Allow-Origin: origin
370
370
371 where `origin` is the origin of the request.
371 where `origin` is the origin of the request.
372
372
373 Ignored if allow_origin is set.
373 Ignored if allow_origin is set.
374 """
374 """
375 )
375 )
376
376
377 allow_credentials = Bool(False, config=True,
377 allow_credentials = Bool(False, config=True,
378 help="Set the Access-Control-Allow-Credentials: true header"
378 help="Set the Access-Control-Allow-Credentials: true header"
379 )
379 )
380
380
381 default_url = Unicode('/tree', config=True,
381 default_url = Unicode('/tree', config=True,
382 help="The default URL to redirect to from `/`"
382 help="The default URL to redirect to from `/`"
383 )
383 )
384
384
385 ip = Unicode('localhost', config=True,
385 ip = Unicode('localhost', config=True,
386 help="The IP address the notebook server will listen on."
386 help="The IP address the notebook server will listen on."
387 )
387 )
388
388
389 def _ip_changed(self, name, old, new):
389 def _ip_changed(self, name, old, new):
390 if new == u'*': self.ip = u''
390 if new == u'*': self.ip = u''
391
391
392 port = Integer(8888, config=True,
392 port = Integer(8888, config=True,
393 help="The port the notebook server will listen on."
393 help="The port the notebook server will listen on."
394 )
394 )
395 port_retries = Integer(50, config=True,
395 port_retries = Integer(50, config=True,
396 help="The number of additional ports to try if the specified port is not available."
396 help="The number of additional ports to try if the specified port is not available."
397 )
397 )
398
398
399 certfile = Unicode(u'', config=True,
399 certfile = Unicode(u'', config=True,
400 help="""The full path to an SSL/TLS certificate file."""
400 help="""The full path to an SSL/TLS certificate file."""
401 )
401 )
402
402
403 keyfile = Unicode(u'', config=True,
403 keyfile = Unicode(u'', config=True,
404 help="""The full path to a private key file for usage with SSL/TLS."""
404 help="""The full path to a private key file for usage with SSL/TLS."""
405 )
405 )
406
406
407 cookie_secret_file = Unicode(config=True,
407 cookie_secret_file = Unicode(config=True,
408 help="""The file where the cookie secret is stored."""
408 help="""The file where the cookie secret is stored."""
409 )
409 )
410 def _cookie_secret_file_default(self):
410 def _cookie_secret_file_default(self):
411 if self.profile_dir is None:
411 if self.profile_dir is None:
412 return ''
412 return ''
413 return os.path.join(self.profile_dir.security_dir, 'notebook_cookie_secret')
413 return os.path.join(self.profile_dir.security_dir, 'notebook_cookie_secret')
414
414
415 cookie_secret = Bytes(b'', config=True,
415 cookie_secret = Bytes(b'', config=True,
416 help="""The random bytes used to secure cookies.
416 help="""The random bytes used to secure cookies.
417 By default this is a new random number every time you start the Notebook.
417 By default this is a new random number every time you start the Notebook.
418 Set it to a value in a config file to enable logins to persist across server sessions.
418 Set it to a value in a config file to enable logins to persist across server sessions.
419
419
420 Note: Cookie secrets should be kept private, do not share config files with
420 Note: Cookie secrets should be kept private, do not share config files with
421 cookie_secret stored in plaintext (you can read the value from a file).
421 cookie_secret stored in plaintext (you can read the value from a file).
422 """
422 """
423 )
423 )
424 def _cookie_secret_default(self):
424 def _cookie_secret_default(self):
425 if os.path.exists(self.cookie_secret_file):
425 if os.path.exists(self.cookie_secret_file):
426 with io.open(self.cookie_secret_file, 'rb') as f:
426 with io.open(self.cookie_secret_file, 'rb') as f:
427 return f.read()
427 return f.read()
428 else:
428 else:
429 secret = base64.encodestring(os.urandom(1024))
429 secret = base64.encodestring(os.urandom(1024))
430 self._write_cookie_secret_file(secret)
430 self._write_cookie_secret_file(secret)
431 return secret
431 return secret
432
432
433 def _write_cookie_secret_file(self, secret):
433 def _write_cookie_secret_file(self, secret):
434 """write my secret to my secret_file"""
434 """write my secret to my secret_file"""
435 self.log.info("Writing notebook server cookie secret to %s", self.cookie_secret_file)
435 self.log.info("Writing notebook server cookie secret to %s", self.cookie_secret_file)
436 with io.open(self.cookie_secret_file, 'wb') as f:
436 with io.open(self.cookie_secret_file, 'wb') as f:
437 f.write(secret)
437 f.write(secret)
438 try:
438 try:
439 os.chmod(self.cookie_secret_file, 0o600)
439 os.chmod(self.cookie_secret_file, 0o600)
440 except OSError:
440 except OSError:
441 self.log.warn(
441 self.log.warn(
442 "Could not set permissions on %s",
442 "Could not set permissions on %s",
443 self.cookie_secret_file
443 self.cookie_secret_file
444 )
444 )
445
445
446 password = Unicode(u'', config=True,
446 password = Unicode(u'', config=True,
447 help="""Hashed password to use for web authentication.
447 help="""Hashed password to use for web authentication.
448
448
449 To generate, type in a python/IPython shell:
449 To generate, type in a python/IPython shell:
450
450
451 from IPython.lib import passwd; passwd()
451 from IPython.lib import passwd; passwd()
452
452
453 The string should be of the form type:salt:hashed-password.
453 The string should be of the form type:salt:hashed-password.
454 """
454 """
455 )
455 )
456
456
457 open_browser = Bool(True, config=True,
457 open_browser = Bool(True, config=True,
458 help="""Whether to open in a browser after starting.
458 help="""Whether to open in a browser after starting.
459 The specific browser used is platform dependent and
459 The specific browser used is platform dependent and
460 determined by the python standard library `webbrowser`
460 determined by the python standard library `webbrowser`
461 module, unless it is overridden using the --browser
461 module, unless it is overridden using the --browser
462 (NotebookApp.browser) configuration option.
462 (NotebookApp.browser) configuration option.
463 """)
463 """)
464
464
465 browser = Unicode(u'', config=True,
465 browser = Unicode(u'', config=True,
466 help="""Specify what command to use to invoke a web
466 help="""Specify what command to use to invoke a web
467 browser when opening the notebook. If not specified, the
467 browser when opening the notebook. If not specified, the
468 default browser will be determined by the `webbrowser`
468 default browser will be determined by the `webbrowser`
469 standard library module, which allows setting of the
469 standard library module, which allows setting of the
470 BROWSER environment variable to override it.
470 BROWSER environment variable to override it.
471 """)
471 """)
472
472
473 webapp_settings = Dict(config=True,
473 webapp_settings = Dict(config=True,
474 help="DEPRECATED, use tornado_settings"
474 help="DEPRECATED, use tornado_settings"
475 )
475 )
476 def _webapp_settings_changed(self, name, old, new):
476 def _webapp_settings_changed(self, name, old, new):
477 self.log.warn("\n webapp_settings is deprecated, use tornado_settings.\n")
477 self.log.warn("\n webapp_settings is deprecated, use tornado_settings.\n")
478 self.tornado_settings = new
478 self.tornado_settings = new
479
479
480 tornado_settings = Dict(config=True,
480 tornado_settings = Dict(config=True,
481 help="Supply overrides for the tornado.web.Application that the "
481 help="Supply overrides for the tornado.web.Application that the "
482 "IPython notebook uses.")
482 "IPython notebook uses.")
483
483
484 jinja_environment_options = Dict(config=True,
484 jinja_environment_options = Dict(config=True,
485 help="Supply extra arguments that will be passed to Jinja environment.")
485 help="Supply extra arguments that will be passed to Jinja environment.")
486
486
487
487
488 enable_mathjax = Bool(True, config=True,
488 enable_mathjax = Bool(True, config=True,
489 help="""Whether to enable MathJax for typesetting math/TeX
489 help="""Whether to enable MathJax for typesetting math/TeX
490
490
491 MathJax is the javascript library IPython uses to render math/LaTeX. It is
491 MathJax is the javascript library IPython uses to render math/LaTeX. It is
492 very large, so you may want to disable it if you have a slow internet
492 very large, so you may want to disable it if you have a slow internet
493 connection, or for offline use of the notebook.
493 connection, or for offline use of the notebook.
494
494
495 When disabled, equations etc. will appear as their untransformed TeX source.
495 When disabled, equations etc. will appear as their untransformed TeX source.
496 """
496 """
497 )
497 )
498 def _enable_mathjax_changed(self, name, old, new):
498 def _enable_mathjax_changed(self, name, old, new):
499 """set mathjax url to empty if mathjax is disabled"""
499 """set mathjax url to empty if mathjax is disabled"""
500 if not new:
500 if not new:
501 self.mathjax_url = u''
501 self.mathjax_url = u''
502
502
503 base_url = Unicode('/', config=True,
503 base_url = Unicode('/', config=True,
504 help='''The base URL for the notebook server.
504 help='''The base URL for the notebook server.
505
505
506 Leading and trailing slashes can be omitted,
506 Leading and trailing slashes can be omitted,
507 and will automatically be added.
507 and will automatically be added.
508 ''')
508 ''')
509 def _base_url_changed(self, name, old, new):
509 def _base_url_changed(self, name, old, new):
510 if not new.startswith('/'):
510 if not new.startswith('/'):
511 self.base_url = '/'+new
511 self.base_url = '/'+new
512 elif not new.endswith('/'):
512 elif not new.endswith('/'):
513 self.base_url = new+'/'
513 self.base_url = new+'/'
514
514
515 base_project_url = Unicode('/', config=True, help="""DEPRECATED use base_url""")
515 base_project_url = Unicode('/', config=True, help="""DEPRECATED use base_url""")
516 def _base_project_url_changed(self, name, old, new):
516 def _base_project_url_changed(self, name, old, new):
517 self.log.warn("base_project_url is deprecated, use base_url")
517 self.log.warn("base_project_url is deprecated, use base_url")
518 self.base_url = new
518 self.base_url = new
519
519
520 extra_static_paths = List(Unicode, config=True,
520 extra_static_paths = List(Unicode, config=True,
521 help="""Extra paths to search for serving static files.
521 help="""Extra paths to search for serving static files.
522
522
523 This allows adding javascript/css to be available from the notebook server machine,
523 This allows adding javascript/css to be available from the notebook server machine,
524 or overriding individual files in the IPython"""
524 or overriding individual files in the IPython"""
525 )
525 )
526 def _extra_static_paths_default(self):
526 def _extra_static_paths_default(self):
527 return [os.path.join(self.profile_dir.location, 'static')]
527 return [os.path.join(self.profile_dir.location, 'static')]
528
528
529 @property
529 @property
530 def static_file_path(self):
530 def static_file_path(self):
531 """return extra paths + the default location"""
531 """return extra paths + the default location"""
532 return self.extra_static_paths + [DEFAULT_STATIC_FILES_PATH]
532 return self.extra_static_paths + [DEFAULT_STATIC_FILES_PATH]
533
533
534 extra_template_paths = List(Unicode, config=True,
534 extra_template_paths = List(Unicode, config=True,
535 help="""Extra paths to search for serving jinja templates.
535 help="""Extra paths to search for serving jinja templates.
536
536
537 Can be used to override templates from IPython.html.templates."""
537 Can be used to override templates from IPython.html.templates."""
538 )
538 )
539 def _extra_template_paths_default(self):
539 def _extra_template_paths_default(self):
540 return []
540 return []
541
541
542 @property
542 @property
543 def template_file_path(self):
543 def template_file_path(self):
544 """return extra paths + the default locations"""
544 """return extra paths + the default locations"""
545 return self.extra_template_paths + DEFAULT_TEMPLATE_PATH_LIST
545 return self.extra_template_paths + DEFAULT_TEMPLATE_PATH_LIST
546
546
547 nbextensions_path = List(Unicode, config=True,
547 nbextensions_path = List(Unicode, config=True,
548 help="""paths for Javascript extensions. By default, this is just IPYTHONDIR/nbextensions"""
548 help="""paths for Javascript extensions. By default, this is just IPYTHONDIR/nbextensions"""
549 )
549 )
550 def _nbextensions_path_default(self):
550 def _nbextensions_path_default(self):
551 return [os.path.join(get_ipython_dir(), 'nbextensions')]
551 return [os.path.join(get_ipython_dir(), 'nbextensions')]
552
552
553 websocket_url = Unicode("", config=True,
553 websocket_url = Unicode("", config=True,
554 help="""The base URL for websockets,
554 help="""The base URL for websockets,
555 if it differs from the HTTP server (hint: it almost certainly doesn't).
555 if it differs from the HTTP server (hint: it almost certainly doesn't).
556
556
557 Should be in the form of an HTTP origin: ws[s]://hostname[:port]
557 Should be in the form of an HTTP origin: ws[s]://hostname[:port]
558 """
558 """
559 )
559 )
560 mathjax_url = Unicode("", config=True,
560 mathjax_url = Unicode("", config=True,
561 help="""The url for MathJax.js."""
561 help="""The url for MathJax.js."""
562 )
562 )
563 def _mathjax_url_default(self):
563 def _mathjax_url_default(self):
564 if not self.enable_mathjax:
564 if not self.enable_mathjax:
565 return u''
565 return u''
566 static_url_prefix = self.tornado_settings.get("static_url_prefix",
566 static_url_prefix = self.tornado_settings.get("static_url_prefix",
567 url_path_join(self.base_url, "static")
567 url_path_join(self.base_url, "static")
568 )
568 )
569
569
570 # try local mathjax, either in nbextensions/mathjax or static/mathjax
570 # try local mathjax, either in nbextensions/mathjax or static/mathjax
571 for (url_prefix, search_path) in [
571 for (url_prefix, search_path) in [
572 (url_path_join(self.base_url, "nbextensions"), self.nbextensions_path),
572 (url_path_join(self.base_url, "nbextensions"), self.nbextensions_path),
573 (static_url_prefix, self.static_file_path),
573 (static_url_prefix, self.static_file_path),
574 ]:
574 ]:
575 self.log.debug("searching for local mathjax in %s", search_path)
575 self.log.debug("searching for local mathjax in %s", search_path)
576 try:
576 try:
577 mathjax = filefind(os.path.join('mathjax', 'MathJax.js'), search_path)
577 mathjax = filefind(os.path.join('mathjax', 'MathJax.js'), search_path)
578 except IOError:
578 except IOError:
579 continue
579 continue
580 else:
580 else:
581 url = url_path_join(url_prefix, u"mathjax/MathJax.js")
581 url = url_path_join(url_prefix, u"mathjax/MathJax.js")
582 self.log.info("Serving local MathJax from %s at %s", mathjax, url)
582 self.log.info("Serving local MathJax from %s at %s", mathjax, url)
583 return url
583 return url
584
584
585 # no local mathjax, serve from CDN
585 # no local mathjax, serve from CDN
586 url = u"https://cdn.mathjax.org/mathjax/latest/MathJax.js"
586 url = u"https://cdn.mathjax.org/mathjax/latest/MathJax.js"
587 self.log.info("Using MathJax from CDN: %s", url)
587 self.log.info("Using MathJax from CDN: %s", url)
588 return url
588 return url
589
589
590 def _mathjax_url_changed(self, name, old, new):
590 def _mathjax_url_changed(self, name, old, new):
591 if new and not self.enable_mathjax:
591 if new and not self.enable_mathjax:
592 # enable_mathjax=False overrides mathjax_url
592 # enable_mathjax=False overrides mathjax_url
593 self.mathjax_url = u''
593 self.mathjax_url = u''
594 else:
594 else:
595 self.log.info("Using MathJax: %s", new)
595 self.log.info("Using MathJax: %s", new)
596
596
597 contents_manager_class = DottedObjectName('IPython.html.services.contents.filemanager.FileContentsManager',
597 contents_manager_class = DottedObjectName('IPython.html.services.contents.filemanager.FileContentsManager',
598 config=True,
598 config=True,
599 help='The notebook manager class to use.'
599 help='The notebook manager class to use.'
600 )
600 )
601 kernel_manager_class = DottedObjectName('IPython.html.services.kernels.kernelmanager.MappingKernelManager',
601 kernel_manager_class = DottedObjectName('IPython.html.services.kernels.kernelmanager.MappingKernelManager',
602 config=True,
602 config=True,
603 help='The kernel manager class to use.'
603 help='The kernel manager class to use.'
604 )
604 )
605 session_manager_class = DottedObjectName('IPython.html.services.sessions.sessionmanager.SessionManager',
605 session_manager_class = DottedObjectName('IPython.html.services.sessions.sessionmanager.SessionManager',
606 config=True,
606 config=True,
607 help='The session manager class to use.'
607 help='The session manager class to use.'
608 )
608 )
609 cluster_manager_class = DottedObjectName('IPython.html.services.clusters.clustermanager.ClusterManager',
609 cluster_manager_class = DottedObjectName('IPython.html.services.clusters.clustermanager.ClusterManager',
610 config=True,
610 config=True,
611 help='The cluster manager class to use.'
611 help='The cluster manager class to use.'
612 )
612 )
613
613
614 kernel_spec_manager = Instance(KernelSpecManager)
614 kernel_spec_manager = Instance(KernelSpecManager)
615
615
616 def _kernel_spec_manager_default(self):
616 def _kernel_spec_manager_default(self):
617 return KernelSpecManager(ipython_dir=self.ipython_dir)
617 return KernelSpecManager(ipython_dir=self.ipython_dir)
618
618
619
620 kernel_spec_manager_class = DottedObjectName('IPython.kernel.kernelspec.KernelSpecManager',
621 config=True,
622 help="""
623 The kernel spec manager class to use. Should be a subclass
624 of `IPython.kernel.kernelspec.KernelSpecManager`.
625
626 The Api of KernelSpecManager is provisional and might change
627 without warning between this version of IPython and the next stable one.
628 """)
629
619 trust_xheaders = Bool(False, config=True,
630 trust_xheaders = Bool(False, config=True,
620 help=("Whether to trust or not X-Scheme/X-Forwarded-Proto and X-Real-Ip/X-Forwarded-For headers"
631 help=("Whether to trust or not X-Scheme/X-Forwarded-Proto and X-Real-Ip/X-Forwarded-For headers"
621 "sent by the upstream reverse proxy. Necessary if the proxy handles SSL")
632 "sent by the upstream reverse proxy. Necessary if the proxy handles SSL")
622 )
633 )
623
634
624 info_file = Unicode()
635 info_file = Unicode()
625
636
626 def _info_file_default(self):
637 def _info_file_default(self):
627 info_file = "nbserver-%s.json"%os.getpid()
638 info_file = "nbserver-%s.json"%os.getpid()
628 return os.path.join(self.profile_dir.security_dir, info_file)
639 return os.path.join(self.profile_dir.security_dir, info_file)
629
640
630 notebook_dir = Unicode(py3compat.getcwd(), config=True,
641 notebook_dir = Unicode(py3compat.getcwd(), config=True,
631 help="The directory to use for notebooks and kernels."
642 help="The directory to use for notebooks and kernels."
632 )
643 )
633
644
634 pylab = Unicode('disabled', config=True,
645 pylab = Unicode('disabled', config=True,
635 help="""
646 help="""
636 DISABLED: use %pylab or %matplotlib in the notebook to enable matplotlib.
647 DISABLED: use %pylab or %matplotlib in the notebook to enable matplotlib.
637 """
648 """
638 )
649 )
639 def _pylab_changed(self, name, old, new):
650 def _pylab_changed(self, name, old, new):
640 """when --pylab is specified, display a warning and exit"""
651 """when --pylab is specified, display a warning and exit"""
641 if new != 'warn':
652 if new != 'warn':
642 backend = ' %s' % new
653 backend = ' %s' % new
643 else:
654 else:
644 backend = ''
655 backend = ''
645 self.log.error("Support for specifying --pylab on the command line has been removed.")
656 self.log.error("Support for specifying --pylab on the command line has been removed.")
646 self.log.error(
657 self.log.error(
647 "Please use `%pylab{0}` or `%matplotlib{0}` in the notebook itself.".format(backend)
658 "Please use `%pylab{0}` or `%matplotlib{0}` in the notebook itself.".format(backend)
648 )
659 )
649 self.exit(1)
660 self.exit(1)
650
661
651 def _notebook_dir_changed(self, name, old, new):
662 def _notebook_dir_changed(self, name, old, new):
652 """Do a bit of validation of the notebook dir."""
663 """Do a bit of validation of the notebook dir."""
653 if not os.path.isabs(new):
664 if not os.path.isabs(new):
654 # If we receive a non-absolute path, make it absolute.
665 # If we receive a non-absolute path, make it absolute.
655 self.notebook_dir = os.path.abspath(new)
666 self.notebook_dir = os.path.abspath(new)
656 return
667 return
657 if not os.path.isdir(new):
668 if not os.path.isdir(new):
658 raise TraitError("No such notebook dir: %r" % new)
669 raise TraitError("No such notebook dir: %r" % new)
659
670
660 # setting App.notebook_dir implies setting notebook and kernel dirs as well
671 # setting App.notebook_dir implies setting notebook and kernel dirs as well
661 self.config.FileContentsManager.root_dir = new
672 self.config.FileContentsManager.root_dir = new
662 self.config.MappingKernelManager.root_dir = new
673 self.config.MappingKernelManager.root_dir = new
663
674
664
675
665 def parse_command_line(self, argv=None):
676 def parse_command_line(self, argv=None):
666 super(NotebookApp, self).parse_command_line(argv)
677 super(NotebookApp, self).parse_command_line(argv)
667
678
668 if self.extra_args:
679 if self.extra_args:
669 arg0 = self.extra_args[0]
680 arg0 = self.extra_args[0]
670 f = os.path.abspath(arg0)
681 f = os.path.abspath(arg0)
671 self.argv.remove(arg0)
682 self.argv.remove(arg0)
672 if not os.path.exists(f):
683 if not os.path.exists(f):
673 self.log.critical("No such file or directory: %s", f)
684 self.log.critical("No such file or directory: %s", f)
674 self.exit(1)
685 self.exit(1)
675
686
676 # Use config here, to ensure that it takes higher priority than
687 # Use config here, to ensure that it takes higher priority than
677 # anything that comes from the profile.
688 # anything that comes from the profile.
678 c = Config()
689 c = Config()
679 if os.path.isdir(f):
690 if os.path.isdir(f):
680 c.NotebookApp.notebook_dir = f
691 c.NotebookApp.notebook_dir = f
681 elif os.path.isfile(f):
692 elif os.path.isfile(f):
682 c.NotebookApp.file_to_run = f
693 c.NotebookApp.file_to_run = f
683 self.update_config(c)
694 self.update_config(c)
684
695
685 def init_kernel_argv(self):
696 def init_kernel_argv(self):
686 """construct the kernel arguments"""
697 """construct the kernel arguments"""
687 # Kernel should get *absolute* path to profile directory
698 # Kernel should get *absolute* path to profile directory
688 self.kernel_argv = ["--profile-dir", self.profile_dir.location]
699 self.kernel_argv = ["--profile-dir", self.profile_dir.location]
689
700
690 def init_configurables(self):
701 def init_configurables(self):
691 # force Session default to be secure
702 # force Session default to be secure
692 default_secure(self.config)
703 default_secure(self.config)
704 kls = import_item(self.kernel_spec_manager_class)
705 self.kernel_spec_manager = kls(ipython_dir=self.ipython_dir)
706
693 kls = import_item(self.kernel_manager_class)
707 kls = import_item(self.kernel_manager_class)
694 self.kernel_manager = kls(
708 self.kernel_manager = kls(
695 parent=self, log=self.log, kernel_argv=self.kernel_argv,
709 parent=self, log=self.log, kernel_argv=self.kernel_argv,
696 connection_dir = self.profile_dir.security_dir,
710 connection_dir = self.profile_dir.security_dir,
697 )
711 )
698 kls = import_item(self.contents_manager_class)
712 kls = import_item(self.contents_manager_class)
699 self.contents_manager = kls(parent=self, log=self.log)
713 self.contents_manager = kls(parent=self, log=self.log)
700 kls = import_item(self.session_manager_class)
714 kls = import_item(self.session_manager_class)
701 self.session_manager = kls(parent=self, log=self.log,
715 self.session_manager = kls(parent=self, log=self.log,
702 kernel_manager=self.kernel_manager,
716 kernel_manager=self.kernel_manager,
703 contents_manager=self.contents_manager)
717 contents_manager=self.contents_manager)
704 kls = import_item(self.cluster_manager_class)
718 kls = import_item(self.cluster_manager_class)
705 self.cluster_manager = kls(parent=self, log=self.log)
719 self.cluster_manager = kls(parent=self, log=self.log)
706 self.cluster_manager.update_profiles()
720 self.cluster_manager.update_profiles()
707
721
708 def init_logging(self):
722 def init_logging(self):
709 # This prevents double log messages because tornado use a root logger that
723 # This prevents double log messages because tornado use a root logger that
710 # self.log is a child of. The logging module dipatches log messages to a log
724 # self.log is a child of. The logging module dipatches log messages to a log
711 # and all of its ancenstors until propagate is set to False.
725 # and all of its ancenstors until propagate is set to False.
712 self.log.propagate = False
726 self.log.propagate = False
713
727
714 for log in app_log, access_log, gen_log:
728 for log in app_log, access_log, gen_log:
715 # consistent log output name (NotebookApp instead of tornado.access, etc.)
729 # consistent log output name (NotebookApp instead of tornado.access, etc.)
716 log.name = self.log.name
730 log.name = self.log.name
717 # hook up tornado 3's loggers to our app handlers
731 # hook up tornado 3's loggers to our app handlers
718 logger = logging.getLogger('tornado')
732 logger = logging.getLogger('tornado')
719 logger.propagate = True
733 logger.propagate = True
720 logger.parent = self.log
734 logger.parent = self.log
721 logger.setLevel(self.log.level)
735 logger.setLevel(self.log.level)
722
736
723 def init_webapp(self):
737 def init_webapp(self):
724 """initialize tornado webapp and httpserver"""
738 """initialize tornado webapp and httpserver"""
725 self.tornado_settings['allow_origin'] = self.allow_origin
739 self.tornado_settings['allow_origin'] = self.allow_origin
726 if self.allow_origin_pat:
740 if self.allow_origin_pat:
727 self.tornado_settings['allow_origin_pat'] = re.compile(self.allow_origin_pat)
741 self.tornado_settings['allow_origin_pat'] = re.compile(self.allow_origin_pat)
728 self.tornado_settings['allow_credentials'] = self.allow_credentials
742 self.tornado_settings['allow_credentials'] = self.allow_credentials
729
743
730 self.web_app = NotebookWebApplication(
744 self.web_app = NotebookWebApplication(
731 self, self.kernel_manager, self.contents_manager,
745 self, self.kernel_manager, self.contents_manager,
732 self.cluster_manager, self.session_manager, self.kernel_spec_manager,
746 self.cluster_manager, self.session_manager, self.kernel_spec_manager,
733 self.log, self.base_url, self.default_url, self.tornado_settings,
747 self.log, self.base_url, self.default_url, self.tornado_settings,
734 self.jinja_environment_options
748 self.jinja_environment_options
735 )
749 )
736 if self.certfile:
750 if self.certfile:
737 ssl_options = dict(certfile=self.certfile)
751 ssl_options = dict(certfile=self.certfile)
738 if self.keyfile:
752 if self.keyfile:
739 ssl_options['keyfile'] = self.keyfile
753 ssl_options['keyfile'] = self.keyfile
740 else:
754 else:
741 ssl_options = None
755 ssl_options = None
742 self.web_app.password = self.password
756 self.web_app.password = self.password
743 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options,
757 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options,
744 xheaders=self.trust_xheaders)
758 xheaders=self.trust_xheaders)
745 if not self.ip:
759 if not self.ip:
746 warning = "WARNING: The notebook server is listening on all IP addresses"
760 warning = "WARNING: The notebook server is listening on all IP addresses"
747 if ssl_options is None:
761 if ssl_options is None:
748 self.log.critical(warning + " and not using encryption. This "
762 self.log.critical(warning + " and not using encryption. This "
749 "is not recommended.")
763 "is not recommended.")
750 if not self.password:
764 if not self.password:
751 self.log.critical(warning + " and not using authentication. "
765 self.log.critical(warning + " and not using authentication. "
752 "This is highly insecure and not recommended.")
766 "This is highly insecure and not recommended.")
753 success = None
767 success = None
754 for port in random_ports(self.port, self.port_retries+1):
768 for port in random_ports(self.port, self.port_retries+1):
755 try:
769 try:
756 self.http_server.listen(port, self.ip)
770 self.http_server.listen(port, self.ip)
757 except socket.error as e:
771 except socket.error as e:
758 if e.errno == errno.EADDRINUSE:
772 if e.errno == errno.EADDRINUSE:
759 self.log.info('The port %i is already in use, trying another random port.' % port)
773 self.log.info('The port %i is already in use, trying another random port.' % port)
760 continue
774 continue
761 elif e.errno in (errno.EACCES, getattr(errno, 'WSAEACCES', errno.EACCES)):
775 elif e.errno in (errno.EACCES, getattr(errno, 'WSAEACCES', errno.EACCES)):
762 self.log.warn("Permission to listen on port %i denied" % port)
776 self.log.warn("Permission to listen on port %i denied" % port)
763 continue
777 continue
764 else:
778 else:
765 raise
779 raise
766 else:
780 else:
767 self.port = port
781 self.port = port
768 success = True
782 success = True
769 break
783 break
770 if not success:
784 if not success:
771 self.log.critical('ERROR: the notebook server could not be started because '
785 self.log.critical('ERROR: the notebook server could not be started because '
772 'no available port could be found.')
786 'no available port could be found.')
773 self.exit(1)
787 self.exit(1)
774
788
775 @property
789 @property
776 def display_url(self):
790 def display_url(self):
777 ip = self.ip if self.ip else '[all ip addresses on your system]'
791 ip = self.ip if self.ip else '[all ip addresses on your system]'
778 return self._url(ip)
792 return self._url(ip)
779
793
780 @property
794 @property
781 def connection_url(self):
795 def connection_url(self):
782 ip = self.ip if self.ip else 'localhost'
796 ip = self.ip if self.ip else 'localhost'
783 return self._url(ip)
797 return self._url(ip)
784
798
785 def _url(self, ip):
799 def _url(self, ip):
786 proto = 'https' if self.certfile else 'http'
800 proto = 'https' if self.certfile else 'http'
787 return "%s://%s:%i%s" % (proto, ip, self.port, self.base_url)
801 return "%s://%s:%i%s" % (proto, ip, self.port, self.base_url)
788
802
789 def init_terminals(self):
803 def init_terminals(self):
790 try:
804 try:
791 from .terminal import initialize
805 from .terminal import initialize
792 initialize(self.web_app)
806 initialize(self.web_app)
793 self.web_app.settings['terminals_available'] = True
807 self.web_app.settings['terminals_available'] = True
794 except ImportError as e:
808 except ImportError as e:
795 self.log.info("Terminals not available (error was %s)", e)
809 self.log.info("Terminals not available (error was %s)", e)
796
810
797 def init_signal(self):
811 def init_signal(self):
798 if not sys.platform.startswith('win'):
812 if not sys.platform.startswith('win'):
799 signal.signal(signal.SIGINT, self._handle_sigint)
813 signal.signal(signal.SIGINT, self._handle_sigint)
800 signal.signal(signal.SIGTERM, self._signal_stop)
814 signal.signal(signal.SIGTERM, self._signal_stop)
801 if hasattr(signal, 'SIGUSR1'):
815 if hasattr(signal, 'SIGUSR1'):
802 # Windows doesn't support SIGUSR1
816 # Windows doesn't support SIGUSR1
803 signal.signal(signal.SIGUSR1, self._signal_info)
817 signal.signal(signal.SIGUSR1, self._signal_info)
804 if hasattr(signal, 'SIGINFO'):
818 if hasattr(signal, 'SIGINFO'):
805 # only on BSD-based systems
819 # only on BSD-based systems
806 signal.signal(signal.SIGINFO, self._signal_info)
820 signal.signal(signal.SIGINFO, self._signal_info)
807
821
808 def _handle_sigint(self, sig, frame):
822 def _handle_sigint(self, sig, frame):
809 """SIGINT handler spawns confirmation dialog"""
823 """SIGINT handler spawns confirmation dialog"""
810 # register more forceful signal handler for ^C^C case
824 # register more forceful signal handler for ^C^C case
811 signal.signal(signal.SIGINT, self._signal_stop)
825 signal.signal(signal.SIGINT, self._signal_stop)
812 # request confirmation dialog in bg thread, to avoid
826 # request confirmation dialog in bg thread, to avoid
813 # blocking the App
827 # blocking the App
814 thread = threading.Thread(target=self._confirm_exit)
828 thread = threading.Thread(target=self._confirm_exit)
815 thread.daemon = True
829 thread.daemon = True
816 thread.start()
830 thread.start()
817
831
818 def _restore_sigint_handler(self):
832 def _restore_sigint_handler(self):
819 """callback for restoring original SIGINT handler"""
833 """callback for restoring original SIGINT handler"""
820 signal.signal(signal.SIGINT, self._handle_sigint)
834 signal.signal(signal.SIGINT, self._handle_sigint)
821
835
822 def _confirm_exit(self):
836 def _confirm_exit(self):
823 """confirm shutdown on ^C
837 """confirm shutdown on ^C
824
838
825 A second ^C, or answering 'y' within 5s will cause shutdown,
839 A second ^C, or answering 'y' within 5s will cause shutdown,
826 otherwise original SIGINT handler will be restored.
840 otherwise original SIGINT handler will be restored.
827
841
828 This doesn't work on Windows.
842 This doesn't work on Windows.
829 """
843 """
830 info = self.log.info
844 info = self.log.info
831 info('interrupted')
845 info('interrupted')
832 print(self.notebook_info())
846 print(self.notebook_info())
833 sys.stdout.write("Shutdown this notebook server (y/[n])? ")
847 sys.stdout.write("Shutdown this notebook server (y/[n])? ")
834 sys.stdout.flush()
848 sys.stdout.flush()
835 r,w,x = select.select([sys.stdin], [], [], 5)
849 r,w,x = select.select([sys.stdin], [], [], 5)
836 if r:
850 if r:
837 line = sys.stdin.readline()
851 line = sys.stdin.readline()
838 if line.lower().startswith('y') and 'n' not in line.lower():
852 if line.lower().startswith('y') and 'n' not in line.lower():
839 self.log.critical("Shutdown confirmed")
853 self.log.critical("Shutdown confirmed")
840 ioloop.IOLoop.instance().stop()
854 ioloop.IOLoop.instance().stop()
841 return
855 return
842 else:
856 else:
843 print("No answer for 5s:", end=' ')
857 print("No answer for 5s:", end=' ')
844 print("resuming operation...")
858 print("resuming operation...")
845 # no answer, or answer is no:
859 # no answer, or answer is no:
846 # set it back to original SIGINT handler
860 # set it back to original SIGINT handler
847 # use IOLoop.add_callback because signal.signal must be called
861 # use IOLoop.add_callback because signal.signal must be called
848 # from main thread
862 # from main thread
849 ioloop.IOLoop.instance().add_callback(self._restore_sigint_handler)
863 ioloop.IOLoop.instance().add_callback(self._restore_sigint_handler)
850
864
851 def _signal_stop(self, sig, frame):
865 def _signal_stop(self, sig, frame):
852 self.log.critical("received signal %s, stopping", sig)
866 self.log.critical("received signal %s, stopping", sig)
853 ioloop.IOLoop.instance().stop()
867 ioloop.IOLoop.instance().stop()
854
868
855 def _signal_info(self, sig, frame):
869 def _signal_info(self, sig, frame):
856 print(self.notebook_info())
870 print(self.notebook_info())
857
871
858 def init_components(self):
872 def init_components(self):
859 """Check the components submodule, and warn if it's unclean"""
873 """Check the components submodule, and warn if it's unclean"""
860 status = submodule.check_submodule_status()
874 status = submodule.check_submodule_status()
861 if status == 'missing':
875 if status == 'missing':
862 self.log.warn("components submodule missing, running `git submodule update`")
876 self.log.warn("components submodule missing, running `git submodule update`")
863 submodule.update_submodules(submodule.ipython_parent())
877 submodule.update_submodules(submodule.ipython_parent())
864 elif status == 'unclean':
878 elif status == 'unclean':
865 self.log.warn("components submodule unclean, you may see 404s on static/components")
879 self.log.warn("components submodule unclean, you may see 404s on static/components")
866 self.log.warn("run `setup.py submodule` or `git submodule update` to update")
880 self.log.warn("run `setup.py submodule` or `git submodule update` to update")
867
881
868 @catch_config_error
882 @catch_config_error
869 def initialize(self, argv=None):
883 def initialize(self, argv=None):
870 super(NotebookApp, self).initialize(argv)
884 super(NotebookApp, self).initialize(argv)
871 self.init_logging()
885 self.init_logging()
872 self.init_kernel_argv()
886 self.init_kernel_argv()
873 self.init_configurables()
887 self.init_configurables()
874 self.init_components()
888 self.init_components()
875 self.init_webapp()
889 self.init_webapp()
876 self.init_terminals()
890 self.init_terminals()
877 self.init_signal()
891 self.init_signal()
878
892
879 def cleanup_kernels(self):
893 def cleanup_kernels(self):
880 """Shutdown all kernels.
894 """Shutdown all kernels.
881
895
882 The kernels will shutdown themselves when this process no longer exists,
896 The kernels will shutdown themselves when this process no longer exists,
883 but explicit shutdown allows the KernelManagers to cleanup the connection files.
897 but explicit shutdown allows the KernelManagers to cleanup the connection files.
884 """
898 """
885 self.log.info('Shutting down kernels')
899 self.log.info('Shutting down kernels')
886 self.kernel_manager.shutdown_all()
900 self.kernel_manager.shutdown_all()
887
901
888 def notebook_info(self):
902 def notebook_info(self):
889 "Return the current working directory and the server url information"
903 "Return the current working directory and the server url information"
890 info = self.contents_manager.info_string() + "\n"
904 info = self.contents_manager.info_string() + "\n"
891 info += "%d active kernels \n" % len(self.kernel_manager._kernels)
905 info += "%d active kernels \n" % len(self.kernel_manager._kernels)
892 return info + "The IPython Notebook is running at: %s" % self.display_url
906 return info + "The IPython Notebook is running at: %s" % self.display_url
893
907
894 def server_info(self):
908 def server_info(self):
895 """Return a JSONable dict of information about this server."""
909 """Return a JSONable dict of information about this server."""
896 return {'url': self.connection_url,
910 return {'url': self.connection_url,
897 'hostname': self.ip if self.ip else 'localhost',
911 'hostname': self.ip if self.ip else 'localhost',
898 'port': self.port,
912 'port': self.port,
899 'secure': bool(self.certfile),
913 'secure': bool(self.certfile),
900 'base_url': self.base_url,
914 'base_url': self.base_url,
901 'notebook_dir': os.path.abspath(self.notebook_dir),
915 'notebook_dir': os.path.abspath(self.notebook_dir),
902 'pid': os.getpid()
916 'pid': os.getpid()
903 }
917 }
904
918
905 def write_server_info_file(self):
919 def write_server_info_file(self):
906 """Write the result of server_info() to the JSON file info_file."""
920 """Write the result of server_info() to the JSON file info_file."""
907 with open(self.info_file, 'w') as f:
921 with open(self.info_file, 'w') as f:
908 json.dump(self.server_info(), f, indent=2)
922 json.dump(self.server_info(), f, indent=2)
909
923
910 def remove_server_info_file(self):
924 def remove_server_info_file(self):
911 """Remove the nbserver-<pid>.json file created for this server.
925 """Remove the nbserver-<pid>.json file created for this server.
912
926
913 Ignores the error raised when the file has already been removed.
927 Ignores the error raised when the file has already been removed.
914 """
928 """
915 try:
929 try:
916 os.unlink(self.info_file)
930 os.unlink(self.info_file)
917 except OSError as e:
931 except OSError as e:
918 if e.errno != errno.ENOENT:
932 if e.errno != errno.ENOENT:
919 raise
933 raise
920
934
921 def start(self):
935 def start(self):
922 """ Start the IPython Notebook server app, after initialization
936 """ Start the IPython Notebook server app, after initialization
923
937
924 This method takes no arguments so all configuration and initialization
938 This method takes no arguments so all configuration and initialization
925 must be done prior to calling this method."""
939 must be done prior to calling this method."""
926 if self.subapp is not None:
940 if self.subapp is not None:
927 return self.subapp.start()
941 return self.subapp.start()
928
942
929 info = self.log.info
943 info = self.log.info
930 for line in self.notebook_info().split("\n"):
944 for line in self.notebook_info().split("\n"):
931 info(line)
945 info(line)
932 info("Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).")
946 info("Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).")
933
947
934 self.write_server_info_file()
948 self.write_server_info_file()
935
949
936 if self.open_browser or self.file_to_run:
950 if self.open_browser or self.file_to_run:
937 try:
951 try:
938 browser = webbrowser.get(self.browser or None)
952 browser = webbrowser.get(self.browser or None)
939 except webbrowser.Error as e:
953 except webbrowser.Error as e:
940 self.log.warn('No web browser found: %s.' % e)
954 self.log.warn('No web browser found: %s.' % e)
941 browser = None
955 browser = None
942
956
943 if self.file_to_run:
957 if self.file_to_run:
944 fullpath = os.path.join(self.notebook_dir, self.file_to_run)
958 fullpath = os.path.join(self.notebook_dir, self.file_to_run)
945 if not os.path.exists(fullpath):
959 if not os.path.exists(fullpath):
946 self.log.critical("%s does not exist" % fullpath)
960 self.log.critical("%s does not exist" % fullpath)
947 self.exit(1)
961 self.exit(1)
948
962
949 uri = url_path_join('notebooks', self.file_to_run)
963 uri = url_path_join('notebooks', self.file_to_run)
950 else:
964 else:
951 uri = 'tree'
965 uri = 'tree'
952 if browser:
966 if browser:
953 b = lambda : browser.open(url_path_join(self.connection_url, uri),
967 b = lambda : browser.open(url_path_join(self.connection_url, uri),
954 new=2)
968 new=2)
955 threading.Thread(target=b).start()
969 threading.Thread(target=b).start()
956 try:
970 try:
957 ioloop.IOLoop.instance().start()
971 ioloop.IOLoop.instance().start()
958 except KeyboardInterrupt:
972 except KeyboardInterrupt:
959 info("Interrupted...")
973 info("Interrupted...")
960 finally:
974 finally:
961 self.cleanup_kernels()
975 self.cleanup_kernels()
962 self.remove_server_info_file()
976 self.remove_server_info_file()
963
977
964
978
965 def list_running_servers(profile='default'):
979 def list_running_servers(profile='default'):
966 """Iterate over the server info files of running notebook servers.
980 """Iterate over the server info files of running notebook servers.
967
981
968 Given a profile name, find nbserver-* files in the security directory of
982 Given a profile name, find nbserver-* files in the security directory of
969 that profile, and yield dicts of their information, each one pertaining to
983 that profile, and yield dicts of their information, each one pertaining to
970 a currently running notebook server instance.
984 a currently running notebook server instance.
971 """
985 """
972 pd = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), name=profile)
986 pd = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), name=profile)
973 for file in os.listdir(pd.security_dir):
987 for file in os.listdir(pd.security_dir):
974 if file.startswith('nbserver-'):
988 if file.startswith('nbserver-'):
975 with io.open(os.path.join(pd.security_dir, file), encoding='utf-8') as f:
989 with io.open(os.path.join(pd.security_dir, file), encoding='utf-8') as f:
976 info = json.load(f)
990 info = json.load(f)
977
991
978 # Simple check whether that process is really still running
992 # Simple check whether that process is really still running
979 if check_pid(info['pid']):
993 if check_pid(info['pid']):
980 yield info
994 yield info
981 else:
995 else:
982 # If the process has died, try to delete its info file
996 # If the process has died, try to delete its info file
983 try:
997 try:
984 os.unlink(file)
998 os.unlink(file)
985 except OSError:
999 except OSError:
986 pass # TODO: This should warn or log or something
1000 pass # TODO: This should warn or log or something
987 #-----------------------------------------------------------------------------
1001 #-----------------------------------------------------------------------------
988 # Main entry point
1002 # Main entry point
989 #-----------------------------------------------------------------------------
1003 #-----------------------------------------------------------------------------
990
1004
991 launch_new_instance = NotebookApp.launch_instance
1005 launch_new_instance = NotebookApp.launch_instance
992
1006
General Comments 0
You need to be logged in to leave comments. Login now