##// END OF EJS Templates
Add REST API for retrieving, storing and updating config
Thomas Kluyver -
Show More
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
@@ -0,0 +1,65 b''
1 """Tornado handlers for kernel specifications."""
2
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
5 import json
6 import os
7 import io
8 from tornado import web
9
10 from ...base.handlers import IPythonHandler, json_errors
11
12
13 class ConfigHandler(IPythonHandler):
14 SUPPORTED_METHODS = ('GET', 'PUT', 'PATCH')
15
16 def file_name(self, section_name):
17 return os.path.join(self.profile_dir, 'nb_%s_config.json' % section_name)
18
19 @web.authenticated
20 @json_errors
21 def get(self, section_name):
22 self.set_header("Content-Type", 'application/json')
23 filename = self.file_name(section_name)
24 if os.path.isfile(filename):
25 with io.open(filename, encoding='utf-8') as f:
26 self.finish(f.read())
27 else:
28 self.finish("{}")
29
30 @web.authenticated
31 @json_errors
32 def put(self, section_name):
33 filename = self.file_name(section_name)
34 with open(filename, 'wb') as f:
35 f.write(self.request.body)
36 self.set_status(204)
37
38 @web.authenticated
39 @json_errors
40 def patch(self, section_name):
41 filename = self.file_name(section_name)
42 if os.path.isfile(filename):
43 with io.open(filename, encoding='utf-8') as f:
44 section = json.load(f)
45 else:
46 section = {}
47
48 for k, v in self.get_json_body().items():
49 if v is None:
50 section.pop(k, None)
51 else:
52 section[k] = v
53
54 with io.open(filename, 'w', encoding='utf-8') as f:
55 json.dump(section, f)
56 self.set_status(204)
57
58
59 # URL to handler mappings
60
61 section_name_regex = r"(?P<section_name>\w+)"
62
63 default_handlers = [
64 (r"/api/config/%s" % section_name_regex, ConfigHandler),
65 ]
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
@@ -0,0 +1,67 b''
1 # coding: utf-8
2 """Test the kernel specs webservice API."""
3
4 import json
5
6 import requests
7
8 from IPython.html.utils import url_path_join
9 from IPython.html.tests.launchnotebook import NotebookTestBase
10
11
12 class ConfigAPI(object):
13 """Wrapper for notebook API calls."""
14 def __init__(self, base_url):
15 self.base_url = base_url
16
17 def _req(self, verb, section, body=None):
18 response = requests.request(verb,
19 url_path_join(self.base_url, 'api/config', section),
20 data=body,
21 )
22 response.raise_for_status()
23 return response
24
25 def get(self, section):
26 return self._req('GET', section)
27
28 def set(self, section, values):
29 return self._req('PUT', section, json.dumps(values))
30
31 def modify(self, section, values):
32 return self._req('PATCH', section, json.dumps(values))
33
34 class APITest(NotebookTestBase):
35 """Test the kernelspec web service API"""
36 def setUp(self):
37 self.config_api = ConfigAPI(self.base_url())
38
39 def test_create_retrieve_config(self):
40 sample = {'foo': 'bar', 'baz': 73}
41 r = self.config_api.set('example', sample)
42 self.assertEqual(r.status_code, 204)
43
44 r = self.config_api.get('example')
45 self.assertEqual(r.status_code, 200)
46 self.assertEqual(r.json(), sample)
47
48 def test_modify(self):
49 sample = {'foo': 'bar', 'baz': 73}
50 self.config_api.set('example', sample)
51
52 r = self.config_api.modify('example', {'foo': None, # should delete foo
53 'baz': 75,
54 'wib': [1,2,3],
55 })
56 self.assertEqual(r.status_code, 204)
57
58 r = self.config_api.get('example')
59 self.assertEqual(r.status_code, 200)
60 self.assertEqual(r.json(), {'baz': 75, 'wib': [1,2,3]})
61
62 def test_get_unknown(self):
63 # We should get an empty config dictionary instead of a 404
64 r = self.config_api.get('nonexistant')
65 self.assertEqual(r.status_code, 200)
66 self.assertEqual(r.json(), {})
67
@@ -120,6 +120,10 b' class IPythonHandler(AuthenticatedHandler):'
120 return Application.instance().log
120 return Application.instance().log
121 else:
121 else:
122 return app_log
122 return app_log
123
124 @property
125 def profile_dir(self):
126 return self.settings.get('profile_dir', '')
123
127
124 #---------------------------------------------------------------
128 #---------------------------------------------------------------
125 # URLs
129 # URLs
@@ -174,6 +174,7 b' class NotebookWebApplication(web.Application):'
174 config=ipython_app.config,
174 config=ipython_app.config,
175 jinja2_env=env,
175 jinja2_env=env,
176 terminals_available=False, # Set later if terminals are available
176 terminals_available=False, # Set later if terminals are available
177 profile_dir = ipython_app.profile_dir.location,
177 )
178 )
178
179
179 # allow custom overrides for the tornado web app.
180 # allow custom overrides for the tornado web app.
@@ -191,6 +192,7 b' class NotebookWebApplication(web.Application):'
191 handlers.extend(load_handlers('notebook.handlers'))
192 handlers.extend(load_handlers('notebook.handlers'))
192 handlers.extend(load_handlers('nbconvert.handlers'))
193 handlers.extend(load_handlers('nbconvert.handlers'))
193 handlers.extend(load_handlers('kernelspecs.handlers'))
194 handlers.extend(load_handlers('kernelspecs.handlers'))
195 handlers.extend(load_handlers('services.config.handlers'))
194 handlers.extend(load_handlers('services.kernels.handlers'))
196 handlers.extend(load_handlers('services.kernels.handlers'))
195 handlers.extend(load_handlers('services.contents.handlers'))
197 handlers.extend(load_handlers('services.contents.handlers'))
196 handlers.extend(load_handlers('services.clusters.handlers'))
198 handlers.extend(load_handlers('services.clusters.handlers'))
General Comments 0
You need to be logged in to leave comments. Login now