diff --git a/IPython/html/services/contents/filemanager.py b/IPython/html/services/contents/filemanager.py
index e994829..89ae995 100644
--- a/IPython/html/services/contents/filemanager.py
+++ b/IPython/html/services/contents/filemanager.py
@@ -255,13 +255,18 @@ class FileContentsManager(ContentsManager):
self.validate_notebook_model(model)
return model
- def get_model(self, path, content=True):
+ def get_model(self, path, content=True, type_=None):
""" Takes a path for an entity and returns its model
Parameters
----------
path : str
the API path that describes the relative path for the target
+ content : bool
+ Whether to include the contents in the reply
+ type_ : str, optional
+ The requested type - 'file', 'notebook', or 'directory'.
+ Will raise HTTPError 406 if the content doesn't match.
Returns
-------
@@ -276,10 +281,16 @@ class FileContentsManager(ContentsManager):
os_path = self._get_os_path(path)
if os.path.isdir(os_path):
+ if type_ not in (None, 'directory'):
+ raise web.HTTPError(400,
+ u'%s is a directory, not a %s' % (path, type_))
model = self._dir_model(path, content=content)
- elif path.endswith('.ipynb'):
+ elif type_ == 'notebook' or (type_ is None and path.endswith('.ipynb')):
model = self._notebook_model(path, content=content)
else:
+ if type_ == 'directory':
+ raise web.HTTPError(400,
+ u'%s is not a directory')
model = self._file_model(path, content=content)
return model
diff --git a/IPython/html/services/contents/handlers.py b/IPython/html/services/contents/handlers.py
index 162e890..d097596 100644
--- a/IPython/html/services/contents/handlers.py
+++ b/IPython/html/services/contents/handlers.py
@@ -58,7 +58,11 @@ class ContentsHandler(IPythonHandler):
of the files and directories it contains.
"""
path = path or ''
- model = self.contents_manager.get_model(path=path)
+ type_ = self.get_query_argument('type', default=None)
+ if type_ not in {None, 'directory', 'file', 'notebook'}:
+ raise web.HTTPError(400, u'Type %r is invalid' % type_)
+
+ model = self.contents_manager.get_model(path=path, type_=type_)
if model['type'] == 'directory':
# group listing by type, then by name (case-insensitive)
# FIXME: sorting should be done in the frontends
diff --git a/IPython/html/services/contents/manager.py b/IPython/html/services/contents/manager.py
index 359cbd1..ea1a080 100644
--- a/IPython/html/services/contents/manager.py
+++ b/IPython/html/services/contents/manager.py
@@ -135,7 +135,7 @@ class ContentsManager(LoggingConfigurable):
"""
return self.file_exists(path) or self.dir_exists(path)
- def get_model(self, path, content=True):
+ def get_model(self, path, content=True, type_=None):
"""Get the model of a file or directory with or without content."""
raise NotImplementedError('must be implemented in a subclass')
diff --git a/IPython/html/services/contents/tests/test_contents_api.py b/IPython/html/services/contents/tests/test_contents_api.py
index 80ab663..8c6ce15 100644
--- a/IPython/html/services/contents/tests/test_contents_api.py
+++ b/IPython/html/services/contents/tests/test_contents_api.py
@@ -46,7 +46,9 @@ class API(object):
def list(self, path='/'):
return self._req('GET', path)
- def read(self, path):
+ def read(self, path, type_=None):
+ if type_ is not None:
+ path += '?type=' + type_
return self._req('GET', path)
def create_untitled(self, path='/', ext='.ipynb'):
@@ -259,6 +261,13 @@ class APITest(NotebookTestBase):
with assert_http_error(404):
self.api.read('foo/q.txt')
+ def test_get_bad_type(self):
+ with assert_http_error(400):
+ self.api.read(u'unicodé', type_='file') # this is a directory
+
+ with assert_http_error(400):
+ self.api.read(u'unicodé/innonascii.ipynb', type_='directory')
+
def _check_created(self, resp, path, type='notebook'):
self.assertEqual(resp.status_code, 201)
location_header = py3compat.str_to_unicode(resp.headers['Location'])
diff --git a/IPython/html/services/contents/tests/test_manager.py b/IPython/html/services/contents/tests/test_manager.py
index 11a75e0..b0aa933 100644
--- a/IPython/html/services/contents/tests/test_manager.py
+++ b/IPython/html/services/contents/tests/test_manager.py
@@ -159,6 +159,12 @@ class TestContentsManager(TestCase):
self.assertEqual(model['name'], name)
self.assertEqual(model['path'], path)
+ nb_as_file = cm.get_model(path, content=True, type_='file')
+ self.assertEqual(nb_as_file['path'], path)
+ self.assertEqual(nb_as_file['type'], 'file')
+ self.assertEqual(nb_as_file['format'], 'text')
+ self.assertNotIsInstance(nb_as_file['content'], dict)
+
# Test in sub-directory
sub_dir = '/foo/'
self.make_dir(cm.root_dir, 'foo')
@@ -170,6 +176,14 @@ class TestContentsManager(TestCase):
self.assertIn('content', model2)
self.assertEqual(model2['name'], 'Untitled0.ipynb')
self.assertEqual(model2['path'], '{0}/{1}'.format(sub_dir.strip('/'), name))
+
+ # Test getting directory model
+ dirmodel = cm.get_model('foo')
+ self.assertEqual(dirmodel['type'], 'directory')
+
+ with self.assertRaises(HTTPError):
+ cm.get_model('foo', type_='file')
+
@dec.skip_win32
def test_bad_symlink(self):