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