Skip to content

Commit 9c7c213

Browse files
authored
Merge pull request #3116 from maartenbreddels/directory_json
allow default json files in a .d directory
2 parents 129fdeb + c73b711 commit 9c7c213

10 files changed

+156
-8
lines changed

notebook/auth/__main__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from notebook.auth import passwd
22
from getpass import getpass
3-
from traitlets.config.manager import BaseJSONConfigManager
3+
from notebook.config_manager import BaseJSONConfigManager
44
from jupyter_core.paths import jupyter_config_dir
55
import argparse
66
import sys

notebook/bundler/bundlerextensions.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@
55

66
from ..extensions import BaseExtensionApp, _get_config_dir, GREEN_ENABLED, RED_DISABLED
77
from .._version import __version__
8+
from notebook.config_manager import BaseJSONConfigManager
89

910
from jupyter_core.paths import jupyter_config_path
1011

11-
from traitlets.config.manager import BaseJSONConfigManager
1212
from traitlets.utils.importstring import import_item
1313
from traitlets import Bool
1414

notebook/bundler/tests/test_bundlerextension.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,10 @@
1515
from ipython_genutils.tempdir import TemporaryDirectory
1616
from ipython_genutils import py3compat
1717

18-
from traitlets.config.manager import BaseJSONConfigManager
1918
from traitlets.tests.utils import check_help_all_output
2019

2120
import notebook.nbextensions as nbextensions
21+
from notebook.config_manager import BaseJSONConfigManager
2222
from ..bundlerextensions import (_get_config_dir, enable_bundler_python,
2323
disable_bundler_python)
2424

notebook/config_manager.py

+109
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
# coding: utf-8
2+
"""Manager to read and modify config data in JSON files."""
3+
4+
# Copyright (c) Jupyter Development Team.
5+
# Distributed under the terms of the Modified BSD License.
6+
7+
import errno
8+
import glob
9+
import io
10+
import json
11+
import os
12+
13+
from six import PY3
14+
from traitlets.config import LoggingConfigurable
15+
from traitlets.traitlets import Unicode, Bool
16+
17+
18+
def recursive_update(target, new):
19+
"""Recursively update one dictionary using another.
20+
21+
None values will delete their keys.
22+
"""
23+
for k, v in new.items():
24+
if isinstance(v, dict):
25+
if k not in target:
26+
target[k] = {}
27+
recursive_update(target[k], v)
28+
if not target[k]:
29+
# Prune empty subdicts
30+
del target[k]
31+
32+
elif v is None:
33+
target.pop(k, None)
34+
35+
else:
36+
target[k] = v
37+
38+
39+
class BaseJSONConfigManager(LoggingConfigurable):
40+
"""General JSON config manager
41+
42+
Deals with persisting/storing config in a json file with optionally
43+
default values in a {section_name}.d directory.
44+
"""
45+
46+
config_dir = Unicode('.')
47+
read_directory = Bool(True)
48+
49+
def ensure_config_dir_exists(self):
50+
"""Will try to create the config_dir directory."""
51+
try:
52+
os.makedirs(self.config_dir, 0o755)
53+
except OSError as e:
54+
if e.errno != errno.EEXIST:
55+
raise
56+
57+
def file_name(self, section_name):
58+
"""Returns the json filename for the section_name: {config_dir}/{section_name}.json"""
59+
return os.path.join(self.config_dir, section_name+'.json')
60+
61+
def directory(self, section_name):
62+
"""Returns the directory name for the section name: {config_dir}/{section_name}.d"""
63+
return os.path.join(self.config_dir, section_name+'.d')
64+
65+
def get(self, section_name):
66+
"""Retrieve the config data for the specified section.
67+
68+
Returns the data as a dictionary, or an empty dictionary if the file
69+
doesn't exist.
70+
"""
71+
paths = [self.file_name(section_name)]
72+
if self.read_directory:
73+
pattern = os.path.join(self.directory(section_name), '*.json')
74+
# These json files should be processed first so that the
75+
# {section_name}.json take precedence.
76+
# The idea behind this is that installing a Python package may
77+
# put a json file somewhere in the a .d directory, while the
78+
# .json file is probably a user configuration.
79+
paths = sorted(glob.glob(pattern)) + paths
80+
self.log.debug('Paths used for configuration of %s: \n\t%s', section_name, '\n\t'.join(paths))
81+
data = {}
82+
for path in paths:
83+
if os.path.isfile(path):
84+
with io.open(path, encoding='utf-8') as f:
85+
recursive_update(data, json.load(f))
86+
return data
87+
88+
def set(self, section_name, data):
89+
"""Store the given config data.
90+
"""
91+
filename = self.file_name(section_name)
92+
self.ensure_config_dir_exists()
93+
94+
if PY3:
95+
f = io.open(filename, 'w', encoding='utf-8')
96+
else:
97+
f = open(filename, 'wb')
98+
with f:
99+
json.dump(data, f, indent=2)
100+
101+
def update(self, section_name, new_data):
102+
"""Modify the config section by recursively updating it with new_data.
103+
104+
Returns the modified config data as a dictionary.
105+
"""
106+
data = self.get(section_name)
107+
recursive_update(data, new_data)
108+
self.set(section_name, data)
109+
return data

notebook/nbextensions.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@
2828
from ipython_genutils.py3compat import string_types, cast_unicode_py2
2929
from ipython_genutils.tempdir import TemporaryDirectory
3030
from ._version import __version__
31+
from .config_manager import BaseJSONConfigManager
3132

32-
from traitlets.config.manager import BaseJSONConfigManager
3333
from traitlets.utils.importstring import import_item
3434

3535
DEPRECATED_ARGUMENT = object()

notebook/serverextensions.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,12 @@
1111

1212
from jupyter_core.paths import jupyter_config_path
1313
from ._version import __version__
14+
from .config_manager import BaseJSONConfigManager
1415
from .extensions import (
1516
BaseExtensionApp, _get_config_dir, GREEN_ENABLED, RED_DISABLED, GREEN_OK, RED_X
1617
)
1718
from traitlets import Bool
1819
from traitlets.utils.importstring import import_item
19-
from traitlets.config.manager import BaseJSONConfigManager
2020

2121

2222
# ------------------------------------------------------------------------------

notebook/services/config/manager.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
import os.path
77

8-
from traitlets.config.manager import BaseJSONConfigManager, recursive_update
8+
from notebook.config_manager import BaseJSONConfigManager, recursive_update
99
from jupyter_core.paths import jupyter_config_dir, jupyter_config_path
1010
from traitlets import Unicode, Instance, List, observe, default
1111
from traitlets.config import LoggingConfigurable

notebook/tests/test_config_manager.py

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import json
2+
import os
3+
import shutil
4+
import tempfile
5+
6+
from notebook.config_manager import BaseJSONConfigManager
7+
8+
9+
def test_json():
10+
tmpdir = tempfile.mkdtemp()
11+
try:
12+
with open(os.path.join(tmpdir, 'foo.json'), 'w') as f:
13+
json.dump(dict(a=1), f)
14+
# also make a foo.d/ directory with multiple json files
15+
os.makedirs(os.path.join(tmpdir, 'foo.d'))
16+
with open(os.path.join(tmpdir, 'foo.d', 'a.json'), 'w') as f:
17+
json.dump(dict(a=2, b=1), f)
18+
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+
manager = BaseJSONConfigManager(config_dir=tmpdir, read_directory=False)
21+
data = manager.get('foo')
22+
assert 'a' in data
23+
assert 'b' not in data
24+
assert 'c' not in data
25+
assert data['a'] == 1
26+
27+
manager = BaseJSONConfigManager(config_dir=tmpdir, read_directory=True)
28+
data = manager.get('foo')
29+
assert 'a' in data
30+
assert 'b' in data
31+
assert 'c' in data
32+
# files should be read in order foo.d/a.json foo.d/b.json foo.json
33+
assert data['a'] == 1
34+
assert data['b'] == 2
35+
assert data['c'] == 3
36+
finally:
37+
shutil.rmtree(tmpdir)
38+
39+

notebook/tests/test_nbextensions.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
validate_nbextension, validate_nbextension_python
3131
)
3232

33-
from traitlets.config.manager import BaseJSONConfigManager
33+
from notebook.config_manager import BaseJSONConfigManager
3434

3535

3636
def touch(file_name, mtime=None):

notebook/tests/test_serverextensions.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from ipython_genutils.tempdir import TemporaryDirectory
1111
from ipython_genutils import py3compat
1212

13-
from traitlets.config.manager import BaseJSONConfigManager
13+
from notebook.config_manager import BaseJSONConfigManager
1414
from traitlets.tests.utils import check_help_all_output
1515
from jupyter_core import paths
1616

0 commit comments

Comments
 (0)