Migration Guide

Migrating from v2.0 to v2.1

v2.1 is fully backwards compatible with v2.0. No code changes are required. All new features are opt-in via new parameters.

New Features (Opt-In)

  1. Disk Persistence: Add backend=DiskBackend(path) to any cache constructor

    # Before (v2.0): in-memory only
    cache = AsyncCache(maxsize=1000, default_ttl=300)
    
    # After (v2.1): add persistence, zero other changes
    from cache import DiskBackend
    cache = AsyncCache(maxsize=1000, default_ttl=300, backend=DiskBackend("/tmp/cache.pkl"))
    
  2. Invalidation Decorators: New AsyncLRUInvalidator / AsyncTTLInvalidator

    # Before: manual invalidation
    get_user.invalidate_cache(user_id)
    
    # After: declarative invalidation decorator
    @AsyncLRUInvalidator(get_user)
    async def update_user(user_id, data): ...
    
  3. Benchmarks: New benchmarks/run.py script

Breaking Changes in v2.2

  1. Invalidators: ``skip_args`` removed. Passing skip_args to AsyncLRUInvalidator, AsyncTTLInvalidator, or AgentCacheInvalidator now raises TypeError. Use key_fn instead:

    # Before (v2.1):
    @AsyncLRUInvalidator(get_data, skip_args=1)
    async def update_data(self, key): ...
    
    # After (v2.2):
    @AsyncLRUInvalidator(get_data, key_fn=lambda args, kw: (args[1:], {}))
    async def update_data(self, key): ...
    
  2. Invalidation fires before the mutation. Previously invalidation happened after the mutation returned. Now it fires before await func(...), guaranteeing no reader ever sees stale data even if the mutation raises. This is a semantic change — code that relied on seeing stale data during the mutation should be updated.

  3. ``AgentCacheInvalidator`` new params. AgentCacheInvalidator now accepts clear_all (default True) and key_fn for selective invalidation, matching the decorator invalidators’ API.

Migrating from Other Libraries

From functools.lru_cache

# Before
from functools import lru_cache

@lru_cache(maxsize=128)
def get_data(key):
    return db.query(key)

# After
from cache import AsyncLRU

@AsyncLRU(maxsize=128)
async def get_data(key):
    return await db.query(key)

From aiocache

# Before
from aiocache import cached

@cached(ttl=60)
async def get_data(key):
    return await db.query(key)

# After
from cache import AsyncTTL

@AsyncTTL(time_to_live=60)
async def get_data(key):
    return await db.query(key)

From cachetools

# Before
from cachetools import TTLCache
cache = TTLCache(maxsize=1000, ttl=300)

def get_data(key):
    if key in cache:
        return cache[key]
    val = db.query(key)
    cache[key] = val
    return val

# After: async-native, thundering herd protection included
from cache import AsyncCache
cache = AsyncCache(maxsize=1000, default_ttl=300)

async def get_data(key):
    return await cache.get(key, loader=lambda: db.query(key))

From dogpile.cache

# Before
from dogpile.cache import make_region
region = make_region().configure('dogpile.cache.memory')

@region.cache_on_arguments()
def get_data(key):
    return db.query(key)

# After
from cache import AsyncLRU

@AsyncLRU(maxsize=1000)
async def get_data(key):
    return await db.query(key)