Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 37 additions & 8 deletions IPython/html/base/handlers.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""Base Tornado handlers for the notebook."""
"""Base Tornado handlers for the notebook server."""

# Copyright (c) IPython Development Team.
# Distributed under the terms of the Modified BSD License.
Expand Down Expand Up @@ -27,7 +27,7 @@
from IPython.config import Application
from IPython.utils.path import filefind
from IPython.utils.py3compat import string_types
from IPython.html.utils import is_hidden
from IPython.html.utils import is_hidden, url_path_join, url_escape

#-----------------------------------------------------------------------------
# Top-level handlers
Expand Down Expand Up @@ -141,8 +141,8 @@ def kernel_manager(self):
return self.settings['kernel_manager']

@property
def notebook_manager(self):
return self.settings['notebook_manager']
def contents_manager(self):
return self.settings['contents_manager']

@property
def cluster_manager(self):
Expand All @@ -156,10 +156,6 @@ def session_manager(self):
def kernel_spec_manager(self):
return self.settings['kernel_spec_manager']

@property
def project_dir(self):
return self.notebook_manager.notebook_dir

#---------------------------------------------------------------
# CORS
#---------------------------------------------------------------
Expand Down Expand Up @@ -409,13 +405,46 @@ class TrailingSlashHandler(web.RequestHandler):
def get(self):
self.redirect(self.request.uri.rstrip('/'))


class FilesRedirectHandler(IPythonHandler):
"""Handler for redirecting relative URLs to the /files/ handler"""
def get(self, path=''):
cm = self.contents_manager
if cm.path_exists(path):
# it's a *directory*, redirect to /tree
url = url_path_join(self.base_url, 'tree', path)
else:
orig_path = path
# otherwise, redirect to /files
parts = path.split('/')
path = '/'.join(parts[:-1])
name = parts[-1]

if not cm.file_exists(name=name, path=path) and 'files' in parts:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should it be only parts[0] == 'files' instead of 'files' in parts?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, because files should be a relative URL, and thus can occur at any point in the URL.

# redirect without files/ iff it would 404
# this preserves pre-2.0-style 'files/' links
self.log.warn("Deprecated files/ URL: %s", orig_path)
parts.remove('files')
path = '/'.join(parts[:-1])

if not cm.file_exists(name=name, path=path):
raise web.HTTPError(404)

url = url_path_join(self.base_url, 'files', path, name)
url = url_escape(url)
self.log.debug("Redirecting %s to %s", self.request.path, url)
self.redirect(url)


#-----------------------------------------------------------------------------
# URL pattern fragments for re-use
#-----------------------------------------------------------------------------

path_regex = r"(?P<path>(?:/.*)*)"
notebook_name_regex = r"(?P<name>[^/]+\.ipynb)"
notebook_path_regex = "%s/%s" % (path_regex, notebook_name_regex)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are these two stlil needed?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They are still used in notebook and nbconvert URLs.

file_name_regex = r"(?P<name>[^/]+)"
file_path_regex = "%s/%s" % (path_regex, file_name_regex)

#-----------------------------------------------------------------------------
# URL to handler mappings
Expand Down
14 changes: 12 additions & 2 deletions IPython/html/nbconvert/handlers.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
"""Tornado handlers for nbconvert."""

# Copyright (c) IPython Development Team.
# Distributed under the terms of the Modified BSD License.

import io
import os
import zipfile

from tornado import web

from ..base.handlers import IPythonHandler, notebook_path_regex
from ..base.handlers import (
IPythonHandler, FilesRedirectHandler,
notebook_path_regex, path_regex,
)
from IPython.nbformat.current import to_notebook_json

from IPython.utils.py3compat import cast_bytes
Expand Down Expand Up @@ -73,7 +81,7 @@ def get(self, format, path='', name=None):
exporter = get_exporter(format, config=self.config, log=self.log)

path = path.strip('/')
model = self.notebook_manager.get_notebook(name=name, path=path)
model = self.contents_manager.get_model(name=name, path=path)

self.set_header('Last-Modified', model['last_modified'])

Expand Down Expand Up @@ -123,6 +131,7 @@ def post(self, format):

self.finish(output)


#-----------------------------------------------------------------------------
# URL to handler mappings
#-----------------------------------------------------------------------------
Expand All @@ -134,4 +143,5 @@ def post(self, format):
(r"/nbconvert/%s%s" % (_format_regex, notebook_path_regex),
NbconvertFileHandler),
(r"/nbconvert/%s" % _format_regex, NbconvertPostHandler),
(r"/nbconvert/html%s" % path_regex, FilesRedirectHandler),
]
4 changes: 2 additions & 2 deletions IPython/html/nbconvert/tests/test_nbconvert_handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ def test_from_file_zip(self):

@onlyif_cmds_exist('pandoc')
def test_from_post(self):
nbmodel_url = url_path_join(self.base_url(), 'api/notebooks/foo/testnb.ipynb')
nbmodel_url = url_path_join(self.base_url(), 'api/contents/foo/testnb.ipynb')
nbmodel = requests.get(nbmodel_url).json()

r = self.nbconvert_api.from_post(format='html', nbmodel=nbmodel)
Expand All @@ -121,7 +121,7 @@ def test_from_post(self):

@onlyif_cmds_exist('pandoc')
def test_from_post_zip(self):
nbmodel_url = url_path_join(self.base_url(), 'api/notebooks/foo/testnb.ipynb')
nbmodel_url = url_path_join(self.base_url(), 'api/contents/foo/testnb.ipynb')
nbmodel = requests.get(nbmodel_url).json()

r = self.nbconvert_api.from_post(format='latex', nbmodel=nbmodel)
Expand Down
61 changes: 11 additions & 50 deletions IPython/html/notebook/handlers.py
Original file line number Diff line number Diff line change
@@ -1,31 +1,17 @@
"""Tornado handlers for the live notebook view.
"""Tornado handlers for the live notebook view."""

Authors:

* Brian Granger
"""

#-----------------------------------------------------------------------------
# Copyright (C) 2011 The IPython Development Team
#
# Distributed under the terms of the BSD License. The full license is in
# the file COPYING, distributed as part of this software.
#-----------------------------------------------------------------------------

#-----------------------------------------------------------------------------
# Imports
#-----------------------------------------------------------------------------
# Copyright (c) IPython Development Team.
# Distributed under the terms of the Modified BSD License.

import os
from tornado import web
HTTPError = web.HTTPError

from ..base.handlers import IPythonHandler, notebook_path_regex, path_regex
from ..utils import url_path_join, url_escape

#-----------------------------------------------------------------------------
# Handlers
#-----------------------------------------------------------------------------
from ..base.handlers import (
IPythonHandler, FilesRedirectHandler,
notebook_path_regex, path_regex,
)
from ..utils import url_escape


class NotebookHandler(IPythonHandler):
Expand All @@ -35,48 +21,23 @@ 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."""
path = path.strip('/')
nbm = self.notebook_manager
cm = self.contents_manager
if name is None:
raise web.HTTPError(500, "This shouldn't be accessible: %s" % self.request.uri)

# a .ipynb filename was given
if not nbm.notebook_exists(name, path):
if not cm.file_exists(name, path):
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should the contents manager have methods/parameters for things like "does this exist and is it a notebook", or "load this, throwing an error if it is not a notebook"?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it's necessary. The caller can always check the type field in the model if it's desired.

raise web.HTTPError(404, u'Notebook does not exist: %s/%s' % (path, name))
name = url_escape(name)
path = url_escape(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,
)
)

class NotebookRedirectHandler(IPythonHandler):
def get(self, path=''):
nbm = self.notebook_manager
if nbm.path_exists(path):
# it's a *directory*, redirect to /tree
url = url_path_join(self.base_url, 'tree', path)
else:
# otherwise, redirect to /files
if '/files/' in path:
# redirect without files/ iff it would 404
# this preserves pre-2.0-style 'files/' links
# FIXME: this is hardcoded based on notebook_path,
# but so is the files handler itself,
# so it should work until both are cleaned up.
parts = path.split('/')
files_path = os.path.join(nbm.notebook_dir, *parts)
if not os.path.exists(files_path):
self.log.warn("Deprecated files/ URL: %s", path)
path = path.replace('/files/', '/', 1)

url = url_path_join(self.base_url, 'files', path)
url = url_escape(url)
self.log.debug("Redirecting %s to %s", self.request.path, url)
self.redirect(url)

#-----------------------------------------------------------------------------
# URL to handler mappings
Expand All @@ -85,6 +46,6 @@ def get(self, path=''):

default_handlers = [
(r"/notebooks%s" % notebook_path_regex, NotebookHandler),
(r"/notebooks%s" % path_regex, NotebookRedirectHandler),
(r"/notebooks%s" % path_regex, FilesRedirectHandler),
]

48 changes: 25 additions & 23 deletions IPython/html/notebookapp.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@
from .base.handlers import Template404
from .log import log_request
from .services.kernels.kernelmanager import MappingKernelManager
from .services.notebooks.nbmanager import NotebookManager
from .services.notebooks.filenbmanager import FileNotebookManager
from .services.contents.manager import ContentsManager
from .services.contents.filemanager import FileContentsManager
from .services.clusters.clustermanager import ClusterManager
from .services.sessions.sessionmanager import SessionManager

Expand Down Expand Up @@ -121,19 +121,19 @@ def load_handlers(name):

class NotebookWebApplication(web.Application):

def __init__(self, ipython_app, kernel_manager, notebook_manager,
def __init__(self, ipython_app, kernel_manager, contents_manager,
cluster_manager, session_manager, kernel_spec_manager, log,
base_url, settings_overrides, jinja_env_options):

settings = self.init_settings(
ipython_app, kernel_manager, notebook_manager, cluster_manager,
ipython_app, kernel_manager, contents_manager, cluster_manager,
session_manager, kernel_spec_manager, log, base_url,
settings_overrides, jinja_env_options)
handlers = self.init_handlers(settings)

super(NotebookWebApplication, self).__init__(handlers, **settings)

def init_settings(self, ipython_app, kernel_manager, notebook_manager,
def init_settings(self, ipython_app, kernel_manager, contents_manager,
cluster_manager, session_manager, kernel_spec_manager,
log, base_url, settings_overrides,
jinja_env_options=None):
Expand Down Expand Up @@ -165,7 +165,7 @@ def init_settings(self, ipython_app, kernel_manager, notebook_manager,

# managers
kernel_manager=kernel_manager,
notebook_manager=notebook_manager,
contents_manager=contents_manager,
cluster_manager=cluster_manager,
session_manager=session_manager,
kernel_spec_manager=kernel_spec_manager,
Expand Down Expand Up @@ -193,18 +193,20 @@ def init_handlers(self, settings):
handlers.extend(load_handlers('nbconvert.handlers'))
handlers.extend(load_handlers('kernelspecs.handlers'))
handlers.extend(load_handlers('services.kernels.handlers'))
handlers.extend(load_handlers('services.notebooks.handlers'))
handlers.extend(load_handlers('services.contents.handlers'))
handlers.extend(load_handlers('services.clusters.handlers'))
handlers.extend(load_handlers('services.sessions.handlers'))
handlers.extend(load_handlers('services.nbconvert.handlers'))
handlers.extend(load_handlers('services.kernelspecs.handlers'))
# FIXME: /files/ should be handled by the Contents service when it exists
nbm = settings['notebook_manager']
if hasattr(nbm, 'notebook_dir'):
handlers.extend([
(r"/files/(.*)", AuthenticatedFileHandler, {'path' : nbm.notebook_dir}),
cm = settings['contents_manager']
if hasattr(cm, 'root_dir'):
handlers.append(
(r"/files/(.*)", AuthenticatedFileHandler, {'path' : cm.root_dir}),
)
handlers.append(
(r"/nbextensions/(.*)", FileFindHandler, {'path' : settings['nbextensions_path']}),
])
)
# prepend base_url onto the patterns that we match
new_handlers = []
for handler in handlers:
Expand Down Expand Up @@ -264,9 +266,9 @@ def start(self):
)

# Add notebook manager flags
flags.update(boolean_flag('script', 'FileNotebookManager.save_script',
'Auto-save a .py script everytime the .ipynb notebook is saved',
'Do not auto-save .py scripts for every notebook'))
flags.update(boolean_flag('script', 'FileContentsManager.save_script',
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to note: we are taking this option away without apparently having a deprecation cycle (the sort where we mark things as deprecated, not the sort where we grumble about them).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've put the flag back, so the notebook will start with a warning instead of refusing to start.

'DEPRECATED, IGNORED',
'DEPRECATED, IGNORED'))

aliases = dict(base_aliases)

Expand Down Expand Up @@ -302,7 +304,7 @@ class NotebookApp(BaseIPythonApplication):

classes = [
KernelManager, ProfileDir, Session, MappingKernelManager,
NotebookManager, FileNotebookManager, NotebookNotary,
ContentsManager, FileContentsManager, NotebookNotary,
]
flags = Dict(flags)
aliases = Dict(aliases)
Expand Down Expand Up @@ -557,7 +559,7 @@ def _mathjax_url_changed(self, name, old, new):
else:
self.log.info("Using MathJax: %s", new)

notebook_manager_class = DottedObjectName('IPython.html.services.notebooks.filenbmanager.FileNotebookManager',
contents_manager_class = DottedObjectName('IPython.html.services.contents.filemanager.FileContentsManager',
config=True,
help='The notebook manager class to use.'
)
Expand Down Expand Up @@ -621,7 +623,7 @@ def _notebook_dir_changed(self, name, old, new):
raise TraitError("No such notebook dir: %r" % new)

# setting App.notebook_dir implies setting notebook and kernel dirs as well
self.config.FileNotebookManager.notebook_dir = new
self.config.FileContentsManager.root_dir = new
self.config.MappingKernelManager.root_dir = new


Expand Down Expand Up @@ -658,12 +660,12 @@ def init_configurables(self):
parent=self, log=self.log, kernel_argv=self.kernel_argv,
connection_dir = self.profile_dir.security_dir,
)
kls = import_item(self.notebook_manager_class)
self.notebook_manager = kls(parent=self, log=self.log)
kls = import_item(self.contents_manager_class)
self.contents_manager = kls(parent=self, log=self.log)
kls = import_item(self.session_manager_class)
self.session_manager = kls(parent=self, log=self.log,
kernel_manager=self.kernel_manager,
notebook_manager=self.notebook_manager)
contents_manager=self.contents_manager)
kls = import_item(self.cluster_manager_class)
self.cluster_manager = kls(parent=self, log=self.log)
self.cluster_manager.update_profiles()
Expand All @@ -688,7 +690,7 @@ def init_webapp(self):
self.webapp_settings['allow_credentials'] = self.allow_credentials

self.web_app = NotebookWebApplication(
self, self.kernel_manager, self.notebook_manager,
self, self.kernel_manager, self.contents_manager,
self.cluster_manager, self.session_manager, self.kernel_spec_manager,
self.log, self.base_url, self.webapp_settings,
self.jinja_environment_options
Expand Down Expand Up @@ -838,7 +840,7 @@ def cleanup_kernels(self):

def notebook_info(self):
"Return the current working directory and the server url information"
info = self.notebook_manager.info_string() + "\n"
info = self.contents_manager.info_string() + "\n"
info += "%d active kernels \n" % len(self.kernel_manager._kernels)
return info + "The IPython Notebook is running at: %s" % self.display_url

Expand Down
Loading