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