~ubuntu-branches/debian/jessie/sqlalchemy/jessie

« back to all changes in this revision

Viewing changes to lib/sqlalchemy/util/_collections.py

  • Committer: Bazaar Package Importer
  • Author(s): Piotr Ożarowski
  • Date: 2011-08-01 23:18:16 UTC
  • mfrom: (1.4.15 upstream) (16.1.14 experimental)
  • Revision ID: james.westby@ubuntu.com-20110801231816-6lx797pi3q1fpqst
Tags: 0.7.2-1
* New upstream release
* Bump minimum required python-mako version to 0.4.1 (closes: 635898)

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# util/_collections.py
 
2
# Copyright (C) 2005-2011 the SQLAlchemy authors and contributors <see AUTHORS file>
 
3
#
 
4
# This module is part of SQLAlchemy and is released under
 
5
# the MIT License: http://www.opensource.org/licenses/mit-license.php
 
6
 
 
7
"""Collection classes and helpers."""
 
8
 
 
9
import sys
 
10
import itertools
 
11
import weakref
 
12
import operator
 
13
from langhelpers import symbol
 
14
from compat import time_func, threading
 
15
 
 
16
EMPTY_SET = frozenset()
 
17
 
 
18
 
 
19
class NamedTuple(tuple):
 
20
    """tuple() subclass that adds labeled names.
 
21
 
 
22
    Is also pickleable.
 
23
 
 
24
    """
 
25
 
 
26
    def __new__(cls, vals, labels=None):
 
27
        t = tuple.__new__(cls, vals)
 
28
        if labels:
 
29
            t.__dict__.update(zip(labels, vals))
 
30
            t._labels = labels
 
31
        return t
 
32
 
 
33
    def keys(self):
 
34
        return [l for l in self._labels if l is not None]
 
35
 
 
36
class ImmutableContainer(object):
 
37
    def _immutable(self, *arg, **kw):
 
38
        raise TypeError("%s object is immutable" % self.__class__.__name__)
 
39
 
 
40
    __delitem__ = __setitem__ = __setattr__ = _immutable
 
41
 
 
42
class immutabledict(ImmutableContainer, dict):
 
43
 
 
44
    clear = pop = popitem = setdefault = \
 
45
        update = ImmutableContainer._immutable
 
46
 
 
47
    def __new__(cls, *args):
 
48
        new = dict.__new__(cls)
 
49
        dict.__init__(new, *args)
 
50
        return new
 
51
 
 
52
    def __init__(self, *args):
 
53
        pass
 
54
 
 
55
    def __reduce__(self):
 
56
        return immutabledict, (dict(self), )
 
57
 
 
58
    def union(self, d):
 
59
        if not self:
 
60
            return immutabledict(d)
 
61
        else:
 
62
            d2 = immutabledict(self)
 
63
            dict.update(d2, d)
 
64
            return d2
 
65
 
 
66
    def __repr__(self):
 
67
        return "immutabledict(%s)" % dict.__repr__(self)
 
68
 
 
69
class Properties(object):
 
70
    """Provide a __getattr__/__setattr__ interface over a dict."""
 
71
 
 
72
    def __init__(self, data):
 
73
        self.__dict__['_data'] = data
 
74
 
 
75
    def __len__(self):
 
76
        return len(self._data)
 
77
 
 
78
    def __iter__(self):
 
79
        return self._data.itervalues()
 
80
 
 
81
    def __add__(self, other):
 
82
        return list(self) + list(other)
 
83
 
 
84
    def __setitem__(self, key, object):
 
85
        self._data[key] = object
 
86
 
 
87
    def __getitem__(self, key):
 
88
        return self._data[key]
 
89
 
 
90
    def __delitem__(self, key):
 
91
        del self._data[key]
 
92
 
 
93
    def __setattr__(self, key, object):
 
94
        self._data[key] = object
 
95
 
 
96
    def __getstate__(self):
 
97
        return {'_data': self.__dict__['_data']}
 
98
 
 
99
    def __setstate__(self, state):
 
100
        self.__dict__['_data'] = state['_data']
 
101
 
 
102
    def __getattr__(self, key):
 
103
        try:
 
104
            return self._data[key]
 
105
        except KeyError:
 
106
            raise AttributeError(key)
 
107
 
 
108
    def __contains__(self, key):
 
109
        return key in self._data
 
110
 
 
111
    def as_immutable(self):
 
112
        """Return an immutable proxy for this :class:`.Properties`."""
 
113
 
 
114
        return ImmutableProperties(self._data)
 
115
 
 
116
    def update(self, value):
 
117
        self._data.update(value)
 
118
 
 
119
    def get(self, key, default=None):
 
120
        if key in self:
 
121
            return self[key]
 
122
        else:
 
123
            return default
 
124
 
 
125
    def keys(self):
 
126
        return self._data.keys()
 
127
 
 
128
    def has_key(self, key):
 
129
        return key in self._data
 
130
 
 
131
    def clear(self):
 
132
        self._data.clear()
 
133
 
 
134
class OrderedProperties(Properties):
 
135
    """Provide a __getattr__/__setattr__ interface with an OrderedDict
 
136
    as backing store."""
 
137
    def __init__(self):
 
138
        Properties.__init__(self, OrderedDict())
 
139
 
 
140
 
 
141
class ImmutableProperties(ImmutableContainer, Properties):
 
142
    """Provide immutable dict/object attribute to an underlying dictionary."""
 
143
 
 
144
 
 
145
class OrderedDict(dict):
 
146
    """A dict that returns keys/values/items in the order they were added."""
 
147
 
 
148
    def __init__(self, ____sequence=None, **kwargs):
 
149
        self._list = []
 
150
        if ____sequence is None:
 
151
            if kwargs:
 
152
                self.update(**kwargs)
 
153
        else:
 
154
            self.update(____sequence, **kwargs)
 
155
 
 
156
    def clear(self):
 
157
        self._list = []
 
158
        dict.clear(self)
 
159
 
 
160
    def copy(self):
 
161
        return self.__copy__()
 
162
 
 
163
    def __copy__(self):
 
164
        return OrderedDict(self)
 
165
 
 
166
    def sort(self, *arg, **kw):
 
167
        self._list.sort(*arg, **kw)
 
168
 
 
169
    def update(self, ____sequence=None, **kwargs):
 
170
        if ____sequence is not None:
 
171
            if hasattr(____sequence, 'keys'):
 
172
                for key in ____sequence.keys():
 
173
                    self.__setitem__(key, ____sequence[key])
 
174
            else:
 
175
                for key, value in ____sequence:
 
176
                    self[key] = value
 
177
        if kwargs:
 
178
            self.update(kwargs)
 
179
 
 
180
    def setdefault(self, key, value):
 
181
        if key not in self:
 
182
            self.__setitem__(key, value)
 
183
            return value
 
184
        else:
 
185
            return self.__getitem__(key)
 
186
 
 
187
    def __iter__(self):
 
188
        return iter(self._list)
 
189
 
 
190
    def values(self):
 
191
        return [self[key] for key in self._list]
 
192
 
 
193
    def itervalues(self):
 
194
        return iter([self[key] for key in self._list])
 
195
 
 
196
    def keys(self):
 
197
        return list(self._list)
 
198
 
 
199
    def iterkeys(self):
 
200
        return iter(self.keys())
 
201
 
 
202
    def items(self):
 
203
        return [(key, self[key]) for key in self.keys()]
 
204
 
 
205
    def iteritems(self):
 
206
        return iter(self.items())
 
207
 
 
208
    def __setitem__(self, key, object):
 
209
        if key not in self:
 
210
            try:
 
211
                self._list.append(key)
 
212
            except AttributeError:
 
213
                # work around Python pickle loads() with 
 
214
                # dict subclass (seems to ignore __setstate__?)
 
215
                self._list = [key]
 
216
        dict.__setitem__(self, key, object)
 
217
 
 
218
    def __delitem__(self, key):
 
219
        dict.__delitem__(self, key)
 
220
        self._list.remove(key)
 
221
 
 
222
    def pop(self, key, *default):
 
223
        present = key in self
 
224
        value = dict.pop(self, key, *default)
 
225
        if present:
 
226
            self._list.remove(key)
 
227
        return value
 
228
 
 
229
    def popitem(self):
 
230
        item = dict.popitem(self)
 
231
        self._list.remove(item[0])
 
232
        return item
 
233
 
 
234
class OrderedSet(set):
 
235
    def __init__(self, d=None):
 
236
        set.__init__(self)
 
237
        self._list = []
 
238
        if d is not None:
 
239
            self.update(d)
 
240
 
 
241
    def add(self, element):
 
242
        if element not in self:
 
243
            self._list.append(element)
 
244
        set.add(self, element)
 
245
 
 
246
    def remove(self, element):
 
247
        set.remove(self, element)
 
248
        self._list.remove(element)
 
249
 
 
250
    def insert(self, pos, element):
 
251
        if element not in self:
 
252
            self._list.insert(pos, element)
 
253
        set.add(self, element)
 
254
 
 
255
    def discard(self, element):
 
256
        if element in self:
 
257
            self._list.remove(element)
 
258
            set.remove(self, element)
 
259
 
 
260
    def clear(self):
 
261
        set.clear(self)
 
262
        self._list = []
 
263
 
 
264
    def __getitem__(self, key):
 
265
        return self._list[key]
 
266
 
 
267
    def __iter__(self):
 
268
        return iter(self._list)
 
269
 
 
270
    def __add__(self, other):
 
271
        return self.union(other)
 
272
 
 
273
    def __repr__(self):
 
274
        return '%s(%r)' % (self.__class__.__name__, self._list)
 
275
 
 
276
    __str__ = __repr__
 
277
 
 
278
    def update(self, iterable):
 
279
        for e in iterable:
 
280
            if e not in self:
 
281
                self._list.append(e)
 
282
                set.add(self, e)
 
283
        return self
 
284
 
 
285
    __ior__ = update
 
286
 
 
287
    def union(self, other):
 
288
        result = self.__class__(self)
 
289
        result.update(other)
 
290
        return result
 
291
 
 
292
    __or__ = union
 
293
 
 
294
    def intersection(self, other):
 
295
        other = set(other)
 
296
        return self.__class__(a for a in self if a in other)
 
297
 
 
298
    __and__ = intersection
 
299
 
 
300
    def symmetric_difference(self, other):
 
301
        other = set(other)
 
302
        result = self.__class__(a for a in self if a not in other)
 
303
        result.update(a for a in other if a not in self)
 
304
        return result
 
305
 
 
306
    __xor__ = symmetric_difference
 
307
 
 
308
    def difference(self, other):
 
309
        other = set(other)
 
310
        return self.__class__(a for a in self if a not in other)
 
311
 
 
312
    __sub__ = difference
 
313
 
 
314
    def intersection_update(self, other):
 
315
        other = set(other)
 
316
        set.intersection_update(self, other)
 
317
        self._list = [ a for a in self._list if a in other]
 
318
        return self
 
319
 
 
320
    __iand__ = intersection_update
 
321
 
 
322
    def symmetric_difference_update(self, other):
 
323
        set.symmetric_difference_update(self, other)
 
324
        self._list =  [ a for a in self._list if a in self]
 
325
        self._list += [ a for a in other._list if a in self]
 
326
        return self
 
327
 
 
328
    __ixor__ = symmetric_difference_update
 
329
 
 
330
    def difference_update(self, other):
 
331
        set.difference_update(self, other)
 
332
        self._list = [ a for a in self._list if a in self]
 
333
        return self
 
334
 
 
335
    __isub__ = difference_update
 
336
 
 
337
 
 
338
class IdentitySet(object):
 
339
    """A set that considers only object id() for uniqueness.
 
340
 
 
341
    This strategy has edge cases for builtin types- it's possible to have
 
342
    two 'foo' strings in one of these sets, for example.  Use sparingly.
 
343
 
 
344
    """
 
345
 
 
346
    _working_set = set
 
347
 
 
348
    def __init__(self, iterable=None):
 
349
        self._members = dict()
 
350
        if iterable:
 
351
            for o in iterable:
 
352
                self.add(o)
 
353
 
 
354
    def add(self, value):
 
355
        self._members[id(value)] = value
 
356
 
 
357
    def __contains__(self, value):
 
358
        return id(value) in self._members
 
359
 
 
360
    def remove(self, value):
 
361
        del self._members[id(value)]
 
362
 
 
363
    def discard(self, value):
 
364
        try:
 
365
            self.remove(value)
 
366
        except KeyError:
 
367
            pass
 
368
 
 
369
    def pop(self):
 
370
        try:
 
371
            pair = self._members.popitem()
 
372
            return pair[1]
 
373
        except KeyError:
 
374
            raise KeyError('pop from an empty set')
 
375
 
 
376
    def clear(self):
 
377
        self._members.clear()
 
378
 
 
379
    def __cmp__(self, other):
 
380
        raise TypeError('cannot compare sets using cmp()')
 
381
 
 
382
    def __eq__(self, other):
 
383
        if isinstance(other, IdentitySet):
 
384
            return self._members == other._members
 
385
        else:
 
386
            return False
 
387
 
 
388
    def __ne__(self, other):
 
389
        if isinstance(other, IdentitySet):
 
390
            return self._members != other._members
 
391
        else:
 
392
            return True
 
393
 
 
394
    def issubset(self, iterable):
 
395
        other = type(self)(iterable)
 
396
 
 
397
        if len(self) > len(other):
 
398
            return False
 
399
        for m in itertools.ifilterfalse(other._members.__contains__,
 
400
                                        self._members.iterkeys()):
 
401
            return False
 
402
        return True
 
403
 
 
404
    def __le__(self, other):
 
405
        if not isinstance(other, IdentitySet):
 
406
            return NotImplemented
 
407
        return self.issubset(other)
 
408
 
 
409
    def __lt__(self, other):
 
410
        if not isinstance(other, IdentitySet):
 
411
            return NotImplemented
 
412
        return len(self) < len(other) and self.issubset(other)
 
413
 
 
414
    def issuperset(self, iterable):
 
415
        other = type(self)(iterable)
 
416
 
 
417
        if len(self) < len(other):
 
418
            return False
 
419
 
 
420
        for m in itertools.ifilterfalse(self._members.__contains__,
 
421
                                        other._members.iterkeys()):
 
422
            return False
 
423
        return True
 
424
 
 
425
    def __ge__(self, other):
 
426
        if not isinstance(other, IdentitySet):
 
427
            return NotImplemented
 
428
        return self.issuperset(other)
 
429
 
 
430
    def __gt__(self, other):
 
431
        if not isinstance(other, IdentitySet):
 
432
            return NotImplemented
 
433
        return len(self) > len(other) and self.issuperset(other)
 
434
 
 
435
    def union(self, iterable):
 
436
        result = type(self)()
 
437
        # testlib.pragma exempt:__hash__
 
438
        result._members.update(
 
439
            self._working_set(self._member_id_tuples()).union(_iter_id(iterable)))
 
440
        return result
 
441
 
 
442
    def __or__(self, other):
 
443
        if not isinstance(other, IdentitySet):
 
444
            return NotImplemented
 
445
        return self.union(other)
 
446
 
 
447
    def update(self, iterable):
 
448
        self._members = self.union(iterable)._members
 
449
 
 
450
    def __ior__(self, other):
 
451
        if not isinstance(other, IdentitySet):
 
452
            return NotImplemented
 
453
        self.update(other)
 
454
        return self
 
455
 
 
456
    def difference(self, iterable):
 
457
        result = type(self)()
 
458
        # testlib.pragma exempt:__hash__
 
459
        result._members.update(
 
460
            self._working_set(self._member_id_tuples()).difference(_iter_id(iterable)))
 
461
        return result
 
462
 
 
463
    def __sub__(self, other):
 
464
        if not isinstance(other, IdentitySet):
 
465
            return NotImplemented
 
466
        return self.difference(other)
 
467
 
 
468
    def difference_update(self, iterable):
 
469
        self._members = self.difference(iterable)._members
 
470
 
 
471
    def __isub__(self, other):
 
472
        if not isinstance(other, IdentitySet):
 
473
            return NotImplemented
 
474
        self.difference_update(other)
 
475
        return self
 
476
 
 
477
    def intersection(self, iterable):
 
478
        result = type(self)()
 
479
        # testlib.pragma exempt:__hash__
 
480
        result._members.update(
 
481
            self._working_set(self._member_id_tuples()).intersection(_iter_id(iterable)))
 
482
        return result
 
483
 
 
484
    def __and__(self, other):
 
485
        if not isinstance(other, IdentitySet):
 
486
            return NotImplemented
 
487
        return self.intersection(other)
 
488
 
 
489
    def intersection_update(self, iterable):
 
490
        self._members = self.intersection(iterable)._members
 
491
 
 
492
    def __iand__(self, other):
 
493
        if not isinstance(other, IdentitySet):
 
494
            return NotImplemented
 
495
        self.intersection_update(other)
 
496
        return self
 
497
 
 
498
    def symmetric_difference(self, iterable):
 
499
        result = type(self)()
 
500
        # testlib.pragma exempt:__hash__
 
501
        result._members.update(
 
502
            self._working_set(self._member_id_tuples()).symmetric_difference(_iter_id(iterable)))
 
503
        return result
 
504
 
 
505
    def _member_id_tuples(self):
 
506
        return ((id(v), v) for v in self._members.itervalues())
 
507
 
 
508
    def __xor__(self, other):
 
509
        if not isinstance(other, IdentitySet):
 
510
            return NotImplemented
 
511
        return self.symmetric_difference(other)
 
512
 
 
513
    def symmetric_difference_update(self, iterable):
 
514
        self._members = self.symmetric_difference(iterable)._members
 
515
 
 
516
    def __ixor__(self, other):
 
517
        if not isinstance(other, IdentitySet):
 
518
            return NotImplemented
 
519
        self.symmetric_difference(other)
 
520
        return self
 
521
 
 
522
    def copy(self):
 
523
        return type(self)(self._members.itervalues())
 
524
 
 
525
    __copy__ = copy
 
526
 
 
527
    def __len__(self):
 
528
        return len(self._members)
 
529
 
 
530
    def __iter__(self):
 
531
        return self._members.itervalues()
 
532
 
 
533
    def __hash__(self):
 
534
        raise TypeError('set objects are unhashable')
 
535
 
 
536
    def __repr__(self):
 
537
        return '%s(%r)' % (type(self).__name__, self._members.values())
 
538
 
 
539
 
 
540
class OrderedIdentitySet(IdentitySet):
 
541
    class _working_set(OrderedSet):
 
542
        # a testing pragma: exempt the OIDS working set from the test suite's
 
543
        # "never call the user's __hash__" assertions.  this is a big hammer,
 
544
        # but it's safe here: IDS operates on (id, instance) tuples in the
 
545
        # working set.
 
546
        __sa_hash_exempt__ = True
 
547
 
 
548
    def __init__(self, iterable=None):
 
549
        IdentitySet.__init__(self)
 
550
        self._members = OrderedDict()
 
551
        if iterable:
 
552
            for o in iterable:
 
553
                self.add(o)
 
554
 
 
555
 
 
556
if sys.version_info >= (2, 5):
 
557
    class PopulateDict(dict):
 
558
        """A dict which populates missing values via a creation function.
 
559
 
 
560
        Note the creation function takes a key, unlike
 
561
        collections.defaultdict.
 
562
 
 
563
        """
 
564
 
 
565
        def __init__(self, creator):
 
566
            self.creator = creator
 
567
 
 
568
        def __missing__(self, key):
 
569
            self[key] = val = self.creator(key)
 
570
            return val
 
571
else:
 
572
    class PopulateDict(dict):
 
573
        """A dict which populates missing values via a creation function."""
 
574
 
 
575
        def __init__(self, creator):
 
576
            self.creator = creator
 
577
 
 
578
        def __getitem__(self, key):
 
579
            try:
 
580
                return dict.__getitem__(self, key)
 
581
            except KeyError:
 
582
                self[key] = value = self.creator(key)
 
583
                return value
 
584
 
 
585
# define collections that are capable of storing 
 
586
# ColumnElement objects as hashable keys/elements.
 
587
column_set = set
 
588
column_dict = dict
 
589
ordered_column_set = OrderedSet
 
590
populate_column_dict = PopulateDict
 
591
 
 
592
def unique_list(seq, hashfunc=None):
 
593
    seen = {}
 
594
    if not hashfunc:
 
595
        return [x for x in seq 
 
596
                if x not in seen 
 
597
                and not seen.__setitem__(x, True)]
 
598
    else:
 
599
        return [x for x in seq 
 
600
                if hashfunc(x) not in seen 
 
601
                and not seen.__setitem__(hashfunc(x), True)]
 
602
 
 
603
class UniqueAppender(object):
 
604
    """Appends items to a collection ensuring uniqueness.
 
605
 
 
606
    Additional appends() of the same object are ignored.  Membership is
 
607
    determined by identity (``is a``) not equality (``==``).
 
608
    """
 
609
 
 
610
    def __init__(self, data, via=None):
 
611
        self.data = data
 
612
        self._unique = {}
 
613
        if via:
 
614
            self._data_appender = getattr(data, via)
 
615
        elif hasattr(data, 'append'):
 
616
            self._data_appender = data.append
 
617
        elif hasattr(data, 'add'):
 
618
            self._data_appender = data.add
 
619
 
 
620
    def append(self, item):
 
621
        id_ = id(item)
 
622
        if id_ not in self._unique:
 
623
            self._data_appender(item)
 
624
            self._unique[id_] = True
 
625
 
 
626
    def __iter__(self):
 
627
        return iter(self.data)
 
628
 
 
629
def to_list(x, default=None):
 
630
    if x is None:
 
631
        return default
 
632
    if not isinstance(x, (list, tuple)):
 
633
        return [x]
 
634
    else:
 
635
        return x
 
636
 
 
637
def to_set(x):
 
638
    if x is None:
 
639
        return set()
 
640
    if not isinstance(x, set):
 
641
        return set(to_list(x))
 
642
    else:
 
643
        return x
 
644
 
 
645
def to_column_set(x):
 
646
    if x is None:
 
647
        return column_set()
 
648
    if not isinstance(x, column_set):
 
649
        return column_set(to_list(x))
 
650
    else:
 
651
        return x
 
652
 
 
653
def update_copy(d, _new=None, **kw):
 
654
    """Copy the given dict and update with the given values."""
 
655
 
 
656
    d = d.copy()
 
657
    if _new:
 
658
        d.update(_new)
 
659
    d.update(**kw)
 
660
    return d
 
661
 
 
662
def flatten_iterator(x):
 
663
    """Given an iterator of which further sub-elements may also be
 
664
    iterators, flatten the sub-elements into a single iterator.
 
665
 
 
666
    """
 
667
    for elem in x:
 
668
        if not isinstance(elem, basestring) and hasattr(elem, '__iter__'):
 
669
            for y in flatten_iterator(elem):
 
670
                yield y
 
671
        else:
 
672
            yield elem
 
673
 
 
674
class WeakIdentityMapping(weakref.WeakKeyDictionary):
 
675
    """A WeakKeyDictionary with an object identity index.
 
676
 
 
677
    Adds a .by_id dictionary to a regular WeakKeyDictionary.  Trades
 
678
    performance during mutation operations for accelerated lookups by id().
 
679
 
 
680
    The usual cautions about weak dictionaries and iteration also apply to
 
681
    this subclass.
 
682
 
 
683
    """
 
684
    _none = symbol('none')
 
685
 
 
686
    def __init__(self):
 
687
        weakref.WeakKeyDictionary.__init__(self)
 
688
        self.by_id = {}
 
689
        self._weakrefs = {}
 
690
 
 
691
    def __setitem__(self, object, value):
 
692
        oid = id(object)
 
693
        self.by_id[oid] = value
 
694
        if oid not in self._weakrefs:
 
695
            self._weakrefs[oid] = self._ref(object)
 
696
        weakref.WeakKeyDictionary.__setitem__(self, object, value)
 
697
 
 
698
    def __delitem__(self, object):
 
699
        del self._weakrefs[id(object)]
 
700
        del self.by_id[id(object)]
 
701
        weakref.WeakKeyDictionary.__delitem__(self, object)
 
702
 
 
703
    def setdefault(self, object, default=None):
 
704
        value = weakref.WeakKeyDictionary.setdefault(self, object, default)
 
705
        oid = id(object)
 
706
        if value is default:
 
707
            self.by_id[oid] = default
 
708
        if oid not in self._weakrefs:
 
709
            self._weakrefs[oid] = self._ref(object)
 
710
        return value
 
711
 
 
712
    def pop(self, object, default=_none):
 
713
        if default is self._none:
 
714
            value = weakref.WeakKeyDictionary.pop(self, object)
 
715
        else:
 
716
            value = weakref.WeakKeyDictionary.pop(self, object, default)
 
717
        if id(object) in self.by_id:
 
718
            del self._weakrefs[id(object)]
 
719
            del self.by_id[id(object)]
 
720
        return value
 
721
 
 
722
    def popitem(self):
 
723
        item = weakref.WeakKeyDictionary.popitem(self)
 
724
        oid = id(item[0])
 
725
        del self._weakrefs[oid]
 
726
        del self.by_id[oid]
 
727
        return item
 
728
 
 
729
    def clear(self):
 
730
        # Py2K
 
731
        # in 3k, MutableMapping calls popitem()
 
732
        self._weakrefs.clear()
 
733
        self.by_id.clear()
 
734
        # end Py2K
 
735
        weakref.WeakKeyDictionary.clear(self)
 
736
 
 
737
    def update(self, *a, **kw):
 
738
        raise NotImplementedError
 
739
 
 
740
    def _cleanup(self, wr, key=None):
 
741
        if key is None:
 
742
            key = wr.key
 
743
        try:
 
744
            del self._weakrefs[key]
 
745
        except (KeyError, AttributeError):  # pragma: no cover
 
746
            pass                            # pragma: no cover
 
747
        try:
 
748
            del self.by_id[key]
 
749
        except (KeyError, AttributeError):  # pragma: no cover
 
750
            pass                            # pragma: no cover
 
751
 
 
752
    class _keyed_weakref(weakref.ref):
 
753
        def __init__(self, object, callback):
 
754
            weakref.ref.__init__(self, object, callback)
 
755
            self.key = id(object)
 
756
 
 
757
    def _ref(self, object):
 
758
        return self._keyed_weakref(object, self._cleanup)
 
759
 
 
760
 
 
761
class LRUCache(dict):
 
762
    """Dictionary with 'squishy' removal of least
 
763
    recently used items.
 
764
 
 
765
    """
 
766
    def __init__(self, capacity=100, threshold=.5):
 
767
        self.capacity = capacity
 
768
        self.threshold = threshold
 
769
 
 
770
    def __getitem__(self, key):
 
771
        item = dict.__getitem__(self, key)
 
772
        item[2] = time_func()
 
773
        return item[1]
 
774
 
 
775
    def values(self):
 
776
        return [i[1] for i in dict.values(self)]
 
777
 
 
778
    def setdefault(self, key, value):
 
779
        if key in self:
 
780
            return self[key]
 
781
        else:
 
782
            self[key] = value
 
783
            return value
 
784
 
 
785
    def __setitem__(self, key, value):
 
786
        item = dict.get(self, key)
 
787
        if item is None:
 
788
            item = [key, value, time_func()]
 
789
            dict.__setitem__(self, key, item)
 
790
        else:
 
791
            item[1] = value
 
792
        self._manage_size()
 
793
 
 
794
    def _manage_size(self):
 
795
        while len(self) > self.capacity + self.capacity * self.threshold:
 
796
            bytime = sorted(dict.values(self), 
 
797
                            key=operator.itemgetter(2),
 
798
                            reverse=True)
 
799
            for item in bytime[self.capacity:]:
 
800
                try:
 
801
                    del self[item[0]]
 
802
                except KeyError:
 
803
                    # if we couldnt find a key, most 
 
804
                    # likely some other thread broke in 
 
805
                    # on us. loop around and try again
 
806
                    break
 
807
 
 
808
 
 
809
class ScopedRegistry(object):
 
810
    """A Registry that can store one or multiple instances of a single
 
811
    class on the basis of a "scope" function.
 
812
 
 
813
    The object implements ``__call__`` as the "getter", so by
 
814
    calling ``myregistry()`` the contained object is returned
 
815
    for the current scope.
 
816
 
 
817
    :param createfunc:
 
818
      a callable that returns a new object to be placed in the registry
 
819
 
 
820
    :param scopefunc:
 
821
      a callable that will return a key to store/retrieve an object.
 
822
    """
 
823
 
 
824
    def __init__(self, createfunc, scopefunc):
 
825
        """Construct a new :class:`.ScopedRegistry`.
 
826
 
 
827
        :param createfunc:  A creation function that will generate
 
828
          a new value for the current scope, if none is present.
 
829
 
 
830
        :param scopefunc:  A function that returns a hashable
 
831
          token representing the current scope (such as, current
 
832
          thread identifier).
 
833
 
 
834
        """
 
835
        self.createfunc = createfunc
 
836
        self.scopefunc = scopefunc
 
837
        self.registry = {}
 
838
 
 
839
    def __call__(self):
 
840
        key = self.scopefunc()
 
841
        try:
 
842
            return self.registry[key]
 
843
        except KeyError:
 
844
            return self.registry.setdefault(key, self.createfunc())
 
845
 
 
846
    def has(self):
 
847
        """Return True if an object is present in the current scope."""
 
848
 
 
849
        return self.scopefunc() in self.registry
 
850
 
 
851
    def set(self, obj):
 
852
        """Set the value forthe current scope."""
 
853
 
 
854
        self.registry[self.scopefunc()] = obj
 
855
 
 
856
    def clear(self):
 
857
        """Clear the current scope, if any."""
 
858
 
 
859
        try:
 
860
            del self.registry[self.scopefunc()]
 
861
        except KeyError:
 
862
            pass
 
863
 
 
864
class ThreadLocalRegistry(ScopedRegistry):
 
865
    """A :class:`.ScopedRegistry` that uses a ``threading.local()`` 
 
866
    variable for storage.
 
867
 
 
868
    """
 
869
    def __init__(self, createfunc):
 
870
        self.createfunc = createfunc
 
871
        self.registry = threading.local()
 
872
 
 
873
    def __call__(self):
 
874
        try:
 
875
            return self.registry.value
 
876
        except AttributeError:
 
877
            val = self.registry.value = self.createfunc()
 
878
            return val
 
879
 
 
880
    def has(self):
 
881
        return hasattr(self.registry, "value")
 
882
 
 
883
    def set(self, obj):
 
884
        self.registry.value = obj
 
885
 
 
886
    def clear(self):
 
887
        try:
 
888
            del self.registry.value
 
889
        except AttributeError:
 
890
            pass
 
891
 
 
892
def _iter_id(iterable):
 
893
    """Generator: ((id(o), o) for o in iterable)."""
 
894
 
 
895
    for item in iterable:
 
896
        yield id(item), item
 
897