Skip to content

Commit

Permalink
Merge pull request #341 from minrk/showconfig
Browse files Browse the repository at this point in the history
add Application.show_config[_json]
  • Loading branch information
minrk authored May 4, 2017
2 parents d939e2b + 476be05 commit 83c87ac
Show file tree
Hide file tree
Showing 2 changed files with 135 additions and 18 deletions.
104 changes: 89 additions & 15 deletions traitlets/config/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@
import json
import logging
import os
import pprint
import re
import sys

from traitlets.config.configurable import Configurable, SingletonConfigurable
from traitlets.config.loader import (
KVArgParseConfigLoader, PyFileConfigLoader, Config, ArgumentError, ConfigFileNotFound, JSONFileConfigLoader
Expand Down Expand Up @@ -236,23 +238,29 @@ def _log_default(self):
#: the alias map for configurables
#: Keys might strings or tuples for additional options; single-letter alias accessed like `-v`.
#: Values might be like "Class.trait" strings of two-tuples: (Class.trait, help-text).
aliases = Dict({'log-level' : 'Application.log_level'})
aliases = {'log-level' : 'Application.log_level'}

# flags for loading Configurables or store_const style flags
# flags are loaded from this dict by '--key' flags
# this must be a dict of two-tuples, the first element being the Config/dict
# and the second being the help string for the flag
flags = Dict()
@observe('flags')
@observe_compat
def _flags_changed(self, change):
"""ensure flags dict is valid"""
new = change.new
for key, value in new.items():
assert len(value) == 2, "Bad flag: %r:%s" % (key, value)
assert isinstance(value[0], (dict, Config)), "Bad flag: %r:%s" % (key, value)
assert isinstance(value[1], six.string_types), "Bad flag: %r:%s" % (key, value)

flags = {
'debug': ({
'Application': {
'log_level': logging.DEBUG,
},
}, "Set log-level to debug, for the most verbose logging."),
'show-config': ({
'Application': {
'show_config': True,
},
}, "Show the application's configuration (human-readable format)"),
'show-config-json': ({
'Application': {
'show_config_json': True,
},
}, "Show the application's configuration (json format)"),
}

# subcommands for launching other applications
# if this is not empty, this will be a parent Application
Expand All @@ -274,6 +282,25 @@ def _flags_changed(self, change):
"""
)

_loaded_config_files = List()

show_config = Bool(
help="Instead of starting the Application, dump configuration to stdout"
).tag(config=True)

show_config_json = Bool(
help="Instead of starting the Application, dump configuration to stdout (as JSON)"
).tag(config=True)

@observe('show_config_json')
def _show_config_json_changed(self, change):
self.show_config = change.new

@observe('show_config')
def _show_config_changed(self, change):
if change.new:
self._save_start = self.start
self.start = self.start_show_config

def __init__(self, **kwargs):
SingletonConfigurable.__init__(self, **kwargs)
Expand Down Expand Up @@ -311,6 +338,45 @@ def start(self):
if self.subapp is not None:
return self.subapp.start()

def start_show_config(self):
"""start function used when show_config is True"""
config = self.config.copy()
# exclude show_config flags from displayed config
for cls in self.__class__.mro():
if cls.__name__ in config:
cls_config = config[cls.__name__]
cls_config.pop('show_config', None)
cls_config.pop('show_config_json', None)

if self.show_config_json:
json.dump(config, sys.stdout,
indent=1, sort_keys=True, default=repr)
# add trailing newline
sys.stdout.write('\n')
return

if self._loaded_config_files:
print("Loaded config files:")
for f in self._loaded_config_files:
print(' ' + f)
print()

for classname in sorted(config):
class_config = config[classname]
if not class_config:
continue
print(classname)
pformat_kwargs = dict(indent=4)
if sys.version_info >= (3,4):
# use compact pretty-print on Pythons that support it
pformat_kwargs['compact'] = True
for traitname in sorted(class_config):
value = class_config[traitname]
print(' .{} = {}'.format(
traitname,
pprint.pformat(value, **pformat_kwargs),
))

def print_alias_help(self):
"""Print the alias part of the help."""
if not self.aliases:
Expand Down Expand Up @@ -615,10 +681,10 @@ def _load_config_files(cls, basefilename, path=None, log=None, raise_config_file
if log:
log.debug("Looking for %s in %s", basefilename, path or os.getcwd())
jsonloader = cls.json_config_loader_class(basefilename+'.json', path=path, log=log)
config = None
loaded = []
filenames = []
for loader in [pyloader, jsonloader]:
config = None
try:
config = loader.load_config()
except ConfigFileNotFound:
Expand All @@ -644,7 +710,7 @@ def _load_config_files(cls, basefilename, path=None, log=None, raise_config_file
" {1} has higher priority: {2}".format(
filename, loader.full_filename, json.dumps(collisions, indent=2),
))
yield config
yield (config, loader.full_filename)
loaded.append(config)
filenames.append(loader.full_filename)

Expand All @@ -653,10 +719,11 @@ def load_config_file(self, filename, path=None):
"""Load config files by filename and path."""
filename, ext = os.path.splitext(filename)
new_config = Config()
for config in self._load_config_files(filename, path=path, log=self.log,
for (config, filename) in self._load_config_files(filename, path=path, log=self.log,
raise_config_file_errors=self.raise_config_file_errors,
):
new_config.merge(config)
self._loaded_config_files.append(filename)
# add self.cli_config to preserve CLI config priority
new_config.merge(self.cli_config)
self.update_config(new_config)
Expand Down Expand Up @@ -727,6 +794,9 @@ def launch_instance(cls, argv=None, **kwargs):
# utility functions, for convenience
#-----------------------------------------------------------------------------

default_aliases = Application.aliases
default_flags = Application.flags

def boolean_flag(name, configurable, set_help='', unset_help=''):
"""Helper for building basic --trait, --no-trait flags.
Expand Down Expand Up @@ -769,3 +839,7 @@ def get_config():
return Application.instance().config
else:
return Config()


if __name__ == '__main__':
Application.launch_instance()
49 changes: 46 additions & 3 deletions traitlets/config/tests/test_application.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@

from traitlets.config.configurable import Configurable
from traitlets.config.loader import Config
from traitlets.tests.utils import check_help_output, check_help_all_output
from traitlets.tests.utils import get_output_error_code, check_help_output, check_help_all_output

from traitlets.config.application import (
Application
Expand Down Expand Up @@ -69,7 +69,9 @@ class MyApp(Application):
warn_tpyo = Unicode(u"yes the name is wrong on purpose", config=True,
help="Should print a warning if `MyApp.warn-typo=...` command is passed")

aliases = Dict({
aliases = {}
aliases.update(Application.aliases)
aliases.update({
('fooi', 'i') : 'Foo.i',
('j', 'fooj') : ('Foo.j', "`j` terse help msg"),
'name' : 'Foo.name',
Expand All @@ -80,7 +82,9 @@ class MyApp(Application):
'log-level' : 'Application.log_level',
})

flags = Dict({('enable', 'e'):
flags = {}
flags.update(Application.flags)
flags.update({('enable', 'e'):
({'Bar': {'enabled' : True}},
"Set Bar.enabled to True"),
('d', 'disable'):
Expand Down Expand Up @@ -588,6 +592,45 @@ def test_help_output():
check_help_output(__name__)
check_help_all_output(__name__)


def test_show_config_cli():
out, err, ec = get_output_error_code([sys.executable, '-m', __name__, '--show-config'])
assert ec == 0
assert 'show_config' not in out


def test_show_config_json_cli():
out, err, ec = get_output_error_code([sys.executable, '-m', __name__, '--show-config-json'])
assert ec == 0
assert 'show_config' not in out


def test_show_config(capsys):
cfg = Config()
cfg.MyApp.i = 5
# don't show empty
cfg.OtherApp

app = MyApp(config=cfg, show_config=True)
app.start()
out, err = capsys.readouterr()
assert 'MyApp' in out
assert 'i = 5' in out
assert 'OtherApp' not in out


def test_show_config_json(capsys):
cfg = Config()
cfg.MyApp.i = 5
cfg.OtherApp

app = MyApp(config=cfg, show_config_json=True)
app.start()
out, err = capsys.readouterr()
displayed = json.loads(out)
assert Config(displayed) == cfg


if __name__ == '__main__':
# for test_help_output:
MyApp.launch_instance()

0 comments on commit 83c87ac

Please sign in to comment.