Getting started
RillKit is a small, synchronous, thread-based library for Redis Streams. You decorate a function, RillKit reads messages from a stream, calls your function, and takes care of acknowledging, retrying and dead-lettering.
Install
pip install rillkitpip install "rillkit[pydantic]"RillKit needs Python 3.9 or newer and redis-py 5 or newer. The pydantic extra is optional and only needed if you want handlers that receive validated models (see Pydantic models).
You also need a Redis server. Retries use the idle filter of XPENDING, so use Redis 6.2 or newer. Locally, docker run -p 6379:6379 redis:7 is enough.
Publish and consume in 20 lines
Save this as app.py:
import logging
from rillkit import Broker, Stream
logging.basicConfig(level=logging.INFO, format="%(levelname)s %(message)s")
broker = Broker("redis://localhost:6379/0")
@broker.subscribe(Stream("orders", group="billing"), block_ms=1000)
def handle_order(order: dict):
print(f"billing order {order['id']}: {order['total']}")
if __name__ == "__main__":
broker.publish("orders", {"id": 1, "total": 49.5})
broker.publish("orders", {"id": 2, "total": 12.0})
broker.run()Run it:
python app.pyYou will see something like this (stop it with Ctrl+C):
INFO consumer started | stream: orders | group: billing | consumer: billing-myhost-71496 | policy: nack_on_error | max retries: 3
billing order 1: 49.5
billing order 2: 12.0
INFO shutdown requested, finishing the current message before exiting
INFO consumer stopped | stream: orders | consumer: billing-myhost-71496Seeing the log lines
RillKit logs through the standard logging module under the name rillkit. The logging.basicConfig(...) line at the top is what makes the INFO lines visible. Without it you only see warnings and errors.
What just happened
Stream("orders", group="billing")names the stream (orders) and the consumer group (billing).@broker.subscribe(...)registeredhandle_order. Nothing runs yet.broker.publish("orders", {...})did anXADDonto the stream. The dict was turned into JSON and stored in a field calleddata.broker.run()created thebillinggroup if it did not exist (reading from the start of the stream), started a consumer thread, and blocked.- The consumer thread read the two entries with
XREADGROUP, decoded the JSON into adict, and calledhandle_orderonce for each. - Each call returned normally, so RillKit acknowledged the entry with
XACK. - On
Ctrl+C(SIGINT) RillKit finished the message it was working on, stopped the thread and exited cleanly with status 0.
Run python app.py a second time and the same two orders are not processed again for the group, because they were acknowledged. Only the two new ones that the script publishes on startup are.
Reusing the stream name
Because the example publishes on every start, running it repeatedly keeps adding orders. That is normal for a stream: it is a log, not a queue that empties itself. See Operations for trimming.
Where next
- New to streams? Read Concepts first. It is short.
- Want to see a handler fail and recover? Retries and the DLQ.
- Want something you can copy? Browse the examples.