Compare commits
No commits in common. "eb183b0d3b0af1fac2a62a27db39551d0e71cd6a" and "7efacf04e41c60cf563bf37567fe67faab2b9b76" have entirely different histories.
eb183b0d3b
...
7efacf04e4
2 changed files with 51 additions and 96 deletions
129
mylib/config.py
129
mylib/config.py
|
@ -1,5 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# pylint: disable=too-many-lines
|
||||
|
||||
""" Configuration & options parser """
|
||||
|
||||
|
@ -137,17 +136,14 @@ class BaseOption: # pylint: disable=too-many-instance-attributes
|
|||
@property
|
||||
def parser_dest(self):
|
||||
""" Get option name in arguments parser options """
|
||||
return f'{self.section.name}_{self.name}'
|
||||
return '{0}_{1}'.format(self.section.name, self.name)
|
||||
|
||||
@property
|
||||
def parser_help(self):
|
||||
""" Get option help message in arguments parser options """
|
||||
if self.arg_help and self.default is not None:
|
||||
# pylint: disable=consider-using-f-string
|
||||
return '{0} (Default: {1})'.format(
|
||||
self.arg_help,
|
||||
re.sub(r'%([^%])', r'%%\1', str(self._default_in_config))
|
||||
)
|
||||
self.arg_help, re.sub(r'%([^%])', r'%%\1', str(self.default)))
|
||||
if self.arg_help:
|
||||
return self.arg_help
|
||||
return None
|
||||
|
@ -157,7 +153,9 @@ class BaseOption: # pylint: disable=too-many-instance-attributes
|
|||
""" Get option argument name in parser options """
|
||||
return (
|
||||
self.arg if self.arg else
|
||||
f'--{self.section.name}-{self.name}'.lower().replace('_', '-')
|
||||
'--{0}-{1}'.format(
|
||||
self.section.name, self.name
|
||||
).lower().replace('_', '-')
|
||||
)
|
||||
|
||||
def add_option_to_parser(self, section_opts):
|
||||
|
@ -294,7 +292,6 @@ class BooleanOption(BaseOption):
|
|||
@property
|
||||
def parser_argument_name(self):
|
||||
""" Get option argument name in parser options """
|
||||
# pylint: disable=consider-using-f-string
|
||||
return (
|
||||
self.arg if self.arg else
|
||||
'--{0}-{1}-{2}'.format(
|
||||
|
@ -307,7 +304,7 @@ class BooleanOption(BaseOption):
|
|||
def _ask_value(self, prompt=None, **kwargs):
|
||||
""" Ask to user to enter value of this option and return it """
|
||||
default_value = self.get()
|
||||
prompt = f'{self.name}: '
|
||||
prompt = "%s: " % self.name
|
||||
if default_value:
|
||||
prompt += '[Y/n] '
|
||||
else:
|
||||
|
@ -416,11 +413,12 @@ class PasswordOption(StringOption):
|
|||
value = keyring.get_password(service_name, username)
|
||||
|
||||
if value is None:
|
||||
# pylint: disable=consider-using-f-string
|
||||
value = getpass(
|
||||
'Please enter {0}{1}: '.format(
|
||||
f'{self.section.name} {self.name}',
|
||||
f' for {username}' if username != self.name else ''
|
||||
'Please enter %s%s: ' % (
|
||||
self.comment if self.comment else
|
||||
'%s %s' % (self.section.name, self.name),
|
||||
(' for %s' % username)
|
||||
if username != self.name else ''
|
||||
)
|
||||
)
|
||||
keyring.set_password(service_name, username, value)
|
||||
|
@ -447,11 +445,11 @@ class PasswordOption(StringOption):
|
|||
print('# ' + self.comment)
|
||||
default_value = kwargs.pop('default_value', self.get())
|
||||
if not prompt:
|
||||
prompt = f'{self.name}: '
|
||||
prompt = '%s: ' % self.name
|
||||
if default_value is not None:
|
||||
# Hide value only if it differed from default value
|
||||
if default_value == self.default:
|
||||
prompt += f'[{default_value}] '
|
||||
prompt += '[%s] ' % default_value
|
||||
else:
|
||||
prompt += '[secret defined, leave to empty to keep it as unchange] '
|
||||
value = getpass(prompt)
|
||||
|
@ -468,8 +466,8 @@ class PasswordOption(StringOption):
|
|||
default_use_keyring = (super().get() == self.keyring_value)
|
||||
while use_keyring is None:
|
||||
prompt = (
|
||||
'Do you want to use XDG keyring ? '
|
||||
f"[{'Y/n' if default_use_keyring else 'y/N'}] "
|
||||
'Do you want to use XDG keyring ? [%s] ' %
|
||||
('Y/n' if default_use_keyring else 'y/N')
|
||||
)
|
||||
result = input(prompt).lower()
|
||||
if result == '':
|
||||
|
@ -490,7 +488,7 @@ class ConfigSection:
|
|||
def __init__(self, config, name, comment=None, order=None):
|
||||
self.config = config
|
||||
self.name = name
|
||||
self.options = {}
|
||||
self.options = dict()
|
||||
self.comment = comment
|
||||
self.order = order if isinstance(order, int) else 10
|
||||
|
||||
|
@ -502,7 +500,7 @@ class ConfigSection:
|
|||
:param name: Option name
|
||||
:param **kwargs: Dict of raw option for type class
|
||||
"""
|
||||
assert not self.defined(name), f'Duplicated option {name}'
|
||||
assert not self.defined(name), "Duplicated option %s" % name
|
||||
self.options[name] = _type(self.config, self, name, **kwargs)
|
||||
return self.options[name]
|
||||
|
||||
|
@ -516,12 +514,12 @@ class ConfigSection:
|
|||
|
||||
def get(self, option):
|
||||
""" Get option value """
|
||||
assert self.defined(option), f'Option {option} unknown'
|
||||
assert self.defined(option), "Option %s unknown" % option
|
||||
return self.options[option].get()
|
||||
|
||||
def set(self, option, value):
|
||||
""" Set option value """
|
||||
assert self.defined(option), f'Option {option} unknown'
|
||||
assert self.defined(option), "Option %s unknown" % option
|
||||
return self.options[option].set(value)
|
||||
|
||||
def add_options_to_parser(self, parser):
|
||||
|
@ -532,16 +530,16 @@ class ConfigSection:
|
|||
else self.name.capitalize()
|
||||
)
|
||||
|
||||
for option in self.options: # pylint: disable=consider-using-dict-items
|
||||
for option in self.options:
|
||||
self.options[option].add_option_to_parser(section_opts)
|
||||
|
||||
def export_to_config(self):
|
||||
""" Export section and their options to configuration file """
|
||||
lines = []
|
||||
if self.comment:
|
||||
lines.append(f'# {self.comment}')
|
||||
lines.append(f'[{self.name}]')
|
||||
for option in self.options: # pylint: disable=consider-using-dict-items
|
||||
lines.append('# %s' % self.comment)
|
||||
lines.append('[%s]' % self.name)
|
||||
for option in self.options:
|
||||
lines.append(self.options[option].export_to_config())
|
||||
return '\n'.join(lines)
|
||||
|
||||
|
@ -557,9 +555,9 @@ class ConfigSection:
|
|||
:rtype: bool of dict
|
||||
"""
|
||||
if self.comment:
|
||||
print(f'# {self.comment}')
|
||||
print(f'[{self.name}]''\n')
|
||||
result = {}
|
||||
print('# %s' % self.comment)
|
||||
print('[%s]\n' % self.name)
|
||||
result = dict()
|
||||
error = False
|
||||
for name, option in self.options.items():
|
||||
option_result = option.ask_value(set_it=set_it)
|
||||
|
@ -624,7 +622,7 @@ class Config: # pylint: disable=too-many-instance-attributes
|
|||
The specified callback method will receive Config object as parameter.
|
||||
: param ** kwargs: Raw parameters dict pass to ConfigSection __init__() method
|
||||
"""
|
||||
assert name not in self.sections, f'Duplicated section {name}'
|
||||
assert name not in self.sections, "Duplicated section %s" % name
|
||||
|
||||
self.sections[name] = ConfigSection(self, name, **kwargs)
|
||||
if loaded_callback:
|
||||
|
@ -646,7 +644,7 @@ class Config: # pylint: disable=too-many-instance-attributes
|
|||
""" Get option value """
|
||||
assert self.config_parser or self.options, 'Unconfigured options parser'
|
||||
assert self.defined(
|
||||
section, option), f'Unknown option {section}.{option}'
|
||||
section, option), 'Unknown option %s.%s' % (section, option)
|
||||
value = self.sections[section].get(option)
|
||||
log.debug('get(%s, %s): %s (%s)', section, option, value, type(value))
|
||||
return value
|
||||
|
@ -666,7 +664,7 @@ class Config: # pylint: disable=too-many-instance-attributes
|
|||
""" Set option value """
|
||||
assert self.config_parser, 'Unconfigured options parser'
|
||||
assert self.defined(
|
||||
section, option), f'Unknown option {section}.{option}'
|
||||
section, option), 'Unknown option %s.%s' % (section, option)
|
||||
self.sections[section].set(option, value)
|
||||
|
||||
def load_file(self, filepath, execute_callback=True):
|
||||
|
@ -736,11 +734,7 @@ class Config: # pylint: disable=too-many-instance-attributes
|
|||
'Configuration directory "%s" does not exist (or not writable)', dirpath)
|
||||
return False
|
||||
|
||||
lines = [
|
||||
'#\n'
|
||||
f'# {self.appname} configuration'
|
||||
'\n#\n'
|
||||
]
|
||||
lines = ['#\n# %s configuration\n#\n' % self.appname]
|
||||
|
||||
for section_name in self._ordered_section_names:
|
||||
lines.append('')
|
||||
|
@ -775,15 +769,9 @@ class Config: # pylint: disable=too-many-instance-attributes
|
|||
formatter_class=RawWrappedTextHelpFormatter,
|
||||
**kwargs)
|
||||
|
||||
config_file_help = (
|
||||
f'Configuration file to use (default: {self.config_filepath})'
|
||||
)
|
||||
config_file_help = 'Configuration file to use (default: %s)' % self.config_filepath
|
||||
if self.config_file_env_variable:
|
||||
config_file_help += (
|
||||
'\n\nYou also could set '
|
||||
f'{self.config_file_env_variable} environment variable to '
|
||||
'specify your configuration file path.'
|
||||
)
|
||||
config_file_help += '\n\nYou also could set %s environment variable to specify your configuration file path.' % self.config_file_env_variable
|
||||
self.options_parser.add_argument(
|
||||
'-c',
|
||||
'--config',
|
||||
|
@ -866,7 +854,7 @@ class Config: # pylint: disable=too-many-instance-attributes
|
|||
# Load configuration file
|
||||
if os.path.isfile(options.config) and not self.load_file(options.config, execute_callback=False):
|
||||
parser.error(
|
||||
f'Failed to load configuration from file {options.config}'
|
||||
'Failed to load configuration from file %s' % options.config
|
||||
)
|
||||
|
||||
if options.save and not already_saved:
|
||||
|
@ -879,20 +867,11 @@ class Config: # pylint: disable=too-many-instance-attributes
|
|||
logging.getLogger().setLevel(logging.INFO)
|
||||
|
||||
if self.get('console', 'enabled'):
|
||||
stdout_console_handler = logging.StreamHandler(sys.stdout)
|
||||
stdout_console_handler.addFilter(StdoutInfoFilter())
|
||||
stdout_console_handler.setLevel(logging.DEBUG)
|
||||
|
||||
stderr_console_handler = logging.StreamHandler(sys.stderr)
|
||||
stderr_console_handler.setLevel(logging.WARNING)
|
||||
|
||||
console_handler = logging.StreamHandler(sys.stdout)
|
||||
if self.get('console', 'log_format'):
|
||||
console_formater = logging.Formatter(self.get('console', 'log_format'))
|
||||
stdout_console_handler.setFormatter(console_formater)
|
||||
stderr_console_handler.setFormatter(console_formater)
|
||||
|
||||
logging.getLogger().addHandler(stdout_console_handler)
|
||||
logging.getLogger().addHandler(stderr_console_handler)
|
||||
console_handler.setFormatter(console_formater)
|
||||
logging.getLogger().addHandler(console_handler)
|
||||
|
||||
if execute_callback:
|
||||
self._loaded()
|
||||
|
@ -928,7 +907,7 @@ class Config: # pylint: disable=too-many-instance-attributes
|
|||
# On set it mode, ensure configuration file parser is initialized
|
||||
if set_it and not self.config_parser:
|
||||
self.config_parser = ConfigParser()
|
||||
result = {}
|
||||
result = dict()
|
||||
error = False
|
||||
for name, section in self.sections.items():
|
||||
section_result = section.ask_values(set_it=set_it)
|
||||
|
@ -981,14 +960,14 @@ class Config: # pylint: disable=too-many-instance-attributes
|
|||
argv, create=False, execute_callback=False)
|
||||
|
||||
if os.path.exists(options.config) and not options.overwrite:
|
||||
print(f'Configuration file {options.config} already exists')
|
||||
print('Configuration file %s already exists' % options.config)
|
||||
sys.exit(1)
|
||||
|
||||
if options.interactive:
|
||||
self.ask_values(set_it=True)
|
||||
|
||||
if self.save(options.config):
|
||||
print(f'Configuration file {options.config} created.')
|
||||
print('Configuration file %s created.' % options.config)
|
||||
if options.validate:
|
||||
print('Validate your configuration...')
|
||||
try:
|
||||
|
@ -997,15 +976,14 @@ class Config: # pylint: disable=too-many-instance-attributes
|
|||
else:
|
||||
print('Error(s) occurred validating your configuration. See logs for details.')
|
||||
sys.exit(1)
|
||||
except Exception: # pylint: disable=broad-except
|
||||
except Exception:
|
||||
print(
|
||||
'Exception occurred validating your configuration:\n'
|
||||
f'{traceback.format_exc()}'
|
||||
'\n\nSee logs for details.'
|
||||
)
|
||||
'Exception occurred validating your configuration: %s\n\nSee logs for details.' %
|
||||
traceback.format_exc())
|
||||
sys.exit(2)
|
||||
else:
|
||||
print(f'Error occured creating configuration file {options.config}')
|
||||
print('Error occured creating configuration file %s' %
|
||||
options.config)
|
||||
sys.exit(1)
|
||||
|
||||
sys.exit(0)
|
||||
|
@ -1028,8 +1006,8 @@ class Config: # pylint: disable=too-many-instance-attributes
|
|||
return os.environ.get(self.config_file_env_variable)
|
||||
return os.path.join(
|
||||
self.config_dir,
|
||||
f'{self.shortname}.ini' if self.shortname
|
||||
else 'config.ini'
|
||||
("%s.ini" % self.shortname) if self.shortname
|
||||
else "config.ini"
|
||||
)
|
||||
|
||||
|
||||
|
@ -1066,7 +1044,7 @@ class ConfigurableObject:
|
|||
**kwargs):
|
||||
|
||||
for key, value in kwargs.items():
|
||||
assert key in self._defaults, f'Unknown {key} option'
|
||||
assert key in self._defaults, "Unknown %s option" % key
|
||||
self._kwargs[key] = value
|
||||
|
||||
if options:
|
||||
|
@ -1076,7 +1054,7 @@ class ConfigurableObject:
|
|||
elif self._config_name:
|
||||
self._options_prefix = self._config_name + '_'
|
||||
else:
|
||||
raise Exception(f'No configuration name defined for {__name__}')
|
||||
raise Exception('No configuration name defined for %s' % __name__)
|
||||
|
||||
if config:
|
||||
self._config = config
|
||||
|
@ -1085,7 +1063,7 @@ class ConfigurableObject:
|
|||
elif self._config_name:
|
||||
self._config_section = self._config_name
|
||||
else:
|
||||
raise Exception(f'No configuration name defined for {__name__}')
|
||||
raise Exception('No configuration name defined for %s' % __name__)
|
||||
|
||||
def _get_option(self, option, default=None, required=False):
|
||||
""" Retreive option value """
|
||||
|
@ -1098,7 +1076,7 @@ class ConfigurableObject:
|
|||
if self._config and self._config.defined(self._config_section, option):
|
||||
return self._config.get(self._config_section, option)
|
||||
|
||||
assert not required, f'Options {option} not defined'
|
||||
assert not required, "Options %s not defined" % option
|
||||
|
||||
return default if default is not None else self._defaults.get(option)
|
||||
|
||||
|
@ -1136,10 +1114,3 @@ class ConfigSectionAsDictWrapper:
|
|||
|
||||
def __delitem__(self, key):
|
||||
raise Exception('Deleting a configuration option is not supported')
|
||||
|
||||
|
||||
# pylint: disable=too-few-public-methods
|
||||
class StdoutInfoFilter(logging.Filter):
|
||||
""" Logging filter to keep messages only >= logging.INFO """
|
||||
def filter(self, record):
|
||||
return record.levelno in (logging.DEBUG, logging.INFO)
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
# pylint: disable=redefined-outer-name,missing-function-docstring,protected-access,global-statement
|
||||
""" Tests on config lib """
|
||||
|
||||
import logging
|
||||
|
||||
from mylib.config import Config, ConfigSection
|
||||
from mylib.config import StringOption
|
||||
|
||||
runned = {}
|
||||
runned = dict()
|
||||
|
||||
|
||||
def test_config_init_default_args():
|
||||
|
@ -210,17 +208,3 @@ def test_get_default():
|
|||
config.parse_arguments_options(argv=[], create=False)
|
||||
|
||||
assert config.get(section_name, opt_name) == opt_default_value
|
||||
|
||||
|
||||
def test_logging_splited_stdout_stderr(capsys):
|
||||
config = Config('Test app')
|
||||
config.parse_arguments_options(argv=['-C', '-v'], create=False)
|
||||
info_msg = "[info]"
|
||||
err_msg = "[error]"
|
||||
logging.getLogger().info(info_msg)
|
||||
logging.getLogger().error(err_msg)
|
||||
captured = capsys.readouterr()
|
||||
assert info_msg in captured.out
|
||||
assert info_msg not in captured.err
|
||||
assert err_msg in captured.err
|
||||
assert err_msg not in captured.out
|
||||
|
|
Loading…
Reference in a new issue