I just finished work on the alpha release of a new Python caching library I’m working on called Slycache.

The API is inspired by the annotations of the Java Caching API. Here’s a peak at how it works:

  1. Register a cache backend

    slycache.register_backend("default", RedisBackend(...))
    
  2. Define a key namespace:

    user_cache = slycache.with_defaults(namespace="user")
    
  3. Use the cache on methods and functions:

    @user_cache.cache_result("{username}")
    def get_user_by_username(username):
        ...
       
    @user_cache.cache_result("{user_id}")
    def get_user_by_id(user_id):
        ...
       
    @user_cache.cache_put([
        "{user.username}", "{user.user_id}"
    ])
    def save_user(user):
        ...
       
    @user_cache.cache_remove([
        "{user.username}", "{user.user_id}"
    ])
    def delete_user(user):
        ...
    

Check it out on Github and Read the Docs.


After 9 years of almost no Java at all I’ve recently been working on a Java service. I must say there are some things that I do like. Lambda’s for one! And Kotlin of course.

One of the things that caught my eye was the Java Caching API, in particular the caching annotations. I really like the API and ease with which you can update and clear cache entries without needing explicit calls to the cache backends.

So I decided to write a Python library that mimics the API style of the Java Caching API. Its called Slycache and I just published an alpha version today.

There are plenty of caching libraries out there that allow you to do some variation on:

@cache("some_key", timeout=1000)
def expensive_method():
    ...

These work well when you want to cache the result of a single function but in many modern applications there is a need to cache more dynamic objects like a user model.

In the past I’ve ended up writing code like this:

class User:
    @cache(["username"])
    @staticmethod
    def get_by_username(username):
        ...
        return user

    def save(self):
        ...
        clear_get_by_username(self.username)

    def delete(self):
        ...
        clear_get_by_username(self.username)

While this works, it does require customizing the model modifier functions which are often provided by frameworks like Django. It also means that if you add another accessor like get_by_id you need to add lines to clear those cache entries when saving or deleting the model.

There is a further inefficiency in that the next call to the accessor will need to recompute the value and store it in the cache.

With Slycache you can not only cache the result of a function, but you can also cache function arguments or remove a cache entry after a function call. You can even cache the value in multiple keys or multiple cache backends:

slycache.register_backend("locmem", InMemoryBackend(), default_timeout=10)
slycache.register_backend("redis", RedisBackend(), default_timeout=60)

user_cache = slycache.with_defaults(namespace="user")

class User:
    @user_cache.caching([
        CacheResult("{username}", cache_name="locmem"),
        CacheResult("{username}", cache_name="redis")
    ])
    @staticmethod
    def get_by_username(username):
        ...
        return user
    
    @user_cache.caching([
        CacheResult("{id}", cache_name="locmem"),
        CacheResult("{id}", cache_name="redis")
    ])
    @staticmethod
    def get_by_id(id):
        ...
        return user

    @user_cache.caching([
        CachePut(["{self.username}", "{self.id}"], cache_value="self", cache_name="locmem"),
        CachePut(["{self.username}", "{self.id}"], cache_value="self", cache_name="redis")
    ])
    def save(self):
        ...

And there’s more! Take a look at the docs for full details.