Source code for lifesaver.config

# encoding: utf-8

import collections
import inspect
import typing

from ruamel.yaml import YAML

from lifesaver.errors import LifesaverError
from lifesaver.utils import merge_dicts


class ConfigError(LifesaverError):
    """An error thrown by the Config loader."""


[docs]class Config: """A dict-like object that encompasses a configuration of some kind. All config files use YAML_ for markup. .. _YAML: https://en.wikipedia.org/wiki/YAML """ def __init__(self, data: dict) -> None: """Create a new Config instance. Parameters ---------- data The configuration data. """ self._load_data(data) def _load_data(self, data): # Grab the type hints as defined in the class. # (Can't do it on the instance, or else it doesn't traverse the MRO.) hints = typing.get_type_hints(self.__class__) for name, hint in hints.items(): # The default value is already set (it's the class attribute!) default_value = getattr(self, name, None) value = data.get(name, default_value) if inspect.isclass(hint) and issubclass(hint, Config): # Load a nested config using the provided inner mapping, or fall # back to an empty dict to use the nested config's defaults. value = hint(value or {}) if isinstance(default_value, collections.abc.Mapping) and isinstance(value, dict): # Merge the provided dict into the default mapping instead of overwriting. value = merge_dicts(default_value, value) setattr(self, name, value)
[docs] @classmethod def load(cls, path: str) -> 'Config': """Creates a Config instance from a file path. Parameters ---------- path A path to a YAML_ file. """ with open(path, 'r') as fp: yaml = fp.read() return cls(YAML().load(yaml))