"""Easy and simple configuration.
See also: :doc:`intro`
"""
import re
import types
from collections import namedtuple
from configparser import ConfigParser
from functools import lru_cache
from importlib.resources import read_text
from io import StringIO
from salmagundi.utils import check_path_like
from ._config import NOVALUE, NOTFOUND, Config
from ._errors import (Error, ConfigError, DuplicateError, ReadonlyError,
SpecError)
from ._spec import Spec
from ._utils import (convert_choice, convert_loglevel, convert_predicate,
convert_string, create_getter, create_setter,
create_deleter)
__version__ = read_text(__package__, 'VERSION').strip()
__all__ = ['NOTFOUND', 'NOVALUE', 'Config', 'ConfigError', 'DuplicateError',
'Error', 'ReadonlyError', 'SpecError', 'configure',
'convert_choice', 'convert_loglevel', 'convert_predicate',
'convert_string']
_OptData = namedtuple('OptData', 'name, ro, value')
def _get_name(sec, opt, create_properties):
if create_properties:
name = f'{sec}_{opt}'
if not name.isidentifier():
raise SpecError(f'not a valid name: {name}')
else:
name = None
return name
def _get_options(cp, sec, spec_opt, wildcard, has_wildcard):
if has_wildcard:
opts = []
for opt in cp.options(sec):
if re.fullmatch(spec_opt.replace(wildcard, '.*?'), opt):
opts.append(opt)
return opts
else:
return [spec_opt] if cp.has_option(sec, spec_opt) else []
def _with_spec(cp, create_properties, spec, kwargs):
@lru_cache
def get_sections(spec_sec, wildcard, has_wildcard):
if has_wildcard:
secs = []
for sec in cp.sections():
if re.fullmatch(spec_sec.replace(wildcard, '.*?'), sec):
secs.append(sec)
return secs
else:
return [spec_sec] if spec_sec in cp else []
options = {}
for spec_sec, spec_opt in [(spec_sec, spec_opt) for spec_sec in spec.data
for spec_opt in spec.data[spec_sec]]:
opt_spec = spec.data[spec_sec][spec_opt]
secs = get_sections(spec_sec, spec.wildcard, opt_spec.sec_wildcard)
if not secs:
if not opt_spec.sec_wildcard:
if opt_spec.required:
raise ConfigError(
f'missing required option {spec_opt!r} '
f'in section {spec_sec!r}')
if not opt_spec.opt_wildcard:
value = opt_spec.default
name = _get_name(spec_sec, spec_opt, create_properties)
options[(spec_sec, spec_opt)] = _OptData(
name, opt_spec.flag is not False, value)
else:
for sec in secs:
opts = _get_options(cp, sec, spec_opt, spec.wildcard,
opt_spec.opt_wildcard)
if not opts:
if opt_spec.required:
raise ConfigError(
f'missing required option {spec_opt!r} '
f'in section {sec!r}')
if not opt_spec.opt_wildcard:
value = opt_spec.default
name = _get_name(sec, spec_opt, create_properties)
options[(sec, spec_opt)] = _OptData(
name, opt_spec.flag is not False, value)
else:
for opt in opts:
value = cp.get(sec, opt, raw=opt_spec.raw)
if (value is None and
kwargs.get('allow_no_value', False)):
if opt_spec.converter is None:
value = NOVALUE
else:
raise ConfigError(
f'option {opt!r} in section {sec!r} '
'has no value')
else:
try:
value = opt_spec.converter(value)
except Exception as ex:
raise ConfigError(
f'error converting value {value!r} for '
f'option {opt!r} in section {sec!r} with '
f'converter {opt_spec.conv_name!r}: {ex}')
name = _get_name(sec, opt, create_properties)
options[(sec, opt)] = _OptData(
name, opt_spec.flag is not False, value)
get_sections.cache_clear()
return options
def _without_spec(cp, create_properties, kwargs):
options = {}
for sec, opt in [(sec, opt)
for sec in cp.sections()
for opt in cp.options(sec)]:
name = _get_name(sec, opt, create_properties)
value = cp.get(sec, opt)
if value is None and kwargs.get('allow_no_value', False):
value = NOVALUE
options[(sec, opt)] = _OptData(name, False, value)
return options
def _read_conf(cp, conf):
if isinstance(conf, ConfigParser):
sio = StringIO()
conf.write(sio)
cp.read_string(sio.getvalue())
else:
try:
check_path_like(conf)
with open(conf) as fh:
cp.read_file(fh)
except TypeError:
cp.read_file(conf)
def _check_fixed_opts(conf, fixed_opts, kwargs):
kwargs.pop('interpolation', None)
cp = ConfigParser(interpolation=None, **kwargs)
_read_conf(cp, conf)
for sec, opt in fixed_opts:
if cp.has_option(sec, opt):
raise ConfigError(
f'option {opt!r} in section {sec!r} is fixed')
return cp