django: how to memoize model manager methods?

917 Views Asked by At

I have a memoized Django model manager method as follows:

class GroupManager(models.Manager):
    def get_for_user(self, user):
        cache_key = 'groups_%s' % (user.id)
        if not hasattr(self, key):
            groups = get_groups_somehow()
            setattr(self, cache_key, groups)
        return getattr(self, cache_key)

But the memoized value persists beyond the request / response cycle; i.e. the value is not re-calculated in the subsequent requests until the server is restarted. This must be because the manager instance is not destroyed.

So, how can I properly memoize model manager methods?

2

There are 2 best solutions below

0
On BEST ANSWER

with inspiration from https://stackoverflow.com/a/1526245/287923, but simplifying it, i've implemented a request cache as follows:

from threading import currentThread

caches = {}

class RequestCache(object):
    def set(self, key, value):
        cache_id = hash(currentThread())
        if caches.get(cache_id):
            caches[cache_id][key] = value
        else:
            caches[cache_id] = {key: value}

    def get(self, key):
        cache_id = hash(currentThread())
        cache = caches.get(cache_id)
        if cache:
            return cache.get(key)
        return None

class RequestCacheMiddleware(object):
    def process_response(self, request, response):
        cache_id = hash(currentThread())
        if caches.get(cache_id):
            del(caches[cache_id])
        return response

caches is a dictionary of cache dictionaries, accessed via get & set methods. a middleware clears the cache for the current thread in process_response method, after the response is rendered.

it is used like this:

from request_cache import RequestCache
cache = RequestCache()
cache.get(key)
cache.set(key, value)
1
On

The key value is not recalculated because you tell it to not be recalculated once the key exists. If you want to recalculate it in subsequent calls, reorder your code

class GroupManager(models.Manager):
    def get_for_user(self, user):
        cache_key = 'groups_%s' % (user.id)
        groups = get_groups_somehow()
        setattr(self, cache_key, groups)
        return getattr(self, cache_key)

And if you want to get the value cached without recalculating, just use getattr with the correct key on your manager.