import logging import os from shutil import copy from typing import Any, final import tomllib @final class Config: """Provides a wrapper for toml config files.""" def __init__(self, file_name: str, sample_file_name: str): """Read in a config file and provide it as a :obj:`Config` object. Args: file_name (str): Path to a TOML config file. sample_file_name (str): Path to a sample TOML config file. Used if :param:`file_name` does not exist. """ self.file_name = file_name """str: Path to a TOML config file.""" self.sample_file_name = sample_file_name """str: Path to a sample TOML config file. Used if :attr:`file_name` does not exist.""" self._toml_data = {} """dict: TOML data, parsed to a nested dict.""" try: with open(str(self.file_name), "rb") as f: self._toml_data = tomllib.load(f) except OSError as error: file_name = copy(str(self.sample_file_name), str(self.file_name)) logging.getLogger().warning( "Could not read config file %r, reason: %r", os.path.realpath(file_name), error, ) def get(self, key: str, default: Any = None): """Recursively calls `get(key, default)` to search nested dictionary. Examples: Fetch a nested configuration value. >>> config = Config("config.toml", "config-sample.toml") >>> config.get("web.run", False) True Returns: Any: Value at key if found, provided default or None otherwise. """ nested_key: list[str] = key.split(".") current_item: Any = self._toml_data # Traverse the dict for every depth of the key the user provided. # If any part of the key does not exist, bail and return default. for part in nested_key: if part not in current_item: return default current_item = current_item[part] # No parts left; `current_item` now holds the requested value. return current_item