Merge pull request #7041 from takluyver/nbconfig-manager

ConfigManager class for frontend config
Min RK 12 years ago
commit 9a4de7b78d

@ -120,10 +120,6 @@ class IPythonHandler(AuthenticatedHandler):
return Application.instance().log
else:
return app_log
@property
def profile_dir(self):
return self.settings.get('profile_dir', '')
#---------------------------------------------------------------
# URLs
@ -180,6 +176,10 @@ class IPythonHandler(AuthenticatedHandler):
def kernel_spec_manager(self):
return self.settings['kernel_spec_manager']
@property
def config_manager(self):
return self.settings['config_manager']
#---------------------------------------------------------------
# CORS
#---------------------------------------------------------------

@ -127,19 +127,21 @@ def load_handlers(name):
class NotebookWebApplication(web.Application):
def __init__(self, ipython_app, kernel_manager, contents_manager,
cluster_manager, session_manager, kernel_spec_manager, log,
cluster_manager, session_manager, kernel_spec_manager,
config_manager, log,
base_url, default_url, settings_overrides, jinja_env_options):
settings = self.init_settings(
ipython_app, kernel_manager, contents_manager, cluster_manager,
session_manager, kernel_spec_manager, log, base_url, default_url,
settings_overrides, jinja_env_options)
session_manager, kernel_spec_manager, config_manager, log, base_url,
default_url, settings_overrides, jinja_env_options)
handlers = self.init_handlers(settings)
super(NotebookWebApplication, self).__init__(handlers, **settings)
def init_settings(self, ipython_app, kernel_manager, contents_manager,
cluster_manager, session_manager, kernel_spec_manager,
config_manager,
log, base_url, default_url, settings_overrides,
jinja_env_options=None):
@ -188,6 +190,7 @@ class NotebookWebApplication(web.Application):
cluster_manager=cluster_manager,
session_manager=session_manager,
kernel_spec_manager=kernel_spec_manager,
config_manager=config_manager,
# IPython stuff
nbextensions_path = ipython_app.nbextensions_path,
@ -196,7 +199,6 @@ class NotebookWebApplication(web.Application):
config=ipython_app.config,
jinja2_env=env,
terminals_available=False, # Set later if terminals are available
profile_dir = ipython_app.profile_dir.location,
)
# allow custom overrides for the tornado web app.
@ -627,6 +629,11 @@ class NotebookApp(BaseIPythonApplication):
help='The cluster manager class to use.'
)
config_manager_class = DottedObjectName('IPython.html.services.config.manager.ConfigManager',
config = True,
help='The config manager class to use'
)
kernel_spec_manager = Instance(KernelSpecManager)
def _kernel_spec_manager_default(self):
@ -742,6 +749,10 @@ class NotebookApp(BaseIPythonApplication):
self.cluster_manager = kls(parent=self, log=self.log)
self.cluster_manager.update_profiles()
kls = import_item(self.config_manager_class)
self.config_manager = kls(parent=self, log=self.log,
profile_dir=self.profile_dir.location)
def init_logging(self):
# This prevents double log messages because tornado use a root logger that
# self.log is a child of. The logging module dipatches log messages to a log
@ -767,6 +778,7 @@ class NotebookApp(BaseIPythonApplication):
self.web_app = NotebookWebApplication(
self, self.kernel_manager, self.contents_manager,
self.cluster_manager, self.session_manager, self.kernel_spec_manager,
self.config_manager,
self.log, self.base_url, self.default_url, self.tornado_settings,
self.jinja_environment_options
)

@ -11,85 +11,27 @@ from tornado import web
from IPython.utils.py3compat import PY3
from ...base.handlers import IPythonHandler, json_errors
def recursive_update(target, new):
"""Recursively update one dictionary using another.
None values will delete their keys.
"""
for k, v in new.items():
if isinstance(v, dict):
if k not in target:
target[k] = {}
recursive_update(target[k], v)
if not target[k]:
# Prune empty subdicts
del target[k]
elif v is None:
target.pop(k, None)
else:
target[k] = v
class ConfigHandler(IPythonHandler):
SUPPORTED_METHODS = ('GET', 'PUT', 'PATCH')
@property
def config_dir(self):
return os.path.join(self.profile_dir, 'nbconfig')
def ensure_config_dir_exists(self):
try:
os.mkdir(self.config_dir, 0o755)
except OSError as e:
if e.errno != errno.EEXIST:
raise
def file_name(self, section_name):
return os.path.join(self.config_dir, section_name+'.json')
@web.authenticated
@json_errors
def get(self, section_name):
self.set_header("Content-Type", 'application/json')
filename = self.file_name(section_name)
if os.path.isfile(filename):
with io.open(filename, encoding='utf-8') as f:
self.finish(f.read())
else:
self.finish("{}")
self.finish(json.dumps(self.config_manager.get(section_name)))
@web.authenticated
@json_errors
def put(self, section_name):
self.get_json_body() # Will raise 400 if content is not valid JSON
filename = self.file_name(section_name)
self.ensure_config_dir_exists()
with open(filename, 'wb') as f:
f.write(self.request.body)
data = self.get_json_body() # Will raise 400 if content is not valid JSON
self.config_manager.set(section_name, data)
self.set_status(204)
@web.authenticated
@json_errors
def patch(self, section_name):
filename = self.file_name(section_name)
if os.path.isfile(filename):
with io.open(filename, encoding='utf-8') as f:
section = json.load(f)
else:
section = {}
update = self.get_json_body()
recursive_update(section, update)
self.ensure_config_dir_exists()
if PY3:
f = io.open(filename, 'w', encoding='utf-8')
else:
f = open(filename, 'wb')
with f:
json.dump(section, f)
new_data = self.get_json_body()
section = self.config_manager.update(section_name, new_data)
self.finish(json.dumps(section))

@ -0,0 +1,90 @@
"""Manager to read and modify frontend config data in JSON files.
"""
# Copyright (c) IPython Development Team.
# Distributed under the terms of the Modified BSD License.
import errno
import io
import json
import os
from IPython.config import LoggingConfigurable
from IPython.utils.path import locate_profile
from IPython.utils.py3compat import PY3
from IPython.utils.traitlets import Unicode
def recursive_update(target, new):
"""Recursively update one dictionary using another.
None values will delete their keys.
"""
for k, v in new.items():
if isinstance(v, dict):
if k not in target:
target[k] = {}
recursive_update(target[k], v)
if not target[k]:
# Prune empty subdicts
del target[k]
elif v is None:
target.pop(k, None)
else:
target[k] = v
class ConfigManager(LoggingConfigurable):
profile_dir = Unicode()
def _profile_dir_default(self):
return locate_profile()
@property
def config_dir(self):
return os.path.join(self.profile_dir, 'nbconfig')
def ensure_config_dir_exists(self):
try:
os.mkdir(self.config_dir, 0o755)
except OSError as e:
if e.errno != errno.EEXIST:
raise
def file_name(self, section_name):
return os.path.join(self.config_dir, section_name+'.json')
def get(self, section_name):
"""Retrieve the config data for the specified section.
Returns the data as a dictionary, or an empty dictionary if the file
doesn't exist.
"""
filename = self.file_name(section_name)
if os.path.isfile(filename):
with io.open(filename, encoding='utf-8') as f:
return json.load(f)
else:
return {}
def set(self, section_name, data):
"""Store the given config data.
"""
filename = self.file_name(section_name)
self.ensure_config_dir_exists()
if PY3:
f = io.open(filename, 'w', encoding='utf-8')
else:
f = open(filename, 'wb')
with f:
json.dump(data, f)
def update(self, section_name, new_data):
"""Modify the config section by recursively updating it with new_data.
Returns the modified config data as a dictionary.
"""
data = self.get(section_name)
recursive_update(data, new_data)
self.set(section_name, data)
return data
Loading…
Cancel
Save