##// END OF EJS Templates
mv services/notebooks services/contents
MinRK -
Show More
1 NO CONTENT: file renamed from IPython/html/services/notebooks/__init__.py to IPython/html/services/contents/__init__.py
NO CONTENT: file renamed from IPython/html/services/notebooks/__init__.py to IPython/html/services/contents/__init__.py
1 NO CONTENT: file renamed from IPython/html/services/notebooks/filenbmanager.py to IPython/html/services/contents/filenbmanager.py, modified file
NO CONTENT: file renamed from IPython/html/services/notebooks/filenbmanager.py to IPython/html/services/contents/filenbmanager.py, modified file
@@ -1,288 +1,287
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) 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, url_escape
23 from IPython.html.utils import url_path_join, url_escape
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 notebook_path_regex, path_regex,
27 notebook_path_regex, path_regex,
28 notebook_name_regex)
28 notebook_name_regex)
29
29
30 #-----------------------------------------------------------------------------
30 #-----------------------------------------------------------------------------
31 # Notebook web service handlers
31 # Notebook web service handlers
32 #-----------------------------------------------------------------------------
32 #-----------------------------------------------------------------------------
33
33
34
34
35 class NotebookHandler(IPythonHandler):
35 class NotebookHandler(IPythonHandler):
36
36
37 SUPPORTED_METHODS = (u'GET', u'PUT', u'PATCH', u'POST', u'DELETE')
37 SUPPORTED_METHODS = (u'GET', u'PUT', u'PATCH', u'POST', u'DELETE')
38
38
39 def notebook_location(self, name, path=''):
39 def notebook_location(self, name, path=''):
40 """Return the full URL location of a notebook based.
40 """Return the full URL location of a notebook based.
41
41
42 Parameters
42 Parameters
43 ----------
43 ----------
44 name : unicode
44 name : unicode
45 The base name of the notebook, such as "foo.ipynb".
45 The base name of the notebook, such as "foo.ipynb".
46 path : unicode
46 path : unicode
47 The URL path of the notebook.
47 The URL path of the notebook.
48 """
48 """
49 return url_escape(url_path_join(
49 return url_escape(url_path_join(
50 self.base_url, 'api', 'notebooks', path, name
50 self.base_url, 'api', 'notebooks', path, name
51 ))
51 ))
52
52
53 def _finish_model(self, model, location=True):
53 def _finish_model(self, model, location=True):
54 """Finish a JSON request with a model, setting relevant headers, etc."""
54 """Finish a JSON request with a model, setting relevant headers, etc."""
55 if location:
55 if location:
56 location = self.notebook_location(model['name'], model['path'])
56 location = self.notebook_location(model['name'], model['path'])
57 self.set_header('Location', location)
57 self.set_header('Location', location)
58 self.set_header('Last-Modified', model['last_modified'])
58 self.set_header('Last-Modified', model['last_modified'])
59 self.finish(json.dumps(model, default=date_default))
59 self.finish(json.dumps(model, default=date_default))
60
60
61 @web.authenticated
61 @web.authenticated
62 @json_errors
62 @json_errors
63 def get(self, path='', name=None):
63 def get(self, path='', name=None):
64 """Return a Notebook or list of notebooks.
64 """Return a Notebook or list of notebooks.
65
65
66 * GET with path and no notebook name lists notebooks in a directory
66 * GET with path and no notebook name lists notebooks in a directory
67 * GET with path and notebook name returns notebook JSON
67 * GET with path and notebook name returns notebook JSON
68 """
68 """
69 nbm = self.notebook_manager
69 nbm = self.notebook_manager
70 # Check to see if a notebook name was given
70 # Check to see if a notebook name was given
71 if name is None:
71 if name is None:
72 # TODO: Remove this after we create the contents web service and directories are
72 # TODO: Remove this after we create the contents web service and directories are
73 # no longer listed by the notebook web service. This should only handle notebooks
73 # no longer listed by the notebook web service. This should only handle notebooks
74 # and not directories.
74 # and not directories.
75 dirs = nbm.list_dirs(path)
75 dirs = nbm.list_dirs(path)
76 notebooks = []
76 notebooks = []
77 index = []
77 index = []
78 for nb in nbm.list_notebooks(path):
78 for nb in nbm.list_notebooks(path):
79 if nb['name'].lower() == 'index.ipynb':
79 if nb['name'].lower() == 'index.ipynb':
80 index.append(nb)
80 index.append(nb)
81 else:
81 else:
82 notebooks.append(nb)
82 notebooks.append(nb)
83 notebooks = index + dirs + notebooks
83 notebooks = index + dirs + notebooks
84 self.finish(json.dumps(notebooks, default=date_default))
84 self.finish(json.dumps(notebooks, default=date_default))
85 return
85 return
86 # get and return notebook representation
86 # get and return notebook representation
87 model = nbm.get_notebook(name, path)
87 model = nbm.get_notebook(name, path)
88 self._finish_model(model, location=False)
88 self._finish_model(model, location=False)
89
89
90 @web.authenticated
90 @web.authenticated
91 @json_errors
91 @json_errors
92 def patch(self, path='', name=None):
92 def patch(self, path='', name=None):
93 """PATCH renames a notebook without re-uploading content."""
93 """PATCH renames a notebook without re-uploading content."""
94 nbm = self.notebook_manager
94 nbm = self.notebook_manager
95 if name is None:
95 if name is None:
96 raise web.HTTPError(400, u'Notebook name missing')
96 raise web.HTTPError(400, u'Notebook name missing')
97 model = self.get_json_body()
97 model = self.get_json_body()
98 if model is None:
98 if model is None:
99 raise web.HTTPError(400, u'JSON body missing')
99 raise web.HTTPError(400, u'JSON body missing')
100 model = nbm.update_notebook(model, name, path)
100 model = nbm.update_notebook(model, name, path)
101 self._finish_model(model)
101 self._finish_model(model)
102
102
103 def _copy_notebook(self, copy_from, path, copy_to=None):
103 def _copy_notebook(self, copy_from, path, copy_to=None):
104 """Copy a notebook in path, optionally specifying the new name.
104 """Copy a notebook in path, optionally specifying the new name.
105
105
106 Only support copying within the same directory.
106 Only support copying within the same directory.
107 """
107 """
108 self.log.info(u"Copying notebook from %s/%s to %s/%s",
108 self.log.info(u"Copying notebook from %s/%s to %s/%s",
109 path, copy_from,
109 path, copy_from,
110 path, copy_to or '',
110 path, copy_to or '',
111 )
111 )
112 model = self.notebook_manager.copy_notebook(copy_from, copy_to, path)
112 model = self.notebook_manager.copy_notebook(copy_from, copy_to, path)
113 self.set_status(201)
113 self.set_status(201)
114 self._finish_model(model)
114 self._finish_model(model)
115
115
116 def _upload_notebook(self, model, path, name=None):
116 def _upload_notebook(self, model, path, name=None):
117 """Upload a notebook
117 """Upload a notebook
118
118
119 If name specified, create it in path/name.
119 If name specified, create it in path/name.
120 """
120 """
121 self.log.info(u"Uploading notebook to %s/%s", path, name or '')
121 self.log.info(u"Uploading notebook to %s/%s", path, name or '')
122 if name:
122 if name:
123 model['name'] = name
123 model['name'] = name
124
124
125 model = self.notebook_manager.create_notebook(model, path)
125 model = self.notebook_manager.create_notebook(model, path)
126 self.set_status(201)
126 self.set_status(201)
127 self._finish_model(model)
127 self._finish_model(model)
128
128
129 def _create_empty_notebook(self, path, name=None):
129 def _create_empty_notebook(self, path, name=None):
130 """Create an empty notebook in path
130 """Create an empty notebook in path
131
131
132 If name specified, create it in path/name.
132 If name specified, create it in path/name.
133 """
133 """
134 self.log.info(u"Creating new notebook in %s/%s", path, name or '')
134 self.log.info(u"Creating new notebook in %s/%s", path, name or '')
135 model = {}
135 model = {}
136 if name:
136 if name:
137 model['name'] = name
137 model['name'] = name
138 model = self.notebook_manager.create_notebook(model, path=path)
138 model = self.notebook_manager.create_notebook(model, path=path)
139 self.set_status(201)
139 self.set_status(201)
140 self._finish_model(model)
140 self._finish_model(model)
141
141
142 def _save_notebook(self, model, path, name):
142 def _save_notebook(self, model, path, name):
143 """Save an existing notebook."""
143 """Save an existing notebook."""
144 self.log.info(u"Saving notebook at %s/%s", path, name)
144 self.log.info(u"Saving notebook at %s/%s", path, name)
145 model = self.notebook_manager.save_notebook(model, name, path)
145 model = self.notebook_manager.save_notebook(model, name, path)
146 if model['path'] != path.strip('/') or model['name'] != name:
146 if model['path'] != path.strip('/') or model['name'] != name:
147 # a rename happened, set Location header
147 # a rename happened, set Location header
148 location = True
148 location = True
149 else:
149 else:
150 location = False
150 location = False
151 self._finish_model(model, location)
151 self._finish_model(model, location)
152
152
153 @web.authenticated
153 @web.authenticated
154 @json_errors
154 @json_errors
155 def post(self, path='', name=None):
155 def post(self, path='', name=None):
156 """Create a new notebook in the specified path.
156 """Create a new notebook in the specified path.
157
157
158 POST creates new notebooks. The server always decides on the notebook name.
158 POST creates new notebooks. The server always decides on the notebook name.
159
159
160 POST /api/notebooks/path
160 POST /api/notebooks/path
161 New untitled notebook in path. If content specified, upload a
161 New untitled notebook in path. If content specified, upload a
162 notebook, otherwise start empty.
162 notebook, otherwise start empty.
163 POST /api/notebooks/path?copy=OtherNotebook.ipynb
163 POST /api/notebooks/path?copy=OtherNotebook.ipynb
164 New copy of OtherNotebook in path
164 New copy of OtherNotebook in path
165 """
165 """
166
166
167 if name is not None:
167 if name is not None:
168 raise web.HTTPError(400, "Only POST to directories. Use PUT for full names.")
168 raise web.HTTPError(400, "Only POST to directories. Use PUT for full names.")
169
169
170 model = self.get_json_body()
170 model = self.get_json_body()
171
171
172 if model is not None:
172 if model is not None:
173 copy_from = model.get('copy_from')
173 copy_from = model.get('copy_from')
174 if copy_from:
174 if copy_from:
175 if model.get('content'):
175 if model.get('content'):
176 raise web.HTTPError(400, "Can't upload and copy at the same time.")
176 raise web.HTTPError(400, "Can't upload and copy at the same time.")
177 self._copy_notebook(copy_from, path)
177 self._copy_notebook(copy_from, path)
178 else:
178 else:
179 self._upload_notebook(model, path)
179 self._upload_notebook(model, path)
180 else:
180 else:
181 self._create_empty_notebook(path)
181 self._create_empty_notebook(path)
182
182
183 @web.authenticated
183 @web.authenticated
184 @json_errors
184 @json_errors
185 def put(self, path='', name=None):
185 def put(self, path='', name=None):
186 """Saves the notebook in the location specified by name and path.
186 """Saves the notebook in the location specified by name and path.
187
187
188 PUT is very similar to POST, but the requester specifies the name,
188 PUT is very similar to POST, but the requester specifies the name,
189 whereas with POST, the server picks the name.
189 whereas with POST, the server picks the name.
190
190
191 PUT /api/notebooks/path/Name.ipynb
191 PUT /api/notebooks/path/Name.ipynb
192 Save notebook at ``path/Name.ipynb``. Notebook structure is specified
192 Save notebook at ``path/Name.ipynb``. Notebook structure is specified
193 in `content` key of JSON request body. If content is not specified,
193 in `content` key of JSON request body. If content is not specified,
194 create a new empty notebook.
194 create a new empty notebook.
195 PUT /api/notebooks/path/Name.ipynb?copy=OtherNotebook.ipynb
195 PUT /api/notebooks/path/Name.ipynb?copy=OtherNotebook.ipynb
196 Copy OtherNotebook to Name
196 Copy OtherNotebook to Name
197 """
197 """
198 if name is None:
198 if name is None:
199 raise web.HTTPError(400, "Only PUT to full names. Use POST for directories.")
199 raise web.HTTPError(400, "Only PUT to full names. Use POST for directories.")
200
200
201 model = self.get_json_body()
201 model = self.get_json_body()
202 if model:
202 if model:
203 copy_from = model.get('copy_from')
203 copy_from = model.get('copy_from')
204 if copy_from:
204 if copy_from:
205 if model.get('content'):
205 if model.get('content'):
206 raise web.HTTPError(400, "Can't upload and copy at the same time.")
206 raise web.HTTPError(400, "Can't upload and copy at the same time.")
207 self._copy_notebook(copy_from, path, name)
207 self._copy_notebook(copy_from, path, name)
208 elif self.notebook_manager.notebook_exists(name, path):
208 elif self.notebook_manager.notebook_exists(name, path):
209 self._save_notebook(model, path, name)
209 self._save_notebook(model, path, name)
210 else:
210 else:
211 self._upload_notebook(model, path, name)
211 self._upload_notebook(model, path, name)
212 else:
212 else:
213 self._create_empty_notebook(path, name)
213 self._create_empty_notebook(path, name)
214
214
215 @web.authenticated
215 @web.authenticated
216 @json_errors
216 @json_errors
217 def delete(self, path='', name=None):
217 def delete(self, path='', name=None):
218 """delete the notebook in the given notebook path"""
218 """delete the notebook in the given notebook path"""
219 nbm = self.notebook_manager
219 nbm = self.notebook_manager
220 nbm.delete_notebook(name, path)
220 nbm.delete_notebook(name, path)
221 self.set_status(204)
221 self.set_status(204)
222 self.finish()
222 self.finish()
223
223
224
224
225 class NotebookCheckpointsHandler(IPythonHandler):
225 class NotebookCheckpointsHandler(IPythonHandler):
226
226
227 SUPPORTED_METHODS = ('GET', 'POST')
227 SUPPORTED_METHODS = ('GET', 'POST')
228
228
229 @web.authenticated
229 @web.authenticated
230 @json_errors
230 @json_errors
231 def get(self, path='', name=None):
231 def get(self, path='', name=None):
232 """get lists checkpoints for a notebook"""
232 """get lists checkpoints for a notebook"""
233 nbm = self.notebook_manager
233 nbm = self.notebook_manager
234 checkpoints = nbm.list_checkpoints(name, path)
234 checkpoints = nbm.list_checkpoints(name, path)
235 data = json.dumps(checkpoints, default=date_default)
235 data = json.dumps(checkpoints, default=date_default)
236 self.finish(data)
236 self.finish(data)
237
237
238 @web.authenticated
238 @web.authenticated
239 @json_errors
239 @json_errors
240 def post(self, path='', name=None):
240 def post(self, path='', name=None):
241 """post creates a new checkpoint"""
241 """post creates a new checkpoint"""
242 nbm = self.notebook_manager
242 nbm = self.notebook_manager
243 checkpoint = nbm.create_checkpoint(name, path)
243 checkpoint = nbm.create_checkpoint(name, path)
244 data = json.dumps(checkpoint, default=date_default)
244 data = json.dumps(checkpoint, default=date_default)
245 location = url_path_join(self.base_url, 'api/notebooks',
245 location = url_path_join(self.base_url, 'api/notebooks',
246 path, name, 'checkpoints', checkpoint['id'])
246 path, name, 'checkpoints', checkpoint['id'])
247 self.set_header('Location', url_escape(location))
247 self.set_header('Location', url_escape(location))
248 self.set_status(201)
248 self.set_status(201)
249 self.finish(data)
249 self.finish(data)
250
250
251
251
252 class ModifyNotebookCheckpointsHandler(IPythonHandler):
252 class ModifyNotebookCheckpointsHandler(IPythonHandler):
253
253
254 SUPPORTED_METHODS = ('POST', 'DELETE')
254 SUPPORTED_METHODS = ('POST', 'DELETE')
255
255
256 @web.authenticated
256 @web.authenticated
257 @json_errors
257 @json_errors
258 def post(self, path, name, checkpoint_id):
258 def post(self, path, name, checkpoint_id):
259 """post restores a notebook from a checkpoint"""
259 """post restores a notebook from a checkpoint"""
260 nbm = self.notebook_manager
260 nbm = self.notebook_manager
261 nbm.restore_checkpoint(checkpoint_id, name, path)
261 nbm.restore_checkpoint(checkpoint_id, name, path)
262 self.set_status(204)
262 self.set_status(204)
263 self.finish()
263 self.finish()
264
264
265 @web.authenticated
265 @web.authenticated
266 @json_errors
266 @json_errors
267 def delete(self, path, name, checkpoint_id):
267 def delete(self, path, name, checkpoint_id):
268 """delete clears a checkpoint for a given notebook"""
268 """delete clears a checkpoint for a given notebook"""
269 nbm = self.notebook_manager
269 nbm = self.notebook_manager
270 nbm.delete_checkpoint(checkpoint_id, name, path)
270 nbm.delete_checkpoint(checkpoint_id, name, path)
271 self.set_status(204)
271 self.set_status(204)
272 self.finish()
272 self.finish()
273
273
274 #-----------------------------------------------------------------------------
274 #-----------------------------------------------------------------------------
275 # URL to handler mappings
275 # URL to handler mappings
276 #-----------------------------------------------------------------------------
276 #-----------------------------------------------------------------------------
277
277
278
278
279 _checkpoint_id_regex = r"(?P<checkpoint_id>[\w-]+)"
279 _checkpoint_id_regex = r"(?P<checkpoint_id>[\w-]+)"
280
280
281 default_handlers = [
281 default_handlers = [
282 (r"/api/notebooks%s/checkpoints" % notebook_path_regex, NotebookCheckpointsHandler),
282 (r"/api/notebooks%s/checkpoints" % notebook_path_regex, NotebookCheckpointsHandler),
283 (r"/api/notebooks%s/checkpoints/%s" % (notebook_path_regex, _checkpoint_id_regex),
283 (r"/api/notebooks%s/checkpoints/%s" % (notebook_path_regex, _checkpoint_id_regex),
284 ModifyNotebookCheckpointsHandler),
284 ModifyNotebookCheckpointsHandler),
285 (r"/api/notebooks%s" % notebook_path_regex, NotebookHandler),
285 (r"/api/notebooks%s" % notebook_path_regex, NotebookHandler),
286 (r"/api/notebooks%s" % path_regex, NotebookHandler),
286 (r"/api/notebooks%s" % path_regex, NotebookHandler),
287 ]
287 ]
288
1 NO CONTENT: file renamed from IPython/html/services/notebooks/nbmanager.py to IPython/html/services/contents/nbmanager.py, modified file
NO CONTENT: file renamed from IPython/html/services/notebooks/nbmanager.py to IPython/html/services/contents/nbmanager.py, modified file
1 NO CONTENT: file renamed from IPython/html/services/notebooks/tests/__init__.py to IPython/html/services/contents/tests/__init__.py
NO CONTENT: file renamed from IPython/html/services/notebooks/tests/__init__.py to IPython/html/services/contents/tests/__init__.py
1 NO CONTENT: file renamed from IPython/html/services/notebooks/tests/test_nbmanager.py to IPython/html/services/contents/tests/test_nbmanager.py, modified file
NO CONTENT: file renamed from IPython/html/services/notebooks/tests/test_nbmanager.py to IPython/html/services/contents/tests/test_nbmanager.py, modified file
@@ -1,347 +1,346
1 # coding: utf-8
1 # coding: utf-8
2 """Test the notebooks webservice API."""
2 """Test the notebooks webservice API."""
3
3
4 import io
4 import io
5 import json
5 import json
6 import os
6 import os
7 import shutil
7 import shutil
8 from unicodedata import normalize
8 from unicodedata import normalize
9
9
10 pjoin = os.path.join
10 pjoin = os.path.join
11
11
12 import requests
12 import requests
13
13
14 from IPython.html.utils import url_path_join, url_escape
14 from IPython.html.utils import url_path_join, url_escape
15 from IPython.html.tests.launchnotebook import NotebookTestBase, assert_http_error
15 from IPython.html.tests.launchnotebook import NotebookTestBase, assert_http_error
16 from IPython.nbformat import current
16 from IPython.nbformat import current
17 from IPython.nbformat.current import (new_notebook, write, read, new_worksheet,
17 from IPython.nbformat.current import (new_notebook, write, read, new_worksheet,
18 new_heading_cell, to_notebook_json)
18 new_heading_cell, to_notebook_json)
19 from IPython.nbformat import v2
19 from IPython.nbformat import v2
20 from IPython.utils import py3compat
20 from IPython.utils import py3compat
21 from IPython.utils.data import uniq_stable
21 from IPython.utils.data import uniq_stable
22
22
23
23
24 # TODO: Remove this after we create the contents web service and directories are
24 # TODO: Remove this after we create the contents web service and directories are
25 # no longer listed by the notebook web service.
25 # no longer listed by the notebook web service.
26 def notebooks_only(nb_list):
26 def notebooks_only(nb_list):
27 return [nb for nb in nb_list if nb['type']=='notebook']
27 return [nb for nb in nb_list if nb['type']=='notebook']
28
28
29 def dirs_only(nb_list):
29 def dirs_only(nb_list):
30 return [x for x in nb_list if x['type']=='directory']
30 return [x for x in nb_list if x['type']=='directory']
31
31
32
32
33 class NBAPI(object):
33 class NBAPI(object):
34 """Wrapper for notebook API calls."""
34 """Wrapper for notebook API calls."""
35 def __init__(self, base_url):
35 def __init__(self, base_url):
36 self.base_url = base_url
36 self.base_url = base_url
37
37
38 def _req(self, verb, path, body=None):
38 def _req(self, verb, path, body=None):
39 response = requests.request(verb,
39 response = requests.request(verb,
40 url_path_join(self.base_url, 'api/notebooks', path),
40 url_path_join(self.base_url, 'api/notebooks', path),
41 data=body,
41 data=body,
42 )
42 )
43 response.raise_for_status()
43 response.raise_for_status()
44 return response
44 return response
45
45
46 def list(self, path='/'):
46 def list(self, path='/'):
47 return self._req('GET', path)
47 return self._req('GET', path)
48
48
49 def read(self, name, path='/'):
49 def read(self, name, path='/'):
50 return self._req('GET', url_path_join(path, name))
50 return self._req('GET', url_path_join(path, name))
51
51
52 def create_untitled(self, path='/'):
52 def create_untitled(self, path='/'):
53 return self._req('POST', path)
53 return self._req('POST', path)
54
54
55 def upload_untitled(self, body, path='/'):
55 def upload_untitled(self, body, path='/'):
56 return self._req('POST', path, body)
56 return self._req('POST', path, body)
57
57
58 def copy_untitled(self, copy_from, path='/'):
58 def copy_untitled(self, copy_from, path='/'):
59 body = json.dumps({'copy_from':copy_from})
59 body = json.dumps({'copy_from':copy_from})
60 return self._req('POST', path, body)
60 return self._req('POST', path, body)
61
61
62 def create(self, name, path='/'):
62 def create(self, name, path='/'):
63 return self._req('PUT', url_path_join(path, name))
63 return self._req('PUT', url_path_join(path, name))
64
64
65 def upload(self, name, body, path='/'):
65 def upload(self, name, body, path='/'):
66 return self._req('PUT', url_path_join(path, name), body)
66 return self._req('PUT', url_path_join(path, name), body)
67
67
68 def copy(self, copy_from, copy_to, path='/'):
68 def copy(self, copy_from, copy_to, path='/'):
69 body = json.dumps({'copy_from':copy_from})
69 body = json.dumps({'copy_from':copy_from})
70 return self._req('PUT', url_path_join(path, copy_to), body)
70 return self._req('PUT', url_path_join(path, copy_to), body)
71
71
72 def save(self, name, body, path='/'):
72 def save(self, name, body, path='/'):
73 return self._req('PUT', url_path_join(path, name), body)
73 return self._req('PUT', url_path_join(path, name), body)
74
74
75 def delete(self, name, path='/'):
75 def delete(self, name, path='/'):
76 return self._req('DELETE', url_path_join(path, name))
76 return self._req('DELETE', url_path_join(path, name))
77
77
78 def rename(self, name, path, new_name):
78 def rename(self, name, path, new_name):
79 body = json.dumps({'name': new_name})
79 body = json.dumps({'name': new_name})
80 return self._req('PATCH', url_path_join(path, name), body)
80 return self._req('PATCH', url_path_join(path, name), body)
81
81
82 def get_checkpoints(self, name, path):
82 def get_checkpoints(self, name, path):
83 return self._req('GET', url_path_join(path, name, 'checkpoints'))
83 return self._req('GET', url_path_join(path, name, 'checkpoints'))
84
84
85 def new_checkpoint(self, name, path):
85 def new_checkpoint(self, name, path):
86 return self._req('POST', url_path_join(path, name, 'checkpoints'))
86 return self._req('POST', url_path_join(path, name, 'checkpoints'))
87
87
88 def restore_checkpoint(self, name, path, checkpoint_id):
88 def restore_checkpoint(self, name, path, checkpoint_id):
89 return self._req('POST', url_path_join(path, name, 'checkpoints', checkpoint_id))
89 return self._req('POST', url_path_join(path, name, 'checkpoints', checkpoint_id))
90
90
91 def delete_checkpoint(self, name, path, checkpoint_id):
91 def delete_checkpoint(self, name, path, checkpoint_id):
92 return self._req('DELETE', url_path_join(path, name, 'checkpoints', checkpoint_id))
92 return self._req('DELETE', url_path_join(path, name, 'checkpoints', checkpoint_id))
93
93
94 class APITest(NotebookTestBase):
94 class APITest(NotebookTestBase):
95 """Test the kernels web service API"""
95 """Test the kernels web service API"""
96 dirs_nbs = [('', 'inroot'),
96 dirs_nbs = [('', 'inroot'),
97 ('Directory with spaces in', 'inspace'),
97 ('Directory with spaces in', 'inspace'),
98 (u'unicodΓ©', 'innonascii'),
98 (u'unicodΓ©', 'innonascii'),
99 ('foo', 'a'),
99 ('foo', 'a'),
100 ('foo', 'b'),
100 ('foo', 'b'),
101 ('foo', 'name with spaces'),
101 ('foo', 'name with spaces'),
102 ('foo', u'unicodΓ©'),
102 ('foo', u'unicodΓ©'),
103 ('foo/bar', 'baz'),
103 ('foo/bar', 'baz'),
104 ('ordering', 'A'),
104 ('ordering', 'A'),
105 ('ordering', 'b'),
105 ('ordering', 'b'),
106 ('ordering', 'C'),
106 ('ordering', 'C'),
107 (u'Γ₯ b', u'Γ§ d'),
107 (u'Γ₯ b', u'Γ§ d'),
108 ]
108 ]
109 hidden_dirs = ['.hidden', '__pycache__']
109 hidden_dirs = ['.hidden', '__pycache__']
110
110
111 dirs = uniq_stable([py3compat.cast_unicode(d) for (d,n) in dirs_nbs])
111 dirs = uniq_stable([py3compat.cast_unicode(d) for (d,n) in dirs_nbs])
112 del dirs[0] # remove ''
112 del dirs[0] # remove ''
113 top_level_dirs = {normalize('NFC', d.split('/')[0]) for d in dirs}
113 top_level_dirs = {normalize('NFC', d.split('/')[0]) for d in dirs}
114
114
115 def setUp(self):
115 def setUp(self):
116 nbdir = self.notebook_dir.name
116 nbdir = self.notebook_dir.name
117
117
118 for d in (self.dirs + self.hidden_dirs):
118 for d in (self.dirs + self.hidden_dirs):
119 d.replace('/', os.sep)
119 d.replace('/', os.sep)
120 if not os.path.isdir(pjoin(nbdir, d)):
120 if not os.path.isdir(pjoin(nbdir, d)):
121 os.mkdir(pjoin(nbdir, d))
121 os.mkdir(pjoin(nbdir, d))
122
122
123 for d, name in self.dirs_nbs:
123 for d, name in self.dirs_nbs:
124 d = d.replace('/', os.sep)
124 d = d.replace('/', os.sep)
125 with io.open(pjoin(nbdir, d, '%s.ipynb' % name), 'w',
125 with io.open(pjoin(nbdir, d, '%s.ipynb' % name), 'w',
126 encoding='utf-8') as f:
126 encoding='utf-8') as f:
127 nb = new_notebook(name=name)
127 nb = new_notebook(name=name)
128 write(nb, f, format='ipynb')
128 write(nb, f, format='ipynb')
129
129
130 self.nb_api = NBAPI(self.base_url())
130 self.nb_api = NBAPI(self.base_url())
131
131
132 def tearDown(self):
132 def tearDown(self):
133 nbdir = self.notebook_dir.name
133 nbdir = self.notebook_dir.name
134
134
135 for dname in (list(self.top_level_dirs) + self.hidden_dirs):
135 for dname in (list(self.top_level_dirs) + self.hidden_dirs):
136 shutil.rmtree(pjoin(nbdir, dname), ignore_errors=True)
136 shutil.rmtree(pjoin(nbdir, dname), ignore_errors=True)
137
137
138 if os.path.isfile(pjoin(nbdir, 'inroot.ipynb')):
138 if os.path.isfile(pjoin(nbdir, 'inroot.ipynb')):
139 os.unlink(pjoin(nbdir, 'inroot.ipynb'))
139 os.unlink(pjoin(nbdir, 'inroot.ipynb'))
140
140
141 def test_list_notebooks(self):
141 def test_list_notebooks(self):
142 nbs = notebooks_only(self.nb_api.list().json())
142 nbs = notebooks_only(self.nb_api.list().json())
143 self.assertEqual(len(nbs), 1)
143 self.assertEqual(len(nbs), 1)
144 self.assertEqual(nbs[0]['name'], 'inroot.ipynb')
144 self.assertEqual(nbs[0]['name'], 'inroot.ipynb')
145
145
146 nbs = notebooks_only(self.nb_api.list('/Directory with spaces in/').json())
146 nbs = notebooks_only(self.nb_api.list('/Directory with spaces in/').json())
147 self.assertEqual(len(nbs), 1)
147 self.assertEqual(len(nbs), 1)
148 self.assertEqual(nbs[0]['name'], 'inspace.ipynb')
148 self.assertEqual(nbs[0]['name'], 'inspace.ipynb')
149
149
150 nbs = notebooks_only(self.nb_api.list(u'/unicodΓ©/').json())
150 nbs = notebooks_only(self.nb_api.list(u'/unicodΓ©/').json())
151 self.assertEqual(len(nbs), 1)
151 self.assertEqual(len(nbs), 1)
152 self.assertEqual(nbs[0]['name'], 'innonascii.ipynb')
152 self.assertEqual(nbs[0]['name'], 'innonascii.ipynb')
153 self.assertEqual(nbs[0]['path'], u'unicodΓ©')
153 self.assertEqual(nbs[0]['path'], u'unicodΓ©')
154
154
155 nbs = notebooks_only(self.nb_api.list('/foo/bar/').json())
155 nbs = notebooks_only(self.nb_api.list('/foo/bar/').json())
156 self.assertEqual(len(nbs), 1)
156 self.assertEqual(len(nbs), 1)
157 self.assertEqual(nbs[0]['name'], 'baz.ipynb')
157 self.assertEqual(nbs[0]['name'], 'baz.ipynb')
158 self.assertEqual(nbs[0]['path'], 'foo/bar')
158 self.assertEqual(nbs[0]['path'], 'foo/bar')
159
159
160 nbs = notebooks_only(self.nb_api.list('foo').json())
160 nbs = notebooks_only(self.nb_api.list('foo').json())
161 self.assertEqual(len(nbs), 4)
161 self.assertEqual(len(nbs), 4)
162 nbnames = { normalize('NFC', n['name']) for n in nbs }
162 nbnames = { normalize('NFC', n['name']) for n in nbs }
163 expected = [ u'a.ipynb', u'b.ipynb', u'name with spaces.ipynb', u'unicodΓ©.ipynb']
163 expected = [ u'a.ipynb', u'b.ipynb', u'name with spaces.ipynb', u'unicodΓ©.ipynb']
164 expected = { normalize('NFC', name) for name in expected }
164 expected = { normalize('NFC', name) for name in expected }
165 self.assertEqual(nbnames, expected)
165 self.assertEqual(nbnames, expected)
166
166
167 nbs = notebooks_only(self.nb_api.list('ordering').json())
167 nbs = notebooks_only(self.nb_api.list('ordering').json())
168 nbnames = [n['name'] for n in nbs]
168 nbnames = [n['name'] for n in nbs]
169 expected = ['A.ipynb', 'b.ipynb', 'C.ipynb']
169 expected = ['A.ipynb', 'b.ipynb', 'C.ipynb']
170 self.assertEqual(nbnames, expected)
170 self.assertEqual(nbnames, expected)
171
171
172 def test_list_dirs(self):
172 def test_list_dirs(self):
173 dirs = dirs_only(self.nb_api.list().json())
173 dirs = dirs_only(self.nb_api.list().json())
174 dir_names = {normalize('NFC', d['name']) for d in dirs}
174 dir_names = {normalize('NFC', d['name']) for d in dirs}
175 self.assertEqual(dir_names, self.top_level_dirs) # Excluding hidden dirs
175 self.assertEqual(dir_names, self.top_level_dirs) # Excluding hidden dirs
176
176
177 def test_list_nonexistant_dir(self):
177 def test_list_nonexistant_dir(self):
178 with assert_http_error(404):
178 with assert_http_error(404):
179 self.nb_api.list('nonexistant')
179 self.nb_api.list('nonexistant')
180
180
181 def test_get_contents(self):
181 def test_get_contents(self):
182 for d, name in self.dirs_nbs:
182 for d, name in self.dirs_nbs:
183 nb = self.nb_api.read('%s.ipynb' % name, d+'/').json()
183 nb = self.nb_api.read('%s.ipynb' % name, d+'/').json()
184 self.assertEqual(nb['name'], u'%s.ipynb' % name)
184 self.assertEqual(nb['name'], u'%s.ipynb' % name)
185 self.assertIn('content', nb)
185 self.assertIn('content', nb)
186 self.assertIn('metadata', nb['content'])
186 self.assertIn('metadata', nb['content'])
187 self.assertIsInstance(nb['content']['metadata'], dict)
187 self.assertIsInstance(nb['content']['metadata'], dict)
188
188
189 # Name that doesn't exist - should be a 404
189 # Name that doesn't exist - should be a 404
190 with assert_http_error(404):
190 with assert_http_error(404):
191 self.nb_api.read('q.ipynb', 'foo')
191 self.nb_api.read('q.ipynb', 'foo')
192
192
193 def _check_nb_created(self, resp, name, path):
193 def _check_nb_created(self, resp, name, path):
194 self.assertEqual(resp.status_code, 201)
194 self.assertEqual(resp.status_code, 201)
195 location_header = py3compat.str_to_unicode(resp.headers['Location'])
195 location_header = py3compat.str_to_unicode(resp.headers['Location'])
196 self.assertEqual(location_header, url_escape(url_path_join(u'/api/notebooks', path, name)))
196 self.assertEqual(location_header, url_escape(url_path_join(u'/api/notebooks', path, name)))
197 self.assertEqual(resp.json()['name'], name)
197 self.assertEqual(resp.json()['name'], name)
198 assert os.path.isfile(pjoin(
198 assert os.path.isfile(pjoin(
199 self.notebook_dir.name,
199 self.notebook_dir.name,
200 path.replace('/', os.sep),
200 path.replace('/', os.sep),
201 name,
201 name,
202 ))
202 ))
203
203
204 def test_create_untitled(self):
204 def test_create_untitled(self):
205 resp = self.nb_api.create_untitled(path=u'Γ₯ b')
205 resp = self.nb_api.create_untitled(path=u'Γ₯ b')
206 self._check_nb_created(resp, 'Untitled0.ipynb', u'Γ₯ b')
206 self._check_nb_created(resp, 'Untitled0.ipynb', u'Γ₯ b')
207
207
208 # Second time
208 # Second time
209 resp = self.nb_api.create_untitled(path=u'Γ₯ b')
209 resp = self.nb_api.create_untitled(path=u'Γ₯ b')
210 self._check_nb_created(resp, 'Untitled1.ipynb', u'Γ₯ b')
210 self._check_nb_created(resp, 'Untitled1.ipynb', u'Γ₯ b')
211
211
212 # And two directories down
212 # And two directories down
213 resp = self.nb_api.create_untitled(path='foo/bar')
213 resp = self.nb_api.create_untitled(path='foo/bar')
214 self._check_nb_created(resp, 'Untitled0.ipynb', 'foo/bar')
214 self._check_nb_created(resp, 'Untitled0.ipynb', 'foo/bar')
215
215
216 def test_upload_untitled(self):
216 def test_upload_untitled(self):
217 nb = new_notebook(name='Upload test')
217 nb = new_notebook(name='Upload test')
218 nbmodel = {'content': nb}
218 nbmodel = {'content': nb}
219 resp = self.nb_api.upload_untitled(path=u'Γ₯ b',
219 resp = self.nb_api.upload_untitled(path=u'Γ₯ b',
220 body=json.dumps(nbmodel))
220 body=json.dumps(nbmodel))
221 self._check_nb_created(resp, 'Untitled0.ipynb', u'Γ₯ b')
221 self._check_nb_created(resp, 'Untitled0.ipynb', u'Γ₯ b')
222
222
223 def test_upload(self):
223 def test_upload(self):
224 nb = new_notebook(name=u'ignored')
224 nb = new_notebook(name=u'ignored')
225 nbmodel = {'content': nb}
225 nbmodel = {'content': nb}
226 resp = self.nb_api.upload(u'Upload tΓ©st.ipynb', path=u'Γ₯ b',
226 resp = self.nb_api.upload(u'Upload tΓ©st.ipynb', path=u'Γ₯ b',
227 body=json.dumps(nbmodel))
227 body=json.dumps(nbmodel))
228 self._check_nb_created(resp, u'Upload tΓ©st.ipynb', u'Γ₯ b')
228 self._check_nb_created(resp, u'Upload tΓ©st.ipynb', u'Γ₯ b')
229
229
230 def test_upload_v2(self):
230 def test_upload_v2(self):
231 nb = v2.new_notebook()
231 nb = v2.new_notebook()
232 ws = v2.new_worksheet()
232 ws = v2.new_worksheet()
233 nb.worksheets.append(ws)
233 nb.worksheets.append(ws)
234 ws.cells.append(v2.new_code_cell(input='print("hi")'))
234 ws.cells.append(v2.new_code_cell(input='print("hi")'))
235 nbmodel = {'content': nb}
235 nbmodel = {'content': nb}
236 resp = self.nb_api.upload(u'Upload tΓ©st.ipynb', path=u'Γ₯ b',
236 resp = self.nb_api.upload(u'Upload tΓ©st.ipynb', path=u'Γ₯ b',
237 body=json.dumps(nbmodel))
237 body=json.dumps(nbmodel))
238 self._check_nb_created(resp, u'Upload tΓ©st.ipynb', u'Γ₯ b')
238 self._check_nb_created(resp, u'Upload tΓ©st.ipynb', u'Γ₯ b')
239 resp = self.nb_api.read(u'Upload tΓ©st.ipynb', u'Γ₯ b')
239 resp = self.nb_api.read(u'Upload tΓ©st.ipynb', u'Γ₯ b')
240 data = resp.json()
240 data = resp.json()
241 self.assertEqual(data['content']['nbformat'], current.nbformat)
241 self.assertEqual(data['content']['nbformat'], current.nbformat)
242 self.assertEqual(data['content']['orig_nbformat'], 2)
242 self.assertEqual(data['content']['orig_nbformat'], 2)
243
243
244 def test_copy_untitled(self):
244 def test_copy_untitled(self):
245 resp = self.nb_api.copy_untitled(u'Γ§ d.ipynb', path=u'Γ₯ b')
245 resp = self.nb_api.copy_untitled(u'Γ§ d.ipynb', path=u'Γ₯ b')
246 self._check_nb_created(resp, u'Γ§ d-Copy0.ipynb', u'Γ₯ b')
246 self._check_nb_created(resp, u'Γ§ d-Copy0.ipynb', u'Γ₯ b')
247
247
248 def test_copy(self):
248 def test_copy(self):
249 resp = self.nb_api.copy(u'Γ§ d.ipynb', u'cΓΈpy.ipynb', path=u'Γ₯ b')
249 resp = self.nb_api.copy(u'Γ§ d.ipynb', u'cΓΈpy.ipynb', path=u'Γ₯ b')
250 self._check_nb_created(resp, u'cΓΈpy.ipynb', u'Γ₯ b')
250 self._check_nb_created(resp, u'cΓΈpy.ipynb', u'Γ₯ b')
251
251
252 def test_delete(self):
252 def test_delete(self):
253 for d, name in self.dirs_nbs:
253 for d, name in self.dirs_nbs:
254 resp = self.nb_api.delete('%s.ipynb' % name, d)
254 resp = self.nb_api.delete('%s.ipynb' % name, d)
255 self.assertEqual(resp.status_code, 204)
255 self.assertEqual(resp.status_code, 204)
256
256
257 for d in self.dirs + ['/']:
257 for d in self.dirs + ['/']:
258 nbs = notebooks_only(self.nb_api.list(d).json())
258 nbs = notebooks_only(self.nb_api.list(d).json())
259 self.assertEqual(len(nbs), 0)
259 self.assertEqual(len(nbs), 0)
260
260
261 def test_rename(self):
261 def test_rename(self):
262 resp = self.nb_api.rename('a.ipynb', 'foo', 'z.ipynb')
262 resp = self.nb_api.rename('a.ipynb', 'foo', 'z.ipynb')
263 self.assertEqual(resp.headers['Location'].split('/')[-1], 'z.ipynb')
263 self.assertEqual(resp.headers['Location'].split('/')[-1], 'z.ipynb')
264 self.assertEqual(resp.json()['name'], 'z.ipynb')
264 self.assertEqual(resp.json()['name'], 'z.ipynb')
265 assert os.path.isfile(pjoin(self.notebook_dir.name, 'foo', 'z.ipynb'))
265 assert os.path.isfile(pjoin(self.notebook_dir.name, 'foo', 'z.ipynb'))
266
266
267 nbs = notebooks_only(self.nb_api.list('foo').json())
267 nbs = notebooks_only(self.nb_api.list('foo').json())
268 nbnames = set(n['name'] for n in nbs)
268 nbnames = set(n['name'] for n in nbs)
269 self.assertIn('z.ipynb', nbnames)
269 self.assertIn('z.ipynb', nbnames)
270 self.assertNotIn('a.ipynb', nbnames)
270 self.assertNotIn('a.ipynb', nbnames)
271
271
272 def test_rename_existing(self):
272 def test_rename_existing(self):
273 with assert_http_error(409):
273 with assert_http_error(409):
274 self.nb_api.rename('a.ipynb', 'foo', 'b.ipynb')
274 self.nb_api.rename('a.ipynb', 'foo', 'b.ipynb')
275
275
276 def test_save(self):
276 def test_save(self):
277 resp = self.nb_api.read('a.ipynb', 'foo')
277 resp = self.nb_api.read('a.ipynb', 'foo')
278 nbcontent = json.loads(resp.text)['content']
278 nbcontent = json.loads(resp.text)['content']
279 nb = to_notebook_json(nbcontent)
279 nb = to_notebook_json(nbcontent)
280 ws = new_worksheet()
280 ws = new_worksheet()
281 nb.worksheets = [ws]
281 nb.worksheets = [ws]
282 ws.cells.append(new_heading_cell(u'Created by test Β³'))
282 ws.cells.append(new_heading_cell(u'Created by test Β³'))
283
283
284 nbmodel= {'name': 'a.ipynb', 'path':'foo', 'content': nb}
284 nbmodel= {'name': 'a.ipynb', 'path':'foo', 'content': nb}
285 resp = self.nb_api.save('a.ipynb', path='foo', body=json.dumps(nbmodel))
285 resp = self.nb_api.save('a.ipynb', path='foo', body=json.dumps(nbmodel))
286
286
287 nbfile = pjoin(self.notebook_dir.name, 'foo', 'a.ipynb')
287 nbfile = pjoin(self.notebook_dir.name, 'foo', 'a.ipynb')
288 with io.open(nbfile, 'r', encoding='utf-8') as f:
288 with io.open(nbfile, 'r', encoding='utf-8') as f:
289 newnb = read(f, format='ipynb')
289 newnb = read(f, format='ipynb')
290 self.assertEqual(newnb.worksheets[0].cells[0].source,
290 self.assertEqual(newnb.worksheets[0].cells[0].source,
291 u'Created by test Β³')
291 u'Created by test Β³')
292 nbcontent = self.nb_api.read('a.ipynb', 'foo').json()['content']
292 nbcontent = self.nb_api.read('a.ipynb', 'foo').json()['content']
293 newnb = to_notebook_json(nbcontent)
293 newnb = to_notebook_json(nbcontent)
294 self.assertEqual(newnb.worksheets[0].cells[0].source,
294 self.assertEqual(newnb.worksheets[0].cells[0].source,
295 u'Created by test Β³')
295 u'Created by test Β³')
296
296
297 # Save and rename
297 # Save and rename
298 nbmodel= {'name': 'a2.ipynb', 'path':'foo/bar', 'content': nb}
298 nbmodel= {'name': 'a2.ipynb', 'path':'foo/bar', 'content': nb}
299 resp = self.nb_api.save('a.ipynb', path='foo', body=json.dumps(nbmodel))
299 resp = self.nb_api.save('a.ipynb', path='foo', body=json.dumps(nbmodel))
300 saved = resp.json()
300 saved = resp.json()
301 self.assertEqual(saved['name'], 'a2.ipynb')
301 self.assertEqual(saved['name'], 'a2.ipynb')
302 self.assertEqual(saved['path'], 'foo/bar')
302 self.assertEqual(saved['path'], 'foo/bar')
303 assert os.path.isfile(pjoin(self.notebook_dir.name,'foo','bar','a2.ipynb'))
303 assert os.path.isfile(pjoin(self.notebook_dir.name,'foo','bar','a2.ipynb'))
304 assert not os.path.isfile(pjoin(self.notebook_dir.name, 'foo', 'a.ipynb'))
304 assert not os.path.isfile(pjoin(self.notebook_dir.name, 'foo', 'a.ipynb'))
305 with assert_http_error(404):
305 with assert_http_error(404):
306 self.nb_api.read('a.ipynb', 'foo')
306 self.nb_api.read('a.ipynb', 'foo')
307
307
308 def test_checkpoints(self):
308 def test_checkpoints(self):
309 resp = self.nb_api.read('a.ipynb', 'foo')
309 resp = self.nb_api.read('a.ipynb', 'foo')
310 r = self.nb_api.new_checkpoint('a.ipynb', 'foo')
310 r = self.nb_api.new_checkpoint('a.ipynb', 'foo')
311 self.assertEqual(r.status_code, 201)
311 self.assertEqual(r.status_code, 201)
312 cp1 = r.json()
312 cp1 = r.json()
313 self.assertEqual(set(cp1), {'id', 'last_modified'})
313 self.assertEqual(set(cp1), {'id', 'last_modified'})
314 self.assertEqual(r.headers['Location'].split('/')[-1], cp1['id'])
314 self.assertEqual(r.headers['Location'].split('/')[-1], cp1['id'])
315
315
316 # Modify it
316 # Modify it
317 nbcontent = json.loads(resp.text)['content']
317 nbcontent = json.loads(resp.text)['content']
318 nb = to_notebook_json(nbcontent)
318 nb = to_notebook_json(nbcontent)
319 ws = new_worksheet()
319 ws = new_worksheet()
320 nb.worksheets = [ws]
320 nb.worksheets = [ws]
321 hcell = new_heading_cell('Created by test')
321 hcell = new_heading_cell('Created by test')
322 ws.cells.append(hcell)
322 ws.cells.append(hcell)
323 # Save
323 # Save
324 nbmodel= {'name': 'a.ipynb', 'path':'foo', 'content': nb}
324 nbmodel= {'name': 'a.ipynb', 'path':'foo', 'content': nb}
325 resp = self.nb_api.save('a.ipynb', path='foo', body=json.dumps(nbmodel))
325 resp = self.nb_api.save('a.ipynb', path='foo', body=json.dumps(nbmodel))
326
326
327 # List checkpoints
327 # List checkpoints
328 cps = self.nb_api.get_checkpoints('a.ipynb', 'foo').json()
328 cps = self.nb_api.get_checkpoints('a.ipynb', 'foo').json()
329 self.assertEqual(cps, [cp1])
329 self.assertEqual(cps, [cp1])
330
330
331 nbcontent = self.nb_api.read('a.ipynb', 'foo').json()['content']
331 nbcontent = self.nb_api.read('a.ipynb', 'foo').json()['content']
332 nb = to_notebook_json(nbcontent)
332 nb = to_notebook_json(nbcontent)
333 self.assertEqual(nb.worksheets[0].cells[0].source, 'Created by test')
333 self.assertEqual(nb.worksheets[0].cells[0].source, 'Created by test')
334
334
335 # Restore cp1
335 # Restore cp1
336 r = self.nb_api.restore_checkpoint('a.ipynb', 'foo', cp1['id'])
336 r = self.nb_api.restore_checkpoint('a.ipynb', 'foo', cp1['id'])
337 self.assertEqual(r.status_code, 204)
337 self.assertEqual(r.status_code, 204)
338 nbcontent = self.nb_api.read('a.ipynb', 'foo').json()['content']
338 nbcontent = self.nb_api.read('a.ipynb', 'foo').json()['content']
339 nb = to_notebook_json(nbcontent)
339 nb = to_notebook_json(nbcontent)
340 self.assertEqual(nb.worksheets, [])
340 self.assertEqual(nb.worksheets, [])
341
341
342 # Delete cp1
342 # Delete cp1
343 r = self.nb_api.delete_checkpoint('a.ipynb', 'foo', cp1['id'])
343 r = self.nb_api.delete_checkpoint('a.ipynb', 'foo', cp1['id'])
344 self.assertEqual(r.status_code, 204)
344 self.assertEqual(r.status_code, 204)
345 cps = self.nb_api.get_checkpoints('a.ipynb', 'foo').json()
345 cps = self.nb_api.get_checkpoints('a.ipynb', 'foo').json()
346 self.assertEqual(cps, [])
346 self.assertEqual(cps, [])
347
General Comments 0
You need to be logged in to leave comments. Login now