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