Skip to main content

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 TIME clock — correct across many app instances and clock skew. No Python read-modify-write.
  • burst is token-bucket only. Only that algorithm has a bucket capacity to raise, so passing burst to fixed_window or sliding_window raises RateLimitError instead 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 past max_deliveries) are dead-lettered.
  • Pub/Sub is not durable. Messages published with no subscriber connected are lost. Use StreamQueue when you need delivery.
  • Locks are single-node. RedisLock is a token-owned SET NX PX lock 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 (RedisInfraError and 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

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)

Source distribution for redis-infra 0.2.0
File Size Uploaded
redis_infra-0.2.0.tar.gz 23.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for redis-infra 0.2.0
File Interpreter ABI Platform
redis_infra-0.2.0-py3-none-any.whl Python 3 none any Details

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

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release 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