Source code for deepr.config.macros

"""Helpers for macros"""

import logging
from typing import Dict, Any, List, Tuple


LOGGER = logging.getLogger(__name__)

MACRO = "$"


[docs]def fill_macros(item: Any, macros: Dict[str, Dict[str, Any]] = None) -> Any: """Create item whose macro params present in macros are filled. Returns a new dictionary, tuple or list or item depending on item's type. String params that use the macro syntax "$macro:param" are replaced by the relevant entry from macros (`macros[macro][param]`) ONLY IF FOUND. WARNING: No exception is raised if a macro value is not found in `macros`. Use :func:`~assert_no_macros` to check that a config contains no macro parameters. Parameters ---------- item : Any Item to filled macros : Dict[str, Dict[str, Any]], optional Dictionary of macros Returns ------- item """ if not macros: return item if ismacro(item): macro, param = get_macro_and_param(item) return macros.get(macro, {}).get(param, item) if isinstance(item, dict): return {key: fill_macros(value, macros) for key, value in item.items()} if isinstance(item, list): return [fill_macros(it, macros) for it in item] if isinstance(item, tuple): return tuple(fill_macros(it, macros) for it in item) return item
[docs]def find_macro_params(item: Any, macro: str) -> List[str]: """Find macro params in item""" if ismacro(item): item_macro, param = get_macro_and_param(item) if item_macro == macro: return [param] if isinstance(item, dict): return find_macro_params(list(item.values()), macro) if isinstance(item, tuple): return find_macro_params(list(item), macro) if isinstance(item, list): found = [] for it in item: found.extend(find_macro_params(it, macro)) return found return []
[docs]def ismacro(item) -> bool: """True if item is a string that looks like '$macro:param'.""" if isinstance(item, str) and item.startswith(MACRO): if len(item.split(":")) != 2: raise ValueError(f"Found unexpected macro {item}, format should be '$macro:name'") return True return False
[docs]def get_macro_and_param(item: str) -> Tuple[str, str]: """Return name of the macro and param for the item. Example ------- >>> from deepr.config import get_macro_and_param >>> get_macro_and_param("$macro:param") ('macro', 'param') """ macro, param = item[len(MACRO) :].split(":") return macro, param
[docs]def assert_no_macros(item: Any): """Raises a ValueError if item has macro parameters. Parameters ---------- item : Any Item to be checked Raises ------ ValueError If any parameter if a macro parameter. """ if ismacro(item): raise ValueError(f"Item {item} is a macro value.") if isinstance(item, dict): for it in item.values(): assert_no_macros(it) if isinstance(item, (list, tuple)): for it in item: assert_no_macros(it)
[docs]def macros_eval_order(macros: Dict = None) -> List[str]: """Resolve order of macros evaluation to account for inter macros. Parameters ---------- macros : Dict, optional Dictionary of macros Returns ------- List[str] """ if macros is None: return [] # Resolve dependencies between macros deps = dict() for name, params in macros.items(): parents = set() for param in params.values(): if ismacro(param): macro, _ = get_macro_and_param(param) parents.add(macro) deps[name] = parents # Resolve evaluation order using dependencies order = [] # type: List[str] def _add(macro: str, stack: Tuple[str, ...] = ()): if macro in order: return if macro in stack: cycle = " -> ".join(list(stack) + [macro]) raise ValueError(f"Unable to resolve order of macro evaluation (cycle: {cycle}, dependencies: {deps})") if macro not in deps: raise ValueError(f"Missing macro: {macro}, mentioned by : {' -> '.join(stack)})") for parent in deps[macro]: _add(parent, (*stack, macro)) order.append(macro) for macro in macros: _add(macro) # Macros should appear once and only once if len(order) != len(macros) or set(order) != set(macros): raise ValueError(f"Order {order} incoherent with macros {macros.keys()}") return order