mirror of
https://github.com/jupyter/notebook.git
synced 2024-12-27 04:20:22 +08:00
Add REST API for retrieving, storing and updating config
This commit is contained in:
parent
39bf433c9d
commit
a76a546627
@ -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
|
||||
|
@ -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'))
|
||||
|
0
IPython/html/services/config/__init__.py
Normal file
0
IPython/html/services/config/__init__.py
Normal file
65
IPython/html/services/config/handlers.py
Normal file
65
IPython/html/services/config/handlers.py
Normal 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),
|
||||
]
|
67
IPython/html/services/config/tests/test_config_api.py
Normal file
67
IPython/html/services/config/tests/test_config_api.py
Normal 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(), {})
|
||||
|
Loading…
Reference in New Issue
Block a user