66
77from __future__ import print_function
88
9+ import json
910import logging
1011import os
1112import re
@@ -123,7 +124,16 @@ class Application(SingletonConfigurable):
123124
124125 # A sequence of Configurable subclasses whose config=True attributes will
125126 # be exposed at the command line.
126- classes = List ([])
127+ classes = []
128+ @property
129+ def _help_classes (self ):
130+ """Define `App.help_classes` if CLI classes should differ from config file classes"""
131+ return getattr (self , 'help_classes' , self .classes )
132+
133+ @property
134+ def _config_classes (self ):
135+ """Define `App.config_classes` if config file classes should differ from CLI classes."""
136+ return getattr (self , 'config_classes' , self .classes )
127137
128138 # The version string of this application.
129139 version = Unicode (u'0.0' )
@@ -256,7 +266,7 @@ def print_alias_help(self):
256266
257267 lines = []
258268 classdict = {}
259- for cls in self .classes :
269+ for cls in self ._help_classes :
260270 # include all parents (up to, but excluding Configurable) in available names
261271 for c in cls .mro ()[:- 3 ]:
262272 classdict [c .__name__ ] = c
@@ -331,15 +341,16 @@ def print_help(self, classes=False):
331341 self .print_options ()
332342
333343 if classes :
334- if self .classes :
344+ help_classes = self ._help_classes
345+ if help_classes :
335346 print ("Class parameters" )
336347 print ("----------------" )
337348 print ()
338349 for p in wrap_paragraphs (self .keyvalue_description ):
339350 print (p )
340351 print ()
341352
342- for cls in self . classes :
353+ for cls in help_classes :
343354 cls .class_print_help ()
344355 print ()
345356 else :
@@ -412,7 +423,7 @@ def flatten_flags(self):
412423 # it will be a dict by parent classname of classes in our list
413424 # that are descendents
414425 mro_tree = defaultdict (list )
415- for cls in self .classes :
426+ for cls in self ._help_classes :
416427 clsname = cls .__name__
417428 for parent in cls .mro ()[1 :- 3 ]:
418429 # exclude cls itself and Configurable,HasTraits,object
@@ -491,27 +502,32 @@ def _load_config_files(cls, basefilename, path=None, log=None):
491502
492503 yield each config object in turn.
493504 """
494- pyloader = PyFileConfigLoader (basefilename + '.py' , path = path , log = log )
495- jsonloader = JSONFileConfigLoader (basefilename + '.json' , path = path , log = log )
496- config = None
497- for loader in [pyloader , jsonloader ]:
498- try :
499- config = loader .load_config ()
500- except ConfigFileNotFound :
501- pass
502- except Exception :
503- # try to get the full filename, but it will be empty in the
504- # unlikely event that the error raised before filefind finished
505- filename = loader .full_filename or basefilename
506- # problem while running the file
507- if log :
508- log .error ("Exception while loading config file %s" ,
509- filename , exc_info = True )
510- else :
511- if log :
512- log .debug ("Loaded config file: %s" , loader .full_filename )
513- if config :
514- yield config
505+
506+ if not isinstance (path , list ):
507+ path = [path ]
508+ for path in path [::- 1 ]:
509+ # path list is in descending priority order, so load files backwards:
510+ pyloader = PyFileConfigLoader (basefilename + '.py' , path = path , log = log )
511+ jsonloader = JSONFileConfigLoader (basefilename + '.json' , path = path , log = log )
512+ config = None
513+ for loader in [pyloader , jsonloader ]:
514+ try :
515+ config = loader .load_config ()
516+ except ConfigFileNotFound :
517+ pass
518+ except Exception :
519+ # try to get the full filename, but it will be empty in the
520+ # unlikely event that the error raised before filefind finished
521+ filename = loader .full_filename or basefilename
522+ # problem while running the file
523+ if log :
524+ log .error ("Exception while loading config file %s" ,
525+ filename , exc_info = True )
526+ else :
527+ if log :
528+ log .debug ("Loaded config file: %s" , loader .full_filename )
529+ if config :
530+ yield config
515531
516532 raise StopIteration
517533
@@ -520,8 +536,17 @@ def _load_config_files(cls, basefilename, path=None, log=None):
520536 def load_config_file (self , filename , path = None ):
521537 """Load config files by filename and path."""
522538 filename , ext = os .path .splitext (filename )
539+ loaded = []
523540 for config in self ._load_config_files (filename , path = path , log = self .log ):
541+ loaded .append (config )
524542 self .update_config (config )
543+ if len (loaded ) > 1 :
544+ collisions = loaded [0 ].collisions (loaded [1 ])
545+ if collisions :
546+ self .log .warn ("Collisions detected in {0}.py and {0}.json config files."
547+ " {0}.json has higher priority: {1}" .format (
548+ filename , json .dumps (collisions , indent = 2 ),
549+ ))
525550
526551
527552 def generate_config_file (self ):
@@ -530,7 +555,7 @@ def generate_config_file(self):
530555 lines .append ('' )
531556 lines .append ('c = get_config()' )
532557 lines .append ('' )
533- for cls in self .classes :
558+ for cls in self ._config_classes :
534559 lines .append (cls .class_config_section ())
535560 return '\n ' .join (lines )
536561
0 commit comments