##// END OF EJS Templates
Merge pull request #7041 from takluyver/nbconfig-manager...
Min RK -
r19122:a7240684 merge
parent child Browse files
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, log,
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, default_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 filename = self.file_name(section_name)
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.InlineBackendConfig:
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