From 9ed97bdb018d8d0230a819014694d9f1e7209421 2014-11-06 19:18:05 From: Thomas Kluyver Date: 2014-11-06 19:18:05 Subject: [PATCH] Apply JSON config updates recursively --- diff --git a/IPython/html/services/config/handlers.py b/IPython/html/services/config/handlers.py index 4f27dd3..a924746 100644 --- a/IPython/html/services/config/handlers.py +++ b/IPython/html/services/config/handlers.py @@ -9,6 +9,25 @@ from tornado import web 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') @@ -46,11 +65,8 @@ class ConfigHandler(IPythonHandler): else: section = {} - for k, v in self.get_json_body().items(): - if v is None: - section.pop(k, None) - else: - section[k] = v + update = self.get_json_body() + recursive_update(section, update) with io.open(filename, 'w', encoding='utf-8') as f: json.dump(section, f) diff --git a/IPython/html/services/config/tests/test_config_api.py b/IPython/html/services/config/tests/test_config_api.py index fac4e15..aece326 100644 --- a/IPython/html/services/config/tests/test_config_api.py +++ b/IPython/html/services/config/tests/test_config_api.py @@ -1,5 +1,5 @@ # coding: utf-8 -"""Test the kernel specs webservice API.""" +"""Test the config webservice API.""" import json @@ -32,7 +32,7 @@ class ConfigAPI(object): return self._req('PATCH', section, json.dumps(values)) class APITest(NotebookTestBase): - """Test the kernelspec web service API""" + """Test the config web service API""" def setUp(self): self.config_api = ConfigAPI(self.base_url()) @@ -46,18 +46,22 @@ class APITest(NotebookTestBase): self.assertEqual(r.json(), sample) def test_modify(self): - sample = {'foo': 'bar', 'baz': 73} + sample = {'foo': 'bar', 'baz': 73, + 'sub': {'a': 6, 'b': 7}, 'sub2': {'c': 8}} self.config_api.set('example', sample) r = self.config_api.modify('example', {'foo': None, # should delete foo 'baz': 75, 'wib': [1,2,3], + 'sub': {'a': 8, 'b': None, 'd': 9}, + 'sub2': {'c': None} # should delete sub2 }) self.assertEqual(r.status_code, 204) r = self.config_api.get('example') self.assertEqual(r.status_code, 200) - self.assertEqual(r.json(), {'baz': 75, 'wib': [1,2,3]}) + self.assertEqual(r.json(), {'baz': 75, 'wib': [1,2,3], + 'sub': {'a': 8, 'd': 9}}) def test_get_unknown(self): # We should get an empty config dictionary instead of a 404