PK!Aaddcachy/__init__.py# -*- coding: utf-8 -*- from .cache_manager import CacheManager from .repository import Repository PK!@K cachy/cache_manager.py# -*- coding: utf-8 -*- import threading import types from .contracts.factory import Factory from .contracts.store import Store from .stores import ( DictStore, FileStore, RedisStore, MemcachedStore ) from .repository import Repository from .serializers import ( Serializer, JsonSerializer, MsgPackSerializer, PickleSerializer ) class CacheManager(Factory, threading.local): """ A CacheManager is a pool of cache stores. """ _serializers = { 'json': JsonSerializer(), 'msgpack': MsgPackSerializer(), 'pickle': PickleSerializer() } def __init__(self, config): super(CacheManager, self).__init__() self._config = config self._stores = {} self._custom_creators = {} self._serializer = self._resolve_serializer(config.get('serializer', 'pickle')) def store(self, name=None): """ Get a cache store instance by name. :param name: The cache store name :type name: str :rtype: Repository """ if name is None: name = self.get_default_driver() self._stores[name] = self._get(name) return self._stores[name] def driver(self, name=None): """ Get a cache store instance by name. :param name: The cache store name :type name: str :rtype: Repository """ return self.store(name) def _get(self, name): """ Attempt to get the store from the local cache. :param name: The store name :type name: str :rtype: Repository """ return self._stores.get(name, self._resolve(name)) def _resolve(self, name): """ Resolve the given store :param name: The store to resolve :type name: str :rtype: Repository """ config = self._get_config(name) if not config: raise RuntimeError('Cache store [%s] is not defined.' % name) if config['driver'] in self._custom_creators: repository = self._call_custom_creator(config) else: repository = getattr(self, '_create_%s_driver' % config['driver'])(config) if 'serializer' in config: serializer = self._resolve_serializer(config['serializer']) else: serializer = self._serializer repository.get_store().set_serializer(serializer) return repository def _call_custom_creator(self, config): """ Call a custom driver creator. :param config: The driver configuration :type config: dict :rtype: Repository """ creator = self._custom_creators[config['driver']](config) if isinstance(creator, Store): creator = self.repository(creator) if not isinstance(creator, Repository): raise RuntimeError('Custom creator should return a Repository instance.') return creator def _create_dict_driver(self, config): """ Create an instance of the dict cache driver. :param config: The driver configuration :type config: dict :rtype: Repository """ return self.repository(DictStore()) def _create_file_driver(self, config): """ Create an instance of the file cache driver. :param config: The driver configuration :type config: dict :rtype: Repository """ kwargs = { 'directory': config['path'] } if 'hash_type' in config: kwargs['hash_type'] = config['hash_type'] return self.repository(FileStore(**kwargs)) def _create_redis_driver(self, config): """ Create an instance of the redis cache driver. :param config: The driver configuration :type config: dict :return: Repository """ return self.repository(RedisStore(**config)) def _create_memcached_driver(self, config): """ Create an instance of the redis cache driver. :param config: The driver configuration :type config: dict :return: Repository """ return self.repository(MemcachedStore(**config)) def repository(self, store): """ Create a new cache repository with the given implementation. :param store: The cache store implementation instance :type store: Store :rtype: Repository """ repository = Repository(store) return repository def _get_prefix(self, config): """ Get the cache prefix. :param config: The configuration :type config: dict :rtype: str """ return config.get('prefix', '') def _get_config(self, name): """ Get the cache connection configuration. :param name: The cache name :type name: str :rtype: dict """ return self._config['stores'].get(name) def get_default_driver(self): """ Get the default cache driver name. :rtype: str :raises: RuntimeError """ if 'default' in self._config: return self._config['default'] if len(self._config['stores']) == 1: return list(self._config['stores'].keys())[0] raise RuntimeError('Missing "default" cache in configuration.') def set_default_driver(self, name): """ Set the default cache driver name. :param name: The default cache driver name :type name: str """ self._config['default'] = name def extend(self, driver, store): """ Register a custom driver creator. :param driver: The driver :type driver: name :param store: The store class :type store: Store or callable :rtype: self """ self._custom_creators[driver] = store return self def _resolve_serializer(self, serializer): """ Resolve the given serializer. :param serializer: The serializer to resolve :type serializer: str or Serializer :rtype: Serializer """ if isinstance(serializer, Serializer): return serializer if serializer in self._serializers: return self._serializers[serializer] raise RuntimeError('Unsupported serializer') def register_serializer(self, name, serializer): """ Register a new serializer. :param name: The name of the serializer :type name: str :param serializer: The serializer :type serializer: Serializer """ self._serializers[name] = serializer def __getattr__(self, item): return getattr(self.store(), item) def __call__(self, store=None, *args, **kwargs): if isinstance(store, (types.FunctionType, types.MethodType)): fn = store if len(args) > 0: store = args[0] args = args[1:] if len(args) > 1 else [] else: store = None args = (fn,) + args return self.store(store)(*args, **kwargs) else: return self.store(store)(*args, **kwargs) PK!cachy/contracts/__init__.py# -*- coding: utf-8 -*- PK!KCCcachy/contracts/factory.py# -*- coding: utf-8 -*- class Factory(object): """ Represent a cahce factory. """ def store(self, name=None): """ Get a cache store instance by name. :param name: The cache store name :type name: str :rtype: mixed """ raise NotImplementedError() PK!7 ~ ~ cachy/contracts/repository.py# -*- coding: utf-8 -*- class Repository(object): def has(self, key): """ Determine if an item exists in the cache. :param key: The cache key :type key: str :rtype: bool """ raise NotImplementedError() def get(self, key, default=None): """ Retrieve an item from the cache by key. :param key: The cache key :type key: str :param default: The default value to return :type default: mixed :rtype: mixed """ raise NotImplementedError() def pull(self, key, default=None): """ Retrieve an item from the cache by key and delete ir. :param key: The cache key :type key: str :param default: The default value to return :type default: mixed :rtype: mixed """ raise NotImplementedError() def put(self, key, value, minutes): """ Store an item in the cache. :param key: The cache key :type key: str :param value: The cache value :type value: mixed :param minutes: The lifetime in minutes of the cached value :type minutes: int or datetime """ raise NotImplementedError() def add(self, key, value, minutes): """ Store an item in the cache if it does not exist. :param key: The cache key :type key: str :param value: The cache value :type value: mixed :param minutes: The lifetime in minutes of the cached value :type minutes: int or datetime :rtype: bool """ raise NotImplementedError() def forever(self, key, value): """ Store an item in the cache indefinitely. :param key: The cache key :type key: str :param value: The cache value :type value: mixed """ raise NotImplementedError() def remember(self, key, minutes, callback): """ Get an item from the cache, or store the default value. :param key: The cache key :type key: str :param minutes: The lifetime in minutes of the cached value :type minutes: int or datetime :param callback: The default function :type callback: callable :rtype: mixed """ raise NotImplementedError() def remember_forever(self, key, callback): """ Get an item from the cache, or store the default value forever. :param key: The cache key :type key: str :param callback: The default function :type callback: callable :rtype: mixed """ raise NotImplementedError() def forget(self, key): """ Remove an item from the cache. :param key: The cache key :type key: str :rtype: bool """ raise NotImplementedError() PK!" " cachy/contracts/store.py# -*- coding: utf-8 -*- from ..serializers import PickleSerializer class Store(object): """ Abstract class representing a cache store. """ _serializer = PickleSerializer() def get(self, key): """ Retrieve an item from the cache by key. :param key: The cache key :type key: str :return: The cache value """ raise NotImplementedError() def put(self, key, value, minutes): """ Store an item in the cache for a given number of minutes. :param key: The cache key :type key: str :param value: The cache value :type value: mixed :param minutes: The lifetime in minutes of the cached value :type minutes: int """ raise NotImplementedError() def increment(self, key, value=1): """ Increment the value of an item in the cache. :param key: The cache key :type key: str :param value: The increment value :type value: int :rtype: int or bool """ raise NotImplementedError() def decrement(self, key, value=1): """ Decrement the value of an item in the cache. :param key: The cache key :type key: str :param value: The decrement value :type value: int :rtype: int or bool """ raise NotImplementedError() def forever(self, key, value): """ Store an item in the cache indefinitely. :param key: The cache key :type key: str :param value: The value :type value: mixed """ raise NotImplementedError() def forget(self, key): """ Remove an item from the cache. :param key: The cache key :type key: str :rtype: bool """ raise NotImplementedError() def flush(self): """ Remove all items from the cache. """ raise NotImplementedError() def get_prefix(self): """ Get the cache key prefix. :rtype: str """ raise NotImplementedError() def set_serializer(self, serializer): """ Set the serializer. :param serializer: The serializer :type serializer: cachy.serializers.Serializer :rtype: Store """ self._serializer = serializer return self def unserialize(self, data): return self._serializer.unserialize(data) def serialize(self, data): return self._serializer.serialize(data) PK!w!cachy/contracts/taggable_store.py# -*- coding: utf-8 -*- from .store import Store from ..tagged_cache import TaggedCache from ..tag_set import TagSet class TaggableStore(Store): def tags(self, *names): """ Begin executing a new tags operation. :param names: The tags :type names: tuple :rtype: cachy.tagged_cache.TaggedCache """ if len(names) == 1 and isinstance(names[0], list): names = names[0] return TaggedCache(self, TagSet(self, names)) PK!Êeecachy/helpers.py# -*- coding: utf-8 -*- def value(val): if callable(val): return val() return val PK!7L{{cachy/redis_tagged_cache.py# -*- coding: utf-8 -*- import hashlib from .tagged_cache import TaggedCache from .utils import encode class RedisTaggedCache(TaggedCache): def forever(self, key, value): """ Store an item in the cache indefinitely. :param key: The cache key :type key: str :param value: The value :type value: mixed """ namespace = self._tags.get_namespace() self._push_forever_keys(namespace, key) self._store.forever( '%s:%s' % (hashlib.sha1(encode(self._tags.get_namespace())).hexdigest(), key), value ) def flush(self): """ Remove all items from the cache. """ self._delete_forever_keys() super(RedisTaggedCache, self).flush() def _push_forever_keys(self, namespace, key): """ Store a copy of the full key for each namespace segment. :type namespace: str :type key: str """ full_key = '%s%s:%s' % (self.get_prefix(), hashlib.sha1(encode(self._tags.get_namespace())).hexdigest(), key) for segment in namespace.split('|'): self._store.connection().lpush(self._forever_key(segment), full_key) def _delete_forever_keys(self): """ Delete all of the items that were stored forever. """ for segment in self._tags.get_namespace().split('|'): segment = self._forever_key(segment) self._delete_forever_values(segment) self._store.connection().delete(segment) def _delete_forever_values(self, forever_key): """ Delete all of the keys that have been stored forever. :type forever_key: str """ forever = self._store.connection().lrange(forever_key, 0, -1) if len(forever) > 0: self._store.connection().delete(*forever) def _forever_key(self, segment): """ Get the forever reference key for the segment. :type segment: str :rtype: str """ return '%s%s:forever' % (self.get_prefix(), segment) PK!]5: : cachy/repository.py# -*- coding: utf-8 -*- import math import datetime import types import hashlib from functools import wraps from .contracts.repository import Repository as CacheContract from .helpers import value from .utils import encode, decode class Repository(CacheContract): _default = 60 def __init__(self, store): """ :param store: The underlying cache store :type store: Store """ self._store = store def has(self, key): """ Determine if an item exists in the cache. :param key: The cache key :type key: str :rtype: bool """ return self.get(key) is not None def get(self, key, default=None): """ Retrieve an item from the cache by key. :param key: The cache key :type key: str :param default: The default value to return :type default: mixed :rtype: mixed """ val = self._store.get(key) if val is None: return value(default) return val def pull(self, key, default=None): """ Retrieve an item from the cache by key and delete ir. :param key: The cache key :type key: str :param default: The default value to return :type default: mixed :rtype: mixed """ val = self.get(key, default) self.forget(key) return val def put(self, key, val, minutes): """ Store an item in the cache. :param key: The cache key :type key: str :param val: The cache value :type val: mixed :param minutes: The lifetime in minutes of the cached value :type minutes: int|datetime """ minutes = self._get_minutes(minutes) if minutes is not None: self._store.put(key, val, minutes) def add(self, key, val, minutes): """ Store an item in the cache if it does not exist. :param key: The cache key :type key: str :param val: The cache value :type val: mixed :param minutes: The lifetime in minutes of the cached value :type minutes: int|datetime :rtype: bool """ if hasattr(self._store, 'add'): return self._store.add(key, val, self._get_minutes(minutes)) if not self.has(key): self.put(key, val, minutes) return True return False def forever(self, key, val): """ Store an item in the cache indefinitely. :param key: The cache key :type key: str :param val: The cache value :type val: mixed """ self._store.forever(key, val) def remember(self, key, minutes, callback): """ Get an item from the cache, or store the default value. :param key: The cache key :type key: str :param minutes: The lifetime in minutes of the cached value :type minutes: int or datetime :param callback: The default function :type callback: mixed :rtype: mixed """ # If the item exists in the cache we will just return this immediately # otherwise we will execute the given callback and cache the result # of that execution for the given number of minutes in storage. val = self.get(key) if val is not None: return val val = value(callback) self.put(key, val, minutes) return val def remember_forever(self, key, callback): """ Get an item from the cache, or store the default value forever. :param key: The cache key :type key: str :param callback: The default function :type callback: mixed :rtype: mixed """ # If the item exists in the cache we will just return this immediately # otherwise we will execute the given callback and cache the result # of that execution forever. val = self.get(key) if val is not None: return val val = value(callback) self.forever(key, val) return val def forget(self, key): """ Remove an item from the cache. :param key: The cache key :type key: str :rtype: bool """ success = self._store.forget(key) return success def get_default_cache_time(self): """ Get the default cache time. :rtype: int """ return self._default def set_default_cache_time(self, minutes): """ Set the default cache time. :param minutes: The default cache time :type minutes: int :rtype: self """ self._default = minutes return self def get_store(self): """ Get the cache store implementation. :rtype: Store """ return self._store def __getitem__(self, item): return self.get(item) def __setitem__(self, key, val): self.put(key, val, self._default) def __delitem__(self, key): self.forget(key) def _get_minutes(self, duration): """ Calculate the number of minutes with the given duration. :param duration: The duration :type duration: int or datetime :rtype: int or None """ if isinstance(duration, datetime.datetime): from_now = (duration - datetime.datetime.now()).total_seconds() from_now = math.ceil(from_now / 60) if from_now > 0: return from_now return return duration def _hash(self, value): """ Calculate the hash given a value. :param value: The value to hash :type value: str or bytes :rtype: str """ return hashlib.sha1(encode(value)).hexdigest() def _get_key(self, fn, args, kwargs): """ Calculate a cache key given a function, args and kwargs. :param fn: The function :type fn: callable or str :param args: The function args :type args: tuple :param kwargs: The function kwargs :type kwargs: dict :rtype: str """ if args: serialized_arguments = ( self._store.serialize(args[1:]) + self._store.serialize([(k, kwargs[k]) for k in sorted(kwargs.keys())]) ) else: serialized_arguments = self._store.serialize([(k, kwargs[k]) for k in sorted(kwargs.keys())]) if isinstance(fn, types.MethodType): key = self._hash('%s.%s.%s' % (fn.__self__.__class__.__name__, args[0].__name__, serialized_arguments)) elif isinstance(fn, types.FunctionType): key = self._hash('%s.%s' % (fn.__name__, serialized_arguments)) else: key = '%s:' % fn + self._hash(serialized_arguments) return key def __getattr__(self, item): try: return object.__getattribute__(self, item) except AttributeError: return getattr(self._store, item) def __call__(self, *args, **kwargs): if args and isinstance(args[0], (types.FunctionType, types.MethodType)): fn = args[0] @wraps(fn) def wrapper(*a, **kw): return self.remember( self._get_key(fn, a, kw), self._default, lambda: fn(*a, **kw) ) return wrapper else: k = kwargs.get('key') minutes = kwargs.get('minutes', self._default) def decorated(fn): key = k @wraps(fn) def wrapper(*a, **kw): return self.remember( self._get_key(key or fn, a, kw), minutes, lambda: fn(*a, **kw) ) return wrapper return decorated PK!Hׇcachy/serializers/__init__.py# -*- coding: utf-8 -*- from .serializer import Serializer from .json_serializer import JsonSerializer from .msgpack_serializer import MsgPackSerializer from .pickle_serializer import PickleSerializer PK!18L$cachy/serializers/json_serializer.py# -*- coding: utf-8 -*- try: import simplejson as json except ImportError: import json from cachy.utils import decode from .serializer import Serializer class JsonSerializer(Serializer): """ Serializer that uses JSON representations. """ def serialize(self, data): """ Serialize data. :param data: The data to serialize :type data: mixed :rtype: str """ return json.dumps(data) def unserialize(self, data): """ Unserialize data. :param data: The data to unserialize :type data: mixed :rtype: str """ return json.loads(decode(data)) PK!..'cachy/serializers/msgpack_serializer.py# -*- coding: utf-8 -*- try: import msgpack except ImportError: msgpack = None from .serializer import Serializer class MsgPackSerializer(Serializer): """ Serializer that uses `msgpack `_ representations. By default, this serializer does not support serializing custom objects. """ def serialize(self, data): """ Serialize data. :param data: The data to serialize :type data: mixed :rtype: str """ return msgpack.packb(data, use_bin_type=True) def unserialize(self, data): """ Unserialize data. :param data: The data to unserialize :type data: mixed :rtype: str """ return msgpack.unpackb(data, encoding='utf-8') PK!TPP&cachy/serializers/pickle_serializer.py# -*- coding: utf-8 -*- from functools import partial try: import cPickle as pickle except ImportError: # noqa import pickle # Serialize pickle dumps using the highest pickle protocol (binary, default # uses ascii) dumps = partial(pickle.dumps, protocol=pickle.HIGHEST_PROTOCOL) loads = pickle.loads from .serializer import Serializer class PickleSerializer(Serializer): """ Serializer that uses the pickle module. """ def serialize(self, data): """ Serialize data. :param data: The data to serialize :type data: mixed :rtype: str """ return dumps(data) def unserialize(self, data): """ Unserialize data. :param data: The data to unserialize :type data: mixed :rtype: str """ return loads(data) PK!Jcachy/serializers/serializer.py# -*- coding: utf-8 -*- class Serializer(object): """ Abstract serializer. """ def serialize(self, data): """ Serialize data. :param data: The data to serialize :type data: mixed :rtype: str """ raise NotImplementedError() def unserialize(self, data): """ Unserialize data. :param data: The data to unserialize :type data: mixed :rtype: str """ raise NotImplementedError() PK!&~[Tcachy/stores/__init__.py# -*- coding: utf-8 -*- from .dict_store import DictStore from .file_store import FileStore from .memcached_store import MemcachedStore from .redis_store import RedisStore from .null_store import NullStore PK!ttcachy/stores/dict_store.py# -*- coding: utf-8 -*- import time import math from ..contracts.taggable_store import TaggableStore class DictStore(TaggableStore): """ A cache store using a dictionary as its backend. """ def __init__(self): self._storage = {} def get(self, key): """ Retrieve an item from the cache by key. :param key: The cache key :type key: str :return: The cache value """ return self._get_payload(key)[0] def _get_payload(self, key): """ Retrieve an item and expiry time from the cache by key. :param key: The cache key :type key: str :rtype: dict """ payload = self._storage.get(key) # If the key does not exist, we return nothing if not payload: return (None, None) expire = payload[0] # If the current time is greater than expiration timestamps we will delete # the entry if round(time.time()) >= expire: self.forget(key) return (None, None) data = payload[1] # Next, we'll extract the number of minutes that are remaining for a cache # so that we can properly retain the time for things like the increment # operation that may be performed on the cache. We'll round this out. time_ = math.ceil((expire - round(time.time())) / 60.) return (data, time_) def put(self, key, value, minutes): """ Store an item in the cache for a given number of minutes. :param key: The cache key :type key: str :param value: The cache value :type value: mixed :param minutes: The lifetime in minutes of the cached value :type minutes: int """ self._storage[key] = (self._expiration(minutes), value) def increment(self, key, value=1): """ Increment the value of an item in the cache. :param key: The cache key :type key: str :param value: The increment value :type value: int :rtype: int or bool """ data, time_ = self._get_payload(key) integer = int(data) + value self.put(key, integer, int(time_)) return integer def decrement(self, key, value=1): """ Decrement the value of an item in the cache. :param key: The cache key :type key: str :param value: The decrement value :type value: int :rtype: int or bool """ return self.increment(key, value * -1) def forever(self, key, value): """ Store an item in the cache indefinitely. :param key: The cache key :type key: str :param value: The increment value :type value: int """ self.put(key, value, 0) def forget(self, key): """ Remove an item from the cache. :param key: The cache key :type key: str :rtype: bool """ if key in self._storage: del self._storage[key] return True return False def flush(self): """ Remove all items from the cache. """ self._storage = {} def _expiration(self, minutes): """ Get the expiration time based on the given minutes. :param minutes: The minutes :type minutes: int :rtype: int """ if minutes == 0: return 9999999999 return round(time.time()) + (minutes * 60) def get_prefix(self): """ Get the cache key prefix. :rtype: str """ return '' PK!틮cachy/stores/file_store.py# -*- coding: utf-8 -*- import os import time import math import hashlib from ..contracts.store import Store from ..utils import mkdir_p, encode class FileStore(Store): """ A cache store using the filesystem as its backend. """ _HASHES = { 'md5': (hashlib.md5, 2), 'sha1': (hashlib.sha1, 4), 'sha256': (hashlib.sha256, 8) } def __init__(self, directory, hash_type='sha256'): """ :param directory: The cache directory :type directory: str """ self._directory = directory if hash_type not in self._HASHES: raise ValueError('hash_type "{}" is not valid.'.format(hash_type)) self._hash_type = hash_type def get(self, key): """ Retrieve an item from the cache by key. :param key: The cache key :type key: str :return: The cache value """ return self._get_payload(key).get('data') def _get_payload(self, key): """ Retrieve an item and expiry time from the cache by key. :param key: The cache key :type key: str :rtype: dict """ path = self._path(key) # If the file doesn't exists, we obviously can't return the cache so we will # just return null. Otherwise, we'll get the contents of the file and get # the expiration UNIX timestamps from the start of the file's contents. if not os.path.exists(path): return {'data': None, 'time': None} with open(path, 'rb') as fh: contents = fh.read() expire = int(contents[:10]) # If the current time is greater than expiration timestamps we will delete # the file and return null. This helps clean up the old files and keeps # this directory much cleaner for us as old files aren't hanging out. if round(time.time()) >= expire: self.forget(key) return {'data': None, 'time': None} data = self.unserialize(contents[10:]) # Next, we'll extract the number of minutes that are remaining for a cache # so that we can properly retain the time for things like the increment # operation that may be performed on the cache. We'll round this out. time_ = math.ceil((expire - round(time.time())) / 60.) return {'data': data, 'time': time_} def put(self, key, value, minutes): """ Store an item in the cache for a given number of minutes. :param key: The cache key :type key: str :param value: The cache value :type value: mixed :param minutes: The lifetime in minutes of the cached value :type minutes: int """ value = encode(str(self._expiration(minutes))) + encode(self.serialize(value)) path = self._path(key) self._create_cache_directory(path) with open(path, 'wb') as fh: fh.write(value) def _create_cache_directory(self, path): """ Create the file cache directory if necessary :param path: The cache path :type path: str """ mkdir_p(os.path.dirname(path)) def increment(self, key, value=1): """ Increment the value of an item in the cache. :param key: The cache key :type key: str :param value: The increment value :type value: int :rtype: int or bool """ raw = self._get_payload(key) integer = int(raw['data']) + value self.put(key, integer, int(raw['time'])) return integer def decrement(self, key, value=1): """ Decrement the value of an item in the cache. :param key: The cache key :type key: str :param value: The decrement value :type value: int :rtype: int or bool """ return self.increment(key, value * -1) def forever(self, key, value): """ Store an item in the cache indefinitely. :param key: The cache key :type key: str :param value: The increment value :type value: int """ self.put(key, value, 0) def forget(self, key): """ Remove an item from the cache. :param key: The cache key :type key: str :rtype: bool """ path = self._path(key) if os.path.exists(path): os.remove(path) return True return False def flush(self): """ Remove all items from the cache. """ if os.path.isdir(self._directory): for root, dirs, files in os.walk(self._directory, topdown=False): for name in files: os.remove(os.path.join(root, name)) for name in dirs: os.rmdir(os.path.join(root, name)) def _path(self, key): """ Get the full path for the given cache key. :param key: The cache key :type key: str :rtype: str """ hash_type, parts_count = self._HASHES[self._hash_type] h = hash_type(encode(key)).hexdigest() parts = [h[i:i+2] for i in range(0, len(h), 2)][:parts_count] return os.path.join(self._directory, os.path.sep.join(parts), h) def _expiration(self, minutes): """ Get the expiration time based on the given minutes. :param minutes: The minutes :type minutes: int :rtype: int """ if minutes == 0: return 9999999999 return int(round(time.time()) + (minutes * 60)) def get_prefix(self): """ Get the cache key prefix. :rtype: str """ return '' return '' PK! N cachy/stores/memcached_store.py# -*- coding: utf-8 -*- try: from pylibmc import memcache except ImportError: try: import memcache except ImportError: memcache = None from ..contracts.taggable_store import TaggableStore class MemcachedStore(TaggableStore): def __init__(self, servers, prefix='', **kwargs): # Removing potential "driver" key kwargs.pop('driver', None) self._prefix = prefix self._memcache = memcache.Client(servers, **kwargs) def get(self, key): """ Retrieve an item from the cache by key. :param key: The cache key :type key: str :return: The cache value """ return self._memcache.get(self._prefix + key) def put(self, key, value, minutes): """ Store an item in the cache for a given number of minutes. :param key: The cache key :type key: str :param value: The cache value :type value: mixed :param minutes: The lifetime in minutes of the cached value :type minutes: int """ self._memcache.set(self._prefix + key, value, minutes * 60) def add(self, key, val, minutes): """ Store an item in the cache if it does not exist. :param key: The cache key :type key: str :param val: The cache value :type val: mixed :param minutes: The lifetime in minutes of the cached value :type minutes: int :rtype: bool """ return self._memcache.add(self._prefix + key, val, minutes * 60) def increment(self, key, value=1): """ Increment the value of an item in the cache. :param key: The cache key :type key: str :param value: The increment value :type value: int :rtype: int or bool """ return self._memcache.incr(self._prefix + key, value) def decrement(self, key, value=1): """ Decrement the value of an item in the cache. :param key: The cache key :type key: str :param value: The decrement value :type value: int :rtype: int or bool """ return self._memcache.decr(self._prefix + key, value) def forever(self, key, value): """ Store an item in the cache indefinitely. :param key: The cache key :type key: str :param value: The value :type value: mixed """ self.put(key, value, 0) def forget(self, key): """ Remove an item from the cache. :param key: The cache key :type key: str :rtype: bool """ return self._memcache.delete(self._prefix + key) def flush(self): """ Remove all items from the cache. """ self._memcache.flush_all() def get_prefix(self): """ Get the cache key prefix. :rtype: str """ return self._prefix PK!4cachy/stores/null_store.py# -*- coding: utf-8 -*- from ..contracts.store import Store class NullStore(Store): """ This cache store implementation is meant to be used only in development or test environments and it never stores anything. """ def get(self, key): """ Retrieve an item from the cache by key. :param key: The cache key :type key: str :return: The cache value """ pass def put(self, key, value, minutes): """ Store an item in the cache for a given number of minutes. :param key: The cache key :type key: str :param value: The cache value :type value: mixed :param minutes: The lifetime in minutes of the cached value :type minutes: int """ pass def increment(self, key, value=1): """ Increment the value of an item in the cache. :param key: The cache key :type key: str :param value: The increment value :type value: int :rtype: int or bool """ pass def decrement(self, key, value=1): """ Decrement the value of an item in the cache. :param key: The cache key :type key: str :param value: The decrement value :type value: int :rtype: int or bool """ pass def forever(self, key, value): """ Store an item in the cache indefinitely. :param key: The cache key :type key: str :param value: The increment value :type value: int """ pass def forget(self, key): """ Remove an item from the cache. :param key: The cache key :type key: str :rtype: bool """ pass def flush(self): """ Remove all items from the cache. """ pass def get_prefix(self): """ Get the cache key prefix. :rtype: str """ return '' PK!2 # cachy/stores/redis_store.py# -*- coding: utf-8 -*- try: from redis import StrictRedis except ImportError: StrictRedis = None from ..contracts.taggable_store import TaggableStore from ..redis_tagged_cache import RedisTaggedCache from ..tag_set import TagSet class RedisStore(TaggableStore): """ A cache store using the Redis as its backend. """ def __init__(self, host='localhost', port=6379, db=0, password=None, prefix='', redis_class=StrictRedis, **kwargs): # Removing potential "driver" key kwargs.pop('driver', None) self._prefix = prefix self._redis = redis_class(host=host, port=port, db=db, password=password, **kwargs) def get(self, key): """ Retrieve an item from the cache by key. :param key: The cache key :type key: str :return: The cache value """ value = self._redis.get(self._prefix + key) if value is not None: return self.unserialize(value) def put(self, key, value, minutes): """ Store an item in the cache for a given number of minutes. :param key: The cache key :type key: str :param value: The cache value :type value: mixed :param minutes: The lifetime in minutes of the cached value :type minutes: int """ value = self.serialize(value) minutes = max(1, minutes) self._redis.setex(self._prefix + key, minutes * 60, value) def increment(self, key, value=1): """ Increment the value of an item in the cache. :param key: The cache key :type key: str :param value: The increment value :type value: int :rtype: int or bool """ return self._redis.incrby(self._prefix + key, value) def decrement(self, key, value=1): """ Decrement the value of an item in the cache. :param key: The cache key :type key: str :param value: The decrement value :type value: int :rtype: int or bool """ return self._redis.decr(self._prefix + key, value) def forever(self, key, value): """ Store an item in the cache indefinitely. :param key: The cache key :type key: str :param value: The value to store :type value: mixed """ value = self.serialize(value) self._redis.set(self._prefix + key, value) def forget(self, key): """ Remove an item from the cache. :param key: The cache key :type key: str :rtype: bool """ return bool(self._redis.delete(self._prefix + key)) def flush(self): """ Remove all items from the cache. """ return self._redis.flushdb() def get_prefix(self): """ Get the cache key prefix. :rtype: str """ return self._prefix def connection(self): return self._redis def tags(self, *names): """ Begin executing a new tags operation. :param names: The tags :type names: tuple :rtype: cachy.tagged_cache.TaggedCache """ return RedisTaggedCache(self, TagSet(self, names)) PK!dcachy/tag_set.py# -*- coding: utf-8 -*- import uuid class TagSet(object): def __init__(self, store, names=None): """ :param store: The cache store implementation :type store: cachy.contracts.store.Store :param names: The tags names :type names: list or tuple """ self._store = store self._names = names or [] def reset(self): """ Reset all tags in the set. """ list(map(self.reset_tag, self._names)) def tag_id(self, name): """ Get the unique tag identifier for a given tag. :param name: The tag :type name: str :rtype: str """ return self._store.get(self.tag_key(name)) or self.reset_tag(name) def _tag_ids(self): """ Get a list of tag identifiers for all of the tags in the set. :rtype: list """ return list(map(self.tag_id, self._names)) def get_namespace(self): """ Get a unique namespace that changes when any of the tags are flushed. :rtype: str """ return '|'.join(self._tag_ids()) def reset_tag(self, name): """ Reset the tag and return the new tag identifier. :param name: The tag :type name: str :rtype: str """ id_ = str(uuid.uuid4()).replace('-', '') self._store.forever(self.tag_key(name), id_) return id_ def tag_key(self, name): """ Get the tag identifier key for a given tag. :param name: The tag :type name: str :rtype: str """ return 'tag:%s:key' % name PK!qcachy/tagged_cache.py# -*- coding: utf-8 -*- import hashlib import datetime import math from .contracts.store import Store from .helpers import value from .utils import encode class TaggedCache(Store): """ """ def __init__(self, store, tags): """ :param store: The cache store implementation :type store: cachy.contracts.store.Store :param tags: The tag set :type tags: cachy.tag_set.TagSet """ self._store = store self._tags = tags def has(self, key): """ Determine if an item exists in the cache. :param key: The cache key :type key: str :rtype: bool """ return self.get(key) is not None def get(self, key, default=None): """ Retrieve an item from the cache by key. :param key: The cache key :type key: str :param default: The default value :type default: mixed :return: The cache value """ val = self._store.get(self.tagged_item_key(key)) if val is not None: return val return value(default) def put(self, key, value, minutes): """ Store an item in the cache for a given number of minutes. :param key: The cache key :type key: str :param value: The cache value :type value: mixed :param minutes: The lifetime in minutes of the cached value :type minutes: int or datetime """ minutes = self._get_minutes(minutes) if minutes is not None: return self._store.put(self.tagged_item_key(key), value, minutes) def add(self, key, val, minutes): """ Store an item in the cache if it does not exist. :param key: The cache key :type key: str :param val: The cache value :type val: mixed :param minutes: The lifetime in minutes of the cached value :type minutes: int|datetime :rtype: bool """ if not self.has(key): self.put(key, val, minutes) return True return False def increment(self, key, value=1): """ Increment the value of an item in the cache. :param key: The cache key :type key: str :param value: The increment value :type value: int :rtype: int or bool """ self._store.increment(self.tagged_item_key(key), value) def decrement(self, key, value=1): """ Decrement the value of an item in the cache. :param key: The cache key :type key: str :param value: The decrement value :type value: int :rtype: int or bool """ self._store.decrement(self.tagged_item_key(key), value) def forever(self, key, value): """ Store an item in the cache indefinitely. :param key: The cache key :type key: str :param value: The value :type value: mixed """ self._store.forever(self.tagged_item_key(key), value) def forget(self, key): """ Remove an item from the cache. :param key: The cache key :type key: str :rtype: bool """ self._store.forget(self.tagged_item_key(key)) def flush(self): """ Remove all items from the cache. """ self._tags.reset() def remember(self, key, minutes, callback): """ Get an item from the cache, or store the default value. :param key: The cache key :type key: str :param minutes: The lifetime in minutes of the cached value :type minutes: int or datetime :param callback: The default function :type callback: mixed :rtype: mixed """ # If the item exists in the cache we will just return this immediately # otherwise we will execute the given callback and cache the result # of that execution for the given number of minutes in storage. val = self.get(key) if val is not None: return val val = value(callback) self.put(key, val, minutes) return val def remember_forever(self, key, callback): """ Get an item from the cache, or store the default value forever. :param key: The cache key :type key: str :param callback: The default function :type callback: mixed :rtype: mixed """ # If the item exists in the cache we will just return this immediately # otherwise we will execute the given callback and cache the result # of that execution forever. val = self.get(key) if val is not None: return val val = value(callback) self.forever(key, val) return val def tagged_item_key(self, key): """ Get a fully qualified key for a tagged item. :param key: The cache key :type key: str :rtype: str """ return '%s:%s' % (hashlib.sha1(encode(self._tags.get_namespace())).hexdigest(), key) def get_prefix(self): """ Get the cache key prefix. :rtype: str """ return self._store.get_prefix() def _get_minutes(self, duration): """ Calculate the number of minutes with the given duration. :param duration: The duration :type duration: int or datetime :rtype: int or None """ if isinstance(duration, datetime.datetime): from_now = (duration - datetime.datetime.now()).total_seconds() from_now = math.ceil(from_now / 60) if from_now > 0: return from_now return return duration PK!|cachy/utils.py# -*- coding: utf-8 -*- import sys import os import errno PY2 = sys.version_info[0] == 2 PY3K = sys.version_info[0] >= 3 PY33 = sys.version_info >= (3, 3) if PY2: import imp long = long unicode = unicode basestring = basestring else: long = int unicode = str basestring = str def decode(string, encodings=None): if not PY2 and not isinstance(string, bytes): return string if encodings is None: encodings = ['utf-8', 'latin1', 'ascii'] for encoding in encodings: try: return string.decode(encoding) except UnicodeDecodeError: pass return string.decode(encodings[0], errors='ignore') def encode(string, encodings=None): if isinstance(string, bytes) or PY2 and isinstance(string, unicode): return string if encodings is None: encodings = ['utf-8', 'latin1', 'ascii'] for encoding in encodings: try: return string.encode(encoding) except UnicodeDecodeError: pass return string.encode(encodings[0], errors='ignore') def mkdir_p(path, mode=0o777): try: os.makedirs(path, mode) except OSError as exc: if exc.errno == errno.EEXIST and os.path.isdir(path): pass else: raise PK!++cachy/version.py# -*- coding: utf-8 -*- VERSION = '0.2.0' PK!Hrz{VWcachy-0.2.0.dist-info/WHEEL A н#V."jm)Afd~ dڡOsL`hAf`VJC˫ w  |PK!H[!"@cachy-0.2.0.dist-info/METADATAT0+Ң P5]PRQw[TP] j.&Bjr$hfЊDX|CCRx} #Eٱc"=E0&> [ vhk^C)dreZaPo-(tRi7Zu(TS~+cafK wIr캴m"Xd%pSq P E@xȿ*ϥf_g) R09x x1󐷫[:6dH%!bׅ&yO(sS#uyZjx|eGx>l77ƌƌ!:0yFDP#9PZa*AQj< 5FxqK~`4:єJ_J5Lk  +?"Fn0ѹ]f:l!0dO1{8h>ΦJ bɵIOǓy ‡Rf )%sV* 5_+lAXYK.+Ll+AsX;Gu":&1Wtҥب~ks*1#*H OgPK!HmDLcachy-0.2.0.dist-info/RECORDuɖX}= d3sYApex63Lksρw^FR֮` wY~nbtS;Y82 5-rhF# W*~BM-RuBq̬H 8|rM=~8*4n6 .mora䷲Ƹof\ a71yM<;xrv2@p4 6)y!ÿ}6C] 䤖YҾ4m.MYրr p1M`6=%0s4BŻ(˟<>!~PWLI ۪=ո$;y'Ӄf8P  _㲍i m}I)%܉3͂TbS2 q {qqsk.Y`rC۲7rqi1ui4|yC拎\.bo6\L'Ǣ#1x/ojK _f[_њL.83OTd4v49g$ ^OQӆ OlM!*D4l#v5]Ռc=巊;l,'S|2\qWBEJ,,JC0TVc{; eSa+4=_i٫ qB/lphKGۨU[V&NU$ 刺HםGe"l9b"#`UK!'ɿ^?O/8{K/kGb!RG38ij_oNZdP`ipBJ &EXH)0]z.L'YϹ h:[HNB H>&c[OeĊeϊY)ĶMVWMU/}OK/&҉s "[ُw~iۇ5 YzG4a !"ס6 qTgt;]B$0 [RzXz)idK N|%{W,׵h VSƬ|^wLn\xXh2Yua]r8J&/OS0!G>LjdMiuEY&R)jّ;]GV">&Fް7q#I#)neF4")$MΞ=AfW[wJOrv"jr˾ww6aٌf<*Q !¾w%j[ PK!Aaddcachy/__init__.pyPK!@K cachy/cache_manager.pyPK!rcachy/contracts/__init__.pyPK!KCCcachy/contracts/factory.pyPK!7 ~ ~ ?cachy/contracts/repository.pyPK!" " *cachy/contracts/store.pyPK!w!P5cachy/contracts/taggable_store.pyPK!Êee7cachy/helpers.pyPK!7L{{8cachy/redis_tagged_cache.pyPK!]5: : @cachy/repository.pyPK!Hׇ2acachy/serializers/__init__.pyPK!18L$7bcachy/serializers/json_serializer.pyPK!..'$ecachy/serializers/msgpack_serializer.pyPK!TPP&hcachy/serializers/pickle_serializer.pyPK!J+lcachy/serializers/serializer.pyPK!&~[Tincachy/stores/__init__.pyPK!ttnocachy/stores/dict_store.pyPK!틮~cachy/stores/file_store.pyPK! N cachy/stores/memcached_store.pyPK!4cachy/stores/null_store.pyPK!2 # cachy/stores/redis_store.pyPK!d6cachy/tag_set.pyPK!qcachy/tagged_cache.pyPK!|cachy/utils.pyPK!++,cachy/version.pyPK!Hrz{VWcachy-0.2.0.dist-info/WHEELPK!H[!"@cachy-0.2.0.dist-info/METADATAPK!HmDLcachy-0.2.0.dist-info/RECORDPK