Show More
@@ -0,0 +1,87 b'' | |||
|
1 | """Manager to read and modify frontend config data in JSON files. | |
|
2 | """ | |
|
3 | # Copyright (c) IPython Development Team. | |
|
4 | # Distributed under the terms of the Modified BSD License. | |
|
5 | import errno | |
|
6 | import io | |
|
7 | import json | |
|
8 | import os | |
|
9 | ||
|
10 | from IPython.config import LoggingConfigurable | |
|
11 | from IPython.utils.py3compat import PY3 | |
|
12 | from IPython.utils.traitlets import Unicode | |
|
13 | ||
|
14 | ||
|
15 | def recursive_update(target, new): | |
|
16 | """Recursively update one dictionary using another. | |
|
17 | ||
|
18 | None values will delete their keys. | |
|
19 | """ | |
|
20 | for k, v in new.items(): | |
|
21 | if isinstance(v, dict): | |
|
22 | if k not in target: | |
|
23 | target[k] = {} | |
|
24 | recursive_update(target[k], v) | |
|
25 | if not target[k]: | |
|
26 | # Prune empty subdicts | |
|
27 | del target[k] | |
|
28 | ||
|
29 | elif v is None: | |
|
30 | target.pop(k, None) | |
|
31 | ||
|
32 | else: | |
|
33 | target[k] = v | |
|
34 | ||
|
35 | ||
|
36 | class ConfigManager(LoggingConfigurable): | |
|
37 | profile_dir = Unicode() | |
|
38 | ||
|
39 | @property | |
|
40 | def config_dir(self): | |
|
41 | return os.path.join(self.profile_dir, 'nbconfig') | |
|
42 | ||
|
43 | def ensure_config_dir_exists(self): | |
|
44 | try: | |
|
45 | os.mkdir(self.config_dir, 0o755) | |
|
46 | except OSError as e: | |
|
47 | if e.errno != errno.EEXIST: | |
|
48 | raise | |
|
49 | ||
|
50 | def file_name(self, section_name): | |
|
51 | return os.path.join(self.config_dir, section_name+'.json') | |
|
52 | ||
|
53 | def get(self, section_name): | |
|
54 | """Retrieve the config data for the specified section. | |
|
55 | ||
|
56 | Returns the data as a dictionary, or an empty dictionary if the file | |
|
57 | doesn't exist. | |
|
58 | """ | |
|
59 | filename = self.file_name(section_name) | |
|
60 | if os.path.isfile(filename): | |
|
61 | with io.open(filename, encoding='utf-8') as f: | |
|
62 | return json.load(f) | |
|
63 | else: | |
|
64 | return {} | |
|
65 | ||
|
66 | def set(self, section_name, data): | |
|
67 | """Store the given config data. | |
|
68 | """ | |
|
69 | filename = self.file_name(section_name) | |
|
70 | self.ensure_config_dir_exists() | |
|
71 | ||
|
72 | if PY3: | |
|
73 | f = io.open(filename, 'w', encoding='utf-8') | |
|
74 | else: | |
|
75 | f = open(filename, 'wb') | |
|
76 | with f: | |
|
77 | json.dump(data, f) | |
|
78 | ||
|
79 | def update(self, section_name, new_data): | |
|
80 | """Modify the config section by recursively updating it with new_data. | |
|
81 | ||
|
82 | Returns the modified config data as a dictionary. | |
|
83 | """ | |
|
84 | data = self.get(section_name) | |
|
85 | recursive_update(data, new_data) | |
|
86 | self.set(section_name, data) | |
|
87 | return data |
@@ -175,6 +175,10 b' class IPythonHandler(AuthenticatedHandler):' | |||
|
175 | 175 | def kernel_spec_manager(self): |
|
176 | 176 | return self.settings['kernel_spec_manager'] |
|
177 | 177 | |
|
178 | @property | |
|
179 | def config_manager(self): | |
|
180 | return self.settings['config_manager'] | |
|
181 | ||
|
178 | 182 | #--------------------------------------------------------------- |
|
179 | 183 | # CORS |
|
180 | 184 | #--------------------------------------------------------------- |
@@ -125,19 +125,21 b' def load_handlers(name):' | |||
|
125 | 125 | class NotebookWebApplication(web.Application): |
|
126 | 126 | |
|
127 | 127 | def __init__(self, ipython_app, kernel_manager, contents_manager, |
|
128 |
cluster_manager, session_manager, kernel_spec_manager, |
|
|
128 | cluster_manager, session_manager, kernel_spec_manager, | |
|
129 | config_manager, log, | |
|
129 | 130 | base_url, default_url, settings_overrides, jinja_env_options): |
|
130 | 131 | |
|
131 | 132 | settings = self.init_settings( |
|
132 | 133 | ipython_app, kernel_manager, contents_manager, cluster_manager, |
|
133 |
session_manager, kernel_spec_manager, log, base_url, |
|
|
134 | settings_overrides, jinja_env_options) | |
|
134 | session_manager, kernel_spec_manager, config_manager, log, base_url, | |
|
135 | default_url, settings_overrides, jinja_env_options) | |
|
135 | 136 | handlers = self.init_handlers(settings) |
|
136 | 137 | |
|
137 | 138 | super(NotebookWebApplication, self).__init__(handlers, **settings) |
|
138 | 139 | |
|
139 | 140 | def init_settings(self, ipython_app, kernel_manager, contents_manager, |
|
140 | 141 | cluster_manager, session_manager, kernel_spec_manager, |
|
142 | config_manager, | |
|
141 | 143 | log, base_url, default_url, settings_overrides, |
|
142 | 144 | jinja_env_options=None): |
|
143 | 145 | |
@@ -172,6 +174,7 b' class NotebookWebApplication(web.Application):' | |||
|
172 | 174 | cluster_manager=cluster_manager, |
|
173 | 175 | session_manager=session_manager, |
|
174 | 176 | kernel_spec_manager=kernel_spec_manager, |
|
177 | config_manager=config_manager, | |
|
175 | 178 | |
|
176 | 179 | # IPython stuff |
|
177 | 180 | nbextensions_path = ipython_app.nbextensions_path, |
@@ -607,6 +610,11 b' class NotebookApp(BaseIPythonApplication):' | |||
|
607 | 610 | help='The cluster manager class to use.' |
|
608 | 611 | ) |
|
609 | 612 | |
|
613 | config_manager_class = DottedObjectName('IPython.html.services.config.manager.ConfigManager', | |
|
614 | config = True, | |
|
615 | help='The config manager class to use' | |
|
616 | ) | |
|
617 | ||
|
610 | 618 | kernel_spec_manager = Instance(KernelSpecManager) |
|
611 | 619 | |
|
612 | 620 | def _kernel_spec_manager_default(self): |
@@ -722,6 +730,10 b' class NotebookApp(BaseIPythonApplication):' | |||
|
722 | 730 | self.cluster_manager = kls(parent=self, log=self.log) |
|
723 | 731 | self.cluster_manager.update_profiles() |
|
724 | 732 | |
|
733 | kls = import_item(self.config_manager_class) | |
|
734 | self.config_manager = kls(parent=self, log=self.log, | |
|
735 | profile_dir=self.profile_dir.location) | |
|
736 | ||
|
725 | 737 | def init_logging(self): |
|
726 | 738 | # This prevents double log messages because tornado use a root logger that |
|
727 | 739 | # self.log is a child of. The logging module dipatches log messages to a log |
@@ -747,6 +759,7 b' class NotebookApp(BaseIPythonApplication):' | |||
|
747 | 759 | self.web_app = NotebookWebApplication( |
|
748 | 760 | self, self.kernel_manager, self.contents_manager, |
|
749 | 761 | self.cluster_manager, self.session_manager, self.kernel_spec_manager, |
|
762 | self.config_manager, | |
|
750 | 763 | self.log, self.base_url, self.default_url, self.tornado_settings, |
|
751 | 764 | self.jinja_environment_options |
|
752 | 765 | ) |
@@ -11,85 +11,27 b' from tornado import web' | |||
|
11 | 11 | from IPython.utils.py3compat import PY3 |
|
12 | 12 | from ...base.handlers import IPythonHandler, json_errors |
|
13 | 13 | |
|
14 | def recursive_update(target, new): | |
|
15 | """Recursively update one dictionary using another. | |
|
16 | ||
|
17 | None values will delete their keys. | |
|
18 | """ | |
|
19 | for k, v in new.items(): | |
|
20 | if isinstance(v, dict): | |
|
21 | if k not in target: | |
|
22 | target[k] = {} | |
|
23 | recursive_update(target[k], v) | |
|
24 | if not target[k]: | |
|
25 | # Prune empty subdicts | |
|
26 | del target[k] | |
|
27 | ||
|
28 | elif v is None: | |
|
29 | target.pop(k, None) | |
|
30 | ||
|
31 | else: | |
|
32 | target[k] = v | |
|
33 | ||
|
34 | 14 | class ConfigHandler(IPythonHandler): |
|
35 | 15 | SUPPORTED_METHODS = ('GET', 'PUT', 'PATCH') |
|
36 | 16 | |
|
37 | @property | |
|
38 | def config_dir(self): | |
|
39 | return os.path.join(self.profile_dir, 'nbconfig') | |
|
40 | ||
|
41 | def ensure_config_dir_exists(self): | |
|
42 | try: | |
|
43 | os.mkdir(self.config_dir, 0o755) | |
|
44 | except OSError as e: | |
|
45 | if e.errno != errno.EEXIST: | |
|
46 | raise | |
|
47 | ||
|
48 | def file_name(self, section_name): | |
|
49 | return os.path.join(self.config_dir, section_name+'.json') | |
|
50 | ||
|
51 | 17 | @web.authenticated |
|
52 | 18 | @json_errors |
|
53 | 19 | def get(self, section_name): |
|
54 | 20 | self.set_header("Content-Type", 'application/json') |
|
55 | filename = self.file_name(section_name) | |
|
56 | if os.path.isfile(filename): | |
|
57 | with io.open(filename, encoding='utf-8') as f: | |
|
58 | self.finish(f.read()) | |
|
59 | else: | |
|
60 | self.finish("{}") | |
|
21 | self.finish(json.dumps(self.config_manager.get(section_name))) | |
|
61 | 22 | |
|
62 | 23 | @web.authenticated |
|
63 | 24 | @json_errors |
|
64 | 25 | def put(self, section_name): |
|
65 | self.get_json_body() # Will raise 400 if content is not valid JSON | |
|
66 |
|
|
|
67 | self.ensure_config_dir_exists() | |
|
68 | with open(filename, 'wb') as f: | |
|
69 | f.write(self.request.body) | |
|
26 | data = self.get_json_body() # Will raise 400 if content is not valid JSON | |
|
27 | self.config_manager.set(section_name, data) | |
|
70 | 28 | self.set_status(204) |
|
71 | 29 | |
|
72 | 30 | @web.authenticated |
|
73 | 31 | @json_errors |
|
74 | 32 | def patch(self, section_name): |
|
75 | filename = self.file_name(section_name) | |
|
76 | if os.path.isfile(filename): | |
|
77 | with io.open(filename, encoding='utf-8') as f: | |
|
78 | section = json.load(f) | |
|
79 | else: | |
|
80 | section = {} | |
|
81 | ||
|
82 | update = self.get_json_body() | |
|
83 | recursive_update(section, update) | |
|
84 | ||
|
85 | self.ensure_config_dir_exists() | |
|
86 | if PY3: | |
|
87 | f = io.open(filename, 'w', encoding='utf-8') | |
|
88 | else: | |
|
89 | f = open(filename, 'wb') | |
|
90 | with f: | |
|
91 | json.dump(section, f) | |
|
92 | ||
|
33 | new_data = self.get_json_body() | |
|
34 | section = self.config_manager.update(section_name, new_data) | |
|
93 | 35 | self.finish(json.dumps(section)) |
|
94 | 36 | |
|
95 | 37 |
General Comments 0
You need to be logged in to leave comments.
Login now