Examples¶
Rate limiting¶
The whole of a fixed-window limiter, atomic because it is one script:
from redis_lua_py import Key, redis, script
@script
def rate_limit(key: Key, limit: int, ttl: int) -> int:
current = redis.incr(key)
if current == 1:
redis.expire(key, ttl)
if current > limit:
return -1
return limit - current
remaining = rate_limit(client, key="user:42", limit=10, ttl=60)
if remaining < 0:
raise TooManyRequests
Claiming due jobs from a queue¶
@script
def claim_jobs(queue: Key, processing: Key, now: int, limit: int) -> list[bytes]:
"""Atomically move due jobs from a sorted set into a processing hash."""
ids = redis.zrangebyscore(queue, 0, now, "LIMIT", 0, limit)
claimed = []
for job_id in ids:
if redis.zrem(queue, job_id) == 1:
redis.hset(processing, job_id, now)
claimed.append(job_id)
return claimed
local queue = KEYS[1]
local processing = KEYS[2]
local now = tonumber(ARGV[1])
local limit = tonumber(ARGV[2])
local ids = redis.call('ZRANGEBYSCORE', queue, 0, now, 'LIMIT', 0, limit)
local claimed = {}
for __i1 = 1, #ids do
local job_id = ids[__i1]
if redis.call('ZREM', queue, job_id) == 1 then
redis.call('HSET', processing, job_id, now)
claimed[#claimed + 1] = job_id
end
end
return claimed
Two keys, declared as keys — which is what Redis Cluster routes on. See Keys and arguments.
A session touch, with a folded constant¶
SESSION_TTL_SECONDS = 30 * 60
@script
def touch_session(session: Key) -> int:
hits = redis.incr(session)
redis.expire(session, SESSION_TTL_SECONDS)
return hits
The constant is read once, at import, and folded into the script as 1800.
See Constants from the module.
Caching a compressed blob¶
import zlib
@script
def cache_compressed(key: Key, blob: bytes, ttl: int) -> int:
redis.set(key, blob)
redis.expire(key, ttl)
return len(blob)
cache_compressed(client, key="report:42", blob=zlib.compress(report), ttl=300)
A bytes annotation is a passthrough — no tonumber, no decoding, no round
trip through text. See Binary values.
Returning a table of mixed types¶
@script
def preview(doc: Key) -> list[int | bytes]:
size = redis.strlen(doc)
if size == 0:
return [0, b""]
return [1, redis.getrange(doc, 0, 1023)]
Both branches return a table of the same shape, which is what keeps a nil from truncating the reply. See What the caller gets.