Skip to main content

EventFlow

Production-ready event-driven infrastructure for Python microservices.
Reliable consumption with the Transactional Inbox pattern on top of Redis Streams + SQLAlchemy.


PyPI version Python 3.10+ License: MIT

eventflow is a small, battle-tested toolkit for building reliable event-driven services. It focuses on the consumer side: it ingests events from Redis Streams, stores them in a durable inbox table, and processes them with retries and dead-lettering.

Note: the producer-side Transactional Outbox is intentionally not implemented yet (OutboxPublisher raises NotImplementedError).

Quick Start

Install:

pip install python-eventflow asyncpg

asyncpg is the PostgreSQL async driver used in the examples; you can use a different SQLAlchemy async driver if needed.

Create the inbox table (standalone usage):

from sqlalchemy.ext.asyncio import create_async_engine
from eventflow.patterns.inbox.models import Base

engine = create_async_engine("postgresql+asyncpg://postgres:1234@localhost:5432/mydb")

async with engine.begin() as conn:
    await conn.run_sync(Base.metadata.create_all)

Run a consumer:

from eventflow import InboxConsumer, RedisStreamsTransport
from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker

class Handlers:
    async def handle_event(self, session, inbox):
        print(inbox.event_type, inbox.payload)

engine = create_async_engine("postgresql+asyncpg://postgres:1234@localhost:5432/mydb")
Session = async_sessionmaker(engine, expire_on_commit=False)

redis = RedisStreamsTransport(host="localhost", port=6379).build_client()

consumer = InboxConsumer(
    redis_client=redis,
    session_factory=Session,
    stream_name="my-events",
    consumer_group="my-service",
    consumer_name_prefix="worker",
    event_handlers=Handlers(),
)

await consumer.start()

Features

  • Transactional Inbox (Exactly-once processing): idempotent persistence keyed by event_id.
  • Redis Streams transport: consumer groups + acknowledgement handling.
  • Safe concurrency: workers cooperate via SELECT ... FOR UPDATE SKIP LOCKED.
  • Retries + dead-lettering: exponential backoff capped at 15 minutes.
  • Type-safe event model: BaseEvent + EventMetadata, full type hints and mypy support.
  • SQLite-friendly tests: JSON payloads fall back cleanly for unit tests (JSONBCompat).

Architecture Flow

flowchart LR
    Producer[Producer] --> Stream[(Redis Stream)]

    subgraph Consumers["Consumers (same consumer group)"]
        C1[InboxConsumer]
        C2[InboxConsumer]
    end

    Stream -->|XREADGROUP| C1
    Stream -->|XREADGROUP| C2

    C1 -->|insert_pending<br/>(idempotent)| Inbox[(event_inbox)]
    C2 -->|insert_pending<br/>(idempotent)| Inbox

    Inbox -->|acquire_due_events<br/>(SKIP LOCKED)| C1
    Inbox -->|acquire_due_events<br/>(SKIP LOCKED)| C2

    C1 --> Handler[Your handler<br/>handle_event(session, inbox)]
    C2 --> Handler

    Handler -->|success| Inbox
    Handler -->|failure<br/>schedule retry / dead-letter| Inbox

Event Format

The consumer supports two common Redis Stream payload styles:

  1. A single data field containing JSON (recommended):
redis-cli XADD my-events '*' data '{"event_id":"e-1","event_type":"OrderCreated","aggregate_id":"7c8f0a6a-7b7c-4c74-9cfb-2e2e2b9b1d33","occurred_on":"2025-01-01T00:00:00Z","payload":{"order_id":"o-123"}}'
  1. A “flattened” entry with top-level fields (event_id, event_type, ...).

Schema Options

If you already have your own SQLAlchemy Base, use the mixin:

from eventflow.patterns.inbox.models import EventInboxMixin

class EventInbox(EventInboxMixin, YourBase):
    __tablename__ = "event_inbox"
PostgreSQL SQL schema (reference)
-- For gen_random_uuid()
CREATE EXTENSION IF NOT EXISTS pgcrypto;

CREATE TABLE event_inbox (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    event_id VARCHAR(255) NOT NULL UNIQUE,
    stream_id VARCHAR(255) NOT NULL,
    event_type VARCHAR(128) NOT NULL,
    aggregate_id UUID NOT NULL,
    correlation_id VARCHAR(255),
    occurred_on TIMESTAMP WITH TIME ZONE NOT NULL,
    payload JSONB NOT NULL,
    status VARCHAR(50) NOT NULL DEFAULT 'pending',
    retry_count INTEGER NOT NULL DEFAULT 0,
    max_retries INTEGER NOT NULL DEFAULT 3,
    received_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
    processed_at TIMESTAMP WITH TIME ZONE,
    next_retry_at TIMESTAMP WITH TIME ZONE,
    error_message TEXT,
    last_error_at TIMESTAMP WITH TIME ZONE,
    CONSTRAINT chk_event_inbox_status CHECK (
        status IN ('pending', 'processing', 'processed', 'failed', 'dead_letter')
    )
);

CREATE UNIQUE INDEX uq_event_inbox_event_id ON event_inbox(event_id);
CREATE INDEX ix_event_inbox_status_next_retry ON event_inbox(status, next_retry_at);
CREATE INDEX ix_event_inbox_aggregate_received ON event_inbox(aggregate_id, received_at);

Configuration & Tuning

  • Batch size: InboxConsumer.BATCH_SIZE (default: 10)
  • Read block time: InboxConsumer.BLOCK_MS (default: 1000)
  • Retry policy: stored per row (max_retries, retry_count, next_retry_at); backoff is exponential and capped at 15 minutes.

Development

poetry install
poetry run pytest
poetry run mypy eventflow
poetry run black --check eventflow tests

License

MIT License. See LICENSE.

Release files for python-eventflow 1.0.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for python-eventflow 1.0.1
File Size Uploaded
python_eventflow-1.0.1.tar.gz 16.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for python-eventflow 1.0.1
File Interpreter ABI Platform
python_eventflow-1.0.1-py3-none-any.whl Python 3 none any Details

Total release size: 34.3 kB

Release files / python_eventflow-1.0.1.tar.gz

Download URL python_eventflow-1.0.1.tar.gz
Size 16.6 kB
Tags Source
SHA-256 checksum
How to use checksums
2c480d5a60c42f9f0ff72c8faf4ee98c71e9ca07d3e85a74093e2ed91f8e55d9
BLAKE2b-256 checksum
How to use checksums
dc9e6ff4f27e86d18dbb8fb723d967179c31beb33211a7a653c2a4d4ec2a5213
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via poetry/1.8.4 CPython/3.11.14 Linux/6.11.0-1018-azure

Release files / python_eventflow-1.0.1-py3-none-any.whl

Download URL python_eventflow-1.0.1-py3-none-any.whl
Size 17.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
6d8274da87f84b8056b556c56eca2c237b4e3bb26b903cf63350aa5304963db9
BLAKE2b-256 checksum
How to use checksums
3fbb04ecc1e2d105b30cbaca3c2eace6f8dc149ffc219953e6cd8baa9ab871d7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via poetry/1.8.4 CPython/3.11.14 Linux/6.11.0-1018-azure

Release history Release notifications | RSS feed

This release

1.0.1 This release

2 release files

1.0.0

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