feat(spaces): implement Phase 2 Event System
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>
This commit is contained in:
239
markitect/spaces/events/models.py
Normal file
239
markitect/spaces/events/models.py
Normal file
@@ -0,0 +1,239 @@
|
||||
"""
|
||||
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,
|
||||
)
|
||||
Reference in New Issue
Block a user