0 $ˆl–Ðz¾”Ëm q@=q¦nõ1hß „�ÁÀ¿?÷"""Fanout cache automatically shards keys and values.""" import contextlib as cl import functools import itertools as it import operator import os.path as op import sqlite3 import tempfile import time from .core import DEFAULT_SETTINGS, ENOVAL, Cache, Disk, Timeout from .persistent import Deque, Index class FanoutCache: """Cache that shards keys and values.""" def __init__( self, directory=None, shards=8, timeout=0.010, disk=Disk, **settings ): """Initialize cache instance. :param str directory: cache directory :param int shards: number of shards to distribute writes :param float timeout: SQLite connection timeout :param disk: `Disk` instance for serialization :param settings: any of `DEFAULT_SETTINGS` """ if directory is None: directory = tempfile.mkdtemp(prefix='diskcache-') directory = str(directory) directory = op.expanduser(directory) directory = op.expandvars(directory) default_size_limit = DEFAULT_SETTINGS['size_limit'] size_limit = settings.pop('size_limit', default_size_limit) / shards self._count = shards self._directory = directory self._disk = disk self._shards = tuple( Cache( directory=op.join(directory, '%03d' % num), timeout=timeout, disk=disk, size_limit=size_limit, **settings, ) for num in range(shards) ) self._hash = self._shards[0].disk.hash self._caches = {} self._deques = {} self._indexes = {} @property def directory(self): """Cache directory.""" return self._directory def __getattr__(self, name): safe_names = {'timeout', 'disk'} valid_name = name in DEFAULT_SETTINGS or name in safe_names assert valid_name, 'cannot access {} in cache shard'.format(name) return getattr(self._shards[0], name) @cl.contextmanager def transact(self, retry=True): """Context manager to perform a transaction by locking the cache. While the cache 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. Blocks until transactions are held on all cache shards by retrying as necessary. >>> cache = FanoutCache() >>> with cache.transact(): # Atomically increment two keys. ... _ = cache.incr('total', 123.4) ... _ = cache.incr('count', 1) >>> with cache.transact(): # Atomically calculate average. ... average = cache['total'] / cache['count'] >>> average 123.4 :return: context manager for use in `with` statement """ assert retry, 'retry must be True in FanoutCache' with cl.ExitStack() as stack: for shard in self._shards: shard_transaction = shard.transact(retry=True) stack.enter_context(shard_transaction) yield def set(self, key, value, expire=None, read=False, tag=None, retry=False): """Set `key` and `value` item in cache. When `read` is `True`, `value` should be a file-like object opened for reading in binary mode. If database timeout occurs then fails silently unless `retry` is set to `True` (default `False`). :param key: key for item :param value: value for item :param float expire: seconds until the key expires (default None, no expiry) :param bool read: read value as raw bytes from file (default False) :param str tag: text to associate with key (default None) :param bool retry: retry if database timeout occurs