handlers.py
245 lines
| 8.9 KiB
| text/x-python
|
PythonLexer
Brian E. Granger
|
r10642 | """Tornado handlers for the notebooks web service. | ||
Brian E. Granger
|
r10641 | |||
Authors: | ||||
* Brian Granger | ||||
""" | ||||
#----------------------------------------------------------------------------- | ||||
# Copyright (C) 2008-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 | ||||
#----------------------------------------------------------------------------- | ||||
Zachary Sailer
|
r13045 | import json | ||
Brian E. Granger
|
r10641 | |||
Zachary Sailer
|
r13045 | from tornado import web | ||
Brian E. Granger
|
r10641 | |||
MinRK
|
r13067 | from IPython.html.utils import url_path_join | ||
Brian E. Granger
|
r10641 | from IPython.utils.jsonutil import date_default | ||
MinRK
|
r13067 | from IPython.html.base.handlers import IPythonHandler, json_errors | ||
Brian E. Granger
|
r10641 | |||
#----------------------------------------------------------------------------- | ||||
# Notebook web service handlers | ||||
#----------------------------------------------------------------------------- | ||||
Zachary Sailer
|
r12984 | |||
Brian E. Granger
|
r10641 | class NotebookHandler(IPythonHandler): | ||
Zachary Sailer
|
r13045 | SUPPORTED_METHODS = (u'GET', u'PUT', u'PATCH', u'POST', u'DELETE') | ||
MinRK
|
r13067 | def notebook_location(self, name, path=''): | ||
Zachary Sailer
|
r13045 | """Return the full URL location of a notebook based. | ||
Parameters | ||||
---------- | ||||
name : unicode | ||||
MinRK
|
r13067 | The base name of the notebook, such as "foo.ipynb". | ||
Zachary Sailer
|
r13045 | path : unicode | ||
The URL path of the notebook. | ||||
""" | ||||
MinRK
|
r13067 | return url_path_join(self.base_project_url, 'api', 'notebooks', path, name) | ||
Brian E. Granger
|
r10641 | |||
MinRK
|
r11644 | @web.authenticated | ||
Zachary Sailer
|
r13045 | @json_errors | ||
MinRK
|
r13067 | 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 | ||||
Zachary Sailer
|
r13036 | in the notebook path given. If a name is given, return | ||
the notebook representation""" | ||||
Brian E. Granger
|
r10641 | nbm = self.notebook_manager | ||
Zachary Sailer
|
r13036 | # Check to see if a notebook name was given | ||
if name is None: | ||||
MinRK
|
r13067 | # List notebooks in 'path' | ||
Zachary Sailer
|
r12997 | notebooks = nbm.list_notebooks(path) | ||
Zachary Sailer
|
r13045 | self.finish(json.dumps(notebooks, default=date_default)) | ||
MinRK
|
r13076 | return | ||
# get and return notebook representation | ||||
model = nbm.get_notebook_model(name, path) | ||||
self.set_header(u'Last-Modified', model[u'last_modified']) | ||||
if self.get_argument('download', default='False') == 'True': | ||||
format = self.get_argument('format', default='json') | ||||
if format == u'json': | ||||
self.set_header('Content-Type', 'application/json') | ||||
raise web.HTTPError(400, "Unrecognized format: %s" % ext) | ||||
self.set_header('Content-Disposition', | ||||
'attachment; filename="%s"' % name | ||||
) | ||||
self.finish(json.dumps(model['content'], default=date_default)) | ||||
Zachary Sailer
|
r12984 | else: | ||
Zachary Sailer
|
r13045 | self.finish(json.dumps(model, default=date_default)) | ||
Brian E. Granger
|
r10641 | |||
@web.authenticated | ||||
MinRK
|
r13067 | @json_errors | ||
def patch(self, path='', name=None): | ||||
MinRK
|
r13074 | """PATCH renames a notebook without re-uploading content.""" | ||
Zachary Sailer
|
r12997 | nbm = self.notebook_manager | ||
Zachary Sailer
|
r13045 | if name is None: | ||
raise web.HTTPError(400, u'Notebook name missing') | ||||
model = self.get_json_body() | ||||
if model is None: | ||||
raise web.HTTPError(400, u'JSON body missing') | ||||
model = nbm.update_notebook_model(model, name, path) | ||||
MinRK
|
r13082 | location = self.notebook_location(model[u'name'], model[u'path']) | ||
self.set_header(u'Location', location) | ||||
Zachary Sailer
|
r13045 | self.set_header(u'Last-Modified', model[u'last_modified']) | ||
self.finish(json.dumps(model, default=date_default)) | ||||
Zachary Sailer
|
r12997 | |||
@web.authenticated | ||||
Zachary Sailer
|
r13045 | @json_errors | ||
MinRK
|
r13067 | def post(self, path='', name=None): | ||
MinRK
|
r13074 | """Create a new notebook in the specified path. | ||
POST creates new notebooks. | ||||
POST /api/notebooks/path : new untitled notebook in path | ||||
POST /api/notebooks/path/notebook.ipynb : new notebook with name in path | ||||
If content specified upload notebook, otherwise start empty. | ||||
""" | ||||
Brian E. Granger
|
r10641 | nbm = self.notebook_manager | ||
Zachary Sailer
|
r13045 | model = self.get_json_body() | ||
MinRK
|
r13074 | if name is None: | ||
# creating new notebook, model doesn't make sense | ||||
if model is not None: | ||||
raise web.HTTPError(400, "Model not valid when creating untitled notebooks.") | ||||
model = nbm.create_notebook_model(path=path) | ||||
else: | ||||
if model is None: | ||||
self.log.info("Creating new Notebook at %s/%s", path, name) | ||||
model = {} | ||||
else: | ||||
self.log.info("Uploading Notebook to %s/%s", path, name) | ||||
# set the model name from the URL | ||||
model['name'] = name | ||||
model = nbm.create_notebook_model(model, path) | ||||
Zachary Sailer
|
r13045 | location = self.notebook_location(model[u'name'], model[u'path']) | ||
self.set_header(u'Location', location) | ||||
self.set_header(u'Last-Modified', model[u'last_modified']) | ||||
self.set_status(201) | ||||
self.finish(json.dumps(model, default=date_default)) | ||||
Zachary Sailer
|
r13036 | |||
@web.authenticated | ||||
Zachary Sailer
|
r13045 | @json_errors | ||
MinRK
|
r13067 | def put(self, path='', name=None): | ||
Zachary Sailer
|
r13036 | """saves the notebook in the location given by 'notebook_path'.""" | ||
nbm = self.notebook_manager | ||||
Zachary Sailer
|
r13045 | model = self.get_json_body() | ||
if model is None: | ||||
raise web.HTTPError(400, u'JSON body missing') | ||||
nbm.save_notebook_model(model, name, path) | ||||
self.finish(json.dumps(model, default=date_default)) | ||||
Brian E. Granger
|
r10641 | |||
@web.authenticated | ||||
Zachary Sailer
|
r13045 | @json_errors | ||
MinRK
|
r13067 | def delete(self, path='', name=None): | ||
Zachary Sailer
|
r13045 | """delete the notebook in the given notebook path""" | ||
Zachary Sailer
|
r12984 | nbm = self.notebook_manager | ||
Zachary Sailer
|
r13045 | nbm.delete_notebook_model(name, path) | ||
Brian E. Granger
|
r10641 | self.set_status(204) | ||
self.finish() | ||||
MinRK
|
r13074 | class NotebookCopyHandler(IPythonHandler): | ||
SUPPORTED_METHODS = ('POST') | ||||
@web.authenticated | ||||
@json_errors | ||||
def post(self, path='', name=None): | ||||
"""Copy an existing notebook.""" | ||||
nbm = self.notebook_manager | ||||
model = self.get_json_body() | ||||
if name is None: | ||||
raise web.HTTPError(400, "Notebook name required") | ||||
self.log.info("Copying Notebook %s/%s", path, name) | ||||
model = nbm.copy_notebook(name, path) | ||||
location = url_path_join( | ||||
self.base_project_url, 'api', 'notebooks', | ||||
model['path'], model['name'], | ||||
) | ||||
self.set_header(u'Location', location) | ||||
self.set_header(u'Last-Modified', model[u'last_modified']) | ||||
self.set_status(201) | ||||
self.finish(json.dumps(model, default=date_default)) | ||||
Brian E. Granger
|
r10641 | |||
class NotebookCheckpointsHandler(IPythonHandler): | ||||
SUPPORTED_METHODS = ('GET', 'POST') | ||||
@web.authenticated | ||||
Zachary Sailer
|
r13045 | @json_errors | ||
MinRK
|
r13067 | def get(self, path='', name=None): | ||
Brian E. Granger
|
r10641 | """get lists checkpoints for a notebook""" | ||
nbm = self.notebook_manager | ||||
Zachary Sailer
|
r12984 | checkpoints = nbm.list_checkpoints(name, path) | ||
Zachary Sailer
|
r13045 | data = json.dumps(checkpoints, default=date_default) | ||
Brian E. Granger
|
r10641 | self.finish(data) | ||
@web.authenticated | ||||
Zachary Sailer
|
r13045 | @json_errors | ||
MinRK
|
r13067 | def post(self, path='', name=None): | ||
Brian E. Granger
|
r10641 | """post creates a new checkpoint""" | ||
nbm = self.notebook_manager | ||||
Zachary Sailer
|
r12984 | checkpoint = nbm.create_checkpoint(name, path) | ||
Zachary Sailer
|
r13045 | data = json.dumps(checkpoint, default=date_default) | ||
location = url_path_join(self.base_project_url, u'/api/notebooks', | ||||
MinRK
|
r13067 | path, name, 'checkpoints', checkpoint[u'checkpoint_id']) | ||
Zachary Sailer
|
r13045 | self.set_header(u'Location', location) | ||
Brian E. Granger
|
r10641 | self.finish(data) | ||
class ModifyNotebookCheckpointsHandler(IPythonHandler): | ||||
SUPPORTED_METHODS = ('POST', 'DELETE') | ||||
@web.authenticated | ||||
Zachary Sailer
|
r13045 | @json_errors | ||
MinRK
|
r13067 | def post(self, path, name, checkpoint_id): | ||
Brian E. Granger
|
r10641 | """post restores a notebook from a checkpoint""" | ||
nbm = self.notebook_manager | ||||
Zachary Sailer
|
r13045 | nbm.restore_checkpoint(checkpoint_id, name, path) | ||
Brian E. Granger
|
r10641 | self.set_status(204) | ||
self.finish() | ||||
@web.authenticated | ||||
Zachary Sailer
|
r13045 | @json_errors | ||
MinRK
|
r13067 | def delete(self, path, name, checkpoint_id): | ||
Brian E. Granger
|
r10641 | """delete clears a checkpoint for a given notebook""" | ||
nbm = self.notebook_manager | ||||
Zachary Sailer
|
r13045 | nbm.delete_checkpoint(checkpoint_id, name, path) | ||
Brian E. Granger
|
r10641 | self.set_status(204) | ||
self.finish() | ||||
Zachary Sailer
|
r12984 | |||
Brian E. Granger
|
r10647 | #----------------------------------------------------------------------------- | ||
# URL to handler mappings | ||||
#----------------------------------------------------------------------------- | ||||
MinRK
|
r13079 | _path_regex = r"(?P<path>(?:/.*)*)" | ||
Brian E. Granger
|
r10647 | _checkpoint_id_regex = r"(?P<checkpoint_id>[\w-]+)" | ||
MinRK
|
r13067 | _notebook_name_regex = r"(?P<name>[^/]+\.ipynb)" | ||
_notebook_path_regex = "%s/%s" % (_path_regex, _notebook_name_regex) | ||||
Brian E. Granger
|
r10647 | |||
default_handlers = [ | ||||
MinRK
|
r13079 | (r"/api/notebooks%s/copy" % _notebook_path_regex, NotebookCopyHandler), | ||
(r"/api/notebooks%s/checkpoints" % _notebook_path_regex, NotebookCheckpointsHandler), | ||||
(r"/api/notebooks%s/checkpoints/%s" % (_notebook_path_regex, _checkpoint_id_regex), | ||||
Zachary Sailer
|
r12984 | ModifyNotebookCheckpointsHandler), | ||
MinRK
|
r13079 | (r"/api/notebooks%s" % _notebook_path_regex, NotebookHandler), | ||
(r"/api/notebooks%s" % _path_regex, NotebookHandler), | ||||
Brian E. Granger
|
r10647 | ] | ||
Brian E. Granger
|
r10641 | |||