| """Persistent Data Types |
| """ |
|
|
| import operator as op |
| from collections import OrderedDict |
| from collections.abc import ( |
| ItemsView, |
| KeysView, |
| MutableMapping, |
| Sequence, |
| ValuesView, |
| ) |
| from contextlib import contextmanager |
| from shutil import rmtree |
|
|
| from .core import ENOVAL, Cache |
|
|
|
|
| def _make_compare(seq_op, doc): |
| """Make compare method with Sequence semantics.""" |
|
|
| def compare(self, that): |
| """Compare method for deque and sequence.""" |
| if not isinstance(that, Sequence): |
| return NotImplemented |
|
|
| len_self = len(self) |
| len_that = len(that) |
|
|
| if len_self != len_that: |
| if seq_op is op.eq: |
| return False |
| if seq_op is op.ne: |
| return True |
|
|
| for alpha, beta in zip(self, that): |
| if alpha != beta: |
| return seq_op(alpha, beta) |
|
|
| return seq_op(len_self, len_that) |
|
|
| compare.__name__ = '__{0}__'.format(seq_op.__name__) |
| doc_str = 'Return True if and only if deque is {0} `that`.' |
| compare.__doc__ = doc_str.format(doc) |
|
|
| return compare |
|
|
|
|
| class Deque(Sequence): |
| """Persistent sequence with double-ended queue semantics. |
| |
| Double-ended queue is an ordered collection with optimized access at its |
| endpoints. |
| |
| Items are serialized to disk. Deque may be initialized from directory path |
| where items are stored. |
| |
| >>> deque = Deque() |
| >>> deque += range(5) |
| >>> list(deque) |
| [0, 1, 2, 3, 4] |
| >>> for value in range(5): |
| ... deque.appendleft(-value) |
| >>> len(deque) |
| 10 |
| >>> list(deque) |
| [-4, -3, -2, -1, 0, 0, 1, 2, 3, 4] |
| >>> deque.pop() |
| 4 |
| >>> deque.popleft() |
| -4 |
| >>> deque.reverse() |
| >>> list(deque) |
| [3, 2, 1, 0, 0, -1, -2, -3] |
| |
| """ |
|
|
| def __init__(self, iterable=(), directory=None, maxlen=None): |
| """Initialize deque instance. |
| |
| If directory is None then temporary directory created. The directory |
| will *not* be automatically removed. |
| |
| :param iterable: iterable of items to append to deque |
| :param directory: deque directory (default None) |
| |
| """ |
| self._cache = Cache(directory, eviction_policy='none') |
| self._maxlen = float('inf') if maxlen is None else maxlen |
| self._extend(iterable) |
|
|
| @classmethod |
| def fromcache(cls, cache, iterable=(), maxlen=None): |
| """Initialize deque using `cache`. |
| |
| >>> cache = Cache() |
| >>> deque = Deque.fromcache(cache, [5, 6, 7, 8]) |
| >>> deque.cache is cache |
| True |
| >>> len(deque) |
| 4 |
| >>> 7 in deque |
| True |
| >>> deque.popleft() |
| 5 |
| |
| :param Cache cache: cache to use |
| :param iterable: iterable of items |
| :return: initialized Deque |
| |
| """ |
| |
| self = cls.__new__(cls) |
| self._cache = cache |
| self._maxlen = float('inf') if maxlen is None else maxlen |
| self._extend(iterable) |
| return self |
|
|
| @property |
| def cache(self): |
| """Cache used by deque.""" |
| return self._cache |
|
|
| @property |
| def directory(self): |
| """Directory path where deque is stored.""" |
| return self._cache.directory |
|
|
| @property |
| def maxlen(self): |
| """Max length of the deque.""" |
| return self._maxlen |
|
|
| @maxlen.setter |
| def maxlen(self, value): |
| """Set max length of the deque. |
| |
| Pops items from left while length greater than max. |
| |
| >>> deque = Deque() |
| >>> deque.extendleft('abcde') |
| >>> deque.maxlen = 3 |
| >>> list(deque) |
| ['c', 'd', 'e'] |
| |
| :param value: max length |
| |
| """ |
| self._maxlen = value |
| with self._cache.transact(retry=True): |
| while len(self._cache) > self._maxlen: |
| self._popleft() |
|
|
| def _index(self, index, func): |
| len_self = len(self) |
|
|
| if index >= 0: |
| if index >= len_self: |
| raise IndexError('deque index out of range') |
|
|
| for key in self._cache.iterkeys(): |
| if index == 0: |
| try: |
| return func(key) |
| except KeyError: |
| continue |
| index -= 1 |
| else: |
| if index < -len_self: |
| raise IndexError('deque index out of range') |
|
|
| index += 1 |
|
|
| for key in self._cache.iterkeys(reverse=True): |
| if index == 0: |
| try: |
| return func(key) |
| except KeyError: |
| continue |
| index += 1 |
|
|
| raise IndexError('deque index out of range') |
|
|
| def __getitem__(self, index): |
| """deque.__getitem__(index) <==> deque[index] |
| |
| Return corresponding item for `index` in deque. |
| |
| See also `Deque.peekleft` and `Deque.peek` for indexing deque at index |
| ``0`` or ``-1``. |
| |
| >>> deque = Deque() |
| >>> deque.extend('abcde') |
| >>> deque[1] |
| 'b' |
| >>> deque[-2] |
| 'd' |
| |
| :param int index: index of item |
| :return: corresponding item |
| :raises IndexError: if index out of range |
| |
| """ |
| return self._index(index, self._cache.__getitem__) |
|
|
| def __setitem__(self, index, value): |
| """deque.__setitem__(index, value) <==> deque[index] = value |
| |
| Store `value` in deque at `index`. |
| |
| >>> deque = Deque() |
| >>> deque.extend([None] * 3) |
| >>> deque[0] = 'a' |
| >>> deque[1] = 'b' |
| >>> deque[-1] = 'c' |
| >>> ''.join(deque) |
| 'abc' |
| |
| :param int index: index of value |
| :param value: value to store |
| :raises IndexError: if index out of range |
| |
| """ |
|
|
| def _set_value(key): |
| return self._cache.__setitem__(key, value) |
|
|
| self._index(index, _set_value) |
|
|
| def __delitem__(self, index): |
| """deque.__delitem__(index) <==> del deque[index] |
| |
| Delete item in deque at `index`. |
| |
| >>> deque = Deque() |
| >>> deque.extend([None] * 3) |
| >>> del deque[0] |
| >>> del deque[1] |
| >>> del deque[-1] |
| >>> len(deque) |
| 0 |
| |
| :param int index: index of item |
| :raises IndexError: if index out of range |
| |
| """ |
| self._index(index, self._cache.__delitem__) |
|
|
| def __repr__(self): |
| """deque.__repr__() <==> repr(deque) |
| |
| Return string with printable representation of deque. |
| |
| """ |
| name = type(self).__name__ |
| return '{0}(directory={1!r})'.format(name, self.directory) |
|
|
| __eq__ = _make_compare(op.eq, 'equal to') |
| __ne__ = _make_compare(op.ne, 'not equal to') |
| __lt__ = _make_compare(op.lt, 'less than') |
| __gt__ = _make_compare(op.gt, 'greater than') |
| __le__ = _make_compare(op.le, 'less than or equal to') |
| __ge__ = _make_compare(op.ge, 'greater than or equal to') |
|
|
| def __iadd__(self, iterable): |
| """deque.__iadd__(iterable) <==> deque += iterable |
| |
| Extend back side of deque with items from iterable. |
| |
| :param iterable: iterable of items to append to deque |
| :return: deque with added items |
| |
| """ |
| self._extend(iterable) |
| return self |
|
|
| def __iter__(self): |
| """deque.__iter__() <==> iter(deque) |
| |
| Return iterator of deque from front to back. |
| |
| """ |
| _cache = self._cache |
|
|
| for key in _cache.iterkeys(): |
| try: |
| yield _cache[key] |
| except KeyError: |
| pass |
|
|
| def __len__(self): |
| """deque.__len__() <==> len(deque) |
| |
| Return length of deque. |
| |
| """ |
| return len(self._cache) |
|
|
| def __reversed__(self): |
| """deque.__reversed__() <==> reversed(deque) |
| |
| Return iterator of deque from back to front. |
| |
| >>> deque = Deque() |
| >>> deque.extend('abcd') |
| >>> iterator = reversed(deque) |
| >>> next(iterator) |
| 'd' |
| >>> list(iterator) |
| ['c', 'b', 'a'] |
| |
| """ |
| _cache = self._cache |
|
|
| for key in _cache.iterkeys(reverse=True): |
| try: |
| yield _cache[key] |
| except KeyError: |
| pass |
|
|
| def __getstate__(self): |
| return self.directory, self.maxlen |
|
|
| def __setstate__(self, state): |
| directory, maxlen = state |
| self.__init__(directory=directory, maxlen=maxlen) |
|
|
| def append(self, value): |
| """Add `value` to back of deque. |
| |
| >>> deque = Deque() |
| >>> deque.append('a') |
| >>> deque.append('b') |
| >>> deque.append('c') |
| >>> list(deque) |
| ['a', 'b', 'c'] |
| |
| :param value: value to add to back of deque |
| |
| """ |
| with self._cache.transact(retry=True): |
| self._cache.push(value, retry=True) |
| if len(self._cache) > self._maxlen: |
| self._popleft() |
|
|
| _append = append |
|
|
| def appendleft(self, value): |
| """Add `value` to front of deque. |
| |
| >>> deque = Deque() |
| >>> deque.appendleft('a') |
| >>> deque.appendleft('b') |
| >>> deque.appendleft('c') |
| >>> list(deque) |
| ['c', 'b', 'a'] |
| |
| :param value: value to add to front of deque |
| |
| """ |
| with self._cache.transact(retry=True): |
| self._cache.push(value, side='front', retry=True) |
| if len(self._cache) > self._maxlen: |
| self._pop() |
|
|
| _appendleft = appendleft |
|
|
| def clear(self): |
| """Remove all elements from deque. |
| |
| >>> deque = Deque('abc') |
| >>> len(deque) |
| 3 |
| >>> deque.clear() |
| >>> list(deque) |
| [] |
| |
| """ |
| self._cache.clear(retry=True) |
|
|
| _clear = clear |
|
|
| def copy(self): |
| """Copy deque with same directory and max length.""" |
| TypeSelf = type(self) |
| return TypeSelf(directory=self.directory, maxlen=self.maxlen) |
|
|
| def count(self, value): |
| """Return number of occurrences of `value` in deque. |
| |
| >>> deque = Deque() |
| >>> deque += [num for num in range(1, 5) for _ in range(num)] |
| >>> deque.count(0) |
| 0 |
| >>> deque.count(1) |
| 1 |
| >>> deque.count(4) |
| 4 |
| |
| :param value: value to count in deque |
| :return: count of items equal to value in deque |
| |
| """ |
| return sum(1 for item in self if value == item) |
|
|
| def extend(self, iterable): |
| """Extend back side of deque with values from `iterable`. |
| |
| :param iterable: iterable of values |
| |
| """ |
| for value in iterable: |
| self._append(value) |
|
|
| _extend = extend |
|
|
| def extendleft(self, iterable): |
| """Extend front side of deque with value from `iterable`. |
| |
| >>> deque = Deque() |
| >>> deque.extendleft('abc') |
| >>> list(deque) |
| ['c', 'b', 'a'] |
| |
| :param iterable: iterable of values |
| |
| """ |
| for value in iterable: |
| self._appendleft(value) |
|
|
| def peek(self): |
| """Peek at value at back of deque. |
| |
| Faster than indexing deque at -1. |
| |
| If deque is empty then raise IndexError. |
| |
| >>> deque = Deque() |
| >>> deque.peek() |
| Traceback (most recent call last): |
| ... |
| IndexError: peek from an empty deque |
| >>> deque += 'abc' |
| >>> deque.peek() |
| 'c' |
| |
| :return: value at back of deque |
| :raises IndexError: if deque is empty |
| |
| """ |
| default = None, ENOVAL |
| _, value = self._cache.peek(default=default, side='back', retry=True) |
| if value is ENOVAL: |
| raise IndexError('peek from an empty deque') |
| return value |
|
|
| def peekleft(self): |
| """Peek at value at front of deque. |
| |
| Faster than indexing deque at 0. |
| |
| If deque is empty then raise IndexError. |
| |
| >>> deque = Deque() |
| >>> deque.peekleft() |
| Traceback (most recent call last): |
| ... |
| IndexError: peek from an empty deque |
| >>> deque += 'abc' |
| >>> deque.peekleft() |
| 'a' |
| |
| :return: value at front of deque |
| :raises IndexError: if deque is empty |
| |
| """ |
| default = None, ENOVAL |
| _, value = self._cache.peek(default=default, side='front', retry=True) |
| if value is ENOVAL: |
| raise IndexError('peek from an empty deque') |
| return value |
|
|
| def pop(self): |
| """Remove and return value at back of deque. |
| |
| If deque is empty then raise IndexError. |
| |
| >>> deque = Deque() |
| >>> deque += 'ab' |
| >>> deque.pop() |
| 'b' |
| >>> deque.pop() |
| 'a' |
| >>> deque.pop() |
| Traceback (most recent call last): |
| ... |
| IndexError: pop from an empty deque |
| |
| :return: value at back of deque |
| :raises IndexError: if deque is empty |
| |
| """ |
| default = None, ENOVAL |
| _, value = self._cache.pull(default=default, side='back', retry=True) |
| if value is ENOVAL: |
| raise IndexError('pop from an empty deque') |
| return value |
|
|
| _pop = pop |
|
|
| def popleft(self): |
| """Remove and return value at front of deque. |
| |
| >>> deque = Deque() |
| >>> deque += 'ab' |
| >>> deque.popleft() |
| 'a' |
| >>> deque.popleft() |
| 'b' |
| >>> deque.popleft() |
| Traceback (most recent call last): |
| ... |
| IndexError: pop from an empty deque |
| |
| :return: value at front of deque |
| :raises IndexError: if deque is empty |
| |
| """ |
| default = None, ENOVAL |
| _, value = self._cache.pull(default=default, retry=True) |
| if value is ENOVAL: |
| raise IndexError('pop from an empty deque') |
| return value |
|
|
| _popleft = popleft |
|
|
| def remove(self, value): |
| """Remove first occurrence of `value` in deque. |
| |
| >>> deque = Deque() |
| >>> deque += 'aab' |
| >>> deque.remove('a') |
| >>> list(deque) |
| ['a', 'b'] |
| >>> deque.remove('b') |
| >>> list(deque) |
| ['a'] |
| >>> deque.remove('c') |
| Traceback (most recent call last): |
| ... |
| ValueError: deque.remove(value): value not in deque |
| |
| :param value: value to remove |
| :raises ValueError: if value not in deque |
| |
| """ |
| _cache = self._cache |
|
|
| for key in _cache.iterkeys(): |
| try: |
| item = _cache[key] |
| except KeyError: |
| continue |
| else: |
| if value == item: |
| try: |
| del _cache[key] |
| except KeyError: |
| continue |
| return |
|
|
| raise ValueError('deque.remove(value): value not in deque') |
|
|
| def reverse(self): |
| """Reverse deque in place. |
| |
| >>> deque = Deque() |
| >>> deque += 'abc' |
| >>> deque.reverse() |
| >>> list(deque) |
| ['c', 'b', 'a'] |
| |
| """ |
| |
| |
| |
| |
| |
| |
| temp = Deque(iterable=reversed(self)) |
| self._clear() |
| self._extend(temp) |
| directory = temp.directory |
| temp._cache.close() |
| del temp |
| rmtree(directory) |
|
|
| def rotate(self, steps=1): |
| """Rotate deque right by `steps`. |
| |
| If steps is negative then rotate left. |
| |
| >>> deque = Deque() |
| >>> deque += range(5) |
| >>> deque.rotate(2) |
| >>> list(deque) |
| [3, 4, 0, 1, 2] |
| >>> deque.rotate(-1) |
| >>> list(deque) |
| [4, 0, 1, 2, 3] |
| |
| :param int steps: number of steps to rotate (default 1) |
| |
| """ |
| if not isinstance(steps, int): |
| type_name = type(steps).__name__ |
| raise TypeError('integer argument expected, got %s' % type_name) |
|
|
| len_self = len(self) |
|
|
| if not len_self: |
| return |
|
|
| if steps >= 0: |
| steps %= len_self |
|
|
| for _ in range(steps): |
| try: |
| value = self._pop() |
| except IndexError: |
| return |
| else: |
| self._appendleft(value) |
| else: |
| steps *= -1 |
| steps %= len_self |
|
|
| for _ in range(steps): |
| try: |
| value = self._popleft() |
| except IndexError: |
| return |
| else: |
| self._append(value) |
|
|
| __hash__ = None |
|
|
| @contextmanager |
| def transact(self): |
| """Context manager to perform a transaction by locking the deque. |
| |
| While the deque is locked, no other write operation is permitted. |
| Transactions should therefore be as short as possible. Read and write |
| operations performed in a transaction are atomic. Read operations may |
| occur concurrent to a transaction. |
| |
| Transactions may be nested and may not be shared between threads. |
| |
| >>> from diskcache import Deque |
| >>> deque = Deque() |
| >>> deque += range(5) |
| >>> with deque.transact(): # Atomically rotate elements. |
| ... value = deque.pop() |
| ... deque.appendleft(value) |
| >>> list(deque) |
| [4, 0, 1, 2, 3] |
| |
| :return: context manager for use in `with` statement |
| |
| """ |
| with self._cache.transact(retry=True): |
| yield |
|
|
|
|
| class Index(MutableMapping): |
| """Persistent mutable mapping with insertion order iteration. |
| |
| Items are serialized to disk. Index may be initialized from directory path |
| where items are stored. |
| |
| Hashing protocol is not used. Keys are looked up by their serialized |
| format. See ``diskcache.Disk`` for details. |
| |
| >>> index = Index() |
| >>> index.update([('a', 1), ('b', 2), ('c', 3)]) |
| >>> index['a'] |
| 1 |
| >>> list(index) |
| ['a', 'b', 'c'] |
| >>> len(index) |
| 3 |
| >>> del index['b'] |
| >>> index.popitem() |
| ('c', 3) |
| |
| """ |
|
|
| def __init__(self, *args, **kwargs): |
| """Initialize index in directory and update items. |
| |
| Optional first argument may be string specifying directory where items |
| are stored. When None or not given, temporary directory is created. |
| |
| >>> index = Index({'a': 1, 'b': 2, 'c': 3}) |
| >>> len(index) |
| 3 |
| >>> directory = index.directory |
| >>> inventory = Index(directory, d=4) |
| >>> inventory['b'] |
| 2 |
| >>> len(inventory) |
| 4 |
| |
| """ |
| if args and isinstance(args[0], (bytes, str)): |
| directory = args[0] |
| args = args[1:] |
| else: |
| if args and args[0] is None: |
| args = args[1:] |
| directory = None |
| self._cache = Cache(directory, eviction_policy='none') |
| self._update(*args, **kwargs) |
|
|
| _update = MutableMapping.update |
|
|
| @classmethod |
| def fromcache(cls, cache, *args, **kwargs): |
| """Initialize index using `cache` and update items. |
| |
| >>> cache = Cache() |
| >>> index = Index.fromcache(cache, {'a': 1, 'b': 2, 'c': 3}) |
| >>> index.cache is cache |
| True |
| >>> len(index) |
| 3 |
| >>> 'b' in index |
| True |
| >>> index['c'] |
| 3 |
| |
| :param Cache cache: cache to use |
| :param args: mapping or sequence of items |
| :param kwargs: mapping of items |
| :return: initialized Index |
| |
| """ |
| |
| self = cls.__new__(cls) |
| self._cache = cache |
| self._update(*args, **kwargs) |
| return self |
|
|
| @property |
| def cache(self): |
| """Cache used by index.""" |
| return self._cache |
|
|
| @property |
| def directory(self): |
| """Directory path where items are stored.""" |
| return self._cache.directory |
|
|
| def __getitem__(self, key): |
| """index.__getitem__(key) <==> index[key] |
| |
| Return corresponding value for `key` in index. |
| |
| >>> index = Index() |
| >>> index.update({'a': 1, 'b': 2}) |
| >>> index['a'] |
| 1 |
| >>> index['b'] |
| 2 |
| >>> index['c'] |
| Traceback (most recent call last): |
| ... |
| KeyError: 'c' |
| |
| :param key: key for item |
| :return: value for item in index with given key |
| :raises KeyError: if key is not found |
| |
| """ |
| return self._cache[key] |
|
|
| def __setitem__(self, key, value): |
| """index.__setitem__(key, value) <==> index[key] = value |
| |
| Set `key` and `value` item in index. |
| |
| >>> index = Index() |
| >>> index['a'] = 1 |
| >>> index[0] = None |
| >>> len(index) |
| 2 |
| |
| :param key: key for item |
| :param value: value for item |
| |
| """ |
| self._cache[key] = value |
|
|
| def __delitem__(self, key): |
| """index.__delitem__(key) <==> del index[key] |
| |
| Delete corresponding item for `key` from index. |
| |
| >>> index = Index() |
| >>> index.update({'a': 1, 'b': 2}) |
| >>> del index['a'] |
| >>> del index['b'] |
| >>> len(index) |
| 0 |
| >>> del index['c'] |
| Traceback (most recent call last): |
| ... |
| KeyError: 'c' |
| |
| :param key: key for item |
| :raises KeyError: if key is not found |
| |
| """ |
| del self._cache[key] |
|
|
| def setdefault(self, key, default=None): |
| """Set and get value for `key` in index using `default`. |
| |
| If `key` is not in index then set corresponding value to `default`. If |
| `key` is in index then ignore `default` and return existing value. |
| |
| >>> index = Index() |
| >>> index.setdefault('a', 0) |
| 0 |
| >>> index.setdefault('a', 1) |
| 0 |
| |
| :param key: key for item |
| :param default: value if key is missing (default None) |
| :return: value for item in index with given key |
| |
| """ |
| _cache = self._cache |
| while True: |
| try: |
| return _cache[key] |
| except KeyError: |
| _cache.add(key, default, retry=True) |
|
|
| def peekitem(self, last=True): |
| """Peek at key and value item pair in index based on iteration order. |
| |
| >>> index = Index() |
| >>> for num, letter in enumerate('xyz'): |
| ... index[letter] = num |
| >>> index.peekitem() |
| ('z', 2) |
| >>> index.peekitem(last=False) |
| ('x', 0) |
| |
| :param bool last: last item in iteration order (default True) |
| :return: key and value item pair |
| :raises KeyError: if cache is empty |
| |
| """ |
| return self._cache.peekitem(last, retry=True) |
|
|
| def pop(self, key, default=ENOVAL): |
| """Remove corresponding item for `key` from index and return value. |
| |
| If `key` is missing then return `default`. If `default` is `ENOVAL` |
| then raise KeyError. |
| |
| >>> index = Index({'a': 1, 'b': 2}) |
| >>> index.pop('a') |
| 1 |
| >>> index.pop('b') |
| 2 |
| >>> index.pop('c', default=3) |
| 3 |
| >>> index.pop('d') |
| Traceback (most recent call last): |
| ... |
| KeyError: 'd' |
| |
| :param key: key for item |
| :param default: return value if key is missing (default ENOVAL) |
| :return: value for item if key is found else default |
| :raises KeyError: if key is not found and default is ENOVAL |
| |
| """ |
| _cache = self._cache |
| value = _cache.pop(key, default=default, retry=True) |
| if value is ENOVAL: |
| raise KeyError(key) |
| return value |
|
|
| def popitem(self, last=True): |
| """Remove and return item pair. |
| |
| Item pairs are returned in last-in-first-out (LIFO) order if last is |
| True else first-in-first-out (FIFO) order. LIFO order imitates a stack |
| and FIFO order imitates a queue. |
| |
| >>> index = Index() |
| >>> index.update([('a', 1), ('b', 2), ('c', 3)]) |
| >>> index.popitem() |
| ('c', 3) |
| >>> index.popitem(last=False) |
| ('a', 1) |
| >>> index.popitem() |
| ('b', 2) |
| >>> index.popitem() |
| Traceback (most recent call last): |
| ... |
| KeyError: 'dictionary is empty' |
| |
| :param bool last: pop last item pair (default True) |
| :return: key and value item pair |
| :raises KeyError: if index is empty |
| |
| """ |
| |
| _cache = self._cache |
|
|
| with _cache.transact(retry=True): |
| key, value = _cache.peekitem(last=last) |
| del _cache[key] |
|
|
| return key, value |
|
|
| def push(self, value, prefix=None, side='back'): |
| """Push `value` onto `side` of queue in index identified by `prefix`. |
| |
| When prefix is None, integer keys are used. Otherwise, string keys are |
| used in the format "prefix-integer". Integer starts at 500 trillion. |
| |
| Defaults to pushing value on back of queue. Set side to 'front' to push |
| value on front of queue. Side must be one of 'back' or 'front'. |
| |
| See also `Index.pull`. |
| |
| >>> index = Index() |
| >>> print(index.push('apples')) |
| 500000000000000 |
| >>> print(index.push('beans')) |
| 500000000000001 |
| >>> print(index.push('cherries', side='front')) |
| 499999999999999 |
| >>> index[500000000000001] |
| 'beans' |
| >>> index.push('dates', prefix='fruit') |
| 'fruit-500000000000000' |
| |
| :param value: value for item |
| :param str prefix: key prefix (default None, key is integer) |
| :param str side: either 'back' or 'front' (default 'back') |
| :return: key for item in cache |
| |
| """ |
| return self._cache.push(value, prefix, side, retry=True) |
|
|
| def pull(self, prefix=None, default=(None, None), side='front'): |
| """Pull key and value item pair from `side` of queue in index. |
| |
| When prefix is None, integer keys are used. Otherwise, string keys are |
| used in the format "prefix-integer". Integer starts at 500 trillion. |
| |
| If queue is empty, return default. |
| |
| Defaults to pulling key and value item pairs from front of queue. Set |
| side to 'back' to pull from back of queue. Side must be one of 'front' |
| or 'back'. |
| |
| See also `Index.push`. |
| |
| >>> index = Index() |
| >>> for letter in 'abc': |
| ... print(index.push(letter)) |
| 500000000000000 |
| 500000000000001 |
| 500000000000002 |
| >>> key, value = index.pull() |
| >>> print(key) |
| 500000000000000 |
| >>> value |
| 'a' |
| >>> _, value = index.pull(side='back') |
| >>> value |
| 'c' |
| >>> index.pull(prefix='fruit') |
| (None, None) |
| |
| :param str prefix: key prefix (default None, key is integer) |
| :param default: value to return if key is missing |
| (default (None, None)) |
| :param str side: either 'front' or 'back' (default 'front') |
| :return: key and value item pair or default if queue is empty |
| |
| """ |
| return self._cache.pull(prefix, default, side, retry=True) |
|
|
| def clear(self): |
| """Remove all items from index. |
| |
| >>> index = Index({'a': 0, 'b': 1, 'c': 2}) |
| >>> len(index) |
| 3 |
| >>> index.clear() |
| >>> dict(index) |
| {} |
| |
| """ |
| self._cache.clear(retry=True) |
|
|
| def __iter__(self): |
| """index.__iter__() <==> iter(index) |
| |
| Return iterator of index keys in insertion order. |
| |
| """ |
| return iter(self._cache) |
|
|
| def __reversed__(self): |
| """index.__reversed__() <==> reversed(index) |
| |
| Return iterator of index keys in reversed insertion order. |
| |
| >>> index = Index() |
| >>> index.update([('a', 1), ('b', 2), ('c', 3)]) |
| >>> iterator = reversed(index) |
| >>> next(iterator) |
| 'c' |
| >>> list(iterator) |
| ['b', 'a'] |
| |
| """ |
| return reversed(self._cache) |
|
|
| def __len__(self): |
| """index.__len__() <==> len(index) |
| |
| Return length of index. |
| |
| """ |
| return len(self._cache) |
|
|
| def keys(self): |
| """Set-like object providing a view of index keys. |
| |
| >>> index = Index() |
| >>> index.update({'a': 1, 'b': 2, 'c': 3}) |
| >>> keys_view = index.keys() |
| >>> 'b' in keys_view |
| True |
| |
| :return: keys view |
| |
| """ |
| return KeysView(self) |
|
|
| def values(self): |
| """Set-like object providing a view of index values. |
| |
| >>> index = Index() |
| >>> index.update({'a': 1, 'b': 2, 'c': 3}) |
| >>> values_view = index.values() |
| >>> 2 in values_view |
| True |
| |
| :return: values view |
| |
| """ |
| return ValuesView(self) |
|
|
| def items(self): |
| """Set-like object providing a view of index items. |
| |
| >>> index = Index() |
| >>> index.update({'a': 1, 'b': 2, 'c': 3}) |
| >>> items_view = index.items() |
| >>> ('b', 2) in items_view |
| True |
| |
| :return: items view |
| |
| """ |
| return ItemsView(self) |
|
|
| __hash__ = None |
|
|
| def __getstate__(self): |
| return self.directory |
|
|
| def __setstate__(self, state): |
| self.__init__(state) |
|
|
| def __eq__(self, other): |
| """index.__eq__(other) <==> index == other |
| |
| Compare equality for index and `other`. |
| |
| Comparison to another index or ordered dictionary is |
| order-sensitive. Comparison to all other mappings is order-insensitive. |
| |
| >>> index = Index() |
| >>> pairs = [('a', 1), ('b', 2), ('c', 3)] |
| >>> index.update(pairs) |
| >>> from collections import OrderedDict |
| >>> od = OrderedDict(pairs) |
| >>> index == od |
| True |
| >>> index == {'c': 3, 'b': 2, 'a': 1} |
| True |
| |
| :param other: other mapping in equality comparison |
| :return: True if index equals other |
| |
| """ |
| if len(self) != len(other): |
| return False |
|
|
| if isinstance(other, (Index, OrderedDict)): |
| alpha = ((key, self[key]) for key in self) |
| beta = ((key, other[key]) for key in other) |
| pairs = zip(alpha, beta) |
| return not any(a != x or b != y for (a, b), (x, y) in pairs) |
| else: |
| return all(self[key] == other.get(key, ENOVAL) for key in self) |
|
|
| def __ne__(self, other): |
| """index.__ne__(other) <==> index != other |
| |
| Compare inequality for index and `other`. |
| |
| Comparison to another index or ordered dictionary is |
| order-sensitive. Comparison to all other mappings is order-insensitive. |
| |
| >>> index = Index() |
| >>> index.update([('a', 1), ('b', 2), ('c', 3)]) |
| >>> from collections import OrderedDict |
| >>> od = OrderedDict([('c', 3), ('b', 2), ('a', 1)]) |
| >>> index != od |
| True |
| >>> index != {'a': 1, 'b': 2} |
| True |
| |
| :param other: other mapping in inequality comparison |
| :return: True if index does not equal other |
| |
| """ |
| return not self == other |
|
|
| def memoize(self, name=None, typed=False, ignore=()): |
| """Memoizing cache decorator. |
| |
| Decorator to wrap callable with memoizing function using cache. |
| Repeated calls with the same arguments will lookup result in cache and |
| avoid function evaluation. |
| |
| If name is set to None (default), the callable name will be determined |
| automatically. |
| |
| If typed is set to True, function arguments of different types will be |
| cached separately. For example, f(3) and f(3.0) will be treated as |
| distinct calls with distinct results. |
| |
| The original underlying function is accessible through the __wrapped__ |
| attribute. This is useful for introspection, for bypassing the cache, |
| or for rewrapping the function with a different cache. |
| |
| >>> from diskcache import Index |
| >>> mapping = Index() |
| >>> @mapping.memoize() |
| ... def fibonacci(number): |
| ... if number == 0: |
| ... return 0 |
| ... elif number == 1: |
| ... return 1 |
| ... else: |
| ... return fibonacci(number - 1) + fibonacci(number - 2) |
| >>> print(fibonacci(100)) |
| 354224848179261915075 |
| |
| An additional `__cache_key__` attribute can be used to generate the |
| cache key used for the given arguments. |
| |
| >>> key = fibonacci.__cache_key__(100) |
| >>> print(mapping[key]) |
| 354224848179261915075 |
| |
| Remember to call memoize when decorating a callable. If you forget, |
| then a TypeError will occur. Note the lack of parenthenses after |
| memoize below: |
| |
| >>> @mapping.memoize |
| ... def test(): |
| ... pass |
| Traceback (most recent call last): |
| ... |
| TypeError: name cannot be callable |
| |
| :param str name: name given for callable (default None, automatic) |
| :param bool typed: cache different types separately (default False) |
| :param set ignore: positional or keyword args to ignore (default ()) |
| :return: callable decorator |
| |
| """ |
| return self._cache.memoize(name, typed, ignore=ignore) |
|
|
| @contextmanager |
| def transact(self): |
| """Context manager to perform a transaction by locking the index. |
| |
| While the index is locked, no other write operation is permitted. |
| Transactions should therefore be as short as possible. Read and write |
| operations performed in a transaction are atomic. Read operations may |
| occur concurrent to a transaction. |
| |
| Transactions may be nested and may not be shared between threads. |
| |
| >>> from diskcache import Index |
| >>> mapping = Index() |
| >>> with mapping.transact(): # Atomically increment two keys. |
| ... mapping['total'] = mapping.get('total', 0) + 123.4 |
| ... mapping['count'] = mapping.get('count', 0) + 1 |
| >>> with mapping.transact(): # Atomically calculate average. |
| ... average = mapping['total'] / mapping['count'] |
| >>> average |
| 123.4 |
| |
| :return: context manager for use in `with` statement |
| |
| """ |
| with self._cache.transact(retry=True): |
| yield |
|
|
| def __repr__(self): |
| """index.__repr__() <==> repr(index) |
| |
| Return string with printable representation of index. |
| |
| """ |
| name = type(self).__name__ |
| return '{0}({1!r})'.format(name, self.directory) |
|
|