##// END OF EJS Templates
update notebook creation handlers...
MinRK -
Show More
@@ -1,232 +1,271 b''
1 """Tornado handlers for the notebooks web service.
1 """Tornado handlers for the notebooks web service.
2
2
3 Authors:
3 Authors:
4
4
5 * Brian Granger
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 # Distributed under the terms of the BSD License. The full license is in
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
12 # the file COPYING, distributed as part of this software.
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 # Imports
16 # Imports
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18
18
19 import json
19 import json
20
20
21 from tornado import web
21 from tornado import web
22
22
23 from IPython.html.utils import url_path_join
23 from IPython.html.utils import url_path_join
24 from IPython.utils.jsonutil import date_default
24 from IPython.utils.jsonutil import date_default
25
25
26 from IPython.html.base.handlers import IPythonHandler, json_errors
26 from IPython.html.base.handlers import IPythonHandler, json_errors
27
27
28 #-----------------------------------------------------------------------------
28 #-----------------------------------------------------------------------------
29 # Notebook web service handlers
29 # Notebook web service handlers
30 #-----------------------------------------------------------------------------
30 #-----------------------------------------------------------------------------
31
31
32
32
33 class NotebookHandler(IPythonHandler):
33 class NotebookHandler(IPythonHandler):
34
34
35 SUPPORTED_METHODS = (u'GET', u'PUT', u'PATCH', u'POST', u'DELETE')
35 SUPPORTED_METHODS = (u'GET', u'PUT', u'PATCH', u'POST', u'DELETE')
36
36
37 def notebook_location(self, name, path=''):
37 def notebook_location(self, name, path=''):
38 """Return the full URL location of a notebook based.
38 """Return the full URL location of a notebook based.
39
39
40 Parameters
40 Parameters
41 ----------
41 ----------
42 name : unicode
42 name : unicode
43 The base name of the notebook, such as "foo.ipynb".
43 The base name of the notebook, such as "foo.ipynb".
44 path : unicode
44 path : unicode
45 The URL path of the notebook.
45 The URL path of the notebook.
46 """
46 """
47 return url_path_join(self.base_project_url, 'api', 'notebooks', path, name)
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 @web.authenticated
57 @web.authenticated
50 @json_errors
58 @json_errors
51 def get(self, path='', name=None):
59 def get(self, path='', name=None):
52 """Return a Notebook or list of notebooks.
60 """Return a Notebook or list of notebooks.
53
61
54 * GET with path and no notebook name lists notebooks in a directory
62 * GET with path and no notebook name lists notebooks in a directory
55 * GET with path and notebook name returns notebook JSON
63 * GET with path and notebook name returns notebook JSON
56 """
64 """
57 nbm = self.notebook_manager
65 nbm = self.notebook_manager
58 # Check to see if a notebook name was given
66 # Check to see if a notebook name was given
59 if name is None:
67 if name is None:
60 # List notebooks in 'path'
68 # List notebooks in 'path'
61 notebooks = nbm.list_notebooks(path)
69 notebooks = nbm.list_notebooks(path)
62 self.finish(json.dumps(notebooks, default=date_default))
70 self.finish(json.dumps(notebooks, default=date_default))
63 return
71 return
64 # get and return notebook representation
72 # get and return notebook representation
65 model = nbm.get_notebook_model(name, path)
73 model = nbm.get_notebook_model(name, path)
66 self.set_header(u'Last-Modified', model[u'last_modified'])
74 self._finish_model(model, location=False)
67 self.finish(json.dumps(model, default=date_default))
68
75
69 @web.authenticated
76 @web.authenticated
70 @json_errors
77 @json_errors
71 def patch(self, path='', name=None):
78 def patch(self, path='', name=None):
72 """PATCH renames a notebook without re-uploading content."""
79 """PATCH renames a notebook without re-uploading content."""
73 nbm = self.notebook_manager
80 nbm = self.notebook_manager
74 if name is None:
81 if name is None:
75 raise web.HTTPError(400, u'Notebook name missing')
82 raise web.HTTPError(400, u'Notebook name missing')
76 model = self.get_json_body()
83 model = self.get_json_body()
77 if model is None:
84 if model is None:
78 raise web.HTTPError(400, u'JSON body missing')
85 raise web.HTTPError(400, u'JSON body missing')
79 model = nbm.update_notebook_model(model, name, path)
86 model = nbm.update_notebook_model(model, name, path)
80 location = self.notebook_location(model[u'name'], model[u'path'])
87 self._finish_model(model)
81 self.set_header(u'Location', location)
88
82 self.set_header(u'Last-Modified', model[u'last_modified'])
89 def _copy_notebook(self, copy_from, path, copy_to=None):
83 self.finish(json.dumps(model, default=date_default))
90 """Copy a notebook in path, optionally specifying the new name.
84
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)
138
85 @web.authenticated
139 @web.authenticated
86 @json_errors
140 @json_errors
87 def post(self, path='', name=None):
141 def post(self, path='', name=None):
88 """Create a new notebook in the specified path.
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 POST /api/notebooks/path : new untitled notebook in path
146 POST /api/notebooks/path : new untitled notebook in path
93 POST /api/notebooks/path/notebook.ipynb : new notebook with name in path
147 If content specified, upload a notebook, otherwise start empty.
94 If content specified upload 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 model = self.get_json_body()
151 model = self.get_json_body()
98 if name is None:
152 copy = self.get_argument("copy", default="")
99 # creating new notebook, model doesn't make sense
153 if name is not None:
100 if model is not None:
154 raise web.HTTPError(400, "Only POST to directories. Use PUT for full names")
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 = {}
107 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
155
113 location = self.notebook_location(model[u'name'], model[u'path'])
156 if copy:
114 self.set_header(u'Location', location)
157 self._copy_notebook(copy, path)
115 self.set_header(u'Last-Modified', model[u'last_modified'])
158 elif model:
116 self.set_status(201)
159 self._upload_notebook(model, path)
117 self.finish(json.dumps(model, default=date_default))
160 else:
161 self._create_empty_notebook(path)
118
162
119 @web.authenticated
163 @web.authenticated
120 @json_errors
164 @json_errors
121 def put(self, path='', name=None):
165 def put(self, path='', name=None):
122 """saves the notebook in the location given by 'notebook_path'."""
166 """Saves the notebook in the location specified by name and path.
123 nbm = self.notebook_manager
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 model = self.get_json_body()
179 model = self.get_json_body()
125 if model is None:
180 copy = self.get_argument("copy", default="")
126 raise web.HTTPError(400, u'JSON body missing')
181 if copy:
127 nbm.save_notebook_model(model, name, path)
182 if model is not None:
128 self.finish(json.dumps(model, default=date_default))
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 @web.authenticated
193 @web.authenticated
131 @json_errors
194 @json_errors
132 def delete(self, path='', name=None):
195 def delete(self, path='', name=None):
133 """delete the notebook in the given notebook path"""
196 """delete the notebook in the given notebook path"""
134 nbm = self.notebook_manager
197 nbm = self.notebook_manager
135 nbm.delete_notebook_model(name, path)
198 nbm.delete_notebook_model(name, path)
136 self.set_status(204)
199 self.set_status(204)
137 self.finish()
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 class NotebookCheckpointsHandler(IPythonHandler):
203 class NotebookCheckpointsHandler(IPythonHandler):
164
204
165 SUPPORTED_METHODS = ('GET', 'POST')
205 SUPPORTED_METHODS = ('GET', 'POST')
166
206
167 @web.authenticated
207 @web.authenticated
168 @json_errors
208 @json_errors
169 def get(self, path='', name=None):
209 def get(self, path='', name=None):
170 """get lists checkpoints for a notebook"""
210 """get lists checkpoints for a notebook"""
171 nbm = self.notebook_manager
211 nbm = self.notebook_manager
172 checkpoints = nbm.list_checkpoints(name, path)
212 checkpoints = nbm.list_checkpoints(name, path)
173 data = json.dumps(checkpoints, default=date_default)
213 data = json.dumps(checkpoints, default=date_default)
174 self.finish(data)
214 self.finish(data)
175
215
176 @web.authenticated
216 @web.authenticated
177 @json_errors
217 @json_errors
178 def post(self, path='', name=None):
218 def post(self, path='', name=None):
179 """post creates a new checkpoint"""
219 """post creates a new checkpoint"""
180 nbm = self.notebook_manager
220 nbm = self.notebook_manager
181 checkpoint = nbm.create_checkpoint(name, path)
221 checkpoint = nbm.create_checkpoint(name, path)
182 data = json.dumps(checkpoint, default=date_default)
222 data = json.dumps(checkpoint, default=date_default)
183 location = url_path_join(self.base_project_url, 'api/notebooks',
223 location = url_path_join(self.base_project_url, 'api/notebooks',
184 path, name, 'checkpoints', checkpoint['id'])
224 path, name, 'checkpoints', checkpoint['id'])
185 self.set_header('Location', location)
225 self.set_header('Location', location)
186 self.set_status(201)
226 self.set_status(201)
187 self.finish(data)
227 self.finish(data)
188
228
189
229
190 class ModifyNotebookCheckpointsHandler(IPythonHandler):
230 class ModifyNotebookCheckpointsHandler(IPythonHandler):
191
231
192 SUPPORTED_METHODS = ('POST', 'DELETE')
232 SUPPORTED_METHODS = ('POST', 'DELETE')
193
233
194 @web.authenticated
234 @web.authenticated
195 @json_errors
235 @json_errors
196 def post(self, path, name, checkpoint_id):
236 def post(self, path, name, checkpoint_id):
197 """post restores a notebook from a checkpoint"""
237 """post restores a notebook from a checkpoint"""
198 nbm = self.notebook_manager
238 nbm = self.notebook_manager
199 nbm.restore_checkpoint(checkpoint_id, name, path)
239 nbm.restore_checkpoint(checkpoint_id, name, path)
200 self.set_status(204)
240 self.set_status(204)
201 self.finish()
241 self.finish()
202
242
203 @web.authenticated
243 @web.authenticated
204 @json_errors
244 @json_errors
205 def delete(self, path, name, checkpoint_id):
245 def delete(self, path, name, checkpoint_id):
206 """delete clears a checkpoint for a given notebook"""
246 """delete clears a checkpoint for a given notebook"""
207 nbm = self.notebook_manager
247 nbm = self.notebook_manager
208 nbm.delete_checkpoint(checkpoint_id, name, path)
248 nbm.delete_checkpoint(checkpoint_id, name, path)
209 self.set_status(204)
249 self.set_status(204)
210 self.finish()
250 self.finish()
211
251
212 #-----------------------------------------------------------------------------
252 #-----------------------------------------------------------------------------
213 # URL to handler mappings
253 # URL to handler mappings
214 #-----------------------------------------------------------------------------
254 #-----------------------------------------------------------------------------
215
255
216
256
217 _path_regex = r"(?P<path>(?:/.*)*)"
257 _path_regex = r"(?P<path>(?:/.*)*)"
218 _checkpoint_id_regex = r"(?P<checkpoint_id>[\w-]+)"
258 _checkpoint_id_regex = r"(?P<checkpoint_id>[\w-]+)"
219 _notebook_name_regex = r"(?P<name>[^/]+\.ipynb)"
259 _notebook_name_regex = r"(?P<name>[^/]+\.ipynb)"
220 _notebook_path_regex = "%s/%s" % (_path_regex, _notebook_name_regex)
260 _notebook_path_regex = "%s/%s" % (_path_regex, _notebook_name_regex)
221
261
222 default_handlers = [
262 default_handlers = [
223 (r"/api/notebooks%s/copy" % _notebook_path_regex, NotebookCopyHandler),
224 (r"/api/notebooks%s/checkpoints" % _notebook_path_regex, NotebookCheckpointsHandler),
263 (r"/api/notebooks%s/checkpoints" % _notebook_path_regex, NotebookCheckpointsHandler),
225 (r"/api/notebooks%s/checkpoints/%s" % (_notebook_path_regex, _checkpoint_id_regex),
264 (r"/api/notebooks%s/checkpoints/%s" % (_notebook_path_regex, _checkpoint_id_regex),
226 ModifyNotebookCheckpointsHandler),
265 ModifyNotebookCheckpointsHandler),
227 (r"/api/notebooks%s" % _notebook_path_regex, NotebookHandler),
266 (r"/api/notebooks%s" % _notebook_path_regex, NotebookHandler),
228 (r"/api/notebooks%s" % _path_regex, NotebookHandler),
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