##// END OF EJS Templates
restore websocket_url configurable...
MinRK -
Show More
@@ -1,422 +1,427
1 1 """Base Tornado handlers for the notebook."""
2 2
3 3 # Copyright (c) IPython Development Team.
4 4 # Distributed under the terms of the Modified BSD License.
5 5
6 6 import functools
7 7 import json
8 8 import logging
9 9 import os
10 10 import re
11 11 import sys
12 12 import traceback
13 13 try:
14 14 # py3
15 15 from http.client import responses
16 16 except ImportError:
17 17 from httplib import responses
18 18
19 19 from jinja2 import TemplateNotFound
20 20 from tornado import web
21 21
22 22 try:
23 23 from tornado.log import app_log
24 24 except ImportError:
25 25 app_log = logging.getLogger()
26 26
27 27 from IPython.config import Application
28 28 from IPython.utils.path import filefind
29 29 from IPython.utils.py3compat import string_types
30 30 from IPython.html.utils import is_hidden
31 31
32 32 #-----------------------------------------------------------------------------
33 33 # Top-level handlers
34 34 #-----------------------------------------------------------------------------
35 35 non_alphanum = re.compile(r'[^A-Za-z0-9]')
36 36
37 37 class AuthenticatedHandler(web.RequestHandler):
38 38 """A RequestHandler with an authenticated user."""
39 39
40 40 def set_default_headers(self):
41 41 headers = self.settings.get('headers', {})
42 42
43 43 if "X-Frame-Options" not in headers:
44 44 headers["X-Frame-Options"] = "SAMEORIGIN"
45 45
46 46 for header_name,value in headers.items() :
47 47 try:
48 48 self.set_header(header_name, value)
49 49 except Exception:
50 50 # tornado raise Exception (not a subclass)
51 51 # if method is unsupported (websocket and Access-Control-Allow-Origin
52 52 # for example, so just ignore)
53 53 pass
54 54
55 55 def clear_login_cookie(self):
56 56 self.clear_cookie(self.cookie_name)
57 57
58 58 def get_current_user(self):
59 59 user_id = self.get_secure_cookie(self.cookie_name)
60 60 # For now the user_id should not return empty, but it could eventually
61 61 if user_id == '':
62 62 user_id = 'anonymous'
63 63 if user_id is None:
64 64 # prevent extra Invalid cookie sig warnings:
65 65 self.clear_login_cookie()
66 66 if not self.login_available:
67 67 user_id = 'anonymous'
68 68 return user_id
69 69
70 70 @property
71 71 def cookie_name(self):
72 72 default_cookie_name = non_alphanum.sub('-', 'username-{}'.format(
73 73 self.request.host
74 74 ))
75 75 return self.settings.get('cookie_name', default_cookie_name)
76 76
77 77 @property
78 78 def password(self):
79 79 """our password"""
80 80 return self.settings.get('password', '')
81 81
82 82 @property
83 83 def logged_in(self):
84 84 """Is a user currently logged in?
85 85
86 86 """
87 87 user = self.get_current_user()
88 88 return (user and not user == 'anonymous')
89 89
90 90 @property
91 91 def login_available(self):
92 92 """May a user proceed to log in?
93 93
94 94 This returns True if login capability is available, irrespective of
95 95 whether the user is already logged in or not.
96 96
97 97 """
98 98 return bool(self.settings.get('password', ''))
99 99
100 100
101 101 class IPythonHandler(AuthenticatedHandler):
102 102 """IPython-specific extensions to authenticated handling
103 103
104 104 Mostly property shortcuts to IPython-specific settings.
105 105 """
106 106
107 107 @property
108 108 def config(self):
109 109 return self.settings.get('config', None)
110 110
111 111 @property
112 112 def log(self):
113 113 """use the IPython log by default, falling back on tornado's logger"""
114 114 if Application.initialized():
115 115 return Application.instance().log
116 116 else:
117 117 return app_log
118 118
119 119 #---------------------------------------------------------------
120 120 # URLs
121 121 #---------------------------------------------------------------
122 122
123 123 @property
124 124 def mathjax_url(self):
125 125 return self.settings.get('mathjax_url', '')
126 126
127 127 @property
128 128 def base_url(self):
129 129 return self.settings.get('base_url', '/')
130 130
131 @property
132 def ws_url(self):
133 return self.settings.get('websocket_url', '')
134
131 135 #---------------------------------------------------------------
132 136 # Manager objects
133 137 #---------------------------------------------------------------
134 138
135 139 @property
136 140 def kernel_manager(self):
137 141 return self.settings['kernel_manager']
138 142
139 143 @property
140 144 def notebook_manager(self):
141 145 return self.settings['notebook_manager']
142 146
143 147 @property
144 148 def cluster_manager(self):
145 149 return self.settings['cluster_manager']
146 150
147 151 @property
148 152 def session_manager(self):
149 153 return self.settings['session_manager']
150 154
151 155 @property
152 156 def kernel_spec_manager(self):
153 157 return self.settings['kernel_spec_manager']
154 158
155 159 @property
156 160 def project_dir(self):
157 161 return self.notebook_manager.notebook_dir
158 162
159 163 #---------------------------------------------------------------
160 164 # CORS
161 165 #---------------------------------------------------------------
162 166
163 167 @property
164 168 def allow_origin(self):
165 169 """Normal Access-Control-Allow-Origin"""
166 170 return self.settings.get('allow_origin', '')
167 171
168 172 @property
169 173 def allow_origin_pat(self):
170 174 """Regular expression version of allow_origin"""
171 175 return self.settings.get('allow_origin_pat', None)
172 176
173 177 @property
174 178 def allow_credentials(self):
175 179 """Whether to set Access-Control-Allow-Credentials"""
176 180 return self.settings.get('allow_credentials', False)
177 181
178 182 def set_default_headers(self):
179 183 """Add CORS headers, if defined"""
180 184 super(IPythonHandler, self).set_default_headers()
181 185 if self.allow_origin:
182 186 self.set_header("Access-Control-Allow-Origin", self.allow_origin)
183 187 elif self.allow_origin_pat:
184 188 origin = self.get_origin()
185 189 if origin and self.allow_origin_pat.match(origin):
186 190 self.set_header("Access-Control-Allow-Origin", origin)
187 191 if self.allow_credentials:
188 192 self.set_header("Access-Control-Allow-Credentials", 'true')
189 193
190 194 def get_origin(self):
191 195 # Handle WebSocket Origin naming convention differences
192 196 # The difference between version 8 and 13 is that in 8 the
193 197 # client sends a "Sec-Websocket-Origin" header and in 13 it's
194 198 # simply "Origin".
195 199 if "Origin" in self.request.headers:
196 200 origin = self.request.headers.get("Origin")
197 201 else:
198 202 origin = self.request.headers.get("Sec-Websocket-Origin", None)
199 203 return origin
200 204
201 205 #---------------------------------------------------------------
202 206 # template rendering
203 207 #---------------------------------------------------------------
204 208
205 209 def get_template(self, name):
206 210 """Return the jinja template object for a given name"""
207 211 return self.settings['jinja2_env'].get_template(name)
208 212
209 213 def render_template(self, name, **ns):
210 214 ns.update(self.template_namespace)
211 215 template = self.get_template(name)
212 216 return template.render(**ns)
213 217
214 218 @property
215 219 def template_namespace(self):
216 220 return dict(
217 221 base_url=self.base_url,
222 ws_url=self.ws_url,
218 223 logged_in=self.logged_in,
219 224 login_available=self.login_available,
220 225 static_url=self.static_url,
221 226 )
222 227
223 228 def get_json_body(self):
224 229 """Return the body of the request as JSON data."""
225 230 if not self.request.body:
226 231 return None
227 232 # Do we need to call body.decode('utf-8') here?
228 233 body = self.request.body.strip().decode(u'utf-8')
229 234 try:
230 235 model = json.loads(body)
231 236 except Exception:
232 237 self.log.debug("Bad JSON: %r", body)
233 238 self.log.error("Couldn't parse JSON", exc_info=True)
234 239 raise web.HTTPError(400, u'Invalid JSON in body of request')
235 240 return model
236 241
237 242 def get_error_html(self, status_code, **kwargs):
238 243 """render custom error pages"""
239 244 exception = kwargs.get('exception')
240 245 message = ''
241 246 status_message = responses.get(status_code, 'Unknown HTTP Error')
242 247 if exception:
243 248 # get the custom message, if defined
244 249 try:
245 250 message = exception.log_message % exception.args
246 251 except Exception:
247 252 pass
248 253
249 254 # construct the custom reason, if defined
250 255 reason = getattr(exception, 'reason', '')
251 256 if reason:
252 257 status_message = reason
253 258
254 259 # build template namespace
255 260 ns = dict(
256 261 status_code=status_code,
257 262 status_message=status_message,
258 263 message=message,
259 264 exception=exception,
260 265 )
261 266
262 267 # render the template
263 268 try:
264 269 html = self.render_template('%s.html' % status_code, **ns)
265 270 except TemplateNotFound:
266 271 self.log.debug("No template for %d", status_code)
267 272 html = self.render_template('error.html', **ns)
268 273 return html
269 274
270 275
271 276 class Template404(IPythonHandler):
272 277 """Render our 404 template"""
273 278 def prepare(self):
274 279 raise web.HTTPError(404)
275 280
276 281
277 282 class AuthenticatedFileHandler(IPythonHandler, web.StaticFileHandler):
278 283 """static files should only be accessible when logged in"""
279 284
280 285 @web.authenticated
281 286 def get(self, path):
282 287 if os.path.splitext(path)[1] == '.ipynb':
283 288 name = os.path.basename(path)
284 289 self.set_header('Content-Type', 'application/json')
285 290 self.set_header('Content-Disposition','attachment; filename="%s"' % name)
286 291
287 292 return web.StaticFileHandler.get(self, path)
288 293
289 294 def compute_etag(self):
290 295 return None
291 296
292 297 def validate_absolute_path(self, root, absolute_path):
293 298 """Validate and return the absolute path.
294 299
295 300 Requires tornado 3.1
296 301
297 302 Adding to tornado's own handling, forbids the serving of hidden files.
298 303 """
299 304 abs_path = super(AuthenticatedFileHandler, self).validate_absolute_path(root, absolute_path)
300 305 abs_root = os.path.abspath(root)
301 306 if is_hidden(abs_path, abs_root):
302 307 self.log.info("Refusing to serve hidden file, via 404 Error")
303 308 raise web.HTTPError(404)
304 309 return abs_path
305 310
306 311
307 312 def json_errors(method):
308 313 """Decorate methods with this to return GitHub style JSON errors.
309 314
310 315 This should be used on any JSON API on any handler method that can raise HTTPErrors.
311 316
312 317 This will grab the latest HTTPError exception using sys.exc_info
313 318 and then:
314 319
315 320 1. Set the HTTP status code based on the HTTPError
316 321 2. Create and return a JSON body with a message field describing
317 322 the error in a human readable form.
318 323 """
319 324 @functools.wraps(method)
320 325 def wrapper(self, *args, **kwargs):
321 326 try:
322 327 result = method(self, *args, **kwargs)
323 328 except web.HTTPError as e:
324 329 status = e.status_code
325 330 message = e.log_message
326 331 self.log.warn(message)
327 332 self.set_status(e.status_code)
328 333 self.finish(json.dumps(dict(message=message)))
329 334 except Exception:
330 335 self.log.error("Unhandled error in API request", exc_info=True)
331 336 status = 500
332 337 message = "Unknown server error"
333 338 t, value, tb = sys.exc_info()
334 339 self.set_status(status)
335 340 tb_text = ''.join(traceback.format_exception(t, value, tb))
336 341 reply = dict(message=message, traceback=tb_text)
337 342 self.finish(json.dumps(reply))
338 343 else:
339 344 return result
340 345 return wrapper
341 346
342 347
343 348
344 349 #-----------------------------------------------------------------------------
345 350 # File handler
346 351 #-----------------------------------------------------------------------------
347 352
348 353 # to minimize subclass changes:
349 354 HTTPError = web.HTTPError
350 355
351 356 class FileFindHandler(web.StaticFileHandler):
352 357 """subclass of StaticFileHandler for serving files from a search path"""
353 358
354 359 # cache search results, don't search for files more than once
355 360 _static_paths = {}
356 361
357 362 def initialize(self, path, default_filename=None):
358 363 if isinstance(path, string_types):
359 364 path = [path]
360 365
361 366 self.root = tuple(
362 367 os.path.abspath(os.path.expanduser(p)) + os.sep for p in path
363 368 )
364 369 self.default_filename = default_filename
365 370
366 371 def compute_etag(self):
367 372 return None
368 373
369 374 @classmethod
370 375 def get_absolute_path(cls, roots, path):
371 376 """locate a file to serve on our static file search path"""
372 377 with cls._lock:
373 378 if path in cls._static_paths:
374 379 return cls._static_paths[path]
375 380 try:
376 381 abspath = os.path.abspath(filefind(path, roots))
377 382 except IOError:
378 383 # IOError means not found
379 384 return ''
380 385
381 386 cls._static_paths[path] = abspath
382 387 return abspath
383 388
384 389 def validate_absolute_path(self, root, absolute_path):
385 390 """check if the file should be served (raises 404, 403, etc.)"""
386 391 if absolute_path == '':
387 392 raise web.HTTPError(404)
388 393
389 394 for root in self.root:
390 395 if (absolute_path + os.sep).startswith(root):
391 396 break
392 397
393 398 return super(FileFindHandler, self).validate_absolute_path(root, absolute_path)
394 399
395 400
396 401 class TrailingSlashHandler(web.RequestHandler):
397 402 """Simple redirect handler that strips trailing slashes
398 403
399 404 This should be the first, highest priority handler.
400 405 """
401 406
402 407 SUPPORTED_METHODS = ['GET']
403 408
404 409 def get(self):
405 410 self.redirect(self.request.uri.rstrip('/'))
406 411
407 412 #-----------------------------------------------------------------------------
408 413 # URL pattern fragments for re-use
409 414 #-----------------------------------------------------------------------------
410 415
411 416 path_regex = r"(?P<path>(?:/.*)*)"
412 417 notebook_name_regex = r"(?P<name>[^/]+\.ipynb)"
413 418 notebook_path_regex = "%s/%s" % (path_regex, notebook_name_regex)
414 419
415 420 #-----------------------------------------------------------------------------
416 421 # URL to handler mappings
417 422 #-----------------------------------------------------------------------------
418 423
419 424
420 425 default_handlers = [
421 426 (r".*/", TrailingSlashHandler)
422 427 ]
@@ -1,935 +1,943
1 1 # coding: utf-8
2 2 """A tornado based IPython notebook server."""
3 3
4 4 # Copyright (c) IPython Development Team.
5 5 # Distributed under the terms of the Modified BSD License.
6 6
7 7 from __future__ import print_function
8 8
9 9 import base64
10 10 import errno
11 11 import io
12 12 import json
13 13 import logging
14 14 import os
15 15 import random
16 16 import re
17 17 import select
18 18 import signal
19 19 import socket
20 20 import sys
21 21 import threading
22 22 import time
23 23 import webbrowser
24 24
25 25
26 26 # check for pyzmq 2.1.11
27 27 from IPython.utils.zmqrelated import check_for_zmq
28 28 check_for_zmq('2.1.11', 'IPython.html')
29 29
30 30 from jinja2 import Environment, FileSystemLoader
31 31
32 32 # Install the pyzmq ioloop. This has to be done before anything else from
33 33 # tornado is imported.
34 34 from zmq.eventloop import ioloop
35 35 ioloop.install()
36 36
37 37 # check for tornado 3.1.0
38 38 msg = "The IPython Notebook requires tornado >= 3.1.0"
39 39 try:
40 40 import tornado
41 41 except ImportError:
42 42 raise ImportError(msg)
43 43 try:
44 44 version_info = tornado.version_info
45 45 except AttributeError:
46 46 raise ImportError(msg + ", but you have < 1.1.0")
47 47 if version_info < (3,1,0):
48 48 raise ImportError(msg + ", but you have %s" % tornado.version)
49 49
50 50 from tornado import httpserver
51 51 from tornado import web
52 52 from tornado.log import LogFormatter
53 53
54 54 from IPython.html import DEFAULT_STATIC_FILES_PATH
55 55 from .base.handlers import Template404
56 56 from .log import log_request
57 57 from .services.kernels.kernelmanager import MappingKernelManager
58 58 from .services.notebooks.nbmanager import NotebookManager
59 59 from .services.notebooks.filenbmanager import FileNotebookManager
60 60 from .services.clusters.clustermanager import ClusterManager
61 61 from .services.sessions.sessionmanager import SessionManager
62 62
63 63 from .base.handlers import AuthenticatedFileHandler, FileFindHandler
64 64
65 65 from IPython.config import Config
66 66 from IPython.config.application import catch_config_error, boolean_flag
67 67 from IPython.core.application import (
68 68 BaseIPythonApplication, base_flags, base_aliases,
69 69 )
70 70 from IPython.core.profiledir import ProfileDir
71 71 from IPython.kernel import KernelManager
72 72 from IPython.kernel.kernelspec import KernelSpecManager
73 73 from IPython.kernel.zmq.session import default_secure, Session
74 74 from IPython.nbformat.sign import NotebookNotary
75 75 from IPython.utils.importstring import import_item
76 76 from IPython.utils import submodule
77 77 from IPython.utils.process import check_pid
78 78 from IPython.utils.traitlets import (
79 79 Dict, Unicode, Integer, List, Bool, Bytes, Instance,
80 80 DottedObjectName, TraitError,
81 81 )
82 82 from IPython.utils import py3compat
83 83 from IPython.utils.path import filefind, get_ipython_dir
84 84
85 85 from .utils import url_path_join
86 86
87 87 #-----------------------------------------------------------------------------
88 88 # Module globals
89 89 #-----------------------------------------------------------------------------
90 90
91 91 _examples = """
92 92 ipython notebook # start the notebook
93 93 ipython notebook --profile=sympy # use the sympy profile
94 94 ipython notebook --certfile=mycert.pem # use SSL/TLS certificate
95 95 """
96 96
97 97 #-----------------------------------------------------------------------------
98 98 # Helper functions
99 99 #-----------------------------------------------------------------------------
100 100
101 101 def random_ports(port, n):
102 102 """Generate a list of n random ports near the given port.
103 103
104 104 The first 5 ports will be sequential, and the remaining n-5 will be
105 105 randomly selected in the range [port-2*n, port+2*n].
106 106 """
107 107 for i in range(min(5, n)):
108 108 yield port + i
109 109 for i in range(n-5):
110 110 yield max(1, port + random.randint(-2*n, 2*n))
111 111
112 112 def load_handlers(name):
113 113 """Load the (URL pattern, handler) tuples for each component."""
114 114 name = 'IPython.html.' + name
115 115 mod = __import__(name, fromlist=['default_handlers'])
116 116 return mod.default_handlers
117 117
118 118 #-----------------------------------------------------------------------------
119 119 # The Tornado web application
120 120 #-----------------------------------------------------------------------------
121 121
122 122 class NotebookWebApplication(web.Application):
123 123
124 124 def __init__(self, ipython_app, kernel_manager, notebook_manager,
125 125 cluster_manager, session_manager, kernel_spec_manager, log,
126 126 base_url, settings_overrides, jinja_env_options):
127 127
128 128 settings = self.init_settings(
129 129 ipython_app, kernel_manager, notebook_manager, cluster_manager,
130 130 session_manager, kernel_spec_manager, log, base_url,
131 131 settings_overrides, jinja_env_options)
132 132 handlers = self.init_handlers(settings)
133 133
134 134 super(NotebookWebApplication, self).__init__(handlers, **settings)
135 135
136 136 def init_settings(self, ipython_app, kernel_manager, notebook_manager,
137 137 cluster_manager, session_manager, kernel_spec_manager,
138 138 log, base_url, settings_overrides,
139 139 jinja_env_options=None):
140 140 # Python < 2.6.5 doesn't accept unicode keys in f(**kwargs), and
141 141 # base_url will always be unicode, which will in turn
142 142 # make the patterns unicode, and ultimately result in unicode
143 143 # keys in kwargs to handler._execute(**kwargs) in tornado.
144 144 # This enforces that base_url be ascii in that situation.
145 145 #
146 146 # Note that the URLs these patterns check against are escaped,
147 147 # and thus guaranteed to be ASCII: 'héllo' is really 'h%C3%A9llo'.
148 148 base_url = py3compat.unicode_to_str(base_url, 'ascii')
149 149 template_path = settings_overrides.get("template_path", os.path.join(os.path.dirname(__file__), "templates"))
150 150 jenv_opt = jinja_env_options if jinja_env_options else {}
151 151 env = Environment(loader=FileSystemLoader(template_path),**jenv_opt )
152 152 settings = dict(
153 153 # basics
154 154 log_function=log_request,
155 155 base_url=base_url,
156 156 template_path=template_path,
157 157 static_path=ipython_app.static_file_path,
158 158 static_handler_class = FileFindHandler,
159 159 static_url_prefix = url_path_join(base_url,'/static/'),
160 160
161 161 # authentication
162 162 cookie_secret=ipython_app.cookie_secret,
163 163 login_url=url_path_join(base_url,'/login'),
164 164 password=ipython_app.password,
165 165
166 166 # managers
167 167 kernel_manager=kernel_manager,
168 168 notebook_manager=notebook_manager,
169 169 cluster_manager=cluster_manager,
170 170 session_manager=session_manager,
171 171 kernel_spec_manager=kernel_spec_manager,
172 172
173 173 # IPython stuff
174 174 nbextensions_path = ipython_app.nbextensions_path,
175 websocket_url=ipython_app.websocket_url,
175 176 mathjax_url=ipython_app.mathjax_url,
176 177 config=ipython_app.config,
177 178 jinja2_env=env,
178 179 )
179 180
180 181 # allow custom overrides for the tornado web app.
181 182 settings.update(settings_overrides)
182 183 return settings
183 184
184 185 def init_handlers(self, settings):
185 186 # Load the (URL pattern, handler) tuples for each component.
186 187 handlers = []
187 188 handlers.extend(load_handlers('base.handlers'))
188 189 handlers.extend(load_handlers('tree.handlers'))
189 190 handlers.extend(load_handlers('auth.login'))
190 191 handlers.extend(load_handlers('auth.logout'))
191 192 handlers.extend(load_handlers('notebook.handlers'))
192 193 handlers.extend(load_handlers('nbconvert.handlers'))
193 194 handlers.extend(load_handlers('kernelspecs.handlers'))
194 195 handlers.extend(load_handlers('services.kernels.handlers'))
195 196 handlers.extend(load_handlers('services.notebooks.handlers'))
196 197 handlers.extend(load_handlers('services.clusters.handlers'))
197 198 handlers.extend(load_handlers('services.sessions.handlers'))
198 199 handlers.extend(load_handlers('services.nbconvert.handlers'))
199 200 handlers.extend(load_handlers('services.kernelspecs.handlers'))
200 201 # FIXME: /files/ should be handled by the Contents service when it exists
201 202 nbm = settings['notebook_manager']
202 203 if hasattr(nbm, 'notebook_dir'):
203 204 handlers.extend([
204 205 (r"/files/(.*)", AuthenticatedFileHandler, {'path' : nbm.notebook_dir}),
205 206 (r"/nbextensions/(.*)", FileFindHandler, {'path' : settings['nbextensions_path']}),
206 207 ])
207 208 # prepend base_url onto the patterns that we match
208 209 new_handlers = []
209 210 for handler in handlers:
210 211 pattern = url_path_join(settings['base_url'], handler[0])
211 212 new_handler = tuple([pattern] + list(handler[1:]))
212 213 new_handlers.append(new_handler)
213 214 # add 404 on the end, which will catch everything that falls through
214 215 new_handlers.append((r'(.*)', Template404))
215 216 return new_handlers
216 217
217 218
218 219 class NbserverListApp(BaseIPythonApplication):
219 220
220 221 description="List currently running notebook servers in this profile."
221 222
222 223 flags = dict(
223 224 json=({'NbserverListApp': {'json': True}},
224 225 "Produce machine-readable JSON output."),
225 226 )
226 227
227 228 json = Bool(False, config=True,
228 229 help="If True, each line of output will be a JSON object with the "
229 230 "details from the server info file.")
230 231
231 232 def start(self):
232 233 if not self.json:
233 234 print("Currently running servers:")
234 235 for serverinfo in list_running_servers(self.profile):
235 236 if self.json:
236 237 print(json.dumps(serverinfo))
237 238 else:
238 239 print(serverinfo['url'], "::", serverinfo['notebook_dir'])
239 240
240 241 #-----------------------------------------------------------------------------
241 242 # Aliases and Flags
242 243 #-----------------------------------------------------------------------------
243 244
244 245 flags = dict(base_flags)
245 246 flags['no-browser']=(
246 247 {'NotebookApp' : {'open_browser' : False}},
247 248 "Don't open the notebook in a browser after startup."
248 249 )
249 250 flags['pylab']=(
250 251 {'NotebookApp' : {'pylab' : 'warn'}},
251 252 "DISABLED: use %pylab or %matplotlib in the notebook to enable matplotlib."
252 253 )
253 254 flags['no-mathjax']=(
254 255 {'NotebookApp' : {'enable_mathjax' : False}},
255 256 """Disable MathJax
256 257
257 258 MathJax is the javascript library IPython uses to render math/LaTeX. It is
258 259 very large, so you may want to disable it if you have a slow internet
259 260 connection, or for offline use of the notebook.
260 261
261 262 When disabled, equations etc. will appear as their untransformed TeX source.
262 263 """
263 264 )
264 265
265 266 # Add notebook manager flags
266 267 flags.update(boolean_flag('script', 'FileNotebookManager.save_script',
267 268 'Auto-save a .py script everytime the .ipynb notebook is saved',
268 269 'Do not auto-save .py scripts for every notebook'))
269 270
270 271 aliases = dict(base_aliases)
271 272
272 273 aliases.update({
273 274 'ip': 'NotebookApp.ip',
274 275 'port': 'NotebookApp.port',
275 276 'port-retries': 'NotebookApp.port_retries',
276 277 'transport': 'KernelManager.transport',
277 278 'keyfile': 'NotebookApp.keyfile',
278 279 'certfile': 'NotebookApp.certfile',
279 280 'notebook-dir': 'NotebookApp.notebook_dir',
280 281 'browser': 'NotebookApp.browser',
281 282 'pylab': 'NotebookApp.pylab',
282 283 })
283 284
284 285 #-----------------------------------------------------------------------------
285 286 # NotebookApp
286 287 #-----------------------------------------------------------------------------
287 288
288 289 class NotebookApp(BaseIPythonApplication):
289 290
290 291 name = 'ipython-notebook'
291 292
292 293 description = """
293 294 The IPython HTML Notebook.
294 295
295 296 This launches a Tornado based HTML Notebook Server that serves up an
296 297 HTML5/Javascript Notebook client.
297 298 """
298 299 examples = _examples
299 300 aliases = aliases
300 301 flags = flags
301 302
302 303 classes = [
303 304 KernelManager, ProfileDir, Session, MappingKernelManager,
304 305 NotebookManager, FileNotebookManager, NotebookNotary,
305 306 ]
306 307 flags = Dict(flags)
307 308 aliases = Dict(aliases)
308 309
309 310 subcommands = dict(
310 311 list=(NbserverListApp, NbserverListApp.description.splitlines()[0]),
311 312 )
312 313
313 314 kernel_argv = List(Unicode)
314 315
315 316 _log_formatter_cls = LogFormatter
316 317
317 318 def _log_level_default(self):
318 319 return logging.INFO
319 320
320 321 def _log_datefmt_default(self):
321 322 """Exclude date from default date format"""
322 323 return "%H:%M:%S"
323 324
324 325 def _log_format_default(self):
325 326 """override default log format to include time"""
326 327 return u"%(color)s[%(levelname)1.1s %(asctime)s.%(msecs).03d %(name)s]%(end_color)s %(message)s"
327 328
328 329 # create requested profiles by default, if they don't exist:
329 330 auto_create = Bool(True)
330 331
331 332 # file to be opened in the notebook server
332 333 file_to_run = Unicode('', config=True)
333 334 def _file_to_run_changed(self, name, old, new):
334 335 path, base = os.path.split(new)
335 336 if path:
336 337 self.file_to_run = base
337 338 self.notebook_dir = path
338 339
339 340 # Network related information
340 341
341 342 allow_origin = Unicode('', config=True,
342 343 help="""Set the Access-Control-Allow-Origin header
343 344
344 345 Use '*' to allow any origin to access your server.
345 346
346 347 Takes precedence over allow_origin_pat.
347 348 """
348 349 )
349 350
350 351 allow_origin_pat = Unicode('', config=True,
351 352 help="""Use a regular expression for the Access-Control-Allow-Origin header
352 353
353 354 Requests from an origin matching the expression will get replies with:
354 355
355 356 Access-Control-Allow-Origin: origin
356 357
357 358 where `origin` is the origin of the request.
358 359
359 360 Ignored if allow_origin is set.
360 361 """
361 362 )
362 363
363 364 allow_credentials = Bool(False, config=True,
364 365 help="Set the Access-Control-Allow-Credentials: true header"
365 366 )
366 367
367 368 ip = Unicode('localhost', config=True,
368 369 help="The IP address the notebook server will listen on."
369 370 )
370 371
371 372 def _ip_changed(self, name, old, new):
372 373 if new == u'*': self.ip = u''
373 374
374 375 port = Integer(8888, config=True,
375 376 help="The port the notebook server will listen on."
376 377 )
377 378 port_retries = Integer(50, config=True,
378 379 help="The number of additional ports to try if the specified port is not available."
379 380 )
380 381
381 382 certfile = Unicode(u'', config=True,
382 383 help="""The full path to an SSL/TLS certificate file."""
383 384 )
384 385
385 386 keyfile = Unicode(u'', config=True,
386 387 help="""The full path to a private key file for usage with SSL/TLS."""
387 388 )
388 389
389 390 cookie_secret_file = Unicode(config=True,
390 391 help="""The file where the cookie secret is stored."""
391 392 )
392 393 def _cookie_secret_file_default(self):
393 394 if self.profile_dir is None:
394 395 return ''
395 396 return os.path.join(self.profile_dir.security_dir, 'notebook_cookie_secret')
396 397
397 398 cookie_secret = Bytes(b'', config=True,
398 399 help="""The random bytes used to secure cookies.
399 400 By default this is a new random number every time you start the Notebook.
400 401 Set it to a value in a config file to enable logins to persist across server sessions.
401 402
402 403 Note: Cookie secrets should be kept private, do not share config files with
403 404 cookie_secret stored in plaintext (you can read the value from a file).
404 405 """
405 406 )
406 407 def _cookie_secret_default(self):
407 408 if os.path.exists(self.cookie_secret_file):
408 409 with io.open(self.cookie_secret_file, 'rb') as f:
409 410 return f.read()
410 411 else:
411 412 secret = base64.encodestring(os.urandom(1024))
412 413 self._write_cookie_secret_file(secret)
413 414 return secret
414 415
415 416 def _write_cookie_secret_file(self, secret):
416 417 """write my secret to my secret_file"""
417 418 self.log.info("Writing notebook server cookie secret to %s", self.cookie_secret_file)
418 419 with io.open(self.cookie_secret_file, 'wb') as f:
419 420 f.write(secret)
420 421 try:
421 422 os.chmod(self.cookie_secret_file, 0o600)
422 423 except OSError:
423 424 self.log.warn(
424 425 "Could not set permissions on %s",
425 426 self.cookie_secret_file
426 427 )
427 428
428 429 password = Unicode(u'', config=True,
429 430 help="""Hashed password to use for web authentication.
430 431
431 432 To generate, type in a python/IPython shell:
432 433
433 434 from IPython.lib import passwd; passwd()
434 435
435 436 The string should be of the form type:salt:hashed-password.
436 437 """
437 438 )
438 439
439 440 open_browser = Bool(True, config=True,
440 441 help="""Whether to open in a browser after starting.
441 442 The specific browser used is platform dependent and
442 443 determined by the python standard library `webbrowser`
443 444 module, unless it is overridden using the --browser
444 445 (NotebookApp.browser) configuration option.
445 446 """)
446 447
447 448 browser = Unicode(u'', config=True,
448 449 help="""Specify what command to use to invoke a web
449 450 browser when opening the notebook. If not specified, the
450 451 default browser will be determined by the `webbrowser`
451 452 standard library module, which allows setting of the
452 453 BROWSER environment variable to override it.
453 454 """)
454 455
455 456 webapp_settings = Dict(config=True,
456 457 help="Supply overrides for the tornado.web.Application that the "
457 458 "IPython notebook uses.")
458 459
459 460 jinja_environment_options = Dict(config=True,
460 461 help="Supply extra arguments that will be passed to Jinja environment.")
461 462
462 463
463 464 enable_mathjax = Bool(True, config=True,
464 465 help="""Whether to enable MathJax for typesetting math/TeX
465 466
466 467 MathJax is the javascript library IPython uses to render math/LaTeX. It is
467 468 very large, so you may want to disable it if you have a slow internet
468 469 connection, or for offline use of the notebook.
469 470
470 471 When disabled, equations etc. will appear as their untransformed TeX source.
471 472 """
472 473 )
473 474 def _enable_mathjax_changed(self, name, old, new):
474 475 """set mathjax url to empty if mathjax is disabled"""
475 476 if not new:
476 477 self.mathjax_url = u''
477 478
478 479 base_url = Unicode('/', config=True,
479 480 help='''The base URL for the notebook server.
480 481
481 482 Leading and trailing slashes can be omitted,
482 483 and will automatically be added.
483 484 ''')
484 485 def _base_url_changed(self, name, old, new):
485 486 if not new.startswith('/'):
486 487 self.base_url = '/'+new
487 488 elif not new.endswith('/'):
488 489 self.base_url = new+'/'
489 490
490 491 base_project_url = Unicode('/', config=True, help="""DEPRECATED use base_url""")
491 492 def _base_project_url_changed(self, name, old, new):
492 493 self.log.warn("base_project_url is deprecated, use base_url")
493 494 self.base_url = new
494 495
495 496 extra_static_paths = List(Unicode, config=True,
496 497 help="""Extra paths to search for serving static files.
497 498
498 499 This allows adding javascript/css to be available from the notebook server machine,
499 500 or overriding individual files in the IPython"""
500 501 )
501 502 def _extra_static_paths_default(self):
502 503 return [os.path.join(self.profile_dir.location, 'static')]
503 504
504 505 @property
505 506 def static_file_path(self):
506 507 """return extra paths + the default location"""
507 508 return self.extra_static_paths + [DEFAULT_STATIC_FILES_PATH]
508 509
509 510 nbextensions_path = List(Unicode, config=True,
510 511 help="""paths for Javascript extensions. By default, this is just IPYTHONDIR/nbextensions"""
511 512 )
512 513 def _nbextensions_path_default(self):
513 514 return [os.path.join(get_ipython_dir(), 'nbextensions')]
514 515
516 websocket_url = Unicode("", config=True,
517 help="""The base URL for websockets,
518 if it differs from the HTTP server (hint: it almost certainly doesn't).
519
520 Should be in the form of an HTTP origin: ws[s]://hostname[:port]
521 """
522 )
515 523 mathjax_url = Unicode("", config=True,
516 524 help="""The url for MathJax.js."""
517 525 )
518 526 def _mathjax_url_default(self):
519 527 if not self.enable_mathjax:
520 528 return u''
521 529 static_url_prefix = self.webapp_settings.get("static_url_prefix",
522 530 url_path_join(self.base_url, "static")
523 531 )
524 532
525 533 # try local mathjax, either in nbextensions/mathjax or static/mathjax
526 534 for (url_prefix, search_path) in [
527 535 (url_path_join(self.base_url, "nbextensions"), self.nbextensions_path),
528 536 (static_url_prefix, self.static_file_path),
529 537 ]:
530 538 self.log.debug("searching for local mathjax in %s", search_path)
531 539 try:
532 540 mathjax = filefind(os.path.join('mathjax', 'MathJax.js'), search_path)
533 541 except IOError:
534 542 continue
535 543 else:
536 544 url = url_path_join(url_prefix, u"mathjax/MathJax.js")
537 545 self.log.info("Serving local MathJax from %s at %s", mathjax, url)
538 546 return url
539 547
540 548 # no local mathjax, serve from CDN
541 549 url = u"//cdn.mathjax.org/mathjax/latest/MathJax.js"
542 550 self.log.info("Using MathJax from CDN: %s", url)
543 551 return url
544 552
545 553 def _mathjax_url_changed(self, name, old, new):
546 554 if new and not self.enable_mathjax:
547 555 # enable_mathjax=False overrides mathjax_url
548 556 self.mathjax_url = u''
549 557 else:
550 558 self.log.info("Using MathJax: %s", new)
551 559
552 560 notebook_manager_class = DottedObjectName('IPython.html.services.notebooks.filenbmanager.FileNotebookManager',
553 561 config=True,
554 562 help='The notebook manager class to use.'
555 563 )
556 564 kernel_manager_class = DottedObjectName('IPython.html.services.kernels.kernelmanager.MappingKernelManager',
557 565 config=True,
558 566 help='The kernel manager class to use.'
559 567 )
560 568 session_manager_class = DottedObjectName('IPython.html.services.sessions.sessionmanager.SessionManager',
561 569 config=True,
562 570 help='The session manager class to use.'
563 571 )
564 572 cluster_manager_class = DottedObjectName('IPython.html.services.clusters.clustermanager.ClusterManager',
565 573 config=True,
566 574 help='The cluster manager class to use.'
567 575 )
568 576
569 577 kernel_spec_manager = Instance(KernelSpecManager)
570 578
571 579 def _kernel_spec_manager_default(self):
572 580 return KernelSpecManager(ipython_dir=self.ipython_dir)
573 581
574 582 trust_xheaders = Bool(False, config=True,
575 583 help=("Whether to trust or not X-Scheme/X-Forwarded-Proto and X-Real-Ip/X-Forwarded-For headers"
576 584 "sent by the upstream reverse proxy. Necessary if the proxy handles SSL")
577 585 )
578 586
579 587 info_file = Unicode()
580 588
581 589 def _info_file_default(self):
582 590 info_file = "nbserver-%s.json"%os.getpid()
583 591 return os.path.join(self.profile_dir.security_dir, info_file)
584 592
585 593 notebook_dir = Unicode(py3compat.getcwd(), config=True,
586 594 help="The directory to use for notebooks and kernels."
587 595 )
588 596
589 597 pylab = Unicode('disabled', config=True,
590 598 help="""
591 599 DISABLED: use %pylab or %matplotlib in the notebook to enable matplotlib.
592 600 """
593 601 )
594 602 def _pylab_changed(self, name, old, new):
595 603 """when --pylab is specified, display a warning and exit"""
596 604 if new != 'warn':
597 605 backend = ' %s' % new
598 606 else:
599 607 backend = ''
600 608 self.log.error("Support for specifying --pylab on the command line has been removed.")
601 609 self.log.error(
602 610 "Please use `%pylab{0}` or `%matplotlib{0}` in the notebook itself.".format(backend)
603 611 )
604 612 self.exit(1)
605 613
606 614 def _notebook_dir_changed(self, name, old, new):
607 615 """Do a bit of validation of the notebook dir."""
608 616 if not os.path.isabs(new):
609 617 # If we receive a non-absolute path, make it absolute.
610 618 self.notebook_dir = os.path.abspath(new)
611 619 return
612 620 if not os.path.isdir(new):
613 621 raise TraitError("No such notebook dir: %r" % new)
614 622
615 623 # setting App.notebook_dir implies setting notebook and kernel dirs as well
616 624 self.config.FileNotebookManager.notebook_dir = new
617 625 self.config.MappingKernelManager.root_dir = new
618 626
619 627
620 628 def parse_command_line(self, argv=None):
621 629 super(NotebookApp, self).parse_command_line(argv)
622 630
623 631 if self.extra_args:
624 632 arg0 = self.extra_args[0]
625 633 f = os.path.abspath(arg0)
626 634 self.argv.remove(arg0)
627 635 if not os.path.exists(f):
628 636 self.log.critical("No such file or directory: %s", f)
629 637 self.exit(1)
630 638
631 639 # Use config here, to ensure that it takes higher priority than
632 640 # anything that comes from the profile.
633 641 c = Config()
634 642 if os.path.isdir(f):
635 643 c.NotebookApp.notebook_dir = f
636 644 elif os.path.isfile(f):
637 645 c.NotebookApp.file_to_run = f
638 646 self.update_config(c)
639 647
640 648 def init_kernel_argv(self):
641 649 """construct the kernel arguments"""
642 650 # Kernel should get *absolute* path to profile directory
643 651 self.kernel_argv = ["--profile-dir", self.profile_dir.location]
644 652
645 653 def init_configurables(self):
646 654 # force Session default to be secure
647 655 default_secure(self.config)
648 656 kls = import_item(self.kernel_manager_class)
649 657 self.kernel_manager = kls(
650 658 parent=self, log=self.log, kernel_argv=self.kernel_argv,
651 659 connection_dir = self.profile_dir.security_dir,
652 660 )
653 661 kls = import_item(self.notebook_manager_class)
654 662 self.notebook_manager = kls(parent=self, log=self.log)
655 663 kls = import_item(self.session_manager_class)
656 664 self.session_manager = kls(parent=self, log=self.log,
657 665 kernel_manager=self.kernel_manager,
658 666 notebook_manager=self.notebook_manager)
659 667 kls = import_item(self.cluster_manager_class)
660 668 self.cluster_manager = kls(parent=self, log=self.log)
661 669 self.cluster_manager.update_profiles()
662 670
663 671 def init_logging(self):
664 672 # This prevents double log messages because tornado use a root logger that
665 673 # self.log is a child of. The logging module dipatches log messages to a log
666 674 # and all of its ancenstors until propagate is set to False.
667 675 self.log.propagate = False
668 676
669 677 # hook up tornado 3's loggers to our app handlers
670 678 logger = logging.getLogger('tornado')
671 679 logger.propagate = True
672 680 logger.parent = self.log
673 681 logger.setLevel(self.log.level)
674 682
675 683 def init_webapp(self):
676 684 """initialize tornado webapp and httpserver"""
677 685 self.webapp_settings['allow_origin'] = self.allow_origin
678 686 if self.allow_origin_pat:
679 687 self.webapp_settings['allow_origin_pat'] = re.compile(self.allow_origin_pat)
680 688 self.webapp_settings['allow_credentials'] = self.allow_credentials
681 689
682 690 self.web_app = NotebookWebApplication(
683 691 self, self.kernel_manager, self.notebook_manager,
684 692 self.cluster_manager, self.session_manager, self.kernel_spec_manager,
685 693 self.log, self.base_url, self.webapp_settings,
686 694 self.jinja_environment_options
687 695 )
688 696 if self.certfile:
689 697 ssl_options = dict(certfile=self.certfile)
690 698 if self.keyfile:
691 699 ssl_options['keyfile'] = self.keyfile
692 700 else:
693 701 ssl_options = None
694 702 self.web_app.password = self.password
695 703 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options,
696 704 xheaders=self.trust_xheaders)
697 705 if not self.ip:
698 706 warning = "WARNING: The notebook server is listening on all IP addresses"
699 707 if ssl_options is None:
700 708 self.log.critical(warning + " and not using encryption. This "
701 709 "is not recommended.")
702 710 if not self.password:
703 711 self.log.critical(warning + " and not using authentication. "
704 712 "This is highly insecure and not recommended.")
705 713 success = None
706 714 for port in random_ports(self.port, self.port_retries+1):
707 715 try:
708 716 self.http_server.listen(port, self.ip)
709 717 except socket.error as e:
710 718 if e.errno == errno.EADDRINUSE:
711 719 self.log.info('The port %i is already in use, trying another random port.' % port)
712 720 continue
713 721 elif e.errno in (errno.EACCES, getattr(errno, 'WSAEACCES', errno.EACCES)):
714 722 self.log.warn("Permission to listen on port %i denied" % port)
715 723 continue
716 724 else:
717 725 raise
718 726 else:
719 727 self.port = port
720 728 success = True
721 729 break
722 730 if not success:
723 731 self.log.critical('ERROR: the notebook server could not be started because '
724 732 'no available port could be found.')
725 733 self.exit(1)
726 734
727 735 @property
728 736 def display_url(self):
729 737 ip = self.ip if self.ip else '[all ip addresses on your system]'
730 738 return self._url(ip)
731 739
732 740 @property
733 741 def connection_url(self):
734 742 ip = self.ip if self.ip else 'localhost'
735 743 return self._url(ip)
736 744
737 745 def _url(self, ip):
738 746 proto = 'https' if self.certfile else 'http'
739 747 return "%s://%s:%i%s" % (proto, ip, self.port, self.base_url)
740 748
741 749 def init_signal(self):
742 750 if not sys.platform.startswith('win'):
743 751 signal.signal(signal.SIGINT, self._handle_sigint)
744 752 signal.signal(signal.SIGTERM, self._signal_stop)
745 753 if hasattr(signal, 'SIGUSR1'):
746 754 # Windows doesn't support SIGUSR1
747 755 signal.signal(signal.SIGUSR1, self._signal_info)
748 756 if hasattr(signal, 'SIGINFO'):
749 757 # only on BSD-based systems
750 758 signal.signal(signal.SIGINFO, self._signal_info)
751 759
752 760 def _handle_sigint(self, sig, frame):
753 761 """SIGINT handler spawns confirmation dialog"""
754 762 # register more forceful signal handler for ^C^C case
755 763 signal.signal(signal.SIGINT, self._signal_stop)
756 764 # request confirmation dialog in bg thread, to avoid
757 765 # blocking the App
758 766 thread = threading.Thread(target=self._confirm_exit)
759 767 thread.daemon = True
760 768 thread.start()
761 769
762 770 def _restore_sigint_handler(self):
763 771 """callback for restoring original SIGINT handler"""
764 772 signal.signal(signal.SIGINT, self._handle_sigint)
765 773
766 774 def _confirm_exit(self):
767 775 """confirm shutdown on ^C
768 776
769 777 A second ^C, or answering 'y' within 5s will cause shutdown,
770 778 otherwise original SIGINT handler will be restored.
771 779
772 780 This doesn't work on Windows.
773 781 """
774 782 info = self.log.info
775 783 info('interrupted')
776 784 print(self.notebook_info())
777 785 sys.stdout.write("Shutdown this notebook server (y/[n])? ")
778 786 sys.stdout.flush()
779 787 r,w,x = select.select([sys.stdin], [], [], 5)
780 788 if r:
781 789 line = sys.stdin.readline()
782 790 if line.lower().startswith('y') and 'n' not in line.lower():
783 791 self.log.critical("Shutdown confirmed")
784 792 ioloop.IOLoop.instance().stop()
785 793 return
786 794 else:
787 795 print("No answer for 5s:", end=' ')
788 796 print("resuming operation...")
789 797 # no answer, or answer is no:
790 798 # set it back to original SIGINT handler
791 799 # use IOLoop.add_callback because signal.signal must be called
792 800 # from main thread
793 801 ioloop.IOLoop.instance().add_callback(self._restore_sigint_handler)
794 802
795 803 def _signal_stop(self, sig, frame):
796 804 self.log.critical("received signal %s, stopping", sig)
797 805 ioloop.IOLoop.instance().stop()
798 806
799 807 def _signal_info(self, sig, frame):
800 808 print(self.notebook_info())
801 809
802 810 def init_components(self):
803 811 """Check the components submodule, and warn if it's unclean"""
804 812 status = submodule.check_submodule_status()
805 813 if status == 'missing':
806 814 self.log.warn("components submodule missing, running `git submodule update`")
807 815 submodule.update_submodules(submodule.ipython_parent())
808 816 elif status == 'unclean':
809 817 self.log.warn("components submodule unclean, you may see 404s on static/components")
810 818 self.log.warn("run `setup.py submodule` or `git submodule update` to update")
811 819
812 820 @catch_config_error
813 821 def initialize(self, argv=None):
814 822 super(NotebookApp, self).initialize(argv)
815 823 self.init_logging()
816 824 self.init_kernel_argv()
817 825 self.init_configurables()
818 826 self.init_components()
819 827 self.init_webapp()
820 828 self.init_signal()
821 829
822 830 def cleanup_kernels(self):
823 831 """Shutdown all kernels.
824 832
825 833 The kernels will shutdown themselves when this process no longer exists,
826 834 but explicit shutdown allows the KernelManagers to cleanup the connection files.
827 835 """
828 836 self.log.info('Shutting down kernels')
829 837 self.kernel_manager.shutdown_all()
830 838
831 839 def notebook_info(self):
832 840 "Return the current working directory and the server url information"
833 841 info = self.notebook_manager.info_string() + "\n"
834 842 info += "%d active kernels \n" % len(self.kernel_manager._kernels)
835 843 return info + "The IPython Notebook is running at: %s" % self.display_url
836 844
837 845 def server_info(self):
838 846 """Return a JSONable dict of information about this server."""
839 847 return {'url': self.connection_url,
840 848 'hostname': self.ip if self.ip else 'localhost',
841 849 'port': self.port,
842 850 'secure': bool(self.certfile),
843 851 'base_url': self.base_url,
844 852 'notebook_dir': os.path.abspath(self.notebook_dir),
845 853 'pid': os.getpid()
846 854 }
847 855
848 856 def write_server_info_file(self):
849 857 """Write the result of server_info() to the JSON file info_file."""
850 858 with open(self.info_file, 'w') as f:
851 859 json.dump(self.server_info(), f, indent=2)
852 860
853 861 def remove_server_info_file(self):
854 862 """Remove the nbserver-<pid>.json file created for this server.
855 863
856 864 Ignores the error raised when the file has already been removed.
857 865 """
858 866 try:
859 867 os.unlink(self.info_file)
860 868 except OSError as e:
861 869 if e.errno != errno.ENOENT:
862 870 raise
863 871
864 872 def start(self):
865 873 """ Start the IPython Notebook server app, after initialization
866 874
867 875 This method takes no arguments so all configuration and initialization
868 876 must be done prior to calling this method."""
869 877 if self.subapp is not None:
870 878 return self.subapp.start()
871 879
872 880 info = self.log.info
873 881 for line in self.notebook_info().split("\n"):
874 882 info(line)
875 883 info("Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).")
876 884
877 885 self.write_server_info_file()
878 886
879 887 if self.open_browser or self.file_to_run:
880 888 try:
881 889 browser = webbrowser.get(self.browser or None)
882 890 except webbrowser.Error as e:
883 891 self.log.warn('No web browser found: %s.' % e)
884 892 browser = None
885 893
886 894 if self.file_to_run:
887 895 fullpath = os.path.join(self.notebook_dir, self.file_to_run)
888 896 if not os.path.exists(fullpath):
889 897 self.log.critical("%s does not exist" % fullpath)
890 898 self.exit(1)
891 899
892 900 uri = url_path_join('notebooks', self.file_to_run)
893 901 else:
894 902 uri = 'tree'
895 903 if browser:
896 904 b = lambda : browser.open(url_path_join(self.connection_url, uri),
897 905 new=2)
898 906 threading.Thread(target=b).start()
899 907 try:
900 908 ioloop.IOLoop.instance().start()
901 909 except KeyboardInterrupt:
902 910 info("Interrupted...")
903 911 finally:
904 912 self.cleanup_kernels()
905 913 self.remove_server_info_file()
906 914
907 915
908 916 def list_running_servers(profile='default'):
909 917 """Iterate over the server info files of running notebook servers.
910 918
911 919 Given a profile name, find nbserver-* files in the security directory of
912 920 that profile, and yield dicts of their information, each one pertaining to
913 921 a currently running notebook server instance.
914 922 """
915 923 pd = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), name=profile)
916 924 for file in os.listdir(pd.security_dir):
917 925 if file.startswith('nbserver-'):
918 926 with io.open(os.path.join(pd.security_dir, file), encoding='utf-8') as f:
919 927 info = json.load(f)
920 928
921 929 # Simple check whether that process is really still running
922 930 if check_pid(info['pid']):
923 931 yield info
924 932 else:
925 933 # If the process has died, try to delete its info file
926 934 try:
927 935 os.unlink(file)
928 936 except OSError:
929 937 pass # TODO: This should warn or log or something
930 938 #-----------------------------------------------------------------------------
931 939 # Main entry point
932 940 #-----------------------------------------------------------------------------
933 941
934 942 launch_new_instance = NotebookApp.launch_instance
935 943
@@ -1,136 +1,137
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 require([
5 5 'base/js/namespace',
6 6 'jquery',
7 7 'notebook/js/notebook',
8 8 'base/js/utils',
9 9 'base/js/page',
10 10 'notebook/js/layoutmanager',
11 11 'base/js/events',
12 12 'auth/js/loginwidget',
13 13 'notebook/js/maintoolbar',
14 14 'notebook/js/pager',
15 15 'notebook/js/quickhelp',
16 16 'notebook/js/menubar',
17 17 'notebook/js/notificationarea',
18 18 'notebook/js/savewidget',
19 19 'notebook/js/keyboardmanager',
20 20 'notebook/js/config',
21 21 ], function(
22 22 IPython,
23 23 $,
24 24 notebook,
25 25 utils,
26 26 page,
27 27 layoutmanager,
28 28 events,
29 29 loginwidget,
30 30 maintoolbar,
31 31 pager,
32 32 quickhelp,
33 33 menubar,
34 34 notificationarea,
35 35 savewidget,
36 36 keyboardmanager,
37 37 config
38 38 ) {
39 39 "use strict";
40 40
41 41 $('#ipython-main-app').addClass('border-box-sizing');
42 42 $('div#notebook_panel').addClass('border-box-sizing');
43 43
44 44 var common_options = {
45 45 base_url : utils.get_body_data("baseUrl"),
46 ws_url : IPython.utils.get_body_data("wsUrl"),
46 47 notebook_path : utils.get_body_data("notebookPath"),
47 48 notebook_name : utils.get_body_data('notebookName')
48 49 };
49 50
50 51 var user_config = $.extend({}, config.default_config);
51 52 var page = new page.Page();
52 53 var layout_manager = new layoutmanager.LayoutManager();
53 54 var events = $([new events.Events()]);
54 55 var pager = new pager.Pager('div#pager', 'div#pager_splitter', {
55 56 layout_manager: layout_manager,
56 57 events: events});
57 58 var keyboard_manager = new keyboardmanager.KeyboardManager({
58 59 pager: pager,
59 60 events: events});
60 61 var save_widget = new savewidget.SaveWidget('span#save_widget', {
61 62 events: events,
62 63 keyboard_manager: keyboard_manager});
63 64 var notebook = new notebook.Notebook('div#notebook', $.extend({
64 65 events: events,
65 66 keyboard_manager: keyboard_manager,
66 67 save_widget: save_widget,
67 68 config: user_config},
68 69 common_options));
69 70 var login_widget = new loginwidget.LoginWidget('span#login_widget', common_options);
70 71 var toolbar = new maintoolbar.MainToolBar('#maintoolbar-container', {
71 72 notebook: notebook,
72 73 events: events});
73 74 var quick_help = new quickhelp.QuickHelp({
74 75 keyboard_manager: keyboard_manager,
75 76 events: events,
76 77 notebook: notebook});
77 78 var menubar = new menubar.MenuBar('#menubar', $.extend({
78 79 notebook: notebook,
79 80 layout_manager: layout_manager,
80 81 events: events,
81 82 save_widget: save_widget,
82 83 quick_help: quick_help},
83 84 common_options));
84 85 var notification_area = new notificationarea.NotificationArea(
85 86 '#notification_area', {
86 87 events: events,
87 88 save_widget: save_widget,
88 89 notebook: notebook,
89 90 keyboard_manager: keyboard_manager});
90 91 notification_area.init_notification_widgets();
91 92
92 93 $('body').append('<div id="fonttest"><pre><span id="test1">x</span>'+
93 94 '<span id="test2" style="font-weight: bold;">x</span>'+
94 95 '<span id="test3" style="font-style: italic;">x</span></pre></div>');
95 96 var nh = $('#test1').innerHeight();
96 97 var bh = $('#test2').innerHeight();
97 98 var ih = $('#test3').innerHeight();
98 99 if(nh != bh || nh != ih) {
99 100 $('head').append('<style>.CodeMirror span { vertical-align: bottom; }</style>');
100 101 }
101 102 $('#fonttest').remove();
102 103
103 104 page.show();
104 105
105 106 layout_manager.do_resize();
106 107 var first_load = function () {
107 108 layout_manager.do_resize();
108 109 var hash = document.location.hash;
109 110 if (hash) {
110 111 document.location.hash = '';
111 112 document.location.hash = hash;
112 113 }
113 114 notebook.set_autosave_interval(notebook.minimum_autosave_interval);
114 115 // only do this once
115 116 events.off('notebook_loaded.Notebook', first_load);
116 117 };
117 118
118 119 events.on('notebook_loaded.Notebook', first_load);
119 120 events.trigger('app_initialized.NotebookApp');
120 121 notebook.load_notebook(common_options.notebook_name, common_options.notebook_path);
121 122
122 123 IPython.page = page;
123 124 IPython.layout_manager = layout_manager;
124 125 IPython.notebook = notebook;
125 126 IPython.pager = pager;
126 127 IPython.quick_help = quick_help;
127 128 IPython.login_widget = login_widget;
128 129 IPython.menubar = menubar;
129 130 IPython.toolbar = toolbar;
130 131 IPython.notification_area = notification_area;
131 132 IPython.events = events;
132 133 IPython.keyboard_manager = keyboard_manager;
133 134 IPython.save_widget = save_widget;
134 135 IPython.config = user_config;
135 136 IPython.tooltip = notebook.tooltip;
136 137 });
@@ -1,613 +1,616
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 define([
5 5 'base/js/namespace',
6 6 'jquery',
7 7 'base/js/utils',
8 8 'services/kernels/js/comm',
9 9 'widgets/js/init',
10 10 ], function(IPython, $, utils, comm, widgetmanager) {
11 11 "use strict";
12 12
13 13 // Initialization and connection.
14 14 /**
15 15 * A Kernel Class to communicate with the Python kernel
16 16 * @Class Kernel
17 17 */
18 18 var Kernel = function (kernel_service_url, notebook, name) {
19 19 this.events = notebook.events;
20 20 this.kernel_id = null;
21 21 this.shell_channel = null;
22 22 this.iopub_channel = null;
23 23 this.stdin_channel = null;
24 24 this.kernel_service_url = kernel_service_url;
25 25 this.name = name;
26 this.ws_url = IPython.utils.get_body_data("wsUrl");
27 if (!this.ws_url) {
28 // trailing 's' in https will become wss for secure web sockets
29 this.ws_url = location.protocol.replace('http', 'ws') + "//" + location.host;
30 }
26 31 this.running = false;
27 32 this.username = "username";
28 33 this.session_id = utils.uuid();
29 34 this._msg_callbacks = {};
30 35 this.post = $.post;
31 36
32 37 if (typeof(WebSocket) !== 'undefined') {
33 38 this.WebSocket = WebSocket;
34 39 } else if (typeof(MozWebSocket) !== 'undefined') {
35 40 this.WebSocket = MozWebSocket;
36 41 } else {
37 42 alert('Your browser does not have WebSocket support, please try Chrome, Safari or Firefox ≥ 6. Firefox 4 and 5 are also supported by you have to enable WebSockets in about:config.');
38 43 }
39 44
40 45 this.bind_events();
41 46 this.init_iopub_handlers();
42 47 this.comm_manager = new comm.CommManager(this);
43 48 this.widget_manager = new widgetmanager.WidgetManager(this.comm_manager, notebook);
44 49
45 50 this.last_msg_id = null;
46 51 this.last_msg_callbacks = {};
47 52 };
48 53
49 54
50 55 Kernel.prototype._get_msg = function (msg_type, content, metadata) {
51 56 var msg = {
52 57 header : {
53 58 msg_id : utils.uuid(),
54 59 username : this.username,
55 60 session : this.session_id,
56 61 msg_type : msg_type,
57 62 version : "5.0"
58 63 },
59 64 metadata : metadata || {},
60 65 content : content,
61 66 parent_header : {}
62 67 };
63 68 return msg;
64 69 };
65 70
66 71 Kernel.prototype.bind_events = function () {
67 72 var that = this;
68 73 this.events.on('send_input_reply.Kernel', function(evt, data) {
69 74 that.send_input_reply(data);
70 75 });
71 76 };
72 77
73 78 // Initialize the iopub handlers
74 79
75 80 Kernel.prototype.init_iopub_handlers = function () {
76 81 var output_msg_types = ['stream', 'display_data', 'execute_result', 'error'];
77 82 this._iopub_handlers = {};
78 83 this.register_iopub_handler('status', $.proxy(this._handle_status_message, this));
79 84 this.register_iopub_handler('clear_output', $.proxy(this._handle_clear_output, this));
80 85
81 86 for (var i=0; i < output_msg_types.length; i++) {
82 87 this.register_iopub_handler(output_msg_types[i], $.proxy(this._handle_output_message, this));
83 88 }
84 89 };
85 90
86 91 /**
87 92 * Start the Python kernel
88 93 * @method start
89 94 */
90 95 Kernel.prototype.start = function (params) {
91 96 params = params || {};
92 97 if (!this.running) {
93 98 var qs = $.param(params);
94 99 this.post(utils.url_join_encode(this.kernel_service_url) + '?' + qs,
95 100 $.proxy(this._kernel_started, this),
96 101 'json'
97 102 );
98 103 }
99 104 };
100 105
101 106 /**
102 107 * Restart the python kernel.
103 108 *
104 109 * Emit a 'status_restarting.Kernel' event with
105 110 * the current object as parameter
106 111 *
107 112 * @method restart
108 113 */
109 114 Kernel.prototype.restart = function () {
110 115 this.events.trigger('status_restarting.Kernel', {kernel: this});
111 116 if (this.running) {
112 117 this.stop_channels();
113 118 this.post(utils.url_join_encode(this.kernel_url, "restart"),
114 119 $.proxy(this._kernel_started, this),
115 120 'json'
116 121 );
117 122 }
118 123 };
119 124
120 125
121 126 Kernel.prototype._kernel_started = function (json) {
122 127 console.log("Kernel started: ", json.id);
123 128 this.running = true;
124 129 this.kernel_id = json.id;
125 // trailing 's' in https will become wss for secure web sockets
126 this.ws_host = location.protocol.replace('http', 'ws') + "//" + location.host;
127 130 this.kernel_url = utils.url_path_join(this.kernel_service_url, this.kernel_id);
128 131 this.start_channels();
129 132 };
130 133
131 134
132 135 Kernel.prototype._websocket_closed = function(ws_url, early) {
133 136 this.stop_channels();
134 137 this.events.trigger('websocket_closed.Kernel',
135 138 {ws_url: ws_url, kernel: this, early: early}
136 139 );
137 140 };
138 141
139 142 /**
140 143 * Start the `shell`and `iopub` channels.
141 144 * Will stop and restart them if they already exist.
142 145 *
143 146 * @method start_channels
144 147 */
145 148 Kernel.prototype.start_channels = function () {
146 149 var that = this;
147 150 this.stop_channels();
148 var ws_host_url = this.ws_host + this.kernel_url;
151 var ws_host_url = this.ws_url + this.kernel_url;
149 152 console.log("Starting WebSockets:", ws_host_url);
150 153 this.shell_channel = new this.WebSocket(
151 this.ws_host + utils.url_join_encode(this.kernel_url, "shell")
154 this.ws_url + utils.url_join_encode(this.kernel_url, "shell")
152 155 );
153 156 this.stdin_channel = new this.WebSocket(
154 this.ws_host + utils.url_join_encode(this.kernel_url, "stdin")
157 this.ws_url + utils.url_join_encode(this.kernel_url, "stdin")
155 158 );
156 159 this.iopub_channel = new this.WebSocket(
157 this.ws_host + utils.url_join_encode(this.kernel_url, "iopub")
160 this.ws_url + utils.url_join_encode(this.kernel_url, "iopub")
158 161 );
159 162
160 163 var already_called_onclose = false; // only alert once
161 164 var ws_closed_early = function(evt){
162 165 if (already_called_onclose){
163 166 return;
164 167 }
165 168 already_called_onclose = true;
166 169 if ( ! evt.wasClean ){
167 170 that._websocket_closed(ws_host_url, true);
168 171 }
169 172 };
170 173 var ws_closed_late = function(evt){
171 174 if (already_called_onclose){
172 175 return;
173 176 }
174 177 already_called_onclose = true;
175 178 if ( ! evt.wasClean ){
176 179 that._websocket_closed(ws_host_url, false);
177 180 }
178 181 };
179 182 var channels = [this.shell_channel, this.iopub_channel, this.stdin_channel];
180 183 for (var i=0; i < channels.length; i++) {
181 184 channels[i].onopen = $.proxy(this._ws_opened, this);
182 185 channels[i].onclose = ws_closed_early;
183 186 }
184 187 // switch from early-close to late-close message after 1s
185 188 setTimeout(function() {
186 189 for (var i=0; i < channels.length; i++) {
187 190 if (channels[i] !== null) {
188 191 channels[i].onclose = ws_closed_late;
189 192 }
190 193 }
191 194 }, 1000);
192 195 this.shell_channel.onmessage = $.proxy(this._handle_shell_reply, this);
193 196 this.iopub_channel.onmessage = $.proxy(this._handle_iopub_message, this);
194 197 this.stdin_channel.onmessage = $.proxy(this._handle_input_request, this);
195 198 };
196 199
197 200 /**
198 201 * Handle a websocket entering the open state
199 202 * sends session and cookie authentication info as first message.
200 203 * Once all sockets are open, signal the Kernel.status_started event.
201 204 * @method _ws_opened
202 205 */
203 206 Kernel.prototype._ws_opened = function (evt) {
204 207 // send the session id so the Session object Python-side
205 208 // has the same identity
206 209 evt.target.send(this.session_id + ':' + document.cookie);
207 210
208 211 var channels = [this.shell_channel, this.iopub_channel, this.stdin_channel];
209 212 for (var i=0; i < channels.length; i++) {
210 213 // if any channel is not ready, don't trigger event.
211 214 if ( !channels[i].readyState ) return;
212 215 }
213 216 // all events ready, trigger started event.
214 217 this.events.trigger('status_started.Kernel', {kernel: this});
215 218 };
216 219
217 220 /**
218 221 * Stop the websocket channels.
219 222 * @method stop_channels
220 223 */
221 224 Kernel.prototype.stop_channels = function () {
222 225 var channels = [this.shell_channel, this.iopub_channel, this.stdin_channel];
223 226 for (var i=0; i < channels.length; i++) {
224 227 if ( channels[i] !== null ) {
225 228 channels[i].onclose = null;
226 229 channels[i].close();
227 230 }
228 231 }
229 232 this.shell_channel = this.iopub_channel = this.stdin_channel = null;
230 233 };
231 234
232 235 // Main public methods.
233 236
234 237 // send a message on the Kernel's shell channel
235 238 Kernel.prototype.send_shell_message = function (msg_type, content, callbacks, metadata) {
236 239 var msg = this._get_msg(msg_type, content, metadata);
237 240 this.shell_channel.send(JSON.stringify(msg));
238 241 this.set_callbacks_for_msg(msg.header.msg_id, callbacks);
239 242 return msg.header.msg_id;
240 243 };
241 244
242 245 /**
243 246 * Get kernel info
244 247 *
245 248 * @param callback {function}
246 249 * @method kernel_info
247 250 *
248 251 * When calling this method, pass a callback function that expects one argument.
249 252 * The callback will be passed the complete `kernel_info_reply` message documented
250 253 * [here](http://ipython.org/ipython-doc/dev/development/messaging.html#kernel-info)
251 254 */
252 255 Kernel.prototype.kernel_info = function (callback) {
253 256 var callbacks;
254 257 if (callback) {
255 258 callbacks = { shell : { reply : callback } };
256 259 }
257 260 return this.send_shell_message("kernel_info_request", {}, callbacks);
258 261 };
259 262
260 263 /**
261 264 * Get info on an object
262 265 *
263 266 * @param code {string}
264 267 * @param cursor_pos {integer}
265 268 * @param callback {function}
266 269 * @method inspect
267 270 *
268 271 * When calling this method, pass a callback function that expects one argument.
269 272 * The callback will be passed the complete `inspect_reply` message documented
270 273 * [here](http://ipython.org/ipython-doc/dev/development/messaging.html#object-information)
271 274 */
272 275 Kernel.prototype.inspect = function (code, cursor_pos, callback) {
273 276 var callbacks;
274 277 if (callback) {
275 278 callbacks = { shell : { reply : callback } };
276 279 }
277 280
278 281 var content = {
279 282 code : code,
280 283 cursor_pos : cursor_pos,
281 284 detail_level : 0,
282 285 };
283 286 return this.send_shell_message("inspect_request", content, callbacks);
284 287 };
285 288
286 289 /**
287 290 * Execute given code into kernel, and pass result to callback.
288 291 *
289 292 * @async
290 293 * @method execute
291 294 * @param {string} code
292 295 * @param [callbacks] {Object} With the following keys (all optional)
293 296 * @param callbacks.shell.reply {function}
294 297 * @param callbacks.shell.payload.[payload_name] {function}
295 298 * @param callbacks.iopub.output {function}
296 299 * @param callbacks.iopub.clear_output {function}
297 300 * @param callbacks.input {function}
298 301 * @param {object} [options]
299 302 * @param [options.silent=false] {Boolean}
300 303 * @param [options.user_expressions=empty_dict] {Dict}
301 304 * @param [options.allow_stdin=false] {Boolean} true|false
302 305 *
303 306 * @example
304 307 *
305 308 * The options object should contain the options for the execute call. Its default
306 309 * values are:
307 310 *
308 311 * options = {
309 312 * silent : true,
310 313 * user_expressions : {},
311 314 * allow_stdin : false
312 315 * }
313 316 *
314 317 * When calling this method pass a callbacks structure of the form:
315 318 *
316 319 * callbacks = {
317 320 * shell : {
318 321 * reply : execute_reply_callback,
319 322 * payload : {
320 323 * set_next_input : set_next_input_callback,
321 324 * }
322 325 * },
323 326 * iopub : {
324 327 * output : output_callback,
325 328 * clear_output : clear_output_callback,
326 329 * },
327 330 * input : raw_input_callback
328 331 * }
329 332 *
330 333 * Each callback will be passed the entire message as a single arugment.
331 334 * Payload handlers will be passed the corresponding payload and the execute_reply message.
332 335 */
333 336 Kernel.prototype.execute = function (code, callbacks, options) {
334 337
335 338 var content = {
336 339 code : code,
337 340 silent : true,
338 341 store_history : false,
339 342 user_expressions : {},
340 343 allow_stdin : false
341 344 };
342 345 callbacks = callbacks || {};
343 346 if (callbacks.input !== undefined) {
344 347 content.allow_stdin = true;
345 348 }
346 349 $.extend(true, content, options);
347 350 this.events.trigger('execution_request.Kernel', {kernel: this, content:content});
348 351 return this.send_shell_message("execute_request", content, callbacks);
349 352 };
350 353
351 354 /**
352 355 * When calling this method, pass a function to be called with the `complete_reply` message
353 356 * as its only argument when it arrives.
354 357 *
355 358 * `complete_reply` is documented
356 359 * [here](http://ipython.org/ipython-doc/dev/development/messaging.html#complete)
357 360 *
358 361 * @method complete
359 362 * @param code {string}
360 363 * @param cursor_pos {integer}
361 364 * @param callback {function}
362 365 *
363 366 */
364 367 Kernel.prototype.complete = function (code, cursor_pos, callback) {
365 368 var callbacks;
366 369 if (callback) {
367 370 callbacks = { shell : { reply : callback } };
368 371 }
369 372 var content = {
370 373 code : code,
371 374 cursor_pos : cursor_pos,
372 375 };
373 376 return this.send_shell_message("complete_request", content, callbacks);
374 377 };
375 378
376 379
377 380 Kernel.prototype.interrupt = function () {
378 381 if (this.running) {
379 382 this.events.trigger('status_interrupting.Kernel', {kernel: this});
380 383 this.post(utils.url_join_encode(this.kernel_url, "interrupt"));
381 384 }
382 385 };
383 386
384 387
385 388 Kernel.prototype.kill = function () {
386 389 if (this.running) {
387 390 this.running = false;
388 391 var settings = {
389 392 cache : false,
390 393 type : "DELETE",
391 394 error : utils.log_ajax_error,
392 395 };
393 396 $.ajax(utils.url_join_encode(this.kernel_url), settings);
394 397 }
395 398 };
396 399
397 400 Kernel.prototype.send_input_reply = function (input) {
398 401 var content = {
399 402 value : input,
400 403 };
401 404 this.events.trigger('input_reply.Kernel', {kernel: this, content:content});
402 405 var msg = this._get_msg("input_reply", content);
403 406 this.stdin_channel.send(JSON.stringify(msg));
404 407 return msg.header.msg_id;
405 408 };
406 409
407 410
408 411 // Reply handlers
409 412
410 413 Kernel.prototype.register_iopub_handler = function (msg_type, callback) {
411 414 this._iopub_handlers[msg_type] = callback;
412 415 };
413 416
414 417 Kernel.prototype.get_iopub_handler = function (msg_type) {
415 418 // get iopub handler for a specific message type
416 419 return this._iopub_handlers[msg_type];
417 420 };
418 421
419 422
420 423 Kernel.prototype.get_callbacks_for_msg = function (msg_id) {
421 424 // get callbacks for a specific message
422 425 if (msg_id == this.last_msg_id) {
423 426 return this.last_msg_callbacks;
424 427 } else {
425 428 return this._msg_callbacks[msg_id];
426 429 }
427 430 };
428 431
429 432
430 433 Kernel.prototype.clear_callbacks_for_msg = function (msg_id) {
431 434 if (this._msg_callbacks[msg_id] !== undefined ) {
432 435 delete this._msg_callbacks[msg_id];
433 436 }
434 437 };
435 438
436 439 Kernel.prototype._finish_shell = function (msg_id) {
437 440 var callbacks = this._msg_callbacks[msg_id];
438 441 if (callbacks !== undefined) {
439 442 callbacks.shell_done = true;
440 443 if (callbacks.iopub_done) {
441 444 this.clear_callbacks_for_msg(msg_id);
442 445 }
443 446 }
444 447 };
445 448
446 449 Kernel.prototype._finish_iopub = function (msg_id) {
447 450 var callbacks = this._msg_callbacks[msg_id];
448 451 if (callbacks !== undefined) {
449 452 callbacks.iopub_done = true;
450 453 if (callbacks.shell_done) {
451 454 this.clear_callbacks_for_msg(msg_id);
452 455 }
453 456 }
454 457 };
455 458
456 459 /* Set callbacks for a particular message.
457 460 * Callbacks should be a struct of the following form:
458 461 * shell : {
459 462 *
460 463 * }
461 464
462 465 */
463 466 Kernel.prototype.set_callbacks_for_msg = function (msg_id, callbacks) {
464 467 this.last_msg_id = msg_id;
465 468 if (callbacks) {
466 469 // shallow-copy mapping, because we will modify it at the top level
467 470 var cbcopy = this._msg_callbacks[msg_id] = this.last_msg_callbacks = {};
468 471 cbcopy.shell = callbacks.shell;
469 472 cbcopy.iopub = callbacks.iopub;
470 473 cbcopy.input = callbacks.input;
471 474 cbcopy.shell_done = (!callbacks.shell);
472 475 cbcopy.iopub_done = (!callbacks.iopub);
473 476 } else {
474 477 this.last_msg_callbacks = {};
475 478 }
476 479 };
477 480
478 481
479 482 Kernel.prototype._handle_shell_reply = function (e) {
480 483 var reply = $.parseJSON(e.data);
481 484 this.events.trigger('shell_reply.Kernel', {kernel: this, reply:reply});
482 485 var content = reply.content;
483 486 var metadata = reply.metadata;
484 487 var parent_id = reply.parent_header.msg_id;
485 488 var callbacks = this.get_callbacks_for_msg(parent_id);
486 489 if (!callbacks || !callbacks.shell) {
487 490 return;
488 491 }
489 492 var shell_callbacks = callbacks.shell;
490 493
491 494 // signal that shell callbacks are done
492 495 this._finish_shell(parent_id);
493 496
494 497 if (shell_callbacks.reply !== undefined) {
495 498 shell_callbacks.reply(reply);
496 499 }
497 500 if (content.payload && shell_callbacks.payload) {
498 501 this._handle_payloads(content.payload, shell_callbacks.payload, reply);
499 502 }
500 503 };
501 504
502 505
503 506 Kernel.prototype._handle_payloads = function (payloads, payload_callbacks, msg) {
504 507 var l = payloads.length;
505 508 // Payloads are handled by triggering events because we don't want the Kernel
506 509 // to depend on the Notebook or Pager classes.
507 510 for (var i=0; i<l; i++) {
508 511 var payload = payloads[i];
509 512 var callback = payload_callbacks[payload.source];
510 513 if (callback) {
511 514 callback(payload, msg);
512 515 }
513 516 }
514 517 };
515 518
516 519 Kernel.prototype._handle_status_message = function (msg) {
517 520 var execution_state = msg.content.execution_state;
518 521 var parent_id = msg.parent_header.msg_id;
519 522
520 523 // dispatch status msg callbacks, if any
521 524 var callbacks = this.get_callbacks_for_msg(parent_id);
522 525 if (callbacks && callbacks.iopub && callbacks.iopub.status) {
523 526 try {
524 527 callbacks.iopub.status(msg);
525 528 } catch (e) {
526 529 console.log("Exception in status msg handler", e, e.stack);
527 530 }
528 531 }
529 532
530 533 if (execution_state === 'busy') {
531 534 this.events.trigger('status_busy.Kernel', {kernel: this});
532 535 } else if (execution_state === 'idle') {
533 536 // signal that iopub callbacks are (probably) done
534 537 // async output may still arrive,
535 538 // but only for the most recent request
536 539 this._finish_iopub(parent_id);
537 540
538 541 // trigger status_idle event
539 542 this.events.trigger('status_idle.Kernel', {kernel: this});
540 543 } else if (execution_state === 'restarting') {
541 544 // autorestarting is distinct from restarting,
542 545 // in that it means the kernel died and the server is restarting it.
543 546 // status_restarting sets the notification widget,
544 547 // autorestart shows the more prominent dialog.
545 548 this.events.trigger('status_autorestarting.Kernel', {kernel: this});
546 549 this.events.trigger('status_restarting.Kernel', {kernel: this});
547 550 } else if (execution_state === 'dead') {
548 551 this.stop_channels();
549 552 this.events.trigger('status_dead.Kernel', {kernel: this});
550 553 }
551 554 };
552 555
553 556
554 557 // handle clear_output message
555 558 Kernel.prototype._handle_clear_output = function (msg) {
556 559 var callbacks = this.get_callbacks_for_msg(msg.parent_header.msg_id);
557 560 if (!callbacks || !callbacks.iopub) {
558 561 return;
559 562 }
560 563 var callback = callbacks.iopub.clear_output;
561 564 if (callback) {
562 565 callback(msg);
563 566 }
564 567 };
565 568
566 569
567 570 // handle an output message (execute_result, display_data, etc.)
568 571 Kernel.prototype._handle_output_message = function (msg) {
569 572 var callbacks = this.get_callbacks_for_msg(msg.parent_header.msg_id);
570 573 if (!callbacks || !callbacks.iopub) {
571 574 return;
572 575 }
573 576 var callback = callbacks.iopub.output;
574 577 if (callback) {
575 578 callback(msg);
576 579 }
577 580 };
578 581
579 582 // dispatch IOPub messages to respective handlers.
580 583 // each message type should have a handler.
581 584 Kernel.prototype._handle_iopub_message = function (e) {
582 585 var msg = $.parseJSON(e.data);
583 586
584 587 var handler = this.get_iopub_handler(msg.header.msg_type);
585 588 if (handler !== undefined) {
586 589 handler(msg);
587 590 }
588 591 };
589 592
590 593
591 594 Kernel.prototype._handle_input_request = function (e) {
592 595 var request = $.parseJSON(e.data);
593 596 var header = request.header;
594 597 var content = request.content;
595 598 var metadata = request.metadata;
596 599 var msg_type = header.msg_type;
597 600 if (msg_type !== 'input_request') {
598 601 console.log("Invalid input request!", request);
599 602 return;
600 603 }
601 604 var callbacks = this.get_callbacks_for_msg(request.parent_header.msg_id);
602 605 if (callbacks) {
603 606 if (callbacks.input) {
604 607 callbacks.input(request);
605 608 }
606 609 }
607 610 };
608 611
609 612 // Backwards compatability.
610 613 IPython.Kernel = Kernel;
611 614
612 615 return {'Kernel': Kernel};
613 616 });
@@ -1,320 +1,321
1 1 {% extends "page.html" %}
2 2
3 3 {% block stylesheet %}
4 4
5 5 {% if mathjax_url %}
6 6 <script type="text/javascript" src="{{mathjax_url}}?config=TeX-AMS_HTML-full&delayStartupUntil=configured" charset="utf-8"></script>
7 7 {% endif %}
8 8 <script type="text/javascript">
9 9 // MathJax disabled, set as null to distingish from *missing* MathJax,
10 10 // where it will be undefined, and should prompt a dialog later.
11 11 window.mathjax_url = "{{mathjax_url}}";
12 12 </script>
13 13
14 14 <link rel="stylesheet" href="{{ static_url("components/bootstrap-tour/build/css/bootstrap-tour.min.css") }}" type="text/css" />
15 15 <link rel="stylesheet" href="{{ static_url("components/codemirror/lib/codemirror.css") }}">
16 16
17 17 {{super()}}
18 18
19 19 <link rel="stylesheet" href="{{ static_url("notebook/css/override.css") }}" type="text/css" />
20 20
21 21 {% endblock %}
22 22
23 23 {% block params %}
24 24
25 25 data-project="{{project}}"
26 26 data-base-url="{{base_url}}"
27 data-ws-url="{{ws_url}}"
27 28 data-notebook-name="{{notebook_name}}"
28 29 data-notebook-path="{{notebook_path}}"
29 30 class="notebook_app"
30 31
31 32 {% endblock %}
32 33
33 34
34 35 {% block header %}
35 36
36 37 <span id="save_widget" class="nav pull-left">
37 38 <span id="notebook_name"></span>
38 39 <span id="checkpoint_status"></span>
39 40 <span id="autosave_status"></span>
40 41 </span>
41 42
42 43 {% endblock %}
43 44
44 45
45 46 {% block site %}
46 47
47 48 <div id="menubar-container" class="container">
48 49 <div id="menubar">
49 50 <div id="menus" class="navbar navbar-default" role="navigation">
50 51 <div class="container-fluid">
51 52 <ul class="nav navbar-nav">
52 53 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">File</a>
53 54 <ul id="file_menu" class="dropdown-menu">
54 55 <li id="new_notebook"
55 56 title="Make a new notebook (Opens a new window)">
56 57 <a href="#">New</a></li>
57 58 <li id="open_notebook"
58 59 title="Opens a new window with the Dashboard view">
59 60 <a href="#">Open...</a></li>
60 61 <!-- <hr/> -->
61 62 <li class="divider"></li>
62 63 <li id="copy_notebook"
63 64 title="Open a copy of this notebook's contents and start a new kernel">
64 65 <a href="#">Make a Copy...</a></li>
65 66 <li id="rename_notebook"><a href="#">Rename...</a></li>
66 67 <li id="save_checkpoint"><a href="#">Save and Checkpoint</a></li>
67 68 <!-- <hr/> -->
68 69 <li class="divider"></li>
69 70 <li id="restore_checkpoint" class="dropdown-submenu"><a href="#">Revert to Checkpoint</a>
70 71 <ul class="dropdown-menu">
71 72 <li><a href="#"></a></li>
72 73 <li><a href="#"></a></li>
73 74 <li><a href="#"></a></li>
74 75 <li><a href="#"></a></li>
75 76 <li><a href="#"></a></li>
76 77 </ul>
77 78 </li>
78 79 <li class="divider"></li>
79 80 <li id="print_preview"><a href="#">Print Preview</a></li>
80 81 <li class="dropdown-submenu"><a href="#">Download as</a>
81 82 <ul class="dropdown-menu">
82 83 <li id="download_ipynb"><a href="#">IPython Notebook (.ipynb)</a></li>
83 84 <li id="download_py"><a href="#">Python (.py)</a></li>
84 85 <li id="download_html"><a href="#">HTML (.html)</a></li>
85 86 <li id="download_rst"><a href="#">reST (.rst)</a></li>
86 87 <li id="download_pdf"><a href="#">PDF (.pdf)</a></li>
87 88 </ul>
88 89 </li>
89 90 <li class="divider"></li>
90 91 <li id="trust_notebook"
91 92 title="Trust the output of this notebook">
92 93 <a href="#" >Trust Notebook</a></li>
93 94 <li class="divider"></li>
94 95 <li id="kill_and_exit"
95 96 title="Shutdown this notebook's kernel, and close this window">
96 97 <a href="#" >Close and halt</a></li>
97 98 </ul>
98 99 </li>
99 100 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Edit</a>
100 101 <ul id="edit_menu" class="dropdown-menu">
101 102 <li id="cut_cell"><a href="#">Cut Cell</a></li>
102 103 <li id="copy_cell"><a href="#">Copy Cell</a></li>
103 104 <li id="paste_cell_above" class="disabled"><a href="#">Paste Cell Above</a></li>
104 105 <li id="paste_cell_below" class="disabled"><a href="#">Paste Cell Below</a></li>
105 106 <li id="paste_cell_replace" class="disabled"><a href="#">Paste Cell &amp; Replace</a></li>
106 107 <li id="delete_cell"><a href="#">Delete Cell</a></li>
107 108 <li id="undelete_cell" class="disabled"><a href="#">Undo Delete Cell</a></li>
108 109 <li class="divider"></li>
109 110 <li id="split_cell"><a href="#">Split Cell</a></li>
110 111 <li id="merge_cell_above"><a href="#">Merge Cell Above</a></li>
111 112 <li id="merge_cell_below"><a href="#">Merge Cell Below</a></li>
112 113 <li class="divider"></li>
113 114 <li id="move_cell_up"><a href="#">Move Cell Up</a></li>
114 115 <li id="move_cell_down"><a href="#">Move Cell Down</a></li>
115 116 <li class="divider"></li>
116 117 <li id="edit_nb_metadata"><a href="#">Edit Notebook Metadata</a></li>
117 118 </ul>
118 119 </li>
119 120 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">View</a>
120 121 <ul id="view_menu" class="dropdown-menu">
121 122 <li id="toggle_header"
122 123 title="Show/Hide the IPython Notebook logo and notebook title (above menu bar)">
123 124 <a href="#">Toggle Header</a></li>
124 125 <li id="toggle_toolbar"
125 126 title="Show/Hide the action icons (below menu bar)">
126 127 <a href="#">Toggle Toolbar</a></li>
127 128 </ul>
128 129 </li>
129 130 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Insert</a>
130 131 <ul id="insert_menu" class="dropdown-menu">
131 132 <li id="insert_cell_above"
132 133 title="Insert an empty Code cell above the currently active cell">
133 134 <a href="#">Insert Cell Above</a></li>
134 135 <li id="insert_cell_below"
135 136 title="Insert an empty Code cell below the currently active cell">
136 137 <a href="#">Insert Cell Below</a></li>
137 138 </ul>
138 139 </li>
139 140 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Cell</a>
140 141 <ul id="cell_menu" class="dropdown-menu">
141 142 <li id="run_cell" title="Run this cell, and move cursor to the next one">
142 143 <a href="#">Run</a></li>
143 144 <li id="run_cell_select_below" title="Run this cell, select below">
144 145 <a href="#">Run and Select Below</a></li>
145 146 <li id="run_cell_insert_below" title="Run this cell, insert below">
146 147 <a href="#">Run and Insert Below</a></li>
147 148 <li id="run_all_cells" title="Run all cells in the notebook">
148 149 <a href="#">Run All</a></li>
149 150 <li id="run_all_cells_above" title="Run all cells above (but not including) this cell">
150 151 <a href="#">Run All Above</a></li>
151 152 <li id="run_all_cells_below" title="Run this cell and all cells below it">
152 153 <a href="#">Run All Below</a></li>
153 154 <li class="divider"></li>
154 155 <li id="change_cell_type" class="dropdown-submenu"
155 156 title="All cells in the notebook have a cell type. By default, new cells are created as 'Code' cells">
156 157 <a href="#">Cell Type</a>
157 158 <ul class="dropdown-menu">
158 159 <li id="to_code"
159 160 title="Contents will be sent to the kernel for execution, and output will display in the footer of cell">
160 161 <a href="#">Code</a></li>
161 162 <li id="to_markdown"
162 163 title="Contents will be rendered as HTML and serve as explanatory text">
163 164 <a href="#">Markdown</a></li>
164 165 <li id="to_raw"
165 166 title="Contents will pass through nbconvert unmodified">
166 167 <a href="#">Raw NBConvert</a></li>
167 168 <li id="to_heading1"><a href="#">Heading 1</a></li>
168 169 <li id="to_heading2"><a href="#">Heading 2</a></li>
169 170 <li id="to_heading3"><a href="#">Heading 3</a></li>
170 171 <li id="to_heading4"><a href="#">Heading 4</a></li>
171 172 <li id="to_heading5"><a href="#">Heading 5</a></li>
172 173 <li id="to_heading6"><a href="#">Heading 6</a></li>
173 174 </ul>
174 175 </li>
175 176 <li class="divider"></li>
176 177 <li id="current_outputs" class="dropdown-submenu"><a href="#">Current Output</a>
177 178 <ul class="dropdown-menu">
178 179 <li id="toggle_current_output"
179 180 title="Hide/Show the output of the current cell">
180 181 <a href="#">Toggle</a>
181 182 </li>
182 183 <li id="toggle_current_output_scroll"
183 184 title="Scroll the output of the current cell">
184 185 <a href="#">Toggle Scrolling</a>
185 186 </li>
186 187 <li id="clear_current_output"
187 188 title="Clear the output of the current cell">
188 189 <a href="#">Clear</a>
189 190 </li>
190 191 </ul>
191 192 </li>
192 193 <li id="all_outputs" class="dropdown-submenu"><a href="#">All Output</a>
193 194 <ul class="dropdown-menu">
194 195 <li id="toggle_all_output"
195 196 title="Hide/Show the output of all cells">
196 197 <a href="#">Toggle</a>
197 198 </li>
198 199 <li id="toggle_all_output_scroll"
199 200 title="Scroll the output of all cells">
200 201 <a href="#">Toggle Scrolling</a>
201 202 </li>
202 203 <li id="clear_all_output"
203 204 title="Clear the output of all cells">
204 205 <a href="#">Clear</a>
205 206 </li>
206 207 </ul>
207 208 </li>
208 209 </ul>
209 210 </li>
210 211 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Kernel</a>
211 212 <ul id="kernel_menu" class="dropdown-menu">
212 213 <li id="int_kernel"
213 214 title="Send KeyboardInterrupt (CTRL-C) to the Kernel">
214 215 <a href="#">Interrupt</a></li>
215 216 <li id="restart_kernel"
216 217 title="Restart the Kernel">
217 218 <a href="#">Restart</a></li>
218 219 </ul>
219 220 </li>
220 221 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Help</a>
221 222 <ul id="help_menu" class="dropdown-menu">
222 223 <li id="notebook_tour" title="A quick tour of the notebook user interface"><a href="#">User Interface Tour</a></li>
223 224 <li id="keyboard_shortcuts" title="Opens a tooltip with all keyboard shortcuts"><a href="#">Keyboard Shortcuts</a></li>
224 225 <li class="divider"></li>
225 226 {% set
226 227 sections = (
227 228 (
228 229 ("http://ipython.org/documentation.html","IPython Help",True),
229 230 ("http://nbviewer.ipython.org/github/ipython/ipython/tree/2.x/examples/Index.ipynb", "Notebook Help", True),
230 231 ),(
231 232 ("http://docs.python.org","Python",True),
232 233 ("http://help.github.com/articles/github-flavored-markdown","Markdown",True),
233 234 ("http://docs.scipy.org/doc/numpy/reference/","NumPy",True),
234 235 ("http://docs.scipy.org/doc/scipy/reference/","SciPy",True),
235 236 ("http://matplotlib.org/contents.html","Matplotlib",True),
236 237 ("http://docs.sympy.org/latest/index.html","SymPy",True),
237 238 ("http://pandas.pydata.org/pandas-docs/stable/","pandas", True)
238 239 )
239 240 )
240 241 %}
241 242
242 243 {% for helplinks in sections %}
243 244 {% for link in helplinks %}
244 245 <li><a href="{{link[0]}}" {{'target="_blank" title="Opens in a new window"' if link[2]}}>
245 246 {{'<i class="icon-external-link menu-icon pull-right"></i>' if link[2]}}
246 247 {{link[1]}}
247 248 </a></li>
248 249 {% endfor %}
249 250 {% if not loop.last %}
250 251 <li class="divider"></li>
251 252 {% endif %}
252 253 {% endfor %}
253 254 </li>
254 255 </ul>
255 256 </li>
256 257 </ul>
257 258 <ul class="nav navbar-nav navbar-right">
258 259 <div id="kernel_indicator">
259 260 <i id="kernel_indicator_icon"></i>
260 261 </div>
261 262 <div id="modal_indicator">
262 263 <i id="modal_indicator_icon"></i>
263 264 </div>
264 265 <div id="notification_area"></div>
265 266 </ul>
266 267 </div>
267 268 </div>
268 269 </div>
269 270 <div id="maintoolbar" class="navbar">
270 271 <div class="toolbar-inner navbar-inner navbar-nobg">
271 272 <div id="maintoolbar-container" class="container"></div>
272 273 </div>
273 274 </div>
274 275 </div>
275 276
276 277 <div id="ipython-main-app">
277 278
278 279 <div id="notebook_panel">
279 280 <div id="notebook"></div>
280 281 <div id="pager_splitter"></div>
281 282 <div id="pager">
282 283 <div id='pager_button_area'>
283 284 </div>
284 285 <div id="pager-container" class="container"></div>
285 286 </div>
286 287 </div>
287 288
288 289 </div>
289 290 <div id='tooltip' class='ipython_tooltip' style='display:none'></div>
290 291
291 292
292 293 {% endblock %}
293 294
294 295
295 296 {% block script %}
296 297 {{super()}}
297 298
298 299 <script src="{{ static_url("components/codemirror/lib/codemirror.js") }}" charset="utf-8"></script>
299 300 <script type="text/javascript">
300 301 CodeMirror.modeURL = "{{ static_url("components/codemirror/mode/%N/%N.js", include_version=False) }}";
301 302 </script>
302 303 <script src="{{ static_url("components/codemirror/addon/mode/loadmode.js") }}" charset="utf-8"></script>
303 304 <script src="{{ static_url("components/codemirror/addon/mode/multiplex.js") }}" charset="utf-8"></script>
304 305 <script src="{{ static_url("components/codemirror/addon/mode/overlay.js") }}" charset="utf-8"></script>
305 306 <script src="{{ static_url("components/codemirror/addon/edit/matchbrackets.js") }}" charset="utf-8"></script>
306 307 <script src="{{ static_url("components/codemirror/addon/edit/closebrackets.js") }}" charset="utf-8"></script>
307 308 <script src="{{ static_url("components/codemirror/addon/comment/comment.js") }}" charset="utf-8"></script>
308 309 <script src="{{ static_url("components/codemirror/mode/htmlmixed/htmlmixed.js") }}" charset="utf-8"></script>
309 310 <script src="{{ static_url("components/codemirror/mode/xml/xml.js") }}" charset="utf-8"></script>
310 311 <script src="{{ static_url("components/codemirror/mode/javascript/javascript.js") }}" charset="utf-8"></script>
311 312 <script src="{{ static_url("components/codemirror/mode/css/css.js") }}" charset="utf-8"></script>
312 313 <script src="{{ static_url("components/codemirror/mode/rst/rst.js") }}" charset="utf-8"></script>
313 314 <script src="{{ static_url("components/codemirror/mode/markdown/markdown.js") }}" charset="utf-8"></script>
314 315 <script src="{{ static_url("components/codemirror/mode/python/python.js") }}" charset="utf-8"></script>
315 316 <script src="{{ static_url("notebook/js/codemirror-ipython.js") }}" charset="utf-8"></script>
316 317 <script src="{{ static_url("notebook/js/codemirror-ipythongfm.js") }}" charset="utf-8"></script>
317 318
318 319 <script src="{{ static_url("notebook/js/main.js") }}" charset="utf-8"></script>
319 320
320 321 {% endblock %}
General Comments 0
You need to be logged in to leave comments. Login now