Source code for rizemind.configuration.toml_config
import os
from functools import reduce
from pathlib import Path
from typing import Any, cast
import tomli
[docs]
def replace_env_vars(obj: dict[str, Any] | str) -> dict[str, Any] | str:
if isinstance(obj, str):
# Replace placeholders with environment variable values
return os.path.expandvars(obj)
elif isinstance(obj, dict):
return {key: replace_env_vars(value) for key, value in obj.items()}
elif isinstance(obj, list):
return [replace_env_vars(item) for item in obj]
return obj
[docs]
class TomlConfig:
"""Load and manage configuration from a TOML file.
On initialization, the file is parsed and all string values containing
environment variable placeholders are expanded.
Attributes:
path: Path to the TOML configuration file.
Examples:
>>> # Example TOML Configuration File:
>>> # [database]
>>> # host = "127.0.0.1"
>>> # port = 5432
>>> # [api]
>>> # key = "$ENV_API_KEY"
>>> config = TomlConfig("config.toml")
>>> database_host = config.get("database.host", "localhost")
>>> api_key = config.get(["api", "key"], "default_key")
"""
def __init__(self, path: str | Path):
"""Initialize a configuration instance for the given TOML file.
Args:
path: Path to the TOML configuration file.
Raises:
FileNotFoundError: If the file does not exist.
ValueError: If the path is not a regular file.
"""
self.path = Path(path)
self._validate_path()
self._data = self._load_toml()
def _validate_path(self):
"""Validate that the configuration path exists and is a file.
Raises:
FileNotFoundError: If the config file was not found.
ValueError: If the provided path is not a file.
"""
if not self.path.exists():
raise FileNotFoundError(f"Config file not found: {self.path}")
if not self.path.is_file():
raise ValueError(f"Provided path is not a file: {self.path}")
def _load_toml(self) -> dict:
"""Load the TOML file and expand environment variables in strings.
Returns:
A dictionary containing the parsed configuration.
"""
with self.path.open("rb") as f:
return cast(dict, replace_env_vars(tomli.load(f)))
@property
def data(self) -> dict:
"""The loaded configuration data as a dictionary."""
return self._data
[docs]
def get(self, keys: list[str] | str, default: Any | None = None) -> Any:
"""Retrieve a nested configuration value.
Args:
keys: A dot-delimited string path (for example, ``"a.b.c"``) or a
list of path segments (for example, ``["a", "b", "c"]``).
default: Value to return if the path does not exist.
Returns:
The value at the given key path, or `default` if the path is not
present.
"""
if isinstance(keys, str):
keys = keys.split(".")
return reduce(
lambda d, key: d.get(key, default) if isinstance(d, dict) else default,
keys,
self._data,
)