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