Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

frontend/extension-config: allow default json files in a .d directory #3116

Merged
merged 7 commits into from
Jan 5, 2018
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion notebook/auth/__main__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from notebook.auth import passwd
from getpass import getpass
from traitlets.config.manager import BaseJSONConfigManager
from notebook.manager import BaseJSONConfigManager
from jupyter_core.paths import jupyter_config_dir
import argparse
import sys
Expand Down
2 changes: 1 addition & 1 deletion notebook/bundler/bundlerextensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@

from ..extensions import BaseExtensionApp, _get_config_dir, GREEN_ENABLED, RED_DISABLED
from .._version import __version__
from notebook.manager import BaseJSONConfigManager

from jupyter_core.paths import jupyter_config_path

from traitlets.config.manager import BaseJSONConfigManager
from traitlets.utils.importstring import import_item
from traitlets import Bool

Expand Down
2 changes: 1 addition & 1 deletion notebook/bundler/tests/test_bundlerextension.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@
from ipython_genutils.tempdir import TemporaryDirectory
from ipython_genutils import py3compat

from traitlets.config.manager import BaseJSONConfigManager
from traitlets.tests.utils import check_help_all_output

import notebook.nbextensions as nbextensions
from notebook.manager import BaseJSONConfigManager
from ..bundlerextensions import (_get_config_dir, enable_bundler_python,
disable_bundler_python)

Expand Down
103 changes: 103 additions & 0 deletions notebook/manager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
"""Manager to read and modify config data in JSON files.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe call this config_manager.py?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, wasn't sure where to put it also, but wanted to start with a clean dup of the original PR. Shall I keep it in the root dir?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think so

"""
# Copyright (c) IPython Development Team.
# Distributed under the terms of the Modified BSD License.
import errno
import glob
import io
import json
import os

from six import PY3
from traitlets.config import LoggingConfigurable
from traitlets.traitlets import Unicode, Bool


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 BaseJSONConfigManager(LoggingConfigurable):
"""General JSON config manager

Deals with persisting/storing config in a json file with optionally
default values in a {section_name}.d directory.
"""

config_dir = Unicode('.')
read_directory = Bool(True)

def ensure_config_dir_exists(self):
try:
Copy link
Member

@blink1073 blink1073 Dec 8, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mind adding short docstrings for these methods?

os.makedirs(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 directory(self, section_name):
return os.path.join(self.config_dir, section_name+'.d')

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.
"""
paths = [self.file_name(section_name)]
if self.read_directory:
pattern = os.path.join(self.directory(section_name), '*.json')
# These json files should be processed first so that the
# {section_name}.json take precedence.
# The idea behind this is that installing a Python package may
# put a json file somewhere in the a .d directory, while the
# .json file is probably a user configuration.
paths = sorted(glob.glob(pattern)) + paths
data = {}
for path in paths:
if os.path.isfile(path):
with io.open(path, encoding='utf-8') as f:
recursive_update(data, json.load(f))
return data

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, indent=2)

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
2 changes: 1 addition & 1 deletion notebook/nbextensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
from ipython_genutils.tempdir import TemporaryDirectory
from ._version import __version__

from traitlets.config.manager import BaseJSONConfigManager
from notebook.manager import BaseJSONConfigManager
from traitlets.utils.importstring import import_item

DEPRECATED_ARGUMENT = object()
Expand Down
2 changes: 1 addition & 1 deletion notebook/serverextensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
)
from traitlets import Bool
from traitlets.utils.importstring import import_item
from traitlets.config.manager import BaseJSONConfigManager
from notebook.manager import BaseJSONConfigManager


# ------------------------------------------------------------------------------
Expand Down
2 changes: 1 addition & 1 deletion notebook/services/config/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import os.path

from traitlets.config.manager import BaseJSONConfigManager, recursive_update
from notebook.manager import BaseJSONConfigManager, recursive_update
from jupyter_core.paths import jupyter_config_dir, jupyter_config_path
from traitlets import Unicode, Instance, List, observe, default
from traitlets.config import LoggingConfigurable
Expand Down
39 changes: 39 additions & 0 deletions notebook/tests/test_manager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import json
import os
import shutil
import tempfile

from notebook.manager import BaseJSONConfigManager


def test_json():
tmpdir = tempfile.mkdtemp()
try:
with open(os.path.join(tmpdir, 'foo.json'), 'w') as f:
json.dump(dict(a=1), f)
# also make a foo.d/ directory with multiple json files
os.makedirs(os.path.join(tmpdir, 'foo.d'))
with open(os.path.join(tmpdir, 'foo.d', 'a.json'), 'w') as f:
json.dump(dict(a=2, b=1), f)
with open(os.path.join(tmpdir, 'foo.d', 'b.json'), 'w') as f:
json.dump(dict(a=3, b=2, c=3), f)
manager = BaseJSONConfigManager(config_dir=tmpdir, read_directory=False)
data = manager.get('foo')
assert 'a' in data
assert 'b' not in data
assert 'c' not in data
assert data['a'] == 1

manager = BaseJSONConfigManager(config_dir=tmpdir, read_directory=True)
data = manager.get('foo')
assert 'a' in data
assert 'b' in data
assert 'c' in data
# files should be read in order foo.d/a.json foo.d/b.json foo.json
assert data['a'] == 1
assert data['b'] == 2
assert data['c'] == 3
finally:
shutil.rmtree(tmpdir)


2 changes: 1 addition & 1 deletion notebook/tests/test_nbextensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
validate_nbextension, validate_nbextension_python
)

from traitlets.config.manager import BaseJSONConfigManager
from notebook.manager import BaseJSONConfigManager


def touch(file_name, mtime=None):
Expand Down
2 changes: 1 addition & 1 deletion notebook/tests/test_serverextensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from ipython_genutils.tempdir import TemporaryDirectory
from ipython_genutils import py3compat

from traitlets.config.manager import BaseJSONConfigManager
from notebook.manager import BaseJSONConfigManager
from traitlets.tests.utils import check_help_all_output
from jupyter_core import paths

Expand Down