Skip to main content

atomic-redis-streams

atomic-streams is a small Python library that adds crash-safe, stateful processing on top of Redis Streams.

Raw Redis Streams give you persistence and at-least-once delivery but do not give:

  • Atomic application state that survives crashes
  • Exactly-once processing (no duplicate messages after restart)
  • Downstream message publications that only happen if the callback succeeded

This library fills that gap. It wraps Redis Streams consumers in a transaction model where the callback result, application state, consumer position, and any outgoing publishes all commit together or not at all.

Installation

pip install atomic-redis-streams

Examples

The core concept is a handler: a class with a handle() method that the library calls for each message. You point a RedisStreamConsumer at a Redis stream, give it a handler, and it takes care of the rest.

Stateless consumer

The simplest handler just implements handle(). Use this when you don't need to track any state between messages:

from atomic_redis_streams import RedisStreamConsumer, TransactionManager

class Logger:
    def handle(self, message: dict, transaction: TransactionManager) -> None:
        print(f"Received: {message}")

consumer = RedisStreamConsumer(
    stream="events",
    consumer_id="logger",
    handler=Logger(),
    redis_url="redis://localhost:6379",
)
consumer.start()

Stateful consumer with crash recovery

Extend Persistable to persist state atomically with each message. On restart, state is restored and no message is processed twice:

from atomic_redis_streams import Persistable, PersistableAttribute, RedisStreamConsumer, TransactionManager

class OrderCounter(Persistable):
    count = PersistableAttribute(default=0)

    @property
    def unique_id(self) -> str:
        return "order-counter"

    def handle(self, message: dict, transaction: TransactionManager) -> None:
        self.count += 1
        print(f"Processed order {message['order_id']} (total: {self.count})")

consumer = RedisStreamConsumer(
    stream="orders",
    consumer_id="order-processor",
    handler=OrderCounter(),
    redis_url="redis://localhost:6379",
)
consumer.start()

Atomic downstream publishing

Use transaction.publish() to forward messages to another stream. The publish is committed atomically with the handler, so if the handler raises, the publish is rolled back:

from atomic_redis_streams import RedisStreamConsumer, TransactionManager

class OrderRouter:
    def handle(self, message: dict, transaction: TransactionManager) -> None:
        transaction.publish("invoices", {"order_id": message["order_id"]})
        # if an exception is raised here, the above publish() call is rolled back

consumer = RedisStreamConsumer(
    stream="orders",
    consumer_id="order-router",
    handler=OrderRouter(),
    redis_url="redis://localhost:6379",
)
consumer.start()

Full example

This example combines all three features: reading a message, updating persistent state, and publishing to a downstream stream. All three happen atomically:

from atomic_redis_streams import Persistable, PersistableAttribute, RedisStreamConsumer, TransactionManager

RESTOCK_THRESHOLD = 10

class InventoryTracker(Persistable):
    stock = PersistableAttribute(default=0)

    @property
    def unique_id(self) -> str:
        return "inventory-tracker"

    def handle(self, message: dict, transaction: TransactionManager) -> None:
        self.stock -= int(message["quantity"])
        if self.stock <= RESTOCK_THRESHOLD:
            transaction.publish("restock-alerts", {
                "sku": message["sku"],
                "stock": str(self.stock),
            })

consumer = RedisStreamConsumer(
    stream="sales",
    consumer_id="inventory-tracker",
    handler=InventoryTracker(),
    redis_url="redis://localhost:6379",
)
consumer.start()

If the process crashes mid-flight, the stock count is not updated and the restock alert is not published. On restart, processing resumes from the last successfully committed message.

Security

This library uses pickle to serialize and deserialize Persistable handler state in Redis. It assumes a trusted Redis connection. If an attacker can write arbitrary bytes to your Redis instance, they can execute arbitrary code when the state is restored on startup. Do not use this library against an untrusted or publicly accessible Redis instance.

Metadata

Release files for atomic-redis-streams 0.1.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 atomic-redis-streams 0.1.0
File Size Uploaded Provenance
atomic_redis_streams-0.1.0.tar.gz 66.2 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for atomic-redis-streams 0.1.0
File Interpreter ABI Platform
atomic_redis_streams-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 86.9 kB

Release files / atomic_redis_streams-0.1.0.tar.gz

Download URL atomic_redis_streams-0.1.0.tar.gz
Size 66.2 kB
Tags Source
SHA-256 checksum
How to use checksums
4e3994b5e3bbc4a7f01da5add08473e4ffc6bac3577c6b9bc2f3a01a65e6677b
BLAKE2b-256 checksum
How to use checksums
d82da3a61105c8d3d91b7f6e0ae9da025e8d956c7b516d61fd5d9dd9a1a06000
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.13

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.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jun 29, 2026.

Transparency log

Release files / atomic_redis_streams-0.1.0-py3-none-any.whl

Download URL atomic_redis_streams-0.1.0-py3-none-any.whl
Size 20.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
b9cfa8642762aba7b51eeaad7c903f94a6c7b5afc071f4f8bf24bf581641edf5
BLAKE2b-256 checksum
How to use checksums
5a083851e9405d387023f882f56e44bfbdcb85e8eade10ab5d1ecba766dadb48
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.13

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.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jun 29, 2026.

Transparency log

Security for atomic-redis-streams 0.1.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.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for atomic_redis_streams-0.1.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jun 29, 2026.

Transparency log

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.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files Full provenance

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