redis-infra
Safe, typed, reusable production patterns on top of redis-py — not a thin wrapper. Every capability turns a Redis primitive into application infrastructure you can drop in without re-deriving the correctness details (atomicity, clock skew, key namespacing, delivery guarantees).
Matching sync and async APIs across the board. Python 3.11+.
Install
pip install redis-infra # core
pip install "redis-infra[orjson]" # faster JSON
What's in the box
| Module | Sync / Async | Guarantee |
|---|---|---|
Cache |
Cache / AsyncCache |
TTL cache with optional stampede guard |
RateLimiter |
RateLimiter / AsyncRateLimiter |
atomic (single Lua script), server clock |
Leaderboard |
Leaderboard / AsyncLeaderboard |
sorted-set ranking with around() windows |
RedisLock |
RedisLock / AsyncRedisLock |
single-node lock, token compare-and-delete |
Idempotency |
Idempotency / AsyncIdempotency |
run-once-per-key, result cached |
Geo |
Geo / AsyncGeo |
validated geospatial add/search |
Publisher/Subscriber |
+ Async* |
fire-and-forget pub/sub |
StreamQueue |
StreamQueue / AsyncStreamQueue |
at-least-once queue, DLQ, graceful worker |
Quick start
from redis_infra import RedisConfig, redis_client, Cache, RateLimiter
client = redis_client(RedisConfig(host="localhost", port=6379, namespace="app"))
# Cache with a compute-on-miss factory (stampede-guarded)
cache = Cache(client, namespace="app")
user = cache.get_or_set("user:1", lambda: load_user(1), ttl=300, stampede=True)
# Atomic rate limit — being limited is a normal result, not an exception
limiter = RateLimiter(client, limit=100, window=60) # 100 / 60s, sliding window
r = limiter.allow("user:1")
if not r.allowed:
raise TooManyRequests(retry_after=r.retry_after)
Async mirrors it exactly:
from redis_infra import async_redis_client, AsyncCache
client = async_redis_client(RedisConfig(host="localhost"))
cache = AsyncCache(client, namespace="app")
user = await cache.get_or_set("user:1", load_user_async, ttl=300)
Durable queue (Redis Streams)
from redis_infra import StreamQueue
q = StreamQueue(client, "jobs", group="workers", consumer="w1")
q.ensure_group()
q.publish({"task": "resize", "id": 42})
def handle(msg):
do_work(msg.data) # MUST be idempotent — at-least-once delivery
q.run(handle, should_stop=lambda: shutting_down) # ack on success, DLQ on poison
Guarantees & non-guarantees
- Rate limiting is atomic. Each decision is one server-side Lua script reading the Redis
TIMEclock — correct across many app instances and clock skew. No Python read-modify-write. burstis token-bucket only. Only that algorithm has a bucket capacity to raise, so passingbursttofixed_windoworsliding_windowraisesRateLimitErrorinstead of being quietly ignored.- Queue is at-least-once, never exactly-once. A message stays pending until acked; a crashed worker's in-flight messages are reclaimed (
XAUTOCLAIM) and redelivered. Handlers must be idempotent. Poison messages (redelivered pastmax_deliveries) are dead-lettered. - Pub/Sub is not durable. Messages published with no subscriber connected are lost. Use
StreamQueuewhen you need delivery. - Locks are single-node.
RedisLockis a token-ownedSET NX PXlock with Lua compare-and-delete release — safe against releasing someone else's lock, and auto-expiring so a crash can't deadlock. It is not Redlock; don't rely on it across independent masters. - Errors are never swallowed. redis-py errors are wrapped in this package's exception tree (
RedisInfraErrorand subclasses) with__cause__preserved.
Configuration
RedisConfig is frozen/validated. Build from code or the environment:
RedisConfig.from_env() # REDIS_URL, REDIS_HOST, REDIS_PORT, REDIS_NAMESPACE, REDIS_SSL, ...
No credentials are ever read from source — supply them via RedisConfig or env vars.
Development
uv pip install -e ".[dev,orjson]"
ruff check . && ruff format --check .
mypy src
pytest tests/unit # pure, no Redis needed
pytest -m integration # requires a live Redis (see repo docker-compose.yml)
License
MIT
Metadata
Release files for redis-infra 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| redis_infra-0.2.0.tar.gz | 23.0 kB | Without provenance | Details for redis_infra-0.2.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| redis_infra-0.2.0-py3-none-any.whl | Python 3 | none | any | Details for redis_infra-0.2.0-py3-none-any.whl |
Total release size: 50.0 kB
Release files / redis_infra-0.2.0.tar.gz
| Download URL | redis_infra-0.2.0.tar.gz |
|---|---|
| Size | 23.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
6789d1cd71649fd5b701ee7a7ed8a78089c84bcc54440add3b735b57495bf4ea
|
|
BLAKE2b-256 checksum How to use checksums |
4b22bfb104f8e38b6a15c8e73b6d062f27c4a681d9bf0eaaa7d28339b9656c52
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.7
|
Release files / redis_infra-0.2.0-py3-none-any.whl
| Download URL | redis_infra-0.2.0-py3-none-any.whl |
|---|---|
| Size | 27.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
e233fc420929b09ab3edce1819aa2c09c67bff53e4d9ecfc5905a377d12d7057
|
|
BLAKE2b-256 checksum How to use checksums |
ebe0342e072eceb2881a4a877311716a9e7b82e1a78c6c321fadd01def9616cd
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.7
|
Security for redis-infra 0.2.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.