Boltons retry decorator and cached property helpers under pytest: working versions (tested 2026-10-16)
A signature-preserving retry decorator composed with boltons.funcutils.wraps, plus cacheutils cachedproperty and cachedmethod memoization tested offline with pytest.
passed6 passed · 0 failed2026-10-16 00:01 UTC- Status
- passed
- Last passed
- 2026-10-16 00:01 UTC
- Tests
- 6 / 6
- Language
- Python
- Runtime
- Python 3.13.16
pinned versions2
boltons@26.2.0pytest@9.1.1
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 38826982604
The code
Exactly the files that passed. Your agent can pull them with get_recipe("boltons-funcutils-retry") (install), or see them on GitHub.
requirements.txt
boltons==26.2.0 pytest==9.1.1
helpers.py
import time
from boltons.cacheutils import cachedmethod, cachedproperty
from boltons.funcutils import wraps
class RetryError(Exception):
"""Raised when a retried callable exhausts every attempt."""
def retry(attempts=3, retry_on=Exception, delay=0.0, sleep=time.sleep):
"""Signature-preserving retry decorator built on boltons.funcutils.wraps.
Calls the wrapped function up to *attempts* times, retrying only when an
exception matching *retry_on* is raised. The final failure is re-raised as
a RetryError whose __cause__ is the last original exception.
"""
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
last = None
for attempt in range(attempts):
try:
return func(*args, **kwargs)
except retry_on as exc:
last = exc
if attempt < attempts - 1:
sleep(delay)
raise RetryError(f"{func.__name__} failed after {attempts} attempts") from last
return wrapper
return decorator
class Rollup:
"""In-memory aggregator showing boltons cachedproperty and cachedmethod."""
def __init__(self, values):
self.values = list(values)
self.calls = {"total": 0, "bucket": 0}
self._buckets = {}
@cachedproperty
def total(self):
self.calls["total"] += 1
return sum(self.values)
@cachedmethod("_buckets")
def bucket(self, width):
self.calls["bucket"] += 1
return self.total // width
class Slotted:
__slots__ = ("x",)
def __init__(self, x):
self.x = x
@cachedproperty
def doubled(self):
return self.x * 2
tests/test_helpers.py
import inspect
import pytest
from helpers import RetryError, Rollup, Slotted, retry
def test_retry_retries_until_success():
calls = []
@retry(attempts=3)
def flaky():
calls.append(1)
if len(calls) < 3:
raise OSError("transient")
return "ok"
assert flaky() == "ok"
assert len(calls) == 3
def test_retry_exhausts_and_chains_cause():
@retry(attempts=2)
def always_fails():
raise OSError("down")
with pytest.raises(RetryError) as excinfo:
always_fails()
assert isinstance(excinfo.value.__cause__, OSError)
def test_retry_preserves_signature_via_wraps():
@retry(attempts=1)
def add(x, y=0):
return x + y
assert str(inspect.signature(add)) == "(x, y=0)"
def test_cachedproperty_caches_and_delattr_invalidates():
r = Rollup([1, 2, 3])
assert r.total == 6
assert r.total == 6
assert r.calls["total"] == 1
del r.total
assert r.total == 6
assert r.calls["total"] == 2
def test_cachedproperty_needs_instance_dict():
s = Slotted(4)
with pytest.raises(AttributeError):
_ = s.doubled
def test_cachedmethod_is_per_instance_and_needs_hashable_args():
a, b = Rollup([10, 10]), Rollup([10, 10])
assert a.bucket(4) == 5
assert a.bucket(4) == 5
assert a.calls["bucket"] == 1
assert b.calls["bucket"] == 0
b.bucket(4)
assert b.calls["bucket"] == 1
assert len(a._buckets) == 1 and len(b._buckets) == 1
with pytest.raises(TypeError):
a.bucket([4])
pytest.ini
[pytest] pythonpath = .
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.