eventsourcing 9.3.5__py3-none-any.whl → 9.4.0a1__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.
Potentially problematic release.
This version of eventsourcing might be problematic. Click here for more details.
- eventsourcing/application.py +75 -11
- eventsourcing/cipher.py +1 -1
- eventsourcing/domain.py +29 -3
- eventsourcing/interface.py +23 -5
- eventsourcing/persistence.py +292 -71
- eventsourcing/popo.py +113 -32
- eventsourcing/postgres.py +265 -103
- eventsourcing/projection.py +157 -0
- eventsourcing/sqlite.py +143 -36
- eventsourcing/system.py +64 -42
- eventsourcing/tests/application.py +48 -12
- eventsourcing/tests/persistence.py +304 -75
- eventsourcing/utils.py +1 -1
- {eventsourcing-9.3.5.dist-info → eventsourcing-9.4.0a1.dist-info}/LICENSE +1 -1
- {eventsourcing-9.3.5.dist-info → eventsourcing-9.4.0a1.dist-info}/METADATA +2 -2
- eventsourcing-9.4.0a1.dist-info/RECORD +25 -0
- eventsourcing-9.3.5.dist-info/RECORD +0 -24
- {eventsourcing-9.3.5.dist-info → eventsourcing-9.4.0a1.dist-info}/AUTHORS +0 -0
- {eventsourcing-9.3.5.dist-info → eventsourcing-9.4.0a1.dist-info}/WHEEL +0 -0
eventsourcing/popo.py
CHANGED
|
@@ -1,30 +1,38 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import contextlib
|
|
3
4
|
from collections import defaultdict
|
|
4
|
-
from threading import Lock
|
|
5
|
-
from typing import TYPE_CHECKING, Any, Dict, Iterable, List, Sequence
|
|
5
|
+
from threading import Event, Lock
|
|
6
|
+
from typing import TYPE_CHECKING, Any, Dict, Iterable, List, Sequence, Set, Type
|
|
6
7
|
|
|
7
8
|
from eventsourcing.persistence import (
|
|
8
9
|
AggregateRecorder,
|
|
9
10
|
ApplicationRecorder,
|
|
10
11
|
InfrastructureFactory,
|
|
11
12
|
IntegrityError,
|
|
13
|
+
ListenNotifySubscription,
|
|
12
14
|
Notification,
|
|
13
15
|
ProcessRecorder,
|
|
14
16
|
StoredEvent,
|
|
15
17
|
Tracking,
|
|
18
|
+
TrackingRecorder,
|
|
16
19
|
)
|
|
17
|
-
from eventsourcing.utils import reversed_keys
|
|
20
|
+
from eventsourcing.utils import resolve_topic, reversed_keys
|
|
18
21
|
|
|
19
|
-
if TYPE_CHECKING: # pragma:
|
|
22
|
+
if TYPE_CHECKING: # pragma: no cover
|
|
20
23
|
from uuid import UUID
|
|
21
24
|
|
|
22
25
|
|
|
23
|
-
class
|
|
26
|
+
class POPORecorder:
|
|
24
27
|
def __init__(self) -> None:
|
|
28
|
+
self._database_lock = Lock()
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class POPOAggregateRecorder(POPORecorder, AggregateRecorder):
|
|
32
|
+
def __init__(self) -> None:
|
|
33
|
+
super().__init__()
|
|
25
34
|
self._stored_events: List[StoredEvent] = []
|
|
26
35
|
self._stored_events_index: Dict[UUID, Dict[int, int]] = defaultdict(dict)
|
|
27
|
-
self._database_lock = Lock()
|
|
28
36
|
|
|
29
37
|
def insert_events(
|
|
30
38
|
self, stored_events: List[StoredEvent], **kwargs: Any
|
|
@@ -91,23 +99,36 @@ class POPOAggregateRecorder(AggregateRecorder):
|
|
|
91
99
|
return results
|
|
92
100
|
|
|
93
101
|
|
|
94
|
-
class POPOApplicationRecorder(
|
|
102
|
+
class POPOApplicationRecorder(POPOAggregateRecorder, ApplicationRecorder):
|
|
103
|
+
def __init__(self) -> None:
|
|
104
|
+
super().__init__()
|
|
105
|
+
self._listeners: Set[Event] = set()
|
|
106
|
+
|
|
95
107
|
def insert_events(
|
|
96
108
|
self, stored_events: List[StoredEvent], **kwargs: Any
|
|
97
109
|
) -> Sequence[int] | None:
|
|
98
|
-
|
|
110
|
+
notification_ids = self._insert_events(stored_events, **kwargs)
|
|
111
|
+
self._notify_listeners()
|
|
112
|
+
return notification_ids
|
|
99
113
|
|
|
100
114
|
def select_notifications(
|
|
101
115
|
self,
|
|
102
|
-
start: int,
|
|
116
|
+
start: int | None,
|
|
103
117
|
limit: int,
|
|
104
118
|
stop: int | None = None,
|
|
105
119
|
topics: Sequence[str] = (),
|
|
120
|
+
*,
|
|
121
|
+
inclusive_of_start: bool = True,
|
|
106
122
|
) -> List[Notification]:
|
|
107
123
|
with self._database_lock:
|
|
108
124
|
results = []
|
|
125
|
+
if start is None:
|
|
126
|
+
start = 1
|
|
127
|
+
inclusive_of_start = True
|
|
128
|
+
if not inclusive_of_start:
|
|
129
|
+
start += 1
|
|
109
130
|
start = max(start, 1) # Don't use negative indexes!
|
|
110
|
-
i = start - 1
|
|
131
|
+
i = start - 1 # Zero-based indexing.
|
|
111
132
|
while True:
|
|
112
133
|
if stop is not None and i > stop - 1:
|
|
113
134
|
break
|
|
@@ -130,28 +151,82 @@ class POPOApplicationRecorder(ApplicationRecorder, POPOAggregateRecorder):
|
|
|
130
151
|
break
|
|
131
152
|
return results
|
|
132
153
|
|
|
133
|
-
def max_notification_id(self) -> int:
|
|
154
|
+
def max_notification_id(self) -> int | None:
|
|
134
155
|
with self._database_lock:
|
|
135
|
-
return len(self._stored_events)
|
|
156
|
+
return len(self._stored_events) or None
|
|
157
|
+
|
|
158
|
+
def subscribe(self, gt: int | None = None) -> POPOSubscription:
|
|
159
|
+
return POPOSubscription(self, gt)
|
|
136
160
|
|
|
161
|
+
def listen(self, event: Event) -> None:
|
|
162
|
+
self._listeners.add(event)
|
|
137
163
|
|
|
138
|
-
|
|
164
|
+
def unlisten(self, event: Event) -> None:
|
|
165
|
+
with contextlib.suppress(KeyError):
|
|
166
|
+
self._listeners.remove(event)
|
|
167
|
+
|
|
168
|
+
def _notify_listeners(self) -> None:
|
|
169
|
+
for listener in self._listeners:
|
|
170
|
+
listener.set()
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
class POPOSubscription(ListenNotifySubscription[POPOApplicationRecorder]):
|
|
174
|
+
def __init__(
|
|
175
|
+
self, recorder: POPOApplicationRecorder, gt: int | None = None
|
|
176
|
+
) -> None:
|
|
177
|
+
assert isinstance(recorder, POPOApplicationRecorder)
|
|
178
|
+
super().__init__(recorder=recorder, gt=gt)
|
|
179
|
+
self._recorder.listen(self._has_been_notified)
|
|
180
|
+
|
|
181
|
+
def stop(self) -> None:
|
|
182
|
+
super().stop()
|
|
183
|
+
self._recorder.unlisten(self._has_been_notified)
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
class POPOTrackingRecorder(POPORecorder, TrackingRecorder):
|
|
139
187
|
def __init__(self) -> None:
|
|
140
188
|
super().__init__()
|
|
141
189
|
self._tracking_table: Dict[str, set[int]] = defaultdict(set)
|
|
142
|
-
self._max_tracking_ids: Dict[str, int] = defaultdict(lambda:
|
|
190
|
+
self._max_tracking_ids: Dict[str, int | None] = defaultdict(lambda: None)
|
|
191
|
+
|
|
192
|
+
def _assert_tracking_uniqueness(self, tracking: Tracking) -> None:
|
|
193
|
+
if tracking.notification_id in self._tracking_table[tracking.application_name]:
|
|
194
|
+
msg = (
|
|
195
|
+
f"Already recorded notification ID {tracking.notification_id} "
|
|
196
|
+
f"for application {tracking.application_name}"
|
|
197
|
+
)
|
|
198
|
+
raise IntegrityError(msg)
|
|
199
|
+
|
|
200
|
+
def insert_tracking(self, tracking: Tracking) -> None:
|
|
201
|
+
with self._database_lock:
|
|
202
|
+
self._assert_tracking_uniqueness(tracking)
|
|
203
|
+
self._insert_tracking(tracking)
|
|
204
|
+
|
|
205
|
+
def _insert_tracking(self, tracking: Tracking) -> None:
|
|
206
|
+
self._tracking_table[tracking.application_name].add(tracking.notification_id)
|
|
207
|
+
max_tracking_id = self._max_tracking_ids[tracking.application_name]
|
|
208
|
+
if max_tracking_id is None or max_tracking_id < tracking.notification_id:
|
|
209
|
+
self._max_tracking_ids[tracking.application_name] = tracking.notification_id
|
|
210
|
+
|
|
211
|
+
def max_tracking_id(self, application_name: str) -> int | None:
|
|
212
|
+
with self._database_lock:
|
|
213
|
+
return self._max_tracking_ids[application_name]
|
|
214
|
+
|
|
215
|
+
def has_tracking_id(self, application_name: str, notification_id: int) -> bool:
|
|
216
|
+
with self._database_lock:
|
|
217
|
+
return notification_id in self._tracking_table[application_name]
|
|
143
218
|
|
|
219
|
+
|
|
220
|
+
class POPOProcessRecorder(
|
|
221
|
+
POPOTrackingRecorder, POPOApplicationRecorder, ProcessRecorder
|
|
222
|
+
):
|
|
144
223
|
def _assert_uniqueness(
|
|
145
224
|
self, stored_events: List[StoredEvent], **kwargs: Any
|
|
146
225
|
) -> None:
|
|
147
226
|
super()._assert_uniqueness(stored_events, **kwargs)
|
|
148
227
|
t: Tracking | None = kwargs.get("tracking", None)
|
|
149
|
-
if t
|
|
150
|
-
|
|
151
|
-
f"Already recorded notification ID {t.notification_id} "
|
|
152
|
-
f"for application {t.application_name}"
|
|
153
|
-
)
|
|
154
|
-
raise IntegrityError(msg)
|
|
228
|
+
if t:
|
|
229
|
+
self._assert_tracking_uniqueness(t)
|
|
155
230
|
|
|
156
231
|
def _update_table(
|
|
157
232
|
self, stored_events: List[StoredEvent], **kwargs: Any
|
|
@@ -159,26 +234,32 @@ class POPOProcessRecorder(ProcessRecorder, POPOApplicationRecorder):
|
|
|
159
234
|
notification_ids = super()._update_table(stored_events, **kwargs)
|
|
160
235
|
t: Tracking | None = kwargs.get("tracking", None)
|
|
161
236
|
if t:
|
|
162
|
-
self.
|
|
163
|
-
if self._max_tracking_ids[t.application_name] < t.notification_id:
|
|
164
|
-
self._max_tracking_ids[t.application_name] = t.notification_id
|
|
237
|
+
self._insert_tracking(t)
|
|
165
238
|
return notification_ids
|
|
166
239
|
|
|
167
|
-
def max_tracking_id(self, application_name: str) -> int:
|
|
168
|
-
with self._database_lock:
|
|
169
|
-
return self._max_tracking_ids[application_name]
|
|
170
|
-
|
|
171
|
-
def has_tracking_id(self, application_name: str, notification_id: int) -> bool:
|
|
172
|
-
with self._database_lock:
|
|
173
|
-
return notification_id in self._tracking_table[application_name]
|
|
174
|
-
|
|
175
240
|
|
|
176
|
-
class
|
|
241
|
+
class POPOFactory(InfrastructureFactory[POPOTrackingRecorder]):
|
|
177
242
|
def aggregate_recorder(self, purpose: str = "events") -> AggregateRecorder:
|
|
178
243
|
return POPOAggregateRecorder()
|
|
179
244
|
|
|
180
245
|
def application_recorder(self) -> ApplicationRecorder:
|
|
181
246
|
return POPOApplicationRecorder()
|
|
182
247
|
|
|
248
|
+
def tracking_recorder(
|
|
249
|
+
self, tracking_recorder_class: Type[POPOTrackingRecorder] | None = None
|
|
250
|
+
) -> POPOTrackingRecorder:
|
|
251
|
+
if tracking_recorder_class is None:
|
|
252
|
+
tracking_recorder_topic = self.env.get(self.TRACKING_RECORDER_TOPIC)
|
|
253
|
+
if tracking_recorder_topic:
|
|
254
|
+
tracking_recorder_class = resolve_topic(tracking_recorder_topic)
|
|
255
|
+
else:
|
|
256
|
+
tracking_recorder_class = POPOTrackingRecorder
|
|
257
|
+
assert tracking_recorder_class is not None
|
|
258
|
+
assert issubclass(tracking_recorder_class, POPOTrackingRecorder)
|
|
259
|
+
return tracking_recorder_class()
|
|
260
|
+
|
|
183
261
|
def process_recorder(self) -> ProcessRecorder:
|
|
184
262
|
return POPOProcessRecorder()
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
Factory = POPOFactory
|