Source code for shuup.core.cache.impl

# -*- coding: utf-8 -*-
# This file is part of Shuup.
#
# Copyright (c) 2012-2021, Shuup Commerce Inc. All rights reserved.
#
# This source code is licensed under the OSL-3.0 license found in the
# LICENSE file in the root directory of this source tree.
from __future__ import unicode_literals

import logging
import random
import threading
import time
from django.conf import settings
from django.core.cache import caches
from django.core.signals import request_finished
from django.utils.encoding import force_str
from pickle import PicklingError

DEFAULT_CACHE_DURATIONS = {
    # Add default durations for various namespaces here (in seconds)
}

LOGGER = logging.getLogger(__name__)

_versions = threading.local()


def _clear_versions_for_request(**kwargs):
    _versions.__dict__.clear()


request_finished.connect(_clear_versions_for_request, dispatch_uid="shuup.core.cache._clear_versions_for_request")


def _get_cache_key_namespace(cache_key):
    """
    Split the given cache key by the first colon to get a namespace.

    :param cache_key: Cache key string
    :type cache_key: str
    :return: Cache namespace string
    :rtype: str
    """
    return force_str(cache_key).split(str(":"), 1)[0]


[docs]def get_cache_duration(cache_key): """ Determine a cache duration for the given cache key. :param cache_key: Cache key string :type cache_key: str :return: Timeout seconds :rtype: int """ namespace = _get_cache_key_namespace(cache_key) duration = settings.SHUUP_CACHE_DURATIONS.get(namespace) if duration is None: duration = DEFAULT_CACHE_DURATIONS.get(namespace, settings.SHUUP_DEFAULT_CACHE_DURATION) return duration
[docs]class VersionedCache(object): def __init__(self, using): """ :param using: Cache alias :type using: str """ self.using = using self._cache = caches[self.using]
[docs] def bump_version(self, cache_key): """ Bump up the cache version for the given cache key/namespace. :param cache_key: Cache key or namespace :type cache_key: str """ namespace = _get_cache_key_namespace(cache_key) version = str("%s/%s" % (time.time(), random.random())) self.set(str("_version:") + namespace, version) setattr(_versions, namespace, version)
[docs] def get_version(self, cache_key): """ Get the cache version (or None) for the given cache key/namespace. The cache version is stored in thread-local storage for the current request, so unless bumped in-request, all gets within a single request should get coherently versioned data from the cache. :param cache_key: Cache key or namespace :type cache_key: str :return: Version ID or none :rtype: str|None """ namespace = _get_cache_key_namespace(cache_key) if not hasattr(_versions, namespace): version = self._cache.get(str("_version:") + namespace) setattr(_versions, namespace, version) else: version = getattr(_versions, namespace, None) return version
[docs] def set(self, key, value, timeout=None, version=None): """ Set the value for key `key` in the cache. Unlike ``django.core.caches[using].set()``, this also derives timeout and versioning information from the key (and cached version data) if the key begins with a colon-separated namespace, such as ``foo:bar``. :param key: Cache key :type key: str :param value: Value to cache :type value: object :param timeout: Timeout seconds or None (for auto-determination) :type timeout: int|None :param version: Version string or None (for auto-determination) :type version: str|None :param using: Cache alias :type using: str """ if timeout is None: timeout = get_cache_duration(key) if version is None: version = self.get_version(key) try: self._cache.set(key, value, timeout=timeout, version=version) except PicklingError: LOGGER.exception( "Unable to set cache with key: {}, value: {!r}, value could not be pickled.".format(key, value) )
[docs] def get(self, key, version=None, default=None): """ Get the value for key `key` in the cache. Unlike `django.core.caches[using].get()`, versioning information can be auto-derived from the key (and cached version data) if the key begins with a colon-separated namespace, such as `foo:bar`. :param key: Cache key :type key: str :param version: Version string or None (for auto-determination) :type version: str|None :param default: Default value, if the key is not found :type default: object :param using: Cache alias :type using: str :return: cached value :rtype: object """ if version is None: version = self.get_version(key) return self._cache.get(key, default=default, version=version)
[docs] def clear(self): self._cache.clear()