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 .. code-block:: python # 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`` .. code-block:: python # 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: .. code-block:: python # 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 ~~~~~~~~~~~~~~~~~~~~~~~~~ .. code-block:: python # 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 ~~~~~~~~~~~~~ .. code-block:: python # 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 ~~~~~~~~~~~~~~~ .. code-block:: python # 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 ~~~~~~~~~~~~~~~~~~ .. code-block:: python # 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)