Source code for logquacious.cascading_config

from collections import deque

from ._compat import Mapping
from .utils import is_sequence


[docs]class CascadingConfig(Mapping): """Cascading configuration values. This class allows you to define parameter names that can match exactly, but if it doesn't, parameter names will be searched as defined by `cascade_map`. `cascade_map` basically defines edges of a dependency graph, which is then used for a breadth-first search of parameter values. Parameters ---------- config_values : dict or list of (key, value) pairs Default values for a configuration, where keys are the parameter names and values are the associated value. cascade_map : dict Dictionary defining cascading defaults. If a parameter name is not found, indexing `cascade_map` with the parameter name will return the parameter to look for. kwargs : dict Keyword arguments for initializing dict. """ def __init__(self, config_values=None, cascade_map=None): if config_values is None: config_values = {} self._config_values = config_values.copy() if cascade_map is None: cascade_map = {} self.cascade_map = cascade_map.copy() def __getitem__(self, key): return self._config_values[key] def __iter__(self): return self._config_values.__iter__() def __len__(self): return len(self._config_values)
[docs] def get(self, name, default=None, _prev=None): """Return best matching config value for `name`. Get value from configuration. The search for `name` is in the following order: - `self` (Value in global configuration) - `default` - Alternate name specified by `self.cascade_map` This method supports the pattern commonly used for optional keyword arguments to a function. For example:: >>> def print_value(key, **kwargs): ... print(kwargs.get(key, 0)) >>> print_value('a') 0 >>> print_value('a', a=1) 1 Instead, you would create a config class and write:: >>> config = CascadingConfig({'a': 0}) >>> def print_value(key, **kwargs): ... print(kwargs.get(key, config.get(key))) >>> print_value('a') 0 >>> print_value('a', a=1) 1 >>> print_value('b') None >>> config.cascade_map['b'] = 'a' >>> print_value('b') 0 See examples below for a demonstration of the cascading of configuration names. Parameters ---------- name : str Name of config value you want. default : object Default value if name doesn't exist in instance. Examples -------- >>> config = CascadingConfig({'size': 0}, ... cascade_map={'arrow.size': 'size'}) >>> config.get('size') 0 >>> top_choice = {'size': 1} >>> top_choice.get('size', config.get('size')) 1 >>> config.get('non-existent', 'unknown') 'unknown' >>> config.get('arrow.size') 0 >>> config.get('arrow.size', 2) 2 >>> top_choice.get('size', config.get('arrow.size')) 1 """ if name in self: return self._config_values[name] elif default is not None: return default elif name not in self.cascade_map: return None else: for name in self._iter_names(name): if name in self: return self._config_values[name]
[docs] def cascade_list(self, name): """Return list of cascade hierarchy for a given configuration name.""" return list(self._iter_names(name))
[docs] def cascade_path(self, name): """Return string of describing cascade.""" return ' -> '.join(self.cascade_list(name))
def __missing__(self, name): return None def _iter_names(self, name): visited = set() q = deque() def not_visited(key): return key not in visited and key not in q def update_queue(name): if name in self.cascade_map: children = self.cascade_map[name] if is_sequence(children): q.extend(filter(not_visited, children)) elif not_visited(children): q.append(children) q.append(name) while q: name = q.popleft() yield name visited.add(name) update_queue(name)