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