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
|
return Application.instance().log
|
||||||
else:
|
else:
|
||||||
return app_log
|
return app_log
|
||||||
|
|
||||||
|
@property
|
||||||
|
def profile_dir(self):
|
||||||
|
return self.settings.get('profile_dir', '')
|
||||||
|
|
||||||
#---------------------------------------------------------------
|
#---------------------------------------------------------------
|
||||||
# URLs
|
# URLs
|
||||||
|
@ -174,6 +174,7 @@ class NotebookWebApplication(web.Application):
|
|||||||
config=ipython_app.config,
|
config=ipython_app.config,
|
||||||
jinja2_env=env,
|
jinja2_env=env,
|
||||||
terminals_available=False, # Set later if terminals are available
|
terminals_available=False, # Set later if terminals are available
|
||||||
|
profile_dir = ipython_app.profile_dir.location,
|
||||||
)
|
)
|
||||||
|
|
||||||
# allow custom overrides for the tornado web app.
|
# 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('notebook.handlers'))
|
||||||
handlers.extend(load_handlers('nbconvert.handlers'))
|
handlers.extend(load_handlers('nbconvert.handlers'))
|
||||||
handlers.extend(load_handlers('kernelspecs.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.kernels.handlers'))
|
||||||
handlers.extend(load_handlers('services.contents.handlers'))
|
handlers.extend(load_handlers('services.contents.handlers'))
|
||||||
handlers.extend(load_handlers('services.clusters.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