InstanceCache

To cache single instance of a model on some (one or more) fields of same model, you have to define a class derived from flash.InstanceCache class in caches.py file inside your app.

Suppose you want to cache User model’s instance on username field. Then you have to create class:

from flash import InstanceCache
from django.contrib.auth.models import User

class UserCacheOnUsername(InstanceCache):
    model = User
    key_fields = ('username',)

Here, we have created a class UserCacheOnUsername derived from InstanceCache with attributes model and key_fields. It’s mandatory for each type of cache class to define these attributes. model tells whose fields will be given to get the instance. key_fields tells what parameters will be given to get result from memcached.

These parameters must be the fields of same model. If value is not found in memcached then these are used to retrieve the result from database.

In case of InstanceCache, the model is the same whose instance will get cached.

Use case

Here’s the code to use our newly created cache class:

username = 'bill1992'
user = UserCacheOnUsername(username).resolve()
# or
user = UserCacheOnUsername(username=username).resolve()

UserCacheOnUsername(username) create the cache query. Calling .resolve() method on it tries to get user from memcached, if not found then fetches it from database and sets it to memcached.

If user is not found then it will raise User.DoesNotExist exception, which is similar in behaviour if we’d written:

# Db query
user = User.objects.get(username=username)

Using cache manager

You can also use cache manager on User to obtain the instance which looks very similar to Django’s get query syntax:

user = User.cache.get(username=username)

You will soon get to know about it when we come to ModelCacheManager section.

There are methods get_or_none and get_or_404 on cache manager which you can also use:

# It returns User's instance if found otherwise returns None
user = User.cache.get_or_none(username=username)

# It raises Http404 exception if instance not found
user = User.cache.get_or_404(username=username)

Override get_instance

The current behaviour of InstanceCache derived classes is to use <model>.objects.get() on given parameters as fallback method if value not found in memcached. You can override this behaviour by defining get_instance method in the class.

E.g. there’s Avatar model defined like:

class Avatar(models.Model):
    user = models.ForeignKey(User)
    file_path = models.FileField()
    primary = models.BooleanField()

And you want to cache primary avatar instnace on user. Then you do it by

class PrimaryAvatarCacheOnUser(InstanceCache):
    model = Avatar
    key_fields = ('user',)

    def get_instance(self, user):
        avatars = self.get_queryset().filter(user=user, primary=True)
        if avatars:
            return avatars[0]
        return None

# Use above cache class
avatar = PrimaryAvatarCacheOnUser(user=user).resolve()

Here, self.get_queryset() will return a queryset on Avatar model. It takes care of which db to make query on.

In this case, this cache class will never raise Avatar.DoesNotExist exception since it is setting None in memcached against the key when primary avatar not found.

More about key_fields

Till now we defined cache classes having key_fields with one field only. So here is an example where more than one fields are used to create key for cache:

class ParticipationCacheOnUserEvent(InstanceCache):
    model = Participation
    # Since only one Participation instance exists for
    # a user and an event
    key_fields = ('user', 'event')

And here are the different ways to use this cache class

# If parameters given as args, taken in same order of key_fields
participation = ParticipationCacheOnUserEvent(user, event).resolve()

# Parameters can be given in hibrid form too (args & kwargs)
participation = ParticipationCacheOnUserEvent(user, event=event).resolve()

# Parameters can be given in any order if given as kwargs
participation = ParticipationCacheOnUserEvent(event=event, user=user).resolve()

# Parameters must be given as kwargs when using cache manager
participation = Participation.cache.get(user=user, event=event)

Even if you have id of any related field, you can pass them instead of instance. So this will work

participation = Participation.cache.get(user=user, event_id=event_id)

Some notes:

  • Names of cache classes should be unique because cache keys are made using that name.
  • Don’t use related fields’s attname as key_fields though those are which gets used in db table. E.g. in above example, you should not use user_id or event_id in key_fields.
  • When defining custom get_instance method, neither the order nor the name of key_fields should be altered.
  • In case of InstanceCache and QuerysetCache, you can put GenericForeignKey field’s name in key_fields.