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