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/actiondata— 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 stringidle_timeout— connection idle timeout before automatic closingretry_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 eventaction— event typekey(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 typeexchange_name— exchange for publishing the resultaction_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_type— optional 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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6d0c695c7a128e66b4c6c6d608348a730d0098225b8cca1a2b27f0446de9c89c
|
|
| MD5 |
87cb456c63240e6f90f09f4a6bba4168
|
|
| BLAKE2b-256 |
62eaaa6db81792f526489d4eea525201f7280fced2e2b866b9f24de1b1b2be8e
|
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8557b472b68d42d8a8c874bb4126286e75d43b588127c55b3d6b292a8eec97e8
|
|
| MD5 |
032f6301136f4950cbf7a77db90e5b75
|
|
| BLAKE2b-256 |
3d12dc109fe85af734a64f878009dee69f2e25d11d932c6baa5595fdc4700318
|