from collections import OrderedDict from django.core.cache import cache from django.conf import settings class NoneFromCache: pass class LocalCacheProxy: # django cache, buffered using a LRU cache # only usable for stuff that never changes, obviously # todo: ensure thread-safety, compatible with async + daphne etc def __init__(self, maxsize=128): self._maxsize = maxsize self._mapupdate = None self._items = OrderedDict() def get(self, key, default=None): if self._mapupdate is None: self._check_mapupdate() try: # first check out cache result = self._items[key] except KeyError: # not in our cache result = cache.get(key, default=NoneFromCache) if result is not NoneFromCache: self._items[key] = result self._prune() else: result = default else: self._items.move_to_end(key, last=True) return result def _prune(self): # remove old items while len(self._items) > self._maxsize: self._items.pop(next(iter(self._items.keys()))) def _check_mapupdate(self): # todo: thanks to enable_globally() we shouldn't need this any more from c3nav.mapdata.models import MapUpdate mapupdate = MapUpdate.current_cache_key() if self._mapupdate != mapupdate: self._items = OrderedDict() self._mapupdate = mapupdate enabled = False @classmethod def enable_globally(cls): """ This gets called when the per request cache middleware is loaded. We don't want local cache proxies to work outside of requests. """ LocalCacheProxy.enabled = True def set(self, key, value, expire): self._check_mapupdate() cache.set(key, value, expire) if LocalCacheProxy.enabled: self._items[key] = value self._prune() def clear(self): self._items.clear() class RequestLocalCacheProxy(LocalCacheProxy): """ this is a subclass without prune, to be cleared after every request """ def _prune(self): pass def _check_mapupdate(self): pass per_request_cache = RequestLocalCacheProxy(maxsize=settings.CACHE_SIZE_LOCATIONS)