diff --git a/IPython/html/services/notebooks/filenbmanager.py b/IPython/html/services/notebooks/filenbmanager.py
index c0f0d1905..d6b9e6c74 100644
--- a/IPython/html/services/notebooks/filenbmanager.py
+++ b/IPython/html/services/notebooks/filenbmanager.py
@@ -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)
diff --git a/IPython/html/services/notebooks/handlers.py b/IPython/html/services/notebooks/handlers.py
index 29fc4c2aa..2c4f86d17 100644
--- a/IPython/html/services/notebooks/handlers.py
+++ b/IPython/html/services/notebooks/handlers.py
@@ -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),
]
diff --git a/IPython/html/services/notebooks/nbmanager.py b/IPython/html/services/notebooks/nbmanager.py
index be33166bf..d610aad35 100644
--- a/IPython/html/services/notebooks/nbmanager.py
+++ b/IPython/html/services/notebooks/nbmanager.py
@@ -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.
diff --git a/IPython/html/services/tests/test_services_api.py b/IPython/html/services/tests/test_services_api.py
new file mode 100644
index 000000000..ddc1e405d
--- /dev/null
+++ b/IPython/html/services/tests/test_services_api.py
@@ -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)
\ No newline at end of file
diff --git a/IPython/html/tests/launchnotebook.py b/IPython/html/tests/launchnotebook.py
index a83cbc38c..d64929ca6 100644
--- a/IPython/html/tests/launchnotebook.py
+++ b/IPython/html/tests/launchnotebook.py
@@ -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)