Skip to content

Mdtc

A model-driven singleton for storing TOML or another dict-based configuration.

Defines a Config class which is to be used for inheriting from. In order to maintain a single configuration instance across your entire application state, this class will enforce a singleton pattern which ensures that multiple instantiations of the parent class which inherits from Config will result in the same class instance.

Moreover, it allows the user to define the structure of their configuration via models, which, out of the box - can be classes, dataclasses or Pydantic models. Practically anything that can be instantiated and implements attribute getters and setters should work as a configuration definition.

Config

Bases: Singleton

Ensures that only on instance of your configuration is present in your application.

At the same time, it enforces a pattern of defining your configuration via model-driven approach, where each key is a pre-defined and pre-typed model of your configuration.

Source code in mdtc/__init__.py
class Config(Singleton):
    """
    Ensures that only on instance of your configuration is present in your application.

    At the same time, it enforces a pattern of defining your configuration via model-driven
    approach, where each key is a pre-defined and pre-typed model of your configuration.
    """

    __isfrozen: bool = False

    def __init__(self, config_object: dict[str, Any]) -> None:
        """
        Initialise the configuration class using a file path and models.

        Args:
            config_object (dict[str, Any]): The config dict (`toml` or other).

        Raises:
            ConfigAttributeError: Raised when a model `_name` does not match the attribute
            defined inside your configuration class.
            ConfigKeyNotFoundError: Raised when a `_key` defined in the model is not found
            in the configuration object.
        """
        # Because defining a model in the config for typing purposes does not define a
        # default value (and should not!), we use `get_type_hints` to retrieve this
        # from the child class and check against what the model would have defined.

        try:
            annotations = get_type_hints(self)
        except TypeError:
            raise Exception("No configs defined!")

        bases = [(name, cls_) for name, cls_ in annotations.items() if self.__implements_cfg(cls_)]

        if not bases:
            raise Exception("Configuration empty!")

        for name, base in bases:
            if name != base._name:
                raise ConfigAttributeError(
                    f"The model - `{base.__name__}` says it's confobj key name is - `{base._name}`,"
                    + f" but it has been declared as `{name}` inside `{self.__class__.__name__}`!"
                )

            if not (conf_dict := self.__get_cfg(base._key, config_object)):
                raise ConfigKeyNotFoundError(
                    f"The model - `{base.__name__}` asked to load a key - `{base._key}`"
                    + " however the configuration does not contain such a key!"
                )

            # Let the model throw own error on instantiation..
            self.__setattr__(base._name, base(**conf_dict))

        # Freeze the class instance
        self.__isfrozen = True

    def __setattr__(self, attr: str, value: Any) -> None:
        if self.__isfrozen:
            raise FrozenConfigException("Can't mutate the config!")
        super().__setattr__(attr, value)

    @staticmethod
    def __implements_cfg(class_: Any) -> bool:
        """Check if a given class uses `_key` and `_name` attributes as this class expects."""
        return hasattr(class_, "_key") and hasattr(class_, "_name")

    @staticmethod
    def __get_cfg(key: str, cfg: dict[str, Any]) -> Any:
        """
        Deep "get" from a n-depth dictionary using a TOML notation key ([A.B..]).

        Args:
            key (str): The TOML key for where the configuration is housed.
            cfg (dict[str, Any]): The dictionary passed in to the config class.

        Returns:
            Any: An applicable ANY-type value or None if key is not found.
        """

        reducer: Callable[..., Any] = lambda dict_, key: dict_.get(key) if dict_ else None
        return reduce(reducer, key.split("."), cfg)

    def __repr__(self) -> str:
        return f"<{self.__class__.__name__}(hash={self.__hash__()})>"

__get_cfg(key, cfg) staticmethod

Deep "get" from a n-depth dictionary using a TOML notation key ([A.B..]).

Parameters:

Name Type Description Default
key str

The TOML key for where the configuration is housed.

required
cfg dict[str, Any]

The dictionary passed in to the config class.

required

Returns:

Name Type Description
Any Any

An applicable ANY-type value or None if key is not found.

Source code in mdtc/__init__.py
@staticmethod
def __get_cfg(key: str, cfg: dict[str, Any]) -> Any:
    """
    Deep "get" from a n-depth dictionary using a TOML notation key ([A.B..]).

    Args:
        key (str): The TOML key for where the configuration is housed.
        cfg (dict[str, Any]): The dictionary passed in to the config class.

    Returns:
        Any: An applicable ANY-type value or None if key is not found.
    """

    reducer: Callable[..., Any] = lambda dict_, key: dict_.get(key) if dict_ else None
    return reduce(reducer, key.split("."), cfg)

__implements_cfg(class_) staticmethod

Check if a given class uses _key and _name attributes as this class expects.

Source code in mdtc/__init__.py
@staticmethod
def __implements_cfg(class_: Any) -> bool:
    """Check if a given class uses `_key` and `_name` attributes as this class expects."""
    return hasattr(class_, "_key") and hasattr(class_, "_name")

__init__(config_object)

Initialise the configuration class using a file path and models.

Parameters:

Name Type Description Default
config_object dict[str, Any]

The config dict (toml or other).

required

Raises:

Type Description
ConfigAttributeError

Raised when a model _name does not match the attribute

ConfigKeyNotFoundError

Raised when a _key defined in the model is not found

Source code in mdtc/__init__.py
def __init__(self, config_object: dict[str, Any]) -> None:
    """
    Initialise the configuration class using a file path and models.

    Args:
        config_object (dict[str, Any]): The config dict (`toml` or other).

    Raises:
        ConfigAttributeError: Raised when a model `_name` does not match the attribute
        defined inside your configuration class.
        ConfigKeyNotFoundError: Raised when a `_key` defined in the model is not found
        in the configuration object.
    """
    # Because defining a model in the config for typing purposes does not define a
    # default value (and should not!), we use `get_type_hints` to retrieve this
    # from the child class and check against what the model would have defined.

    try:
        annotations = get_type_hints(self)
    except TypeError:
        raise Exception("No configs defined!")

    bases = [(name, cls_) for name, cls_ in annotations.items() if self.__implements_cfg(cls_)]

    if not bases:
        raise Exception("Configuration empty!")

    for name, base in bases:
        if name != base._name:
            raise ConfigAttributeError(
                f"The model - `{base.__name__}` says it's confobj key name is - `{base._name}`,"
                + f" but it has been declared as `{name}` inside `{self.__class__.__name__}`!"
            )

        if not (conf_dict := self.__get_cfg(base._key, config_object)):
            raise ConfigKeyNotFoundError(
                f"The model - `{base.__name__}` asked to load a key - `{base._key}`"
                + " however the configuration does not contain such a key!"
            )

        # Let the model throw own error on instantiation..
        self.__setattr__(base._name, base(**conf_dict))

    # Freeze the class instance
    self.__isfrozen = True