Source code for deepr.config.base

"""Evaluate objects from arbitrary nested dictionaries."""

import functools
import logging
from typing import Dict, Any

from deepr.config.macros import fill_macros, find_macro_params, assert_no_macros, macros_eval_order
from deepr.config.references import fill_references, default_references


LOGGER = logging.getLogger(__name__)

TYPE = "type"

EVAL = "eval"

CALL = "call"

PARTIAL = "partial"

POSITIONAL = "*"


[docs]def parse_config(config: Dict, macros: Dict = None) -> Dict: """Fill macro parameters and references in config from macros. Example ------- >>> from deepr.config import parse_config >>> config = {"x": "$params:x", "y": 2} >>> macros = {"params": {"x": 1}} >>> parse_config(config, macros) {'x': 1, 'y': 2} Parameters ---------- config : Dict Config dictionary macros : Dict, optional Dictionary of macro parameters. Returns ------- Dict Parsed Config, without macro parameters and references. Raises ------ ValueError If some macro parameter in config not found in macros. If some references not found. """ # Evaluate macros in order to account for inter macro params if macros is not None: macros_eval = dict() # type: ignore for macro in macros_eval_order(macros): macro_eval = fill_macros(macros[macro], macros_eval) assert_no_macros(macro_eval) macros_eval[macro] = from_config(macro_eval) else: macros_eval = None # type: ignore # Fill macros and references in config, evaluate and return config_no_macro = fill_macros(config, macros_eval) assert_no_macros(config_no_macro) references = default_references(config=config, macros=macros, macros_eval=macros_eval) parsed = fill_references(config_no_macro, references) # Look for macro params that were not used if macros_eval is not None: for macro, params in macros_eval.items(): used = find_macro_params({"config": config, "macros": macros}, macro) for param in set(params) - set(used): LOGGER.warning(f"- MACRO PARAM NOT USED: macro = '{macro}', param = '{param}'") return parsed
[docs]def from_config(item: Any) -> Any: """Instantiate item from config. Raises ------ ValueError If item is not a valid config (unexpected eval method) """ if isinstance(item, dict): # Get eval_mode from item, default is EVAL_MODE_INSTANCE mode = item.get(EVAL, CALL) params = {key: value for key, value in item.items() if key != EVAL} # Return evaluated class or function with provided arguments if mode == CALL: if TYPE in params: cls_or_fn = _import(params[TYPE]) args = from_config(params.get(POSITIONAL)) kwargs = {key: from_config(value) for key, value in params.items() if key not in {TYPE, POSITIONAL}} try: return cls_or_fn(*args, **kwargs) if args else cls_or_fn(**kwargs) except (ValueError, TypeError) as e: raise type(e)(f"Error while calling {cls_or_fn})") from e else: return {key: from_config(value) for key, value in params.items()} # Return partial class or function with provided arguments if mode == PARTIAL: if TYPE in params: cls_or_fn = _import(params[TYPE]) args = from_config(params.get(POSITIONAL)) kwargs = {key: from_config(value) for key, value in params.items() if key not in {TYPE, POSITIONAL}} return functools.partial(cls_or_fn, *args, **kwargs) if args else functools.partial(cls_or_fn, **kwargs) else: instantiated = from_config(params) def _partial(**kwargs): return {**instantiated, **kwargs} return _partial # Return raw dictionary if mode is None: return params raise ValueError(f"Unexpected evaluation mode: '{mode}' in item {item}") if isinstance(item, list): return [from_config(it) for it in item] if isinstance(item, tuple): return tuple(from_config(it) for it in item) return item
def _import(import_str: str): """Import class using import string""" parts = import_str.split(".") module = ".".join(parts[:-1]) m = __import__(module) for comp in parts[1:]: m = getattr(m, comp) return m