##// END OF EJS Templates
update notebook creation handlers...
MinRK -
Show More
@@ -1,232 +1,271
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 # Copyright (C) 2008-2011 The IPython Development Team
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 json
20 20
21 21 from tornado import web
22 22
23 23 from IPython.html.utils import url_path_join
24 24 from IPython.utils.jsonutil import date_default
25 25
26 26 from IPython.html.base.handlers import IPythonHandler, json_errors
27 27
28 28 #-----------------------------------------------------------------------------
29 29 # Notebook web service handlers
30 30 #-----------------------------------------------------------------------------
31 31
32 32
33 33 class NotebookHandler(IPythonHandler):
34 34
35 35 SUPPORTED_METHODS = (u'GET', u'PUT', u'PATCH', u'POST', u'DELETE')
36 36
37 37 def notebook_location(self, name, path=''):
38 38 """Return the full URL location of a notebook based.
39 39
40 40 Parameters
41 41 ----------
42 42 name : unicode
43 43 The base name of the notebook, such as "foo.ipynb".
44 44 path : unicode
45 45 The URL path of the notebook.
46 46 """
47 47 return url_path_join(self.base_project_url, 'api', 'notebooks', path, name)
48 48
49 def _finish_model(self, model, location=True):
50 """Finish a JSON request with a model, setting relevant headers, etc."""
51 if location:
52 location = self.notebook_location(model['name'], model['path'])
53 self.set_header('Location', location)
54 self.set_header('Last-Modified', model['last_modified'])
55 self.finish(json.dumps(model, default=date_default))
56
49 57 @web.authenticated
50 58 @json_errors
51 59 def get(self, path='', name=None):
52 60 """Return a Notebook or list of notebooks.
53 61
54 62 * GET with path and no notebook name lists notebooks in a directory
55 63 * GET with path and notebook name returns notebook JSON
56 64 """
57 65 nbm = self.notebook_manager
58 66 # Check to see if a notebook name was given
59 67 if name is None:
60 68 # List notebooks in 'path'
61 69 notebooks = nbm.list_notebooks(path)
62 70 self.finish(json.dumps(notebooks, default=date_default))
63 71 return
64 72 # get and return notebook representation
65 73 model = nbm.get_notebook_model(name, path)
66 self.set_header(u'Last-Modified', model[u'last_modified'])
67 self.finish(json.dumps(model, default=date_default))
74 self._finish_model(model, location=False)
68 75
69 76 @web.authenticated
70 77 @json_errors
71 78 def patch(self, path='', name=None):
72 79 """PATCH renames a notebook without re-uploading content."""
73 80 nbm = self.notebook_manager
74 81 if name is None:
75 82 raise web.HTTPError(400, u'Notebook name missing')
76 83 model = self.get_json_body()
77 84 if model is None:
78 85 raise web.HTTPError(400, u'JSON body missing')
79 86 model = nbm.update_notebook_model(model, name, path)
80 location = self.notebook_location(model[u'name'], model[u'path'])
81 self.set_header(u'Location', location)
82 self.set_header(u'Last-Modified', model[u'last_modified'])
83 self.finish(json.dumps(model, default=date_default))
87 self._finish_model(model)
88
89 def _copy_notebook(self, copy_from, path, copy_to=None):
90 """Copy a notebook in path, optionally specifying the new name.
91
92 Only support copying within the same directory.
93 """
94 self.log.info(u"Copying notebook from %s/%s to %s/%s",
95 path, copy_from,
96 path, copy_to or '',
97 )
98 model = self.notebook_manager.copy_notebook(copy_from, copy_to, path)
99 self.set_status(201)
100 self._finish_model(model)
101
102 def _upload_notebook(self, model, path, name=None):
103 """Upload a notebook
104
105 If name specified, create it in path/name.
106 """
107 self.log.info(u"Uploading notebook to %s/%s", path, name or '')
108 if name:
109 model['name'] = name
110
111 model = self.notebook_manager.create_notebook_model(model, path)
112 self.set_status(201)
113 self._finish_model(model)
114
115 def _create_empty_notebook(self, path, name=None):
116 """Create an empty notebook in path
117
118 If name specified, create it in path/name.
119 """
120 self.log.info(u"Creating new notebook in %s/%s", path, name or '')
121 model = {}
122 if name:
123 model['name'] = name
124 model = self.notebook_manager.create_notebook_model(model, path=path)
125 self.set_status(201)
126 self._finish_model(model)
127
128 def _save_notebook(self, model, path, name):
129 """Save an existing notebook."""
130 self.log.info(u"Saving notebook at %s/%s", path, name)
131 model = self.notebook_manager.save_notebook_model(model, name, path)
132 if model['path'] != path.strip('/') or model['name'] != name:
133 # a rename happened, set Location header
134 location = True
135 else:
136 location = False
137 self._finish_model(model, location)
84 138
85 139 @web.authenticated
86 140 @json_errors
87 141 def post(self, path='', name=None):
88 142 """Create a new notebook in the specified path.
89 143
90 POST creates new notebooks.
144 POST creates new notebooks. The server always decides on the notebook name.
91 145
92 146 POST /api/notebooks/path : new untitled notebook in path
93 POST /api/notebooks/path/notebook.ipynb : new notebook with name in path
94 If content specified upload notebook, otherwise start empty.
147 If content specified, upload a notebook, otherwise start empty.
148 POST /api/notebooks/path?copy=OtherNotebook.ipynb : new copy of OtherNotebook in path
95 149 """
96 nbm = self.notebook_manager
150
97 151 model = self.get_json_body()
98 if name is None:
99 # creating new notebook, model doesn't make sense
100 if model is not None:
101 raise web.HTTPError(400, "Model not valid when creating untitled notebooks.")
102 model = nbm.create_notebook_model(path=path)
103 else:
104 if model is None:
105 self.log.info("Creating new Notebook at %s/%s", path, name)
106 model = {}
152 copy = self.get_argument("copy", default="")
153 if name is not None:
154 raise web.HTTPError(400, "Only POST to directories. Use PUT for full names")
155
156 if copy:
157 self._copy_notebook(copy, path)
158 elif model:
159 self._upload_notebook(model, path)
107 160 else:
108 self.log.info("Uploading Notebook to %s/%s", path, name)
109 # set the model name from the URL
110 model['name'] = name
111 model = nbm.create_notebook_model(model, path)
112
113 location = self.notebook_location(model[u'name'], model[u'path'])
114 self.set_header(u'Location', location)
115 self.set_header(u'Last-Modified', model[u'last_modified'])
116 self.set_status(201)
117 self.finish(json.dumps(model, default=date_default))
161 self._create_empty_notebook(path)
118 162
119 163 @web.authenticated
120 164 @json_errors
121 165 def put(self, path='', name=None):
122 """saves the notebook in the location given by 'notebook_path'."""
123 nbm = self.notebook_manager
166 """Saves the notebook in the location specified by name and path.
167
168 PUT /api/notebooks/path/Name.ipynb : Save notebook at path/Name.ipynb
169 Notebook structure is specified in `content` key of JSON request body.
170 If content is not specified, create a new empty notebook.
171 PUT /api/notebooks/path/Name.ipynb?copy=OtherNotebook.ipynb : copy OtherNotebook to Name
172
173 POST and PUT are basically the same. The only difference:
174
175 - with POST, server always picks the name, with PUT the requester does
176 """
177 if name is None:
178 raise web.HTTPError(400, "Only PUT to full names. Use POST for directories.")
124 179 model = self.get_json_body()
125 if model is None:
126 raise web.HTTPError(400, u'JSON body missing')
127 nbm.save_notebook_model(model, name, path)
128 self.finish(json.dumps(model, default=date_default))
180 copy = self.get_argument("copy", default="")
181 if copy:
182 if model is not None:
183 raise web.HTTPError(400)
184 self._copy_notebook(copy, path, name)
185 elif model:
186 if self.notebook_manager.notebook_exists(name, path):
187 self._save_notebook(model, path, name)
188 else:
189 self._upload_notebook(model, path, name)
190 else:
191 self._create_empty_notebook(path, name)
129 192
130 193 @web.authenticated
131 194 @json_errors
132 195 def delete(self, path='', name=None):
133 196 """delete the notebook in the given notebook path"""
134 197 nbm = self.notebook_manager
135 198 nbm.delete_notebook_model(name, path)
136 199 self.set_status(204)
137 200 self.finish()
138 201
139 class NotebookCopyHandler(IPythonHandler):
140
141 SUPPORTED_METHODS = ('POST')
142
143 @web.authenticated
144 @json_errors
145 def post(self, path='', name=None):
146 """Copy an existing notebook."""
147 nbm = self.notebook_manager
148 model = self.get_json_body()
149 if name is None:
150 raise web.HTTPError(400, "Notebook name required")
151 self.log.info("Copying Notebook %s/%s", path, name)
152 model = nbm.copy_notebook(name, path)
153 location = url_path_join(
154 self.base_project_url, 'api', 'notebooks',
155 model['path'], model['name'],
156 )
157 self.set_header(u'Location', location)
158 self.set_header(u'Last-Modified', model[u'last_modified'])
159 self.set_status(201)
160 self.finish(json.dumps(model, default=date_default))
161
162 202
163 203 class NotebookCheckpointsHandler(IPythonHandler):
164 204
165 205 SUPPORTED_METHODS = ('GET', 'POST')
166 206
167 207 @web.authenticated
168 208 @json_errors
169 209 def get(self, path='', name=None):
170 210 """get lists checkpoints for a notebook"""
171 211 nbm = self.notebook_manager
172 212 checkpoints = nbm.list_checkpoints(name, path)
173 213 data = json.dumps(checkpoints, default=date_default)
174 214 self.finish(data)
175 215
176 216 @web.authenticated
177 217 @json_errors
178 218 def post(self, path='', name=None):
179 219 """post creates a new checkpoint"""
180 220 nbm = self.notebook_manager
181 221 checkpoint = nbm.create_checkpoint(name, path)
182 222 data = json.dumps(checkpoint, default=date_default)
183 223 location = url_path_join(self.base_project_url, 'api/notebooks',
184 224 path, name, 'checkpoints', checkpoint['id'])
185 225 self.set_header('Location', location)
186 226 self.set_status(201)
187 227 self.finish(data)
188 228
189 229
190 230 class ModifyNotebookCheckpointsHandler(IPythonHandler):
191 231
192 232 SUPPORTED_METHODS = ('POST', 'DELETE')
193 233
194 234 @web.authenticated
195 235 @json_errors
196 236 def post(self, path, name, checkpoint_id):
197 237 """post restores a notebook from a checkpoint"""
198 238 nbm = self.notebook_manager
199 239 nbm.restore_checkpoint(checkpoint_id, name, path)
200 240 self.set_status(204)
201 241 self.finish()
202 242
203 243 @web.authenticated
204 244 @json_errors
205 245 def delete(self, path, name, checkpoint_id):
206 246 """delete clears a checkpoint for a given notebook"""
207 247 nbm = self.notebook_manager
208 248 nbm.delete_checkpoint(checkpoint_id, name, path)
209 249 self.set_status(204)
210 250 self.finish()
211 251
212 252 #-----------------------------------------------------------------------------
213 253 # URL to handler mappings
214 254 #-----------------------------------------------------------------------------
215 255
216 256
217 257 _path_regex = r"(?P<path>(?:/.*)*)"
218 258 _checkpoint_id_regex = r"(?P<checkpoint_id>[\w-]+)"
219 259 _notebook_name_regex = r"(?P<name>[^/]+\.ipynb)"
220 260 _notebook_path_regex = "%s/%s" % (_path_regex, _notebook_name_regex)
221 261
222 262 default_handlers = [
223 (r"/api/notebooks%s/copy" % _notebook_path_regex, NotebookCopyHandler),
224 263 (r"/api/notebooks%s/checkpoints" % _notebook_path_regex, NotebookCheckpointsHandler),
225 264 (r"/api/notebooks%s/checkpoints/%s" % (_notebook_path_regex, _checkpoint_id_regex),
226 265 ModifyNotebookCheckpointsHandler),
227 266 (r"/api/notebooks%s" % _notebook_path_regex, NotebookHandler),
228 267 (r"/api/notebooks%s" % _path_regex, NotebookHandler),
229 268 ]
230 269
231 270
232 271
General Comments 0
You need to be logged in to leave comments. Login now