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