Add REST API for retrieving, storing and updating config

This commit is contained in:
Thomas Kluyver 2014-10-14 12:40:21 -07:00
parent 39bf433c9d
commit a76a546627
5 changed files with 138 additions and 0 deletions

View File

@ -120,6 +120,10 @@ class IPythonHandler(AuthenticatedHandler):
return Application.instance().log
else:
return app_log
@property
def profile_dir(self):
return self.settings.get('profile_dir', '')
#---------------------------------------------------------------
# URLs

View File

@ -174,6 +174,7 @@ class NotebookWebApplication(web.Application):
config=ipython_app.config,
jinja2_env=env,
terminals_available=False, # Set later if terminals are available
profile_dir = ipython_app.profile_dir.location,
)
# allow custom overrides for the tornado web app.
@ -191,6 +192,7 @@ class NotebookWebApplication(web.Application):
handlers.extend(load_handlers('notebook.handlers'))
handlers.extend(load_handlers('nbconvert.handlers'))
handlers.extend(load_handlers('kernelspecs.handlers'))
handlers.extend(load_handlers('services.config.handlers'))
handlers.extend(load_handlers('services.kernels.handlers'))
handlers.extend(load_handlers('services.contents.handlers'))
handlers.extend(load_handlers('services.clusters.handlers'))

View File

View File

@ -0,0 +1,65 @@
"""Tornado handlers for kernel specifications."""
# Copyright (c) IPython Development Team.
# Distributed under the terms of the Modified BSD License.
import json
import os
import io
from tornado import web
from ...base.handlers import IPythonHandler, json_errors
class ConfigHandler(IPythonHandler):
SUPPORTED_METHODS = ('GET', 'PUT', 'PATCH')
def file_name(self, section_name):
return os.path.join(self.profile_dir, 'nb_%s_config.json' % section_name)
@web.authenticated
@json_errors
def get(self, section_name):
self.set_header("Content-Type", 'application/json')
filename = self.file_name(section_name)
if os.path.isfile(filename):
with io.open(filename, encoding='utf-8') as f:
self.finish(f.read())
else:
self.finish("{}")
@web.authenticated
@json_errors
def put(self, section_name):
filename = self.file_name(section_name)
with open(filename, 'wb') as f:
f.write(self.request.body)
self.set_status(204)
@web.authenticated
@json_errors
def patch(self, section_name):
filename = self.file_name(section_name)
if os.path.isfile(filename):
with io.open(filename, encoding='utf-8') as f:
section = json.load(f)
else:
section = {}
for k, v in self.get_json_body().items():
if v is None:
section.pop(k, None)
else:
section[k] = v
with io.open(filename, 'w', encoding='utf-8') as f:
json.dump(section, f)
self.set_status(204)
# URL to handler mappings
section_name_regex = r"(?P<section_name>\w+)"
default_handlers = [
(r"/api/config/%s" % section_name_regex, ConfigHandler),
]

View File

@ -0,0 +1,67 @@
# coding: utf-8
"""Test the kernel specs webservice API."""
import json
import requests
from IPython.html.utils import url_path_join
from IPython.html.tests.launchnotebook import NotebookTestBase
class ConfigAPI(object):
"""Wrapper for notebook API calls."""
def __init__(self, base_url):
self.base_url = base_url
def _req(self, verb, section, body=None):
response = requests.request(verb,
url_path_join(self.base_url, 'api/config', section),
data=body,
)
response.raise_for_status()
return response
def get(self, section):
return self._req('GET', section)
def set(self, section, values):
return self._req('PUT', section, json.dumps(values))
def modify(self, section, values):
return self._req('PATCH', section, json.dumps(values))
class APITest(NotebookTestBase):
"""Test the kernelspec web service API"""
def setUp(self):
self.config_api = ConfigAPI(self.base_url())
def test_create_retrieve_config(self):
sample = {'foo': 'bar', 'baz': 73}
r = self.config_api.set('example', sample)
self.assertEqual(r.status_code, 204)
r = self.config_api.get('example')
self.assertEqual(r.status_code, 200)
self.assertEqual(r.json(), sample)
def test_modify(self):
sample = {'foo': 'bar', 'baz': 73}
self.config_api.set('example', sample)
r = self.config_api.modify('example', {'foo': None, # should delete foo
'baz': 75,
'wib': [1,2,3],
})
self.assertEqual(r.status_code, 204)
r = self.config_api.get('example')
self.assertEqual(r.status_code, 200)
self.assertEqual(r.json(), {'baz': 75, 'wib': [1,2,3]})
def test_get_unknown(self):
# We should get an empty config dictionary instead of a 404
r = self.config_api.get('nonexistant')
self.assertEqual(r.status_code, 200)
self.assertEqual(r.json(), {})