stub class to ensure not passing in a timeout argument results in the default timeout memcached does not accept keys longer than this default function to generate keys construct the key used by all other methods by default prepend the keyprefix keyfunction can be used to specify an alternate function with custom key making behavior function to decide which key function to use default to defaultkeyfunc return the timeout value usable by this backend based upon the provided timeout ticket 21147 avoid time time related precision issues construct the key used by all other methods by default use the keyfunc to generate a key which by default prepends the keyprefix and version a different key function can be provided at the time of cache construction alternatively you can subclass the cache backend to provide custom key making behavior warn about keys that would not be portable to the memcached backend this encourages but does not force writing backendportable cache code helper to make and validate keys key self makekeykey versionversion self validatekeykey return key def addself key value timeoutdefaulttimeout versionnone raise notimplementederror subclasses of basecache must provide an add method async def aaddself key value timeoutdefaulttimeout versionnone return await synctoasyncself add threadsensitivetrue key value timeout version def getself key defaultnone versionnone raise notimplementederrorsubclasses of basecache must provide a get method async def agetself key defaultnone versionnone return await synctoasyncself get threadsensitivetrue key default version def setself key value timeoutdefaulttimeout versionnone raise notimplementederrorsubclasses of basecache must provide a set method async def asetself key value timeoutdefaulttimeout versionnone return await synctoasyncself set threadsensitivetrue key value timeout version def touchself key timeoutdefaulttimeout versionnone raise notimplementederror subclasses of basecache must provide a touch method async def atouchself key timeoutdefaulttimeout versionnone return await synctoasyncself touch threadsensitivetrue key timeout version def deleteself key versionnone raise notimplementederror subclasses of basecache must provide a delete method async def adeleteself key versionnone return await synctoasyncself delete threadsensitivetruekey version def getmanyself keys versionnone d for k in keys val self getk self missingkey versionversion if val is not self missingkey dk val return d async def agetmanyself keys versionnone fetch a given key from the cache if the key does not exist add the key and set it to the default value the default value can also be any callable if timeout is given use that timeout for the key otherwise use the default cache timeout return the value of the key stored or retrieved fetch the value again to avoid a race condition if another caller added a value between the first get and the add above see getorset val await self agetkey self missingkey versionversion if val is self missingkey if callabledefault default default await self aaddkey default timeouttimeout versionversion fetch the value again to avoid a race condition if another caller added a value between the first aget and the aadd above return await self agetkey default versionversion return val def haskeyself key versionnone return self getkey self missingkey versionversion is not self missingkey async def ahaskeyself key versionnone return await self agetkey self missingkey versionversion is not self missingkey def incrself key delta1 versionnone value self getkey self missingkey versionversion if value is self missingkey raise valueerrorkey s not found key newvalue value delta self setkey newvalue versionversion return newvalue async def aincrself key delta1 versionnone subtract delta from value in the cache if the key does not exist raise a valueerror exception return true if the key is in the cache and has not expired this is a separate method rather than just a copy of haskey so that it always has the same functionality as haskey even if a subclass overrides it set a bunch of values in the cache at once from a dict of keyvalue pairs for certain backends memcached this is much more efficient than calling set multiple times if timeout is given use that timeout for the key otherwise use the default cache timeout on backends that support it return a list of keys that failed insertion or an empty list if all keys were inserted successfully delete a bunch of values in the cache at once for certain backends memcached this is much more efficient than calling delete multiple times remove all values from the cache at once raise notimplementederror subclasses of basecache must provide a clear method async def aclearself return await synctoasyncself clear threadsensitivetrue def incrversionself key delta1 versionnone if version is none version self version value self getkey self missingkey versionversion if value is self missingkey raise valueerrorkey s not found key self setkey value versionversion delta self deletekey versionversion return version delta async def aincrversionself key delta1 versionnone subtract delta from the cache version for the supplied key return the new version close the cache connection pass async def acloseself kwargs pass memcachederrorcharsre lazyrecompilerx00x20x7f def memcachekeywarningskey if lenkey memcachemaxkeylength yield cache key will cause errors if used with memcached r longer than s key memcachemaxkeylength if memcachederrorcharsre searchkey yield cache key contains characters that will cause errors if used with fmemcached key r stub class to ensure not passing in a timeout argument results in the default timeout memcached does not accept keys longer than this default function to generate keys construct the key used by all other methods by default prepend the key_prefix key_function can be used to specify an alternate function with custom key making behavior function to decide which key function to use default to default_key_func return the timeout value usable by this backend based upon the provided timeout ticket 21147 avoid time time related precision issues construct the key used by all other methods by default use the key_func to generate a key which by default prepends the key_prefix and version a different key function can be provided at the time of cache construction alternatively you can subclass the cache backend to provide custom key making behavior warn about keys that would not be portable to the memcached backend this encourages but does not force writing backend portable cache code helper to make and validate keys set a value in the cache if the key does not already exist if timeout is given use that timeout for the key otherwise use the default cache timeout return true if the value was stored false otherwise fetch a given key from the cache if the key does not exist return default which itself defaults to none set a value in the cache if timeout is given use that timeout for the key otherwise use the default cache timeout update the key s expiry time using timeout return true if successful or false if the key does not exist delete a key from the cache and return whether it succeeded failing silently fetch a bunch of keys from the cache for certain backends memcached pgsql this can be much faster when fetching multiple values return a dict mapping each key in keys to its value if the given key is missing it will be missing from the response dict see get_many fetch a given key from the cache if the key does not exist add the key and set it to the default value the default value can also be any callable if timeout is given use that timeout for the key otherwise use the default cache timeout return the value of the key stored or retrieved fetch the value again to avoid a race condition if another caller added a value between the first get and the add above see get_or_set fetch the value again to avoid a race condition if another caller added a value between the first aget and the aadd above return true if the key is in the cache and has not expired add delta to value in the cache if the key does not exist raise a valueerror exception see incr subtract delta from value in the cache if the key does not exist raise a valueerror exception return true if the key is in the cache and has not expired this is a separate method rather than just a copy of has_key so that it always has the same functionality as has_key even if a subclass overrides it set a bunch of values in the cache at once from a dict of key value pairs for certain backends memcached this is much more efficient than calling set multiple times if timeout is given use that timeout for the key otherwise use the default cache timeout on backends that support it return a list of keys that failed insertion or an empty list if all keys were inserted successfully delete a bunch of values in the cache at once for certain backends memcached this is much more efficient than calling delete multiple times remove all values from the cache at once add delta to the cache version for the supplied key return the new version see incr_version subtract delta from the cache version for the supplied key return the new version close the cache connection
"Base Cache class."
import time
import warnings

from asgiref.sync import sync_to_async

from django.core.exceptions import ImproperlyConfigured
from django.utils.module_loading import import_string
from django.utils.regex_helper import _lazy_re_compile


class InvalidCacheBackendError(ImproperlyConfigured):
    pass


class CacheKeyWarning(RuntimeWarning):
    pass


class InvalidCacheKey(ValueError):
    pass




DEFAULT_TIMEOUT = object()


MEMCACHE_MAX_KEY_LENGTH = 250


def default_key_func(key, key_prefix, version):
    
    return "%s:%s:%s" % (key_prefix, version, key)


def get_key_func(key_func):
    
    if key_func is not None:
        if callable(key_func):
            return key_func
        else:
            return import_string(key_func)
    return default_key_func


class BaseCache:
    _missing_key = object()

    def __init__(self, params):
        timeout = params.get("timeout", params.get("TIMEOUT", 300))
        if timeout is not None:
            try:
                timeout = int(timeout)
            except (ValueError, TypeError):
                timeout = 300
        self.default_timeout = timeout

        options = params.get("OPTIONS", {})
        max_entries = params.get("max_entries", options.get("MAX_ENTRIES", 300))
        try:
            self._max_entries = int(max_entries)
        except (ValueError, TypeError):
            self._max_entries = 300

        cull_frequency = params.get("cull_frequency", options.get("CULL_FREQUENCY", 3))
        try:
            self._cull_frequency = int(cull_frequency)
        except (ValueError, TypeError):
            self._cull_frequency = 3

        self.key_prefix = params.get("KEY_PREFIX", "")
        self.version = params.get("VERSION", 1)
        self.key_func = get_key_func(params.get("KEY_FUNCTION"))

    def get_backend_timeout(self, timeout=DEFAULT_TIMEOUT):
        
        if timeout == DEFAULT_TIMEOUT:
            timeout = self.default_timeout
        elif timeout == 0:
            
            timeout = -1
        return None if timeout is None else time.time() + timeout

    def make_key(self, key, version=None):
        
        if version is None:
            version = self.version

        return self.key_func(key, self.key_prefix, version)

    def validate_key(self, key):
        
        for warning in memcache_key_warnings(key):
            warnings.warn(warning, CacheKeyWarning)

    def make_and_validate_key(self, key, version=None):
        
        key = self.make_key(key, version=version)
        self.validate_key(key)
        return key

    def add(self, key, value, timeout=DEFAULT_TIMEOUT, version=None):
        
        raise NotImplementedError(
            "subclasses of BaseCache must provide an add() method"
        )

    async def aadd(self, key, value, timeout=DEFAULT_TIMEOUT, version=None):
        return await sync_to_async(self.add, thread_sensitive=True)(
            key, value, timeout, version
        )

    def get(self, key, default=None, version=None):
        
        raise NotImplementedError("subclasses of BaseCache must provide a get() method")

    async def aget(self, key, default=None, version=None):
        return await sync_to_async(self.get, thread_sensitive=True)(
            key, default, version
        )

    def set(self, key, value, timeout=DEFAULT_TIMEOUT, version=None):
        
        raise NotImplementedError("subclasses of BaseCache must provide a set() method")

    async def aset(self, key, value, timeout=DEFAULT_TIMEOUT, version=None):
        return await sync_to_async(self.set, thread_sensitive=True)(
            key, value, timeout, version
        )

    def touch(self, key, timeout=DEFAULT_TIMEOUT, version=None):
        
        raise NotImplementedError(
            "subclasses of BaseCache must provide a touch() method"
        )

    async def atouch(self, key, timeout=DEFAULT_TIMEOUT, version=None):
        return await sync_to_async(self.touch, thread_sensitive=True)(
            key, timeout, version
        )

    def delete(self, key, version=None):
        
        raise NotImplementedError(
            "subclasses of BaseCache must provide a delete() method"
        )

    async def adelete(self, key, version=None):
        return await sync_to_async(self.delete, thread_sensitive=True)(key, version)

    def get_many(self, keys, version=None):
        
        d = {}
        for k in keys:
            val = self.get(k, self._missing_key, version=version)
            if val is not self._missing_key:
                d[k] = val
        return d

    async def aget_many(self, keys, version=None):
        
        d = {}
        for k in keys:
            val = await self.aget(k, self._missing_key, version=version)
            if val is not self._missing_key:
                d[k] = val
        return d

    def get_or_set(self, key, default, timeout=DEFAULT_TIMEOUT, version=None):
        
        val = self.get(key, self._missing_key, version=version)
        if val is self._missing_key:
            if callable(default):
                default = default()
            self.add(key, default, timeout=timeout, version=version)
            
            
            return self.get(key, default, version=version)
        return val

    async def aget_or_set(self, key, default, timeout=DEFAULT_TIMEOUT, version=None):
        
        val = await self.aget(key, self._missing_key, version=version)
        if val is self._missing_key:
            if callable(default):
                default = default()
            await self.aadd(key, default, timeout=timeout, version=version)
            
            
            return await self.aget(key, default, version=version)
        return val

    def has_key(self, key, version=None):
        
        return (
            self.get(key, self._missing_key, version=version) is not self._missing_key
        )

    async def ahas_key(self, key, version=None):
        return (
            await self.aget(key, self._missing_key, version=version)
            is not self._missing_key
        )

    def incr(self, key, delta=1, version=None):
        
        value = self.get(key, self._missing_key, version=version)
        if value is self._missing_key:
            raise ValueError("Key '%s' not found" % key)
        new_value = value + delta
        self.set(key, new_value, version=version)
        return new_value

    async def aincr(self, key, delta=1, version=None):
        
        value = await self.aget(key, self._missing_key, version=version)
        if value is self._missing_key:
            raise ValueError("Key '%s' not found" % key)
        new_value = value + delta
        await self.aset(key, new_value, version=version)
        return new_value

    def decr(self, key, delta=1, version=None):
        
        return self.incr(key, -delta, version=version)

    async def adecr(self, key, delta=1, version=None):
        return await self.aincr(key, -delta, version=version)

    def __contains__(self, key):
        
        
        
        
        return self.has_key(key)

    def set_many(self, data, timeout=DEFAULT_TIMEOUT, version=None):
        
        for key, value in data.items():
            self.set(key, value, timeout=timeout, version=version)
        return []

    async def aset_many(self, data, timeout=DEFAULT_TIMEOUT, version=None):
        for key, value in data.items():
            await self.aset(key, value, timeout=timeout, version=version)
        return []

    def delete_many(self, keys, version=None):
        
        for key in keys:
            self.delete(key, version=version)

    async def adelete_many(self, keys, version=None):
        for key in keys:
            await self.adelete(key, version=version)

    def clear(self):
        
        raise NotImplementedError(
            "subclasses of BaseCache must provide a clear() method"
        )

    async def aclear(self):
        return await sync_to_async(self.clear, thread_sensitive=True)()

    def incr_version(self, key, delta=1, version=None):
        
        if version is None:
            version = self.version

        value = self.get(key, self._missing_key, version=version)
        if value is self._missing_key:
            raise ValueError("Key '%s' not found" % key)

        self.set(key, value, version=version + delta)
        self.delete(key, version=version)
        return version + delta

    async def aincr_version(self, key, delta=1, version=None):
        
        if version is None:
            version = self.version

        value = await self.aget(key, self._missing_key, version=version)
        if value is self._missing_key:
            raise ValueError("Key '%s' not found" % key)

        await self.aset(key, value, version=version + delta)
        await self.adelete(key, version=version)
        return version + delta

    def decr_version(self, key, delta=1, version=None):
        
        return self.incr_version(key, -delta, version)

    async def adecr_version(self, key, delta=1, version=None):
        return await self.aincr_version(key, -delta, version)

    def close(self, **kwargs):
        
        pass

    async def aclose(self, **kwargs):
        pass


memcached_error_chars_re = _lazy_re_compile(r"[\x00-\x20\x7f]")


def memcache_key_warnings(key):
    if len(key) > MEMCACHE_MAX_KEY_LENGTH:
        yield (
            "Cache key will cause errors if used with memcached: %r "
            "(longer than %s)" % (key, MEMCACHE_MAX_KEY_LENGTH)
        )
    if memcached_error_chars_re.search(key):
        yield (
            "Cache key contains characters that will cause errors if used with "
            f"memcached: {key!r}"
        )