Source code for pimpmyclass.dictprops

# -*- coding: utf-8 -*-
"""
    pimpmyclass.dictprops
    ~~~~~~~~~~~~~~~~~~~~~

    DictProperty is a property that behaves like a dictionary.

    Wrapped get methods must have one arguments: key
    Wrapped set methods must have one arguments: key and value

    :copyright: 2019 by pimpmyclass Authors, see AUTHORS for more details.
    :license: BSD, see LICENSE for more details.
"""

import enum

from .helpers import missingdict, require, DictPropertyNameKey
from .mixins import CacheMixin, ObservableMixin
from .props import NamedProperty


[docs]class DictProperty(NamedProperty): _subproperties = None _subproperty_init = NamedProperty def __init__(self, *args, **kwargs): self.keys = kwargs.pop('keys', None) self._subproperties = {} super().__init__(*args, **kwargs) self._kwargs['keys'] = self.keys def __get__(self, instance, owner=None): if instance is None: return self return BoundedDictProperty(self, instance) def __set__(self, instance, value): if not isinstance(value, dict): raise AttributeError('The dictionary property (%s) cannot be set to a type %s ' 'You probably want to do something like:' 'obj.prop[index] = value or obj.prop = dict-like' % (self.name, type(value))) for key, value in value.items(): self.setitem(instance, key, value) def __delete__(self, instance): raise AttributeError('{} is a permanent feat of {}'.format(self.name, instance.__class__.__name__)) def build_subproperty(self, key, fget, fset, instance=None): p = self._subproperty_init( fget=fget, fset=fset ) return p def subproperty(self, instance, key): if key not in self._subproperties: p = self.build_subproperty(key, lambda s: self.fget(s, key) if self.fget is not None else None, lambda s, v: self.fset(s, key, v) if self.fset is not None else None, instance) assert isinstance(p, NamedProperty) p.__set_name__(instance.__class__, DictPropertyNameKey(self.name, key)) self._subproperties[key] = p return self._subproperties[key] def getitem(self, instance, key): return self.subproperty(instance, key).__get__(instance) def setitem(self, instance, key, value): return self.subproperty(instance, key).__set__(instance, value) def delitem(self, instance, key): return self.subproperty(instance, key).__delete__(instance) def getall(self, instance): return {key: self.getitem(instance, key) for key in self._subproperties.keys()}
class BoundedDictProperty: """Helper class to provide indexed access to DictFeat. """ def __init__(self, dictfeat, instance): self.instance = instance self.df = dictfeat def _get_key_val(self, key): keys = self.df.keys if keys is None: return key if isinstance(keys, enum.EnumMeta): if isinstance(key, enum.Enum): return key.value elif isinstance(key, str): try: key = keys[key] except KeyError: raise KeyError('{} is not valid key for {} {}'.format(key, self.df.name, keys)) return key.value else: raise KeyError('{} is not valid key for {} {}'.format(key, self.df.name, keys)) elif isinstance(keys, dict): try: key = keys[key] except KeyError: raise KeyError('{} is not valid key for {} {}'.format(key, self.df.name, keys)) elif isinstance(keys, (set, list, tuple)): if key not in keys: raise KeyError('{} is not valid key for {} {}'.format(key, self.df.name, keys)) return key def __getitem__(self, key): if self.df.fget is None: raise AttributeError('{} is a read-only feat'.format(self.df.name)) key = self._get_key_val(key) return DictProperty.getitem(self.df, self.instance, key) def __setitem__(self, key, value): if self.df.fset is None: raise AttributeError('{} is a write-only feat'.format(self.df.name)) key = self._get_key_val(key) DictProperty.setitem(self.df, self.instance, key, value) def __delete__(self, instance): if self.df.fdel: raise AttributeError('{} is a permanent feat of {}'.format(self.df.name, instance.__class__.__name__)) def __repr__(self): return '%r.%s[]' % (self.instance, self.df.name) def __getattr__(self, item): return getattr(self.df, item)
[docs]class DictCacheProperty(DictProperty): def __set_name__(self, owner, name): require(self, owner, name, CacheMixin) super().__set_name__(owner, name) def recall(self, instance): grab = {key: prop.recall(instance) for key, prop in self._subproperties.items()} return missingdict(instance._cache_unset_value, grab) def store(self, instance, value): for name, prop in self._subproperties.items(): prop.store(instance, value[name]) def invalidate_cache(self, instance): for _, prop in self._subproperties.items(): prop.invalidate_cache(instance)
[docs]class DictObservableProperty(DictCacheProperty): def __set_name__(self, owner, name): require(self, owner, name, CacheMixin, ObservableMixin) setattr(owner, name + '_changed', owner._observer_signal_init()) super().__set_name__(owner, name)
# Signals are emitted by subproperties.