Week 4 - Event Infrastructure: - Create SpaceEventType enum with 18 event types covering space lifecycle, document operations, variables, references, rendering, sync, and cache - Create SpaceEvent dataclass with serialization/deserialization - Create EventBus with sync/async handler support, priority ordering, global handlers, and optional event history - Add event factory functions for common events Week 5 - Event Integration: - Wire EventBus into SpaceService as optional dependency - Emit events for all space operations: - SPACE_CREATED, SPACE_UPDATED, SPACE_DELETED, SPACE_ACTIVATED, SPACE_ARCHIVED - DOCUMENT_ADDED, DOCUMENT_REMOVED, DOCUMENT_MOVED, DOCUMENT_CONTENT_CHANGED - VARIABLE_SET, VARIABLE_DELETED - Create integration tests for event propagation patterns Test coverage: 187 tests total - 43 unit tests for event system - 20 integration tests for event propagation - 124 existing tests continue to pass Capabilities delivered: - CAP-010: SpaceEvent base with type, payload, timestamp - CAP-011: EventBus with in-process publish/subscribe - CAP-012: Event handlers registry with priority support - CAP-013: Change detection via content hash comparison Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
240 lines
6.5 KiB
Python
240 lines
6.5 KiB
Python
"""
|
|
Event models for Information Spaces.
|
|
|
|
This module defines the event types and data structures used
|
|
by the event system for space operations.
|
|
"""
|
|
|
|
from dataclasses import dataclass, field
|
|
from datetime import datetime
|
|
from enum import Enum
|
|
from typing import Any, Dict, Optional
|
|
import uuid
|
|
|
|
|
|
class SpaceEventType(Enum):
|
|
"""
|
|
Types of events that can occur in the space system.
|
|
|
|
Events are organized by category:
|
|
- Space lifecycle: creation, updates, deletion, status changes
|
|
- Document operations: add, update, remove, move
|
|
- Variable operations: set, delete
|
|
- Rendering: start, complete, fail
|
|
- Sync: start, complete, conflict
|
|
"""
|
|
|
|
# Space lifecycle events
|
|
SPACE_CREATED = "space.created"
|
|
SPACE_UPDATED = "space.updated"
|
|
SPACE_DELETED = "space.deleted"
|
|
SPACE_ACTIVATED = "space.activated"
|
|
SPACE_ARCHIVED = "space.archived"
|
|
|
|
# Document events
|
|
DOCUMENT_ADDED = "document.added"
|
|
DOCUMENT_UPDATED = "document.updated"
|
|
DOCUMENT_REMOVED = "document.removed"
|
|
DOCUMENT_MOVED = "document.moved"
|
|
DOCUMENT_CONTENT_CHANGED = "document.content_changed"
|
|
|
|
# Variable events
|
|
VARIABLE_SET = "variable.set"
|
|
VARIABLE_DELETED = "variable.deleted"
|
|
|
|
# Reference events
|
|
REFERENCE_ADDED = "reference.added"
|
|
REFERENCE_CLEARED = "reference.cleared"
|
|
|
|
# Rendering events
|
|
RENDER_STARTED = "render.started"
|
|
RENDER_COMPLETED = "render.completed"
|
|
RENDER_FAILED = "render.failed"
|
|
|
|
# Sync events
|
|
SYNC_STARTED = "sync.started"
|
|
SYNC_COMPLETED = "sync.completed"
|
|
SYNC_CONFLICT = "sync.conflict"
|
|
|
|
# Cache events
|
|
CACHE_INVALIDATED = "cache.invalidated"
|
|
|
|
|
|
@dataclass
|
|
class SpaceEvent:
|
|
"""
|
|
Represents an event in the space system.
|
|
|
|
Events are immutable records of operations that have occurred.
|
|
They carry enough information for handlers to react appropriately.
|
|
|
|
Attributes:
|
|
event_type: The type of event
|
|
space_id: The ID of the affected space
|
|
payload: Event-specific data
|
|
event_id: Unique identifier for this event
|
|
timestamp: When the event occurred
|
|
source: Optional identifier of the event source
|
|
correlation_id: Optional ID to correlate related events
|
|
"""
|
|
|
|
event_type: SpaceEventType
|
|
space_id: str
|
|
payload: Dict[str, Any] = field(default_factory=dict)
|
|
event_id: str = field(default_factory=lambda: str(uuid.uuid4()))
|
|
timestamp: datetime = field(default_factory=datetime.now)
|
|
source: Optional[str] = None
|
|
correlation_id: Optional[str] = None
|
|
|
|
def to_dict(self) -> Dict[str, Any]:
|
|
"""Convert the event to a dictionary for serialization."""
|
|
return {
|
|
"event_id": self.event_id,
|
|
"event_type": self.event_type.value,
|
|
"space_id": self.space_id,
|
|
"payload": self.payload,
|
|
"timestamp": self.timestamp.isoformat(),
|
|
"source": self.source,
|
|
"correlation_id": self.correlation_id,
|
|
}
|
|
|
|
@classmethod
|
|
def from_dict(cls, data: Dict[str, Any]) -> "SpaceEvent":
|
|
"""Create an event from a dictionary."""
|
|
return cls(
|
|
event_id=data.get("event_id", str(uuid.uuid4())),
|
|
event_type=SpaceEventType(data["event_type"]),
|
|
space_id=data["space_id"],
|
|
payload=data.get("payload", {}),
|
|
timestamp=datetime.fromisoformat(data["timestamp"])
|
|
if data.get("timestamp")
|
|
else datetime.now(),
|
|
source=data.get("source"),
|
|
correlation_id=data.get("correlation_id"),
|
|
)
|
|
|
|
|
|
# Convenience factory functions for common events
|
|
|
|
|
|
def space_created_event(
|
|
space_id: str,
|
|
name: str,
|
|
source: Optional[str] = None,
|
|
) -> SpaceEvent:
|
|
"""Create a SPACE_CREATED event."""
|
|
return SpaceEvent(
|
|
event_type=SpaceEventType.SPACE_CREATED,
|
|
space_id=space_id,
|
|
payload={"name": name},
|
|
source=source,
|
|
)
|
|
|
|
|
|
def space_updated_event(
|
|
space_id: str,
|
|
changes: Dict[str, Any],
|
|
source: Optional[str] = None,
|
|
) -> SpaceEvent:
|
|
"""Create a SPACE_UPDATED event."""
|
|
return SpaceEvent(
|
|
event_type=SpaceEventType.SPACE_UPDATED,
|
|
space_id=space_id,
|
|
payload={"changes": changes},
|
|
source=source,
|
|
)
|
|
|
|
|
|
def space_deleted_event(
|
|
space_id: str,
|
|
name: str,
|
|
source: Optional[str] = None,
|
|
) -> SpaceEvent:
|
|
"""Create a SPACE_DELETED event."""
|
|
return SpaceEvent(
|
|
event_type=SpaceEventType.SPACE_DELETED,
|
|
space_id=space_id,
|
|
payload={"name": name},
|
|
source=source,
|
|
)
|
|
|
|
|
|
def document_added_event(
|
|
space_id: str,
|
|
document_id: str,
|
|
space_path: str,
|
|
source: Optional[str] = None,
|
|
) -> SpaceEvent:
|
|
"""Create a DOCUMENT_ADDED event."""
|
|
return SpaceEvent(
|
|
event_type=SpaceEventType.DOCUMENT_ADDED,
|
|
space_id=space_id,
|
|
payload={"document_id": document_id, "space_path": space_path},
|
|
source=source,
|
|
)
|
|
|
|
|
|
def document_updated_event(
|
|
space_id: str,
|
|
document_id: str,
|
|
changes: Dict[str, Any],
|
|
source: Optional[str] = None,
|
|
) -> SpaceEvent:
|
|
"""Create a DOCUMENT_UPDATED event."""
|
|
return SpaceEvent(
|
|
event_type=SpaceEventType.DOCUMENT_UPDATED,
|
|
space_id=space_id,
|
|
payload={"document_id": document_id, "changes": changes},
|
|
source=source,
|
|
)
|
|
|
|
|
|
def document_removed_event(
|
|
space_id: str,
|
|
document_id: str,
|
|
space_path: str,
|
|
source: Optional[str] = None,
|
|
) -> SpaceEvent:
|
|
"""Create a DOCUMENT_REMOVED event."""
|
|
return SpaceEvent(
|
|
event_type=SpaceEventType.DOCUMENT_REMOVED,
|
|
space_id=space_id,
|
|
payload={"document_id": document_id, "space_path": space_path},
|
|
source=source,
|
|
)
|
|
|
|
|
|
def document_content_changed_event(
|
|
space_id: str,
|
|
document_id: str,
|
|
old_hash: Optional[str],
|
|
new_hash: str,
|
|
source: Optional[str] = None,
|
|
) -> SpaceEvent:
|
|
"""Create a DOCUMENT_CONTENT_CHANGED event."""
|
|
return SpaceEvent(
|
|
event_type=SpaceEventType.DOCUMENT_CONTENT_CHANGED,
|
|
space_id=space_id,
|
|
payload={
|
|
"document_id": document_id,
|
|
"old_hash": old_hash,
|
|
"new_hash": new_hash,
|
|
},
|
|
source=source,
|
|
)
|
|
|
|
|
|
def cache_invalidated_event(
|
|
space_id: str,
|
|
document_ids: list,
|
|
reason: str,
|
|
source: Optional[str] = None,
|
|
) -> SpaceEvent:
|
|
"""Create a CACHE_INVALIDATED event."""
|
|
return SpaceEvent(
|
|
event_type=SpaceEventType.CACHE_INVALIDATED,
|
|
space_id=space_id,
|
|
payload={"document_ids": document_ids, "reason": reason},
|
|
source=source,
|
|
)
|