Files
sysmonstm/shared/events/base.py
2025-12-29 14:40:06 -03:00

118 lines
3.2 KiB
Python

"""Abstract base classes for event publishing and subscribing."""
from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from datetime import datetime
from typing import Any, AsyncIterator
import uuid
@dataclass
class Event:
"""Standard event envelope."""
topic: str
payload: dict[str, Any]
event_id: str = field(default_factory=lambda: str(uuid.uuid4()))
timestamp: datetime = field(default_factory=datetime.utcnow)
source: str = ""
def to_dict(self) -> dict[str, Any]:
return {
"event_id": self.event_id,
"topic": self.topic,
"timestamp": self.timestamp.isoformat(),
"source": self.source,
"payload": self.payload,
}
@classmethod
def from_dict(cls, data: dict[str, Any]) -> "Event":
return cls(
event_id=data.get("event_id", str(uuid.uuid4())),
topic=data["topic"],
timestamp=datetime.fromisoformat(data["timestamp"]) if "timestamp" in data else datetime.utcnow(),
source=data.get("source", ""),
payload=data.get("payload", {}),
)
class EventPublisher(ABC):
"""Abstract base for event publishers."""
@abstractmethod
async def connect(self) -> None:
"""Establish connection to the message broker."""
pass
@abstractmethod
async def disconnect(self) -> None:
"""Close connection to the message broker."""
pass
@abstractmethod
async def publish(self, topic: str, payload: dict[str, Any], **kwargs) -> str:
"""
Publish an event to a topic.
Args:
topic: The topic/channel to publish to
payload: The event data
**kwargs: Additional options (e.g., headers, partition key)
Returns:
The event ID
"""
pass
async def publish_event(self, event: Event) -> str:
"""Publish a pre-constructed Event object."""
return await self.publish(event.topic, event.payload, event_id=event.event_id)
async def __aenter__(self) -> "EventPublisher":
await self.connect()
return self
async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
await self.disconnect()
class EventSubscriber(ABC):
"""Abstract base for event subscribers."""
@abstractmethod
async def connect(self) -> None:
"""Establish connection to the message broker."""
pass
@abstractmethod
async def disconnect(self) -> None:
"""Close connection and unsubscribe."""
pass
@abstractmethod
async def subscribe(self, topics: list[str]) -> None:
"""
Subscribe to one or more topics.
Args:
topics: List of topics/patterns to subscribe to
"""
pass
@abstractmethod
async def consume(self) -> AsyncIterator[Event]:
"""
Async generator that yields events as they arrive.
Yields:
Event objects
"""
pass
async def __aenter__(self) -> "EventSubscriber":
await self.connect()
return self
async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
await self.disconnect()