125 lines
3.1 KiB
Python
125 lines
3.1 KiB
Python
"""
|
|
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.
|
|
|
|
Author(s): anatol.ulrich@basebox.io
|
|
|
|
Copyright (c) 2023 basebox GmbH. All rights reserved.
|
|
"""
|
|
|
|
|
|
# stdlib imports
|
|
from os import environ
|
|
from functools import wraps
|
|
|
|
# dependencies imports
|
|
try:
|
|
import tomllib
|
|
except ImportError:
|
|
import tomli as tomllib
|
|
import logging
|
|
|
|
# app imports
|
|
import consts
|
|
|
|
logger = logging.getLogger(consts.LOG_ROOT)
|
|
|
|
|
|
def load(file):
|
|
"""
|
|
Load configuration from toml and add to environment.
|
|
Skip anything already present there.
|
|
Configuration key names are converted according to the module documentation.
|
|
"""
|
|
|
|
if file is None:
|
|
logger.debug('no config file provided.')
|
|
else:
|
|
logger.debug('loading config')
|
|
with open(file, 'rb') as handle:
|
|
config = tomllib.load(handle)
|
|
for section, vals in config.items():
|
|
for key, val in vals.items():
|
|
effective_key = f'{section.upper()}_{key.upper()}'
|
|
if effective_key in environ:
|
|
logger.warning(
|
|
'%s already exists in os environment, skipping', effective_key)
|
|
else:
|
|
environ[effective_key] = str(val)
|
|
logger.debug('config/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 values
|
|
"""
|
|
@wraps(f)
|
|
def decorated(*args, **kwargs):
|
|
res = f(*args, **kwargs)
|
|
logger.debug('config/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` or `False`.
|
|
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)
|