Redis-backed bounded quota primitives for prepaid credits, reservations, top-ups, and chunk leases.
Project description
EscrowMint Python
Exact, Redis-backed bounded consumption for shared quotas.
EscrowMint Python is for cases where many threads, processes, or services need to consume from the same global quota without letting it go below zero.
Good fits:
- prepaid credits
- inventory reservation
- budget caps
- worker permit pools
- campaign spend controls
Why EscrowMint
EscrowMint is not a generic counter library. It is a quota and reservation library with application-level semantics:
- consistent pool top-up
- exact bounded decrement
- idempotent consume
- reservation with TTL
- commit and cancel flow
- crash recovery via lazy expiry reclaim
Install
Install from PyPI:
uv add escrowmint
or:
pip install escrowmint
Quickstart
from escrowmint import Client
with Client.from_url("redis://localhost:6379/0") as client:
result = client.try_consume(
"wallet:123",
5,
idempotency_key="req-001",
)
print(result.applied) # True
print(result.remaining) # remaining global quota
Top Up Pool
result = client.top_up(
"wallet:123",
25,
idempotency_key="credit-001",
)
print(result.added) # 25
print(result.available) # current global quota after top-up
Crash-Safe Reservation
from escrowmint import Client, ReservationExpired
client = Client.from_url("redis://localhost:6379/0")
reservation = client.reserve(
"wallet:123",
10,
ttl_ms=30_000,
)
try:
result = client.commit("wallet:123", reservation.reservation_id)
except ReservationExpired:
# the hold expired and the quota was released
...
If a worker crashes after reserve but before commit, the held quota is released after TTL expiry on the next mutation or get_state call for that same resource.
Current API
client.try_consume(resource, amount, idempotency_key=None)
client.top_up(resource, amount, idempotency_key=None)
client.reserve(resource, amount, ttl_ms=..., reservation_id=None)
client.commit(resource, reservation_id)
client.cancel(resource, reservation_id)
client.get_state(resource)
Chunk Lease Path
EscrowMint also ships an explicit chunk-lease lifecycle for hot resources:
lease = client.allocate_chunk(
"wallet:123",
100,
owner_id="worker-a",
ttl_ms=30_000,
)
result = client.consume_chunk("wallet:123", lease.lease_id, 5, owner_id="worker-a")
lease = client.renew_chunk("wallet:123", lease.lease_id, owner_id="worker-a", ttl_ms=30_000)
lease = client.release_chunk("wallet:123", lease.lease_id, owner_id="worker-a")
This is the authoritative distributed chunk path. It keeps chunk state in Redis and supports expiry reclaim, renew, release, and worker ownership checks.
How It Works
- Redis remains the source of truth for each resource.
- Lua scripts make each operation atomic.
- Reservations move units from
availabletoreserved. - Top-ups add units back into
availablewithout bypassing expiry reclaim. - Pending reservations are indexed by expiry time in Redis.
- Expired reservations are reclaimed lazily in bounded batches on the next touch of that resource.
- Terminal reservation outcomes are moved into short-lived receipt keys so the hot reservation hash stays small.
Direct Path and Chunk Lease Path
EscrowMint currently ships both models.
The direct path is the shared-resource path:
try_consume,reserve,commit, andcanceltop_upfor consistent replenishment of the shared pool- exact bounded updates against the resource's shared state
- the simplest way to get correctness and crash recovery
The chunk lease path adds a worker-owned lease layer on top of that model:
allocate_chunk,consume_chunk,renew_chunk,release_chunk, andget_chunk- explicit escrow or chunk allocation per worker
- better control over hot-resource ownership, refill, expiry, and reclaim
- more operational complexity than the direct path
Choose the direct path when you want the simplest exact path.
Choose the chunk lease path when a resource benefits from explicit worker-level quota management.
The current chunk lease implementation is an authoritative Redis-backed lease lifecycle. It improves the state model for hot resources, but it does not automatically become a no-Redis local fast path. If you want fewer Redis round trips than the shipped chunk API provides, you can layer an in-process chunk consumer on top of the authoritative lease lifecycle.
See docs/CHUNK_LEASES.md.
Development
EscrowMint Python uses uv.
uv sync --dev
uv run ruff check
uv run pytest
uv build
Notes:
- Python version is pinned in
.python-version - dependencies are locked in uv.lock
- tests use Docker-backed Redis integration cases
- coverage is enforced from pyproject.toml
Support
- Python:
3.8+ - Redis: intended for modern Redis deployments that support Lua scripting and standard key expiry semantics
- Stability: the package is published and follows SemVer before
1.0; breaking API changes remain possible, but release notes will call them out explicitly
Release Process
EscrowMint Python uses Conventional Commits and Release Please for semantic versioning and release notes.
fix:-> patch releasefeat:-> minor releasefeat!:orBREAKING CHANGE:-> major release
When releasable commits land on main, Release Please opens or updates a release PR. Merging that PR updates CHANGELOG.md, creates the vX.Y.Z tag, and creates the GitHub release notes. The dedicated release workflow then verifies the tagged code and publishes the package to PyPI.
Docs
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file escrowmint-0.5.0.tar.gz.
File metadata
- Download URL: escrowmint-0.5.0.tar.gz
- Upload date:
- Size: 11.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.7 {"installer":{"name":"uv","version":"0.11.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4b3bd626006fa61bbff23f6c88be679f367fcf42dd4f48d51246848b84ed604b
|
|
| MD5 |
aed22b33e6abd52c0c66497369a604a9
|
|
| BLAKE2b-256 |
124c8b39f8ceff196d3d81dcd509f023bebd50916f234f1ce1df02f0c8b1dcdf
|
File details
Details for the file escrowmint-0.5.0-py3-none-any.whl.
File metadata
- Download URL: escrowmint-0.5.0-py3-none-any.whl
- Upload date:
- Size: 13.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.7 {"installer":{"name":"uv","version":"0.11.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
71807207dacdc9afc7442efbf4bc62349a131b296204e9662d9de24739dbb5de
|
|
| MD5 |
514787c25c006906bb37c82f5977ccde
|
|
| BLAKE2b-256 |
dfc7293564820da9f4c8e5b0eeb7d3672c09180827a824cd2105c9c459e690f7
|