diff --git a/IPython/html/notebook/handlers.py b/IPython/html/notebook/handlers.py
index 42feea6..c3777ec 100644
--- a/IPython/html/notebook/handlers.py
+++ b/IPython/html/notebook/handlers.py
@@ -23,6 +23,7 @@ from zmq.utils import jsonapi
from ..base.handlers import IPythonHandler
+from ..services.notebooks.handlers import _notebook_path_regex, _path_regex
from ..utils import url_path_join
from urllib import quote
@@ -51,32 +52,31 @@ class NotebookHandler(IPythonHandler):
class NamedNotebookHandler(IPythonHandler):
@web.authenticated
- def get(self, notebook_path):
+ def get(self, path='', name=None):
"""get renders the notebook template if a name is given, or
redirects to the '/files/' handler if the name is not given."""
nbm = self.notebook_manager
- name, path = nbm.named_notebook_path(notebook_path)
- if name is not None:
- # a .ipynb filename was given
- if not nbm.notebook_exists(name, path):
- raise web.HTTPError(404, u'Notebook does not exist: %s' % name)
- name = nbm.url_encode(name)
- path = nbm.url_encode(path)
- self.write(self.render_template('notebook.html',
- project=self.project_dir,
- notebook_path=path,
- notebook_name=name,
- kill_kernel=False,
- mathjax_url=self.mathjax_url,
- )
- )
- else:
- url = "/files/" + notebook_path
+ if name is None:
+ url = url_path_join(self.base_project_url, 'files', path)
self.redirect(url)
+ return
+ # a .ipynb filename was given
+ if not nbm.notebook_exists(name, path):
+ raise web.HTTPError(404, u'Notebook does not exist: %s/%s' % (path, name))
+ name = nbm.url_encode(name)
+ path = nbm.url_encode(path)
+ self.write(self.render_template('notebook.html',
+ project=self.project_dir,
+ notebook_path=path,
+ notebook_name=name,
+ kill_kernel=False,
+ mathjax_url=self.mathjax_url,
+ )
+ )
@web.authenticated
- def post(self, notebook_path):
+ def post(self, path='', name=None):
"""post either creates a new notebook if no json data is
sent to the server, or copies the data and returns a
copied notebook in the location given by 'notebook_path."""
@@ -95,10 +95,9 @@ class NamedNotebookHandler(IPythonHandler):
#-----------------------------------------------------------------------------
-_notebook_path_regex = r"(?P.+)"
-
default_handlers = [
- (r"/notebooks/%s" % _notebook_path_regex, NamedNotebookHandler),
- (r"/notebooks/", NotebookHandler),
+ (r"/notebooks/?%s" % _notebook_path_regex, NamedNotebookHandler),
+ (r"/notebooks/?%s" % _path_regex, NamedNotebookHandler),
+ (r"/notebooks/?", NotebookHandler),
]
diff --git a/IPython/html/notebookapp.py b/IPython/html/notebookapp.py
index 98a8a5a..51b52ed 100644
--- a/IPython/html/notebookapp.py
+++ b/IPython/html/notebookapp.py
@@ -734,10 +734,9 @@ class NotebookApp(BaseIPythonApplication):
browser = None
if self.file_to_run:
- name, _ = os.path.splitext(os.path.basename(self.file_to_run))
- url = 'notebooks/' + self.entry_path + name + _
+ url = url_path_join('notebooks', self.entry_path, self.file_to_run)
else:
- url = 'tree/' + self.entry_path
+ url = url_path_join('tree', self.entry_path)
if browser:
b = lambda : browser.open("%s://%s:%i%s%s" % (proto, ip,
self.port, self.base_project_url, url), new=2)
diff --git a/IPython/html/services/notebooks/filenbmanager.py b/IPython/html/services/notebooks/filenbmanager.py
index b5693e1..b01c3a7 100644
--- a/IPython/html/services/notebooks/filenbmanager.py
+++ b/IPython/html/services/notebooks/filenbmanager.py
@@ -73,14 +73,14 @@ class FileNotebookManager(NotebookManager):
except:
raise TraitError("Couldn't create checkpoint dir %r" % new)
- def get_notebook_names(self, path='/'):
+ def get_notebook_names(self, path=''):
"""List all notebook names in the notebook dir and path."""
names = glob.glob(self.get_os_path('*'+self.filename_ext, path))
names = [os.path.basename(name)
for name in names]
return names
- def increment_filename(self, basename, path='/'):
+ def increment_filename(self, basename, path=''):
"""Return a non-used filename of the form basename."""
i = 0
while True:
@@ -97,7 +97,7 @@ class FileNotebookManager(NotebookManager):
if os.path.exists(path) is False:
raise web.HTTPError(404, "No file or directory found.")
- def notebook_exists(self, name, path='/'):
+ def notebook_exists(self, name, path=''):
"""Returns a True if the notebook exists. Else, returns False.
Parameters
@@ -111,8 +111,8 @@ class FileNotebookManager(NotebookManager):
-------
bool
"""
- path = self.get_os_path(name, path='/')
- return os.path.isfile(path)
+ nbpath = self.get_os_path(name, path=path)
+ return os.path.isfile(nbpath)
def list_notebooks(self, path):
"""Returns a list of dictionaries that are the standard model
@@ -137,7 +137,7 @@ class FileNotebookManager(NotebookManager):
notebooks = sorted(notebooks, key=lambda item: item['name'])
return notebooks
- def get_notebook_model(self, name, path='/', content=True):
+ def get_notebook_model(self, name, path='', content=True):
""" Takes a path and name for a notebook and returns it's model
Parameters
@@ -173,13 +173,13 @@ class FileNotebookManager(NotebookManager):
model['content'] = nb
return model
- def save_notebook_model(self, model, name, path='/'):
+ def save_notebook_model(self, model, name, path=''):
"""Save the notebook model and return the model with no content."""
if 'content' not in model:
raise web.HTTPError(400, u'No notebook JSON data provided')
- new_path = model.get('path', path)
+ new_path = model.get('path', path).strip('/')
new_name = model.get('name', name)
if path != new_path or name != new_name:
diff --git a/IPython/html/services/notebooks/handlers.py b/IPython/html/services/notebooks/handlers.py
index 40ae7e3..f834d95 100644
--- a/IPython/html/services/notebooks/handlers.py
+++ b/IPython/html/services/notebooks/handlers.py
@@ -20,10 +20,10 @@ import json
from tornado import web
-from ...utils import url_path_join
+from IPython.html.utils import url_path_join
from IPython.utils.jsonutil import date_default
-from ...base.handlers import IPythonHandler, json_errors
+from IPython.html.base.handlers import IPythonHandler, json_errors
#-----------------------------------------------------------------------------
# Notebook web service handlers
@@ -34,31 +34,32 @@ class NotebookHandler(IPythonHandler):
SUPPORTED_METHODS = (u'GET', u'PUT', u'PATCH', u'POST', u'DELETE')
- def notebook_location(self, name, path):
+ def notebook_location(self, name, path=''):
"""Return the full URL location of a notebook based.
Parameters
----------
name : unicode
- The name of the notebook like "foo.ipynb".
+ The base name of the notebook, such as "foo.ipynb".
path : unicode
The URL path of the notebook.
"""
- return url_path_join(self.base_project_url, u'/api/notebooks', path, name)
+ return url_path_join(self.base_project_url, 'api', 'notebooks', path, name)
@web.authenticated
@json_errors
- def get(self, notebook_path):
- """get checks if a notebook is not named, an returns a list of notebooks
+ def get(self, path='', name=None):
+ """
+ GET with path and no notebook lists notebooks in a directory
+ GET with path and notebook name
+
+ GET get checks if a notebook is not named, an returns a list of notebooks
in the notebook path given. If a name is given, return
the notebook representation"""
nbm = self.notebook_manager
- # path will have leading and trailing slashes, such as '/foo/bar/'
- name, path = nbm.named_notebook_path(notebook_path)
-
# Check to see if a notebook name was given
if name is None:
- # List notebooks in 'notebook_path'
+ # List notebooks in 'path'
notebooks = nbm.list_notebooks(path)
self.finish(json.dumps(notebooks, default=date_default))
else:
@@ -68,13 +69,11 @@ class NotebookHandler(IPythonHandler):
self.finish(json.dumps(model, default=date_default))
@web.authenticated
- # @json_errors
- def patch(self, notebook_path):
+ @json_errors
+ def patch(self, path='', name=None):
"""patch is currently used strictly for notebook renaming.
Changes the notebook name to the name given in data."""
nbm = self.notebook_manager
- # path will have leading and trailing slashes, such as '/foo/bar/'
- name, path = nbm.named_notebook_path(notebook_path)
if name is None:
raise web.HTTPError(400, u'Notebook name missing')
model = self.get_json_body()
@@ -90,11 +89,9 @@ class NotebookHandler(IPythonHandler):
@web.authenticated
@json_errors
- def post(self, notebook_path):
+ def post(self, path='', name=None):
"""Create a new notebook in the location given by 'notebook_path'."""
nbm = self.notebook_manager
- # path will have leading and trailing slashes, such as '/foo/bar/'
- name, path = nbm.named_notebook_path(notebook_path)
model = self.get_json_body()
if name is not None:
raise web.HTTPError(400, 'No name can be provided when POSTing a new notebook.')
@@ -108,11 +105,9 @@ class NotebookHandler(IPythonHandler):
@web.authenticated
@json_errors
- def put(self, notebook_path):
+ def put(self, path='', name=None):
"""saves the notebook in the location given by 'notebook_path'."""
nbm = self.notebook_manager
- # path will have leading and trailing slashes, such as '/foo/bar/'
- name, path = nbm.named_notebook_path(notebook_path)
model = self.get_json_body()
if model is None:
raise web.HTTPError(400, u'JSON body missing')
@@ -121,11 +116,9 @@ class NotebookHandler(IPythonHandler):
@web.authenticated
@json_errors
- def delete(self, notebook_path):
+ def delete(self, path='', name=None):
"""delete the notebook in the given notebook path"""
nbm = self.notebook_manager
- # path will have leading and trailing slashes, such as '/foo/bar/'
- name, path = nbm.named_notebook_path(notebook_path)
nbm.delete_notebook_model(name, path)
self.set_status(204)
self.finish()
@@ -137,26 +130,22 @@ class NotebookCheckpointsHandler(IPythonHandler):
@web.authenticated
@json_errors
- def get(self, notebook_path):
+ def get(self, path='', name=None):
"""get lists checkpoints for a notebook"""
nbm = self.notebook_manager
- # path will have leading and trailing slashes, such as '/foo/bar/'
- name, path = nbm.named_notebook_path(notebook_path)
checkpoints = nbm.list_checkpoints(name, path)
data = json.dumps(checkpoints, default=date_default)
self.finish(data)
@web.authenticated
@json_errors
- def post(self, notebook_path):
+ def post(self, path='', name=None):
"""post creates a new checkpoint"""
nbm = self.notebook_manager
- name, path = nbm.named_notebook_path(notebook_path)
- # path will have leading and trailing slashes, such as '/foo/bar/'
checkpoint = nbm.create_checkpoint(name, path)
data = json.dumps(checkpoint, default=date_default)
location = url_path_join(self.base_project_url, u'/api/notebooks',
- path, name, '/checkpoints', checkpoint[u'checkpoint_id'])
+ path, name, 'checkpoints', checkpoint[u'checkpoint_id'])
self.set_header(u'Location', location)
self.finish(data)
@@ -167,22 +156,18 @@ class ModifyNotebookCheckpointsHandler(IPythonHandler):
@web.authenticated
@json_errors
- def post(self, notebook_path, checkpoint_id):
+ def post(self, path, name, checkpoint_id):
"""post restores a notebook from a checkpoint"""
nbm = self.notebook_manager
- # path will have leading and trailing slashes, such as '/foo/bar/'
- name, path = nbm.named_notebook_path(notebook_path)
nbm.restore_checkpoint(checkpoint_id, name, path)
self.set_status(204)
self.finish()
@web.authenticated
@json_errors
- def delete(self, notebook_path, checkpoint_id):
+ def delete(self, path, name, checkpoint_id):
"""delete clears a checkpoint for a given notebook"""
nbm = self.notebook_manager
- # path will have leading and trailing slashes, such as '/foo/bar/'
- name, path = nbm.named_notebook_path(notebook_path)
nbm.delete_checkpoint(checkpoint_id, name, path)
self.set_status(204)
self.finish()
@@ -192,14 +177,17 @@ class ModifyNotebookCheckpointsHandler(IPythonHandler):
#-----------------------------------------------------------------------------
-_notebook_path_regex = r"(?P.*)"
+_path_regex = r"(?P.*)"
_checkpoint_id_regex = r"(?P[\w-]+)"
+_notebook_name_regex = r"(?P[^/]+\.ipynb)"
+_notebook_path_regex = "%s/%s" % (_path_regex, _notebook_name_regex)
default_handlers = [
- (r"/api/notebooks/%s/checkpoints" % _notebook_path_regex, NotebookCheckpointsHandler),
- (r"/api/notebooks/%s/checkpoints/%s" % (_notebook_path_regex, _checkpoint_id_regex),
+ (r"/api/notebooks/?%s/checkpoints" % _notebook_path_regex, NotebookCheckpointsHandler),
+ (r"/api/notebooks/?%s/checkpoints/%s" % (_notebook_path_regex, _checkpoint_id_regex),
ModifyNotebookCheckpointsHandler),
- (r"/api/notebooks%s" % _notebook_path_regex, NotebookHandler),
+ (r"/api/notebooks/?%s" % _notebook_path_regex, NotebookHandler),
+ (r"/api/notebooks/?%s/?" % _path_regex, NotebookHandler),
]
diff --git a/IPython/html/services/notebooks/nbmanager.py b/IPython/html/services/notebooks/nbmanager.py
index ad5a7b1..c17f420 100644
--- a/IPython/html/services/notebooks/nbmanager.py
+++ b/IPython/html/services/notebooks/nbmanager.py
@@ -45,45 +45,33 @@ class NotebookManager(LoggingConfigurable):
""")
filename_ext = Unicode(u'.ipynb')
-
- def named_notebook_path(self, notebook_path):
- """Given notebook_path (*always* a URL path to notebook), returns a
- (name, path) tuple, where name is a .ipynb file, and path is the
- URL path that describes the file system path for the file.
- It *always* starts *and* ends with a '/' character.
-
+
+ def path_exists(self, path):
+ """Does the API-style path (directory) actually exist?
+
+ Override this method for non-filesystem-based notebooks.
+
Parameters
----------
- notebook_path : string
- A path that may be a .ipynb name or a directory
-
+ path : string
+ The
+
Returns
-------
- name : string or None
- the filename of the notebook, or None if not a .ipynb extension
- path : string
- the path to the directory which contains the notebook
+ exists : bool
+ Whether the path does indeed exist.
"""
- names = notebook_path.split('/')
- names = [n for n in names if n != ''] # remove duplicate splits
-
- names = [''] + names
-
- if names and names[-1].endswith(".ipynb"):
- name = names[-1]
- path = "/".join(names[:-1]) + '/'
- else:
- name = None
- path = "/".join(names) + '/'
- return name, path
+ os_path = self.get_os_path(name, path)
+ return os.path.exists(os_path)
- def get_os_path(self, fname=None, path='/'):
+
+ def get_os_path(self, name=None, path=''):
"""Given a notebook name and a URL path, return its file system
path.
Parameters
----------
- fname : string
+ name : string
The name of a notebook file with the .ipynb extension
path : string
The relative URL path (with '/' as separator) to the named
@@ -96,10 +84,10 @@ class NotebookManager(LoggingConfigurable):
server started), the relative path, and the filename with the
current operating system's url.
"""
- parts = path.split('/')
+ parts = path.strip('/').split('/')
parts = [p for p in parts if p != ''] # remove duplicate splits
- if fname is not None:
- parts += [fname]
+ if name is not None:
+ parts.append(name)
path = os.path.join(self.notebook_dir, *parts)
return path
@@ -132,7 +120,7 @@ class NotebookManager(LoggingConfigurable):
# Main notebook API
- def increment_filename(self, basename, path='/'):
+ def increment_filename(self, basename, path=''):
"""Increment a notebook filename without the .ipynb to make it unique.
Parameters
@@ -157,15 +145,15 @@ class NotebookManager(LoggingConfigurable):
"""
raise NotImplementedError('must be implemented in a subclass')
- def get_notebook_model(self, name, path='/', content=True):
+ def get_notebook_model(self, name, path='', content=True):
"""Get the notebook model with or without content."""
raise NotImplementedError('must be implemented in a subclass')
- def save_notebook_model(self, model, name, path='/'):
+ def save_notebook_model(self, model, name, path=''):
"""Save the notebook model and return the model with no content."""
raise NotImplementedError('must be implemented in a subclass')
- def update_notebook_model(self, model, name, path='/'):
+ def update_notebook_model(self, model, name, path=''):
"""Update the notebook model and return the model with no content."""
raise NotImplementedError('must be implemented in a subclass')
@@ -173,7 +161,7 @@ class NotebookManager(LoggingConfigurable):
"""Delete notebook by name and path."""
raise NotImplementedError('must be implemented in a subclass')
- def create_notebook_model(self, model=None, path='/'):
+ def create_notebook_model(self, model=None, path=''):
"""Create a new untitled notebook and return its model with no content."""
name = self.increment_filename('Untitled', path)
if model is None:
diff --git a/IPython/html/services/notebooks/tests/test_nbmanager.py b/IPython/html/services/notebooks/tests/test_nbmanager.py
index 9ae9f0a..cf65fbf 100644
--- a/IPython/html/services/notebooks/tests/test_nbmanager.py
+++ b/IPython/html/services/notebooks/tests/test_nbmanager.py
@@ -67,48 +67,6 @@ class TestNotebookManager(TestCase):
except OSError:
print "Directory already exists."
- def test_named_notebook_path(self):
- """the `named_notebook_path` method takes a URL path to
- a notebook and returns a url path split into nb and path"""
- nm = NotebookManager()
-
- # doesn't end with ipynb, should just be path
- name, path = nm.named_notebook_path('hello')
- self.assertEqual(name, None)
- self.assertEqual(path, '/hello/')
-
- # Root path returns just the root slash
- name, path = nm.named_notebook_path('/')
- self.assertEqual(name, None)
- self.assertEqual(path, '/')
-
- # get notebook, and return the path as '/'
- name, path = nm.named_notebook_path('notebook.ipynb')
- self.assertEqual(name, 'notebook.ipynb')
- self.assertEqual(path, '/')
-
- # Test a notebook name with leading slash returns
- # the same as above
- name, path = nm.named_notebook_path('/notebook.ipynb')
- self.assertEqual(name, 'notebook.ipynb')
- self.assertEqual(path, '/')
-
- # Multiple path arguments splits the notebook name
- # and returns path with leading and trailing '/'
- name, path = nm.named_notebook_path('/this/is/a/path/notebook.ipynb')
- self.assertEqual(name, 'notebook.ipynb')
- self.assertEqual(path, '/this/is/a/path/')
-
- # path without leading slash is returned with leading slash
- name, path = nm.named_notebook_path('path/without/leading/slash/notebook.ipynb')
- self.assertEqual(name, 'notebook.ipynb')
- self.assertEqual(path, '/path/without/leading/slash/')
-
- # path with spaces and no leading or trailing '/'
- name, path = nm.named_notebook_path('foo / bar% path& to# @/ notebook name.ipynb')
- self.assertEqual(name, ' notebook name.ipynb')
- self.assertEqual(path, '/foo / bar% path& to# @/')
-
def test_url_encode(self):
nm = NotebookManager()
diff --git a/IPython/html/tree/handlers.py b/IPython/html/tree/handlers.py
index b7d7614..01b23cd 100644
--- a/IPython/html/tree/handlers.py
+++ b/IPython/html/tree/handlers.py
@@ -20,6 +20,7 @@ import os
from tornado import web
from ..base.handlers import IPythonHandler
from ..utils import url_path_join, path2url, url2path
+from ..services.notebooks.handlers import _notebook_path_regex, _path_regex
#-----------------------------------------------------------------------------
# Handlers
@@ -30,23 +31,19 @@ class TreeHandler(IPythonHandler):
"""Render the tree view, listing notebooks, clusters, etc."""
@web.authenticated
- def get(self, notebook_path=""):
+ def get(self, path='', name=None):
nbm = self.notebook_manager
- name, path = nbm.named_notebook_path(notebook_path)
if name is not None:
# is a notebook, redirect to notebook handler
url = url_path_join(self.base_project_url, 'notebooks', path, name)
self.redirect(url)
else:
- location = nbm.get_os_path(path=path)
-
- if not os.path.exists(location):
+ if not nbm.path_exists(path=path):
# no such directory, 404
raise web.HTTPError(404)
-
self.write(self.render_template('tree.html',
project=self.project_dir,
- tree_url_path=path2url(location),
+ tree_url_path=path,
notebook_path=path,
))
@@ -55,8 +52,8 @@ class TreeRedirectHandler(IPythonHandler):
"""Redirect a request to the corresponding tree URL"""
@web.authenticated
- def get(self, notebook_path=''):
- url = url_path_join(self.base_project_url, 'tree', notebook_path)
+ def get(self, path=''):
+ url = url_path_join(self.base_project_url, 'tree', path).rstrip('/')
self.log.debug("Redirecting %s to %s", self.request.uri, url)
self.redirect(url)
@@ -66,11 +63,10 @@ class TreeRedirectHandler(IPythonHandler):
#-----------------------------------------------------------------------------
-_notebook_path_regex = r"(?P.+)"
-
default_handlers = [
- (r"/tree/%s/" % _notebook_path_regex, TreeRedirectHandler),
- (r"/tree/%s" % _notebook_path_regex, TreeHandler),
+ (r"/tree/(.*)/", TreeRedirectHandler),
+ (r"/tree/?%s" % _notebook_path_regex, TreeHandler),
+ (r"/tree/?%s" % _path_regex, TreeHandler),
(r"/tree/", TreeRedirectHandler),
(r"/tree", TreeHandler),
(r"/", TreeRedirectHandler),