SkillAgentSearch skills...

Cashews

Cache with async power

Install / Use

/learn @Krukov/Cashews

README

<h1 align="center">🥔 CASHEWS 🥔</h1> <p align="center"> <em>Async cache framework with simple API to build fast and reliable applications</em> </p>
pip install cashews
pip install cashews[redis]
pip install cashews[diskcache]
pip install cashews[dill] # can cache in redis more types of objects
pip install cashews[speedup] # for bloom filters

Why

Cache plays a significant role in modern applications and everybody wants to use all the power of async programming and cache. There are a few advanced techniques with cache and async programming that can help you build simple, fast, scalable and reliable applications. This library intends to make it easy to implement such techniques.

Features

  • Easy to configure and use
  • Decorator-based API, decorate and play
  • Different cache strategies out-of-the-box
  • Support for multiple storage backends (In-memory, Redis, DiskCache)
  • Set TTL as a string ("2h5m"), as timedelta or use a function in case TTL depends on key parameters
  • Transactionality
  • Middlewares
  • Client-side cache (10x faster than simple cache with redis)
  • Bloom filters
  • Different cache invalidation techniques (time-based or tags)
  • Cache any objects securely with pickle (use secret)
  • Save memory size with compression
  • 2x faster than aiocache (with client side caching)

Usage Example

from cashews import cache

cache.setup("mem://")  # configure as in-memory cache, but redis/diskcache is also supported

# use a decorator-based API
@cache(ttl="3h", key="user:{request.user.uid}")
async def long_running_function(request):
    ...

# or for fine-grained control, use it directly in a function
async def cache_using_function(request):
    await cache.set(key=request.user.uid, value=request.user, expire="20h")
    ...

More examples here

Table of Contents

Configuration

cashews provides a default cache, that you can setup in two different ways:

from cashews import cache

# via url
cache.setup("redis://0.0.0.0/?db=1&socket_connect_timeout=0.5&suppress=0&secret=my_secret&enable=1")
# or via kwargs
cache.setup("redis://0.0.0.0/", db=1, wait_for_connection_timeout=0.5, suppress=False, secret=b"my_key", enable=True)

Alternatively, you can create a cache instance yourself:

from cashews import Cache

cache = Cache()
cache.setup(...)

Optionally, you can disable cache with disable/enable parameter (see Disable Cache):

cache.setup("redis://redis/0?enable=1")
cache.setup("mem://?size=500", disable=True)
cache.setup("mem://?size=500", enable=False)

You can setup different Backends based on a prefix:

cache.setup("redis://redis/0")
cache.setup("mem://?size=500", prefix="user")

await cache.get("accounts")  # will use the redis backend
await cache.get("user:1")  # will use the memory backend

Available Backends

In-memory

The in-memory cache uses fixed-sized LRU dict to store values. It checks expiration on get and periodically purge expired keys.

cache.setup("mem://")
cache.setup("mem://?check_interval=10&size=10000")

Redis

Requires redis package.\

This will use Redis as a storage.

This backend uses pickle module to serialize values, but the cashes can store values with md5-keyed hash.

Use secret and digestmod parameters to protect your application from security vulnerabilities. The digestmod is a hashing algorithm that can be used: sum, md5 (default), sha1 and sha256. To use xxhash algorithms (digestmods: xxh3_64, xxh3_128, xxh32 and xxh64) install xxhash package or setup cashews with speedup requirements (pip install cashews[speedup])) The secret is a salt for a hash.

Pickle can't serialize any type of object. In case you need to store more complex types you can use dill - set pickle_type="dill". Dill is great, but less performance.

If you need complex serializer for sqlalchemy objects you can set pickle_type="sqlalchemy" Use json also an option to serialize/deserialize an object, but it very limited (pickle_type="json")

Any connection errors are suppressed, to disable it use suppress=False - a CacheBackendInteractionError will be raised

For some data, it may be useful to use compression. Gzip and zlib compression are available; you can use the compress_type parameter to configure it.

If you would like to use client-side cache set client_side=True. Client side cache will add cashews: prefix for each key, to customize it use client_side_prefix option.

If you would like to use RedisCluster set cluster=True.

cache.setup("redis://0.0.0.0/?db=1&minsize=10&suppress=false&secret=my_secret", prefix="func")
cache.setup("redis://0.0.0.0/2", password="my_pass", socket_connect_timeout=0.1, retry_on_timeout=True, secret="my_secret")
cache.setup("redis://0.0.0.0", client_side=True, client_side_prefix="my_prefix:", pickle_type="dill", compress_type="gzip")
cache.setup("redis://0.0.0.0:6379", cluster=True)

For using secure connections to redis (over ssl) uri should have rediss as schema

cache.setup("rediss://0.0.0.0/", ssl_ca_certs="path/to/ca.crt", ssl_keyfile="path/to/client.key",ssl_certfile="path/to/client.crt",)

DiskCache

Requires diskcache package.

This will use local sqlite databases (with shards) as storage.

It is a good choice if you don't want to use redis, but you need a shared storage, or your cache takes a lot of local memory. Also, it is a good choice for client side local storage.

You can setup disk cache with Cache parameters

** Warning ** cache.scan and cache.get_match does not work with this storage (works only if shards are disabled)

** Warning ** Be careful with the default settings as they contain parameters such as size_limit

cache.setup("disk://")
cache.setup("disk://?directory=/tmp/cache&timeout=1&shards=0")  # disable shards
Gb = 1073741824
cache.setup("disk://", size_limit=3 * Gb, shards=12)

Basic API

There are a few basic methods to work with cache:

from cashews import cache

cache.setup("mem://")  # configure as in-memory cache

await cache.set(key="key", value=90, expire="2h", exist=None)  # -> bool
await cache.set_raw(key="key", value="str")  # -> bool
await cache.set_many({"key1": value, "key2": value})  # -> None

await cache.get("key", default=None)  # -> Any
await cache.get_or_set("key", default=awaitable_or_callable, expire="1h")  # -> Any
await cache.get_raw("key") # -> Any
await cache.get_many("key1", "key2", default=None)  # -> tuple[Any]
async for key, value in cache.get_match("pattern:*", batch_size=100):
    ...

await cache.incr("key") # -> int
await cache.exists("key") # -> bool

await cache.delete("key")
await cache.delete_many("key1", "key2")
await cache.delete_match("pattern:*")

async for key in cache.scan("pattern:*"):
    ...

await cache.expire("key", timeout=10)
await cache.get_expire("key")  # -> int seconds to expire

await cache.ping(message=None)  # -> bytes
await cache.clear()

await cache.is_locked("key", wait=60)  # -> bool
async with cache.lock("key", expire=10):
    ...
await cache.set_lock("key", value="value", expire=60)  # -> bool
await cache.unlock("key", "value")  # -> bool

await cache.get_keys_count()  # -> int - total number of keys in cache
await cache.close()

Disable Cache

Cache can be disabled not only at setup, but also in runtime. Cashews allow you to disable/enable any call of cache or specific commands:

from cashews import cache, Command

cache.setup("mem://")  # configure as in-memory cache

cache.disable(Command.DELETE)
cache.disable()
cache.enable(Command.GET, Command.SET)
cache.enable()

with cache.disabling():
  ...

Strategies

Simple cache

This is a typical cache strategy: execute, store and return from cache until it expires.

from datetime import timedelta
from cashews import cache

cache.setup("mem://")

@cache(ttl=timedelta(hours=3), key="user:{request.user.uid}")
async def long_running_function(request):
    ...

Fail cache (Failover cache)

Return cache result, if one of the given exceptions is raised (at least one function call should succeed prior to that).

from cashews import cache

cache.setup("mem://")

# note: the key will be "__module__.get_status:name:{name}"
@cache.failover(ttl="2h", exceptions=(Valu
View on GitHub
GitHub Stars571
CategoryDevelopment
Updated3d ago
Forks40

Languages

Python

Security Score

100/100

Audited on Mar 25, 2026

No findings