Skip to content

Commit

Permalink
add Application.show_config[_json]
Browse files Browse the repository at this point in the history
setting this value will skip Application start and show loaded config on stdout, instead.

if show_config[_json is given, it will be json-formatted
  • Loading branch information
minrk committed May 3, 2017
1 parent 3d41bb7 commit eaf1ae8
Showing 1 changed file with 82 additions and 15 deletions.
97 changes: 82 additions & 15 deletions traitlets/config/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,16 @@

from collections import defaultdict, OrderedDict
from copy import deepcopy
import io
import json
import logging
import os
import pprint
import re
import sys

from decorator import decorator

from traitlets.config.configurable import Configurable, SingletonConfigurable
from traitlets.config.loader import (
KVArgParseConfigLoader, PyFileConfigLoader, Config, ArgumentError, ConfigFileNotFound, JSONFileConfigLoader
Expand Down Expand Up @@ -236,23 +241,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,
},
},
'show-config': {
'Application': {
'show_config': True,
},
},
'show-config-json': {
'Application': {
'show_config_json': True,
},
},
}

# subcommands for launching other applications
# if this is not empty, this will be a parent Application
Expand All @@ -274,6 +285,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

This comment has been minimized.

Copy link
@ankostis

ankostis Feb 6, 2018

Contributor

@minrk Is there a reason to replace start in order to print configs,
instead of simply putting an if then inside default start()?

The way it is currently implemented makes problematic to override Application.start() in subclasses.

This comment has been minimized.

Copy link
@minrk

minrk Feb 6, 2018

Author Member

Yes, because all applications override start, and this logic would need to be in every start implementation in every Application. No existing applications would get support for --show-config without new releases modifying every Application. Whereas start shouldn't do anything but show config if the show-config flag is set, and this way all traitlets Applications gain support for --show-config. If you want to override how config is shown, then overriding start_show_config would be the right thing to do.

self.start = self.start_show_config

def __init__(self, **kwargs):
SingletonConfigurable.__init__(self, **kwargs)
Expand Down Expand Up @@ -311,6 +341,35 @@ 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"""
if self.show_config_json:
json.dump(self.config, sys.stdout,
indent=1, sort_keys=True, default=repr)
return

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

for classname in sorted(self.config):
print(classname)
class_config = self.config[classname]
if not class_config:
continue
for traitname in sorted(class_config):
value = class_config[traitname]
sio = io.StringIO()
pprint.pprint(value, stream=sio)
vs = sio.getvalue()
sys.stdout.write(' .%s = ' % traitname)
if '\n' in vs.strip():
sys.stdout.write('\n')
vs = indent(vs, 4)
sys.stdout.write(vs)

def print_alias_help(self):
"""Print the alias part of the help."""
if not self.aliases:
Expand Down Expand Up @@ -615,10 +674,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 +703,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 +712,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 +787,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 +832,7 @@ def get_config():
return Application.instance().config
else:
return Config()


if __name__ == '__main__':
Application.launch_instance()

0 comments on commit eaf1ae8

Please sign in to comment.