wallet_helper.guard module
Wallet and the memoize decorator: run a heavy call once, never twice.
Wallet is the front door of wallet-helper. It wraps a callable whose
run is expensive (a paid API request, a slow model, any heavy function) so that:
an identical call already in the ledger returns the stored result without running, across process restarts (persistent memoization);
two identical calls made at the same time collapse into one: the second waits for the first and reuses its result instead of running again (single-flight).
Single-flight works in-process for the default Ledger
(via threading), and across processes for any backend that offers a claim lease
(SqliteLedger, or
RemoteLedger over HTTP). Wallet picks the right
path automatically, so Wallet(SqliteLedger(...)) and
Wallet(RemoteLedger(url)) get cross-process dedup with no extra code.
The memoize() decorator wires that onto a function in one line, using a
shared default wallet, so the common case needs no setup.
Usage example
>>> import os_helper as osh
>>> from wallet_helper.ledger import Ledger
>>> with osh.temporary_folder() as tmp:
... w = Wallet(Ledger(tmp))
... def transcribe():
... print("running the heavy call") # a visible side effect
... return {"text": "hello"}
... w.call("demo", {"file": "a.wav"}, transcribe) # miss: runs
... w.call("demo", {"file": "a.wav"}, transcribe) # hit: silent
running the heavy call
({'text': 'hello'}, False)
({'text': 'hello'}, True)
- class wallet_helper.guard.Wallet(ledger=None, poll_interval=0.05)[source]
Bases:
objectA ledger plus single-flight around heavy calls.
- Parameters:
ledger (wallet_helper.ledger.LedgerLike, optional) – The result store, any backend satisfying
LedgerLike. With the default JSONLedger, single-flight is in-process. With a claim-capable backend (SqliteLedgerorRemoteLedger) it is cross-process. A defaultLedgeris created when omitted.poll_interval (float, optional) – How often a waiter re-checks a claim-based backend while another caller computes a key. Defaults to 0.05 s.
- async acall(namespace, key_payload, fn, *, ttl=None, stale_while_revalidate=False)[source]
Async twin of
call(): awaitfnonce and cache its result.This is what memoizes an
async defcorrectly: it awaits the coroutine and stores the result, never the coroutine object. Concurrent identical awaits coalesce (async single-flight), and a claim-capable backend still gives cross-process dedup (its blocking calls run in a worker thread).Parameters mirror
call(), exceptfnis a zero-argument callable returning an awaitable.
- call(namespace, key_payload, fn, *, ttl=None, stale_while_revalidate=False)[source]
Return a cached result, or run
fnonce and store it.- Parameters:
namespace (str) – Scope for the call, for example the provider or endpoint name.
key_payload (Any) – What determines the result: arguments, a file path, or bytes. Hashed to the ledger key (see
wallet_helper.ledger.make_key()).fn (callable) – Zero-argument callable doing the heavy work, run at most once per key even under concurrency.
ttl (float, optional) – Seconds the stored result stays fresh. After it expires the next call recomputes.
None(default) means it never expires.stale_while_revalidate (bool, optional) – Only for the in-process
Ledger. When an entry is expired, return the stale result at once and refresh it in a background thread, so callers never wait on the recompute.
- Returns:
The result and
from_cache:Truewhen served from the ledger,Falsewhen this call did the real work. A raisingfnstores nothing, so a failed call is never cached.- Return type:
- paid(namespace, *, key=None, ignore=(), ttl=None, stale_while_revalidate=False)[source]
Decorator memoizing a function through this wallet.
- Parameters:
namespace (str) – Scope for the call.
key (callable, optional) –
key(*args, **kwargs)returning the payload that identifies the result. Overrides the default (all args and kwargs).ignore (tuple of str, optional) – Parameter names to exclude from the cache key, the tidy alternative to a
key=lambda when you just need to drop a volatile handle (for exampleignore=("client",)).ttl (float, optional) – Seconds the stored result stays fresh (see
call()).stale_while_revalidate (bool, optional) – Serve a stale result and refresh in the background (in-process Ledger only; see
call()).
- Returns:
The wrapped function (repeat identical calls are free). It carries
.cache_info()(this namespace’s{entries, hits}) and.cache_clear()(drop this namespace’s entries), likefunctools.lru_cache.- Return type:
callable
Examples
>>> import os_helper as osh >>> from wallet_helper.ledger import Ledger >>> with osh.temporary_folder() as tmp: ... w = Wallet(Ledger(tmp)) ... @w.paid("square") ... def square(n): ... return n * n ... square(9), square(9) # second is free ... square.cache_info()["entries"] # one entry stored (81, 81) 1
- wallet_helper.guard.default_wallet()[source]
Return the process-wide default
Wallet, created on first use.It uses the default
Ledgerlocation ($WALLET_HELPER_DIRthen~/.cache/wallet-helper). Assignwallet_helper.guard._default_walletyourself to point it elsewhere.- Return type:
- wallet_helper.guard.memoize(fn=None, *, namespace=None, key=None, ignore=(), ttl=None, stale_while_revalidate=False, wallet=None)[source]
Persistent memoization: a cache that survives restarts, plus single-flight.
Drop it on any function and its results are content-addressed to disk, reused across process restarts, and shared between concurrent callers so the same heavy call never runs twice. Works bare (
@memoize) or configured (@memoize(ttl=3600, ignore=("client",))).- Parameters:
fn (callable, optional) – The function, when used bare as
@memoize(filled in by Python).namespace (str, optional) – Cache scope; defaults to the function’s
module.qualnameso distinct functions never collide.key (callable, optional) – Custom key builder
key(*args, **kwargs).ignore (tuple of str, optional) – Parameter names to exclude from the key.
ttl (float, optional) – Seconds the stored result stays fresh.
stale_while_revalidate (bool, optional) – Serve a stale result and refresh in the background (in-process store only).
wallet (Wallet, optional) – The wallet to use; defaults to the shared
default_wallet().
- Returns:
The memoized function, carrying
.cache_info()and.cache_clear().- Return type:
callable
Examples
>>> import os_helper as osh >>> from wallet_helper.ledger import Ledger >>> with osh.temporary_folder() as tmp: ... w = Wallet(Ledger(tmp)) ... @memoize(wallet=w) ... def double(n): ... return n * 2 ... double(21), double(21) (42, 42)