Skip to main content

Event sourcing library for RabbitMQ (aio_pika)

Project description

eventrabbit

A library for asynchronous work with RabbitMQ, implementing the Event Sourcing pattern. It allows you to easily build an event-driven architecture using decorators for producers, consumers, and functions.

Purpose

Implements the event sourcing pattern: all state changes in the system are represented as events, which are sent and processed via the RabbitMQ message queue.

Message Structure

All messages processed by the library must have the following format:

{
  "action": "<event_type>",
  "data": { ... } // event parameters
}
  • action — a string that defines the type of event/action
  • data — a dictionary with event parameters

Quick Start and Setup

from eventrabbit import build_event_dependencies, RetryConfig

# Create decorators and event handler
retry_config = RetryConfig(max_retries=3, retry_delay_seconds=60)
events, handle = build_event_dependencies(
    url="amqp://user:password@localhost:5672/",
    idle_timeout=300,  # connection idle timeout before closing (seconds)
    retry_config=retry_config,  # retry parameters
)
  • url — RabbitMQ connection string
  • idle_timeout — connection idle timeout before automatic closing
  • retry_config — retry parameters (default: infinite retries, 5 seconds delay)

Using Decorators

1. @events.consumer

Registers an async function as a handler for incoming messages with a specific action.

@events.consumer(action="USER_CREATED")
async def handle_user_created(user_id: int, name: str):
    # handle user creation event
    ...
  • The function must accept parameters matching the keys in data.

2. @events.producer

Wraps a function so that its result is automatically sent to the queue as an event.

@events.producer(exchange_name="user", action="USER_CREATED")
async def create_user(user_id: int, name: str):
    # user creation logic
    return {"user_id": user_id, "name": name}
  • exchange_name — exchange for publishing the event
  • action — event type
  • key (optional) — routing key

3. @events.function

Registers a function as an event handler and automatically sends the result to the queue.

@events.function(action="SEND_EMAIL", exchange_name="email", action_reply="EMAIL_SENT")
async def send_email(user_id: int, email: str):
    # email sending logic
    return {"user_id": user_id, "email": email, "status": "sent"}
  • action — incoming event type
  • exchange_name — exchange for publishing the result
  • action_reply — event type for the reply (by default, same as action)
  • key (optional) — routing key

Important

  • All messages must be in the format { "action": str, "data": dict } — otherwise, processing will not occur.
  • The library automatically manages the connection and retry logic.

Minimal Configuration

from eventrabbit import build_event_dependencies

events, handle = build_event_dependencies(
    url="amqp://user:password@localhost:5672/"
)
  • For advanced scenarios, use the idle_timeout, retry_config, and other parameters.

Queue Setup and Consumption

To consume queues, use the ConsumeChannel object, where you specify the queue name, exchange, and exchange type:

from eventrabbit.common import ConsumeChannel
from aio_pika import ExchangeType

channel = ConsumeChannel(
    url="amqp://user:password@localhost:5672/",
    queue_name="my_queue",
    exchange_name="MY_EXCHANGE",
    exchange_type=ExchangeType.FANOUT,  # optional parameter
)

await handle.consume(channel)
  • exchange_typeoptional parameter. If not specified, the queue will be bound to the default RabbitMQ exchange.

You can run multiple queues in parallel using asyncio.gather:

import asyncio

queues = [
    ConsumeChannel(
        url="amqp://user:password@localhost:5672/",
        queue_name="queue1",
        exchange_name="EX1",
        exchange_type=ExchangeType.FANOUT,
    ),
    ConsumeChannel(
        url="amqp://user:password@localhost:5672/",
        queue_name="queue2",
        exchange_name="EX2",
        exchange_type=ExchangeType.DIRECT,
    ),
    # You can omit exchange_type — the default exchange will be used
    ConsumeChannel(
        url="amqp://user:password@localhost:5672/",
        queue_name="queue3",
    ),
]

await asyncio.gather(*(handle.consume(ch) for ch in queues))

reply_to Support (Response to Messages)

The library supports the reply_to mechanism. If the incoming message contains the reply_to field, the result of your function will be automatically sent back to the sender in the queue specified in reply_to.

  • The QueueResponse model is used for the response, which automatically serializes the data to JSON.
  • All return values of your function must be JSON-serializable (e.g., dicts, lists, strings, numbers, etc.).
  • Response format: { "data": <your function result> }
  • This is convenient for implementing RPC over RabbitMQ.

Full Example

import asyncio
from aio_pika import ExchangeType
from eventrabbit import build_event_dependencies
from eventrabbit.common import ConsumeChannel

# Initialize dependencies

events, handle = build_event_dependencies(
    url="amqp://user:password@localhost:5672/",
    idle_timeout=300,
)

# Global call counter
count_call = 0

# Handler for TRACKERS_INFO event
@events.consumer(action="TRACKERS_INFO")
async def a1(b: str):
    global count_call
    print(b, "23")
    retro = Retro()
    await retro.abc()
    count_call += 1
    print("count", count_call)
    return b

# Handler for TRACKERS_INFO_1 event
@events.consumer(action="TRACKERS_INFO_1")
async def a2(b: str):
    global count_call
    print(b, "23")
    retro = Retro()
    count_call += 1
    print("count", count_call)
    await retro.abc1()
    return b

# Map of queues and exchanges
QUEUES_EXCHANGES = {
    "calendar_user_sync": "PROFILE_FANOUT_EXCHANGE",
    "calendar_google_sync": "GOOGLE_FANOUT_EXCHANGE",
    "calendar_user_status_sync": "USER_STATUS_FANOUT_EXCHANGE",
}

# Class with producers
class Retro:
    @events.producer(
        exchange_name="GOOGLE_FANOUT_EXCHANGE",
        action="TRACKERS_INFO",
    )
    async def abc(self):
        return {"b": "12"}

    @events.producer(
        exchange_name="PROFILE_FANOUT_EXCHANGE",
        action="TRACKERS_INFO_1",
    )
    async def abc1(self):
        return {"b": "12"}

# Main function to start queue consumption
async def main() -> None:
    tasks = [
        asyncio.create_task(
            handle.consume(ConsumeChannel(
                url="amqp://user:password@localhost:5672/",
                queue_name=queue,
                exchange_name=exchange,
                exchange_type=ExchangeType.FANOUT,
            )),
        )
        for queue, exchange in QUEUES_EXCHANGES.items()
    ]
    tasks += [handle.consume(ConsumeChannel(
            url="amqp://user:password@localhost:5672/",
            queue_name="tracker_info",
            exchange_type=ExchangeType.DIRECT,
        ))]

    await asyncio.gather(*tasks)

if __name__ == "__main__":
    asyncio.run(main())

The library does not clutter your project with unnecessary abstractions and is suitable for a concise event-driven architecture.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

eventrabbit-0.1.1.tar.gz (11.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

eventrabbit-0.1.1-py3-none-any.whl (12.9 kB view details)

Uploaded Python 3

File details

Details for the file eventrabbit-0.1.1.tar.gz.

File metadata

  • Download URL: eventrabbit-0.1.1.tar.gz
  • Upload date:
  • Size: 11.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/1.8.5 CPython/3.8.10 Darwin/23.6.0

File hashes

Hashes for eventrabbit-0.1.1.tar.gz
Algorithm Hash digest
SHA256 6d0c695c7a128e66b4c6c6d608348a730d0098225b8cca1a2b27f0446de9c89c
MD5 87cb456c63240e6f90f09f4a6bba4168
BLAKE2b-256 62eaaa6db81792f526489d4eea525201f7280fced2e2b866b9f24de1b1b2be8e

See more details on using hashes here.

File details

Details for the file eventrabbit-0.1.1-py3-none-any.whl.

File metadata

  • Download URL: eventrabbit-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 12.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/1.8.5 CPython/3.8.10 Darwin/23.6.0

File hashes

Hashes for eventrabbit-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 8557b472b68d42d8a8c874bb4126286e75d43b588127c55b3d6b292a8eec97e8
MD5 032f6301136f4950cbf7a77db90e5b75
BLAKE2b-256 3d12dc109fe85af734a64f878009dee69f2e25d11d932c6baa5595fdc4700318

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page