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