cache.py 4.91 KB
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import unicode_literals, absolute_import, print_function

import string
import inspect
import functools
try:
    import cPickle as pickle
except ImportError:
    import pickle

import redis
from django.conf import settings
from utils.common import big_data_iter
from qa.cache.cache_v2 import RedisWrapper  # todo RedisWrapper 需要独立出来


class ViewRecordBase(object):
    def __init__(self, model_name, redis_conf):
        self.model_name = model_name

        self.__pool = redis.ConnectionPool(**redis_conf)
        self.client = redis.StrictRedis(connection_pool=self.__pool)

    def __getitem__(self, item):
        return self.client.hget(self.model_name, item) or 0

    def __setitem__(self, key, value):
        return self.client.hset(self.model_name, key, value)

    def view_hmget(self, item_list):
        result = []
        for _ids in big_data_iter(item_list):
            _vs = self.client.hmget(self.model_name, _ids)
            result.extend(_vs)
        return result


class RedisCache(object):
    def __init__(self, client):
        self.client = client

    def get(self, key):
        data = self.client.get(key)
        if data is not None:
            data = pickle.loads(data)
        return data

    def set(self, key, value, expire=60):
        data = pickle.dumps(value, protocol=pickle.HIGHEST_PROTOCOL)
        self.client.set(key, data, expire)

    def invalidate(self, key):
        self.client.delete(key)

    def __call__(self, key_str, key_args=None, expire=60):
        def _cache(fn):
            @functools.wraps(fn)
            def _wrap(*args, **kwargs):
                argvalues = inspect.getcallargs(fn, *args, **kwargs)
                cache_key = self.get_cache_key(key_str, key_args, argvalues)

                cache_data = self.get(cache_key)
                if cache_data is None:
                    cache_data = fn(*args, **kwargs)
                    self.set(cache_key, cache_data, expire)
                return cache_data
            return _wrap
        return _cache

    @classmethod
    def get_key_args(cls, key_str):
        """从`key_str`里自动获取args"""
        args = []
        for item in string.Formatter().parse(key_str):
            if item[1] is None:
                continue
            if not item[1]:
                raise Exception(u'不能使用位置参数 如{}, {0}')
            args.append(item[1])
        if not args:
            return None
        return args

    @classmethod
    def get_cache_key(cls, key_str, key_args=None, argvalues=None):
        """生成缓存key

        :param key_str:  @str, 缓存key, 如果有动态参数, 在`key_args`里指定, 字符串按`Format String Syntax`
        :param key_args: @list, 缓存key的参数名k列表,列表的元素有三种写法:
                            1. 元素是字符串,说明key_str 的参数名和取值的参数名一致,直接获取
                            2. 元素是列表,长度为2
                                第一个元素是字符串,为key_str 的参数名和取值的参数名
                                第二个元素是函数,以获取到的值为参数的函数
                            3. 元素是列表,长度为3
                                第一个元素和第二个元素是字符串,分别表示key_str的参数名和取值的参数名
                                第三个元素是函数,以获取到的值为参数的函数
                            [key1, key2, key3...]
                            [(key1, fn1), (key2, fn2)...]
                            [(str_key1, val_key1, fn1), (str_key2, val_key2, fn2)...]
        :param argvalues: @dict 参数值
        """
        if key_args is None:
            key_args = cls.get_key_args(key_str)
        if key_args is not None and isinstance(key_args, (list, tuple)):
            params = {}
            for item in key_args:
                if isinstance(item, (list, tuple)):
                    item_len = len(item)
                    if item_len == 2:
                        # item 有两个元素,string 里的参数名与取值的参数名一致
                        k, fnk = item[:2]
                        str_k = val_k = k
                    elif item_len >= 3:
                        # item 有三个元素,按(str参数名, 取值参数名, 处理函数)来处理
                        str_k, val_k, fnk = item[:3]
                    params[str_k] = fnk(argvalues[val_k])
                else:
                    str_k = val_k = item
                    params[str_k] = argvalues[val_k]
            cache_key = key_str.format(**params)
        else:
            cache_key = key_str
        return cache_key


common_cache = RedisWrapper('__common_cache', settings.REDIS_TOPIC_1ST['page_cache'])
picture_tool_cache = RedisWrapper('__picture_tool', settings.REDIS_TOPIC_1ST['page_cache'])
common_cache_wrapper = RedisCache(common_cache)