Source code for typed_settings.dict_utils

"""
Utility functions for working settings dicts and serilizing nested settings.
"""

from typing import Any, Generator, Sequence, Tuple

from .types import (
    LoadedSettings,
    LoadedValue,
    MergedSettings,
    OptionList,
    SettingsDict,
)


__all__ = [
    "iter_settings",
    "get_path",
    "set_path",
    "merge_settings",
    "update_settings",
    "flat2nested",
]


[docs] def iter_settings( dct: SettingsDict, options: OptionList ) -> Generator[Tuple[str, Any], None, None]: """ Iterate over the (possibly nested) options dict *dct* and yield *(option_path, value)* tuples. Args: dct: The dict of settings as returned by a loader. options: The list of all available options for a settings class. Return: A generator yield *(opton_path, value)* tuples. """ for option in options: try: yield option.path, get_path(dct, option.path) except KeyError: continue
[docs] def get_path(dct: SettingsDict, path: str) -> Any: """ Performs a nested dict lookup for *path* and returns the result. Calling ``get_path(dct, "a.b")`` is equivalent to ``dict["a"]["b"]``. Args: dct: The source dict path: The path to look up. It consists of the dot-separated nested keys. Returns: The looked up value. Raises: KeyError: if a key in *path* does not exist. """ for part in path.split("."): dct = dct[part] return dct
[docs] def set_path(dct: SettingsDict, path: str, val: Any) -> None: """ Sets a value to a nested dict and automatically creates missing dicts should they not exist. Calling ``set_path(dct, "a.b", 3)`` is equivalent to ``dict["a"]["b"] = 3``. Args: dct: The dict that should contain the value path: The (nested) path, a dot-separated concatenation of keys. val: The value to set """ *parts, key = path.split(".") for part in parts: dct = dct.setdefault(part, {}) dct[key] = val
[docs] def merge_settings( options: OptionList, settings: Sequence[LoadedSettings] ) -> MergedSettings: """ Merge a sequence of settings dicts to a flat dict that maps option paths to the corresponding option values. Args: options: The list of all available options. settings: A sequence of loaded settings. Return: A dict that maps option paths to :class:`.LoadedValue` instances. The simplified input settings look like this:: [ ("loader a", {"spam": 1, "eggs": True}), ("loader b", {"spam": 2, "nested": {"x": "test"}}), ] The simpliefied output looks like this:: { "spam": ("loader b", 2), "eggs": ("loader a", True), "nested.x": ("loader b", "test"), } """ rsettings = settings[::-1] merged_settings: MergedSettings = {} for option_info in options: for loaded_settings in rsettings: try: value = get_path(loaded_settings.settings, option_info.path) except KeyError: pass else: merged_settings[option_info.path] = LoadedValue( value, loaded_settings.meta ) break return merged_settings
[docs] def update_settings( merged_settings: MergedSettings, settings: SettingsDict ) -> MergedSettings: """ Return a copy of *merged_settings* updated with the values from *settings*. The loader meta data is not changed. Args: merged_settings: The merged settnigs dict to be updated. settings: The settings dict with additional values. Return: A copy of the input merged settings updated with the values from *settings*. """ updated: MergedSettings = {} for path, (value, meta) in merged_settings.items(): try: value = get_path(settings, path) except KeyError: pass updated[path] = LoadedValue(value, meta) return updated
[docs] def flat2nested(merged_settings: MergedSettings) -> SettingsDict: """ Convert the flat *merged_settings* to a nested settings dict. """ settings: SettingsDict = {} for path, loaded_value in merged_settings.items(): set_path(settings, path, loaded_value.value) return settings