root/masterdriverz/snakeoil-formatters/snakeoil/mappings.py @ masterdriverz%2540gentoo.org-20070511172237-409tafi6loyxfmkz

Revision masterdriverz%2540gentoo.org-20070511172237-409tafi6loyxfmkz, 15.2 kB (checked in by Charlie Shepherd <masterdriverz@…>, 20 months ago)

Collapse multiple definitions

Line 
1# Copyright: 2005-2007 Brian Harring <ferringb@gmail.com>
2# License: GPL2
3
4"""
5miscellanious mapping/dict related classes
6"""
7
8import operator
9from itertools import imap, chain, ifilterfalse, izip
10from snakeoil.klass import get, contains
11from collections import deque
12
13
14class DictMixin(object):
15    """
16    new style class replacement for L{UserDict.DictMixin}
17    designed around iter* methods rather then forcing lists as DictMixin does
18    """
19
20    __slots__ = ()
21
22    __externally_mutable__ = True
23
24    def __init__(self, iterable=None, **kwargs):
25        if iterable is not None:
26            self.update(iterable)
27
28        if kwargs:
29            self.update(kwargs.iteritems())
30
31    def __iter__(self):
32        return self.iterkeys()
33
34    def keys(self):
35        return list(self.iterkeys())
36
37    def values(self):
38        return list(self.itervalues())
39
40    def items(self):
41        return list(self.iteritems())
42
43    def update(self, iterable):
44        for k, v in iterable:
45            self[k] = v
46
47    get = get
48    __contains__ = contains
49
50    # default cmp actually operates based on key len comparison, oddly enough
51    def __cmp__(self, other):
52        for k1, k2 in izip(sorted(self), sorted(other)):
53            c = cmp(k1, k2)
54            if c != 0:
55                return c
56            c = cmp(self[k1], other[k2])
57            if c != 0:
58                return c
59        c = cmp(len(self), len(other))
60        return c
61
62    def __eq__(self, other):
63        return self.__cmp__(other) == 0
64
65    def __ne__(self, other):
66        return self.__cmp__(other) != 0
67
68    def pop(self, key, default=None):
69        if not self.__externally_mutable__:
70            raise AttributeError(self, "pop")
71        try:
72            val = self[key]
73            del self[key]
74        except KeyError:
75            if default is not None:
76                return default
77            raise
78        return val
79
80    def setdefault(self, key, default=None):
81        if not self.__externally_mutable__:
82            raise AttributeError(self, "setdefault")
83        if key in self:
84            return self[key]
85        self[key] = default
86        return default
87
88    def has_key(self, key):
89        return key in self
90
91    def iterkeys(self):
92        raise NotImplementedError(self, "iterkeys")
93
94    def itervalues(self):
95        return imap(self.__getitem__, self)
96
97    def iteritems(self):
98        for k in self:
99            yield k, self[k]
100
101    def __getitem__(self, key):
102        raise NotImplementedError(self, "__getitem__")
103
104    def __setitem__(self, key, val):
105        if not self.__externally_mutable__:
106            raise AttributeError(self, "__setitem__")
107        raise NotImplementedError(self, "__setitem__")
108
109    def __delitem__(self, key):
110        if not self.__externally_mutable__:
111            raise AttributeError(self, "__delitem__")
112        raise NotImplementedError(self, "__delitem__")
113
114    def clear(self):
115        if not self.__externally_mutable__:
116            raise AttributeError(self, "clear")
117        # crappy, override if faster method exists.
118        map(self.__delitem__, self.keys())
119
120    def __len__(self):
121        c = 0
122        for x in self:
123            c += 1
124        return c
125
126    def popitem(self):
127        if not self.__externally_mutable__:
128            raise AttributeError(self, "popitem")
129        # do it this way so python handles the stopiteration; faster
130        for key, val in self.iteritems():
131            del self[key]
132            return key, val
133        raise KeyError("container is empty")
134
135
136class LazyValDict(DictMixin):
137
138    """
139    Mapping that loads values via a callable
140
141    given a function to get keys, and to look up the val for those keys, it'll
142    lazily load key definitions and values as requested
143    """
144    __slots__ = ("_keys", "_keys_func", "_vals", "_val_func")
145    __externally_mutable__ = False
146
147    def __init__(self, get_keys_func, get_val_func):
148        """
149        @param get_keys_func: either a container, or func to call to get keys.
150        @param get_val_func: a callable that is JIT called
151            with the key requested.
152        """
153        if not callable(get_val_func):
154            raise TypeError("get_val_func isn't a callable")
155        if hasattr(get_keys_func, "__iter__"):
156            self._keys = get_keys_func
157            self._keys_func = None
158        else:
159            if not callable(get_keys_func):
160                raise TypeError(
161                    "get_keys_func isn't iterable or callable")
162            self._keys_func = get_keys_func
163        self._val_func = get_val_func
164        self._vals = {}
165
166    def __getitem__(self, key):
167        if self._keys_func is not None:
168            self._keys = set(self._keys_func())
169            self._keys_func = None
170        if key in self._vals:
171            return self._vals[key]
172        if key in self._keys:
173            v = self._vals[key] = self._val_func(key)
174            return v
175        raise KeyError(key)
176
177    def keys(self):
178        if self._keys_func is not None:
179            self._keys = set(self._keys_func())
180            self._keys_func = None
181        return list(self._keys)
182
183    def iterkeys(self):
184        if self._keys_func is not None:
185            self._keys = set(self._keys_func())
186            self._keys_func = None
187        return iter(self._keys)
188
189    def itervalues(self):
190        return imap(self.__getitem__, self.iterkeys())
191
192    def iteritems(self):
193        return ((k, self[k]) for k in self.iterkeys())
194
195    def __contains__(self, key):
196        if self._keys_func is not None:
197            self._keys = set(self._keys_func())
198            self._keys_func = None
199        return key in self._keys
200
201    def __len__(self):
202        if self._keys_func is not None:
203            self._keys = set(self._keys_func())
204            self._keys_func = None
205        return len(self._keys)
206
207
208class LazyFullValLoadDict(LazyValDict):
209
210    __slots__ = ()
211
212    def __getitem__(self, key):
213        if self._keys_func is not None:
214            self._keys = set(self._keys_func())
215            self._keys_func = None
216        if key in self._vals:
217            return self._vals[key]
218        if key in self._keys:
219            if self._val_func is not None:
220                self._vals.update(self._val_func(self._keys))
221                return self._vals[key]
222        raise KeyError(key)
223
224
225class ProtectedDict(DictMixin):
226
227    """
228    Mapping wrapper storing changes to a dict without modifying the original.
229
230    Changes are stored in a secondary dict, protecting the underlying
231    mapping from changes.
232    """
233
234    __slots__ = ("orig", "new", "blacklist")
235
236    def __init__(self, orig):
237        self.orig = orig
238        self.new = {}
239        self.blacklist = {}
240
241    def __setitem__(self, key, val):
242        self.new[key] = val
243        if key in self.blacklist:
244            del self.blacklist[key]
245
246    def __getitem__(self, key):
247        if key in self.new:
248            return self.new[key]
249        if key in self.blacklist:
250            raise KeyError(key)
251        return self.orig[key]
252
253    def __delitem__(self, key):
254        if key in self.new:
255            del self.new[key]
256            self.blacklist[key] = True
257            return
258        elif key in self.orig:
259            if key not in self.blacklist:
260                self.blacklist[key] = True
261                return
262        raise KeyError(key)
263
264    def iterkeys(self):
265        for k in self.new.iterkeys():
266            yield k
267        for k in self.orig.iterkeys():
268            if k not in self.blacklist and k not in self.new:
269                yield k
270
271    def __contains__(self, key):
272        return key in self.new or (key not in self.blacklist and
273                                   key in self.orig)
274
275
276class ImmutableDict(dict):
277
278    """Immutable Dict, non changable after instantiating"""
279
280    _hash_key_grabber = operator.itemgetter(0)
281
282    def __delitem__(self, *args):
283        raise TypeError("non modifiable")
284
285    __setitem__ = clear = update = pop = popitem = setdefault = __delitem__
286
287    def __hash__(self):
288        k = self.items()
289        k.sort(key=self._hash_key_grabber)
290        return hash(tuple(k))
291
292    __delattr__ = __setitem__
293    __setattr__ = __setitem__
294
295
296class IndeterminantDict(object):
297
298    """A wrapped dict with constant defaults, and a function for other keys."""
299
300    __slots__ = ("__initial", "__pull")
301
302    def __init__(self, pull_func, starter_dict=None):
303        object.__init__(self)
304        if starter_dict is None:
305            self.__initial = {}
306        else:
307            self.__initial = starter_dict
308        self.__pull = pull_func
309
310    def __getitem__(self, key):
311        if key in self.__initial:
312            return self.__initial[key]
313        else:
314            return self.__pull(key)
315
316    def get(self, key, val=None):
317        try:
318            return self[key]
319        except KeyError:
320            return val
321
322    def __hash__(self):
323        raise TypeError("non hashable")
324
325    def __delitem__(self, *args):
326        raise TypeError("non modifiable")
327
328    pop = get
329
330    clear = update = popitem = setdefault = __setitem__ = __delitem__
331    __iter__ = keys = values = items = __len__ = __delitem__
332    iteritems = iterkeys = itervalues = __delitem__
333
334
335class StackedDict(DictMixin):
336
337    """A non modifiable dict that makes multiple dicts appear as one"""
338
339    def __init__(self, *dicts):
340        self._dicts = dicts
341
342    def __getitem__(self, key):
343        for x in self._dicts:
344            if key in x:
345                return x[key]
346        raise KeyError(key)
347
348    def iterkeys(self):
349        s = set()
350        for k in ifilterfalse(s.__contains__, chain(*map(iter, self._dicts))):
351            s.add(k)
352            yield k
353
354    def __contains__(self, key):
355        for x in self._dicts:
356            if key in x:
357                return True
358        return False
359
360    def __setitem__(self, *a):
361        raise TypeError("non modifiable")
362
363    __delitem__ = clear = __setitem__
364
365
366class OrderedDict(DictMixin):
367
368    """Dict that preserves insertion ordering which is used for iteration ops"""
369
370    __slots__ = ("_data", "_order")
371
372    def __init__(self, iterable=()):
373        self._order = deque()
374        self._data = {}
375        for k, v in iterable:
376            self[k] = v
377
378    def __setitem__(self, key, val):
379        if key not in self:
380            self._order.append(key)
381        self._data[key] = val
382
383    def __delitem__(self, key):
384        del self._data[key]
385
386        for idx, o in enumerate(self._order):
387            if o == key:
388                del self._order[idx]
389                break
390        else:
391            raise AssertionError("orderdict lost its internal ordering")
392
393    def __getitem__(self, key):
394        return self._data[key]
395
396    def __len__(self):
397        return len(self._order)
398
399    def iterkeys(self):
400        return iter(self._order)
401
402    def clear(self):
403        self._order = deque()
404        self._data = {}
405
406    def __contains__(self, key):
407        return key in self._data
408
409
410class ListBackedDict(DictMixin):
411
412    __slots__ = ("_data")
413    _kls = list
414    _key_grabber = operator.itemgetter(0)
415    _value_grabber = operator.itemgetter(1)
416
417    def __init__(self, iterables=()):
418        self._data = self._kls((k, v) for k, v in iterables)
419
420    def __setitem__(self, key, val):
421        for idx, vals in enumerate(self._data):
422            if vals[0] == key:
423                self._data[idx] = (key, val)
424                break
425        else:
426            self._data.append((key, val))
427
428    def __getitem__(self, key):
429        for existing_key, val in self._data:
430            if key == existing_key:
431                return val
432        raise KeyError(key)
433
434    def __delitem__(self, key):
435        l = self._kls((k, v) for k, v in self._data if k != key)
436        if len(l) == len(self._data):
437            # no match.
438            raise KeyError(key)
439        self._data = l
440
441    def iterkeys(self):
442        return imap(self._key_grabber, self._data)
443
444    def itervalues(self):
445        return imap(self._value_grabber, self._data)
446
447    def iteritems(self):
448        return iter(self._data)
449
450    def __contains__(self, key):
451        for k, v in self._data:
452            if k == key:
453                return True
454        return False
455
456    def __len__(self):
457        return len(self._data)
458
459class TupleBackedDict(ListBackedDict):
460    __slots__ = ()
461    _kls = tuple
462
463    def __setitem__(self, key, val):
464        self._data = self._kls(
465            chain((x for x in self.iteritems() if x[0] != key), ((key, val),)))
466
467
468class PreservingFoldingDict(DictMixin):
469
470    """dict that uses a 'folder' function when looking up keys.
471
472    The most common use for this is to implement a dict with
473    case-insensitive key values (by using C{str.lower} as folder
474    function).
475
476    This version returns the original 'unfolded' key.
477    """
478
479    def __init__(self, folder, sourcedict=None):
480        self._folder = folder
481        # dict mapping folded keys to (original key, value)
482        self._dict = {}
483        if sourcedict is not None:
484            self.update(sourcedict)
485
486    def copy(self):
487        return PreservingFoldingDict(self._folder, self.iteritems())
488
489    def refold(self, folder=None):
490        """Use the remembered original keys to update to a new folder.
491
492        If folder is C{None}, keep the current folding function (this
493        is useful if the folding function uses external data and that
494        data changed).
495        """
496        if folder is not None:
497            self._folder = folder
498        oldDict = self._dict
499        self._dict = {}
500        for key, value in oldDict.itervalues():
501            self._dict[self._folder(key)] = (key, value)
502
503    def __getitem__(self, key):
504        return self._dict[self._folder(key)][1]
505
506    def __setitem__(self, key, value):
507        self._dict[self._folder(key)] = (key, value)
508
509    def __delitem__(self, key):
510        del self._dict[self._folder(key)]
511
512    def iteritems(self):
513        return self._dict.itervalues()
514
515    def iterkeys(self):
516        for val in self._dict.itervalues():
517            yield val[0]
518
519    def itervalues(self):
520        for val in self._dict.itervalues():
521            yield val[1]
522
523    def __contains__(self, key):
524        return self._folder(key) in self._dict
525
526    def __len__(self):
527        return len(self._dict)
528
529    def clear(self):
530        self._dict = {}
531
532
533class NonPreservingFoldingDict(DictMixin):
534
535    """dict that uses a 'folder' function when looking up keys.
536
537    The most common use for this is to implement a dict with
538    case-insensitive key values (by using C{str.lower} as folder
539    function).
540
541    This version returns the 'folded' key.
542    """
543
544    def __init__(self, folder, sourcedict=None):
545        self._folder = folder
546        # dict mapping folded keys to values.
547        self._dict = {}
548        if sourcedict is not None:
549            self.update(sourcedict)
550
551    def copy(self):
552        return NonPreservingFoldingDict(self._folder, self.iteritems())
553
554    def __getitem__(self, key):
555        return self._dict[self._folder(key)]
556
557    def __setitem__(self, key, value):
558        self._dict[self._folder(key)] = value
559
560    def __delitem__(self, key):
561        del self._dict[self._folder(key)]
562
563    def iterkeys(self):
564        return iter(self._dict)
565
566    def itervalues(self):
567        return self._dict.itervalues()
568
569    def iteritems(self):
570        return self._dict.iteritems()
571
572    def __contains__(self, key):
573        return self._folder(key) in self._dict
574
575    def __len__(self):
576        return len(self._dict)
577
578    def clear(self):
579        self._dict = {}
Note: See TracBrowser for help on using the browser.