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:

  1. an identical call already in the ledger returns the stored result without running, across process restarts (persistent memoization);

  2. 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)

Author

Warith HARCHAOUI, https://linkedin.com/in/warith-harchaoui

class wallet_helper.guard.Wallet(ledger=None, poll_interval=0.05)[source]

Bases: object

A ledger plus single-flight around heavy calls.

Parameters:
  • ledger (wallet_helper.ledger.LedgerLike, optional) – The result store, any backend satisfying LedgerLike. With the default JSON Ledger, single-flight is in-process. With a claim-capable backend (SqliteLedger or RemoteLedger) it is cross-process. A default Ledger is 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(): await fn once and cache its result.

This is what memoizes an async def correctly: 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(), except fn is a zero-argument callable returning an awaitable.

Parameters:
Return type:

tuple[Any, bool]

call(namespace, key_payload, fn, *, ttl=None, stale_while_revalidate=False)[source]

Return a cached result, or run fn once 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: True when served from the ledger, False when this call did the real work. A raising fn stores nothing, so a failed call is never cached.

Return type:

tuple of (Any, bool)

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 example ignore=("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), like functools.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 Ledger location ($WALLET_HELPER_DIR then ~/.cache/wallet-helper). Assign wallet_helper.guard._default_wallet yourself to point it elsewhere.

Return type:

Wallet

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.qualname so 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)