""" Application configuration values can be configured in the OS environment via KEY_NAME=... or in a toml file using [section] foo = ... bar_baz = ... in the latter case, the section and name are uppercased and joined with `_`, then inserted into the OS environment (in this case, resulting in SECTION_FOO=... and SECTION_BAR_BAZ=... ). The OS environment takes precedence - if a key is found in both env and config, the config setting will be ignored. """ import tomllib import logging from os import environ from functools import wraps import consts logger = logging.getLogger(consts.LOG_ROOT) def load(file): """ Load configuration from toml and add to environment """ if file is None: logger.debug('no config file provided.') else: logger.debug('loading config') with open(file, 'rb') as fh: config = tomllib.load(fh) for k, v in config.items(): for k_inner, v_inner in v.items(): effective_key = f'{k.upper()}_{k_inner.upper()}' if effective_key in environ: logger.warning( '%s already exists in os environment, skipping', effective_key) else: environ[effective_key] = str(v_inner) logger.debug('raw: %s = %s', effective_key, environ[effective_key]) def env(key, default=None): """ retrieve a key from the OS environment. throws `KeyError` when the key is not found and no default fallback is provided. """ if default is None: return environ[key] return environ.get(key, default=default) def logged(f): """ logging wrapper for resolved configuration settings """ @wraps(f) def decorated(*args, **kwargs): res = f(*args, **kwargs) logger.debug('resolved: %s = %s', args[0], res) return res return decorated @logged def int_val(key, default=None): """ retrieve configuration value as int """ return int(env(key, default=default)) def strtobool(val): """Convert a string representation of truth to true (1) or false (0). True values are 'y', 'yes', 't', 'true', 'on', and '1'; false values are 'n', 'no', 'f', 'false', 'off', and '0'. Raises ValueError if 'val' is anything else. """ val = val.lower() if val in ("y", "yes", "t", "true", "on", "1"): return True elif val in ("n", "no", "f", "false", "off", "0"): return False else: raise ValueError(f'Invalid bool string: {val}') @logged def bool_val(key, default=None): """ retrieve configuration value as bool """ return strtobool(env(key, default=default)) @logged def str_val(key, default=None): """ retrieve configuration value as bool """ return env(key, default=default)