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 | def kernel_spec_manager(self): |
|
175 | def kernel_spec_manager(self): | |
176 | return self.settings['kernel_spec_manager'] |
|
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 | # CORS |
|
183 | # CORS | |
180 | #--------------------------------------------------------------- |
|
184 | #--------------------------------------------------------------- |
@@ -125,19 +125,21 b' def load_handlers(name):' | |||||
125 | class NotebookWebApplication(web.Application): |
|
125 | class NotebookWebApplication(web.Application): | |
126 |
|
126 | |||
127 | def __init__(self, ipython_app, kernel_manager, contents_manager, |
|
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 | base_url, default_url, settings_overrides, jinja_env_options): |
|
130 | base_url, default_url, settings_overrides, jinja_env_options): | |
130 |
|
131 | |||
131 | settings = self.init_settings( |
|
132 | settings = self.init_settings( | |
132 | ipython_app, kernel_manager, contents_manager, cluster_manager, |
|
133 | ipython_app, kernel_manager, contents_manager, cluster_manager, | |
133 |
session_manager, kernel_spec_manager, log, base_url, |
|
134 | session_manager, kernel_spec_manager, config_manager, log, base_url, | |
134 | settings_overrides, jinja_env_options) |
|
135 | default_url, settings_overrides, jinja_env_options) | |
135 | handlers = self.init_handlers(settings) |
|
136 | handlers = self.init_handlers(settings) | |
136 |
|
137 | |||
137 | super(NotebookWebApplication, self).__init__(handlers, **settings) |
|
138 | super(NotebookWebApplication, self).__init__(handlers, **settings) | |
138 |
|
139 | |||
139 | def init_settings(self, ipython_app, kernel_manager, contents_manager, |
|
140 | def init_settings(self, ipython_app, kernel_manager, contents_manager, | |
140 | cluster_manager, session_manager, kernel_spec_manager, |
|
141 | cluster_manager, session_manager, kernel_spec_manager, | |
|
142 | config_manager, | |||
141 | log, base_url, default_url, settings_overrides, |
|
143 | log, base_url, default_url, settings_overrides, | |
142 | jinja_env_options=None): |
|
144 | jinja_env_options=None): | |
143 |
|
145 | |||
@@ -172,6 +174,7 b' class NotebookWebApplication(web.Application):' | |||||
172 | cluster_manager=cluster_manager, |
|
174 | cluster_manager=cluster_manager, | |
173 | session_manager=session_manager, |
|
175 | session_manager=session_manager, | |
174 | kernel_spec_manager=kernel_spec_manager, |
|
176 | kernel_spec_manager=kernel_spec_manager, | |
|
177 | config_manager=config_manager, | |||
175 |
|
178 | |||
176 | # IPython stuff |
|
179 | # IPython stuff | |
177 | nbextensions_path = ipython_app.nbextensions_path, |
|
180 | nbextensions_path = ipython_app.nbextensions_path, | |
@@ -607,6 +610,11 b' class NotebookApp(BaseIPythonApplication):' | |||||
607 | help='The cluster manager class to use.' |
|
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 | kernel_spec_manager = Instance(KernelSpecManager) |
|
618 | kernel_spec_manager = Instance(KernelSpecManager) | |
611 |
|
619 | |||
612 | def _kernel_spec_manager_default(self): |
|
620 | def _kernel_spec_manager_default(self): | |
@@ -722,6 +730,10 b' class NotebookApp(BaseIPythonApplication):' | |||||
722 | self.cluster_manager = kls(parent=self, log=self.log) |
|
730 | self.cluster_manager = kls(parent=self, log=self.log) | |
723 | self.cluster_manager.update_profiles() |
|
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 | def init_logging(self): |
|
737 | def init_logging(self): | |
726 | # This prevents double log messages because tornado use a root logger that |
|
738 | # This prevents double log messages because tornado use a root logger that | |
727 | # self.log is a child of. The logging module dipatches log messages to a log |
|
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 | self.web_app = NotebookWebApplication( |
|
759 | self.web_app = NotebookWebApplication( | |
748 | self, self.kernel_manager, self.contents_manager, |
|
760 | self, self.kernel_manager, self.contents_manager, | |
749 | self.cluster_manager, self.session_manager, self.kernel_spec_manager, |
|
761 | self.cluster_manager, self.session_manager, self.kernel_spec_manager, | |
|
762 | self.config_manager, | |||
750 | self.log, self.base_url, self.default_url, self.tornado_settings, |
|
763 | self.log, self.base_url, self.default_url, self.tornado_settings, | |
751 | self.jinja_environment_options |
|
764 | self.jinja_environment_options | |
752 | ) |
|
765 | ) |
@@ -11,85 +11,27 b' from tornado import web' | |||||
11 | from IPython.utils.py3compat import PY3 |
|
11 | from IPython.utils.py3compat import PY3 | |
12 | from ...base.handlers import IPythonHandler, json_errors |
|
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 | class ConfigHandler(IPythonHandler): |
|
14 | class ConfigHandler(IPythonHandler): | |
35 | SUPPORTED_METHODS = ('GET', 'PUT', 'PATCH') |
|
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 | @web.authenticated |
|
17 | @web.authenticated | |
52 | @json_errors |
|
18 | @json_errors | |
53 | def get(self, section_name): |
|
19 | def get(self, section_name): | |
54 | self.set_header("Content-Type", 'application/json') |
|
20 | self.set_header("Content-Type", 'application/json') | |
55 | filename = self.file_name(section_name) |
|
21 | self.finish(json.dumps(self.config_manager.get(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("{}") |
|
|||
61 |
|
22 | |||
62 | @web.authenticated |
|
23 | @web.authenticated | |
63 | @json_errors |
|
24 | @json_errors | |
64 | def put(self, section_name): |
|
25 | def put(self, section_name): | |
65 | self.get_json_body() # Will raise 400 if content is not valid JSON |
|
26 | data = self.get_json_body() # Will raise 400 if content is not valid JSON | |
66 |
|
|
27 | self.config_manager.set(section_name, data) | |
67 | self.ensure_config_dir_exists() |
|
|||
68 | with open(filename, 'wb') as f: |
|
|||
69 | f.write(self.request.body) |
|
|||
70 | self.set_status(204) |
|
28 | self.set_status(204) | |
71 |
|
29 | |||
72 | @web.authenticated |
|
30 | @web.authenticated | |
73 | @json_errors |
|
31 | @json_errors | |
74 | def patch(self, section_name): |
|
32 | def patch(self, section_name): | |
75 | filename = self.file_name(section_name) |
|
33 | new_data = self.get_json_body() | |
76 | if os.path.isfile(filename): |
|
34 | section = self.config_manager.update(section_name, new_data) | |
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 |
|
||||
93 | self.finish(json.dumps(section)) |
|
35 | self.finish(json.dumps(section)) | |
94 |
|
36 | |||
95 |
|
37 |
General Comments 0
You need to be logged in to leave comments.
Login now