| # |
| # 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 |