aiocache SimpleMemoryCache async caching with TTL: working versions (tested 2026-10-18)
Async caching with aiocache Cache(Cache.MEMORY): the @cached decorator, TTL expiry, namespaces, custom key builders and noself.
passed7 passed · 0 failed2026-10-18 03:56 UTC- Status
- passed
- Last passed
- 2026-10-18 03:56 UTC
- Tests
- 7 / 7
- Language
- Python
- Runtime
- Python 3.13.16
pinned versions3
aiocache@0.12.3pytest@9.1.1pytest-asyncio@1.4.0
run it
uv venv -q .venv && uv pip install -q --python .venv/bin/python -r requirements.txt .venv/bin/python -m pytest -q
test environment
Python 3.13.16 linux 6.17.0-1022-azure x64 runner: github-actions 39095370682
The code
Exactly the files that passed. Your agent can pull them with get_recipe("aiocache-memory-ttl") (install), or see them on GitHub.
requirements.txt
aiocache==0.12.3 pytest==9.1.1 pytest-asyncio==1.4.0
memory_cache.py
import asyncio
from aiocache import Cache, cached
def build_cache(namespace=None, ttl=None, key_builder=None):
return Cache(Cache.MEMORY, namespace=namespace, ttl=ttl, key_builder=key_builder)
def make_square(ttl=60.0, namespace="calc"):
stats = {"calls": 0}
@cached(ttl=ttl, namespace=namespace)
async def square(n):
stats["calls"] += 1
await asyncio.sleep(0)
return n * n
return square, stats
def make_article_lookup(rows, namespace="blog"):
stats = {"calls": 0}
@cached(
ttl=60,
namespace=namespace,
key_builder=lambda func, slug, **kwargs: f"{func.__name__}:{slug}",
)
async def article(slug):
stats["calls"] += 1
return rows[slug]
return article, stats
class RepoClient:
def __init__(self, owner):
self.owner = owner
self.calls = 0
@cached(ttl=60, namespace="repo", noself=True)
async def repo(self, name):
self.calls += 1
return f"{self.owner}/{name}"
tests/test_memory_cache.py
import asyncio
import pytest
from aiocache import Cache, cached
from memory_cache import RepoClient, build_cache, make_article_lookup, make_square
async def test_cached_ttl_expiry_recomputes():
square, stats = make_square(ttl=0.05)
assert await square(3) == 9
assert await square(3) == 9
assert stats["calls"] == 1
await asyncio.sleep(0.15)
assert await square(3) == 9
assert stats["calls"] == 2
async def test_cached_distinct_args_cached_separately():
square, stats = make_square(namespace="calc")
assert await square(2) == 4
assert await square(3) == 9
assert await square(2) == 4
assert stats["calls"] == 2
assert square.cache.namespace == "calc"
assert square.cache.build_key("k", namespace=square.cache.namespace) == "calck"
async def test_namespace_isolation_and_key_builder():
billing = build_cache(namespace="billing")
shipping = build_cache(namespace="shipping")
await billing.set("total", 41)
assert await billing.get("total") == 41
assert await shipping.get("total") is None
def upper_key(key, namespace=None):
return f"{namespace or ''}:{key}".upper()
users = build_cache(namespace="users", key_builder=upper_key)
await users.set("1", "amy")
assert await users.get("1") == "amy"
assert users.build_key("1", namespace="users") == "USERS:1"
async def test_decorator_key_builder_memoizes_by_slug():
rows = {"intro": {"slug": "intro", "title": "Intro"}}
article, stats = make_article_lookup(rows)
assert await article("intro") == {"slug": "intro", "title": "Intro"}
assert await article("intro") == {"slug": "intro", "title": "Intro"}
assert stats["calls"] == 1
async def test_skip_cache_func_does_not_store_none():
stats = {"calls": 0}
@cached(ttl=60, skip_cache_func=lambda result: result is None)
async def maybe(name):
stats["calls"] += 1
return None if name == "missing" else name.upper()
await maybe("missing")
await maybe("missing")
assert stats["calls"] == 2
await maybe("amy")
await maybe("amy")
assert stats["calls"] == 3
async def test_noself_shares_cache_across_instances():
await RepoClient.repo.cache.clear()
alice, bob = RepoClient("alice"), RepoClient("bob")
assert await alice.repo("x") == "alice/x"
assert await bob.repo("x") == "alice/x"
assert bob.calls == 0
def test_cached_rejects_cache_instance():
with pytest.raises(TypeError):
@cached(ttl=60, cache=Cache(Cache.MEMORY)())
async def token():
return "t"
pytest.ini
[pytest] pythonpath = . asyncio_mode = auto
Connect to ProvenPin
Add the free MCP server so your agent can pull this recipe and others like it. Same setup as the docs.
terminal
claude mcp add --transport http provenpin https://mcp.provenpin.com/mcp
~/.cursor/mcp.json
{ "mcpServers": { "provenpin": { "url": "https://mcp.provenpin.com/mcp" } } }.vscode/mcp.json
{ "servers": { "provenpin": { "type": "http", "url": "https://mcp.provenpin.com/mcp" } } }~/.codeium/windsurf/mcp_config.json
{ "mcpServers": { "provenpin": { "serverUrl": "https://mcp.provenpin.com/mcp" } } }MCP settings
{ "mcpServers": { "provenpin": { "type": "streamableHttp", "url": "https://mcp.provenpin.com/mcp" } } }Any other MCP client: Streamable HTTP at https://mcp.provenpin.com/mcp. No API key, no signup.