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