blob: cc8793fba0f2e8e217eb7b39daed6c10eb3aa8f2 [file] [log] [blame]
#
# Copyright (C) 2015 The Android Open Source Project
#
# 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.
#
"""Properties base classes and interfaces."""
import string
class PropEntry(object):
"""Leaf node in the property tree representing the final value."""
def __init__(self, name, full_key, legal_values, setter, getter):
self._name = name
self._full_key = full_key
self._legal_values = legal_values
self._getter = getter
self._setter = setter
def name(self):
return self._name
def get(self):
return self._getter(self._full_key)
def set(self, val):
if self._legal_values and val not in self._legal_values:
raise ValueError(
'Cannot set {} to {} (legal values are {}).'.format(
self._full_key, val, self._legal_values))
return self._setter(self._full_key, val)
def __repr__(self):
return 'PropEntry(%s)' % self._name
def __str__(self):
return 'PropEntry(name=%s)' % self._name
class PropGroup(object):
"""Group of groups or entries used by PropBase"""
def __init__(self, name, parent):
self._name = name
self._parent = parent
self._children = []
def __setattr__(self, key, val):
if key.startswith('_'):
super(PropGroup, self).__setattr__(key, val)
else:
if self.has_child(key):
c = self.child(key)
if type(c) == PropEntry:
return c.set(val)
p = self._parent
prefix = self.name()
while type(p) == PropGroup:
if p.name() != 'root':
prefix = string.join([p.name(), prefix], '.')
p = p.parent()
raise AttributeError(
"AttributeError: '{}' object has no attribute '{}'".format(
type(p), string.join([prefix, key], '.')))
def __getattr__(self, key):
if key.startswith('_'):
return super(PropGroup, self).__getattr__(key)
entry = string.split(key, '/')
if self.has_child(entry[0]):
c = self.child(entry[0])
if type(c) == PropEntry:
return c.get()
return c
p = self._parent
prefix = self._name
while type(p) == PropGroup:
if p.name() != 'root':
prefix = string.join([p.name(), prefix], '.')
p = p.parent()
raise AttributeError(
"AttributeError: '{}' object has no attribute '{}'".format(
type(p), string.join([prefix, key], '.')))
def name(self):
return self._name
def parent(self):
return self._parent
def children(self):
return self._children
def add_child(self, child):
self._children.append(child)
def child(self, child_name):
for c in self._children:
if c.name() == child_name:
return c
raise KeyError, child_name
def has_child(self, child_name):
for c in self._children:
if c.name() == child_name:
return True
return False
def __repr__(self):
parent = None
if type(self._parent) == PropGroup:
parent = self._parent.name()
return "{{ name: '{}', parent: '{}', children: {} }}".format(
self._name, parent, self._children)
def __str__(self):
return 'PropGroup(name="%s")' % self._name
# pylint: disable=abstract-class-not-used
class PropBase(object):
"""PropBase - makes a list of path-like properties into attr interface
This class provides a base class for creating arbitrary tree structures of
properties using a path-like definition. One can set DESIRED_PROPS and
REQUIRED_PROPS, as well as enable or disable CACHING. Only two methods need
to be overriden: _save, and _load. For instance,
class PropDict(PropBase):
REQUIRED_PROPS = { 'editor/config/vim': [],
'editor/config/emacs': [],
'current_editor' : ['emacs', 'vim']
}
CACHING = True
def __init__(self):
self._d = {}
super(PropDict, self).__init__()
def _save(self, key, value):
self._d[key] = value
def _load(self, key):
if not self._d.has_key(key):
return ''
return self._d[key]
Usage then follows:
p = PropDict()
p.editor.config.vim = '$HOME/.vimrc'
p.current_editor = 'vim'
print p.editor.config.emacs
Note! No properties may start with '_'.
"""
# Fields of the store that can be accessed as properties.
# { 'field_name': [], # Contains anything
# 'restricted_field' : ['1', '0'] } # Only '0' or '1'
REQUIRED_PROPS = {}
OPTIONAL_PROPS = {}
CACHING = False
def __init__(self):
self._root = self.create_prop_tree()
self._cache = {}
def complete(self):
"""Returns true if all expected properties are set."""
for p in self.REQUIRED_PROPS.keys():
# We use getattr instead of _load so that results get cached.
if getattr(self, p) is None:
return False
return True
@classmethod
def properties(cls):
"""Returns a dict of valid item keys and lists of validation values.
Keys must be bare, e.g., 'foo', or a valid relative path, e.g.,
'foo/bar/baz'. Any intermediate values cannot contain values.
"""
d = cls.OPTIONAL_PROPS.copy()
d.update(cls.REQUIRED_PROPS)
return d
def _load(self, key):
"""Returns the correct value for the given |key|."""
raise NotImplementedError
def _save(self, key, val):
"""Sets the backing for the |key| to the given |val|.
Returns:
The saved value, in case any manipultions were made.
"""
raise NotImplementedError
def __getattr__(self, key):
if key.startswith('_'):
return super(PropBase, self).__getattr__(key)
entry = string.split(key, '/')
if self._root.has_child(entry[0]):
c = self._root.child(entry[0])
if type(c) == PropEntry:
result = None
# If not self.CACHING, self._cache is empty so this is false.
if c in self._cache:
result = self._cache[c]
else:
result = c.get()
# For space efficiency, only cache if desired.
if self.CACHING:
self._cache[c] = result
return result
return c
raise AttributeError(
"AttributeError: '%s' object has no attribute '%s'" % (type(self),
key))
def __setattr__(self, key, val):
if key.startswith('_'):
super(PropBase, self).__setattr__(key, val)
else:
if self._root.has_child(key):
c = self._root.child(key)
if type(c) == PropEntry:
result = self._cache.get(c)
if val != result:
result = c.set(val)
if self.CACHING:
self._cache[c] = result
return result
raise AttributeError(
"AttributeError: '%s' object has no attribute '%s'" % (
type(self), key))
def create_prop_tree(self):
"""Creates a tree of PropGroups and PropEntrys from properties()"""
root = PropGroup('root', self)
for k, v in self.properties().iteritems():
entry = string.split(k, '/')
entry.reverse()
component = root
prefix = ''
while len(entry) > 1:
name = entry.pop()
prefix = string.join([prefix, name], '.')
if not component.has_child(name):
component.add_child(PropGroup(name, component))
component = component.child(name)
# leaf
component.add_child(PropEntry(entry[0],
k, v,
self._save, self._load))
return root
def dict(self):
"""Returns a fully populated a dict"""
d = {}
for p in self.properties():
d[p] = self._load(p)
return d