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

Facilitate adding new config-file loaders #470

Closed
wants to merge 7 commits into from
30 changes: 25 additions & 5 deletions traitlets/config/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,9 +132,16 @@ class Application(SingletonConfigurable):
keyvalue_description = Unicode(keyvalue_description)
subcommand_description = Unicode(subcommand_description)

#: .. deprecated:: 5.0.0
#: Replaced by `supported_cfg_loaders` ordered-map.
python_config_loader_class = PyFileConfigLoader
json_config_loader_class = JSONFileConfigLoader

supported_cfg_loaders = OrderedDict([
('.py', PyFileConfigLoader),
('.json', JSONFileConfigLoader)])


# The usage and example string that goes at the end of the help string.
examples = Unicode()

Expand Down Expand Up @@ -593,7 +600,7 @@ def initialize_subcommand(self, subc, argv=None):
# or ask factory to create it...
self.subapp = subapp(self)
else:
raise AssertionError("Invalid mappings for subcommand '%s'!" % subc)
raise ValueError("Invalid mappings for subcommand '%s'!" % subc)

# ... and finally initialize subapp.
self.subapp.initialize(argv)
Expand Down Expand Up @@ -711,18 +718,31 @@ def _load_config_files(cls, basefilename, path=None, log=None, raise_config_file
yield each config object in turn.
"""

def new_loader(name, path):
for ext, loader in cls.supported_cfg_loaders.items():
if name.endswith(ext):
return loader(name, path, log=log)
raise ValueError("Unknown file-extension in config-file %r!" % name)

if not isinstance(path, list):
path = [path]
for path in path[::-1]:
# path list is in descending priority order, so load files backwards:
pyloader = cls.python_config_loader_class(basefilename+'.py', path=path, log=log)
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)
loaders = [new_loader(basefilename + ext, path=path)
for ext in cls.supported_cfg_loaders]
# load conf.d/config files in lorder
conf_d = os.path.join(path, basefilename + '.d')
if os.path.isdir(conf_d):
supported_cfg_extensions = tuple(cls.supported_cfg_loaders)
for filename in sorted(os.listdir(conf_d)):
if filename.endswith(supported_cfg_extensions):
loaders.append(new_loader(filename, path=conf_d))
config = None
loaded = []
filenames = []
for loader in [pyloader, jsonloader]:
config = None
for loader in loaders:
try:
config = loader.load_config()
except ConfigFileNotFound:
Expand Down
3 changes: 2 additions & 1 deletion traitlets/config/configurable.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,8 +138,9 @@ def _find_my_config(self, cfg):
if self.parent:
cfgs.append(self.parent._find_my_config(cfg))
my_config = Config()
section_names = self.section_names()
for c in cfgs:
for sname in self.section_names():
for sname in section_names:
# Don't do a blind getattr as that would cause the config to
# dynamically create the section with name Class.__name__.
if c._has_section(sname):
Expand Down
20 changes: 17 additions & 3 deletions traitlets/config/tests/test_application.py
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,20 @@ class TestApp(Application):
assert app.config.TestApp.value == 'cli'
assert app.value == 'cli'

def test_modify_config_loaders(self):
class TestApp(Application):
supported_cfg_loaders = {'.myext': Application.json_config_loader_class}
config_file_loaded = Bool().tag(config=True)

name = 'config.myext'
app = TestApp()
with TemporaryDirectory() as td:
config_file = pjoin(td, name)
with open(config_file, 'w') as f:
f.write('{"TestApp": {"config_file_loaded": true}}')
app.load_config_file(name, path=[td])
assert app.config_file_loaded

def test_flags(self):
app = MyApp()
app.parse_command_line(["--disable"])
Expand Down Expand Up @@ -418,7 +432,7 @@ def test_generate_config_file(self):
def test_generate_config_file_classes_to_include(self):
class NotInConfig(HasTraits):
from_hidden = Unicode('x', help="""From hidden class

Details about from_hidden.
""").tag(config=True)

Expand Down Expand Up @@ -611,7 +625,7 @@ def test_show_config(capsys):
cfg.MyApp.i = 5
# don't show empty
cfg.OtherApp

app = MyApp(config=cfg, show_config=True)
app.start()
out, err = capsys.readouterr()
Expand All @@ -624,7 +638,7 @@ 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()
Expand Down