Coverage for src / lexigram / contracts / data / sql / append_log.py: 0%

18 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-08-15 18:57 +0800

1"""Append log protocols for event sourcing. 

2 

3This module defines protocols for append-only event log storage, 

4used by the event sourcing infrastructure in lexigram-events. 

5""" 

6 

7from __future__ import annotations 

8 

9from typing import Any, Protocol, TypeVar, runtime_checkable 

10 

11T = TypeVar("T") 

12 

13 

14@runtime_checkable 

15class AppendLogProtocol(Protocol[T]): 

16 """Protocol for append-only event log storage. 

17 

18 This protocol defines the interface for storing and retrieving 

19 events in an append-only log structure, typical of event sourcing 

20 architectures. 

21 

22 Example: 

23 ```python 

24 class PostgresAppendLog: 

25 def __init__(self, pool): 

26 self.pool = pool 

27 

28 async def append(self, stream_id: str, events: list[Event]) -> int: 

29 async with self.pool.acquire() as conn: 

30 # Implementation uses parameterized queries 

31 pass 

32 

33 async def read(self, stream_id: str, from_version: int = 0) -> list[Event]: 

34 # Read events from a specific stream version 

35 pass 

36 

37 async def read_all(self, offset: int = 0, limit: int = 100) -> list[Event]: 

38 # Read all events with pagination 

39 pass 

40 ``` 

41 """ 

42 

43 async def append( 

44 self, 

45 stream_id: str, 

46 events: list[T], 

47 expected_version: int | None = None, 

48 ) -> int: 

49 """Append events to a stream. 

50 

51 Args: 

52 stream_id: Unique identifier for the event stream. 

53 events: List of events to append. 

54 expected_version: Expected current version for optimistic concurrency. 

55 If provided and doesn't match, raise ConflictError. 

56 

57 Returns: 

58 The new stream version after appending. 

59 

60 Raises: 

61 ConflictError: If expected_version doesn't match current version. 

62 """ 

63 ... 

64 

65 async def read( 

66 self, 

67 stream_id: str, 

68 from_version: int = 0, 

69 to_version: int | None = None, 

70 ) -> list[T]: 

71 """Read events from a stream. 

72 

73 Args: 

74 stream_id: Unique identifier for the event stream. 

75 from_version: Starting stream version (inclusive). 

76 to_version: Ending stream version (inclusive). If None, read to end. 

77 

78 Returns: 

79 List of events from the stream. 

80 """ 

81 ... 

82 

83 async def read_all(self, offset: int = 0, limit: int = 100) -> list[T]: 

84 """Read all events with pagination. 

85 

86 Args: 

87 offset: Number of events to skip. 

88 limit: Maximum number of events to return. 

89 

90 Returns: 

91 List of events. 

92 """ 

93 ... 

94 

95 async def get_stream_version(self, stream_id: str) -> int: 

96 """Get the current version of a stream. 

97 

98 Args: 

99 stream_id: Unique identifier for the event stream. 

100 

101 Returns: 

102 Current stream version (0 if stream doesn't exist). 

103 """ 

104 ... 

105 

106 async def delete_stream(self, stream_id: str) -> bool: 

107 """Delete all events from a stream. 

108 

109 Args: 

110 stream_id: Unique identifier for the event stream. 

111 

112 Returns: 

113 True if stream was deleted, False if not found. 

114 """ 

115 ... 

116 

117 async def count(self, stream_id: str | None = None) -> int: 

118 """Count events in a stream or total events. 

119 

120 Args: 

121 stream_id: Optional stream ID. If None, count all events. 

122 

123 Returns: 

124 Number of events. 

125 """ 

126 ... 

127 

128 

129from lexigram.contracts.events.protocols import SnapshotStoreProtocol 

130 

131 

132@runtime_checkable 

133class AppendLogSnapshotStore(SnapshotStoreProtocol, Protocol): 

134 """Protocol for aggregate snapshot storage used by append-log. 

135 

136 Unlike the event-sourcing snapshot store, this interface includes 

137 explicit aggregate type and state serialization helpers. The name has 

138 been changed to avoid collision with the generic ``SnapshotStoreProtocol`` used 

139 by the events package. 

140 """ 

141 

142 async def save_snapshot( 

143 self, 

144 aggregate_id: str, 

145 aggregate_type: str, 

146 version: int, 

147 state: dict[str, Any], 

148 ) -> None: 

149 """Save an aggregate snapshot. 

150 

151 Args: 

152 aggregate_id: Unique identifier for the aggregate. 

153 aggregate_type: Type of the aggregate. 

154 version: Snapshot version. 

155 state: Serialized aggregate state. 

156 """ 

157 ... 

158 

159 async def get_snapshot(self, aggregate_id: str) -> dict[str, Any] | None: 

160 """Get the latest snapshot for an aggregate. 

161 

162 Args: 

163 aggregate_id: Unique identifier for the aggregate. 

164 

165 Returns: 

166 Snapshot data with version and state, or None if not found. 

167 """ 

168 ... 

169 

170 async def delete_snapshots(self, aggregate_id: str) -> int: 

171 """Delete all snapshots for an aggregate. 

172 

173 Args: 

174 aggregate_id: Unique identifier for the aggregate. 

175 

176 Returns: 

177 Number of snapshots deleted. 

178 """ 

179 ... 

180 

181 

182__all__ = ["AppendLogProtocol", "AppendLogSnapshotStore", "T"]