18ffe1e44SBrad Bishop# Contributors Listed Below - COPYRIGHT 2016 28ffe1e44SBrad Bishop# [+] International Business Machines Corp. 38ffe1e44SBrad Bishop# 48ffe1e44SBrad Bishop# 58ffe1e44SBrad Bishop# Licensed under the Apache License, Version 2.0 (the "License"); 68ffe1e44SBrad Bishop# you may not use this file except in compliance with the License. 78ffe1e44SBrad Bishop# You may obtain a copy of the License at 88ffe1e44SBrad Bishop# 98ffe1e44SBrad Bishop# http://www.apache.org/licenses/LICENSE-2.0 108ffe1e44SBrad Bishop# 118ffe1e44SBrad Bishop# Unless required by applicable law or agreed to in writing, software 128ffe1e44SBrad Bishop# distributed under the License is distributed on an "AS IS" BASIS, 138ffe1e44SBrad Bishop# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148ffe1e44SBrad Bishop# implied. See the License for the specific language governing 158ffe1e44SBrad Bishop# permissions and limitations under the License. 168ffe1e44SBrad Bishop 178ffe1e44SBrad Bishop 188ffe1e44SBrad Bishopclass PathTreeItemIterator(object): 198ffe1e44SBrad Bishop def __init__(self, path_tree, subtree, depth): 208ffe1e44SBrad Bishop self.path_tree = path_tree 218ffe1e44SBrad Bishop self.path = [] 228ffe1e44SBrad Bishop self.itlist = [] 23dc63ed49SCamVan Nguyen self.subtree = ['/'] + list(filter(bool, subtree.split('/'))) 248ffe1e44SBrad Bishop self.depth = depth 258ffe1e44SBrad Bishop d = path_tree.root 268ffe1e44SBrad Bishop for k in self.subtree: 278ffe1e44SBrad Bishop try: 288ffe1e44SBrad Bishop d = d[k]['children'] 298ffe1e44SBrad Bishop except KeyError: 308ffe1e44SBrad Bishop raise KeyError(subtree) 31dc63ed49SCamVan Nguyen # TODO: openbmc/openbmc#2994 remove python 2 support 32dc63ed49SCamVan Nguyen try: # python 2 33d641c086SBrad Bishop self.it = d.iteritems() 34dc63ed49SCamVan Nguyen except AttributeError: # python 3 35dc63ed49SCamVan Nguyen self.it = iter(d.items()) 368ffe1e44SBrad Bishop 378ffe1e44SBrad Bishop def __iter__(self): 388ffe1e44SBrad Bishop return self 398ffe1e44SBrad Bishop 40dc63ed49SCamVan Nguyen # TODO: openbmc/openbmc#2994 remove python 2 support 41dc63ed49SCamVan Nguyen # python 2 42d641c086SBrad Bishop def next(self): 438ffe1e44SBrad Bishop key, value = self._next() 448ffe1e44SBrad Bishop path = self.subtree[0] + '/'.join(self.subtree[1:] + self.path) 458ffe1e44SBrad Bishop return path, value.get('data') 468ffe1e44SBrad Bishop 47dc63ed49SCamVan Nguyen # python 3 48dc63ed49SCamVan Nguyen import sys 49dc63ed49SCamVan Nguyen if sys.version_info[0] > 2: 50dc63ed49SCamVan Nguyen __next__ = next 51dc63ed49SCamVan Nguyen 528ffe1e44SBrad Bishop def _next(self): 538ffe1e44SBrad Bishop try: 548ffe1e44SBrad Bishop while True: 55dc63ed49SCamVan Nguyen x = next(self.it) 56786e6a6cSAndrew Jeffery if self.depth: 57786e6a6cSAndrew Jeffery if len(self.path) + 1 > self.depth: 588ffe1e44SBrad Bishop continue 598ffe1e44SBrad Bishop self.itlist.append(self.it) 608ffe1e44SBrad Bishop self.path.append(x[0]) 61dc63ed49SCamVan Nguyen # TODO: openbmc/openbmc#2994 remove python 2 support 62dc63ed49SCamVan Nguyen try: # python 2 63d641c086SBrad Bishop self.it = x[1]['children'].iteritems() 64dc63ed49SCamVan Nguyen except AttributeError: # python 3 65dc63ed49SCamVan Nguyen self.it = iter(x[1]['children'].items()) 668ffe1e44SBrad Bishop break 678ffe1e44SBrad Bishop 688ffe1e44SBrad Bishop except StopIteration: 698ffe1e44SBrad Bishop if not self.itlist: 708ffe1e44SBrad Bishop raise StopIteration 718ffe1e44SBrad Bishop 728ffe1e44SBrad Bishop self.it = self.itlist.pop() 738ffe1e44SBrad Bishop self.path.pop() 748ffe1e44SBrad Bishop x = self._next() 758ffe1e44SBrad Bishop 768ffe1e44SBrad Bishop return x 778ffe1e44SBrad Bishop 788ffe1e44SBrad Bishop 798ffe1e44SBrad Bishopclass PathTreeKeyIterator(PathTreeItemIterator): 808ffe1e44SBrad Bishop def __init__(self, path_tree, subtree, depth): 818ffe1e44SBrad Bishop super(PathTreeKeyIterator, self).__init__(path_tree, subtree, depth) 828ffe1e44SBrad Bishop 83dc63ed49SCamVan Nguyen # TODO: openbmc/openbmc#2994 remove python 2 support 84dc63ed49SCamVan Nguyen # python 2 85d641c086SBrad Bishop def next(self): 868ffe1e44SBrad Bishop return super(PathTreeKeyIterator, self).next()[0] 878ffe1e44SBrad Bishop 88dc63ed49SCamVan Nguyen # python 3 89dc63ed49SCamVan Nguyen import sys 90dc63ed49SCamVan Nguyen if sys.version_info[0] > 2: 91dc63ed49SCamVan Nguyen __next__ = next 92dc63ed49SCamVan Nguyen 938ffe1e44SBrad Bishop 948ffe1e44SBrad Bishopclass PathTree: 958ffe1e44SBrad Bishop def __init__(self): 968ffe1e44SBrad Bishop self.root = {} 9752aeb314SAndrew Jeffery self.cache = {} 988ffe1e44SBrad Bishop 998ffe1e44SBrad Bishop def _try_delete_parent(self, elements): 1008ffe1e44SBrad Bishop if len(elements) == 1: 1018ffe1e44SBrad Bishop return False 1028ffe1e44SBrad Bishop 1038ffe1e44SBrad Bishop kids = 'children' 1048ffe1e44SBrad Bishop elements.pop() 1058ffe1e44SBrad Bishop d = self.root 1068ffe1e44SBrad Bishop for k in elements[:-1]: 1078ffe1e44SBrad Bishop d = d[k][kids] 1088ffe1e44SBrad Bishop 1098ffe1e44SBrad Bishop if 'data' not in d[elements[-1]] and not d[elements[-1]][kids]: 1108ffe1e44SBrad Bishop del d[elements[-1]] 1118ffe1e44SBrad Bishop self._try_delete_parent(elements) 1128ffe1e44SBrad Bishop 1138ffe1e44SBrad Bishop def _get_node(self, key): 1148ffe1e44SBrad Bishop kids = 'children' 115dc63ed49SCamVan Nguyen elements = ['/'] + list(filter(bool, key.split('/'))) 1168ffe1e44SBrad Bishop d = self.root 1178ffe1e44SBrad Bishop for k in elements[:-1]: 1188ffe1e44SBrad Bishop try: 1198ffe1e44SBrad Bishop d = d[k][kids] 1208ffe1e44SBrad Bishop except KeyError: 1218ffe1e44SBrad Bishop raise KeyError(key) 1228ffe1e44SBrad Bishop 1238ffe1e44SBrad Bishop return d[elements[-1]] 1248ffe1e44SBrad Bishop 1258ffe1e44SBrad Bishop def __iter__(self): 126786e6a6cSAndrew Jeffery return PathTreeItemIterator(self, '/', None) 1278ffe1e44SBrad Bishop 1288ffe1e44SBrad Bishop def __missing__(self, key): 129d641c086SBrad Bishop for x in self.iterkeys(): 1308ffe1e44SBrad Bishop if key == x: 1318ffe1e44SBrad Bishop return False 1328ffe1e44SBrad Bishop return True 1338ffe1e44SBrad Bishop 1348ffe1e44SBrad Bishop def __delitem__(self, key): 13552aeb314SAndrew Jeffery del self.cache[key] 1368ffe1e44SBrad Bishop kids = 'children' 137dc63ed49SCamVan Nguyen elements = ['/'] + list(filter(bool, key.split('/'))) 1388ffe1e44SBrad Bishop d = self.root 1398ffe1e44SBrad Bishop for k in elements[:-1]: 1408ffe1e44SBrad Bishop try: 1418ffe1e44SBrad Bishop d = d[k][kids] 1428ffe1e44SBrad Bishop except KeyError: 1438ffe1e44SBrad Bishop raise KeyError(key) 1448ffe1e44SBrad Bishop 1458ffe1e44SBrad Bishop del d[elements[-1]] 1468ffe1e44SBrad Bishop self._try_delete_parent(elements) 1478ffe1e44SBrad Bishop 1488ffe1e44SBrad Bishop def __setitem__(self, key, value): 14952aeb314SAndrew Jeffery self.cache[key] = value 1508ffe1e44SBrad Bishop kids = 'children' 151dc63ed49SCamVan Nguyen elements = ['/'] + list(filter(bool, key.split('/'))) 1528ffe1e44SBrad Bishop d = self.root 1538ffe1e44SBrad Bishop for k in elements[:-1]: 1548ffe1e44SBrad Bishop d = d.setdefault(k, {kids: {}})[kids] 1558ffe1e44SBrad Bishop 1568ffe1e44SBrad Bishop children = d.setdefault(elements[-1], {kids: {}})[kids] 1578ffe1e44SBrad Bishop d[elements[-1]].update({kids: children, 'data': value}) 1588ffe1e44SBrad Bishop 1598ffe1e44SBrad Bishop def __getitem__(self, key): 160ce2a6f03SAndrew Jeffery if key in self.cache: 16152aeb314SAndrew Jeffery return self.cache[key] 1628ffe1e44SBrad Bishop 163ce2a6f03SAndrew Jeffery return self._get_node(key).get('data') 164ce2a6f03SAndrew Jeffery 1658ffe1e44SBrad Bishop def setdefault(self, key, default): 1668ffe1e44SBrad Bishop if not self.get(key): 1678ffe1e44SBrad Bishop self.__setitem__(key, default) 1688ffe1e44SBrad Bishop 1698ffe1e44SBrad Bishop return self.__getitem__(key) 1708ffe1e44SBrad Bishop 1718ffe1e44SBrad Bishop def get(self, key, default=None): 1728ffe1e44SBrad Bishop try: 1738ffe1e44SBrad Bishop x = self.__getitem__(key) 1748ffe1e44SBrad Bishop except KeyError: 1758ffe1e44SBrad Bishop x = default 1768ffe1e44SBrad Bishop 1778ffe1e44SBrad Bishop return x 1788ffe1e44SBrad Bishop 1798ffe1e44SBrad Bishop def get_children(self, key): 180dc63ed49SCamVan Nguyen return [x for x in self._get_node(key)['children'].keys()] 1818ffe1e44SBrad Bishop 1828ffe1e44SBrad Bishop def demote(self, key): 1838ffe1e44SBrad Bishop n = self._get_node(key) 1848ffe1e44SBrad Bishop if 'data' in n: 1858ffe1e44SBrad Bishop del n['data'] 1868ffe1e44SBrad Bishop 1878ffe1e44SBrad Bishop def keys(self, subtree='/', depth=None): 1888ffe1e44SBrad Bishop return [x for x in self.iterkeys(subtree, depth)] 1898ffe1e44SBrad Bishop 1908ffe1e44SBrad Bishop def values(self, subtree='/', depth=None): 1918ffe1e44SBrad Bishop return [x[1] for x in self.iteritems(subtree, depth)] 1928ffe1e44SBrad Bishop 1938ffe1e44SBrad Bishop def items(self, subtree='/', depth=None): 1948ffe1e44SBrad Bishop return [x for x in self.iteritems(subtree, depth)] 1958ffe1e44SBrad Bishop 1968ffe1e44SBrad Bishop def dataitems(self, subtree='/', depth=None): 197*c31ae3b0SAndrew Jeffery # dataitems() must return an iterable object containing all of the 198*c31ae3b0SAndrew Jeffery # items explicitly inserted into the tree, rooted at subtree with 199*c31ae3b0SAndrew Jeffery # depth number of path elements from the subtree root. 200*c31ae3b0SAndrew Jeffery # 201*c31ae3b0SAndrew Jeffery # Calling dataitems() to request all of the populated entries is 202*c31ae3b0SAndrew Jeffery # unfortunately common, and it's (also) unfortunately expensive to 203*c31ae3b0SAndrew Jeffery # generate (done by iterating over the entire tree). However, given we 204*c31ae3b0SAndrew Jeffery # have a flat dict whose job is to cache the explicitly inserted values 205*c31ae3b0SAndrew Jeffery # we can leverage that to provide a cheap-to-calculate answer to 206*c31ae3b0SAndrew Jeffery # requests for the entire set of populated entries 207*c31ae3b0SAndrew Jeffery if subtree == '/' and not depth: 208*c31ae3b0SAndrew Jeffery return self.cache.items() 209*c31ae3b0SAndrew Jeffery 2108ffe1e44SBrad Bishop return [x for x in self.iteritems(subtree, depth) 2118ffe1e44SBrad Bishop if x[1] is not None] 2128ffe1e44SBrad Bishop 2138ffe1e44SBrad Bishop def iterkeys(self, subtree='/', depth=None): 2148ffe1e44SBrad Bishop if not self.root: 215dc63ed49SCamVan Nguyen # TODO: openbmc/openbmc#2994 remove python 2 support 216dc63ed49SCamVan Nguyen try: # python 2 217d641c086SBrad Bishop return {}.iterkeys() 218dc63ed49SCamVan Nguyen except AttributeError: # python 3 219dc63ed49SCamVan Nguyen return iter({}.keys()) 2208ffe1e44SBrad Bishop return PathTreeKeyIterator(self, subtree, depth) 2218ffe1e44SBrad Bishop 2228ffe1e44SBrad Bishop def iteritems(self, subtree='/', depth=None): 2238ffe1e44SBrad Bishop if not self.root: 224dc63ed49SCamVan Nguyen # TODO: openbmc/openbmc#2994 remove python 2 support 225dc63ed49SCamVan Nguyen try: # python 2 226d641c086SBrad Bishop return {}.iteritems() 227dc63ed49SCamVan Nguyen except AttributeError: # python 3 228dc63ed49SCamVan Nguyen return iter({}.items()) 2298ffe1e44SBrad Bishop return PathTreeItemIterator(self, subtree, depth) 2304e601a0aSBrad Bishop 2314e601a0aSBrad Bishop def dumpd(self, subtree='/'): 2324e601a0aSBrad Bishop result = {} 2334e601a0aSBrad Bishop d = result 2344e601a0aSBrad Bishop 2354e601a0aSBrad Bishop for k, v in self.iteritems(subtree): 236dc63ed49SCamVan Nguyen elements = ['/'] + list(filter(bool, k.split('/'))) 2374e601a0aSBrad Bishop d = result 2384e601a0aSBrad Bishop for k in elements: 2394e601a0aSBrad Bishop d = d.setdefault(k, {}) 2404e601a0aSBrad Bishop if v is not None: 2414e601a0aSBrad Bishop d.update(v) 2424e601a0aSBrad Bishop 2434e601a0aSBrad Bishop return result 244