##// END OF EJS Templates
redirect /api/notebooks to /api/contents...
MinRK -
Show More
@@ -1,286 +1,303 b''
1 1 """Tornado handlers for the contents web service."""
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 json
7 7
8 8 from tornado import web
9 9
10 10 from IPython.html.utils import url_path_join, url_escape
11 11 from IPython.utils.jsonutil import date_default
12 12
13 13 from IPython.html.base.handlers import (IPythonHandler, json_errors,
14 14 file_path_regex, path_regex,
15 15 file_name_regex)
16 16
17 17
18 18 def sort_key(model):
19 19 """key function for case-insensitive sort by name and type"""
20 20 iname = model['name'].lower()
21 21 type_key = {
22 22 'directory' : '0',
23 23 'notebook' : '1',
24 24 'file' : '2',
25 25 }.get(model['type'], '9')
26 26 return u'%s%s' % (type_key, iname)
27 27
28 28 class ContentsHandler(IPythonHandler):
29 29
30 30 SUPPORTED_METHODS = (u'GET', u'PUT', u'PATCH', u'POST', u'DELETE')
31 31
32 32 def location_url(self, name, path):
33 33 """Return the full URL location of a file.
34 34
35 35 Parameters
36 36 ----------
37 37 name : unicode
38 38 The base name of the file, such as "foo.ipynb".
39 39 path : unicode
40 40 The API path of the file, such as "foo/bar".
41 41 """
42 42 return url_escape(url_path_join(
43 43 self.base_url, 'api', 'contents', path, name
44 44 ))
45 45
46 46 def _finish_model(self, model, location=True):
47 47 """Finish a JSON request with a model, setting relevant headers, etc."""
48 48 if location:
49 49 location = self.location_url(model['name'], model['path'])
50 50 self.set_header('Location', location)
51 51 self.set_header('Last-Modified', model['last_modified'])
52 52 self.finish(json.dumps(model, default=date_default))
53 53
54 54 @web.authenticated
55 55 @json_errors
56 56 def get(self, path='', name=None):
57 57 """Return a model for a file or directory.
58 58
59 59 A directory model contains a list of models (without content)
60 60 of the files and directories it contains.
61 61 """
62 62 path = path or ''
63 63 model = self.contents_manager.get_model(name=name, path=path)
64 64 if model['type'] == 'directory':
65 65 # group listing by type, then by name (case-insensitive)
66 66 # FIXME: sorting should be done in the frontends
67 67 model['content'].sort(key=sort_key)
68 68 self._finish_model(model, location=False)
69 69
70 70 @web.authenticated
71 71 @json_errors
72 72 def patch(self, path='', name=None):
73 73 """PATCH renames a notebook without re-uploading content."""
74 74 cm = self.contents_manager
75 75 if name is None:
76 76 raise web.HTTPError(400, u'Filename missing')
77 77 model = self.get_json_body()
78 78 if model is None:
79 79 raise web.HTTPError(400, u'JSON body missing')
80 80 model = cm.update(model, name, path)
81 81 self._finish_model(model)
82 82
83 83 def _copy(self, copy_from, path, copy_to=None):
84 84 """Copy a file, optionally specifying the new name.
85 85 """
86 86 self.log.info(u"Copying {copy_from} to {path}/{copy_to}".format(
87 87 copy_from=copy_from,
88 88 path=path,
89 89 copy_to=copy_to or '',
90 90 ))
91 91 model = self.contents_manager.copy(copy_from, copy_to, path)
92 92 self.set_status(201)
93 93 self._finish_model(model)
94 94
95 95 def _upload(self, model, path, name=None):
96 96 """Handle upload of a new file
97 97
98 98 If name specified, create it in path/name,
99 99 otherwise create a new untitled file in path.
100 100 """
101 101 self.log.info(u"Uploading file to %s/%s", path, name or '')
102 102 if name:
103 103 model['name'] = name
104 104
105 105 model = self.contents_manager.create_file(model, path)
106 106 self.set_status(201)
107 107 self._finish_model(model)
108 108
109 109 def _create_empty_file(self, path, name=None, ext='.ipynb'):
110 110 """Create an empty file in path
111 111
112 112 If name specified, create it in path/name.
113 113 """
114 114 self.log.info(u"Creating new file in %s/%s", path, name or '')
115 115 model = {}
116 116 if name:
117 117 model['name'] = name
118 118 model = self.contents_manager.create_file(model, path=path, ext=ext)
119 119 self.set_status(201)
120 120 self._finish_model(model)
121 121
122 122 def _save(self, model, path, name):
123 123 """Save an existing file."""
124 124 self.log.info(u"Saving file at %s/%s", path, name)
125 125 model = self.contents_manager.save(model, name, path)
126 126 if model['path'] != path.strip('/') or model['name'] != name:
127 127 # a rename happened, set Location header
128 128 location = True
129 129 else:
130 130 location = False
131 131 self._finish_model(model, location)
132 132
133 133 @web.authenticated
134 134 @json_errors
135 135 def post(self, path='', name=None):
136 136 """Create a new file or directory in the specified path.
137 137
138 138 POST creates new files or directories. The server always decides on the name.
139 139
140 140 POST /api/contents/path
141 141 New untitled notebook in path. If content specified, upload a
142 142 notebook, otherwise start empty.
143 143 POST /api/contents/path
144 144 with body {"copy_from" : "OtherNotebook.ipynb"}
145 145 New copy of OtherNotebook in path
146 146 """
147 147
148 148 if name is not None:
149 149 path = u'{}/{}'.format(path, name)
150 150
151 151 cm = self.contents_manager
152 152
153 153 if cm.file_exists(path):
154 154 raise web.HTTPError(400, "Cannot POST to existing files, use PUT instead.")
155 155
156 156 if not cm.path_exists(path):
157 157 raise web.HTTPError(404, "No such directory: %s" % path)
158 158
159 159 model = self.get_json_body()
160 160
161 161 if model is not None:
162 162 copy_from = model.get('copy_from')
163 163 ext = model.get('ext', '.ipynb')
164 164 if model.get('content') is not None:
165 165 if copy_from:
166 166 raise web.HTTPError(400, "Can't upload and copy at the same time.")
167 167 self._upload(model, path)
168 168 elif copy_from:
169 169 self._copy(copy_from, path)
170 170 else:
171 171 self._create_empty_file(path, ext=ext)
172 172 else:
173 173 self._create_empty_file(path)
174 174
175 175 @web.authenticated
176 176 @json_errors
177 177 def put(self, path='', name=None):
178 178 """Saves the file in the location specified by name and path.
179 179
180 180 PUT is very similar to POST, but the requester specifies the name,
181 181 whereas with POST, the server picks the name.
182 182
183 183 PUT /api/contents/path/Name.ipynb
184 184 Save notebook at ``path/Name.ipynb``. Notebook structure is specified
185 185 in `content` key of JSON request body. If content is not specified,
186 186 create a new empty notebook.
187 187 PUT /api/contents/path/Name.ipynb
188 188 with JSON body::
189 189
190 190 {
191 191 "copy_from" : "[path/to/]OtherNotebook.ipynb"
192 192 }
193 193
194 194 Copy OtherNotebook to Name
195 195 """
196 196 if name is None:
197 197 raise web.HTTPError(400, "name must be specified with PUT.")
198 198
199 199 model = self.get_json_body()
200 200 if model:
201 201 copy_from = model.get('copy_from')
202 202 if copy_from:
203 203 if model.get('content'):
204 204 raise web.HTTPError(400, "Can't upload and copy at the same time.")
205 205 self._copy(copy_from, path, name)
206 206 elif self.contents_manager.file_exists(name, path):
207 207 self._save(model, path, name)
208 208 else:
209 209 self._upload(model, path, name)
210 210 else:
211 211 self._create_empty_file(path, name)
212 212
213 213 @web.authenticated
214 214 @json_errors
215 215 def delete(self, path='', name=None):
216 216 """delete a file in the given path"""
217 217 cm = self.contents_manager
218 218 self.log.warn('delete %s:%s', path, name)
219 219 cm.delete(name, path)
220 220 self.set_status(204)
221 221 self.finish()
222 222
223 223
224 224 class CheckpointsHandler(IPythonHandler):
225 225
226 226 SUPPORTED_METHODS = ('GET', 'POST')
227 227
228 228 @web.authenticated
229 229 @json_errors
230 230 def get(self, path='', name=None):
231 231 """get lists checkpoints for a file"""
232 232 cm = self.contents_manager
233 233 checkpoints = cm.list_checkpoints(name, path)
234 234 data = json.dumps(checkpoints, default=date_default)
235 235 self.finish(data)
236 236
237 237 @web.authenticated
238 238 @json_errors
239 239 def post(self, path='', name=None):
240 240 """post creates a new checkpoint"""
241 241 cm = self.contents_manager
242 242 checkpoint = cm.create_checkpoint(name, path)
243 243 data = json.dumps(checkpoint, default=date_default)
244 244 location = url_path_join(self.base_url, 'api/contents',
245 245 path, name, 'checkpoints', checkpoint['id'])
246 246 self.set_header('Location', url_escape(location))
247 247 self.set_status(201)
248 248 self.finish(data)
249 249
250 250
251 251 class ModifyCheckpointsHandler(IPythonHandler):
252 252
253 253 SUPPORTED_METHODS = ('POST', 'DELETE')
254 254
255 255 @web.authenticated
256 256 @json_errors
257 257 def post(self, path, name, checkpoint_id):
258 258 """post restores a file from a checkpoint"""
259 259 cm = self.contents_manager
260 260 cm.restore_checkpoint(checkpoint_id, name, path)
261 261 self.set_status(204)
262 262 self.finish()
263 263
264 264 @web.authenticated
265 265 @json_errors
266 266 def delete(self, path, name, checkpoint_id):
267 267 """delete clears a checkpoint for a given file"""
268 268 cm = self.contents_manager
269 269 cm.delete_checkpoint(checkpoint_id, name, path)
270 270 self.set_status(204)
271 271 self.finish()
272 272
273
274 class NotebooksRedirectHandler(IPythonHandler):
275 """Redirect /api/notebooks to /api/contents"""
276 SUPPORTED_METHODS = ('GET', 'PUT', 'PATCH', 'POST', 'DELETE')
277
278 def get(self, path):
279 self.log.warn("/api/notebooks is deprecated, use /api/contents")
280 self.redirect(url_path_join(
281 self.base_url,
282 'api/contents',
283 path
284 ))
285
286 put = patch = post = delete = get
287
288
273 289 #-----------------------------------------------------------------------------
274 290 # URL to handler mappings
275 291 #-----------------------------------------------------------------------------
276 292
277 293
278 294 _checkpoint_id_regex = r"(?P<checkpoint_id>[\w-]+)"
279 295
280 296 default_handlers = [
281 297 (r"/api/contents%s/checkpoints" % file_path_regex, CheckpointsHandler),
282 298 (r"/api/contents%s/checkpoints/%s" % (file_path_regex, _checkpoint_id_regex),
283 299 ModifyCheckpointsHandler),
284 300 (r"/api/contents%s" % file_path_regex, ContentsHandler),
285 301 (r"/api/contents%s" % path_regex, ContentsHandler),
302 (r"/api/notebooks/?(.*)", NotebooksRedirectHandler),
286 303 ]
General Comments 0
You need to be logged in to leave comments. Login now