blob: b24c162ac2ff0e4cc6333b9ca5e53e1df45f1210 [file] [log] [blame]
# Contributors Listed Below - COPYRIGHT 2016
# [+] International Business Machines Corp.
#
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied. See the License for the specific language governing
# permissions and limitations under the License.
class PathTreeItemIterator(object):
def __init__(self, path_tree, subtree, depth):
self.path_tree = path_tree
self.path = []
self.itlist = []
self.subtree = ['/'] + list(filter(bool, subtree.split('/')))
self.depth = depth
d = path_tree.root
for k in self.subtree:
try:
d = d[k]['children']
except KeyError:
raise KeyError(subtree)
# TODO: openbmc/openbmc#2994 remove python 2 support
try: # python 2
self.it = d.iteritems()
except AttributeError: # python 3
self.it = iter(d.items())
def __iter__(self):
return self
# TODO: openbmc/openbmc#2994 remove python 2 support
# python 2
def next(self):
key, value = self._next()
path = self.subtree[0] + '/'.join(self.subtree[1:] + self.path)
return path, value.get('data')
# python 3
import sys
if sys.version_info[0] > 2:
__next__ = next
def _next(self):
try:
while True:
x = next(self.it)
if self.depth:
if len(self.path) + 1 > self.depth:
continue
self.itlist.append(self.it)
self.path.append(x[0])
# TODO: openbmc/openbmc#2994 remove python 2 support
try: # python 2
self.it = x[1]['children'].iteritems()
except AttributeError: # python 3
self.it = iter(x[1]['children'].items())
break
except StopIteration:
if not self.itlist:
raise StopIteration
self.it = self.itlist.pop()
self.path.pop()
x = self._next()
return x
class PathTreeKeyIterator(PathTreeItemIterator):
def __init__(self, path_tree, subtree, depth):
super(PathTreeKeyIterator, self).__init__(path_tree, subtree, depth)
# TODO: openbmc/openbmc#2994 remove python 2 support
# python 2
def next(self):
return super(PathTreeKeyIterator, self).next()[0]
# python 3
import sys
if sys.version_info[0] > 2:
__next__ = next
class PathTree:
def __init__(self):
self.root = {}
self.cache = {}
def _try_delete_parent(self, elements):
if len(elements) == 1:
return False
kids = 'children'
elements.pop()
d = self.root
for k in elements[:-1]:
d = d[k][kids]
if 'data' not in d[elements[-1]] and not d[elements[-1]][kids]:
del d[elements[-1]]
self._try_delete_parent(elements)
def _get_node(self, key):
kids = 'children'
elements = ['/'] + list(filter(bool, key.split('/')))
d = self.root
for k in elements[:-1]:
try:
d = d[k][kids]
except KeyError:
raise KeyError(key)
return d[elements[-1]]
def __iter__(self):
return PathTreeItemIterator(self, '/', None)
def __missing__(self, key):
for x in self.iterkeys():
if key == x:
return False
return True
def __delitem__(self, key):
del self.cache[key]
kids = 'children'
elements = ['/'] + list(filter(bool, key.split('/')))
d = self.root
for k in elements[:-1]:
try:
d = d[k][kids]
except KeyError:
raise KeyError(key)
del d[elements[-1]]
self._try_delete_parent(elements)
def __setitem__(self, key, value):
self.cache[key] = value
kids = 'children'
elements = ['/'] + list(filter(bool, key.split('/')))
d = self.root
for k in elements[:-1]:
d = d.setdefault(k, {kids: {}})[kids]
children = d.setdefault(elements[-1], {kids: {}})[kids]
d[elements[-1]].update({kids: children, 'data': value})
def __getitem__(self, key):
if key in self.cache:
return self.cache[key]
return self._get_node(key).get('data')
def setdefault(self, key, default):
if not self.get(key):
self.__setitem__(key, default)
return self.__getitem__(key)
def get(self, key, default=None):
try:
x = self.__getitem__(key)
except KeyError:
x = default
return x
def get_children(self, key):
return [x for x in self._get_node(key)['children'].keys()]
def demote(self, key):
n = self._get_node(key)
if 'data' in n:
del n['data']
def keys(self, subtree='/', depth=None):
return [x for x in self.iterkeys(subtree, depth)]
def values(self, subtree='/', depth=None):
return [x[1] for x in self.iteritems(subtree, depth)]
def items(self, subtree='/', depth=None):
return [x for x in self.iteritems(subtree, depth)]
def dataitems(self, subtree='/', depth=None):
# dataitems() must return an iterable object containing all of the
# items explicitly inserted into the tree, rooted at subtree with
# depth number of path elements from the subtree root.
#
# Calling dataitems() to request all of the populated entries is
# unfortunately common, and it's (also) unfortunately expensive to
# generate (done by iterating over the entire tree). However, given we
# have a flat dict whose job is to cache the explicitly inserted values
# we can leverage that to provide a cheap-to-calculate answer to
# requests for the entire set of populated entries
if subtree == '/' and not depth:
return self.cache.items()
return [x for x in self.iteritems(subtree, depth)
if x[1] is not None]
def iterkeys(self, subtree='/', depth=None):
if not self.root:
# TODO: openbmc/openbmc#2994 remove python 2 support
try: # python 2
return {}.iterkeys()
except AttributeError: # python 3
return iter({}.keys())
return PathTreeKeyIterator(self, subtree, depth)
def iteritems(self, subtree='/', depth=None):
if not self.root:
# TODO: openbmc/openbmc#2994 remove python 2 support
try: # python 2
return {}.iteritems()
except AttributeError: # python 3
return iter({}.items())
return PathTreeItemIterator(self, subtree, depth)
def dumpd(self, subtree='/'):
result = {}
d = result
for k, v in self.iteritems(subtree):
elements = ['/'] + list(filter(bool, k.split('/')))
d = result
for k in elements:
d = d.setdefault(k, {})
if v is not None:
d.update(v)
return result