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