py-microservice/config.py
2023-10-11 16:58:50 +02:00

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)