##// END OF EJS Templates
review fixes on tests, add extra kernel api test
Zachary Sailer -
Show More
@@ -218,10 +218,6 b' class IPythonHandler(AuthenticatedHandler):'
218 218 return self.settings['session_manager']
219 219
220 220 @property
221 def content_manager(self):
222 return self.settings['content_manager']
223
224 @property
225 221 def project(self):
226 222 return self.notebook_manager.notebook_dir
227 223
@@ -16,11 +16,15 b' class KernelAPITest(NotebookTestBase):'
16 16 def base_url(self):
17 17 return super(KernelAPITest,self).base_url() + 'api/kernels'
18 18
19 def mkkernel(self):
20 r = requests.post(self.base_url())
21 return r.json()
22
19 23 def test_no_kernels(self):
20 24 """Make sure there are no kernels running at the start"""
21 25 url = self.base_url()
22 26 r = requests.get(url)
23 assert r.json() == []
27 self.assertEqual(r.json(), [])
24 28
25 29 def test_main_kernel_handler(self):
26 30 # POST request
@@ -33,4 +37,17 b' class KernelAPITest(NotebookTestBase):'
33 37 assert isinstance(r.json(), list)
34 38 self.assertEqual(r.json()[0], data['id'])
35 39
36 No newline at end of file
40 def test_kernel_handler(self):
41 # GET kernel with id
42 data = self.mkkernel()
43 url = self.base_url() +'/' + data['id']
44 r = requests.get(url)
45 assert isinstance(r.json(), dict)
46 self.assertIn('id', r.json())
47 self.assertEqual(r.json()['id'], data['id'])
48
49 # DELETE kernel with id
50 r = requests.delete(url)
51 self.assertEqual(r.status_code, 204)
52 r = requests.get(self.base_url())
53 self.assertEqual(r.json(), []) No newline at end of file
@@ -91,7 +91,7 b' class FileNotebookManager(NotebookManager):'
91 91 notebooks.append(model)
92 92 return notebooks
93 93
94 def change_notebook(self, data, notebook_name, notebook_path='/'):
94 def update_notebook(self, data, notebook_name, notebook_path='/'):
95 95 """Changes notebook"""
96 96 changes = data.keys()
97 97 for change in changes:
@@ -16,127 +16,117 b' Authors:'
16 16 # Imports
17 17 #-----------------------------------------------------------------------------
18 18
19 from tornado import web
19 import json
20 20
21 from zmq.utils import jsonapi
21 from tornado import web
22 22
23 from ...utils import url_path_join
23 24 from IPython.utils.jsonutil import date_default
24 25
25 from ...base.handlers import IPythonHandler
26 from ...base.handlers import IPythonHandler, json_errors
26 27
27 28 #-----------------------------------------------------------------------------
28 29 # Notebook web service handlers
29 30 #-----------------------------------------------------------------------------
30 31
31 32
32 class NotebookRootHandler(IPythonHandler):
33
34 @web.authenticated
35 def get(self):
36 """get returns a list of notebooks from the location
37 where the server was started."""
38 nbm = self.notebook_manager
39 notebooks = nbm.list_notebooks("/")
40 self.finish(jsonapi.dumps(notebooks))
41
42 @web.authenticated
43 def post(self):
44 """post creates a notebooks in the directory where the
45 server was started"""
46 nbm = self.notebook_manager
47 self.log.info(nbm.notebook_dir)
48 body = self.request.body.strip()
49 format = self.get_argument('format', default='json')
50 name = self.get_argument('name', default=None)
51 if body:
52 fname = nbm.save_new_notebook(body, notebook_path='/', name=name, format=format)
53 else:
54 fname = nbm.new_notebook(notebook_path='/')
55 self.set_header('Location', nbm.notebook_dir + fname)
56 model = nbm.notebook_model(fname)
57 self.set_header('Location', '{0}api/notebooks/{1}'.format(self.base_project_url, fname))
58 self.finish(jsonapi.dumps(model))
59
60 33 class NotebookHandler(IPythonHandler):
61 34
62 SUPPORTED_METHODS = ('GET', 'PUT', 'PATCH', 'POST','DELETE')
35 SUPPORTED_METHODS = (u'GET', u'PUT', u'PATCH', u'POST', u'DELETE')
36
37 def notebook_location(self, name, path):
38 """Return the full URL location of a notebook based.
39
40 Parameters
41 ----------
42 name : unicode
43 The name of the notebook like "foo.ipynb".
44 path : unicode
45 The URL path of the notebook.
46 """
47 return url_path_join(self.base_project_url, u'/api/notebooks', path, name)
63 48
64 49 @web.authenticated
50 @json_errors
65 51 def get(self, notebook_path):
66 52 """get checks if a notebook is not named, an returns a list of notebooks
67 53 in the notebook path given. If a name is given, return
68 54 the notebook representation"""
69 55 nbm = self.notebook_manager
56 # path will have leading and trailing slashes, such as '/foo/bar/'
70 57 name, path = nbm.named_notebook_path(notebook_path)
71 58
72 59 # Check to see if a notebook name was given
73 60 if name is None:
74 61 # List notebooks in 'notebook_path'
75 62 notebooks = nbm.list_notebooks(path)
76 self.finish(jsonapi.dumps(notebooks))
63 self.finish(json.dumps(notebooks, default=date_default))
77 64 else:
78 65 # get and return notebook representation
79 format = self.get_argument('format', default='json')
80 download = self.get_argument('download', default='False')
81 model = nbm.notebook_model(name, path)
82 last_mod, representation, name = nbm.get_notebook(name, path, format)
83 self.set_header('Last-Modified', last_mod)
84
85 if download == 'True':
86 if format == u'json':
87 self.set_header('Content-Type', 'application/json')
88 self.set_header('Content-Disposition','attachment; filename="%s.ipynb"' % name)
89 self.finish(representation)
90 elif format == u'py':
91 self.set_header('Content-Type', 'application/x-python')
92 self.set_header('Content-Disposition','attachment; filename="%s.py"' % name)
93 self.finish(representation)
94 else:
95 self.finish(jsonapi.dumps(model))
66 model = nbm.get_notebook_model(name, path)
67 self.set_header(u'Last-Modified', model[u'last_modified'])
68 self.finish(json.dumps(model, default=date_default))
96 69
97 70 @web.authenticated
71 # @json_errors
98 72 def patch(self, notebook_path):
99 73 """patch is currently used strictly for notebook renaming.
100 74 Changes the notebook name to the name given in data."""
101 75 nbm = self.notebook_manager
76 # path will have leading and trailing slashes, such as '/foo/bar/'
102 77 name, path = nbm.named_notebook_path(notebook_path)
103 data = jsonapi.loads(self.request.body)
104 model = nbm.change_notebook(data, name, path)
105 self.finish(jsonapi.dumps(model))
78 if name is None:
79 raise web.HTTPError(400, u'Notebook name missing')
80 model = self.get_json_body()
81 if model is None:
82 raise web.HTTPError(400, u'JSON body missing')
83 model = nbm.update_notebook_model(model, name, path)
84 if model[u'name'] != name or model[u'path'] != path:
85 self.set_status(301)
86 location = self.notebook_location(model[u'name'], model[u'path'])
87 self.set_header(u'Location', location)
88 self.set_header(u'Last-Modified', model[u'last_modified'])
89 self.finish(json.dumps(model, default=date_default))
106 90
107 91 @web.authenticated
108 def post(self,notebook_path):
92 @json_errors
93 def post(self, notebook_path):
109 94 """Create a new notebook in the location given by 'notebook_path'."""
110 95 nbm = self.notebook_manager
111 fname, path = nbm.named_notebook_path(notebook_path)
112 body = self.request.body.strip()
113 format = self.get_argument('format', default='json')
114 name = self.get_argument('name', default=None)
115 if body:
116 fname = nbm.save_new_notebook(body, notebook_path=path, name=name, format=format)
117 else:
118 fname = nbm.new_notebook(notebook_path=path)
119 self.set_header('Location', nbm.notebook_dir + path + fname)
120 model = nbm.notebook_model(fname, path)
121 self.finish(jsonapi.dumps(model))
96 # path will have leading and trailing slashes, such as '/foo/bar/'
97 name, path = nbm.named_notebook_path(notebook_path)
98 model = self.get_json_body()
99 if name is not None:
100 raise web.HTTPError(400, 'No name can be provided when POSTing a new notebook.')
101 model = nbm.create_notebook_model(model, path)
102 location = nbm.notebook_dir + model[u'path'] + model[u'name']
103 location = self.notebook_location(model[u'name'], model[u'path'])
104 self.set_header(u'Location', location)
105 self.set_header(u'Last-Modified', model[u'last_modified'])
106 self.set_status(201)
107 self.finish(json.dumps(model, default=date_default))
122 108
123 109 @web.authenticated
110 @json_errors
124 111 def put(self, notebook_path):
125 112 """saves the notebook in the location given by 'notebook_path'."""
126 113 nbm = self.notebook_manager
114 # path will have leading and trailing slashes, such as '/foo/bar/'
127 115 name, path = nbm.named_notebook_path(notebook_path)
128 format = self.get_argument('format', default='json')
129 nbm.save_notebook(self.request.body, notebook_path=path, name=name, format=format)
130 model = nbm.notebook_model(name, path)
131 self.set_status(204)
132 self.finish(jsonapi.dumps(model))
116 model = self.get_json_body()
117 if model is None:
118 raise web.HTTPError(400, u'JSON body missing')
119 nbm.save_notebook_model(model, name, path)
120 self.finish(json.dumps(model, default=date_default))
133 121
134 122 @web.authenticated
123 @json_errors
135 124 def delete(self, notebook_path):
136 """delete rmoves the notebook in the given notebook path"""
125 """delete the notebook in the given notebook path"""
137 126 nbm = self.notebook_manager
127 # path will have leading and trailing slashes, such as '/foo/bar/'
138 128 name, path = nbm.named_notebook_path(notebook_path)
139 nbm.delete_notebook(name, path)
129 nbm.delete_notebook_model(name, path)
140 130 self.set_status(204)
141 131 self.finish()
142 132
@@ -146,29 +136,28 b' class NotebookCheckpointsHandler(IPythonHandler):'
146 136 SUPPORTED_METHODS = ('GET', 'POST')
147 137
148 138 @web.authenticated
139 @json_errors
149 140 def get(self, notebook_path):
150 141 """get lists checkpoints for a notebook"""
151 142 nbm = self.notebook_manager
143 # path will have leading and trailing slashes, such as '/foo/bar/'
152 144 name, path = nbm.named_notebook_path(notebook_path)
153 145 checkpoints = nbm.list_checkpoints(name, path)
154 data = jsonapi.dumps(checkpoints, default=date_default)
146 data = json.dumps(checkpoints, default=date_default)
155 147 self.finish(data)
156 148
157 149 @web.authenticated
150 @json_errors
158 151 def post(self, notebook_path):
159 152 """post creates a new checkpoint"""
160 153 nbm = self.notebook_manager
161 154 name, path = nbm.named_notebook_path(notebook_path)
155 # path will have leading and trailing slashes, such as '/foo/bar/'
162 156 checkpoint = nbm.create_checkpoint(name, path)
163 data = jsonapi.dumps(checkpoint, default=date_default)
164 if path == None:
165 self.set_header('Location', '{0}notebooks/{1}/checkpoints/{2}'.format(
166 self.base_project_url, name, checkpoint['checkpoint_id']
167 ))
168 else:
169 self.set_header('Location', '{0}notebooks/{1}/{2}/checkpoints/{3}'.format(
170 self.base_project_url, path, name, checkpoint['checkpoint_id']
171 ))
157 data = json.dumps(checkpoint, default=date_default)
158 location = url_path_join(self.base_project_url, u'/api/notebooks',
159 path, name, '/checkpoints', checkpoint[u'checkpoint_id'])
160 self.set_header(u'Location', location)
172 161 self.finish(data)
173 162
174 163
@@ -177,20 +166,24 b' class ModifyNotebookCheckpointsHandler(IPythonHandler):'
177 166 SUPPORTED_METHODS = ('POST', 'DELETE')
178 167
179 168 @web.authenticated
169 @json_errors
180 170 def post(self, notebook_path, checkpoint_id):
181 171 """post restores a notebook from a checkpoint"""
182 172 nbm = self.notebook_manager
173 # path will have leading and trailing slashes, such as '/foo/bar/'
183 174 name, path = nbm.named_notebook_path(notebook_path)
184 nbm.restore_checkpoint(name, checkpoint_id, path)
175 nbm.restore_checkpoint(checkpoint_id, name, path)
185 176 self.set_status(204)
186 177 self.finish()
187 178
188 179 @web.authenticated
180 @json_errors
189 181 def delete(self, notebook_path, checkpoint_id):
190 182 """delete clears a checkpoint for a given notebook"""
191 183 nbm = self.notebook_manager
184 # path will have leading and trailing slashes, such as '/foo/bar/'
192 185 name, path = nbm.named_notebook_path(notebook_path)
193 nbm.delete_checkpoint(name, checkpoint_id, path)
186 nbm.delete_checkpoint(checkpoint_id, name, path)
194 187 self.set_status(204)
195 188 self.finish()
196 189
@@ -199,19 +192,15 b' class ModifyNotebookCheckpointsHandler(IPythonHandler):'
199 192 #-----------------------------------------------------------------------------
200 193
201 194
202 _notebook_path_regex = r"(?P<notebook_path>.+)"
195 _notebook_path_regex = r"(?P<notebook_path>.*)"
203 196 _checkpoint_id_regex = r"(?P<checkpoint_id>[\w-]+)"
204 197
205 198 default_handlers = [
206 (r"api/notebooks/%s/checkpoints" % _notebook_path_regex, NotebookCheckpointsHandler),
207 (r"api/notebooks/%s/checkpoints/%s" % (_notebook_path_regex, _checkpoint_id_regex),
199 (r"/api/notebooks/%s/checkpoints" % _notebook_path_regex, NotebookCheckpointsHandler),
200 (r"/api/notebooks/%s/checkpoints/%s" % (_notebook_path_regex, _checkpoint_id_regex),
208 201 ModifyNotebookCheckpointsHandler),
209 (r"api/notebooks/%s/" % _notebook_path_regex, NotebookHandler),
210 (r"api/notebooks/%s" % _notebook_path_regex, NotebookHandler),
211 (r"api/notebooks/", NotebookRootHandler),
212 (r"api/notebooks", NotebookRootHandler),
202 (r"/api/notebooks%s" % _notebook_path_regex, NotebookHandler),
213 203 ]
214 204
215 205
216 206
217
@@ -155,12 +155,12 b' class NotebookManager(LoggingConfigurable):'
155 155 """
156 156 raise NotImplementedError('must be implemented in a subclass')
157 157
158 def notebook_model(self, notebook_name, notebook_path='/', content=True):
158 def notebook_model(self, name, path='/', content=True):
159 159 """ Creates the standard notebook model """
160 last_modified, contents = self.read_notebook_object(notebook_name, notebook_path)
161 model = {"name": notebook_name,
162 "path": notebook_path,
163 "last_modified (UTC)": last_modified.ctime()}
160 last_modified, contents = self.read_notebook_model(name, path)
161 model = {"name": name,
162 "path": path,
163 "last_modified": last_modified.ctime()}
164 164 if content is True:
165 165 model['content'] = contents
166 166 return model
@@ -180,10 +180,22 b' class NotebookManager(LoggingConfigurable):'
180 180 name = nb.metadata.get('name', 'notebook')
181 181 return last_mod, representation, name
182 182
183 def read_notebook_object(self, notebook_name, notebook_path='/'):
183 def read_notebook_model(self, notebook_name, notebook_path='/'):
184 184 """Get the object representation of a notebook by notebook_id."""
185 185 raise NotImplementedError('must be implemented in a subclass')
186 186
187 def save_notebook(self, model, name=None, path='/'):
188 """Save the Notebook"""
189 if name is None:
190 name = self.increment_filename('Untitled', path)
191 if 'content' not in model:
192 metadata = current.new_metadata(name=name)
193 nb = current.new_notebook(metadata=metadata)
194 else:
195 nb = model['content']
196 self.write_notebook_object()
197
198
187 199 def save_new_notebook(self, data, notebook_path='/', name=None, format=u'json'):
188 200 """Save a new notebook and return its name.
189 201
@@ -208,7 +220,7 b' class NotebookManager(LoggingConfigurable):'
208 220 notebook_name = self.write_notebook_object(nb, notebook_path=notebook_path)
209 221 return notebook_name
210 222
211 def save_notebook(self, data, notebook_path='/', name=None, new_name=None, format=u'json'):
223 def save_notebook(self, data, notebook_path='/', name=None, format=u'json'):
212 224 """Save an existing notebook by notebook_name."""
213 225 if format not in self.allowed_formats:
214 226 raise web.HTTPError(415, u'Invalid notebook format: %s' % format)
@@ -222,7 +234,7 b' class NotebookManager(LoggingConfigurable):'
222 234 nb.metadata.name = name
223 235 self.write_notebook_object(nb, name, notebook_path, new_name)
224 236
225 def write_notebook_object(self, nb, notebook_name='/', notebook_path='/', new_name=None):
237 def write_notebook_model(self, model):
226 238 """Write a notebook object and return its notebook_name.
227 239
228 240 If notebook_name is None, this method should create a new notebook_name.
@@ -25,15 +25,15 b' class APITest(NotebookTestBase):'
25 25 r = requests.delete(url)
26 26 return r.status_code
27 27
28 def test_notebook_root_handler(self):
28 def test_notebook_handler(self):
29 29 # POST a notebook and test the dict thats returned.
30 30 #url, nb = self.mknb()
31 31 url = self.notebook_url()
32 32 nb = requests.post(url)
33 33 data = nb.json()
34 34 assert isinstance(data, dict)
35 assert data.has_key("name")
36 assert data.has_key("path")
35 self.assertIn('name', data)
36 self.assertIn('path', data)
37 37 self.assertEqual(data['name'], u'Untitled0.ipynb')
38 38 self.assertEqual(data['path'], u'/')
39 39
@@ -43,8 +43,7 b' class APITest(NotebookTestBase):'
43 43 assert isinstance(r.json()[0], dict)
44 44
45 45 self.delnb('Untitled0.ipynb')
46
47 def test_notebook_handler(self):
46
48 47 # GET with a notebook name.
49 48 url, nb = self.mknb()
50 49 data = nb.json()
@@ -79,18 +78,18 b' class APITest(NotebookTestBase):'
79 78 data2 = nb2.json()
80 79 assert isinstance(data, dict)
81 80 assert isinstance(data2, dict)
82 assert data.has_key("name")
83 assert data.has_key("path")
81 self.assertIn('name', data)
82 self.assertIn('path', data)
84 83 self.assertEqual(data['name'], u'Untitled0.ipynb')
85 84 self.assertEqual(data['path'], u'/foo/')
86 assert data2.has_key("name")
87 assert data2.has_key("path")
85 self.assertIn('name', data2)
86 self.assertIn('path', data2)
88 87 self.assertEqual(data2['name'], u'Untitled0.ipynb')
89 88 self.assertEqual(data2['path'], u'/foo/bar/')
90 89
91 90 # GET request on notebooks one and two levels down.
92 r = requests.get(url+'Untitled0.ipynb')
93 r2 = requests.get(url2+'Untitled0.ipynb')
91 r = requests.get(url+'/Untitled0.ipynb')
92 r2 = requests.get(url2+'/Untitled0.ipynb')
94 93 assert isinstance(r.json(), dict)
95 94 self.assertEqual(r.json(), data)
96 95 assert isinstance(r2.json(), dict)
@@ -98,13 +97,13 b' class APITest(NotebookTestBase):'
98 97
99 98 # PATCH notebooks that are one and two levels down.
100 99 new_name = {'name': 'testfoo.ipynb'}
101 r = requests.patch(url+'Untitled0.ipynb', data=jsonapi.dumps(new_name))
102 r = requests.get(url+'testfoo.ipynb')
100 r = requests.patch(url+'/Untitled0.ipynb', data=jsonapi.dumps(new_name))
101 r = requests.get(url+'/testfoo.ipynb')
103 102 data = r.json()
104 103 assert isinstance(data, dict)
105 assert data.has_key('name')
104 self.assertIn('name', data)
106 105 self.assertEqual(data['name'], 'testfoo.ipynb')
107 r = requests.get(url+'Untitled0.ipynb')
106 r = requests.get(url+'/Untitled0.ipynb')
108 107 self.assertEqual(r.status_code, 404)
109 108
110 109 # DELETE notebooks
@@ -43,7 +43,7 b' class SessionAPITest(NotebookTestBase):'
43 43 r = requests.post(self.session_url(), params=param)
44 44 data = r.json()
45 45 assert isinstance(data, dict)
46 assert data.has_key('name')
46 self.assertIn('name', data)
47 47 self.assertEqual(data['name'], notebook['name'])
48 48
49 49 # GET sessions
@@ -79,8 +79,8 b' class SessionAPITest(NotebookTestBase):'
79 79 requests.patch(self.notebook_url() + '/Untitled0.ipynb',
80 80 data=jsonapi.dumps({'name':'test.ipynb'}))
81 81 assert isinstance(r.json(), dict)
82 assert r.json().has_key('name')
83 assert r.json().has_key('id')
82 self.assertIn('name', r.json())
83 self.assertIn('id', r.json())
84 84 self.assertEqual(r.json()['name'], 'test.ipynb')
85 85 self.assertEqual(r.json()['id'], session['id'])
86 86
@@ -88,8 +88,8 b' class SessionAPITest(NotebookTestBase):'
88 88 r = requests.delete(sess_url)
89 89 self.assertEqual(r.status_code, 204)
90 90 r = requests.get(self.session_url())
91 assert r.json() == []
91 self.assertEqual(r.json(), [])
92 92
93 93 # Clean up
94 94 r = self.delnb('test.ipynb')
95 assert r == 204 No newline at end of file
95 self.assertEqual(r, 204) No newline at end of file
@@ -2,6 +2,7 b''
2 2
3 3 import sys
4 4 import time
5 import requests
5 6 from subprocess import Popen, PIPE
6 7 from unittest import TestCase
7 8
@@ -17,6 +18,26 b' class NotebookTestBase(TestCase):'
17 18
18 19 port = 1234
19 20
21 def wait_till_alive(self):
22 url = 'http://localhost:%i/' % self.port
23 while True:
24 time.sleep(.1)
25 try:
26 r = requests.get(url + 'api/notebooks')
27 break
28 except requests.exceptions.ConnectionError:
29 pass
30
31 def wait_till_dead(self):
32 url = 'http://localhost:%i/' % self.port
33 while True:
34 time.sleep(.1)
35 try:
36 r = requests.get(url + 'api/notebooks')
37 continue
38 except requests.exceptions.ConnectionError:
39 break
40
20 41 def setUp(self):
21 42 self.ipython_dir = TemporaryDirectory()
22 43 self.notebook_dir = TemporaryDirectory()
@@ -27,15 +48,16 b' class NotebookTestBase(TestCase):'
27 48 '--no-browser',
28 49 '--ipython-dir=%s' % self.ipython_dir.name,
29 50 '--notebook-dir=%s' % self.notebook_dir.name
30 ]
51 ]
31 52 self.notebook = Popen(notebook_args, stdout=PIPE, stderr=PIPE)
32 time.sleep(3.0)
53 self.wait_till_alive()
54 #time.sleep(3.0)
33 55
34 56 def tearDown(self):
35 57 self.notebook.terminate()
36 58 self.ipython_dir.cleanup()
37 59 self.notebook_dir.cleanup()
38 time.sleep(3.0)
39
60 self.wait_till_dead()
61
40 62 def base_url(self):
41 63 return 'http://localhost:%i/' % self.port
General Comments 0
You need to be logged in to leave comments. Login now