# -*- coding: utf-8 -*-
# Copyright 2012 Tomasz Wójcik <tomek@bthlabs.pl>. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY TOMASZ WÓJCIK ``AS IS'' AND ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
# SHALL TOMASZ WÓJCIK OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
# OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
# ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
# The views and conclusions contained in the software and documentation are those
# of the authors and should not be interpreted as representing official policies,
# either expressed or implied, of Tomasz Wójcik.
#
"""
:mod:`redcache.cache_manager` Module
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This module implements CacheManager class.
"""
import cPickle
from functools import wraps
import inspect
from .connection import get_current_connection
[docs]class CacheManager(object):
"""This is the base class for cache managers.
*key_base* is used to prefix cache keys generated by this instance.
*ttl* is cache key TTL in seconds. If it is omitted (or *None*) then keys
stored by this instance won't expire.
*connection* allows binding the instance to an explicit Redis connection.
If it is omitted global connection defined with
:func:`redcache.connection.use_connection` will be used.
If no connection is defined then no caching will happen."""
def __init__(self, key_base=u'cache', ttl=None, connection=None):
self._key_base = key_base
self._ttl = ttl
self._connection = connection
@property
[docs] def connection(self):
"""Connection property."""
if self._connection:
return self._connection
else:
return get_current_connection()
[docs] def key(self, f, args):
"""Key generator for function *f* with positional arguments *args*.
Example key: ``key_base:func_name:arg1:arg2``.
**Instance and class methods**
If the first argument of *f* is either *self* or *cls* it won't be used
while creating the key."""
f_args = inspect.getargspec(f).args
nameparts = [f.__name__]
if inspect.ismethod(f):
f_class = None
if inspect.isclass(f.im_self):
f_class = f.im_self # f is a class method
else:
f_class = f.im_self.__class__ # f is an instance method
nameparts = [f_class.__name__] + nameparts
argparts = [self._key_base, '.'.join(nameparts)]
if args and len(f_args) > 0:
idx = 0
if f_args[0] in ('cls', 'self'):
idx = 1
for arg in args[idx:]:
argparts.append(unicode(arg))
key = u':'.join(argparts)
return key
[docs] def after_load(self, data, f_args=None, f_kwargs=None):
"""Process and return *data* after loading it from Redis. *f_args* and
*f_kwargs* contain positional and keywords args passed to decorated
function.
Default implementation uses cPickle to unserialize data."""
return cPickle.loads(data)
[docs] def before_save(self, data, f_args=None, f_kwargs=None):
"""Process and return *data* before saving it to Redis. *f_args* and
*f_kwargs* contain positional and keywords args passed to decorated
function.
Default implementation uses cPickle to unserialize data."""
return cPickle.dumps(data)
[docs] def load(self, key, f_args=None, f_kwargs=None):
"""Load data for *key* from Redis. *f_args* and *f_kwargs* contain
positional and keywords args passed to decorated function.
Default implementation uses GET command."""
return self.connection.get(key)
[docs] def save(self, key, data, f_args=None, f_kwargs=None):
"""Save data into Redis *key*. *f_args* and *f_kwargs* contain
positional and keywords args passed to decorated function.
Default implementation uses SET command."""
self.connection.set(key, data)
[docs] def cache(self, f):
"""Decorate *f* function to enable caching it.
If the function returns *None* then it won't be cached."""
@wraps(f)
def wrapper(*args, **kwargs):
key = self.key(f, args)
data = None
if self.connection:
self.load(key, f_args=args, f_kwargs=kwargs)
if data:
data = self.after_load(data, f_args=args, f_kwargs=kwargs)
else:
data = f(*args, **kwargs)
if data is not None:
cached = self.before_save(data, f_args=args,
f_kwargs=kwargs)
if self.connection:
self.save(key, cached, f_args=args, f_kwargs=kwargs)
if self._ttl:
self.connection.expire(key, self._ttl)
return data
return wrapper
[docs]class DefaultCacheManager(CacheManager):
"""This is default cache manager for simple caching of generic
functions.
Basically it's equivalent to :py:class:`CacheManager` with default
settings."""
[docs] def cache(self, *args, **kwargs):
"""Decorate *f* function to enable caching it.
Use *ttl* keyword arg to override default infinite TTL.
If the function returns *None* then it won't be cached."""
ttl = kwargs.get('ttl', self._ttl)
def decorator(f):
@wraps(f)
def wrapper(*args, **kwargs):
key = self.key(f, args)
data = None
if self.connection:
self.load(key, f_args=args, f_kwargs=kwargs)
if data:
data = self.after_load(data, f_args=args, f_kwargs=kwargs)
else:
data = f(*args, **kwargs)
if data is not None:
cached = self.before_save(data, f_args=args,
f_kwargs=kwargs)
if self.connection:
self.save(key, cached, f_args=args,
f_kwargs=kwargs)
if ttl:
self.connection.expire(key, ttl)
return data
return wrapper
if args:
return decorator(args[0])
else:
return decorator
"""Instance of :py:class:`DefaultCacheManager` for convenient caching."""
default_cache = DefaultCacheManager()