"""Tornado handlers for frontend config storage.""" # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. import json import os import io import errno 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("{}") @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) 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) self.finish(json.dumps(section)) # URL to handler mappings section_name_regex = r"(?P\w+)" default_handlers = [ (r"/api/config/%s" % section_name_regex, ConfigHandler), ]