django接口添加缓存:drf-extensions 深入学习


ImportError: cannot import name 'EmptyResultSet' from 'django.db.models.sql.datastructures'

最新版本的drf-extensions不兼容8.4发布的django3.1
原因django.core.exceptions.EmptyResultSet的兼容性导入被删除:
from django.db.models.sql.datastructures import EmptyResultSet
以上位置不再能导入EmptyResultSet,而drf-extensions使用了以上位置的EmptyResultSet

需要做如下修改:
把它替换成django.core.exceptions.EmptyResultSet即可
django接口添加缓存:drf-extensions 深入学习_第1张图片


了解key构造类KeyConstructor

class KeyConstructor:
    def __init__(self, memoize_for_request=None, params=None):
        if memoize_for_request is None:
            # 默认False
            self.memoize_for_request = extensions_api_settings.DEFAULT_KEY_CONSTRUCTOR_MEMOIZE_FOR_REQUEST
        else:
            self.memoize_for_request = memoize_for_request
        if params is None:
            self.params = {}
        else:
            self.params = params
        self.bits = self.get_bits()

    # 获取设置的所有bits
    def get_bits(self):
        _bits = {}
        for attr in dir(self.__class__):
            attr_value = getattr(self.__class__, attr)
            if isinstance(attr_value, bits.KeyBitBase):
                _bits[attr] = attr_value
        return _bits

    def __call__(self, **kwargs):
        return self.get_key(**kwargs)

    # 获取key
    def get_key(self, view_instance, view_method, request, args, kwargs):
        # 可以设置把key保存在request里
        if self.memoize_for_request:
            memoization_key = self._get_memoization_key(
                view_instance=view_instance,
                view_method=view_method,
                args=args,
                kwargs=kwargs
            )
            if not hasattr(request, '_key_constructor_cache'):
                request._key_constructor_cache = {}
        if self.memoize_for_request and memoization_key in request._key_constructor_cache:
            return request._key_constructor_cache.get(memoization_key)
        else:
            # md5加密后的key密文
            value = self._get_key(
                view_instance=view_instance,
                view_method=view_method,
                request=request,
                args=args,
                kwargs=kwargs
            )
            if self.memoize_for_request:
                request._key_constructor_cache[memoization_key] = value
            return value

    def _get_memoization_key(self, view_instance, view_method, args, kwargs):
        from rest_framework_extensions.utils import get_unique_method_id
        return json.dumps({
            'unique_method_id': get_unique_method_id(view_instance=view_instance, view_method=view_method),
            'args': args,
            'kwargs': kwargs,
            'instance_id': id(self)
        })

    # 根据请求视图,生成加密key
    def _get_key(self, view_instance, view_method, request, args, kwargs):
        _kwargs = {
            'view_instance': view_instance,
            'view_method': view_method,
            'request': request,
            'args': args,
            'kwargs': kwargs,
        }
        return self.prepare_key(
            self.get_data_from_bits(**_kwargs)
        )

    # 对获取到的生成key用的数据dict 以key排序后转为md5
    def prepare_key(self, key_dict):
        res = hashlib.md5(json.dumps(key_dict, sort_keys=True).encode('utf-8')).hexdigest()
        return res

    # 从设置的bits(key规则)里,根据请求视图,获取key生成所需要的数据
    # 例如{'unique_method_id': 'blog.views.PostViewSet.list'}
    def get_data_from_bits(self, **kwargs):
        result_dict = {}
        for bit_name, bit_instance in self.bits.items():
            if bit_name in self.params:
                params = self.params[bit_name]
            else:
                try:
                    params = bit_instance.params
                except AttributeError:
                    params = None
            result_dict[bit_name] = bit_instance.get_data(
                params=params, **kwargs)
        return result_dict

以上总结:KeyConstructor定义了:
0.获取bits的方法
1.根据请求视图和设置的bits生成加密key的方法
2.可以设置memoize_for_request属性来把key依据视图、请求方法保存在request的_key_constructor_cache里。


介绍bits

默认DefaultKeyConstructor

# unique_method_id      接口调用的视图方法的id,例如 'blog.views.PostViewSet.list'
# format                可以理解为视图格式类型 例如:'api'
# language              语言'zh-hans'
class DefaultKeyConstructor(KeyConstructor):
    unique_method_id = bits.UniqueMethodIdKeyBit()
    format = bits.FormatKeyBit()
    language = bits.LanguageKeyBit()

一般来说可能默认的DefaultKeyConstructor就会足够了。它的key主要是根据视图方法来构建的
但是很多时候我们的list需要满足对应不同的访问者不同的页码,甚至是不一样的sql查询语句来查询不一样的列表,因此我们也可以自定义KeyConstructor的bits.

get_data是不同bit获取不同数据的实现。那么除了UniqueMethodIdKeyBit,FormatKeyBit,LanguageKeyBit有哪些Bits呢?

  • ListSqlQueryKeyBit和RetrieveSqlQueryKeyBit
    • 不一样的查询语句,此bit的get_data方法:
    class ListSqlQueryKeyBit(bits.SqlQueryKeyBitBase):
        def get_data(self, params, view_instance, view_method, request, args, kwargs):
            queryset = view_instance.filter_queryset(view_instance.get_queryset())
            res = self._get_queryset_query_string(queryset) # 数据库查询语句
            return res
    
    以上两者原理一致,只是后者是Retrieve方法专用。最终其返回了数据库查询语句,也就是说如果查询语句不一样,那么就能针对性的缓存不一样数据的结果,当然使用该bit很想明显需要注意的是,必须在视图集的get_queryset方法就确定查询集Queryset。
  • ListModelKeyBit和RetrieveModelKeyBit
    • 和上面2个使用sql语句不一样,这里使用的是queryset查询结果变成值列表后的结果也就是queryset.values_list()的结果,总之就是根据查询结果的数据来作为bit
      例如[(1, True), (2, True), (3, False)]和[(3, False)]
  • PaginationKeyBit
    • 不一样的分页参数,例如:{‘page_size’: 100, ‘page’: ‘1’}
  • UserKeyBit
    • 不一样的用户,分为匿名用户和认证通过的用户:'anonymous’或用户id
  • HeadersKeyBit
    • {‘accept-language’: u’ru’, ‘x-geobase-id’: ‘123’}
  • RequestMetaKeyBit
    • {‘REMOTE_ADDR’: u’127.0.0.2’, ‘REMOTE_HOST’: u’yandex.ru’}
  • ArgsKeyBit 和KwargsKeyBit

自定义KeyBit

对于一篇可编辑的文章,DefaultKeyConstructor满足不了我们,因为文章可以能修改,修改后缓存里可是之前的旧文章,所以我们也可以自定义KeyBit。

class UpdatedAtKeyBit(bits.KeyBitBase):
    key = "updated_at"
	# 缓存上一次修改文章的时间,注意必须只要做了文章创建或修改操作就更新这个值
    def get_data(self, **kwargs):
        value = cache.get(self.key, None)
        if not value:
            value = datetime.datetime.utcnow()
            cache.set(self.key, value=value)
        return str(value)
        
class PostUpdatedAtKeyBit(UpdatedAtKeyBit):
    key = "post_updated_at"

# list
# DefaultKeyConstructor包含请求方法,其他2个没啥大用这里不提
# 自定义添加ListSqlQueryKeyBit:根据sql查询语句
# PaginationKeyBit根据分页参数
# PostUpdatedAtKeyBit根据更新时间
class PostListKeyConstructor(DefaultKeyConstructor):
    list_sql = bits.ListSqlQueryKeyBit()
    pagination = bits.PaginationKeyBit()
    updated_at = PostUpdatedAtKeyBit()

# retrieve
class PostObjectKeyConstructor(DefaultKeyConstructor):
    retrieve_sql = bits.RetrieveSqlQueryKeyBit()
    updated_at = PostUpdatedAtKeyBit()

如何使用

装饰器@cache_response(timeout=保存时间单位:秒,key_func=key构造类())

from rest_framework_extensions.cache.decorators import cache_response

class XXXViewSet(ModelViewSet):
	……
	……
	@cache_response(timeout=20, key_func=PostListKeyConstructor())
    def list(self, request, *args, **kwargs):
        return super().list(request, *args, **kwargs)

以上本人基于
追梦人生的drf博客教程:加缓存为接口提速
一文,后深入学习的笔记

你可能感兴趣的:(Django学习,django,python,缓存)