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