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