diskcache-community 5.7.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1253 @@
1
+ """Persistent Data Types
2
+ """
3
+
4
+ import operator as op
5
+ from collections import OrderedDict
6
+ from collections.abc import (
7
+ ItemsView,
8
+ KeysView,
9
+ MutableMapping,
10
+ Sequence,
11
+ ValuesView,
12
+ )
13
+ from contextlib import contextmanager
14
+ from shutil import rmtree
15
+
16
+ from .core import ENOVAL, Cache, Disk
17
+
18
+
19
+ def _make_compare(seq_op, doc):
20
+ """Make compare method with Sequence semantics."""
21
+
22
+ def compare(self, that):
23
+ """Compare method for deque and sequence."""
24
+ if not isinstance(that, Sequence):
25
+ return NotImplemented
26
+
27
+ len_self = len(self)
28
+ len_that = len(that)
29
+
30
+ if len_self != len_that:
31
+ if seq_op is op.eq:
32
+ return False
33
+ if seq_op is op.ne:
34
+ return True
35
+
36
+ for alpha, beta in zip(self, that):
37
+ if alpha != beta:
38
+ return seq_op(alpha, beta)
39
+
40
+ return seq_op(len_self, len_that)
41
+
42
+ compare.__name__ = '__{0}__'.format(seq_op.__name__)
43
+ doc_str = 'Return True if and only if deque is {0} `that`.'
44
+ compare.__doc__ = doc_str.format(doc)
45
+
46
+ return compare
47
+
48
+
49
+ class Deque(Sequence):
50
+ """Persistent sequence with double-ended queue semantics.
51
+
52
+ Double-ended queue is an ordered collection with optimized access at its
53
+ endpoints.
54
+
55
+ Items are serialized to disk. Deque may be initialized from directory path
56
+ where items are stored.
57
+
58
+ >>> deque = Deque()
59
+ >>> deque += range(5)
60
+ >>> list(deque)
61
+ [0, 1, 2, 3, 4]
62
+ >>> for value in range(5):
63
+ ... deque.appendleft(-value)
64
+ >>> len(deque)
65
+ 10
66
+ >>> list(deque)
67
+ [-4, -3, -2, -1, 0, 0, 1, 2, 3, 4]
68
+ >>> deque.pop()
69
+ 4
70
+ >>> deque.popleft()
71
+ -4
72
+ >>> deque.reverse()
73
+ >>> list(deque)
74
+ [3, 2, 1, 0, 0, -1, -2, -3]
75
+
76
+ """
77
+
78
+ def __init__(self, iterable=(), directory=None, maxlen=None, disk=Disk):
79
+ """Initialize deque instance.
80
+
81
+ If directory is None then temporary directory created. The directory
82
+ will *not* be automatically removed.
83
+
84
+ :param iterable: iterable of items to append to deque
85
+ :param directory: deque directory (default None)
86
+ :param maxlen: max length (default None, no max)
87
+ :param disk: `Disk` type or subclass for serialization (default
88
+ :class:`Disk <diskcache.Disk>`)
89
+
90
+ """
91
+ self._cache = Cache(directory, disk=disk, eviction_policy='none')
92
+ self._maxlen = float('inf') if maxlen is None else maxlen
93
+ self._extend(iterable)
94
+
95
+ @classmethod
96
+ def fromcache(cls, cache, iterable=(), maxlen=None):
97
+ """Initialize deque using `cache`.
98
+
99
+ >>> cache = Cache()
100
+ >>> deque = Deque.fromcache(cache, [5, 6, 7, 8])
101
+ >>> deque.cache is cache
102
+ True
103
+ >>> len(deque)
104
+ 4
105
+ >>> 7 in deque
106
+ True
107
+ >>> deque.popleft()
108
+ 5
109
+
110
+ :param Cache cache: cache to use
111
+ :param iterable: iterable of items
112
+ :return: initialized Deque
113
+
114
+ """
115
+ # pylint: disable=no-member,protected-access
116
+ self = cls.__new__(cls)
117
+ self._cache = cache
118
+ self._maxlen = float('inf') if maxlen is None else maxlen
119
+ self._extend(iterable)
120
+ return self
121
+
122
+ @property
123
+ def cache(self):
124
+ """Cache used by deque."""
125
+ return self._cache
126
+
127
+ @property
128
+ def directory(self):
129
+ """Directory path where deque is stored."""
130
+ return self._cache.directory
131
+
132
+ @property
133
+ def maxlen(self):
134
+ """Max length of the deque."""
135
+ return self._maxlen
136
+
137
+ @maxlen.setter
138
+ def maxlen(self, value):
139
+ """Set max length of the deque.
140
+
141
+ Pops items from left while length greater than max.
142
+
143
+ >>> deque = Deque()
144
+ >>> deque.extendleft('abcde')
145
+ >>> deque.maxlen = 3
146
+ >>> list(deque)
147
+ ['c', 'd', 'e']
148
+
149
+ :param value: max length
150
+
151
+ """
152
+ self._maxlen = value
153
+ with self._cache.transact(retry=True):
154
+ while len(self._cache) > self._maxlen:
155
+ self._popleft()
156
+
157
+ def _index(self, index, func):
158
+ len_self = len(self)
159
+
160
+ if index >= 0:
161
+ if index >= len_self:
162
+ raise IndexError('deque index out of range')
163
+
164
+ for key in self._cache.iterkeys():
165
+ if index == 0:
166
+ try:
167
+ return func(key)
168
+ except KeyError:
169
+ continue
170
+ index -= 1
171
+ else:
172
+ if index < -len_self:
173
+ raise IndexError('deque index out of range')
174
+
175
+ index += 1
176
+
177
+ for key in self._cache.iterkeys(reverse=True):
178
+ if index == 0:
179
+ try:
180
+ return func(key)
181
+ except KeyError:
182
+ continue
183
+ index += 1
184
+
185
+ raise IndexError('deque index out of range')
186
+
187
+ def __getitem__(self, index):
188
+ """deque.__getitem__(index) <==> deque[index]
189
+
190
+ Return corresponding item for `index` in deque.
191
+
192
+ See also `Deque.peekleft` and `Deque.peek` for indexing deque at index
193
+ ``0`` or ``-1``.
194
+
195
+ >>> deque = Deque()
196
+ >>> deque.extend('abcde')
197
+ >>> deque[1]
198
+ 'b'
199
+ >>> deque[-2]
200
+ 'd'
201
+
202
+ :param int index: index of item
203
+ :return: corresponding item
204
+ :raises IndexError: if index out of range
205
+
206
+ """
207
+ return self._index(index, self._cache.__getitem__)
208
+
209
+ def __setitem__(self, index, value):
210
+ """deque.__setitem__(index, value) <==> deque[index] = value
211
+
212
+ Store `value` in deque at `index`.
213
+
214
+ >>> deque = Deque()
215
+ >>> deque.extend([None] * 3)
216
+ >>> deque[0] = 'a'
217
+ >>> deque[1] = 'b'
218
+ >>> deque[-1] = 'c'
219
+ >>> ''.join(deque)
220
+ 'abc'
221
+
222
+ :param int index: index of value
223
+ :param value: value to store
224
+ :raises IndexError: if index out of range
225
+
226
+ """
227
+
228
+ def _set_value(key):
229
+ return self._cache.__setitem__(key, value)
230
+
231
+ self._index(index, _set_value)
232
+
233
+ def __delitem__(self, index):
234
+ """deque.__delitem__(index) <==> del deque[index]
235
+
236
+ Delete item in deque at `index`.
237
+
238
+ >>> deque = Deque()
239
+ >>> deque.extend([None] * 3)
240
+ >>> del deque[0]
241
+ >>> del deque[1]
242
+ >>> del deque[-1]
243
+ >>> len(deque)
244
+ 0
245
+
246
+ :param int index: index of item
247
+ :raises IndexError: if index out of range
248
+
249
+ """
250
+ self._index(index, self._cache.__delitem__)
251
+
252
+ def __repr__(self):
253
+ """deque.__repr__() <==> repr(deque)
254
+
255
+ Return string with printable representation of deque.
256
+
257
+ """
258
+ name = type(self).__name__
259
+ return '{0}(directory={1!r})'.format(name, self.directory)
260
+
261
+ __eq__ = _make_compare(op.eq, 'equal to')
262
+ __ne__ = _make_compare(op.ne, 'not equal to')
263
+ __lt__ = _make_compare(op.lt, 'less than')
264
+ __gt__ = _make_compare(op.gt, 'greater than')
265
+ __le__ = _make_compare(op.le, 'less than or equal to')
266
+ __ge__ = _make_compare(op.ge, 'greater than or equal to')
267
+
268
+ def __iadd__(self, iterable):
269
+ """deque.__iadd__(iterable) <==> deque += iterable
270
+
271
+ Extend back side of deque with items from iterable.
272
+
273
+ :param iterable: iterable of items to append to deque
274
+ :return: deque with added items
275
+
276
+ """
277
+ self._extend(iterable)
278
+ return self
279
+
280
+ def __iter__(self):
281
+ """deque.__iter__() <==> iter(deque)
282
+
283
+ Return iterator of deque from front to back.
284
+
285
+ """
286
+ _cache = self._cache
287
+
288
+ for key in _cache.iterkeys():
289
+ try:
290
+ yield _cache[key]
291
+ except KeyError:
292
+ pass
293
+
294
+ def __len__(self):
295
+ """deque.__len__() <==> len(deque)
296
+
297
+ Return length of deque.
298
+
299
+ """
300
+ return len(self._cache)
301
+
302
+ def __reversed__(self):
303
+ """deque.__reversed__() <==> reversed(deque)
304
+
305
+ Return iterator of deque from back to front.
306
+
307
+ >>> deque = Deque()
308
+ >>> deque.extend('abcd')
309
+ >>> iterator = reversed(deque)
310
+ >>> next(iterator)
311
+ 'd'
312
+ >>> list(iterator)
313
+ ['c', 'b', 'a']
314
+
315
+ """
316
+ _cache = self._cache
317
+
318
+ for key in _cache.iterkeys(reverse=True):
319
+ try:
320
+ yield _cache[key]
321
+ except KeyError:
322
+ pass
323
+
324
+ def __getstate__(self):
325
+ return self.directory, self.maxlen
326
+
327
+ def __setstate__(self, state):
328
+ directory, maxlen = state
329
+ self.__init__(directory=directory, maxlen=maxlen)
330
+
331
+ def append(self, value):
332
+ """Add `value` to back of deque.
333
+
334
+ >>> deque = Deque()
335
+ >>> deque.append('a')
336
+ >>> deque.append('b')
337
+ >>> deque.append('c')
338
+ >>> list(deque)
339
+ ['a', 'b', 'c']
340
+
341
+ :param value: value to add to back of deque
342
+
343
+ """
344
+ with self._cache.transact(retry=True):
345
+ self._cache.push(value, retry=True)
346
+ if len(self._cache) > self._maxlen:
347
+ self._popleft()
348
+
349
+ _append = append
350
+
351
+ def appendleft(self, value):
352
+ """Add `value` to front of deque.
353
+
354
+ >>> deque = Deque()
355
+ >>> deque.appendleft('a')
356
+ >>> deque.appendleft('b')
357
+ >>> deque.appendleft('c')
358
+ >>> list(deque)
359
+ ['c', 'b', 'a']
360
+
361
+ :param value: value to add to front of deque
362
+
363
+ """
364
+ with self._cache.transact(retry=True):
365
+ self._cache.push(value, side='front', retry=True)
366
+ if len(self._cache) > self._maxlen:
367
+ self._pop()
368
+
369
+ _appendleft = appendleft
370
+
371
+ def clear(self):
372
+ """Remove all elements from deque.
373
+
374
+ >>> deque = Deque('abc')
375
+ >>> len(deque)
376
+ 3
377
+ >>> deque.clear()
378
+ >>> list(deque)
379
+ []
380
+
381
+ """
382
+ self._cache.clear(retry=True)
383
+
384
+ _clear = clear
385
+
386
+ def copy(self):
387
+ """Copy deque with same directory and max length."""
388
+ TypeSelf = type(self)
389
+ return TypeSelf(directory=self.directory, maxlen=self.maxlen)
390
+
391
+ def count(self, value):
392
+ """Return number of occurrences of `value` in deque.
393
+
394
+ >>> deque = Deque()
395
+ >>> deque += [num for num in range(1, 5) for _ in range(num)]
396
+ >>> deque.count(0)
397
+ 0
398
+ >>> deque.count(1)
399
+ 1
400
+ >>> deque.count(4)
401
+ 4
402
+
403
+ :param value: value to count in deque
404
+ :return: count of items equal to value in deque
405
+
406
+ """
407
+ return sum(1 for item in self if value == item)
408
+
409
+ def extend(self, iterable):
410
+ """Extend back side of deque with values from `iterable`.
411
+
412
+ :param iterable: iterable of values
413
+
414
+ """
415
+ for value in iterable:
416
+ self._append(value)
417
+
418
+ _extend = extend
419
+
420
+ def extendleft(self, iterable):
421
+ """Extend front side of deque with value from `iterable`.
422
+
423
+ >>> deque = Deque()
424
+ >>> deque.extendleft('abc')
425
+ >>> list(deque)
426
+ ['c', 'b', 'a']
427
+
428
+ :param iterable: iterable of values
429
+
430
+ """
431
+ for value in iterable:
432
+ self._appendleft(value)
433
+
434
+ def peek(self):
435
+ """Peek at value at back of deque.
436
+
437
+ Faster than indexing deque at -1.
438
+
439
+ If deque is empty then raise IndexError.
440
+
441
+ >>> deque = Deque()
442
+ >>> deque.peek()
443
+ Traceback (most recent call last):
444
+ ...
445
+ IndexError: peek from an empty deque
446
+ >>> deque += 'abc'
447
+ >>> deque.peek()
448
+ 'c'
449
+
450
+ :return: value at back of deque
451
+ :raises IndexError: if deque is empty
452
+
453
+ """
454
+ default = None, ENOVAL
455
+ _, value = self._cache.peek(default=default, side='back', retry=True)
456
+ if value is ENOVAL:
457
+ raise IndexError('peek from an empty deque')
458
+ return value
459
+
460
+ def peekleft(self):
461
+ """Peek at value at front of deque.
462
+
463
+ Faster than indexing deque at 0.
464
+
465
+ If deque is empty then raise IndexError.
466
+
467
+ >>> deque = Deque()
468
+ >>> deque.peekleft()
469
+ Traceback (most recent call last):
470
+ ...
471
+ IndexError: peek from an empty deque
472
+ >>> deque += 'abc'
473
+ >>> deque.peekleft()
474
+ 'a'
475
+
476
+ :return: value at front of deque
477
+ :raises IndexError: if deque is empty
478
+
479
+ """
480
+ default = None, ENOVAL
481
+ _, value = self._cache.peek(default=default, side='front', retry=True)
482
+ if value is ENOVAL:
483
+ raise IndexError('peek from an empty deque')
484
+ return value
485
+
486
+ def pop(self):
487
+ """Remove and return value at back of deque.
488
+
489
+ If deque is empty then raise IndexError.
490
+
491
+ >>> deque = Deque()
492
+ >>> deque += 'ab'
493
+ >>> deque.pop()
494
+ 'b'
495
+ >>> deque.pop()
496
+ 'a'
497
+ >>> deque.pop()
498
+ Traceback (most recent call last):
499
+ ...
500
+ IndexError: pop from an empty deque
501
+
502
+ :return: value at back of deque
503
+ :raises IndexError: if deque is empty
504
+
505
+ """
506
+ default = None, ENOVAL
507
+ _, value = self._cache.pull(default=default, side='back', retry=True)
508
+ if value is ENOVAL:
509
+ raise IndexError('pop from an empty deque')
510
+ return value
511
+
512
+ _pop = pop
513
+
514
+ def popleft(self):
515
+ """Remove and return value at front of deque.
516
+
517
+ >>> deque = Deque()
518
+ >>> deque += 'ab'
519
+ >>> deque.popleft()
520
+ 'a'
521
+ >>> deque.popleft()
522
+ 'b'
523
+ >>> deque.popleft()
524
+ Traceback (most recent call last):
525
+ ...
526
+ IndexError: pop from an empty deque
527
+
528
+ :return: value at front of deque
529
+ :raises IndexError: if deque is empty
530
+
531
+ """
532
+ default = None, ENOVAL
533
+ _, value = self._cache.pull(default=default, retry=True)
534
+ if value is ENOVAL:
535
+ raise IndexError('pop from an empty deque')
536
+ return value
537
+
538
+ _popleft = popleft
539
+
540
+ def remove(self, value):
541
+ """Remove first occurrence of `value` in deque.
542
+
543
+ >>> deque = Deque()
544
+ >>> deque += 'aab'
545
+ >>> deque.remove('a')
546
+ >>> list(deque)
547
+ ['a', 'b']
548
+ >>> deque.remove('b')
549
+ >>> list(deque)
550
+ ['a']
551
+ >>> deque.remove('c')
552
+ Traceback (most recent call last):
553
+ ...
554
+ ValueError: deque.remove(value): value not in deque
555
+
556
+ :param value: value to remove
557
+ :raises ValueError: if value not in deque
558
+
559
+ """
560
+ _cache = self._cache
561
+
562
+ for key in _cache.iterkeys():
563
+ try:
564
+ item = _cache[key]
565
+ except KeyError:
566
+ continue
567
+ else:
568
+ if value == item:
569
+ try:
570
+ del _cache[key]
571
+ except KeyError:
572
+ continue
573
+ return
574
+
575
+ raise ValueError('deque.remove(value): value not in deque')
576
+
577
+ def reverse(self):
578
+ """Reverse deque in place.
579
+
580
+ >>> deque = Deque()
581
+ >>> deque += 'abc'
582
+ >>> deque.reverse()
583
+ >>> list(deque)
584
+ ['c', 'b', 'a']
585
+
586
+ """
587
+ # pylint: disable=protected-access
588
+ # GrantJ 2019-03-22 Consider using an algorithm that swaps the values
589
+ # at two keys. Like self._cache.swap(key1, key2, retry=True) The swap
590
+ # method would exchange the values at two given keys. Then, using a
591
+ # forward iterator and a reverse iterator, the reverse method could
592
+ # avoid making copies of the values.
593
+ temp = Deque(iterable=reversed(self))
594
+ self._clear()
595
+ self._extend(temp)
596
+ directory = temp.directory
597
+ temp._cache.close()
598
+ del temp
599
+ rmtree(directory)
600
+
601
+ def rotate(self, steps=1):
602
+ """Rotate deque right by `steps`.
603
+
604
+ If steps is negative then rotate left.
605
+
606
+ >>> deque = Deque()
607
+ >>> deque += range(5)
608
+ >>> deque.rotate(2)
609
+ >>> list(deque)
610
+ [3, 4, 0, 1, 2]
611
+ >>> deque.rotate(-1)
612
+ >>> list(deque)
613
+ [4, 0, 1, 2, 3]
614
+
615
+ :param int steps: number of steps to rotate (default 1)
616
+
617
+ """
618
+ if not isinstance(steps, int):
619
+ type_name = type(steps).__name__
620
+ raise TypeError('integer argument expected, got %s' % type_name)
621
+
622
+ len_self = len(self)
623
+
624
+ if not len_self:
625
+ return
626
+
627
+ if steps >= 0:
628
+ steps %= len_self
629
+
630
+ for _ in range(steps):
631
+ try:
632
+ value = self._pop()
633
+ except IndexError:
634
+ return
635
+ else:
636
+ self._appendleft(value)
637
+ else:
638
+ steps *= -1
639
+ steps %= len_self
640
+
641
+ for _ in range(steps):
642
+ try:
643
+ value = self._popleft()
644
+ except IndexError:
645
+ return
646
+ else:
647
+ self._append(value)
648
+
649
+ __hash__ = None # type: ignore
650
+
651
+ @contextmanager
652
+ def transact(self):
653
+ """Context manager to perform a transaction by locking the deque.
654
+
655
+ While the deque is locked, no other write operation is permitted.
656
+ Transactions should therefore be as short as possible. Read and write
657
+ operations performed in a transaction are atomic. Read operations may
658
+ occur concurrent to a transaction.
659
+
660
+ Transactions may be nested and may not be shared between threads.
661
+
662
+ >>> from diskcache import Deque
663
+ >>> deque = Deque()
664
+ >>> deque += range(5)
665
+ >>> with deque.transact(): # Atomically rotate elements.
666
+ ... value = deque.pop()
667
+ ... deque.appendleft(value)
668
+ >>> list(deque)
669
+ [4, 0, 1, 2, 3]
670
+
671
+ :return: context manager for use in `with` statement
672
+
673
+ """
674
+ with self._cache.transact(retry=True):
675
+ yield
676
+
677
+
678
+ class Index(MutableMapping):
679
+ """Persistent mutable mapping with insertion order iteration.
680
+
681
+ Items are serialized to disk. Index may be initialized from directory path
682
+ where items are stored.
683
+
684
+ Hashing protocol is not used. Keys are looked up by their serialized
685
+ format. See ``diskcache.Disk`` for details.
686
+
687
+ >>> index = Index()
688
+ >>> index.update([('a', 1), ('b', 2), ('c', 3)])
689
+ >>> index['a']
690
+ 1
691
+ >>> list(index)
692
+ ['a', 'b', 'c']
693
+ >>> len(index)
694
+ 3
695
+ >>> del index['b']
696
+ >>> index.popitem()
697
+ ('c', 3)
698
+
699
+ """
700
+
701
+ def __init__(self, *args, disk=Disk, **kwargs):
702
+ """Initialize index in directory and update items.
703
+
704
+ Optional first argument may be string specifying directory where items
705
+ are stored. When None or not given, temporary directory is created.
706
+
707
+ The keyword argument `disk` selects the :class:`Disk <diskcache.Disk>`
708
+ type or subclass used for serialization. Every other keyword argument
709
+ is stored as an item, so an item named ``disk`` has to be set after
710
+ initialization.
711
+
712
+ >>> index = Index({'a': 1, 'b': 2, 'c': 3})
713
+ >>> len(index)
714
+ 3
715
+ >>> directory = index.directory
716
+ >>> inventory = Index(directory, d=4)
717
+ >>> inventory['b']
718
+ 2
719
+ >>> len(inventory)
720
+ 4
721
+
722
+ """
723
+ if args and isinstance(args[0], (bytes, str)):
724
+ directory = args[0]
725
+ args = args[1:]
726
+ else:
727
+ if args and args[0] is None:
728
+ args = args[1:]
729
+ directory = None
730
+ self._cache = Cache(directory, disk=disk, eviction_policy='none')
731
+ self._update(*args, **kwargs)
732
+
733
+ _update = MutableMapping.update
734
+
735
+ @classmethod
736
+ def fromcache(cls, cache, *args, **kwargs):
737
+ """Initialize index using `cache` and update items.
738
+
739
+ >>> cache = Cache()
740
+ >>> index = Index.fromcache(cache, {'a': 1, 'b': 2, 'c': 3})
741
+ >>> index.cache is cache
742
+ True
743
+ >>> len(index)
744
+ 3
745
+ >>> 'b' in index
746
+ True
747
+ >>> index['c']
748
+ 3
749
+
750
+ :param Cache cache: cache to use
751
+ :param args: mapping or sequence of items
752
+ :param kwargs: mapping of items
753
+ :return: initialized Index
754
+
755
+ """
756
+ # pylint: disable=no-member,protected-access
757
+ self = cls.__new__(cls)
758
+ self._cache = cache
759
+ self._update(*args, **kwargs)
760
+ return self
761
+
762
+ @property
763
+ def cache(self):
764
+ """Cache used by index."""
765
+ return self._cache
766
+
767
+ @property
768
+ def directory(self):
769
+ """Directory path where items are stored."""
770
+ return self._cache.directory
771
+
772
+ def __getitem__(self, key):
773
+ """index.__getitem__(key) <==> index[key]
774
+
775
+ Return corresponding value for `key` in index.
776
+
777
+ >>> index = Index()
778
+ >>> index.update({'a': 1, 'b': 2})
779
+ >>> index['a']
780
+ 1
781
+ >>> index['b']
782
+ 2
783
+ >>> index['c']
784
+ Traceback (most recent call last):
785
+ ...
786
+ KeyError: 'c'
787
+
788
+ :param key: key for item
789
+ :return: value for item in index with given key
790
+ :raises KeyError: if key is not found
791
+
792
+ """
793
+ return self._cache[key]
794
+
795
+ def __setitem__(self, key, value):
796
+ """index.__setitem__(key, value) <==> index[key] = value
797
+
798
+ Set `key` and `value` item in index.
799
+
800
+ >>> index = Index()
801
+ >>> index['a'] = 1
802
+ >>> index[0] = None
803
+ >>> len(index)
804
+ 2
805
+
806
+ :param key: key for item
807
+ :param value: value for item
808
+
809
+ """
810
+ self._cache[key] = value
811
+
812
+ def __delitem__(self, key):
813
+ """index.__delitem__(key) <==> del index[key]
814
+
815
+ Delete corresponding item for `key` from index.
816
+
817
+ >>> index = Index()
818
+ >>> index.update({'a': 1, 'b': 2})
819
+ >>> del index['a']
820
+ >>> del index['b']
821
+ >>> len(index)
822
+ 0
823
+ >>> del index['c']
824
+ Traceback (most recent call last):
825
+ ...
826
+ KeyError: 'c'
827
+
828
+ :param key: key for item
829
+ :raises KeyError: if key is not found
830
+
831
+ """
832
+ del self._cache[key]
833
+
834
+ def setdefault(self, key, default=None):
835
+ """Set and get value for `key` in index using `default`.
836
+
837
+ If `key` is not in index then set corresponding value to `default`. If
838
+ `key` is in index then ignore `default` and return existing value.
839
+
840
+ >>> index = Index()
841
+ >>> index.setdefault('a', 0)
842
+ 0
843
+ >>> index.setdefault('a', 1)
844
+ 0
845
+
846
+ :param key: key for item
847
+ :param default: value if key is missing (default None)
848
+ :return: value for item in index with given key
849
+
850
+ """
851
+ _cache = self._cache
852
+ while True:
853
+ try:
854
+ return _cache[key]
855
+ except KeyError:
856
+ _cache.add(key, default, retry=True)
857
+
858
+ def peekitem(self, last=True):
859
+ """Peek at key and value item pair in index based on iteration order.
860
+
861
+ >>> index = Index()
862
+ >>> for num, letter in enumerate('xyz'):
863
+ ... index[letter] = num
864
+ >>> index.peekitem()
865
+ ('z', 2)
866
+ >>> index.peekitem(last=False)
867
+ ('x', 0)
868
+
869
+ :param bool last: last item in iteration order (default True)
870
+ :return: key and value item pair
871
+ :raises KeyError: if cache is empty
872
+
873
+ """
874
+ return self._cache.peekitem(last, retry=True)
875
+
876
+ def pop(self, key, default=ENOVAL):
877
+ """Remove corresponding item for `key` from index and return value.
878
+
879
+ If `key` is missing then return `default`. If `default` is `ENOVAL`
880
+ then raise KeyError.
881
+
882
+ >>> index = Index({'a': 1, 'b': 2})
883
+ >>> index.pop('a')
884
+ 1
885
+ >>> index.pop('b')
886
+ 2
887
+ >>> index.pop('c', default=3)
888
+ 3
889
+ >>> index.pop('d')
890
+ Traceback (most recent call last):
891
+ ...
892
+ KeyError: 'd'
893
+
894
+ :param key: key for item
895
+ :param default: return value if key is missing (default ENOVAL)
896
+ :return: value for item if key is found else default
897
+ :raises KeyError: if key is not found and default is ENOVAL
898
+
899
+ """
900
+ _cache = self._cache
901
+ value = _cache.pop(key, default=default, retry=True)
902
+ if value is ENOVAL:
903
+ raise KeyError(key)
904
+ return value
905
+
906
+ def popitem(self, last=True):
907
+ """Remove and return item pair.
908
+
909
+ Item pairs are returned in last-in-first-out (LIFO) order if last is
910
+ True else first-in-first-out (FIFO) order. LIFO order imitates a stack
911
+ and FIFO order imitates a queue.
912
+
913
+ >>> index = Index()
914
+ >>> index.update([('a', 1), ('b', 2), ('c', 3)])
915
+ >>> index.popitem()
916
+ ('c', 3)
917
+ >>> index.popitem(last=False)
918
+ ('a', 1)
919
+ >>> index.popitem()
920
+ ('b', 2)
921
+ >>> index.popitem()
922
+ Traceback (most recent call last):
923
+ ...
924
+ KeyError: 'dictionary is empty'
925
+
926
+ :param bool last: pop last item pair (default True)
927
+ :return: key and value item pair
928
+ :raises KeyError: if index is empty
929
+
930
+ """
931
+ # pylint: disable=arguments-differ,unbalanced-tuple-unpacking
932
+ _cache = self._cache
933
+
934
+ with _cache.transact(retry=True):
935
+ key, value = _cache.peekitem(last=last)
936
+ del _cache[key]
937
+
938
+ return key, value
939
+
940
+ def push(self, value, prefix=None, side='back'):
941
+ """Push `value` onto `side` of queue in index identified by `prefix`.
942
+
943
+ When prefix is None, integer keys are used. Otherwise, string keys are
944
+ used in the format "prefix-integer". Integer starts at 500 trillion.
945
+
946
+ Defaults to pushing value on back of queue. Set side to 'front' to push
947
+ value on front of queue. Side must be one of 'back' or 'front'.
948
+
949
+ See also `Index.pull`.
950
+
951
+ >>> index = Index()
952
+ >>> print(index.push('apples'))
953
+ 500000000000000
954
+ >>> print(index.push('beans'))
955
+ 500000000000001
956
+ >>> print(index.push('cherries', side='front'))
957
+ 499999999999999
958
+ >>> index[500000000000001]
959
+ 'beans'
960
+ >>> index.push('dates', prefix='fruit')
961
+ 'fruit-500000000000000'
962
+
963
+ :param value: value for item
964
+ :param str prefix: key prefix (default None, key is integer)
965
+ :param str side: either 'back' or 'front' (default 'back')
966
+ :return: key for item in cache
967
+
968
+ """
969
+ return self._cache.push(value, prefix, side, retry=True)
970
+
971
+ def pull(self, prefix=None, default=(None, None), side='front'):
972
+ """Pull key and value item pair from `side` of queue in index.
973
+
974
+ When prefix is None, integer keys are used. Otherwise, string keys are
975
+ used in the format "prefix-integer". Integer starts at 500 trillion.
976
+
977
+ If queue is empty, return default.
978
+
979
+ Defaults to pulling key and value item pairs from front of queue. Set
980
+ side to 'back' to pull from back of queue. Side must be one of 'front'
981
+ or 'back'.
982
+
983
+ See also `Index.push`.
984
+
985
+ >>> index = Index()
986
+ >>> for letter in 'abc':
987
+ ... print(index.push(letter))
988
+ 500000000000000
989
+ 500000000000001
990
+ 500000000000002
991
+ >>> key, value = index.pull()
992
+ >>> print(key)
993
+ 500000000000000
994
+ >>> value
995
+ 'a'
996
+ >>> _, value = index.pull(side='back')
997
+ >>> value
998
+ 'c'
999
+ >>> index.pull(prefix='fruit')
1000
+ (None, None)
1001
+
1002
+ :param str prefix: key prefix (default None, key is integer)
1003
+ :param default: value to return if key is missing
1004
+ (default (None, None))
1005
+ :param str side: either 'front' or 'back' (default 'front')
1006
+ :return: key and value item pair or default if queue is empty
1007
+
1008
+ """
1009
+ return self._cache.pull(prefix, default, side, retry=True)
1010
+
1011
+ def clear(self):
1012
+ """Remove all items from index.
1013
+
1014
+ >>> index = Index({'a': 0, 'b': 1, 'c': 2})
1015
+ >>> len(index)
1016
+ 3
1017
+ >>> index.clear()
1018
+ >>> dict(index)
1019
+ {}
1020
+
1021
+ """
1022
+ self._cache.clear(retry=True)
1023
+
1024
+ def __iter__(self):
1025
+ """index.__iter__() <==> iter(index)
1026
+
1027
+ Return iterator of index keys in insertion order.
1028
+
1029
+ """
1030
+ return iter(self._cache)
1031
+
1032
+ def __reversed__(self):
1033
+ """index.__reversed__() <==> reversed(index)
1034
+
1035
+ Return iterator of index keys in reversed insertion order.
1036
+
1037
+ >>> index = Index()
1038
+ >>> index.update([('a', 1), ('b', 2), ('c', 3)])
1039
+ >>> iterator = reversed(index)
1040
+ >>> next(iterator)
1041
+ 'c'
1042
+ >>> list(iterator)
1043
+ ['b', 'a']
1044
+
1045
+ """
1046
+ return reversed(self._cache)
1047
+
1048
+ def __len__(self):
1049
+ """index.__len__() <==> len(index)
1050
+
1051
+ Return length of index.
1052
+
1053
+ """
1054
+ return len(self._cache)
1055
+
1056
+ def keys(self):
1057
+ """Set-like object providing a view of index keys.
1058
+
1059
+ >>> index = Index()
1060
+ >>> index.update({'a': 1, 'b': 2, 'c': 3})
1061
+ >>> keys_view = index.keys()
1062
+ >>> 'b' in keys_view
1063
+ True
1064
+
1065
+ :return: keys view
1066
+
1067
+ """
1068
+ return KeysView(self)
1069
+
1070
+ def values(self):
1071
+ """Set-like object providing a view of index values.
1072
+
1073
+ >>> index = Index()
1074
+ >>> index.update({'a': 1, 'b': 2, 'c': 3})
1075
+ >>> values_view = index.values()
1076
+ >>> 2 in values_view
1077
+ True
1078
+
1079
+ :return: values view
1080
+
1081
+ """
1082
+ return ValuesView(self)
1083
+
1084
+ def items(self):
1085
+ """Set-like object providing a view of index items.
1086
+
1087
+ >>> index = Index()
1088
+ >>> index.update({'a': 1, 'b': 2, 'c': 3})
1089
+ >>> items_view = index.items()
1090
+ >>> ('b', 2) in items_view
1091
+ True
1092
+
1093
+ :return: items view
1094
+
1095
+ """
1096
+ return ItemsView(self)
1097
+
1098
+ __hash__ = None # type: ignore
1099
+
1100
+ def __getstate__(self):
1101
+ return self.directory
1102
+
1103
+ def __setstate__(self, state):
1104
+ self.__init__(state)
1105
+
1106
+ def __eq__(self, other):
1107
+ """index.__eq__(other) <==> index == other
1108
+
1109
+ Compare equality for index and `other`.
1110
+
1111
+ Comparison to another index or ordered dictionary is
1112
+ order-sensitive. Comparison to all other mappings is order-insensitive.
1113
+
1114
+ >>> index = Index()
1115
+ >>> pairs = [('a', 1), ('b', 2), ('c', 3)]
1116
+ >>> index.update(pairs)
1117
+ >>> from collections import OrderedDict
1118
+ >>> od = OrderedDict(pairs)
1119
+ >>> index == od
1120
+ True
1121
+ >>> index == {'c': 3, 'b': 2, 'a': 1}
1122
+ True
1123
+
1124
+ :param other: other mapping in equality comparison
1125
+ :return: True if index equals other
1126
+
1127
+ """
1128
+ if len(self) != len(other):
1129
+ return False
1130
+
1131
+ if isinstance(other, (Index, OrderedDict)):
1132
+ alpha = ((key, self[key]) for key in self)
1133
+ beta = ((key, other[key]) for key in other)
1134
+ pairs = zip(alpha, beta)
1135
+ return not any(a != x or b != y for (a, b), (x, y) in pairs)
1136
+ else:
1137
+ return all(self[key] == other.get(key, ENOVAL) for key in self)
1138
+
1139
+ def __ne__(self, other):
1140
+ """index.__ne__(other) <==> index != other
1141
+
1142
+ Compare inequality for index and `other`.
1143
+
1144
+ Comparison to another index or ordered dictionary is
1145
+ order-sensitive. Comparison to all other mappings is order-insensitive.
1146
+
1147
+ >>> index = Index()
1148
+ >>> index.update([('a', 1), ('b', 2), ('c', 3)])
1149
+ >>> from collections import OrderedDict
1150
+ >>> od = OrderedDict([('c', 3), ('b', 2), ('a', 1)])
1151
+ >>> index != od
1152
+ True
1153
+ >>> index != {'a': 1, 'b': 2}
1154
+ True
1155
+
1156
+ :param other: other mapping in inequality comparison
1157
+ :return: True if index does not equal other
1158
+
1159
+ """
1160
+ return not self == other
1161
+
1162
+ def memoize(self, name=None, typed=False, ignore=()):
1163
+ """Memoizing cache decorator.
1164
+
1165
+ Decorator to wrap callable with memoizing function using cache.
1166
+ Repeated calls with the same arguments will lookup result in cache and
1167
+ avoid function evaluation.
1168
+
1169
+ If name is set to None (default), the callable name will be determined
1170
+ automatically.
1171
+
1172
+ If typed is set to True, function arguments of different types will be
1173
+ cached separately. For example, f(3) and f(3.0) will be treated as
1174
+ distinct calls with distinct results.
1175
+
1176
+ The original underlying function is accessible through the __wrapped__
1177
+ attribute. This is useful for introspection, for bypassing the cache,
1178
+ or for rewrapping the function with a different cache.
1179
+
1180
+ >>> from diskcache import Index
1181
+ >>> mapping = Index()
1182
+ >>> @mapping.memoize()
1183
+ ... def fibonacci(number):
1184
+ ... if number == 0:
1185
+ ... return 0
1186
+ ... elif number == 1:
1187
+ ... return 1
1188
+ ... else:
1189
+ ... return fibonacci(number - 1) + fibonacci(number - 2)
1190
+ >>> print(fibonacci(100))
1191
+ 354224848179261915075
1192
+
1193
+ An additional `__cache_key__` attribute can be used to generate the
1194
+ cache key used for the given arguments.
1195
+
1196
+ >>> key = fibonacci.__cache_key__(100)
1197
+ >>> print(mapping[key])
1198
+ 354224848179261915075
1199
+
1200
+ Remember to call memoize when decorating a callable. If you forget,
1201
+ then a TypeError will occur. Note the lack of parenthenses after
1202
+ memoize below:
1203
+
1204
+ >>> @mapping.memoize
1205
+ ... def test():
1206
+ ... pass
1207
+ Traceback (most recent call last):
1208
+ ...
1209
+ TypeError: name cannot be callable
1210
+
1211
+ :param str name: name given for callable (default None, automatic)
1212
+ :param bool typed: cache different types separately (default False)
1213
+ :param set ignore: positional or keyword args to ignore (default ())
1214
+ :return: callable decorator
1215
+
1216
+ """
1217
+ return self._cache.memoize(name, typed, ignore=ignore)
1218
+
1219
+ @contextmanager
1220
+ def transact(self):
1221
+ """Context manager to perform a transaction by locking the index.
1222
+
1223
+ While the index is locked, no other write operation is permitted.
1224
+ Transactions should therefore be as short as possible. Read and write
1225
+ operations performed in a transaction are atomic. Read operations may
1226
+ occur concurrent to a transaction.
1227
+
1228
+ Transactions may be nested and may not be shared between threads.
1229
+
1230
+ >>> from diskcache import Index
1231
+ >>> mapping = Index()
1232
+ >>> with mapping.transact(): # Atomically increment two keys.
1233
+ ... mapping['total'] = mapping.get('total', 0) + 123.4
1234
+ ... mapping['count'] = mapping.get('count', 0) + 1
1235
+ >>> with mapping.transact(): # Atomically calculate average.
1236
+ ... average = mapping['total'] / mapping['count']
1237
+ >>> average
1238
+ 123.4
1239
+
1240
+ :return: context manager for use in `with` statement
1241
+
1242
+ """
1243
+ with self._cache.transact(retry=True):
1244
+ yield
1245
+
1246
+ def __repr__(self):
1247
+ """index.__repr__() <==> repr(index)
1248
+
1249
+ Return string with printable representation of index.
1250
+
1251
+ """
1252
+ name = type(self).__name__
1253
+ return '{0}({1!r})'.format(name, self.directory)