cachetools provides in-memory cache data structures. pip install cachetools. LRU: from cachetools import LRUCache; cache = LRUCache(maxsize=128). cache["key"] = value. cache.get("key"). cache.maxsize, cache.currsize. Evicts least-recently-used when full. TTL: from cachetools import TTLCache; cache = TTLCache(maxsize=128, ttl=300) — items expire after 300s. LFU: from cachetools import LFUCache — evicts least-frequently-used. RR: from cachetools import RRCache — random eviction. Decorator: from cachetools import cached; @cached(cache=LRUCache(maxsize=256)). Method: from cachetools import cachedmethod; class Repo: _cache = {}; @cachedmethod(lambda self: self._cache). Key: from cachetools.keys import hashkey, typedkey. @cached(cache={}, key=lambda a, b: hashkey(a, b)). typedkey: 1 and 1.0 get different entries. TTL decorator: @cached(cache=TTLCache(maxsize=64, ttl=60)). Thread safe: from threading import RLock; lock = RLock(); @cached(cache=LRUCache(maxsize=128), lock=lock). Invalidate: cache.pop("key", None). cache.clear(). del cache["key"]. Size: len(cache), cache.currsize, cache.maxsize. Dict-like: supports __contains__, keys(), values(), items(). MutableMapping: compatible with all dict operations. Miss: returns KeyError — use .get(key, default). popitem(): evicts one item per cache policy. Claude Code generates cachetools decorators, TTL wrappers, and thread-safe cache patterns.
CLAUDE.md for cachetools
## cachetools Stack
- Version: cachetools >= 5.3 | pip install cachetools
- LRU: LRUCache(maxsize=128) — evict least-recently-used on overflow
- TTL: TTLCache(maxsize=128, ttl=300) — auto-expire after N seconds
- Decorator: @cached(cache=LRUCache(128)) | @cached(TTLCache(64, ttl=60))
- Method: @cachedmethod(lambda self: self._cache, key=hashkey)
- Thread: @cached(cache=..., lock=RLock()) — add lock for concurrent access
- Invalidate: cache.pop(key, None) | cache.clear() — manual eviction
cachetools In-Memory Cache Pipeline
# app/cache.py — cachetools LRU, TTL, and decorator patterns
from __future__ import annotations
import time
from threading import RLock
from typing import Any, Callable, TypeVar
from cachetools import LFUCache, LRUCache, RRCache, TTLCache, cached, cachedmethod
from cachetools.keys import hashkey, typedkey
T = TypeVar("T")
# ─────────────────────────────────────────────────────────────────────────────
# 1. Cache instances — direct dict-like usage
# ─────────────────────────────────────────────────────────────────────────────
def demo_cache_types() -> None:
# LRU — great for repeated access patterns (API responses, parsed configs)
lru: LRUCache = LRUCache(maxsize=3)
lru["a"] = 1
lru["b"] = 2
lru["c"] = 3
lru["d"] = 4 # evicts "a" (least recently used)
assert "a" not in lru
assert "d" in lru
print(f"LRU: {dict(lru)}")
# TTL — great for external API responses that go stale
ttl: TTLCache = TTLCache(maxsize=128, ttl=0.1)
ttl["token"] = "abc123"
assert "token" in ttl
time.sleep(0.15)
assert "token" not in ttl # expired
print("TTL: expired as expected")
# LFU — great when access frequency predicts future access
lfu: LFUCache = LFUCache(maxsize=3)
lfu["hot"] = "frequent"
for _ in range(5):
_ = lfu["hot"] # boost frequency
lfu["cold1"] = "rare"
lfu["cold2"] = "rare"
lfu["cold3"] = "new" # evicts one of the cold items
assert "hot" in lfu
print(f"LFU size: {lfu.currsize}/{lfu.maxsize}")
# RR — random eviction, O(1), no eviction-order bookkeeping
rr: RRCache = RRCache(maxsize=4)
for i in range(6):
rr[i] = i * 10
assert rr.currsize == 4
print(f"RR: {dict(rr)}")
# ─────────────────────────────────────────────────────────────────────────────
# 2. @cached decorator — automatic function memoization
# ─────────────────────────────────────────────────────────────────────────────
# LRU memoization — pure functions that are expensive to compute
@cached(cache=LRUCache(maxsize=256))
def fibonacci(n: int) -> int:
if n < 2:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
# TTL cache — results valid for 5 minutes
@cached(cache=TTLCache(maxsize=64, ttl=300))
def get_exchange_rate(from_currency: str, to_currency: str) -> float:
"""Simulates an expensive external API call."""
time.sleep(0.05)
rates = {"USD_EUR": 0.92, "EUR_USD": 1.09, "USD_GBP": 0.79}
return rates.get(f"{from_currency}_{to_currency}", 1.0)
# Custom key — cache by (query, page) but ignore per-request timestamps
_query_cache: LRUCache = LRUCache(maxsize=128)
@cached(
cache=_query_cache,
key=lambda query, page=1, **_kwargs: hashkey(query.strip().lower(), page),
)
def search_products(query: str, page: int = 1, timestamp: float = 0) -> list[dict]:
"""timestamp parameter is ignored in cache key via custom key function."""
time.sleep(0.02)
return [{"id": i, "name": f"{query} result {i}", "page": page} for i in range(10)]
# typedkey — treat 1 (int) and 1.0 (float) as different keys
@cached(cache=LRUCache(maxsize=64), key=typedkey)
def typed_lookup(value: int | float) -> str:
return f"{type(value).__name__}:{value}"
# ─────────────────────────────────────────────────────────────────────────────
# 3. Thread-safe caching — concurrent access
# ─────────────────────────────────────────────────────────────────────────────
_users_cache: TTLCache = TTLCache(maxsize=512, ttl=120)
_users_lock = RLock()
@cached(cache=_users_cache, lock=_users_lock)
def get_user(user_id: int) -> dict:
"""Thread-safe TTL-cached DB lookup — safe for multi-threaded Flask/Django."""
time.sleep(0.01)
return {"id": user_id, "name": f"User {user_id}", "active": True}
def invalidate_user(user_id: int) -> None:
"""Manually evict a cache entry after an update."""
key = hashkey(user_id)
with _users_lock:
_users_cache.pop(key, None)
# ─────────────────────────────────────────────────────────────────────────────
# 4. @cachedmethod — per-instance method caches
# ─────────────────────────────────────────────────────────────────────────────
class ProductRepository:
"""
cachedmethod uses a getter to find the cache on the instance.
Each ProductRepository instance has its own cache — useful when
different repos point to different databases.
"""
def __init__(self, max_cached: int = 256) -> None:
self._cache: LRUCache = LRUCache(maxsize=max_cached)
self._lock = RLock()
@cachedmethod(
cache=lambda self: self._cache,
key=lambda self, product_id: hashkey(product_id),
lock=lambda self: self._lock,
)
def get_product(self, product_id: int) -> dict:
"""Cached per-instance product lookup."""
time.sleep(0.01)
return {"id": product_id, "name": f"Product {product_id}", "price": product_id * 9.99}
def invalidate(self, product_id: int) -> None:
key = hashkey(product_id)
with self._lock:
self._cache.pop(key, None)
@property
def cache_info(self) -> dict:
return {
"size": self._cache.currsize,
"maxsize": self._cache.maxsize,
}
# ─────────────────────────────────────────────────────────────────────────────
# 5. API client with TTL caching
# ─────────────────────────────────────────────────────────────────────────────
class WeatherClient:
"""
Real-world pattern: wrap an external API with a TTL cache.
Cache at the method level so each city gets its own TTL slot.
"""
def __init__(self, api_key: str, ttl: float = 600) -> None:
self._api_key = api_key
self._cache: TTLCache = TTLCache(maxsize=256, ttl=ttl)
self._lock = RLock()
@cachedmethod(
cache=lambda self: self._cache,
key=lambda self, city, units="metric": hashkey(city.lower(), units),
lock=lambda self: self._lock,
)
def get_weather(self, city: str, units: str = "metric") -> dict:
"""Cached for `ttl` seconds per (city, units) pair."""
time.sleep(0.05) # simulate HTTP
return {
"city": city,
"temp": 20.0,
"units": units,
"fetched_at": time.time(),
}
def prefetch(self, cities: list[str]) -> None:
"""Warm the cache for a list of cities."""
for city in cities:
self.get_weather(city)
@property
def cache_size(self) -> int:
return self._cache.currsize
# ─────────────────────────────────────────────────────────────────────────────
# Demo
# ─────────────────────────────────────────────────────────────────────────────
if __name__ == "__main__":
print("=== Cache types ===")
demo_cache_types()
print("\n=== Fibonacci (LRU) ===")
start = time.perf_counter()
print(f" fib(35) = {fibonacci(35)}")
first = time.perf_counter() - start
start = time.perf_counter()
print(f" fib(35) = {fibonacci(35)} (cached)")
second = time.perf_counter() - start
print(f" First: {first*1000:.2f}ms Cached: {second*1000:.4f}ms")
print("\n=== Exchange rate (TTL) ===")
r1 = get_exchange_rate("USD", "EUR")
r2 = get_exchange_rate("USD", "EUR") # cached
print(f" USD→EUR: {r1} (both calls return {r2})")
print("\n=== Search (custom key, ignores timestamp) ===")
r1 = search_products("laptop", page=1, timestamp=1000.0)
r2 = search_products("laptop", page=1, timestamp=9999.0) # same cache entry
print(f" same result: {r1[0] == r2[0]}")
print("\n=== Thread-safe user cache ===")
u = get_user(42)
print(f" {u['name']}")
invalidate_user(42)
u2 = get_user(42) # re-fetched
print(f" after invalidate: {u2['name']}")
print("\n=== ProductRepository ===")
repo = ProductRepository(max_cached=10)
p1 = repo.get_product(1)
p2 = repo.get_product(1) # cached
print(f" {p1['name']} — cache size: {repo.cache_info['size']}")
print("\n=== WeatherClient ===")
client = WeatherClient(api_key="demo", ttl=60)
client.prefetch(["London", "Paris", "Tokyo"])
print(f" cached {client.cache_size} cities")
w = client.get_weather("London") # from cache
print(f" London: {w['temp']}°{w['units']}")
For the functools.lru_cache alternative — @functools.lru_cache(maxsize=128) provides LRU memoization for plain functions but does not support TTL expiration, cannot be selectively invalidated (only cache_clear() wipes everything), is not thread-safe by default for non-atomic operations, and does not expose a dict-like interface for introspection, while cachetools.TTLCache auto-expires stale entries, cache.pop(key) invalidates individual keys, the lock=RLock() parameter makes @cached thread-safe, and cache.currsize / cache.maxsize expose live capacity metrics. For the Redis caching alternative — Redis caching is durable, shared across processes and hosts, and survives application restarts, while cachetools lives in-process with microsecond access latency and zero network overhead — use cachetools for hot per-process caches (parsed configs, decoded JWTs, computed exchange rate conversions) that are cheap to recompute, and Redis for shared cross-process state that must survive restarts. The Claude Skills 360 bundle includes cachetools skill sets covering LRUCache/TTLCache/LFUCache/RRCache data structures, @cached decorator with LRU and TTL, custom key functions with hashkey and typedkey, @cachedmethod for per-instance caches, thread-safe @cached with RLock, manual invalidation with cache.pop, cache capacity monitoring, WeatherClient and ProductRepository real-world patterns, TTL expiry verification in tests, and cache warming with prefetch. Start with the free tier to try in-memory caching code generation.