diff --git a/app_skellington/app_container.py b/app_skellington/app_container.py index 3c396c7..7979018 100644 --- a/app_skellington/app_container.py +++ b/app_skellington/app_container.py @@ -12,6 +12,8 @@ from . import _util from . import cli from . import cfg +import logging + # These two variables affect the directory paths for # config files and logging. DEFAULT_APP_NAME = '' @@ -54,7 +56,13 @@ class ApplicationContainer: config = cfg.Config(configspec_filepath, configini_filepath) logger = log.LoggingLayer(self.appname, self.appauthor) - logger.configure_logging() + # Try and load logging configuration if provided + log_config = config.get('logging') + if log_config is not None: + logger.configure_logging(log_config) + else: + logger.configure_logging() + self.ctx = ApplicationContext(config, logger) self['ctx'] = lambda: self.ctx diff --git a/app_skellington/cfg.py b/app_skellington/cfg.py index ac6f0ee..aaf5a31 100644 --- a/app_skellington/cfg.py +++ b/app_skellington/cfg.py @@ -18,6 +18,11 @@ class Config: """ Structure to store application runtime configuration. Also contains functionality to load configuration from local site file. + + Provide config.spec - specification file which defines allowed parameters and types. + + Provide config.ini - configuration instance which contains values for any + configuration arguments. """ DEFAULT_CAPABILITIES = { @@ -146,6 +151,19 @@ class Config: """ self._config_obj[key] = value + def get(self, key, default=None): + """ + Attempt to retrieve configuration item, otherwise return default + provided value. + + Similar to Dictionary.get() + """ + try: + v = self.__getitem__(key) + return v + except KeyError as ex: + return default + def load_config( self, configspec_filepath=None, configini_filepath=None ): diff --git a/app_skellington/cli.py b/app_skellington/cli.py index 40202fe..1a78f7e 100644 --- a/app_skellington/cli.py +++ b/app_skellington/cli.py @@ -80,9 +80,8 @@ class CommandTree: Creates a root-level submenu with no entries. SubMenu node is returned which can have submenus and commands attached to it. """ - # NOTE(MG) Fix below strategizes whether to pass in 'required' - # paremter to ArgumentParser.add_subparsers() - # which was added in in Python3.7. + # NOTE(MG) Fix for Python>=3.7, + # argparse.ArgumentParser added 'required' argument. # Must also be written into SubMenu.create_submenu. func_args = { 'dest': param_name, @@ -91,11 +90,12 @@ class CommandTree: } if ( sys.version_info.major == 3 - and sys.version_info.minor <= 6 + and sys.version_info.minor < 7 ): if is_required: _bootstrap_logger.warn('Unable to enforce required submenu: Requires >= Python 3.7') del func_args['required'] + # END fix for Python<3.7 # Creates an argument as a slot in the underlying argparse. subparsers = self.root_parser.add_subparsers( @@ -447,9 +447,8 @@ class SubMenu: help='sub-submenu help', description='sub-sub description') - # NOTE(MG) Fix below strategizes whether to pass in 'required' - # paremter to ArgumentParser.add_subparsers() - # which was added in in Python3.7. + # NOTE(MG) Fix for Python>=3.7, + # argparse.ArgumentParser added 'required' argument. # Must also be written into CommandTree.init_submenu func_args = { 'dest': var_name, @@ -458,11 +457,14 @@ class SubMenu: } if ( sys.version_info.major == 3 - and sys.version_info.minor <= 6 + and sys.version_info.minor < 7 ): if is_required: _bootstrap_logger.warn('Unable to enforce required submenu: Requires >= Python 3.7') del func_args['required'] + # END fix for Python<3.7 + + # Turn entry into a submenu of it's own: # type = _SubParsersAction subp_node = entry_node.add_subparsers( diff --git a/app_skellington/log.py b/app_skellington/log.py index c2ed2ed..ad8ea00 100644 --- a/app_skellington/log.py +++ b/app_skellington/log.py @@ -78,7 +78,6 @@ class LoggingLayer: self.transform_config(config_dict) try: - # TODO(MG) Pretty print _bootstrap_logger.debug('log - Log configuration: %s', config_dict) logging.config.dictConfig(config_dict) _bootstrap_logger.debug('log - Configured all logging') @@ -108,13 +107,16 @@ class LoggingLayer: d = config_dict['loggers'][logger] self._convert_str_to_loglevel(d, 'level') - # Replace 'root' logger with '', logging module convention for root handler - # Note: '' is disallowed in ConfigObj (hence the reason for this replacement) + + # Implementation note: + # app_skellington expects root logger configuration to be under 'root' + # instead of '' (python spec) because '' is not a valid name in ConfigObj. try: - config_dict['loggers'][''] = config_dict['loggers']['root'] - del config_dict['loggers']['root'] + if config_dict['loggers'].get('root') is not None: + config_dict['loggers'][''] = config_dict['loggers']['root'] + del config_dict['loggers']['root'] except Exception as ex: - _bootstrap.logger.warn('internal failure patching root logger') + _bootstrap_logger.warn('was not able to find and patch root logger configuration from arguments') # Evaluate the full filepath of the file handler