Source code for indra.util.nested_dict

from __future__ import absolute_import, print_function, unicode_literals
from builtins import dict, str


[docs]class NestedDict(dict): """A dict-like object that recursively populates elements of a dict. More specifically, this acts like a recursive defaultdict, allowing, for example: >> nd = NestedDict() >> nd['a']['b']['c'] = 'foo' In addition, useful methods have been defined that allow the user to search the data structure. Note that the are not particularly optimized methods at this time. However, for convenience, you can for example simply call `get_path` to get the path to a particular key: >> nd.get_path('c') (('a', 'b', 'c'), 'foo') and the value at that key. Similarly: >> nd.get_path('b') (('a', 'b'), NestedDict( 'c': 'foo' )) `get`, `gets`, and `get_paths` operate on similar principles, and are documented below. """ def __getitem__(self, key): if key not in self.keys(): val = self.__class__() self.__setitem__(key, val) else: val = dict.__getitem__(self, key) return val def __repr__(self): sub_str = dict.__repr__(self)[1:-1] if not sub_str: return self.__class__.__name__ + '()' # This does not completely generalize, but it works for most cases. for old, new in [('), ', '),\n'), ('\n', '\n ')]: sub_str = sub_str.replace(old, new) return'%s(\n %s\n)' % (self.__class__.__name__, sub_str) def __str__(self): return self.__repr__()
[docs] def export_dict(self): "Convert this into an ordinary dict (of dicts)." return {k: v.export_dict() if isinstance(v, self.__class__) else v for k, v in self.items()}
[docs] def get(self, key): "Find the first value within the tree which has the key." if key in self.keys(): return self[key] else: res = None for v in self.values(): # This could get weird if the actual expected returned value # is None, especially in teh case of overlap. Any ambiguity # would be resolved by get_path(s). if hasattr(v, 'get'): res = v.get(key) if res is not None: break return res
[docs] def get_path(self, key): "Like `get`, but also return the path taken to the value." if key in self.keys(): return (key,), self[key] else: key_path, res = (None, None) for sub_key, v in self.items(): if isinstance(v, self.__class__): key_path, res = v.get_path(key) elif hasattr(v, 'get'): res = v.get(key) key_path = (key,) if res is not None else None if res is not None and key_path is not None: key_path = (sub_key,) + key_path break return key_path, res
[docs] def gets(self, key): "Like `get`, but return all matches, not just the first." result_list = [] if key in self.keys(): result_list.append(self[key]) for v in self.values(): if isinstance(v, self.__class__): sub_res_list = v.gets(key) for res in sub_res_list: result_list.append(res) elif isinstance(v, dict): if key in v.keys(): result_list.append(v[key]) return result_list
[docs] def get_paths(self, key): "Like `gets`, but include the paths, like `get_path` for all matches." result_list = [] if key in self.keys(): result_list.append(((key,), self[key])) for sub_key, v in self.items(): if isinstance(v, self.__class__): sub_res_list = v.get_paths(key) for key_path, res in sub_res_list: result_list.append(((sub_key,) + key_path, res)) elif isinstance(v, dict): if key in v.keys(): result_list.append(((sub_key, key), v[key])) return result_list
[docs] def get_leaves(self): """Get the deepest entries as a flat set.""" ret_set = set() for val in self.values(): if isinstance(val, self.__class__): ret_set |= val.get_leaves() elif isinstance(val, dict): ret_set |= set(val.values()) elif isinstance(val, list): ret_set |= set(val) elif isinstance(val, set): ret_set |= val else: ret_set.add(val) return ret_set