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