eventsourcing 9.3.5__py3-none-any.whl → 9.4.0__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/__init__.py +0 -1
- eventsourcing/application.py +115 -173
- eventsourcing/cipher.py +9 -10
- eventsourcing/compressor.py +2 -6
- eventsourcing/cryptography.py +91 -0
- eventsourcing/dispatch.py +52 -11
- eventsourcing/domain.py +733 -690
- eventsourcing/interface.py +39 -32
- eventsourcing/persistence.py +412 -287
- eventsourcing/popo.py +136 -44
- eventsourcing/postgres.py +404 -187
- eventsourcing/projection.py +428 -0
- eventsourcing/sqlite.py +167 -55
- eventsourcing/system.py +230 -341
- eventsourcing/tests/__init__.py +3 -0
- eventsourcing/tests/application.py +195 -129
- eventsourcing/tests/domain.py +19 -37
- eventsourcing/tests/persistence.py +533 -235
- eventsourcing/tests/postgres_utils.py +12 -9
- eventsourcing/utils.py +39 -47
- {eventsourcing-9.3.5.dist-info → eventsourcing-9.4.0.dist-info}/LICENSE +1 -1
- {eventsourcing-9.3.5.dist-info → eventsourcing-9.4.0.dist-info}/METADATA +14 -13
- eventsourcing-9.4.0.dist-info/RECORD +26 -0
- {eventsourcing-9.3.5.dist-info → eventsourcing-9.4.0.dist-info}/WHEEL +1 -1
- eventsourcing-9.3.5.dist-info/RECORD +0 -24
- {eventsourcing-9.3.5.dist-info → eventsourcing-9.4.0.dist-info}/AUTHORS +0 -0
eventsourcing/popo.py
CHANGED
|
@@ -1,45 +1,55 @@
|
|
|
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
|
|
5
|
+
from threading import Event, Lock
|
|
6
|
+
from typing import TYPE_CHECKING, Any
|
|
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,
|
|
17
|
+
Subscription,
|
|
15
18
|
Tracking,
|
|
19
|
+
TrackingRecorder,
|
|
16
20
|
)
|
|
17
|
-
from eventsourcing.utils import reversed_keys
|
|
21
|
+
from eventsourcing.utils import resolve_topic, reversed_keys
|
|
18
22
|
|
|
19
|
-
if TYPE_CHECKING:
|
|
23
|
+
if TYPE_CHECKING:
|
|
24
|
+
from collections.abc import Iterable, Sequence
|
|
20
25
|
from uuid import UUID
|
|
21
26
|
|
|
22
27
|
|
|
23
|
-
class
|
|
28
|
+
class POPORecorder:
|
|
24
29
|
def __init__(self) -> None:
|
|
25
|
-
self._stored_events: List[StoredEvent] = []
|
|
26
|
-
self._stored_events_index: Dict[UUID, Dict[int, int]] = defaultdict(dict)
|
|
27
30
|
self._database_lock = Lock()
|
|
28
31
|
|
|
32
|
+
|
|
33
|
+
class POPOAggregateRecorder(POPORecorder, AggregateRecorder):
|
|
34
|
+
def __init__(self) -> None:
|
|
35
|
+
super().__init__()
|
|
36
|
+
self._stored_events: list[StoredEvent] = []
|
|
37
|
+
self._stored_events_index: dict[UUID, dict[int, int]] = defaultdict(dict)
|
|
38
|
+
|
|
29
39
|
def insert_events(
|
|
30
|
-
self, stored_events:
|
|
40
|
+
self, stored_events: list[StoredEvent], **kwargs: Any
|
|
31
41
|
) -> Sequence[int] | None:
|
|
32
42
|
self._insert_events(stored_events, **kwargs)
|
|
33
43
|
return None
|
|
34
44
|
|
|
35
45
|
def _insert_events(
|
|
36
|
-
self, stored_events:
|
|
46
|
+
self, stored_events: list[StoredEvent], **kwargs: Any
|
|
37
47
|
) -> Sequence[int] | None:
|
|
38
48
|
with self._database_lock:
|
|
39
49
|
self._assert_uniqueness(stored_events, **kwargs)
|
|
40
50
|
return self._update_table(stored_events, **kwargs)
|
|
41
51
|
|
|
42
|
-
def _assert_uniqueness(self, stored_events:
|
|
52
|
+
def _assert_uniqueness(self, stored_events: list[StoredEvent], **_: Any) -> None:
|
|
43
53
|
new = set()
|
|
44
54
|
for s in stored_events:
|
|
45
55
|
# Check events don't already exist.
|
|
@@ -53,7 +63,7 @@ class POPOAggregateRecorder(AggregateRecorder):
|
|
|
53
63
|
raise IntegrityError(msg)
|
|
54
64
|
|
|
55
65
|
def _update_table(
|
|
56
|
-
self, stored_events:
|
|
66
|
+
self, stored_events: list[StoredEvent], **_: Any
|
|
57
67
|
) -> Sequence[int] | None:
|
|
58
68
|
notification_ids = []
|
|
59
69
|
for s in stored_events:
|
|
@@ -72,7 +82,7 @@ class POPOAggregateRecorder(AggregateRecorder):
|
|
|
72
82
|
lte: int | None = None,
|
|
73
83
|
desc: bool = False,
|
|
74
84
|
limit: int | None = None,
|
|
75
|
-
) ->
|
|
85
|
+
) -> list[StoredEvent]:
|
|
76
86
|
with self._database_lock:
|
|
77
87
|
results = []
|
|
78
88
|
|
|
@@ -91,23 +101,36 @@ class POPOAggregateRecorder(AggregateRecorder):
|
|
|
91
101
|
return results
|
|
92
102
|
|
|
93
103
|
|
|
94
|
-
class POPOApplicationRecorder(
|
|
104
|
+
class POPOApplicationRecorder(POPOAggregateRecorder, ApplicationRecorder):
|
|
105
|
+
def __init__(self) -> None:
|
|
106
|
+
super().__init__()
|
|
107
|
+
self._listeners: set[Event] = set()
|
|
108
|
+
|
|
95
109
|
def insert_events(
|
|
96
|
-
self, stored_events:
|
|
110
|
+
self, stored_events: list[StoredEvent], **kwargs: Any
|
|
97
111
|
) -> Sequence[int] | None:
|
|
98
|
-
|
|
112
|
+
notification_ids = self._insert_events(stored_events, **kwargs)
|
|
113
|
+
self._notify_listeners()
|
|
114
|
+
return notification_ids
|
|
99
115
|
|
|
100
116
|
def select_notifications(
|
|
101
117
|
self,
|
|
102
|
-
start: int,
|
|
118
|
+
start: int | None,
|
|
103
119
|
limit: int,
|
|
104
120
|
stop: int | None = None,
|
|
105
121
|
topics: Sequence[str] = (),
|
|
106
|
-
|
|
122
|
+
*,
|
|
123
|
+
inclusive_of_start: bool = True,
|
|
124
|
+
) -> list[Notification]:
|
|
107
125
|
with self._database_lock:
|
|
108
126
|
results = []
|
|
127
|
+
if start is None:
|
|
128
|
+
start = 1
|
|
129
|
+
inclusive_of_start = True
|
|
130
|
+
if not inclusive_of_start:
|
|
131
|
+
start += 1
|
|
109
132
|
start = max(start, 1) # Don't use negative indexes!
|
|
110
|
-
i = start - 1
|
|
133
|
+
i = start - 1 # Zero-based indexing.
|
|
111
134
|
while True:
|
|
112
135
|
if stop is not None and i > stop - 1:
|
|
113
136
|
break
|
|
@@ -130,55 +153,124 @@ class POPOApplicationRecorder(ApplicationRecorder, POPOAggregateRecorder):
|
|
|
130
153
|
break
|
|
131
154
|
return results
|
|
132
155
|
|
|
133
|
-
def max_notification_id(self) -> int:
|
|
156
|
+
def max_notification_id(self) -> int | None:
|
|
134
157
|
with self._database_lock:
|
|
135
|
-
return len(self._stored_events)
|
|
158
|
+
return len(self._stored_events) or None
|
|
159
|
+
|
|
160
|
+
def subscribe(
|
|
161
|
+
self, gt: int | None = None, topics: Sequence[str] = ()
|
|
162
|
+
) -> Subscription[ApplicationRecorder]:
|
|
163
|
+
return POPOSubscription(recorder=self, gt=gt, topics=topics)
|
|
164
|
+
|
|
165
|
+
def listen(self, event: Event) -> None:
|
|
166
|
+
self._listeners.add(event)
|
|
167
|
+
|
|
168
|
+
def unlisten(self, event: Event) -> None:
|
|
169
|
+
with contextlib.suppress(KeyError):
|
|
170
|
+
self._listeners.remove(event)
|
|
136
171
|
|
|
172
|
+
def _notify_listeners(self) -> None:
|
|
173
|
+
for listener in self._listeners:
|
|
174
|
+
listener.set()
|
|
137
175
|
|
|
138
|
-
|
|
176
|
+
|
|
177
|
+
class POPOSubscription(ListenNotifySubscription[POPOApplicationRecorder]):
|
|
178
|
+
def __init__(
|
|
179
|
+
self,
|
|
180
|
+
recorder: POPOApplicationRecorder,
|
|
181
|
+
gt: int | None = None,
|
|
182
|
+
topics: Sequence[str] = (),
|
|
183
|
+
) -> None:
|
|
184
|
+
assert isinstance(recorder, POPOApplicationRecorder)
|
|
185
|
+
super().__init__(recorder=recorder, gt=gt, topics=topics)
|
|
186
|
+
self._recorder.listen(self._has_been_notified)
|
|
187
|
+
|
|
188
|
+
def stop(self) -> None:
|
|
189
|
+
super().stop()
|
|
190
|
+
self._recorder.unlisten(self._has_been_notified)
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
class POPOTrackingRecorder(POPORecorder, TrackingRecorder):
|
|
139
194
|
def __init__(self) -> None:
|
|
140
195
|
super().__init__()
|
|
141
|
-
self._tracking_table:
|
|
142
|
-
self._max_tracking_ids:
|
|
196
|
+
self._tracking_table: dict[str, set[int]] = defaultdict(set)
|
|
197
|
+
self._max_tracking_ids: dict[str, int | None] = defaultdict(lambda: None)
|
|
143
198
|
|
|
144
|
-
def
|
|
145
|
-
|
|
146
|
-
) -> None:
|
|
147
|
-
super()._assert_uniqueness(stored_events, **kwargs)
|
|
148
|
-
t: Tracking | None = kwargs.get("tracking", None)
|
|
149
|
-
if t and t.notification_id in self._tracking_table[t.application_name]:
|
|
199
|
+
def _assert_tracking_uniqueness(self, tracking: Tracking) -> None:
|
|
200
|
+
if tracking.notification_id in self._tracking_table[tracking.application_name]:
|
|
150
201
|
msg = (
|
|
151
|
-
f"Already recorded notification ID {
|
|
152
|
-
f"for application {
|
|
202
|
+
f"Already recorded notification ID {tracking.notification_id} "
|
|
203
|
+
f"for application {tracking.application_name}"
|
|
153
204
|
)
|
|
154
205
|
raise IntegrityError(msg)
|
|
155
206
|
|
|
156
|
-
def
|
|
157
|
-
self
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
t: Tracking | None = kwargs.get("tracking", None)
|
|
161
|
-
if t:
|
|
162
|
-
self._tracking_table[t.application_name].add(t.notification_id)
|
|
163
|
-
if self._max_tracking_ids[t.application_name] < t.notification_id:
|
|
164
|
-
self._max_tracking_ids[t.application_name] = t.notification_id
|
|
165
|
-
return notification_ids
|
|
207
|
+
def insert_tracking(self, tracking: Tracking) -> None:
|
|
208
|
+
with self._database_lock:
|
|
209
|
+
self._assert_tracking_uniqueness(tracking)
|
|
210
|
+
self._insert_tracking(tracking)
|
|
166
211
|
|
|
167
|
-
def
|
|
212
|
+
def _insert_tracking(self, tracking: Tracking) -> None:
|
|
213
|
+
self._tracking_table[tracking.application_name].add(tracking.notification_id)
|
|
214
|
+
max_tracking_id = self._max_tracking_ids[tracking.application_name]
|
|
215
|
+
if max_tracking_id is None or max_tracking_id < tracking.notification_id:
|
|
216
|
+
self._max_tracking_ids[tracking.application_name] = tracking.notification_id
|
|
217
|
+
|
|
218
|
+
def max_tracking_id(self, application_name: str) -> int | None:
|
|
168
219
|
with self._database_lock:
|
|
169
220
|
return self._max_tracking_ids[application_name]
|
|
170
221
|
|
|
171
|
-
def has_tracking_id(
|
|
222
|
+
def has_tracking_id(
|
|
223
|
+
self, application_name: str, notification_id: int | None
|
|
224
|
+
) -> bool:
|
|
225
|
+
if notification_id is None:
|
|
226
|
+
return True
|
|
172
227
|
with self._database_lock:
|
|
173
228
|
return notification_id in self._tracking_table[application_name]
|
|
174
229
|
|
|
175
230
|
|
|
176
|
-
class
|
|
231
|
+
class POPOProcessRecorder(
|
|
232
|
+
POPOTrackingRecorder, POPOApplicationRecorder, ProcessRecorder
|
|
233
|
+
):
|
|
234
|
+
def _assert_uniqueness(
|
|
235
|
+
self, stored_events: list[StoredEvent], **kwargs: Any
|
|
236
|
+
) -> None:
|
|
237
|
+
super()._assert_uniqueness(stored_events, **kwargs)
|
|
238
|
+
t: Tracking | None = kwargs.get("tracking")
|
|
239
|
+
if t:
|
|
240
|
+
self._assert_tracking_uniqueness(t)
|
|
241
|
+
|
|
242
|
+
def _update_table(
|
|
243
|
+
self, stored_events: list[StoredEvent], **kwargs: Any
|
|
244
|
+
) -> Sequence[int] | None:
|
|
245
|
+
notification_ids = super()._update_table(stored_events, **kwargs)
|
|
246
|
+
t: Tracking | None = kwargs.get("tracking")
|
|
247
|
+
if t:
|
|
248
|
+
self._insert_tracking(t)
|
|
249
|
+
return notification_ids
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
class POPOFactory(InfrastructureFactory[POPOTrackingRecorder]):
|
|
177
253
|
def aggregate_recorder(self, purpose: str = "events") -> AggregateRecorder:
|
|
178
254
|
return POPOAggregateRecorder()
|
|
179
255
|
|
|
180
256
|
def application_recorder(self) -> ApplicationRecorder:
|
|
181
257
|
return POPOApplicationRecorder()
|
|
182
258
|
|
|
259
|
+
def tracking_recorder(
|
|
260
|
+
self, tracking_recorder_class: type[POPOTrackingRecorder] | None = None
|
|
261
|
+
) -> POPOTrackingRecorder:
|
|
262
|
+
if tracking_recorder_class is None:
|
|
263
|
+
tracking_recorder_topic = self.env.get(self.TRACKING_RECORDER_TOPIC)
|
|
264
|
+
if tracking_recorder_topic:
|
|
265
|
+
tracking_recorder_class = resolve_topic(tracking_recorder_topic)
|
|
266
|
+
else:
|
|
267
|
+
tracking_recorder_class = POPOTrackingRecorder
|
|
268
|
+
assert tracking_recorder_class is not None
|
|
269
|
+
assert issubclass(tracking_recorder_class, POPOTrackingRecorder)
|
|
270
|
+
return tracking_recorder_class()
|
|
271
|
+
|
|
183
272
|
def process_recorder(self) -> ProcessRecorder:
|
|
184
273
|
return POPOProcessRecorder()
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
Factory = POPOFactory
|