Skip to main content

wallet-helper

🇫🇷 · 🇬🇧

CI License: BSD-3-Clause Python Local-first

wallet-helper Logo

Never run the same heavy call twice. wallet-helper is persistent memoization for expensive calls (a paid API request, a slow model, any heavy function): an identical call is served from a local store instead of running again, across process restarts. When two identical calls start at the same time, they collapse into one, so the second waits for the first and reuses its result instead of running in parallel (single-flight).

By Warith HARCHAOUI

Documentation

💻 Documentation

🗺️ Landscape

📋 Examples

What it does

A heavy call is a problem you do not want to pay for twice. Two things cause a double run:

  1. You call it again next week. wallet-helper stores each result on disk, content-addressed by a namespace plus the inputs (arguments, a file's content, or bytes), so the repeat is served from the store rather than recomputed.
  2. You call it twice at once. Two threads, or two processes, launch the same slow call before either finishes. wallet-helper lets one of them run it and makes the others wait for that result, so the work happens once.

It is content-addressed, so a renamed input file still hits and two different inputs never collide. The default store is a folder of JSON files, easy to read and to delete. A SQLite backend adds a shared, concurrency-safe store and cross-process single-flight. A small HTTP server centralizes that dedup for many clients.

Status

What ships today:

  • library with Wallet and the @memoize decorator (sync and async def), over a Ledger (JSON files), a SqliteLedger (one shared file), or a RemoteLedger (an HTTP server). In-process single-flight is built in.
  • cross-process single-flight through the SQLite backend or the server, with a fencing token so a crashed or stalled leader cannot disrupt a new leader's lease, a lease timeout so a dead leader never blocks waiters, and a heartbeat so a long job keeps its lease. Duplicates coalesce as long as the leader finishes within its lease or keeps a heartbeat.
  • time-to-live and eviction: per-entry ttl, optional stale-while-revalidate, an evict policy by age or size, and an automatic size cap (max_entries).
  • wallet-helper / cli_argparse and wallet-helper-click: inspect, clear, and evict the store.
  • HTTP dedup server (the [api] extra) plus RemoteLedger, so many clients on any host share one dedup point.

Installation

The only requirement is Python 3.10 to 3.13. If you need Python itself:

  • 🍎 macOS (Homebrew): brew install python
  • 🐧 Ubuntu/Debian: sudo apt update && sudo apt install -y python3 python3-pip
  • 🪟 Windows (PowerShell): winget install Python.Python.3.12

Install from GitHub, pinned to the release tag:

pip install "git+https://github.com/warith-harchaoui/wallet-helper.git@v0.3.0"

The command-line and HTTP surfaces are opt-in extras:

pip install "wallet-helper[cli] @ git+https://github.com/warith-harchaoui/wallet-helper.git@v0.3.0"   # click CLI variant    -> click
pip install "wallet-helper[api] @ git+https://github.com/warith-harchaoui/wallet-helper.git@v0.3.0"   # HTTP dedup server     -> fastapi, uvicorn

Quick start

Memoize any function with one line. The result is stored on disk and reused on the next identical call, this run or next week:

from wallet_helper import memoize

@memoize
def transcribe(path):
    return call_some_paid_api(path)   # slow and billed; runs at most once per file

transcribe("meeting.wav")   # runs, stores the result
transcribe("meeting.wav")   # served from the store, no second call

A file argument is keyed by its content, not its path. So the same file reached under a different name, or a byte-for-byte copy in another folder, still hits, and two different files never collide even if their names look alike:

transcribe("meeting.wav")          # runs
transcribe("archive/meeting.wav")  # a copy with the same bytes, served from the store

Ignore an argument that should not change the result, such as a client handle:

@memoize(ignore=("client",))
def fetch(doc_id, client):
    return client.get(doc_id)

Inspect or drop a function's cache, like functools.lru_cache:

transcribe.cache_info()    # {'entries': 1, 'hits': 1}
transcribe.cache_clear()   # forget this function's stored results

Set a freshness window with ttl (seconds), and share one store across a fleet by pointing at a running server:

from wallet_helper import Wallet, RemoteLedger, memoize

@memoize(ttl=3600)                       # results expire after an hour
def price(symbol):
    return call_pricing_api(symbol)

wallet = Wallet(RemoteLedger("http://cache.internal:8000"))
@memoize(wallet=wallet)                  # every host dedups through one server
def transcribe(path):
    return call_some_paid_api(path)

Async functions work the same. The result is cached, never the coroutine, and concurrent awaits coalesce:

@memoize
async def fetch(url):
    return await http_get(url)

Two command-line tools inspect and manage the store (it defaults to $WALLET_HELPER_DIR, then ~/.cache/wallet-helper):

python -m wallet_helper.cli_argparse stats   # how many results are cached and how many calls they saved
python -m wallet_helper.cli_argparse path    # where the store lives
python -m wallet_helper.cli_argparse clear    # empty the store

wallet-helper-click stats                     # same, via the click variant

For a shared store and cross-process single-flight, use the SQLite backend or the HTTP server. See EXAMPLES.md.

Built on os-helper

wallet-helper is part of the AI Helpers suite and builds on os-helper for content-addressed hashing, path helpers, temporary folders, and logging. That is one direct dependency, which pulls a few common transitive libraries (requests, pyyaml, tqdm, and so on). wallet-helper is local-first and needs no separate service, but it is not dependency-free.

Architecture

Piece Role
make_key Content hash of a namespace plus a payload (arguments, file content, or bytes).
Ledger Default store: one JSON file per entry.
SqliteLedger One shared SQLite file, atomic reuse counters, TTL, and the claim/submit/release/extend lease.
RemoteLedger A LedgerLike that talks to the server, so Wallet(RemoteLedger(url)) dedups across a fleet.
Wallet / memoize Front door: lookup, single-flight (in-process or via the lease), then store.
wallet_helper.api HTTP server that centralizes dedup for many clients.

Tests

make install   # editable install with dev and all extras
make lint      # ruff (PEP 8 and import order)
make test      # pytest and doctests
make           # lint then test

CI runs the same gate on a Python 3.10 to 3.13 matrix (Linux, plus macOS on the newest version). Windows is not in the CI matrix for now — its runners are slow and the fcntl-less lock fallback is low priority today; the library still installs and runs on Windows, and Windows can be re-added later.

The Promise

wallet-helper is part of a local-first, sovereignty-minded suite, and like os-helper it is a small toolbox rather than a service. Rather than market that, here is the honest, case-by-case reality:

  1. Guaranteed local. The default Ledger (a folder of JSON files) and the SqliteLedger (one file) live under $WALLET_HELPER_DIR, or ~/.cache/wallet-helper — on your machine. Nothing is uploaded, there is no telemetry, and there is no account. Your cached results, and the inputs that key them, never leave the disk.

  2. Not possible to be local — the caveat. wallet-helper exists to avoid running your heavy call; it makes no network requests of its own. The one exception is by design: the optional [api] dedup server and RemoteLedger speak HTTP so a fleet can share one dedup point — and they talk only to the endpoint you point them at.

  3. Your decision. wallet-helper stores whatever your function returns; if that function calls a paid cloud API, that is your code's choice, never wallet-helper's. Point RemoteLedger at your own host and the shared store stays sovereign; point it at a third party and that too is your call — never a default.

Author

License

wallet-helper is licensed under BSD-3-Clause. See LICENSE.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

wallet_helper-0.3.0.tar.gz (43.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

wallet_helper-0.3.0-py3-none-any.whl (36.1 kB view details)

Uploaded Python 3

File details

Details for the file wallet_helper-0.3.0.tar.gz.

File metadata

  • Download URL: wallet_helper-0.3.0.tar.gz
  • Upload date:
  • Size: 43.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.13

File hashes

Hashes for wallet_helper-0.3.0.tar.gz
Algorithm Hash digest
SHA256 77f8149b1496ed0307f8eec555682ae176b45c8e62ad30e8102b1c19f1ef6961
MD5 88313fce75233f690af9fc47e92437d5
BLAKE2b-256 2692daeceadaf9ac479e80ea79a8fcf85fb258703f4256c6112efba2f5af1742

See more details on using hashes here.

File details

Details for the file wallet_helper-0.3.0-py3-none-any.whl.

File metadata

  • Download URL: wallet_helper-0.3.0-py3-none-any.whl
  • Upload date:
  • Size: 36.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.13

File hashes

Hashes for wallet_helper-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 d57e8cb884a084a5abbb812bad2fee9c7b9ba75998e5ee8d119e084dd53cc544
MD5 cdc1eb50e36f506d9811be56b539a7fc
BLAKE2b-256 149cdb31c20ed6de40cf587f8315f04afc9f9688312c1b7722262f579fbb4c37

See more details on using hashes here.

Release history Release notifications | RSS feed

1.1.4

2 files

1.1.3

2 files

1.1.2

2 files

1.1.1

2 files

1.1.0

2 files

1.0.1

2 files

1.0.0

2 files

0.3.1

2 files

This release

0.3.0 This release

2 files

0.2.2

2 files

0.2.1

2 files

0.2.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page