Source code for resoterre.config_utils

"""Utilities for handling configuration classes."""

from datetime import datetime
from pathlib import Path
from typing import Any

from dacite import Config, from_dict

from resoterre.io_utils import get_yaml_dict


default_dacite_config = Config(type_hooks={Path: Path, datetime: datetime.fromisoformat, tuple[str, ...]: tuple})

known_configs: dict[str, Any] = {}


[docs] def register_config(name: str, known_configs_dict: dict[str, Any] | None = None, overwrite: bool = False) -> Any: """ Decorator to register a configuration class with a given name. Parameters ---------- name : str The name to register the configuration class under. known_configs_dict : dict, optional The dictionary to register the configuration class in. Defaults to the global known_configs. overwrite : bool Whether overwriting an existing configuration with the same name is allowed. Returns ------- decorator A decorator that registers the configuration class. """ if known_configs_dict is None: known_configs_dict = known_configs def decorator(cls: Any) -> Any: """ Decorator function to register the class. Parameters ---------- cls : type The configuration class to register. Returns ------- cls The original class, unmodified. """ if (name in known_configs_dict) and not overwrite: raise ValueError(f"Config with name '{name}' already exists.") known_configs_dict[name] = cls return cls return decorator
[docs] def assign_custom_class_to_config_dict( config_dict: dict[Any, Any], known_custom_config_dict: dict[str, Any] | None = None, type_key: str = "type" ) -> None: """ Recursively assign custom classes to a configuration dictionary. Parameters ---------- config_dict : dict The configuration dictionary to process. known_custom_config_dict : dict, optional A dictionary mapping names to configuration classes. Defaults to the global known_configs. type_key : str The key in the configuration dictionary that indicates the class name to assign. Returns ------- None The function modifies the config_dict in place. """ known_custom_config_dict = known_custom_config_dict or known_configs # Currently only supports nested dict, could consider supporting lists and tuples... for key, value in config_dict.items(): if isinstance(value, dict): if type_key in value: cls = None if value[type_key] in known_custom_config_dict: cls = known_custom_config_dict[value[type_key]] if cls is None: raise ValueError( f"Unknown config type '{value[type_key]}' for key '{key}'. " f"Known types: {list(known_custom_config_dict.keys())}" ) config_dict[key] = from_dict( cls, {k: v for k, v in value.items() if k != type_key}, config=default_dacite_config, ) else: assign_custom_class_to_config_dict( value, known_custom_config_dict=known_custom_config_dict, type_key=type_key )
[docs] def config_from_yaml( config_cls: Any, yaml_obj: dict[str, Any] | Path | str, known_custom_config_dict: dict[str, Any] | None = None, type_key: str = "type", ) -> Any: """ Create a configuration object from a YAML object, handling custom classes. Parameters ---------- config_cls : type The configuration class to instantiate. yaml_obj : dict | Path | str The YAML object to convert. known_custom_config_dict : dict, optional A dictionary mapping names to configuration classes. Defaults to the global known_configs. type_key : str The key in the configuration dictionary that indicates the class name to assign. Returns ------- config_cls An instance of the configuration class populated from the YAML object. """ config_dict = get_yaml_dict(yaml_obj) assign_custom_class_to_config_dict( config_dict, known_custom_config_dict=known_custom_config_dict, type_key=type_key ) return from_dict(config_cls, config_dict, config=default_dacite_config)