Skip to content
Snippets Groups Projects
Commit 35cc1138 authored by David Huss's avatar David Huss :speech_balloon:
Browse files

Add Option to override config with environment variables

parent 61d9ef44
No related branches found
No related tags found
No related merge requests found
......@@ -66,4 +66,21 @@ print(f"This application is called {config.APPLICATION_NAME}, the mood is {confi
```
## Environment Variables
You can override single settings with environment variables in the style:
`PREFIX_KEY1_KEY2_..._KEYn`
For example, if your application name is `eigenservice`, a top-level setting like `loglevel` in your configuration can be overridden by setting the environment variable:
```bash
export EIGENSERVICE_LOGLEVEL=debug
```
For nested configuration keys, the variable name is constructed by concatenating the keys in uppercase with underscores. For instance, to override the setting `database.path`, you would set:
```bash
export EIGENSERVICE_DATABASE_PATH=":memory:"
```
......@@ -208,6 +208,8 @@ def initialize_config(logger=None, suffix=None) -> MutableMapping[str, Any]:
else:
print(f"Config [{i+2}]: {p} (overrides previous configs)")
config = override_config_with_env(config)
if logger is not None:
logger = set_loglevel(config, logger)
......@@ -224,6 +226,55 @@ def read_config(config_path: str) -> MutableMapping[str, Any]:
return ConfigDict(config)
def override_config_with_env(
config: dict, prefix: str = None, verbose: bool = True
) -> dict:
"""
Recursively override config values with environment variables.
The environment variable name is constructed as:
PREFIX_KEY1_KEY2_..._KEYn
where the keys are from the config hierarchy in uppercase.
If no prefix is provided, it defaults to APPLICATION_NAME.upper().
If verbose is True, prints out any overrides.
"""
if prefix is None:
from common_config.common import (
APPLICATION_NAME,
) # using the global APPLICATION_NAME
prefix = APPLICATION_NAME.upper()
for key, value in config.items():
# Build the env var name for the current key.
env_var = f"{prefix}_{key}".upper()
if isinstance(value, dict):
# Recurse into dictionaries.
config[key] = override_config_with_env(
value, prefix=env_var, verbose=verbose
)
else:
# Check if an override exists.
env_value = os.getenv(env_var)
if env_value is not None:
# Try to cast the env_value to the type of the current value.
if isinstance(value, bool):
casted = env_value.lower() in ("true", "1", "yes")
else:
try:
casted = type(value)(env_value)
except Exception:
# Fallback: leave the value as a string.
casted = env_value
if verbose:
print(
f"Environment override: {env_var} set to {env_value} (was {value})"
)
config[key] = casted
return config
def set_loglevel(config, logger: logging.Logger) -> logging.Logger:
"""
Set the loglevel based on the config settings
......
[project]
name = "common-config"
version = "0.2.0"
version = "0.3.0"
description = "A config library for python based services"
authors = [{ name = "David Huss", email = "dh@atoav.com" }]
requires-python = "~=3.8"
......
......@@ -2,4 +2,4 @@ from common_config import __version__
def test_version():
assert __version__ == '0.1.0'
assert __version__ == "0.3.0"
version = 1
revision = 1
requires-python = ">=3.8, <4"
resolution-markers = [
"python_full_version >= '3.9'",
"python_full_version < '3.9'",
]
[[package]]
name = "atomicwrites"
......@@ -10,11 +14,11 @@ sdist = { url = "https://files.pythonhosted.org/packages/87/c6/53da25344e3e3a9c0
[[package]]
name = "attrs"
version = "24.3.0"
version = "25.3.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/48/c8/6260f8ccc11f0917360fc0da435c5c9c7504e3db174d5a12a1494887b045/attrs-24.3.0.tar.gz", hash = "sha256:8f5c07333d543103541ba7be0e2ce16eeee8130cb0b3f9238ab904ce1e85baff", size = 805984 }
sdist = { url = "https://files.pythonhosted.org/packages/5a/b0/1367933a8532ee6ff8d63537de4f1177af4bff9f3e829baf7331f595bb24/attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b", size = 812032 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/89/aa/ab0f7891a01eeb2d2e338ae8fecbe57fcebea1a24dbb64d45801bfab481d/attrs-24.3.0-py3-none-any.whl", hash = "sha256:ac96cd038792094f438ad1f6ff80837353805ac950cd2aa0e0625ef19850c308", size = 63397 },
{ url = "https://files.pythonhosted.org/packages/77/06/bb80f5f86020c4551da315d78b3ab75e8228f89f0162f2c3a819e407941a/attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3", size = 63815 },
]
[[package]]
......@@ -28,7 +32,7 @@ wheels = [
[[package]]
name = "common-config"
version = "0.2.0"
version = "0.3.0"
source = { editable = "." }
dependencies = [
{ name = "toml" },
......@@ -49,11 +53,26 @@ dev = [{ name = "pytest", specifier = "~=5.2" }]
name = "more-itertools"
version = "10.5.0"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version < '3.9'",
]
sdist = { url = "https://files.pythonhosted.org/packages/51/78/65922308c4248e0eb08ebcbe67c95d48615cc6f27854b6f2e57143e9178f/more-itertools-10.5.0.tar.gz", hash = "sha256:5482bfef7849c25dc3c6dd53a6173ae4795da2a41a80faea6700d9f5846c5da6", size = 121020 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/48/7e/3a64597054a70f7c86eb0a7d4fc315b8c1ab932f64883a297bdffeb5f967/more_itertools-10.5.0-py3-none-any.whl", hash = "sha256:037b0d3203ce90cca8ab1defbbdac29d5f993fc20131f3664dc8d6acfa872aef", size = 60952 },
]
[[package]]
name = "more-itertools"
version = "10.6.0"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version >= '3.9'",
]
sdist = { url = "https://files.pythonhosted.org/packages/88/3b/7fa1fe835e2e93fd6d7b52b2f95ae810cf5ba133e1845f726f5a992d62c2/more-itertools-10.6.0.tar.gz", hash = "sha256:2cd7fad1009c31cc9fb6a035108509e6547547a7a738374f10bd49a09eb3ee3b", size = 125009 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/23/62/0fe302c6d1be1c777cab0616e6302478251dfbf9055ad426f5d0def75c89/more_itertools-10.6.0-py3-none-any.whl", hash = "sha256:6eb054cb4b6db1473f6e15fcc676a08e4732548acd47c708f0e179c2c7c01e89", size = 63038 },
]
[[package]]
name = "packaging"
version = "24.2"
......@@ -89,7 +108,8 @@ dependencies = [
{ name = "atomicwrites", marker = "sys_platform == 'win32'" },
{ name = "attrs" },
{ name = "colorama", marker = "sys_platform == 'win32'" },
{ name = "more-itertools" },
{ name = "more-itertools", version = "10.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" },
{ name = "more-itertools", version = "10.6.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" },
{ name = "packaging" },
{ name = "pluggy" },
{ name = "py" },
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment