##// END OF EJS Templates
Updating import statements after moving notebook files around.
Brian E. Granger -
Show More
@@ -1,62 +1,62 b''
1 1 """Tornado handlers logging into the notebook.
2 2
3 3 Authors:
4 4
5 5 * Brian Granger
6 6 """
7 7
8 8 #-----------------------------------------------------------------------------
9 9 # Copyright (C) 2011 The IPython Development Team
10 10 #
11 11 # Distributed under the terms of the BSD License. The full license is in
12 12 # the file COPYING, distributed as part of this software.
13 13 #-----------------------------------------------------------------------------
14 14
15 15 #-----------------------------------------------------------------------------
16 16 # Imports
17 17 #-----------------------------------------------------------------------------
18 18
19 19 import uuid
20 20
21 21 from tornado.escape import url_escape
22 22
23 23 from IPython.lib.security import passwd_check
24 24
25 from .base import IPythonHandler
25 from ..base.handlers import IPythonHandler
26 26
27 27 #-----------------------------------------------------------------------------
28 28 # Handler
29 29 #-----------------------------------------------------------------------------
30 30
31 31 class LoginHandler(IPythonHandler):
32 32
33 33 def _render(self, message=None):
34 34 self.write(self.render_template('login.html',
35 35 next=url_escape(self.get_argument('next', default=self.base_project_url)),
36 36 message=message,
37 37 ))
38 38
39 39 def get(self):
40 40 if self.current_user:
41 41 self.redirect(self.get_argument('next', default=self.base_project_url))
42 42 else:
43 43 self._render()
44 44
45 45 def post(self):
46 46 pwd = self.get_argument('password', default=u'')
47 47 if self.login_available:
48 48 if passwd_check(self.password, pwd):
49 49 self.set_secure_cookie(self.cookie_name, str(uuid.uuid4()))
50 50 else:
51 51 self._render(message={'error': 'Invalid password'})
52 52 return
53 53
54 54 self.redirect(self.get_argument('next', default=self.base_project_url))
55 55
56 56
57 57 #-----------------------------------------------------------------------------
58 58 # URL to handler mappings
59 59 #-----------------------------------------------------------------------------
60 60
61 61
62 62 default_handlers = [(r"/login", LoginHandler)]
@@ -1,44 +1,44 b''
1 1 """Tornado handlers for logging out of the notebook.
2 2
3 3 Authors:
4 4
5 5 * Brian Granger
6 6 """
7 7
8 8 #-----------------------------------------------------------------------------
9 9 # Copyright (C) 2011 The IPython Development Team
10 10 #
11 11 # Distributed under the terms of the BSD License. The full license is in
12 12 # the file COPYING, distributed as part of this software.
13 13 #-----------------------------------------------------------------------------
14 14
15 15 #-----------------------------------------------------------------------------
16 16 # Imports
17 17 #-----------------------------------------------------------------------------
18 18
19 from .base import IPythonHandler
19 from ..base.handlers import IPythonHandler
20 20
21 21 #-----------------------------------------------------------------------------
22 22 # Handler
23 23 #-----------------------------------------------------------------------------
24 24
25 25
26 26 class LogoutHandler(IPythonHandler):
27 27
28 28 def get(self):
29 29 self.clear_login_cookie()
30 30 if self.login_available:
31 31 message = {'info': 'Successfully logged out.'}
32 32 else:
33 33 message = {'warning': 'Cannot log out. Notebook authentication '
34 34 'is disabled.'}
35 35 self.write(self.render_template('logout.html',
36 36 message=message))
37 37
38 38
39 39 #-----------------------------------------------------------------------------
40 40 # URL to handler mappings
41 41 #-----------------------------------------------------------------------------
42 42
43 43
44 44 default_handlers = [(r"/logout", LogoutHandler)] No newline at end of file
@@ -1,72 +1,72 b''
1 1 """Tornado handlers for cluster web service.
2 2
3 3 Authors:
4 4
5 5 * Brian Granger
6 6 """
7 7
8 8 #-----------------------------------------------------------------------------
9 9 # Copyright (C) 2011 The IPython Development Team
10 10 #
11 11 # Distributed under the terms of the BSD License. The full license is in
12 12 # the file COPYING, distributed as part of this software.
13 13 #-----------------------------------------------------------------------------
14 14
15 15 #-----------------------------------------------------------------------------
16 16 # Imports
17 17 #-----------------------------------------------------------------------------
18 18
19 19 from tornado import web
20 20
21 21 from zmq.utils import jsonapi
22 22
23 from .base import IPythonHandler
23 from ..base.handlers import IPythonHandler
24 24
25 25 #-----------------------------------------------------------------------------
26 26 # Cluster handlers
27 27 #-----------------------------------------------------------------------------
28 28
29 29
30 30 class MainClusterHandler(IPythonHandler):
31 31
32 32 @web.authenticated
33 33 def get(self):
34 34 self.finish(jsonapi.dumps(self.cluster_manager.list_profiles()))
35 35
36 36
37 37 class ClusterProfileHandler(IPythonHandler):
38 38
39 39 @web.authenticated
40 40 def get(self, profile):
41 41 self.finish(jsonapi.dumps(self.cluster_manager.profile_info(profile)))
42 42
43 43
44 44 class ClusterActionHandler(IPythonHandler):
45 45
46 46 @web.authenticated
47 47 def post(self, profile, action):
48 48 cm = self.cluster_manager
49 49 if action == 'start':
50 50 n = self.get_argument('n',default=None)
51 51 if n is None:
52 52 data = cm.start_cluster(profile)
53 53 else:
54 54 data = cm.start_cluster(profile, int(n))
55 55 if action == 'stop':
56 56 data = cm.stop_cluster(profile)
57 57 self.finish(jsonapi.dumps(data))
58 58
59 59
60 60 #-----------------------------------------------------------------------------
61 61 # URL to handler mappings
62 62 #-----------------------------------------------------------------------------
63 63
64 64
65 65 _cluster_action_regex = r"(?P<action>start|stop)"
66 66 _profile_regex = r"(?P<profile>[^\/]+)" # there is almost no text that is invalid
67 67
68 68 default_handlers = [
69 69 (r"/clusters", MainClusterHandler),
70 70 (r"/clusters/%s/%s" % (_profile_regex, _cluster_action_regex), ClusterActionHandler),
71 71 (r"/clusters/%s" % _profile_regex, ClusterProfileHandler),
72 72 ]
@@ -1,271 +1,271 b''
1 1 """Tornado handlers for the notebook.
2 2
3 3 Authors:
4 4
5 5 * Brian Granger
6 6 """
7 7
8 8 #-----------------------------------------------------------------------------
9 9 # Copyright (C) 2008-2011 The IPython Development Team
10 10 #
11 11 # Distributed under the terms of the BSD License. The full license is in
12 12 # the file COPYING, distributed as part of this software.
13 13 #-----------------------------------------------------------------------------
14 14
15 15 #-----------------------------------------------------------------------------
16 16 # Imports
17 17 #-----------------------------------------------------------------------------
18 18
19 19 import Cookie
20 20 import logging
21 21 from tornado import web
22 22 from tornado import websocket
23 23
24 24 from zmq.utils import jsonapi
25 25
26 26 from IPython.kernel.zmq.session import Session
27 27 from IPython.utils.jsonutil import date_default
28 28 from IPython.utils.py3compat import PY3
29 29
30 from .base import IPythonHandler
30 from ..base.handlers import IPythonHandler
31 31
32 32 #-----------------------------------------------------------------------------
33 33 # Kernel handlers
34 34 #-----------------------------------------------------------------------------
35 35
36 36
37 37 class MainKernelHandler(IPythonHandler):
38 38
39 39 @web.authenticated
40 40 def get(self):
41 41 km = self.kernel_manager
42 42 self.finish(jsonapi.dumps(km.list_kernel_ids()))
43 43
44 44 @web.authenticated
45 45 def post(self):
46 46 km = self.kernel_manager
47 47 nbm = self.notebook_manager
48 48 notebook_id = self.get_argument('notebook', default=None)
49 49 kernel_id = km.start_kernel(notebook_id, cwd=nbm.notebook_dir)
50 50 data = {'ws_url':self.ws_url,'kernel_id':kernel_id}
51 51 self.set_header('Location', '{0}kernels/{1}'.format(self.base_kernel_url, kernel_id))
52 52 self.finish(jsonapi.dumps(data))
53 53
54 54
55 55 class KernelHandler(IPythonHandler):
56 56
57 57 SUPPORTED_METHODS = ('DELETE')
58 58
59 59 @web.authenticated
60 60 def delete(self, kernel_id):
61 61 km = self.kernel_manager
62 62 km.shutdown_kernel(kernel_id)
63 63 self.set_status(204)
64 64 self.finish()
65 65
66 66
67 67 class KernelActionHandler(IPythonHandler):
68 68
69 69 @web.authenticated
70 70 def post(self, kernel_id, action):
71 71 km = self.kernel_manager
72 72 if action == 'interrupt':
73 73 km.interrupt_kernel(kernel_id)
74 74 self.set_status(204)
75 75 if action == 'restart':
76 76 km.restart_kernel(kernel_id)
77 77 data = {'ws_url':self.ws_url, 'kernel_id':kernel_id}
78 78 self.set_header('Location', '{0}kernels/{1}'.format(self.base_kernel_url, kernel_id))
79 79 self.write(jsonapi.dumps(data))
80 80 self.finish()
81 81
82 82
83 83 class ZMQStreamHandler(websocket.WebSocketHandler):
84 84
85 85 def clear_cookie(self, *args, **kwargs):
86 86 """meaningless for websockets"""
87 87 pass
88 88
89 89 def _reserialize_reply(self, msg_list):
90 90 """Reserialize a reply message using JSON.
91 91
92 92 This takes the msg list from the ZMQ socket, unserializes it using
93 93 self.session and then serializes the result using JSON. This method
94 94 should be used by self._on_zmq_reply to build messages that can
95 95 be sent back to the browser.
96 96 """
97 97 idents, msg_list = self.session.feed_identities(msg_list)
98 98 msg = self.session.unserialize(msg_list)
99 99 try:
100 100 msg['header'].pop('date')
101 101 except KeyError:
102 102 pass
103 103 try:
104 104 msg['parent_header'].pop('date')
105 105 except KeyError:
106 106 pass
107 107 msg.pop('buffers')
108 108 return jsonapi.dumps(msg, default=date_default)
109 109
110 110 def _on_zmq_reply(self, msg_list):
111 111 # Sometimes this gets triggered when the on_close method is scheduled in the
112 112 # eventloop but hasn't been called.
113 113 if self.stream.closed(): return
114 114 try:
115 115 msg = self._reserialize_reply(msg_list)
116 116 except Exception:
117 117 self.log.critical("Malformed message: %r" % msg_list, exc_info=True)
118 118 else:
119 119 self.write_message(msg)
120 120
121 121 def allow_draft76(self):
122 122 """Allow draft 76, until browsers such as Safari update to RFC 6455.
123 123
124 124 This has been disabled by default in tornado in release 2.2.0, and
125 125 support will be removed in later versions.
126 126 """
127 127 return True
128 128
129 129
130 130 class AuthenticatedZMQStreamHandler(ZMQStreamHandler, IPythonHandler):
131 131
132 132 def open(self, kernel_id):
133 133 self.kernel_id = kernel_id.decode('ascii')
134 134 self.session = Session(config=self.config)
135 135 self.save_on_message = self.on_message
136 136 self.on_message = self.on_first_message
137 137
138 138 def _inject_cookie_message(self, msg):
139 139 """Inject the first message, which is the document cookie,
140 140 for authentication."""
141 141 if not PY3 and isinstance(msg, unicode):
142 142 # Cookie constructor doesn't accept unicode strings
143 143 # under Python 2.x for some reason
144 144 msg = msg.encode('utf8', 'replace')
145 145 try:
146 146 identity, msg = msg.split(':', 1)
147 147 self.session.session = identity.decode('ascii')
148 148 except Exception:
149 149 logging.error("First ws message didn't have the form 'identity:[cookie]' - %r", msg)
150 150
151 151 try:
152 152 self.request._cookies = Cookie.SimpleCookie(msg)
153 153 except:
154 154 self.log.warn("couldn't parse cookie string: %s",msg, exc_info=True)
155 155
156 156 def on_first_message(self, msg):
157 157 self._inject_cookie_message(msg)
158 158 if self.get_current_user() is None:
159 159 self.log.warn("Couldn't authenticate WebSocket connection")
160 160 raise web.HTTPError(403)
161 161 self.on_message = self.save_on_message
162 162
163 163
164 164 class ZMQChannelHandler(AuthenticatedZMQStreamHandler):
165 165
166 166 @property
167 167 def max_msg_size(self):
168 168 return self.settings.get('max_msg_size', 65535)
169 169
170 170 def create_stream(self):
171 171 km = self.kernel_manager
172 172 meth = getattr(km, 'connect_%s' % self.channel)
173 173 self.zmq_stream = meth(self.kernel_id, identity=self.session.bsession)
174 174
175 175 def initialize(self, *args, **kwargs):
176 176 self.zmq_stream = None
177 177
178 178 def on_first_message(self, msg):
179 179 try:
180 180 super(ZMQChannelHandler, self).on_first_message(msg)
181 181 except web.HTTPError:
182 182 self.close()
183 183 return
184 184 try:
185 185 self.create_stream()
186 186 except web.HTTPError:
187 187 # WebSockets don't response to traditional error codes so we
188 188 # close the connection.
189 189 if not self.stream.closed():
190 190 self.stream.close()
191 191 self.close()
192 192 else:
193 193 self.zmq_stream.on_recv(self._on_zmq_reply)
194 194
195 195 def on_message(self, msg):
196 196 if len(msg) < self.max_msg_size:
197 197 msg = jsonapi.loads(msg)
198 198 self.session.send(self.zmq_stream, msg)
199 199
200 200 def on_close(self):
201 201 # This method can be called twice, once by self.kernel_died and once
202 202 # from the WebSocket close event. If the WebSocket connection is
203 203 # closed before the ZMQ streams are setup, they could be None.
204 204 if self.zmq_stream is not None and not self.zmq_stream.closed():
205 205 self.zmq_stream.on_recv(None)
206 206 self.zmq_stream.close()
207 207
208 208
209 209 class IOPubHandler(ZMQChannelHandler):
210 210 channel = 'iopub'
211 211
212 212 def create_stream(self):
213 213 super(IOPubHandler, self).create_stream()
214 214 km = self.kernel_manager
215 215 km.add_restart_callback(self.kernel_id, self.on_kernel_restarted)
216 216 km.add_restart_callback(self.kernel_id, self.on_restart_failed, 'dead')
217 217
218 218 def on_close(self):
219 219 km = self.kernel_manager
220 220 if self.kernel_id in km:
221 221 km.remove_restart_callback(
222 222 self.kernel_id, self.on_kernel_restarted,
223 223 )
224 224 km.remove_restart_callback(
225 225 self.kernel_id, self.on_restart_failed, 'dead',
226 226 )
227 227 super(IOPubHandler, self).on_close()
228 228
229 229 def _send_status_message(self, status):
230 230 msg = self.session.msg("status",
231 231 {'execution_state': status}
232 232 )
233 233 self.write_message(jsonapi.dumps(msg, default=date_default))
234 234
235 235 def on_kernel_restarted(self):
236 236 logging.warn("kernel %s restarted", self.kernel_id)
237 237 self._send_status_message('restarting')
238 238
239 239 def on_restart_failed(self):
240 240 logging.error("kernel %s restarted failed!", self.kernel_id)
241 241 self._send_status_message('dead')
242 242
243 243 def on_message(self, msg):
244 244 """IOPub messages make no sense"""
245 245 pass
246 246
247 247
248 248 class ShellHandler(ZMQChannelHandler):
249 249 channel = 'shell'
250 250
251 251
252 252 class StdinHandler(ZMQChannelHandler):
253 253 channel = 'stdin'
254 254
255 255
256 256 #-----------------------------------------------------------------------------
257 257 # URL to handler mappings
258 258 #-----------------------------------------------------------------------------
259 259
260 260
261 261 _kernel_id_regex = r"(?P<kernel_id>\w+-\w+-\w+-\w+-\w+)"
262 262 _kernel_action_regex = r"(?P<action>restart|interrupt)"
263 263
264 264 default_handlers = [
265 265 (r"/kernels", MainKernelHandler),
266 266 (r"/kernels/%s" % _kernel_id_regex, KernelHandler),
267 267 (r"/kernels/%s/%s" % (_kernel_id_regex, _kernel_action_regex), KernelActionHandler),
268 268 (r"/kernels/%s/iopub" % _kernel_id_regex, IOPubHandler),
269 269 (r"/kernels/%s/shell" % _kernel_id_regex, ShellHandler),
270 270 (r"/kernels/%s/stdin" % _kernel_id_regex, StdinHandler)
271 271 ]
@@ -1,748 +1,729 b''
1 1 # coding: utf-8
2 2 """A tornado based IPython notebook server.
3 3
4 4 Authors:
5 5
6 6 * Brian Granger
7 7 """
8 8 #-----------------------------------------------------------------------------
9 9 # Copyright (C) 2013 The IPython Development Team
10 10 #
11 11 # Distributed under the terms of the BSD License. The full license is in
12 12 # the file COPYING, distributed as part of this software.
13 13 #-----------------------------------------------------------------------------
14 14
15 15 #-----------------------------------------------------------------------------
16 16 # Imports
17 17 #-----------------------------------------------------------------------------
18 18
19 19 # stdlib
20 20 import errno
21 21 import logging
22 22 import os
23 23 import random
24 24 import select
25 25 import signal
26 26 import socket
27 27 import sys
28 28 import threading
29 29 import time
30 30 import uuid
31 31 import webbrowser
32 32
33 33
34 34 # Third party
35 35 # check for pyzmq 2.1.11
36 36 from IPython.utils.zmqrelated import check_for_zmq
37 37 check_for_zmq('2.1.11', 'IPython.frontend.html.notebook')
38 38
39 39 import zmq
40 40 from jinja2 import Environment, FileSystemLoader
41 41
42 42 # Install the pyzmq ioloop. This has to be done before anything else from
43 43 # tornado is imported.
44 44 from zmq.eventloop import ioloop
45 45 ioloop.install()
46 46
47 47 # check for tornado 2.1.0
48 48 msg = "The IPython Notebook requires tornado >= 2.1.0"
49 49 try:
50 50 import tornado
51 51 except ImportError:
52 52 raise ImportError(msg)
53 53 try:
54 54 version_info = tornado.version_info
55 55 except AttributeError:
56 56 raise ImportError(msg + ", but you have < 1.1.0")
57 57 if version_info < (2,1,0):
58 58 raise ImportError(msg + ", but you have %s" % tornado.version)
59 59
60 60 from tornado import httpserver
61 61 from tornado import web
62 62
63 63 # Our own libraries
64 64 from IPython.frontend.html.notebook import DEFAULT_STATIC_FILES_PATH
65 from .kernelmanager import MappingKernelManager
66 65
67 from .handlers.clustersapi import (
68 MainClusterHandler, ClusterProfileHandler, ClusterActionHandler
69 )
70 from .handlers.kernelsapi import (
71 MainKernelHandler, KernelHandler, KernelActionHandler,
72 IOPubHandler, StdinHandler, ShellHandler
73 )
74 from .handlers.notebooksapi import (
75 NotebookRootHandler, NotebookHandler,
76 NotebookCheckpointsHandler, ModifyNotebookCheckpointsHandler
77 )
78 from .handlers.tree import ProjectDashboardHandler
79 from .handlers.login import LoginHandler
80 from .handlers.logout import LogoutHandler
81 from .handlers.notebooks import (
82 NewHandler, NamedNotebookHandler,
83 NotebookCopyHandler, NotebookRedirectHandler
84 )
85
86 from .handlers.base import AuthenticatedFileHandler
87 from .handlers.files import FileFindHandler
66 from .kernels.kernelmanager import MappingKernelManager
67 from .notebooks.nbmanager import NotebookManager
68 from .notebooks.filenbmanager import FileNotebookManager
69 from .clusters.clustermanager import ClusterManager
88 70
89 from .nbmanager import NotebookManager
90 from .filenbmanager import FileNotebookManager
91 from .clustermanager import ClusterManager
71 from .base.handlers import AuthenticatedFileHandler
72 from .base.files import FileFindHandler
92 73
93 74 from IPython.config.application import catch_config_error, boolean_flag
94 75 from IPython.core.application import BaseIPythonApplication
95 76 from IPython.frontend.consoleapp import IPythonConsoleApp
96 77 from IPython.kernel import swallow_argv
97 78 from IPython.kernel.zmq.session import default_secure
98 79 from IPython.kernel.zmq.kernelapp import (
99 80 kernel_flags,
100 81 kernel_aliases,
101 82 )
102 83 from IPython.utils.importstring import import_item
103 84 from IPython.utils.localinterfaces import LOCALHOST
104 85 from IPython.utils import submodule
105 86 from IPython.utils.traitlets import (
106 87 Dict, Unicode, Integer, List, Bool,
107 88 DottedObjectName
108 89 )
109 90 from IPython.utils import py3compat
110 91 from IPython.utils.path import filefind
111 92
112 93 from .utils import url_path_join
113 94
114 95 #-----------------------------------------------------------------------------
115 96 # Module globals
116 97 #-----------------------------------------------------------------------------
117 98
118 99 _examples = """
119 100 ipython notebook # start the notebook
120 101 ipython notebook --profile=sympy # use the sympy profile
121 102 ipython notebook --pylab=inline # pylab in inline plotting mode
122 103 ipython notebook --certfile=mycert.pem # use SSL/TLS certificate
123 104 ipython notebook --port=5555 --ip=* # Listen on port 5555, all interfaces
124 105 """
125 106
126 107 #-----------------------------------------------------------------------------
127 108 # Helper functions
128 109 #-----------------------------------------------------------------------------
129 110
130 111 def random_ports(port, n):
131 112 """Generate a list of n random ports near the given port.
132 113
133 114 The first 5 ports will be sequential, and the remaining n-5 will be
134 115 randomly selected in the range [port-2*n, port+2*n].
135 116 """
136 117 for i in range(min(5, n)):
137 118 yield port + i
138 119 for i in range(n-5):
139 120 yield port + random.randint(-2*n, 2*n)
140 121
141 122 def load_handlers(name):
142 123 """Load the (URL pattern, handler) tuples for each component."""
143 name = 'IPython.frontend.html.notebook.handlers.' + name
124 name = 'IPython.frontend.html.notebook.' + name
144 125 mod = __import__(name, fromlist=['default_handlers'])
145 126 return mod.default_handlers
146 127
147 128 #-----------------------------------------------------------------------------
148 129 # The Tornado web application
149 130 #-----------------------------------------------------------------------------
150 131
151 132 class NotebookWebApplication(web.Application):
152 133
153 134 def __init__(self, ipython_app, kernel_manager, notebook_manager,
154 135 cluster_manager, log,
155 136 base_project_url, settings_overrides):
156 137
157 138 # Load the (URL pattern, handler) tuples for each component.
158 139 handlers = []
159 handlers.extend(load_handlers('base'))
160 handlers.extend(load_handlers('tree'))
161 handlers.extend(load_handlers('login'))
162 handlers.extend(load_handlers('logout'))
163 handlers.extend(load_handlers('notebooks'))
164 handlers.extend(load_handlers('kernelsapi'))
165 handlers.extend(load_handlers('notebooksapi'))
166 handlers.extend(load_handlers('clustersapi'))
140 handlers.extend(load_handlers('base.handlers'))
141 handlers.extend(load_handlers('tree.handlers'))
142 handlers.extend(load_handlers('auth.login'))
143 handlers.extend(load_handlers('auth.logout'))
144 handlers.extend(load_handlers('notebooks.handlers'))
145 handlers.extend(load_handlers('kernels.handlers'))
146 handlers.extend(load_handlers('notebooks.apihandlers'))
147 handlers.extend(load_handlers('clusters.handlers'))
167 148 handlers.extend([
168 149 (r"/files/(.*)", AuthenticatedFileHandler, {'path' : notebook_manager.notebook_dir}),
169 150 ])
170 151
171 152 # Python < 2.6.5 doesn't accept unicode keys in f(**kwargs), and
172 153 # base_project_url will always be unicode, which will in turn
173 154 # make the patterns unicode, and ultimately result in unicode
174 155 # keys in kwargs to handler._execute(**kwargs) in tornado.
175 156 # This enforces that base_project_url be ascii in that situation.
176 157 #
177 158 # Note that the URLs these patterns check against are escaped,
178 159 # and thus guaranteed to be ASCII: 'hΓ©llo' is really 'h%C3%A9llo'.
179 160 base_project_url = py3compat.unicode_to_str(base_project_url, 'ascii')
180 161 template_path = os.path.join(os.path.dirname(__file__), "templates")
181 162 settings = dict(
182 163 # basics
183 164 base_project_url=base_project_url,
184 165 base_kernel_url=ipython_app.base_kernel_url,
185 166 template_path=template_path,
186 167 static_path=ipython_app.static_file_path,
187 168 static_handler_class = FileFindHandler,
188 169 static_url_prefix = url_path_join(base_project_url,'/static/'),
189 170
190 171 # authentication
191 172 cookie_secret=os.urandom(1024),
192 173 login_url=url_path_join(base_project_url,'/login'),
193 174 cookie_name='username-%s' % uuid.uuid4(),
194 175 read_only=ipython_app.read_only,
195 176 password=ipython_app.password,
196 177
197 178 # managers
198 179 kernel_manager=kernel_manager,
199 180 notebook_manager=notebook_manager,
200 181 cluster_manager=cluster_manager,
201 182
202 183 # IPython stuff
203 184 mathjax_url=ipython_app.mathjax_url,
204 185 max_msg_size=ipython_app.max_msg_size,
205 186 config=ipython_app.config,
206 187 use_less=ipython_app.use_less,
207 188 jinja2_env=Environment(loader=FileSystemLoader(template_path)),
208 189 )
209 190
210 191 # allow custom overrides for the tornado web app.
211 192 settings.update(settings_overrides)
212 193
213 194 # prepend base_project_url onto the patterns that we match
214 195 new_handlers = []
215 196 for handler in handlers:
216 197 pattern = url_path_join(base_project_url, handler[0])
217 198 new_handler = tuple([pattern] + list(handler[1:]))
218 199 new_handlers.append(new_handler)
219 200
220 201 super(NotebookWebApplication, self).__init__(new_handlers, **settings)
221 202
222 203
223 204
224 205 #-----------------------------------------------------------------------------
225 206 # Aliases and Flags
226 207 #-----------------------------------------------------------------------------
227 208
228 209 flags = dict(kernel_flags)
229 210 flags['no-browser']=(
230 211 {'NotebookApp' : {'open_browser' : False}},
231 212 "Don't open the notebook in a browser after startup."
232 213 )
233 214 flags['no-mathjax']=(
234 215 {'NotebookApp' : {'enable_mathjax' : False}},
235 216 """Disable MathJax
236 217
237 218 MathJax is the javascript library IPython uses to render math/LaTeX. It is
238 219 very large, so you may want to disable it if you have a slow internet
239 220 connection, or for offline use of the notebook.
240 221
241 222 When disabled, equations etc. will appear as their untransformed TeX source.
242 223 """
243 224 )
244 225 flags['read-only'] = (
245 226 {'NotebookApp' : {'read_only' : True}},
246 227 """Allow read-only access to notebooks.
247 228
248 229 When using a password to protect the notebook server, this flag
249 230 allows unauthenticated clients to view the notebook list, and
250 231 individual notebooks, but not edit them, start kernels, or run
251 232 code.
252 233
253 234 If no password is set, the server will be entirely read-only.
254 235 """
255 236 )
256 237
257 238 # Add notebook manager flags
258 239 flags.update(boolean_flag('script', 'FileNotebookManager.save_script',
259 240 'Auto-save a .py script everytime the .ipynb notebook is saved',
260 241 'Do not auto-save .py scripts for every notebook'))
261 242
262 243 # the flags that are specific to the frontend
263 244 # these must be scrubbed before being passed to the kernel,
264 245 # or it will raise an error on unrecognized flags
265 246 notebook_flags = ['no-browser', 'no-mathjax', 'read-only', 'script', 'no-script']
266 247
267 248 aliases = dict(kernel_aliases)
268 249
269 250 aliases.update({
270 251 'ip': 'NotebookApp.ip',
271 252 'port': 'NotebookApp.port',
272 253 'port-retries': 'NotebookApp.port_retries',
273 254 'transport': 'KernelManager.transport',
274 255 'keyfile': 'NotebookApp.keyfile',
275 256 'certfile': 'NotebookApp.certfile',
276 257 'notebook-dir': 'NotebookManager.notebook_dir',
277 258 'browser': 'NotebookApp.browser',
278 259 })
279 260
280 261 # remove ipkernel flags that are singletons, and don't make sense in
281 262 # multi-kernel evironment:
282 263 aliases.pop('f', None)
283 264
284 265 notebook_aliases = [u'port', u'port-retries', u'ip', u'keyfile', u'certfile',
285 266 u'notebook-dir']
286 267
287 268 #-----------------------------------------------------------------------------
288 269 # NotebookApp
289 270 #-----------------------------------------------------------------------------
290 271
291 272 class NotebookApp(BaseIPythonApplication):
292 273
293 274 name = 'ipython-notebook'
294 275 default_config_file_name='ipython_notebook_config.py'
295 276
296 277 description = """
297 278 The IPython HTML Notebook.
298 279
299 280 This launches a Tornado based HTML Notebook Server that serves up an
300 281 HTML5/Javascript Notebook client.
301 282 """
302 283 examples = _examples
303 284
304 285 classes = IPythonConsoleApp.classes + [MappingKernelManager, NotebookManager,
305 286 FileNotebookManager]
306 287 flags = Dict(flags)
307 288 aliases = Dict(aliases)
308 289
309 290 kernel_argv = List(Unicode)
310 291
311 292 max_msg_size = Integer(65536, config=True, help="""
312 293 The max raw message size accepted from the browser
313 294 over a WebSocket connection.
314 295 """)
315 296
316 297 def _log_level_default(self):
317 298 return logging.INFO
318 299
319 300 def _log_format_default(self):
320 301 """override default log format to include time"""
321 302 return u"%(asctime)s.%(msecs).03d [%(name)s]%(highlevel)s %(message)s"
322 303
323 304 # create requested profiles by default, if they don't exist:
324 305 auto_create = Bool(True)
325 306
326 307 # file to be opened in the notebook server
327 308 file_to_run = Unicode('')
328 309
329 310 # Network related information.
330 311
331 312 ip = Unicode(LOCALHOST, config=True,
332 313 help="The IP address the notebook server will listen on."
333 314 )
334 315
335 316 def _ip_changed(self, name, old, new):
336 317 if new == u'*': self.ip = u''
337 318
338 319 port = Integer(8888, config=True,
339 320 help="The port the notebook server will listen on."
340 321 )
341 322 port_retries = Integer(50, config=True,
342 323 help="The number of additional ports to try if the specified port is not available."
343 324 )
344 325
345 326 certfile = Unicode(u'', config=True,
346 327 help="""The full path to an SSL/TLS certificate file."""
347 328 )
348 329
349 330 keyfile = Unicode(u'', config=True,
350 331 help="""The full path to a private key file for usage with SSL/TLS."""
351 332 )
352 333
353 334 password = Unicode(u'', config=True,
354 335 help="""Hashed password to use for web authentication.
355 336
356 337 To generate, type in a python/IPython shell:
357 338
358 339 from IPython.lib import passwd; passwd()
359 340
360 341 The string should be of the form type:salt:hashed-password.
361 342 """
362 343 )
363 344
364 345 open_browser = Bool(True, config=True,
365 346 help="""Whether to open in a browser after starting.
366 347 The specific browser used is platform dependent and
367 348 determined by the python standard library `webbrowser`
368 349 module, unless it is overridden using the --browser
369 350 (NotebookApp.browser) configuration option.
370 351 """)
371 352
372 353 browser = Unicode(u'', config=True,
373 354 help="""Specify what command to use to invoke a web
374 355 browser when opening the notebook. If not specified, the
375 356 default browser will be determined by the `webbrowser`
376 357 standard library module, which allows setting of the
377 358 BROWSER environment variable to override it.
378 359 """)
379 360
380 361 read_only = Bool(False, config=True,
381 362 help="Whether to prevent editing/execution of notebooks."
382 363 )
383 364
384 365 use_less = Bool(False, config=True,
385 366 help="""Wether to use Browser Side less-css parsing
386 367 instead of compiled css version in templates that allows
387 368 it. This is mainly convenient when working on the less
388 369 file to avoid a build step, or if user want to overwrite
389 370 some of the less variables without having to recompile
390 371 everything.
391 372
392 373 You will need to install the less.js component in the static directory
393 374 either in the source tree or in your profile folder.
394 375 """)
395 376
396 377 webapp_settings = Dict(config=True,
397 378 help="Supply overrides for the tornado.web.Application that the "
398 379 "IPython notebook uses.")
399 380
400 381 enable_mathjax = Bool(True, config=True,
401 382 help="""Whether to enable MathJax for typesetting math/TeX
402 383
403 384 MathJax is the javascript library IPython uses to render math/LaTeX. It is
404 385 very large, so you may want to disable it if you have a slow internet
405 386 connection, or for offline use of the notebook.
406 387
407 388 When disabled, equations etc. will appear as their untransformed TeX source.
408 389 """
409 390 )
410 391 def _enable_mathjax_changed(self, name, old, new):
411 392 """set mathjax url to empty if mathjax is disabled"""
412 393 if not new:
413 394 self.mathjax_url = u''
414 395
415 396 base_project_url = Unicode('/', config=True,
416 397 help='''The base URL for the notebook server.
417 398
418 399 Leading and trailing slashes can be omitted,
419 400 and will automatically be added.
420 401 ''')
421 402 def _base_project_url_changed(self, name, old, new):
422 403 if not new.startswith('/'):
423 404 self.base_project_url = '/'+new
424 405 elif not new.endswith('/'):
425 406 self.base_project_url = new+'/'
426 407
427 408 base_kernel_url = Unicode('/', config=True,
428 409 help='''The base URL for the kernel server
429 410
430 411 Leading and trailing slashes can be omitted,
431 412 and will automatically be added.
432 413 ''')
433 414 def _base_kernel_url_changed(self, name, old, new):
434 415 if not new.startswith('/'):
435 416 self.base_kernel_url = '/'+new
436 417 elif not new.endswith('/'):
437 418 self.base_kernel_url = new+'/'
438 419
439 420 websocket_host = Unicode("", config=True,
440 421 help="""The hostname for the websocket server."""
441 422 )
442 423
443 424 extra_static_paths = List(Unicode, config=True,
444 425 help="""Extra paths to search for serving static files.
445 426
446 427 This allows adding javascript/css to be available from the notebook server machine,
447 428 or overriding individual files in the IPython"""
448 429 )
449 430 def _extra_static_paths_default(self):
450 431 return [os.path.join(self.profile_dir.location, 'static')]
451 432
452 433 @property
453 434 def static_file_path(self):
454 435 """return extra paths + the default location"""
455 436 return self.extra_static_paths + [DEFAULT_STATIC_FILES_PATH]
456 437
457 438 mathjax_url = Unicode("", config=True,
458 439 help="""The url for MathJax.js."""
459 440 )
460 441 def _mathjax_url_default(self):
461 442 if not self.enable_mathjax:
462 443 return u''
463 444 static_url_prefix = self.webapp_settings.get("static_url_prefix",
464 445 "/static/")
465 446 try:
466 447 mathjax = filefind(os.path.join('mathjax', 'MathJax.js'), self.static_file_path)
467 448 except IOError:
468 449 if self.certfile:
469 450 # HTTPS: load from Rackspace CDN, because SSL certificate requires it
470 451 base = u"https://c328740.ssl.cf1.rackcdn.com"
471 452 else:
472 453 base = u"http://cdn.mathjax.org"
473 454
474 455 url = base + u"/mathjax/latest/MathJax.js"
475 456 self.log.info("Using MathJax from CDN: %s", url)
476 457 return url
477 458 else:
478 459 self.log.info("Using local MathJax from %s" % mathjax)
479 460 return static_url_prefix+u"mathjax/MathJax.js"
480 461
481 462 def _mathjax_url_changed(self, name, old, new):
482 463 if new and not self.enable_mathjax:
483 464 # enable_mathjax=False overrides mathjax_url
484 465 self.mathjax_url = u''
485 466 else:
486 467 self.log.info("Using MathJax: %s", new)
487 468
488 notebook_manager_class = DottedObjectName('IPython.frontend.html.notebook.filenbmanager.FileNotebookManager',
469 notebook_manager_class = DottedObjectName('IPython.frontend.html.notebook.notebooks.filenbmanager.FileNotebookManager',
489 470 config=True,
490 471 help='The notebook manager class to use.')
491 472
492 473 trust_xheaders = Bool(False, config=True,
493 474 help=("Whether to trust or not X-Scheme/X-Forwarded-Proto and X-Real-Ip/X-Forwarded-For headers"
494 475 "sent by the upstream reverse proxy. Neccesary if the proxy handles SSL")
495 476 )
496 477
497 478 def parse_command_line(self, argv=None):
498 479 super(NotebookApp, self).parse_command_line(argv)
499 480 if argv is None:
500 481 argv = sys.argv[1:]
501 482
502 483 # Scrub frontend-specific flags
503 484 self.kernel_argv = swallow_argv(argv, notebook_aliases, notebook_flags)
504 485 # Kernel should inherit default config file from frontend
505 486 self.kernel_argv.append("--IPKernelApp.parent_appname='%s'" % self.name)
506 487
507 488 if self.extra_args:
508 489 f = os.path.abspath(self.extra_args[0])
509 490 if os.path.isdir(f):
510 491 nbdir = f
511 492 else:
512 493 self.file_to_run = f
513 494 nbdir = os.path.dirname(f)
514 495 self.config.NotebookManager.notebook_dir = nbdir
515 496
516 497 def init_configurables(self):
517 498 # force Session default to be secure
518 499 default_secure(self.config)
519 500 self.kernel_manager = MappingKernelManager(
520 501 config=self.config, log=self.log, kernel_argv=self.kernel_argv,
521 502 connection_dir = self.profile_dir.security_dir,
522 503 )
523 504 kls = import_item(self.notebook_manager_class)
524 505 self.notebook_manager = kls(config=self.config, log=self.log)
525 506 self.notebook_manager.load_notebook_names()
526 507 self.cluster_manager = ClusterManager(config=self.config, log=self.log)
527 508 self.cluster_manager.update_profiles()
528 509
529 510 def init_logging(self):
530 511 # This prevents double log messages because tornado use a root logger that
531 512 # self.log is a child of. The logging module dipatches log messages to a log
532 513 # and all of its ancenstors until propagate is set to False.
533 514 self.log.propagate = False
534 515
535 516 # hook up tornado 3's loggers to our app handlers
536 517 for name in ('access', 'application', 'general'):
537 518 logging.getLogger('tornado.%s' % name).handlers = self.log.handlers
538 519
539 520 def init_webapp(self):
540 521 """initialize tornado webapp and httpserver"""
541 522 self.web_app = NotebookWebApplication(
542 523 self, self.kernel_manager, self.notebook_manager,
543 524 self.cluster_manager, self.log,
544 525 self.base_project_url, self.webapp_settings
545 526 )
546 527 if self.certfile:
547 528 ssl_options = dict(certfile=self.certfile)
548 529 if self.keyfile:
549 530 ssl_options['keyfile'] = self.keyfile
550 531 else:
551 532 ssl_options = None
552 533 self.web_app.password = self.password
553 534 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options,
554 535 xheaders=self.trust_xheaders)
555 536 if not self.ip:
556 537 warning = "WARNING: The notebook server is listening on all IP addresses"
557 538 if ssl_options is None:
558 539 self.log.critical(warning + " and not using encryption. This"
559 540 "is not recommended.")
560 541 if not self.password and not self.read_only:
561 542 self.log.critical(warning + "and not using authentication."
562 543 "This is highly insecure and not recommended.")
563 544 success = None
564 545 for port in random_ports(self.port, self.port_retries+1):
565 546 try:
566 547 self.http_server.listen(port, self.ip)
567 548 except socket.error as e:
568 549 # XXX: remove the e.errno == -9 block when we require
569 550 # tornado >= 3.0
570 551 if e.errno == -9 and tornado.version_info[0] < 3:
571 552 # The flags passed to socket.getaddrinfo from
572 553 # tornado.netutils.bind_sockets can cause "gaierror:
573 554 # [Errno -9] Address family for hostname not supported"
574 555 # when the interface is not associated, for example.
575 556 # Changing the flags to exclude socket.AI_ADDRCONFIG does
576 557 # not cause this error, but the only way to do this is to
577 558 # monkeypatch socket to remove the AI_ADDRCONFIG attribute
578 559 saved_AI_ADDRCONFIG = socket.AI_ADDRCONFIG
579 560 self.log.warn('Monkeypatching socket to fix tornado bug')
580 561 del(socket.AI_ADDRCONFIG)
581 562 try:
582 563 # retry the tornado call without AI_ADDRCONFIG flags
583 564 self.http_server.listen(port, self.ip)
584 565 except socket.error as e2:
585 566 e = e2
586 567 else:
587 568 self.port = port
588 569 success = True
589 570 break
590 571 # restore the monekypatch
591 572 socket.AI_ADDRCONFIG = saved_AI_ADDRCONFIG
592 573 if e.errno != errno.EADDRINUSE:
593 574 raise
594 575 self.log.info('The port %i is already in use, trying another random port.' % port)
595 576 else:
596 577 self.port = port
597 578 success = True
598 579 break
599 580 if not success:
600 581 self.log.critical('ERROR: the notebook server could not be started because '
601 582 'no available port could be found.')
602 583 self.exit(1)
603 584
604 585 def init_signal(self):
605 586 if not sys.platform.startswith('win'):
606 587 signal.signal(signal.SIGINT, self._handle_sigint)
607 588 signal.signal(signal.SIGTERM, self._signal_stop)
608 589 if hasattr(signal, 'SIGUSR1'):
609 590 # Windows doesn't support SIGUSR1
610 591 signal.signal(signal.SIGUSR1, self._signal_info)
611 592 if hasattr(signal, 'SIGINFO'):
612 593 # only on BSD-based systems
613 594 signal.signal(signal.SIGINFO, self._signal_info)
614 595
615 596 def _handle_sigint(self, sig, frame):
616 597 """SIGINT handler spawns confirmation dialog"""
617 598 # register more forceful signal handler for ^C^C case
618 599 signal.signal(signal.SIGINT, self._signal_stop)
619 600 # request confirmation dialog in bg thread, to avoid
620 601 # blocking the App
621 602 thread = threading.Thread(target=self._confirm_exit)
622 603 thread.daemon = True
623 604 thread.start()
624 605
625 606 def _restore_sigint_handler(self):
626 607 """callback for restoring original SIGINT handler"""
627 608 signal.signal(signal.SIGINT, self._handle_sigint)
628 609
629 610 def _confirm_exit(self):
630 611 """confirm shutdown on ^C
631 612
632 613 A second ^C, or answering 'y' within 5s will cause shutdown,
633 614 otherwise original SIGINT handler will be restored.
634 615
635 616 This doesn't work on Windows.
636 617 """
637 618 # FIXME: remove this delay when pyzmq dependency is >= 2.1.11
638 619 time.sleep(0.1)
639 620 info = self.log.info
640 621 info('interrupted')
641 622 print self.notebook_info()
642 623 sys.stdout.write("Shutdown this notebook server (y/[n])? ")
643 624 sys.stdout.flush()
644 625 r,w,x = select.select([sys.stdin], [], [], 5)
645 626 if r:
646 627 line = sys.stdin.readline()
647 628 if line.lower().startswith('y'):
648 629 self.log.critical("Shutdown confirmed")
649 630 ioloop.IOLoop.instance().stop()
650 631 return
651 632 else:
652 633 print "No answer for 5s:",
653 634 print "resuming operation..."
654 635 # no answer, or answer is no:
655 636 # set it back to original SIGINT handler
656 637 # use IOLoop.add_callback because signal.signal must be called
657 638 # from main thread
658 639 ioloop.IOLoop.instance().add_callback(self._restore_sigint_handler)
659 640
660 641 def _signal_stop(self, sig, frame):
661 642 self.log.critical("received signal %s, stopping", sig)
662 643 ioloop.IOLoop.instance().stop()
663 644
664 645 def _signal_info(self, sig, frame):
665 646 print self.notebook_info()
666 647
667 648 def init_components(self):
668 649 """Check the components submodule, and warn if it's unclean"""
669 650 status = submodule.check_submodule_status()
670 651 if status == 'missing':
671 652 self.log.warn("components submodule missing, running `git submodule update`")
672 653 submodule.update_submodules(submodule.ipython_parent())
673 654 elif status == 'unclean':
674 655 self.log.warn("components submodule unclean, you may see 404s on static/components")
675 656 self.log.warn("run `setup.py submodule` or `git submodule update` to update")
676 657
677 658
678 659 @catch_config_error
679 660 def initialize(self, argv=None):
680 661 self.init_logging()
681 662 super(NotebookApp, self).initialize(argv)
682 663 self.init_configurables()
683 664 self.init_components()
684 665 self.init_webapp()
685 666 self.init_signal()
686 667
687 668 def cleanup_kernels(self):
688 669 """Shutdown all kernels.
689 670
690 671 The kernels will shutdown themselves when this process no longer exists,
691 672 but explicit shutdown allows the KernelManagers to cleanup the connection files.
692 673 """
693 674 self.log.info('Shutting down kernels')
694 675 self.kernel_manager.shutdown_all()
695 676
696 677 def notebook_info(self):
697 678 "Return the current working directory and the server url information"
698 679 mgr_info = self.notebook_manager.info_string() + "\n"
699 680 return mgr_info +"The IPython Notebook is running at: %s" % self._url
700 681
701 682 def start(self):
702 683 """ Start the IPython Notebook server app, after initialization
703 684
704 685 This method takes no arguments so all configuration and initialization
705 686 must be done prior to calling this method."""
706 687 ip = self.ip if self.ip else '[all ip addresses on your system]'
707 688 proto = 'https' if self.certfile else 'http'
708 689 info = self.log.info
709 690 self._url = "%s://%s:%i%s" % (proto, ip, self.port,
710 691 self.base_project_url)
711 692 for line in self.notebook_info().split("\n"):
712 693 info(line)
713 694 info("Use Control-C to stop this server and shut down all kernels.")
714 695
715 696 if self.open_browser or self.file_to_run:
716 697 ip = self.ip or LOCALHOST
717 698 try:
718 699 browser = webbrowser.get(self.browser or None)
719 700 except webbrowser.Error as e:
720 701 self.log.warn('No web browser found: %s.' % e)
721 702 browser = None
722 703
723 704 if self.file_to_run:
724 705 name, _ = os.path.splitext(os.path.basename(self.file_to_run))
725 706 url = self.notebook_manager.rev_mapping.get(name, '')
726 707 else:
727 708 url = ''
728 709 if browser:
729 710 b = lambda : browser.open("%s://%s:%i%s%s" % (proto, ip,
730 711 self.port, self.base_project_url, url), new=2)
731 712 threading.Thread(target=b).start()
732 713 try:
733 714 ioloop.IOLoop.instance().start()
734 715 except KeyboardInterrupt:
735 716 info("Interrupted...")
736 717 finally:
737 718 self.cleanup_kernels()
738 719
739 720
740 721 #-----------------------------------------------------------------------------
741 722 # Main entry point
742 723 #-----------------------------------------------------------------------------
743 724
744 725 def launch_new_instance():
745 726 app = NotebookApp.instance()
746 727 app.initialize()
747 728 app.start()
748 729
@@ -1,156 +1,156 b''
1 1 """Tornado handlers for the notebooks web service.
2 2
3 3 Authors:
4 4
5 5 * Brian Granger
6 6 """
7 7
8 8 #-----------------------------------------------------------------------------
9 9 # Copyright (C) 2008-2011 The IPython Development Team
10 10 #
11 11 # Distributed under the terms of the BSD License. The full license is in
12 12 # the file COPYING, distributed as part of this software.
13 13 #-----------------------------------------------------------------------------
14 14
15 15 #-----------------------------------------------------------------------------
16 16 # Imports
17 17 #-----------------------------------------------------------------------------
18 18
19 19 from tornado import web
20 20
21 21 from zmq.utils import jsonapi
22 22
23 23 from IPython.utils.jsonutil import date_default
24 24
25 from .base import IPythonHandler, authenticate_unless_readonly
25 from ..base.handlers import IPythonHandler, authenticate_unless_readonly
26 26
27 27 #-----------------------------------------------------------------------------
28 28 # Notebook web service handlers
29 29 #-----------------------------------------------------------------------------
30 30
31 31 class NotebookRootHandler(IPythonHandler):
32 32
33 33 @authenticate_unless_readonly
34 34 def get(self):
35 35 nbm = self.notebook_manager
36 36 km = self.kernel_manager
37 37 files = nbm.list_notebooks()
38 38 for f in files :
39 39 f['kernel_id'] = km.kernel_for_notebook(f['notebook_id'])
40 40 self.finish(jsonapi.dumps(files))
41 41
42 42 @web.authenticated
43 43 def post(self):
44 44 nbm = self.notebook_manager
45 45 body = self.request.body.strip()
46 46 format = self.get_argument('format', default='json')
47 47 name = self.get_argument('name', default=None)
48 48 if body:
49 49 notebook_id = nbm.save_new_notebook(body, name=name, format=format)
50 50 else:
51 51 notebook_id = nbm.new_notebook()
52 52 self.set_header('Location', '{0}notebooks/{1}'.format(self.base_project_url, notebook_id))
53 53 self.finish(jsonapi.dumps(notebook_id))
54 54
55 55
56 56 class NotebookHandler(IPythonHandler):
57 57
58 58 SUPPORTED_METHODS = ('GET', 'PUT', 'DELETE')
59 59
60 60 @authenticate_unless_readonly
61 61 def get(self, notebook_id):
62 62 nbm = self.notebook_manager
63 63 format = self.get_argument('format', default='json')
64 64 last_mod, name, data = nbm.get_notebook(notebook_id, format)
65 65
66 66 if format == u'json':
67 67 self.set_header('Content-Type', 'application/json')
68 68 self.set_header('Content-Disposition','attachment; filename="%s.ipynb"' % name)
69 69 elif format == u'py':
70 70 self.set_header('Content-Type', 'application/x-python')
71 71 self.set_header('Content-Disposition','attachment; filename="%s.py"' % name)
72 72 self.set_header('Last-Modified', last_mod)
73 73 self.finish(data)
74 74
75 75 @web.authenticated
76 76 def put(self, notebook_id):
77 77 nbm = self.notebook_manager
78 78 format = self.get_argument('format', default='json')
79 79 name = self.get_argument('name', default=None)
80 80 nbm.save_notebook(notebook_id, self.request.body, name=name, format=format)
81 81 self.set_status(204)
82 82 self.finish()
83 83
84 84 @web.authenticated
85 85 def delete(self, notebook_id):
86 86 self.notebook_manager.delete_notebook(notebook_id)
87 87 self.set_status(204)
88 88 self.finish()
89 89
90 90
91 91 class NotebookCheckpointsHandler(IPythonHandler):
92 92
93 93 SUPPORTED_METHODS = ('GET', 'POST')
94 94
95 95 @web.authenticated
96 96 def get(self, notebook_id):
97 97 """get lists checkpoints for a notebook"""
98 98 nbm = self.notebook_manager
99 99 checkpoints = nbm.list_checkpoints(notebook_id)
100 100 data = jsonapi.dumps(checkpoints, default=date_default)
101 101 self.finish(data)
102 102
103 103 @web.authenticated
104 104 def post(self, notebook_id):
105 105 """post creates a new checkpoint"""
106 106 nbm = self.notebook_manager
107 107 checkpoint = nbm.create_checkpoint(notebook_id)
108 108 data = jsonapi.dumps(checkpoint, default=date_default)
109 109 self.set_header('Location', '{0}notebooks/{1}/checkpoints/{2}'.format(
110 110 self.base_project_url, notebook_id, checkpoint['checkpoint_id']
111 111 ))
112 112
113 113 self.finish(data)
114 114
115 115
116 116 class ModifyNotebookCheckpointsHandler(IPythonHandler):
117 117
118 118 SUPPORTED_METHODS = ('POST', 'DELETE')
119 119
120 120 @web.authenticated
121 121 def post(self, notebook_id, checkpoint_id):
122 122 """post restores a notebook from a checkpoint"""
123 123 nbm = self.notebook_manager
124 124 nbm.restore_checkpoint(notebook_id, checkpoint_id)
125 125 self.set_status(204)
126 126 self.finish()
127 127
128 128 @web.authenticated
129 129 def delete(self, notebook_id, checkpoint_id):
130 130 """delete clears a checkpoint for a given notebook"""
131 131 nbm = self.notebook_manager
132 132 nbm.delte_checkpoint(notebook_id, checkpoint_id)
133 133 self.set_status(204)
134 134 self.finish()
135 135
136 136
137 137 #-----------------------------------------------------------------------------
138 138 # URL to handler mappings
139 139 #-----------------------------------------------------------------------------
140 140
141 141
142 142 _notebook_id_regex = r"(?P<notebook_id>\w+-\w+-\w+-\w+-\w+)"
143 143 _checkpoint_id_regex = r"(?P<checkpoint_id>[\w-]+)"
144 144
145 145 default_handlers = [
146 146 (r"/notebooks", NotebookRootHandler),
147 147 (r"/notebooks/%s" % _notebook_id_regex, NotebookHandler),
148 148 (r"/notebooks/%s/checkpoints" % _notebook_id_regex, NotebookCheckpointsHandler),
149 149 (r"/notebooks/%s/checkpoints/%s" % (_notebook_id_regex, _checkpoint_id_regex),
150 150 ModifyNotebookCheckpointsHandler
151 151 ),
152 152 ]
153 153
154 154
155 155
156 156
@@ -1,91 +1,91 b''
1 1 """Tornado handlers for the live notebook view.
2 2
3 3 Authors:
4 4
5 5 * Brian Granger
6 6 """
7 7
8 8 #-----------------------------------------------------------------------------
9 9 # Copyright (C) 2011 The IPython Development Team
10 10 #
11 11 # Distributed under the terms of the BSD License. The full license is in
12 12 # the file COPYING, distributed as part of this software.
13 13 #-----------------------------------------------------------------------------
14 14
15 15 #-----------------------------------------------------------------------------
16 16 # Imports
17 17 #-----------------------------------------------------------------------------
18 18
19 19 import os
20 20 from tornado import web
21 21 HTTPError = web.HTTPError
22 22
23 from .base import IPythonHandler, authenticate_unless_readonly
23 from ..base.handlers import IPythonHandler, authenticate_unless_readonly
24 24 from ..utils import url_path_join
25 25
26 26 #-----------------------------------------------------------------------------
27 27 # Handlers
28 28 #-----------------------------------------------------------------------------
29 29
30 30
31 31 class NewHandler(IPythonHandler):
32 32
33 33 @web.authenticated
34 34 def get(self):
35 35 notebook_id = self.notebook_manager.new_notebook()
36 36 self.redirect(url_path_join(self.base_project_url, notebook_id))
37 37
38 38
39 39 class NamedNotebookHandler(IPythonHandler):
40 40
41 41 @authenticate_unless_readonly
42 42 def get(self, notebook_id):
43 43 nbm = self.notebook_manager
44 44 if not nbm.notebook_exists(notebook_id):
45 45 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)
46 46 self.write(self.render_template('notebook.html',
47 47 project=self.project,
48 48 notebook_id=notebook_id,
49 49 kill_kernel=False,
50 50 mathjax_url=self.mathjax_url,
51 51 )
52 52 )
53 53
54 54
55 55 class NotebookRedirectHandler(IPythonHandler):
56 56
57 57 @authenticate_unless_readonly
58 58 def get(self, notebook_name):
59 59 # strip trailing .ipynb:
60 60 notebook_name = os.path.splitext(notebook_name)[0]
61 61 notebook_id = self.notebook_manager.rev_mapping.get(notebook_name, '')
62 62 if notebook_id:
63 63 url = url_path_join(self.settings.get('base_project_url', '/'), notebook_id)
64 64 return self.redirect(url)
65 65 else:
66 66 raise HTTPError(404)
67 67
68 68
69 69 class NotebookCopyHandler(IPythonHandler):
70 70
71 71 @web.authenticated
72 72 def get(self, notebook_id):
73 73 notebook_id = self.notebook_manager.copy_notebook(notebook_id)
74 74 self.redirect(url_path_join(self.base_project_url, notebook_id))
75 75
76 76
77 77 #-----------------------------------------------------------------------------
78 78 # URL to handler mappings
79 79 #-----------------------------------------------------------------------------
80 80
81 81
82 82 _notebook_id_regex = r"(?P<notebook_id>\w+-\w+-\w+-\w+-\w+)"
83 83 _notebook_name_regex = r"(?P<notebook_name>.+\.ipynb)"
84 84
85 85 default_handlers = [
86 86 (r"/new", NewHandler),
87 87 (r"/%s" % _notebook_id_regex, NamedNotebookHandler),
88 88 (r"/%s" % _notebook_name_regex, NotebookRedirectHandler),
89 89 (r"/%s/copy" % _notebook_id_regex, NotebookCopyHandler),
90 90
91 91 ]
@@ -1,41 +1,41 b''
1 1 """Tornado handlers for the tree view.
2 2
3 3 Authors:
4 4
5 5 * Brian Granger
6 6 """
7 7
8 8 #-----------------------------------------------------------------------------
9 9 # Copyright (C) 2011 The IPython Development Team
10 10 #
11 11 # Distributed under the terms of the BSD License. The full license is in
12 12 # the file COPYING, distributed as part of this software.
13 13 #-----------------------------------------------------------------------------
14 14
15 15 #-----------------------------------------------------------------------------
16 16 # Imports
17 17 #-----------------------------------------------------------------------------
18 18
19 from .base import IPythonHandler, authenticate_unless_readonly
19 from ..base.handlers import IPythonHandler, authenticate_unless_readonly
20 20
21 21 #-----------------------------------------------------------------------------
22 22 # Handlers
23 23 #-----------------------------------------------------------------------------
24 24
25 25
26 26 class ProjectDashboardHandler(IPythonHandler):
27 27
28 28 @authenticate_unless_readonly
29 29 def get(self):
30 30 self.write(self.render_template('projectdashboard.html',
31 31 project=self.project,
32 32 project_component=self.project.split('/'),
33 33 ))
34 34
35 35
36 36 #-----------------------------------------------------------------------------
37 37 # URL to handler mappings
38 38 #-----------------------------------------------------------------------------
39 39
40 40
41 41 default_handlers = [(r"/", ProjectDashboardHandler)] No newline at end of file
General Comments 0
You need to be logged in to leave comments. Login now