Show More
@@ -0,0 +1,90 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.path import locate_profile | |||
|
12 | from IPython.utils.py3compat import PY3 | |||
|
13 | from IPython.utils.traitlets import Unicode | |||
|
14 | ||||
|
15 | ||||
|
16 | def recursive_update(target, new): | |||
|
17 | """Recursively update one dictionary using another. | |||
|
18 | ||||
|
19 | None values will delete their keys. | |||
|
20 | """ | |||
|
21 | for k, v in new.items(): | |||
|
22 | if isinstance(v, dict): | |||
|
23 | if k not in target: | |||
|
24 | target[k] = {} | |||
|
25 | recursive_update(target[k], v) | |||
|
26 | if not target[k]: | |||
|
27 | # Prune empty subdicts | |||
|
28 | del target[k] | |||
|
29 | ||||
|
30 | elif v is None: | |||
|
31 | target.pop(k, None) | |||
|
32 | ||||
|
33 | else: | |||
|
34 | target[k] = v | |||
|
35 | ||||
|
36 | ||||
|
37 | class ConfigManager(LoggingConfigurable): | |||
|
38 | profile_dir = Unicode() | |||
|
39 | def _profile_dir_default(self): | |||
|
40 | return locate_profile() | |||
|
41 | ||||
|
42 | @property | |||
|
43 | def config_dir(self): | |||
|
44 | return os.path.join(self.profile_dir, 'nbconfig') | |||
|
45 | ||||
|
46 | def ensure_config_dir_exists(self): | |||
|
47 | try: | |||
|
48 | os.mkdir(self.config_dir, 0o755) | |||
|
49 | except OSError as e: | |||
|
50 | if e.errno != errno.EEXIST: | |||
|
51 | raise | |||
|
52 | ||||
|
53 | def file_name(self, section_name): | |||
|
54 | return os.path.join(self.config_dir, section_name+'.json') | |||
|
55 | ||||
|
56 | def get(self, section_name): | |||
|
57 | """Retrieve the config data for the specified section. | |||
|
58 | ||||
|
59 | Returns the data as a dictionary, or an empty dictionary if the file | |||
|
60 | doesn't exist. | |||
|
61 | """ | |||
|
62 | filename = self.file_name(section_name) | |||
|
63 | if os.path.isfile(filename): | |||
|
64 | with io.open(filename, encoding='utf-8') as f: | |||
|
65 | return json.load(f) | |||
|
66 | else: | |||
|
67 | return {} | |||
|
68 | ||||
|
69 | def set(self, section_name, data): | |||
|
70 | """Store the given config data. | |||
|
71 | """ | |||
|
72 | filename = self.file_name(section_name) | |||
|
73 | self.ensure_config_dir_exists() | |||
|
74 | ||||
|
75 | if PY3: | |||
|
76 | f = io.open(filename, 'w', encoding='utf-8') | |||
|
77 | else: | |||
|
78 | f = open(filename, 'wb') | |||
|
79 | with f: | |||
|
80 | json.dump(data, f) | |||
|
81 | ||||
|
82 | def update(self, section_name, new_data): | |||
|
83 | """Modify the config section by recursively updating it with new_data. | |||
|
84 | ||||
|
85 | Returns the modified config data as a dictionary. | |||
|
86 | """ | |||
|
87 | data = self.get(section_name) | |||
|
88 | recursive_update(data, new_data) | |||
|
89 | self.set(section_name, data) | |||
|
90 | return data |
@@ -199,11 +199,12 b' class BaseIPythonApplication(Application):' | |||||
199 | return crashhandler.crash_handler_lite(etype, evalue, tb) |
|
199 | return crashhandler.crash_handler_lite(etype, evalue, tb) | |
200 |
|
200 | |||
201 | def _ipython_dir_changed(self, name, old, new): |
|
201 | def _ipython_dir_changed(self, name, old, new): | |
202 | str_old = py3compat.cast_bytes_py2(os.path.abspath(old), |
|
202 | if old is not None: | |
203 | sys.getfilesystemencoding() |
|
203 | str_old = py3compat.cast_bytes_py2(os.path.abspath(old), | |
204 | ) |
|
204 | sys.getfilesystemencoding() | |
205 | if str_old in sys.path: |
|
205 | ) | |
206 | sys.path.remove(str_old) |
|
206 | if str_old in sys.path: | |
|
207 | sys.path.remove(str_old) | |||
207 | str_path = py3compat.cast_bytes_py2(os.path.abspath(new), |
|
208 | str_path = py3compat.cast_bytes_py2(os.path.abspath(new), | |
208 | sys.getfilesystemencoding() |
|
209 | sys.getfilesystemencoding() | |
209 | ) |
|
210 | ) |
@@ -120,10 +120,6 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', '') |
|
|||
127 |
|
123 | |||
128 | #--------------------------------------------------------------- |
|
124 | #--------------------------------------------------------------- | |
129 | # URLs |
|
125 | # URLs | |
@@ -180,6 +176,10 b' class IPythonHandler(AuthenticatedHandler):' | |||||
180 | def kernel_spec_manager(self): |
|
176 | def kernel_spec_manager(self): | |
181 | return self.settings['kernel_spec_manager'] |
|
177 | return self.settings['kernel_spec_manager'] | |
182 |
|
178 | |||
|
179 | @property | |||
|
180 | def config_manager(self): | |||
|
181 | return self.settings['config_manager'] | |||
|
182 | ||||
183 | #--------------------------------------------------------------- |
|
183 | #--------------------------------------------------------------- | |
184 | # CORS |
|
184 | # CORS | |
185 | #--------------------------------------------------------------- |
|
185 | #--------------------------------------------------------------- |
@@ -127,19 +127,21 b' def load_handlers(name):' | |||||
127 | class NotebookWebApplication(web.Application): |
|
127 | class NotebookWebApplication(web.Application): | |
128 |
|
128 | |||
129 | def __init__(self, ipython_app, kernel_manager, contents_manager, |
|
129 | def __init__(self, ipython_app, kernel_manager, contents_manager, | |
130 |
cluster_manager, session_manager, kernel_spec_manager, |
|
130 | cluster_manager, session_manager, kernel_spec_manager, | |
|
131 | config_manager, log, | |||
131 | base_url, default_url, settings_overrides, jinja_env_options): |
|
132 | base_url, default_url, settings_overrides, jinja_env_options): | |
132 |
|
133 | |||
133 | settings = self.init_settings( |
|
134 | settings = self.init_settings( | |
134 | ipython_app, kernel_manager, contents_manager, cluster_manager, |
|
135 | ipython_app, kernel_manager, contents_manager, cluster_manager, | |
135 |
session_manager, kernel_spec_manager, log, base_url, |
|
136 | session_manager, kernel_spec_manager, config_manager, log, base_url, | |
136 | settings_overrides, jinja_env_options) |
|
137 | default_url, settings_overrides, jinja_env_options) | |
137 | handlers = self.init_handlers(settings) |
|
138 | handlers = self.init_handlers(settings) | |
138 |
|
139 | |||
139 | super(NotebookWebApplication, self).__init__(handlers, **settings) |
|
140 | super(NotebookWebApplication, self).__init__(handlers, **settings) | |
140 |
|
141 | |||
141 | def init_settings(self, ipython_app, kernel_manager, contents_manager, |
|
142 | def init_settings(self, ipython_app, kernel_manager, contents_manager, | |
142 | cluster_manager, session_manager, kernel_spec_manager, |
|
143 | cluster_manager, session_manager, kernel_spec_manager, | |
|
144 | config_manager, | |||
143 | log, base_url, default_url, settings_overrides, |
|
145 | log, base_url, default_url, settings_overrides, | |
144 | jinja_env_options=None): |
|
146 | jinja_env_options=None): | |
145 |
|
147 | |||
@@ -188,6 +190,7 b' class NotebookWebApplication(web.Application):' | |||||
188 | cluster_manager=cluster_manager, |
|
190 | cluster_manager=cluster_manager, | |
189 | session_manager=session_manager, |
|
191 | session_manager=session_manager, | |
190 | kernel_spec_manager=kernel_spec_manager, |
|
192 | kernel_spec_manager=kernel_spec_manager, | |
|
193 | config_manager=config_manager, | |||
191 |
|
194 | |||
192 | # IPython stuff |
|
195 | # IPython stuff | |
193 | nbextensions_path = ipython_app.nbextensions_path, |
|
196 | nbextensions_path = ipython_app.nbextensions_path, | |
@@ -196,7 +199,6 b' class NotebookWebApplication(web.Application):' | |||||
196 | config=ipython_app.config, |
|
199 | config=ipython_app.config, | |
197 | jinja2_env=env, |
|
200 | jinja2_env=env, | |
198 | terminals_available=False, # Set later if terminals are available |
|
201 | terminals_available=False, # Set later if terminals are available | |
199 | profile_dir = ipython_app.profile_dir.location, |
|
|||
200 | ) |
|
202 | ) | |
201 |
|
203 | |||
202 | # allow custom overrides for the tornado web app. |
|
204 | # allow custom overrides for the tornado web app. | |
@@ -627,6 +629,11 b' class NotebookApp(BaseIPythonApplication):' | |||||
627 | help='The cluster manager class to use.' |
|
629 | help='The cluster manager class to use.' | |
628 | ) |
|
630 | ) | |
629 |
|
631 | |||
|
632 | config_manager_class = DottedObjectName('IPython.html.services.config.manager.ConfigManager', | |||
|
633 | config = True, | |||
|
634 | help='The config manager class to use' | |||
|
635 | ) | |||
|
636 | ||||
630 | kernel_spec_manager = Instance(KernelSpecManager) |
|
637 | kernel_spec_manager = Instance(KernelSpecManager) | |
631 |
|
638 | |||
632 | def _kernel_spec_manager_default(self): |
|
639 | def _kernel_spec_manager_default(self): | |
@@ -742,6 +749,10 b' class NotebookApp(BaseIPythonApplication):' | |||||
742 | self.cluster_manager = kls(parent=self, log=self.log) |
|
749 | self.cluster_manager = kls(parent=self, log=self.log) | |
743 | self.cluster_manager.update_profiles() |
|
750 | self.cluster_manager.update_profiles() | |
744 |
|
751 | |||
|
752 | kls = import_item(self.config_manager_class) | |||
|
753 | self.config_manager = kls(parent=self, log=self.log, | |||
|
754 | profile_dir=self.profile_dir.location) | |||
|
755 | ||||
745 | def init_logging(self): |
|
756 | def init_logging(self): | |
746 | # This prevents double log messages because tornado use a root logger that |
|
757 | # This prevents double log messages because tornado use a root logger that | |
747 | # self.log is a child of. The logging module dipatches log messages to a log |
|
758 | # self.log is a child of. The logging module dipatches log messages to a log | |
@@ -767,6 +778,7 b' class NotebookApp(BaseIPythonApplication):' | |||||
767 | self.web_app = NotebookWebApplication( |
|
778 | self.web_app = NotebookWebApplication( | |
768 | self, self.kernel_manager, self.contents_manager, |
|
779 | self, self.kernel_manager, self.contents_manager, | |
769 | self.cluster_manager, self.session_manager, self.kernel_spec_manager, |
|
780 | self.cluster_manager, self.session_manager, self.kernel_spec_manager, | |
|
781 | self.config_manager, | |||
770 | self.log, self.base_url, self.default_url, self.tornado_settings, |
|
782 | self.log, self.base_url, self.default_url, self.tornado_settings, | |
771 | self.jinja_environment_options |
|
783 | self.jinja_environment_options | |
772 | ) |
|
784 | ) |
@@ -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 |
@@ -13,6 +13,7 b' This module does not import anything from matplotlib.' | |||||
13 | # Imports |
|
13 | # Imports | |
14 | #----------------------------------------------------------------------------- |
|
14 | #----------------------------------------------------------------------------- | |
15 |
|
15 | |||
|
16 | from IPython.config import Config | |||
16 | from IPython.config.configurable import SingletonConfigurable |
|
17 | from IPython.config.configurable import SingletonConfigurable | |
17 | from IPython.utils.traitlets import ( |
|
18 | from IPython.utils.traitlets import ( | |
18 | Dict, Instance, CaselessStrEnum, Set, Bool, Int, TraitError, Unicode |
|
19 | Dict, Instance, CaselessStrEnum, Set, Bool, Int, TraitError, Unicode | |
@@ -42,7 +43,7 b' class InlineBackend(InlineBackendConfig):' | |||||
42 |
|
43 | |||
43 | def _config_changed(self, name, old, new): |
|
44 | def _config_changed(self, name, old, new): | |
44 | # warn on change of renamed config section |
|
45 | # warn on change of renamed config section | |
45 |
if new.InlineBackendConfig != old |
|
46 | if new.InlineBackendConfig != getattr(old, 'InlineBackendConfig', Config()): | |
46 | warn("InlineBackendConfig has been renamed to InlineBackend") |
|
47 | warn("InlineBackendConfig has been renamed to InlineBackend") | |
47 | super(InlineBackend, self)._config_changed(name, old, new) |
|
48 | super(InlineBackend, self)._config_changed(name, old, new) | |
48 |
|
49 |
@@ -415,7 +415,11 b' class TraitType(object):' | |||||
415 |
|
415 | |||
416 | def __set__(self, obj, value): |
|
416 | def __set__(self, obj, value): | |
417 | new_value = self._validate(obj, value) |
|
417 | new_value = self._validate(obj, value) | |
418 | old_value = self.__get__(obj) |
|
418 | try: | |
|
419 | old_value = obj._trait_values[self.name] | |||
|
420 | except KeyError: | |||
|
421 | old_value = None | |||
|
422 | ||||
419 | obj._trait_values[self.name] = new_value |
|
423 | obj._trait_values[self.name] = new_value | |
420 | try: |
|
424 | try: | |
421 | silent = bool(old_value == new_value) |
|
425 | silent = bool(old_value == new_value) |
General Comments 0
You need to be logged in to leave comments.
Login now