eventsourcing 9.5.0b3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,178 @@
1
+ from __future__ import annotations
2
+
3
+ import contextlib
4
+ from copy import deepcopy
5
+ from typing import TYPE_CHECKING
6
+
7
+ from eventsourcing.dcb.api import (
8
+ DCBAppendCondition,
9
+ DCBEvent,
10
+ DCBQuery,
11
+ DCBReadResponse,
12
+ DCBRecorder,
13
+ DCBSequencedEvent,
14
+ )
15
+ from eventsourcing.dcb.persistence import (
16
+ DCBInfrastructureFactory,
17
+ DCBListenNotifySubscription,
18
+ )
19
+ from eventsourcing.persistence import IntegrityError, ProgrammingError
20
+ from eventsourcing.popo import POPOFactory, POPORecorder, POPOTrackingRecorder
21
+
22
+ if TYPE_CHECKING:
23
+ from collections.abc import Iterator, Sequence
24
+ from threading import Event
25
+
26
+
27
+ class InMemoryDCBRecorder(DCBRecorder, POPORecorder):
28
+ def __init__(self) -> None:
29
+ super().__init__()
30
+ self.events: list[DCBSequencedEvent] = []
31
+ self.position_sequence = self._position_sequence_generator()
32
+ self._listeners: set[Event] = set()
33
+
34
+ def read(
35
+ self,
36
+ query: DCBQuery | None = None,
37
+ *,
38
+ after: int | None = None,
39
+ limit: int | None = None,
40
+ ) -> DCBReadResponse:
41
+ query = query or DCBQuery()
42
+ with self._database_lock:
43
+ events_generator = (
44
+ event
45
+ for event in self.events
46
+ if (after is None or event.position > after)
47
+ and (
48
+ not query.items
49
+ or any(
50
+ (not item.types or event.event.type in item.types)
51
+ and (set(event.event.tags) >= set(item.tags))
52
+ for item in query.items
53
+ )
54
+ )
55
+ )
56
+
57
+ events = []
58
+ for i, event in enumerate(events_generator):
59
+ if limit is not None and i >= limit:
60
+ break
61
+ events.append(deepcopy(event))
62
+ if limit is None:
63
+ head = self.events[-1].position if self.events else None
64
+ else:
65
+ head = events[-1].position if events else None
66
+ # TODO: Change the previous few lines to actually be an iterator.
67
+ return SimpleDCBReadResponse(iter(events), head)
68
+
69
+ def append(
70
+ self, events: Sequence[DCBEvent], condition: DCBAppendCondition | None = None
71
+ ) -> int:
72
+ if len(events) == 0:
73
+ msg = "Should be at least one event. Avoid this elsewhere"
74
+ raise ProgrammingError(msg)
75
+ with self._database_lock:
76
+ if condition is not None:
77
+ read_response = self.read(
78
+ query=condition.fail_if_events_match,
79
+ after=condition.after,
80
+ limit=1,
81
+ )
82
+ try:
83
+ next(read_response)
84
+ except StopIteration:
85
+ pass
86
+ else:
87
+ raise IntegrityError(condition)
88
+ self.events.extend(
89
+ DCBSequencedEvent(
90
+ position=next(self.position_sequence),
91
+ event=deepcopy(event),
92
+ )
93
+ for event in events
94
+ )
95
+ self._notify_listeners()
96
+ return self.events[-1].position
97
+
98
+ def _position_sequence_generator(self) -> Iterator[int]:
99
+ position = 1
100
+ while True:
101
+ yield position
102
+ position += 1
103
+
104
+ def subscribe(
105
+ self,
106
+ query: DCBQuery | None = None,
107
+ *,
108
+ after: int | None = None,
109
+ ) -> InMemorySubscription:
110
+ return InMemorySubscription(self, query=query, after=after)
111
+
112
+ def listen(self, event: Event) -> None:
113
+ self._listeners.add(event)
114
+
115
+ def unlisten(self, event: Event) -> None:
116
+ with contextlib.suppress(KeyError):
117
+ self._listeners.remove(event)
118
+
119
+ def _notify_listeners(self) -> None:
120
+ for listener in self._listeners:
121
+ listener.set()
122
+
123
+
124
+ class InMemorySubscription(DCBListenNotifySubscription[InMemoryDCBRecorder]):
125
+ def __init__(
126
+ self,
127
+ recorder: InMemoryDCBRecorder,
128
+ query: DCBQuery | None = None,
129
+ after: int | None = None,
130
+ ) -> None:
131
+ super().__init__(recorder=recorder, query=query, after=after)
132
+ self._recorder.listen(self._has_been_notified)
133
+
134
+ def stop(self) -> None:
135
+ super().stop()
136
+ self._recorder.unlisten(self._has_been_notified)
137
+
138
+
139
+ class SimpleDCBReadResponse(DCBReadResponse):
140
+ def __init__(self, events: Iterator[DCBSequencedEvent], head: int | None = None):
141
+ self.events = events
142
+ self._head_was_given = head is not None
143
+ self._head = head
144
+
145
+ @property
146
+ def head(self) -> int | None:
147
+ return self._head
148
+
149
+ def __next__(self) -> DCBSequencedEvent:
150
+ event = next(self.events)
151
+ if not self._head_was_given: # pragma: no cover
152
+ self._head = event.position
153
+ return event
154
+
155
+ # def next_batch(self) -> list[DCBSequencedEvent]:
156
+ # """
157
+ # Returns a batch of events as a list.
158
+ # Updates the head position similar to __next__.
159
+ # """
160
+ # result = []
161
+ # max_batch_size = 100
162
+ #
163
+ # # Get up to max_batch_size events from the iterator
164
+ # try:
165
+ # for _ in range(max_batch_size):
166
+ # event = next(self.events)
167
+ # if not self._head_was_given:
168
+ # self._head = event.position
169
+ # result.append(event)
170
+ # except StopIteration:
171
+ # pass
172
+ # return result
173
+
174
+
175
+ class InMemoryDCBFactory(POPOFactory, DCBInfrastructureFactory[POPOTrackingRecorder]):
176
+
177
+ def dcb_recorder(self) -> DCBRecorder:
178
+ return InMemoryDCBRecorder()