Added notebooks API tests.

This commit is contained in:
Zachary Sailer 2013-08-22 10:42:45 -07:00 committed by MinRK
parent 5f1b7727d8
commit 95b505d602
5 changed files with 162 additions and 47 deletions

View File

@ -94,17 +94,16 @@ class FileNotebookManager(NotebookManager):
def change_notebook(self, data, notebook_name, notebook_path='/'):
"""Changes notebook"""
changes = data.keys()
response = 200
for change in changes:
full_path = self.get_os_path(notebook_name, notebook_path)
if change == "name":
new_path = self.get_os_path(data['name'], notebook_path)
if not os.path.isfile(new_path):
try:
os.rename(full_path,
self.get_os_path(data['name'], notebook_path))
notebook_name = data['name']
else:
response = 409
except OSError as e:
raise web.HTTPError(409, u'Notebook name already exists.')
if change == "path":
new_path = self.get_os_path(data['name'], data['path'])
stutil.move(full_path, new_path)
@ -112,7 +111,7 @@ class FileNotebookManager(NotebookManager):
if change == "content":
self.save_notebook(data, notebook_name, notebook_path)
model = self.notebook_model(notebook_name, notebook_path)
return model, response
return model
def notebook_exists(self, name, path):
"""Returns a True if the notebook exists. Else, returns False.
@ -131,31 +130,6 @@ class FileNotebookManager(NotebookManager):
path = self.get_os_path(name, path)
return os.path.isfile(path)
def get_os_path(self, fname, path='/'):
"""Given a notebook name and a server URL path, return its file system
path.
Parameters
----------
fname : string
The name of a notebook file with the .ipynb extension
path : string
The relative URL path (with '/' as separator) to the named
notebook.
Returns
-------
path : string
A file system path that combines notebook_dir (location where
server started), the relative path, and the filename with the
current operating system's url.
"""
parts = path.split('/')
parts = [p for p in parts if p != ''] # remove duplicate splits
parts += [fname]
path = os.path.join(self.notebook_dir, *parts)
return path
def read_notebook_object_from_path(self, path):
"""read a notebook object from a path"""
info = os.stat(path)

View File

@ -36,7 +36,7 @@ class NotebookRootHandler(IPythonHandler):
"""get returns a list of notebooks from the location
where the server was started."""
nbm = self.notebook_manager
notebooks = nbm.list_notebooks("")
notebooks = nbm.list_notebooks("/")
self.finish(jsonapi.dumps(notebooks))
@web.authenticated
@ -53,18 +53,9 @@ class NotebookRootHandler(IPythonHandler):
fname = nbm.new_notebook(notebook_path='/')
self.set_header('Location', nbm.notebook_dir + fname)
model = nbm.notebook_model(fname)
self.set_header('Location', '{0}api/notebooks/{1}'.format(self.base_project_url, notebook_name))
self.set_header('Location', '{0}api/notebooks/{1}'.format(self.base_project_url, fname))
self.finish(jsonapi.dumps(model))
class NotebookRootRedirect(IPythonHandler):
@web.authenticated
def get(self):
"""get redirects to not include trailing backslash"""
self.redirect("/api/notebooks")
class NotebookHandler(IPythonHandler):
SUPPORTED_METHODS = ('GET', 'PUT', 'PATCH', 'POST','DELETE')
@ -86,7 +77,7 @@ class NotebookHandler(IPythonHandler):
# get and return notebook representation
format = self.get_argument('format', default='json')
download = self.get_argument('download', default='False')
model = nbm.notebook_model(name,path)
model = nbm.notebook_model(name, path)
last_mod, representation, name = nbm.get_notebook(name, path, format)
self.set_header('Last-Modified', last_mod)
@ -109,8 +100,7 @@ class NotebookHandler(IPythonHandler):
nbm = self.notebook_manager
name, path = nbm.named_notebook_path(notebook_path)
data = jsonapi.loads(self.request.body)
model, response = nbm.change_notebook(data, name, path)
self.set_status(response)
model = nbm.change_notebook(data, name, path)
self.finish(jsonapi.dumps(model))
@web.authenticated
@ -217,7 +207,7 @@ default_handlers = [
ModifyNotebookCheckpointsHandler),
(r"api/notebooks/%s/" % _notebook_path_regex, NotebookHandler),
(r"api/notebooks/%s" % _notebook_path_regex, NotebookHandler),
(r"api/notebooks/", NotebookRootRedirect),
(r"api/notebooks/", NotebookRootHandler),
(r"api/notebooks", NotebookRootHandler),
]

View File

@ -71,6 +71,32 @@ class NotebookManager(LoggingConfigurable):
name = None
path = "/".join(names) + '/'
return name, path
def get_os_path(self, fname=None, path='/'):
"""Given a notebook name and a server URL path, return its file system
path.
Parameters
----------
fname : string
The name of a notebook file with the .ipynb extension
path : string
The relative URL path (with '/' as separator) to the named
notebook.
Returns
-------
path : string
A file system path that combines notebook_dir (location where
server started), the relative path, and the filename with the
current operating system's url.
"""
parts = path.split('/')
parts = [p for p in parts if p != ''] # remove duplicate splits
if fname is not None:
parts += [fname]
path = os.path.join(self.notebook_dir, *parts)
return path
def url_encode(self, path):
"""Returns the path with all special characters URL encoded"""
@ -135,7 +161,7 @@ class NotebookManager(LoggingConfigurable):
model = {"name": notebook_name,
"path": notebook_path,
"last_modified (UTC)": last_modified.ctime()}
if content == True:
if content is True:
model['content'] = contents
return model
@ -196,7 +222,7 @@ class NotebookManager(LoggingConfigurable):
nb.metadata.name = name
self.write_notebook_object(nb, name, notebook_path, new_name)
def write_notebook_object(self, nb, notebook_name=None, notebook_path='/', new_name=None):
def write_notebook_object(self, nb, notebook_name='/', notebook_path='/', new_name=None):
"""Write a notebook object and return its notebook_name.
If notebook_name is None, this method should create a new notebook_name.

View File

@ -0,0 +1,124 @@
"""Test the all of the services API."""
import os
import sys
import json
import urllib
from zmq.utils import jsonapi
import requests
from IPython.html.tests.launchnotebook import NotebookTestBase
class APITest(NotebookTestBase):
"""Test the kernels web service API"""
def base_url(self):
return super(APITest,self).base_url()
def notebooks_url(self):
return self.base_url() + 'api/notebooks'
def kernels_url(self):
return self.base_url() + 'api/kernels'
def sessions_url(self):
return self.base_url() + 'api/sessions'
def contents_url(self):
return self.contents_url() + 'api/contents'
def mknb(self, name='', path='/'):
url = self.notebooks_url() + path
return url, requests.post(url)
def delnb(self, name, path='/'):
url = self.notebooks_url() + path + name
r = requests.delete(url)
return r.status_code
def test_no_notebooks(self):
url = self.notebooks_url()
r = requests.get(url)
self.assertEqual(r.json(), [])
def test_root_notebook_handler(self):
# POST a notebook and test the dict thats returned.
url, nb = self.mknb()
data = nb.json()
assert isinstance(data, dict)
assert data.has_key("name")
assert data.has_key("path")
self.assertEqual(data['name'], u'Untitled0.ipynb')
self.assertEqual(data['path'], u'/')
# GET list of notebooks in directory.
r = requests.get(url)
assert isinstance(r.json(), list)
assert isinstance(r.json()[0], dict)
# GET with a notebook name.
url = self.notebooks_url() + '/Untitled0.ipynb'
r = requests.get(url)
assert isinstance(data, dict)
self.assertEqual(r.json(), data)
# PATCH (rename) request.
new_name = {'name':'test.ipynb'}
r = requests.patch(url, data=jsonapi.dumps(new_name))
data = r.json()
assert isinstance(data, dict)
# make sure the patch worked.
new_url = self.notebooks_url() + '/test.ipynb'
r = requests.get(new_url)
assert isinstance(r.json(), dict)
self.assertEqual(r.json(), data)
# GET bad (old) notebook name.
r = requests.get(url)
self.assertEqual(r.status_code, 404)
# POST notebooks to folders one and two levels down.
os.makedirs(os.path.join(self.notebook_dir.name, 'foo'))
os.makedirs(os.path.join(self.notebook_dir.name, 'foo','bar'))
url, nb = self.mknb(path='/foo/')
url2, nb2 = self.mknb(path='/foo/bar/')
data = nb.json()
data2 = nb2.json()
assert isinstance(data, dict)
assert isinstance(data2, dict)
assert data.has_key("name")
assert data.has_key("path")
self.assertEqual(data['name'], u'Untitled0.ipynb')
self.assertEqual(data['path'], u'/foo/')
assert data2.has_key("name")
assert data2.has_key("path")
self.assertEqual(data2['name'], u'Untitled0.ipynb')
self.assertEqual(data2['path'], u'/foo/bar/')
# GET request on notebooks one and two levels down.
r = requests.get(url+'Untitled0.ipynb')
r2 = requests.get(url2+'Untitled0.ipynb')
assert isinstance(r.json(), dict)
self.assertEqual(r.json(), data)
assert isinstance(r2.json(), dict)
self.assertEqual(r2.json(), data2)
# PATCH notebooks that are one and two levels down.
new_name = {'name': 'testfoo.ipynb'}
r = requests.patch(url+'Untitled0.ipynb', data=jsonapi.dumps(new_name))
r = requests.get(url+'testfoo.ipynb')
data = r.json()
assert isinstance(data, dict)
assert data.has_key('name')
self.assertEqual(data['name'], 'testfoo.ipynb')
r = requests.get(url+'Untitled0.ipynb')
self.assertEqual(r.status_code, 404)
# DELETE notebooks
r = self.delnb('testfoo.ipynb', '/foo/')
r2 = self.delnb('Untitled0.ipynb', '/foo/bar/')
self.assertEqual(r, 204)
self.assertEqual(r2, 204)

View File

@ -28,6 +28,7 @@ class NotebookTestBase(TestCase):
'--ipython-dir=%s' % self.ipython_dir.name,
'--notebook-dir=%s' % self.notebook_dir.name
]
#self.notebook = Popen(notebook_args)
self.notebook = Popen(notebook_args, stdout=PIPE, stderr=PIPE)
time.sleep(3.0)