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