xref: /openbmc/pyphosphor/obmc/utils/pathtree.py (revision c31ae3b0b8cbe39f37ec1d14c2420b4fa3e4b43e)
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