Skip to content

Commit 85b60e2

Browse files
authored
Merge pull request #3485 from maartenbreddels/config_manager_no_defaults
ConfigManager should not write out default values found in the .d directory
2 parents 3bc3b5b + b7731b9 commit 85b60e2

File tree

2 files changed

+49
-7
lines changed

2 files changed

+49
-7
lines changed

notebook/config_manager.py

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import io
1010
import json
1111
import os
12+
import copy
1213

1314
from six import PY3
1415
from traitlets.config import LoggingConfigurable
@@ -36,9 +37,23 @@ def recursive_update(target, new):
3637
target[k] = v
3738

3839

40+
def remove_defaults(data, defaults):
41+
"""Recursively remove items from dict that are already in defaults"""
42+
# copy the iterator, since data will be modified
43+
for key, value in list(data.items()):
44+
if key in defaults:
45+
if isinstance(value, dict):
46+
remove_defaults(data[key], defaults[key])
47+
if not data[key]: # prune empty subdicts
48+
del data[key]
49+
else:
50+
if value == defaults[key]:
51+
del data[key]
52+
53+
3954
class BaseJSONConfigManager(LoggingConfigurable):
4055
"""General JSON config manager
41-
56+
4257
Deals with persisting/storing config in a json file with optionally
4358
default values in a {section_name}.d directory.
4459
"""
@@ -62,19 +77,22 @@ def directory(self, section_name):
6277
"""Returns the directory name for the section name: {config_dir}/{section_name}.d"""
6378
return os.path.join(self.config_dir, section_name+'.d')
6479

65-
def get(self, section_name):
80+
def get(self, section_name, include_root=True):
6681
"""Retrieve the config data for the specified section.
6782
6883
Returns the data as a dictionary, or an empty dictionary if the file
6984
doesn't exist.
85+
86+
When include_root is False, it will not read the root .json file,
87+
effectively returning the default values.
7088
"""
71-
paths = [self.file_name(section_name)]
89+
paths = [self.file_name(section_name)] if include_root else []
7290
if self.read_directory:
7391
pattern = os.path.join(self.directory(section_name), '*.json')
7492
# These json files should be processed first so that the
7593
# {section_name}.json take precedence.
7694
# The idea behind this is that installing a Python package may
77-
# put a json file somewhere in the a .d directory, while the
95+
# put a json file somewhere in the a .d directory, while the
7896
# .json file is probably a user configuration.
7997
paths = sorted(glob.glob(pattern)) + paths
8098
self.log.debug('Paths used for configuration of %s: \n\t%s', section_name, '\n\t'.join(paths))
@@ -91,6 +109,12 @@ def set(self, section_name, data):
91109
filename = self.file_name(section_name)
92110
self.ensure_config_dir_exists()
93111

112+
if self.read_directory:
113+
# we will modify data in place, so make a copy
114+
data = copy.deepcopy(data)
115+
defaults = self.get(section_name, include_root=False)
116+
remove_defaults(data, defaults)
117+
94118
# Generate the JSON up front, since it could raise an exception,
95119
# in order to avoid writing half-finished corrupted data to disk.
96120
json_content = json.dumps(data, indent=2)

notebook/tests/test_config_manager.py

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,27 @@
99
def test_json():
1010
tmpdir = tempfile.mkdtemp()
1111
try:
12+
root_data = dict(a=1, x=2, nest={'a':1, 'x':2})
1213
with open(os.path.join(tmpdir, 'foo.json'), 'w') as f:
13-
json.dump(dict(a=1), f)
14+
json.dump(root_data, f)
1415
# also make a foo.d/ directory with multiple json files
1516
os.makedirs(os.path.join(tmpdir, 'foo.d'))
1617
with open(os.path.join(tmpdir, 'foo.d', 'a.json'), 'w') as f:
17-
json.dump(dict(a=2, b=1), f)
18+
json.dump(dict(a=2, b=1, nest={'a':2, 'b':1}), f)
1819
with open(os.path.join(tmpdir, 'foo.d', 'b.json'), 'w') as f:
19-
json.dump(dict(a=3, b=2, c=3), f)
20+
json.dump(dict(a=3, b=2, c=3, nest={'a':3, 'b':2, 'c':3}, only_in_b={'x':1}), f)
2021
manager = BaseJSONConfigManager(config_dir=tmpdir, read_directory=False)
2122
data = manager.get('foo')
2223
assert 'a' in data
24+
assert 'x' in data
2325
assert 'b' not in data
2426
assert 'c' not in data
2527
assert data['a'] == 1
28+
assert 'x' in data['nest']
29+
# if we write it out, it also shouldn't pick up the subdirectoy
30+
manager.set('foo', data)
31+
data = manager.get('foo')
32+
assert data == root_data
2633

2734
manager = BaseJSONConfigManager(config_dir=tmpdir, read_directory=True)
2835
data = manager.get('foo')
@@ -33,6 +40,17 @@ def test_json():
3340
assert data['a'] == 1
3441
assert data['b'] == 2
3542
assert data['c'] == 3
43+
assert data['nest']['a'] == 1
44+
assert data['nest']['b'] == 2
45+
assert data['nest']['c'] == 3
46+
assert data['nest']['x'] == 2
47+
48+
# when writing out, we don't want foo.d/*.json data to be included in the root foo.json
49+
manager.set('foo', data)
50+
manager = BaseJSONConfigManager(config_dir=tmpdir, read_directory=False)
51+
data = manager.get('foo')
52+
assert data == root_data
53+
3654
finally:
3755
shutil.rmtree(tmpdir)
3856

0 commit comments

Comments
 (0)