Source code for dk.collections.pset

# -*- coding: utf-8 -*-

"""Mapping classes.
"""
from __future__ import absolute_import
from collections import namedtuple
import six


keyval = namedtuple('keyval', 'key val')


[docs]def xmlrepr(v, toplevel=False): "Return ``v`` as xml tag-soup." if toplevel: return '<?xml version="1.0" standalone="yes"?>\n' + xmlrepr(v) if hasattr(v, '__xml__'): return v.__xml__() if isinstance(v, list): res = [] for item in v: if hasattr(item, '__xml__'): res.append(xmlrepr(item)) else: res.append('<item>%s</item>' % xmlrepr(item)) return '<list>%s</list>' % (''.join(res)) return str(v)
########################################################################
[docs]class pset(dict): """This code is placed in the Public Domain, or released under the wtfpl (http://sam.zoy.org/wtfpl/COPYING) wherever PD is problematic. Property Set class. A property set is an object where values are attached to attributes, but can still be iterated over as key/value pairs. The order of assignment is maintained during iteration. Only one value allowed per key. >>> x = pset() >>> x.a = 42 >>> x.b = 'foo' >>> x.a = 314 >>> x pset(a=314, b='foo') """ def __init__(self, items=(), **attrs): object.__setattr__(self, '_order', []) super(pset, self).__init__() for k, v in self._get_iterator(items): self._add(k, v) for k, v in attrs.items(): self._add(k, v) def __json__(self): return dict(self.items()) def _add(self, key, value): "Add key->value to client vars." if type(key) in six.integer_types: key = self._order[key] elif key not in self._order: self._order.append(key) dict.__setitem__(self, key, value)
[docs] def apply(self, fn): "Apply function ``fn`` to all values in self." object.__setattr__(self, '_order', []) for k, v in self: self[k] = fn(v)
[docs] def remove(self, key): "Remove key from client vars." if type(key) in six.integer_types: key = self._order[key] del self._order[key] elif key in self._order: self._order.remove(key) dict.__delitem__(self, key)
def __eq__(self, other): """Equal iff they have the same set of keys, and the values for each key is equal. Key order is not considered for equality. """ if other is None: return False if set(self._order) == set(other._order): # pylint: disable=W0212 for key in self._order: if self[key] != other[key]: return False return True return False def _get_iterator(self, val): if not val: return [] if isinstance(val, pset): return val elif isinstance(val, dict): return val.items() else: return val def __iadd__(self, other): for k, v in self._get_iterator(other): self._add(k, v) return self def __add__(self, other): "self + other" tmp = self.__class__() tmp += self tmp += other return tmp def __radd__(self, other): "other + self" tmp = self.__class__() for k, v in other.items(): tmp[k] = v tmp += self return tmp def __neg__(self): "Reverse keys and values." return self.__class__((v, k) for (k, v) in self.items()) def _name(self): if 'name' in self: return self.name else: return self.__class__.__name__ def __xml__(self): res = ['<%s>' % self._name()] for k, v in self: if k != 'name': res.append('<%s>%s</%s>' % (k, xmlrepr(v), k)) res.append('</%s>' % self._name()) return ''.join(res) def __unicode__(self): vals = [] for k, v in self: if k != 'name': try: vals.append(u'%s=%s' % (k, repr(v))) except: vals.append(u'%s=UNPRINTABLE' % k) vals = u', '.join(vals) return u'%s(%s)' % (self._name(), vals) def __repr__(self): return unicode(self).encode('utf-8') # .encode('ascii', 'ignore')
[docs] def pprint(self, indent=0, tab=' ', seen=None): "Pretty print the pset, indented." if seen is None: seen = [self] if indent == 0: six.print_('{|') indent += 1 for key in self.keys(): six.print_(tab * indent, key, '=',) val = self[key] if isinstance(val, pset): six.print_('{|') if val in seen: six.print_(tab * (1 + indent), '...') six.print_(tab * indent, '|}') else: val.pprint(indent, tab, seen) elif isinstance(val, list): six.print_('[') for item in val: if isinstance(item, pset): six.print_(tab * (indent + 1), '{|') if item in seen: six.print_('...') else: item.pprint(indent + 1, tab) else: six.print_(tab * (indent + 1), item) six.print_(tab * indent, ']') else: six.print_(val) indent -= 1 six.print_(tab * indent, '|}')
def __getattr__(self, key): if not super(pset, self).__contains__(key): raise AttributeError(key) return dict.get(self, key) def __getitem__(self, key): if type(key) in six.integer_types: key = self._order[key] return dict.get(self, key) def __delattr__(self, key): if key in self: self.remove(key) def __delitem__(self, key): if key in self: self.remove(key) __str__ = __repr__ def __iter__(self): return ((k, dict.get(self, k)) for k in self._order)
[docs] def items(self): return iter(self)
[docs] def values(self): return [dict.get(self, k) for k in self._order]
[docs] def keys(self): return self._order
def __setattr__(self, key, val): #assert key not in self._reserved, key if key.startswith('_'): object.__setattr__(self, key, val) else: self._add(key, val) def __setitem__(self, key, val): self._add(key, val)
[docs]class defset(pset): "pset with default value." def __init__(self, defval): object.__setattr__(self, '_defval', defval) super(defset, self).__init__() def __getattr__(self, key): if key not in self: self[key] = self._defval() return dict.get(self, key) def __getitem__(self, key): if key not in self: self[key] = self._defval() return dict.get(self, key) def _add(self, key, value): if key not in self._order: self._order.append(key) dict.__setitem__(self, key, value)
[docs]class record(pset): # pylint:disable=R0904 """A property set with commit, rollback, and encoding translation. """ @property def fields(self): "Verbose name of all fields." return [k.title() for k in self._order]
[docs] def strvals(self, empty='', none='NULL', encoding='u8'): "Return a list of all values, formatted for human consumption." def cnvt(v): "Convert ``v`` to a human readable format." if v is None: res = none # from outer scope parameters elif isinstance(v, str): res = v elif isinstance(v, unicode): res = v.encode(encoding) elif hasattr(v, 'strfmt'): if hasattr(v, 'minute'): res = v.strfmt('%d.%m.%Y %H:%M') else: res = v.strfmt('%d.%m.%Y') elif v == '': res = empty else: res = str(v) return res return [cnvt(self[f]) for f in self._order]
[docs] def commit(self): "Copy current state to ``self._history``" self._history = pset() # pylint:disable=W0201 for f in self._order: self._history[f] = self[f] return self
[docs] def rollback(self): "Copy snapshot from ``self._history`` into self." if not hasattr(self, '_history'): raise ValueError('Record has no history.') self.clear() # dict.clear() del self._order[:] for k, v in self._history: self[k] = v return self
[docs] def changed(self): "Return list of fields that have changed since last commit." if not hasattr(self, '_history'): raise ValueError('Record has no history.') return [k for k in self._order if self[k] != self._history[k]]
[docs] def trans(self, source='iso-8859-1', dest='utf-8'): "Translate encoding." self.decode(source) self.encode(dest) return self
[docs] def decode(self, encoding): "Decode using ``encoding``." def decodeval(v): "Helper function to decode value ``v``." if type(v) is str: return v.decode(encoding) else: return v neworder = [] for k in self._order: newval = decodeval(self.get(k)) newkey = decodeval(k) neworder.append(newkey) dict.__delitem__(self, k) dict.__setitem__(self, newkey, newval) self._order = neworder return self
[docs] def encode(self, encoding): "Encode using ``encoding``." def encodeval(v): "Helper function to encode value ``v``." if type(v) is unicode: return v.encode(encoding) else: return v neworder = [] for k in self._order: newval = encodeval(self.get(k)) newkey = encodeval(k) neworder.append(newkey) dict.__delitem__(self, k) dict.__setitem__(self, newkey, newval) self._order = neworder # pylint:disable=W0201 return self
[docs]def test_pset(): """ Unit tests... >>> request = pset(REQUEST={}, META={}, path='/', user=None, session={}, method='GET', ... COOKIES={}, LANGUAGE_CODE='no') >>> p = page(request) >>> p.forms = 'fruit' >>> p.forms.foo = 'bar' >>> print p.forms.foo bar >>> p.forms.fob = 'baz' >>> print p.forms.fob baz >>> x = pset() >>> x.a Traceback (most recent call last): ... AttributeError: a >>> y = pset(a=1, b=2, c=3) >>> y.a 1 >>> y.b 2 >>> y.c 3 >>> z = pset() >>> z.a = 1 >>> z.b = 2 >>> z.c = 3 >>> z[1] 2 >>> z pset(a=1, b=2, c=3) >>> class Point(pset): pass >>> p = Point(x=11, y=22) >>> p Point(y=22, x=11) """ import doctest # pylint:disable=W0404 doctest.testmod()