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