How to Implement a Simple Event Bus in Python
Create a publish-subscribe event bus using dataclasses and defaultdict to decouple event producers from consumers.
Python code
38 linesfrom collections import defaultdict
from dataclasses import dataclass, field
from typing import Callable, Dict, List, Set
@dataclass
class EventBus:
_subscribers: Dict[str, List[Callable]] = field(
default_factory=lambda: defaultdict(list)
)
def subscribe(self, event_type: str, handler: Callable) -> None:
self._subscribers[event_type].append(handler)
def publish(self, event_type: str, payload: dict = None) -> None:
for handler in self._subscribers.get(event_type, []):
handler(payload or {})
def order_created_handler(data):
print(f"Order created: {data['order_id']} for {data['customer']}")
def inventory_handler(data):
print(f"Inventory reserved: {data['quantity']} units of {data['sku']}")
if __name__ == "__main__":
bus = EventBus()
bus.subscribe("order.created", order_created_handler)
bus.subscribe("order.created", inventory_handler)
bus.publish("order.created", {
"order_id": 12345,
"customer": "Alice",
"sku": "BOOK-001",
"quantity": 2
})
Output
Order created: 12345 for Alice
Inventory reserved: 2 units of BOOK-001
How it works
The EventBus dataclass uses a defaultdict(list) to automatically create a new list for each event type on first access. The subscribe method appends handler callables to the list for a given event type. The publish method retrieves the handlers for the event type (defaulting to an empty list) and calls each with the payload. Handlers are called synchronously in the order they were subscribed, making this a simple in-process implementation without external dependencies.
Common mistakes
- Forgetting that handlers run synchronously, which can block the publisher.
- Not using a default dict, leading to KeyError when publishing to an unsubscribed event.
- Assuming handlers receive the same payload object without copying, causing shared-state bugs.
- Using `payload or {}` which drops valid empty dictionaries or falsy values like 0.
Variations
- Use `asyncio` to make the bus async and publish events without blocking the main thread.
- Add priority ordering by storing handlers in a sorted list or using a `SortedDict`.
Real-world use cases
- E-commerce platforms decoupling order placement from email notifications and inventory updates.
- Microservices triggering side effects like logging or analytics when user signup happens.
- Plugin systems allowing third-party modules to react to core application events without tight coupling.
Sponsored
More from System design patterns
- Build a BFF (Backend for Frontend) Mock Aggregator in Python medium
- Builder pattern for mocking complex objects in Python easy
- Circuit Breaker Pattern in Python: Closed, Open, and Half-Open States medium
- Create a Data Helper Class in Python easy
- Domain Driven Design Aggregate Root Example in Python medium
- Facade Pattern in Python with Mock Simplification medium
Keep learning
Related tutorials and quizzes for this topic.