eventsourcing 9.4.0a3__tar.gz → 9.4.0a5__tar.gz
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-9.4.0a3 → eventsourcing-9.4.0a5}/PKG-INFO +3 -3
- {eventsourcing-9.4.0a3 → eventsourcing-9.4.0a5}/eventsourcing/persistence.py +14 -4
- {eventsourcing-9.4.0a3 → eventsourcing-9.4.0a5}/eventsourcing/popo.py +10 -4
- {eventsourcing-9.4.0a3 → eventsourcing-9.4.0a5}/eventsourcing/postgres.py +10 -4
- {eventsourcing-9.4.0a3 → eventsourcing-9.4.0a5}/eventsourcing/projection.py +29 -14
- {eventsourcing-9.4.0a3 → eventsourcing-9.4.0a5}/eventsourcing/sqlite.py +3 -1
- {eventsourcing-9.4.0a3 → eventsourcing-9.4.0a5}/eventsourcing/tests/persistence.py +12 -0
- {eventsourcing-9.4.0a3 → eventsourcing-9.4.0a5}/pyproject.toml +1 -1
- {eventsourcing-9.4.0a3 → eventsourcing-9.4.0a5}/AUTHORS +0 -0
- {eventsourcing-9.4.0a3 → eventsourcing-9.4.0a5}/LICENSE +0 -0
- {eventsourcing-9.4.0a3 → eventsourcing-9.4.0a5}/README.md +0 -0
- {eventsourcing-9.4.0a3 → eventsourcing-9.4.0a5}/eventsourcing/__init__.py +0 -0
- {eventsourcing-9.4.0a3 → eventsourcing-9.4.0a5}/eventsourcing/application.py +0 -0
- {eventsourcing-9.4.0a3 → eventsourcing-9.4.0a5}/eventsourcing/cipher.py +0 -0
- {eventsourcing-9.4.0a3 → eventsourcing-9.4.0a5}/eventsourcing/compressor.py +0 -0
- {eventsourcing-9.4.0a3 → eventsourcing-9.4.0a5}/eventsourcing/cryptography.py +0 -0
- {eventsourcing-9.4.0a3 → eventsourcing-9.4.0a5}/eventsourcing/dispatch.py +0 -0
- {eventsourcing-9.4.0a3 → eventsourcing-9.4.0a5}/eventsourcing/domain.py +0 -0
- {eventsourcing-9.4.0a3 → eventsourcing-9.4.0a5}/eventsourcing/interface.py +0 -0
- {eventsourcing-9.4.0a3 → eventsourcing-9.4.0a5}/eventsourcing/py.typed +0 -0
- {eventsourcing-9.4.0a3 → eventsourcing-9.4.0a5}/eventsourcing/system.py +0 -0
- {eventsourcing-9.4.0a3 → eventsourcing-9.4.0a5}/eventsourcing/tests/__init__.py +0 -0
- {eventsourcing-9.4.0a3 → eventsourcing-9.4.0a5}/eventsourcing/tests/application.py +0 -0
- {eventsourcing-9.4.0a3 → eventsourcing-9.4.0a5}/eventsourcing/tests/domain.py +0 -0
- {eventsourcing-9.4.0a3 → eventsourcing-9.4.0a5}/eventsourcing/tests/postgres_utils.py +0 -0
- {eventsourcing-9.4.0a3 → eventsourcing-9.4.0a5}/eventsourcing/utils.py +0 -0
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
2
|
Name: eventsourcing
|
|
3
|
-
Version: 9.4.
|
|
3
|
+
Version: 9.4.0a5
|
|
4
4
|
Summary: Event sourcing in Python
|
|
5
|
-
Home-page: https://github.com/pyeventsourcing/eventsourcing
|
|
6
5
|
License: BSD 3-Clause
|
|
7
6
|
Keywords: event sourcing,event store,domain driven design,domain-driven design,ddd,cqrs,cqs
|
|
8
7
|
Author: John Bywater
|
|
@@ -32,6 +31,7 @@ Requires-Dist: cryptography (>=44.0,<44.1) ; extra == "cryptography"
|
|
|
32
31
|
Requires-Dist: psycopg[pool] (<=3.2.99999) ; extra == "postgres"
|
|
33
32
|
Requires-Dist: pycryptodome (>=3.22,<3.23) ; extra == "crypto"
|
|
34
33
|
Requires-Dist: typing_extensions
|
|
34
|
+
Project-URL: Homepage, https://github.com/pyeventsourcing/eventsourcing
|
|
35
35
|
Project-URL: Repository, https://github.com/pyeventsourcing/eventsourcing
|
|
36
36
|
Description-Content-Type: text/markdown
|
|
37
37
|
|
|
@@ -486,7 +486,9 @@ class ApplicationRecorder(AggregateRecorder):
|
|
|
486
486
|
"""
|
|
487
487
|
|
|
488
488
|
@abstractmethod
|
|
489
|
-
def subscribe(
|
|
489
|
+
def subscribe(
|
|
490
|
+
self, gt: int | None = None, topics: Sequence[str] = ()
|
|
491
|
+
) -> Subscription[ApplicationRecorder]:
|
|
490
492
|
"""
|
|
491
493
|
Returns an iterator of Notification objects representing events from an
|
|
492
494
|
application sequence.
|
|
@@ -1311,10 +1313,14 @@ TApplicationRecorder_co = TypeVar(
|
|
|
1311
1313
|
|
|
1312
1314
|
class Subscription(Iterator[Notification], Generic[TApplicationRecorder_co]):
|
|
1313
1315
|
def __init__(
|
|
1314
|
-
self,
|
|
1316
|
+
self,
|
|
1317
|
+
recorder: TApplicationRecorder_co,
|
|
1318
|
+
gt: int | None = None,
|
|
1319
|
+
topics: Sequence[str] = (),
|
|
1315
1320
|
) -> None:
|
|
1316
1321
|
self._recorder = recorder
|
|
1317
1322
|
self._last_notification_id = gt
|
|
1323
|
+
self._topics = topics
|
|
1318
1324
|
self._has_been_entered = False
|
|
1319
1325
|
self._has_been_stopped = False
|
|
1320
1326
|
|
|
@@ -1349,9 +1355,12 @@ class Subscription(Iterator[Notification], Generic[TApplicationRecorder_co]):
|
|
|
1349
1355
|
|
|
1350
1356
|
class ListenNotifySubscription(Subscription[TApplicationRecorder_co]):
|
|
1351
1357
|
def __init__(
|
|
1352
|
-
self,
|
|
1358
|
+
self,
|
|
1359
|
+
recorder: TApplicationRecorder_co,
|
|
1360
|
+
gt: int | None = None,
|
|
1361
|
+
topics: Sequence[str] = (),
|
|
1353
1362
|
) -> None:
|
|
1354
|
-
super().__init__(recorder=recorder, gt=gt)
|
|
1363
|
+
super().__init__(recorder=recorder, gt=gt, topics=topics)
|
|
1355
1364
|
self._select_limit = 500
|
|
1356
1365
|
self._notifications: List[Notification] = []
|
|
1357
1366
|
self._notifications_index: int = 0
|
|
@@ -1410,6 +1419,7 @@ class ListenNotifySubscription(Subscription[TApplicationRecorder_co]):
|
|
|
1410
1419
|
notifications = self._recorder.select_notifications(
|
|
1411
1420
|
start=self._last_notification_id or 0,
|
|
1412
1421
|
limit=self._select_limit,
|
|
1422
|
+
topics=self._topics,
|
|
1413
1423
|
inclusive_of_start=False,
|
|
1414
1424
|
)
|
|
1415
1425
|
if len(notifications) > 0:
|
|
@@ -14,6 +14,7 @@ from eventsourcing.persistence import (
|
|
|
14
14
|
Notification,
|
|
15
15
|
ProcessRecorder,
|
|
16
16
|
StoredEvent,
|
|
17
|
+
Subscription,
|
|
17
18
|
Tracking,
|
|
18
19
|
TrackingRecorder,
|
|
19
20
|
)
|
|
@@ -155,8 +156,10 @@ class POPOApplicationRecorder(POPOAggregateRecorder, ApplicationRecorder):
|
|
|
155
156
|
with self._database_lock:
|
|
156
157
|
return len(self._stored_events) or None
|
|
157
158
|
|
|
158
|
-
def subscribe(
|
|
159
|
-
|
|
159
|
+
def subscribe(
|
|
160
|
+
self, gt: int | None = None, topics: Sequence[str] = ()
|
|
161
|
+
) -> Subscription[ApplicationRecorder]:
|
|
162
|
+
return POPOSubscription(recorder=self, gt=gt, topics=topics)
|
|
160
163
|
|
|
161
164
|
def listen(self, event: Event) -> None:
|
|
162
165
|
self._listeners.add(event)
|
|
@@ -172,10 +175,13 @@ class POPOApplicationRecorder(POPOAggregateRecorder, ApplicationRecorder):
|
|
|
172
175
|
|
|
173
176
|
class POPOSubscription(ListenNotifySubscription[POPOApplicationRecorder]):
|
|
174
177
|
def __init__(
|
|
175
|
-
self,
|
|
178
|
+
self,
|
|
179
|
+
recorder: POPOApplicationRecorder,
|
|
180
|
+
gt: int | None = None,
|
|
181
|
+
topics: Sequence[str] = (),
|
|
176
182
|
) -> None:
|
|
177
183
|
assert isinstance(recorder, POPOApplicationRecorder)
|
|
178
|
-
super().__init__(recorder=recorder, gt=gt)
|
|
184
|
+
super().__init__(recorder=recorder, gt=gt, topics=topics)
|
|
179
185
|
self._recorder.listen(self._has_been_notified)
|
|
180
186
|
|
|
181
187
|
def stop(self) -> None:
|
|
@@ -30,6 +30,7 @@ from eventsourcing.persistence import (
|
|
|
30
30
|
ProcessRecorder,
|
|
31
31
|
ProgrammingError,
|
|
32
32
|
StoredEvent,
|
|
33
|
+
Subscription,
|
|
33
34
|
Tracking,
|
|
34
35
|
TrackingRecorder,
|
|
35
36
|
)
|
|
@@ -524,16 +525,21 @@ class PostgresApplicationRecorder(PostgresAggregateRecorder, ApplicationRecorder
|
|
|
524
525
|
raise ProgrammingError(msg)
|
|
525
526
|
return notification_ids
|
|
526
527
|
|
|
527
|
-
def subscribe(
|
|
528
|
-
|
|
528
|
+
def subscribe(
|
|
529
|
+
self, gt: int | None = None, topics: Sequence[str] = ()
|
|
530
|
+
) -> Subscription[ApplicationRecorder]:
|
|
531
|
+
return PostgresSubscription(recorder=self, gt=gt, topics=topics)
|
|
529
532
|
|
|
530
533
|
|
|
531
534
|
class PostgresSubscription(ListenNotifySubscription[PostgresApplicationRecorder]):
|
|
532
535
|
def __init__(
|
|
533
|
-
self,
|
|
536
|
+
self,
|
|
537
|
+
recorder: PostgresApplicationRecorder,
|
|
538
|
+
gt: int | None = None,
|
|
539
|
+
topics: Sequence[str] = (),
|
|
534
540
|
) -> None:
|
|
535
541
|
assert isinstance(recorder, PostgresApplicationRecorder)
|
|
536
|
-
super().__init__(recorder=recorder, gt=gt)
|
|
542
|
+
super().__init__(recorder=recorder, gt=gt, topics=topics)
|
|
537
543
|
self._listen_thread = Thread(target=self._listen)
|
|
538
544
|
self._listen_thread.start()
|
|
539
545
|
|
|
@@ -5,7 +5,17 @@ import weakref
|
|
|
5
5
|
from abc import ABC, abstractmethod
|
|
6
6
|
from threading import Event, Thread
|
|
7
7
|
from traceback import format_exc
|
|
8
|
-
from typing import
|
|
8
|
+
from typing import (
|
|
9
|
+
TYPE_CHECKING,
|
|
10
|
+
Any,
|
|
11
|
+
Dict,
|
|
12
|
+
Generic,
|
|
13
|
+
Iterator,
|
|
14
|
+
Sequence,
|
|
15
|
+
Tuple,
|
|
16
|
+
Type,
|
|
17
|
+
TypeVar,
|
|
18
|
+
)
|
|
9
19
|
from warnings import warn
|
|
10
20
|
|
|
11
21
|
from eventsourcing.application import Application
|
|
@@ -37,11 +47,12 @@ class ApplicationSubscription(Iterator[Tuple[DomainEventProtocol, Tracking]]):
|
|
|
37
47
|
self,
|
|
38
48
|
app: Application,
|
|
39
49
|
gt: int | None = None,
|
|
50
|
+
topics: Sequence[str] = (),
|
|
40
51
|
):
|
|
41
52
|
self.name = app.name
|
|
42
53
|
self.recorder = app.recorder
|
|
43
54
|
self.mapper = app.mapper
|
|
44
|
-
self.subscription = self.recorder.subscribe(gt=gt)
|
|
55
|
+
self.subscription = self.recorder.subscribe(gt=gt, topics=topics)
|
|
45
56
|
|
|
46
57
|
def __enter__(self) -> Self:
|
|
47
58
|
self.subscription.__enter__()
|
|
@@ -68,6 +79,9 @@ class ApplicationSubscription(Iterator[Tuple[DomainEventProtocol, Tracking]]):
|
|
|
68
79
|
|
|
69
80
|
class Projection(ABC, Generic[TTrackingRecorder]):
|
|
70
81
|
name: str = ""
|
|
82
|
+
"""Name of projection, used to pick prefixed environment variables."""
|
|
83
|
+
topics: Sequence[str] = ()
|
|
84
|
+
"""Event topics, used to filter events in database."""
|
|
71
85
|
|
|
72
86
|
def __init__(
|
|
73
87
|
self,
|
|
@@ -112,21 +126,22 @@ class ProjectionRunner(Generic[TApplication, TTrackingRecorder]):
|
|
|
112
126
|
self.projection_factory.tracking_recorder(tracking_recorder_class)
|
|
113
127
|
)
|
|
114
128
|
|
|
129
|
+
self.projection = projection_class(
|
|
130
|
+
tracking_recorder=self.tracking_recorder,
|
|
131
|
+
)
|
|
115
132
|
self.subscription = ApplicationSubscription(
|
|
116
133
|
app=self.app,
|
|
117
134
|
gt=self.tracking_recorder.max_tracking_id(self.app.name),
|
|
135
|
+
topics=self.projection.topics,
|
|
118
136
|
)
|
|
119
|
-
self.
|
|
120
|
-
tracking_recorder=self.tracking_recorder,
|
|
121
|
-
)
|
|
122
|
-
self._has_error = Event()
|
|
137
|
+
self._is_stopping = Event()
|
|
123
138
|
self.thread_error: BaseException | None = None
|
|
124
139
|
self.processing_thread = Thread(
|
|
125
140
|
target=self._process_events_loop,
|
|
126
141
|
kwargs={
|
|
127
142
|
"subscription": self.subscription,
|
|
128
143
|
"projection": self.projection,
|
|
129
|
-
"
|
|
144
|
+
"is_stopping": self._is_stopping,
|
|
130
145
|
"runner": weakref.ref(self),
|
|
131
146
|
},
|
|
132
147
|
)
|
|
@@ -143,13 +158,14 @@ class ProjectionRunner(Generic[TApplication, TTrackingRecorder]):
|
|
|
143
158
|
return Environment(name, _env)
|
|
144
159
|
|
|
145
160
|
def stop(self) -> None:
|
|
161
|
+
self._is_stopping.set()
|
|
146
162
|
self.subscription.stop()
|
|
147
163
|
|
|
148
164
|
@staticmethod
|
|
149
165
|
def _process_events_loop(
|
|
150
166
|
subscription: ApplicationSubscription,
|
|
151
167
|
projection: Projection[TrackingRecorder],
|
|
152
|
-
|
|
168
|
+
is_stopping: Event,
|
|
153
169
|
runner: weakref.ReferenceType[ProjectionRunner[Application, TrackingRecorder]],
|
|
154
170
|
) -> None:
|
|
155
171
|
try:
|
|
@@ -169,12 +185,11 @@ class ProjectionRunner(Generic[TApplication, TTrackingRecorder]):
|
|
|
169
185
|
stacklevel=2,
|
|
170
186
|
)
|
|
171
187
|
|
|
172
|
-
|
|
188
|
+
is_stopping.set()
|
|
173
189
|
subscription.subscription.stop()
|
|
174
190
|
|
|
175
191
|
def run_forever(self, timeout: float | None = None) -> None:
|
|
176
|
-
if self.
|
|
177
|
-
assert self.thread_error is not None # for mypy
|
|
192
|
+
if self._is_stopping.wait(timeout=timeout) and self.thread_error is not None:
|
|
178
193
|
raise self.thread_error
|
|
179
194
|
|
|
180
195
|
def wait(self, notification_id: int, timeout: float = 1.0) -> None:
|
|
@@ -183,11 +198,11 @@ class ProjectionRunner(Generic[TApplication, TTrackingRecorder]):
|
|
|
183
198
|
application_name=self.subscription.name,
|
|
184
199
|
notification_id=notification_id,
|
|
185
200
|
timeout=timeout,
|
|
186
|
-
interrupt=self.
|
|
201
|
+
interrupt=self._is_stopping,
|
|
187
202
|
)
|
|
188
203
|
except WaitInterruptedError:
|
|
189
|
-
|
|
190
|
-
|
|
204
|
+
if self.thread_error is not None:
|
|
205
|
+
raise self.thread_error from None
|
|
191
206
|
|
|
192
207
|
def __enter__(self) -> Self:
|
|
193
208
|
return self
|
|
@@ -471,7 +471,9 @@ class SQLiteApplicationRecorder(
|
|
|
471
471
|
c.execute(self.select_max_notification_id_statement)
|
|
472
472
|
return c.fetchone()[0]
|
|
473
473
|
|
|
474
|
-
def subscribe(
|
|
474
|
+
def subscribe(
|
|
475
|
+
self, gt: int | None = None, topics: Sequence[str] = ()
|
|
476
|
+
) -> Subscription[ApplicationRecorder]:
|
|
475
477
|
msg = f"The {type(self).__qualname__} recorder does not support subscriptions"
|
|
476
478
|
raise NotImplementedError(msg)
|
|
477
479
|
|
|
@@ -734,6 +734,18 @@ class ApplicationRecorderTestCase(TestCase, ABC):
|
|
|
734
734
|
# Shouldn't get here...
|
|
735
735
|
self.fail("Got here")
|
|
736
736
|
|
|
737
|
+
# Start a subscription with topics.
|
|
738
|
+
subscription = recorder.subscribe(gt=None, topics=["topic3"])
|
|
739
|
+
with subscription:
|
|
740
|
+
for notification in subscription:
|
|
741
|
+
self.assertEqual(notification.topic, "topic3")
|
|
742
|
+
if (
|
|
743
|
+
notification.originator_id == stored_event3.originator_id
|
|
744
|
+
and notification.originator_version
|
|
745
|
+
== stored_event3.originator_version
|
|
746
|
+
):
|
|
747
|
+
break
|
|
748
|
+
|
|
737
749
|
def close_db_connection(self, *args: Any) -> None:
|
|
738
750
|
""""""
|
|
739
751
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|