vocal_helper.wallet module

vocal_helper.wallet

Never pay twice for the same audio.

The two paid modes place billed calls to hosted services. This module is the ledger in front of them: a paid call is memoised, across processes and across restarts, on a key derived from the audio bytes themselves, so re-running a build, re-extracting the same audio or re-rendering a report costs nothing the second time.

Extracted from notes_helper.cloud_transcribe (same author), where the reasoning below was learned the expensive way; it belongs in the library that owns the paid calls, not in a product on top of it.

Why the key is the audio’s bytes

The obvious ledger key is path:size:mtime. It is wrong here, and the failure is silent.

Re-extracting the exact same audio — a rebuild, a reprocess, a second pass with a different glossary — rewrites the file with a fresh modification time while the bytes are identical. A size:mtime key would change, miss the ledger, and bill the call again. Worse: when the quota is spent, the miss does not merely cost money, it fails over and the run quietly produces a different (local) result.

Hashing the bytes makes the key survive re-extraction: identical audio means an identical key, which means a hit.

Why most keyword arguments are excluded from the key

Only the arguments that genuinely change the output belong in the key.

  • Transcription keys on the audio alone. A changed glossary or keyterm bias, a language relabel, a different prompt: none of these justify paying to transcribe the same bytes a second time.

  • Diarization keys on the audio plus an explicit speaker count, when one is forced. Auto-detection is a pure function of the audio, but a run with a forced count must not silently reuse the auto-detected result, so the two are cached separately.

  • A transcriber that bundles diarization (ElevenLabs Scribe does; Gladia does not) therefore keys on the diarization arguments too.

Escape hatch

VOH_WALLET_DISABLED=1 bypasses the ledger and forces a fresh, billed call. It is off by default, deliberately: a paid call stays protected unless somebody asks for it in so many words.

Usage example

>>> import os
>>> os.environ["VOH_WALLET_DISABLED"] = "1"      # keep a doctest from touching a ledger
>>> calls = []
>>> @paid("demo.transcribe")
... def transcribe(*, audio: str) -> str:
...     calls.append(audio)
...     return "text"
>>> transcribe(audio=__file__), len(calls)
('text', 1)
>>> del os.environ["VOH_WALLET_DISABLED"]
vocal_helper.wallet.audio_fingerprint(path)[source]

Content-addressed fingerprint of an audio file, for ledger keying.

Parameters:

path (str) – Path to the audio actually sent to the paid service.

Returns:

"sha256:<hex>". Identical bytes always give an identical string, whatever the file is called or when it was written.

Return type:

str

Examples

>>> fp = audio_fingerprint(__file__)
>>> fp.startswith("sha256:") and len(fp) == 71
True
>>> fp == audio_fingerprint(__file__)
True
vocal_helper.wallet.paid(namespace, *, key_fields=())[source]

Decorate a paid call so the same audio is never billed twice.

The decorated function must take its audio path as the keyword argument audio; that is what the key is built from.

Parameters:
  • namespace (str) – Ledger namespace, e.g. "gladia.transcribe". Distinct providers and distinct operations must not share one.

  • key_fields (tuple of str, keyword-only, optional) – Names of the keyword arguments that genuinely change the result and must therefore join the audio in the key (typically ("num_speakers",), plus ("diarize",) for a transcriber that bundles diarization). Everything else is excluded on purpose — see the module docstring.

Returns:

A decorator.

Return type:

callable

Notes

wallet_helper is imported lazily, on the first genuinely billed call rather than when the decorator is applied at import time, so a caller that never places a paid call does not pull the dependency in. That matters for a fast test lane installed with core extras only.

Examples

>>> import os
>>> os.environ["VOH_WALLET_DISABLED"] = "1"
>>> @paid("demo.diarize", key_fields=("num_speakers",))
... def diarize(*, audio: str, num_speakers: int = 0) -> int:
...     return num_speakers
>>> diarize(audio=__file__, num_speakers=3)
3
>>> del os.environ["VOH_WALLET_DISABLED"]
vocal_helper.wallet.wallet_disabled()[source]

Whether VOH_WALLET_DISABLED asks to bypass memoisation.

Returns:

True only when the variable is set to something other than "", 0, false or no. Anything unset reads as “ledger active”, which is the safe default: a typo must not start billing.

Return type:

bool

Examples

>>> import os
>>> os.environ["VOH_WALLET_DISABLED"] = "1"
>>> wallet_disabled()
True
>>> os.environ["VOH_WALLET_DISABLED"] = "false"
>>> wallet_disabled()
False
>>> del os.environ["VOH_WALLET_DISABLED"]
>>> wallet_disabled()
False
vocal_helper.wallet.wallet_path()[source]

Resolve the ledger file, environment first.

Resolution order: VOH_WALLET_DB, then the wallet_db key of the paid: section of settings.yaml, then ~/.vocal-helper/wallet.sqlite.

Point an existing ledger here when migrating. A product that already paid for hundreds of hours keeps every one of those results only if vocal-helper reads the same file; a fresh default path is a silently empty ledger, and the next run re-bills everything.

Returns:

The ledger path. Its parent directory is created on first use, not here.

Return type:

pathlib.Path

Examples

>>> import os
>>> os.environ["VOH_WALLET_DB"] = "/tmp/demo-ledger.sqlite"
>>> wallet_path().as_posix()
'/tmp/demo-ledger.sqlite'
>>> del os.environ["VOH_WALLET_DB"]
>>> wallet_path().name
'wallet.sqlite'