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