eventsourcing 9.4.4__py3-none-any.whl → 9.4.6__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 +92 -68
- eventsourcing/domain.py +479 -226
- eventsourcing/interface.py +10 -2
- eventsourcing/persistence.py +121 -33
- eventsourcing/popo.py +16 -14
- eventsourcing/postgres.py +20 -20
- eventsourcing/projection.py +25 -20
- eventsourcing/sqlite.py +28 -18
- eventsourcing/system.py +130 -90
- eventsourcing/tests/application.py +14 -97
- eventsourcing/tests/persistence.py +86 -18
- eventsourcing/tests/postgres_utils.py +27 -7
- eventsourcing/utils.py +1 -1
- {eventsourcing-9.4.4.dist-info → eventsourcing-9.4.6.dist-info}/METADATA +18 -12
- eventsourcing-9.4.6.dist-info/RECORD +26 -0
- eventsourcing-9.4.4.dist-info/RECORD +0 -26
- {eventsourcing-9.4.4.dist-info → eventsourcing-9.4.6.dist-info}/AUTHORS +0 -0
- {eventsourcing-9.4.4.dist-info → eventsourcing-9.4.6.dist-info}/LICENSE +0 -0
- {eventsourcing-9.4.4.dist-info → eventsourcing-9.4.6.dist-info}/WHEEL +0 -0
eventsourcing/projection.py
CHANGED
|
@@ -8,14 +8,15 @@ from abc import ABC, abstractmethod
|
|
|
8
8
|
from collections.abc import Iterator, Sequence
|
|
9
9
|
from threading import Event, Thread
|
|
10
10
|
from traceback import format_exc
|
|
11
|
-
from typing import TYPE_CHECKING, Any,
|
|
11
|
+
from typing import TYPE_CHECKING, Any, Generic, TypeVar
|
|
12
12
|
from warnings import warn
|
|
13
13
|
|
|
14
14
|
from eventsourcing.application import Application, ProcessingEvent
|
|
15
15
|
from eventsourcing.dispatch import singledispatchmethod
|
|
16
|
-
from eventsourcing.domain import DomainEventProtocol
|
|
16
|
+
from eventsourcing.domain import DomainEventProtocol, TAggregateID
|
|
17
17
|
from eventsourcing.persistence import (
|
|
18
18
|
InfrastructureFactory,
|
|
19
|
+
Mapper,
|
|
19
20
|
ProcessRecorder,
|
|
20
21
|
Tracking,
|
|
21
22
|
TrackingRecorder,
|
|
@@ -30,7 +31,9 @@ if TYPE_CHECKING:
|
|
|
30
31
|
from typing_extensions import Self
|
|
31
32
|
|
|
32
33
|
|
|
33
|
-
class ApplicationSubscription(
|
|
34
|
+
class ApplicationSubscription(
|
|
35
|
+
Iterator[tuple[DomainEventProtocol[TAggregateID], Tracking]]
|
|
36
|
+
):
|
|
34
37
|
"""An iterator that yields all domain events recorded in an application
|
|
35
38
|
sequence that have notification IDs greater than a given value. The iterator
|
|
36
39
|
will block when all recorded domain events have been yielded, and then
|
|
@@ -40,7 +43,7 @@ class ApplicationSubscription(Iterator[tuple[DomainEventProtocol, Tracking]]):
|
|
|
40
43
|
|
|
41
44
|
def __init__(
|
|
42
45
|
self,
|
|
43
|
-
app: Application,
|
|
46
|
+
app: Application[TAggregateID],
|
|
44
47
|
gt: int | None = None,
|
|
45
48
|
topics: Sequence[str] = (),
|
|
46
49
|
):
|
|
@@ -49,7 +52,7 @@ class ApplicationSubscription(Iterator[tuple[DomainEventProtocol, Tracking]]):
|
|
|
49
52
|
"""
|
|
50
53
|
self.name = app.name
|
|
51
54
|
self.recorder = app.recorder
|
|
52
|
-
self.mapper = app.mapper
|
|
55
|
+
self.mapper: Mapper[TAggregateID] = app.mapper
|
|
53
56
|
self.subscription = self.recorder.subscribe(gt=gt, topics=topics)
|
|
54
57
|
|
|
55
58
|
def stop(self) -> None:
|
|
@@ -68,7 +71,7 @@ class ApplicationSubscription(Iterator[tuple[DomainEventProtocol, Tracking]]):
|
|
|
68
71
|
def __iter__(self) -> Self:
|
|
69
72
|
return self
|
|
70
73
|
|
|
71
|
-
def __next__(self) -> tuple[DomainEventProtocol, Tracking]:
|
|
74
|
+
def __next__(self) -> tuple[DomainEventProtocol[TAggregateID], Tracking]:
|
|
72
75
|
"""Returns the next stored event from subscription to the application's
|
|
73
76
|
recorder. Constructs a tracking object that identifies the position of
|
|
74
77
|
the event in the application sequence. Constructs a domain event object
|
|
@@ -116,18 +119,18 @@ class Projection(ABC, Generic[TTrackingRecorder]):
|
|
|
116
119
|
@singledispatchmethod
|
|
117
120
|
@abstractmethod
|
|
118
121
|
def process_event(
|
|
119
|
-
self, domain_event: DomainEventProtocol, tracking: Tracking
|
|
122
|
+
self, domain_event: DomainEventProtocol[TAggregateID], tracking: Tracking
|
|
120
123
|
) -> None:
|
|
121
124
|
"""Process a domain event and track it."""
|
|
122
125
|
|
|
123
126
|
|
|
124
|
-
class EventSourcedProjection(Application, ABC):
|
|
127
|
+
class EventSourcedProjection(Application[TAggregateID], ABC):
|
|
125
128
|
"""Extends the :py:class:`~eventsourcing.application.Application` class
|
|
126
129
|
by using a process recorder as its application recorder, and by
|
|
127
130
|
processing domain events through its :py:func:`policy` method.
|
|
128
131
|
"""
|
|
129
132
|
|
|
130
|
-
topics:
|
|
133
|
+
topics: Sequence[str] = ()
|
|
131
134
|
|
|
132
135
|
def __init__(self, env: EnvType | None = None) -> None:
|
|
133
136
|
super().__init__(env)
|
|
@@ -141,7 +144,7 @@ class EventSourcedProjection(Application, ABC):
|
|
|
141
144
|
return self.factory.process_recorder()
|
|
142
145
|
|
|
143
146
|
def process_event(
|
|
144
|
-
self, domain_event: DomainEventProtocol, tracking: Tracking
|
|
147
|
+
self, domain_event: DomainEventProtocol[TAggregateID], tracking: Tracking
|
|
145
148
|
) -> None:
|
|
146
149
|
"""Calls :func:`~eventsourcing.system.Follower.policy` method with the given
|
|
147
150
|
domain event and a new :class:`~eventsourcing.application.ProcessingEvent`
|
|
@@ -158,7 +161,7 @@ class EventSourcedProjection(Application, ABC):
|
|
|
158
161
|
the recordings are passed in a call to
|
|
159
162
|
:py:func:`~eventsourcing.application.Application._notify`.
|
|
160
163
|
"""
|
|
161
|
-
processing_event = ProcessingEvent(tracking=tracking)
|
|
164
|
+
processing_event = ProcessingEvent[TAggregateID](tracking=tracking)
|
|
162
165
|
self.policy(domain_event, processing_event)
|
|
163
166
|
recordings = self._record(processing_event)
|
|
164
167
|
self._take_snapshots(processing_event)
|
|
@@ -168,8 +171,8 @@ class EventSourcedProjection(Application, ABC):
|
|
|
168
171
|
@singledispatchmethod
|
|
169
172
|
def policy(
|
|
170
173
|
self,
|
|
171
|
-
domain_event: DomainEventProtocol,
|
|
172
|
-
processing_event: ProcessingEvent,
|
|
174
|
+
domain_event: DomainEventProtocol[TAggregateID],
|
|
175
|
+
processing_event: ProcessingEvent[TAggregateID],
|
|
173
176
|
) -> None:
|
|
174
177
|
"""Abstract domain event processing policy method. Must be
|
|
175
178
|
implemented by event processing applications. When
|
|
@@ -183,9 +186,9 @@ class EventSourcedProjection(Application, ABC):
|
|
|
183
186
|
"""
|
|
184
187
|
|
|
185
188
|
|
|
186
|
-
TApplication = TypeVar("TApplication", bound=Application)
|
|
189
|
+
TApplication = TypeVar("TApplication", bound=Application[Any])
|
|
187
190
|
TEventSourcedProjection = TypeVar(
|
|
188
|
-
"TEventSourcedProjection", bound=EventSourcedProjection
|
|
191
|
+
"TEventSourcedProjection", bound=EventSourcedProjection[Any]
|
|
189
192
|
)
|
|
190
193
|
|
|
191
194
|
|
|
@@ -193,7 +196,7 @@ class BaseProjectionRunner(Generic[TApplication]):
|
|
|
193
196
|
def __init__(
|
|
194
197
|
self,
|
|
195
198
|
*,
|
|
196
|
-
projection: EventSourcedProjection | Projection[Any],
|
|
199
|
+
projection: EventSourcedProjection[Any] | Projection[Any],
|
|
197
200
|
application_class: type[TApplication],
|
|
198
201
|
tracking_recorder: TrackingRecorder,
|
|
199
202
|
topics: Sequence[str],
|
|
@@ -258,7 +261,7 @@ class BaseProjectionRunner(Generic[TApplication]):
|
|
|
258
261
|
|
|
259
262
|
@staticmethod
|
|
260
263
|
def _stop_subscription_when_stopping(
|
|
261
|
-
subscription: ApplicationSubscription,
|
|
264
|
+
subscription: ApplicationSubscription[TAggregateID],
|
|
262
265
|
is_stopping: Event,
|
|
263
266
|
) -> None:
|
|
264
267
|
"""Stops the application subscription, which
|
|
@@ -272,10 +275,12 @@ class BaseProjectionRunner(Generic[TApplication]):
|
|
|
272
275
|
|
|
273
276
|
@staticmethod
|
|
274
277
|
def _process_events_loop(
|
|
275
|
-
subscription: ApplicationSubscription,
|
|
276
|
-
projection: EventSourcedProjection | Projection[Any],
|
|
278
|
+
subscription: ApplicationSubscription[TAggregateID],
|
|
279
|
+
projection: EventSourcedProjection[Any] | Projection[Any],
|
|
277
280
|
is_stopping: Event,
|
|
278
|
-
runner: weakref.ReferenceType[
|
|
281
|
+
runner: weakref.ReferenceType[
|
|
282
|
+
ProjectionRunner[Application[Any], TrackingRecorder]
|
|
283
|
+
],
|
|
279
284
|
) -> None:
|
|
280
285
|
"""Iterates over the subscription and calls process_event()."""
|
|
281
286
|
try:
|
eventsourcing/sqlite.py
CHANGED
|
@@ -29,7 +29,7 @@ from eventsourcing.persistence import (
|
|
|
29
29
|
Tracking,
|
|
30
30
|
TrackingRecorder,
|
|
31
31
|
)
|
|
32
|
-
from eventsourcing.utils import Environment, resolve_topic, strtobool
|
|
32
|
+
from eventsourcing.utils import Environment, EnvType, resolve_topic, strtobool
|
|
33
33
|
|
|
34
34
|
if TYPE_CHECKING:
|
|
35
35
|
from collections.abc import Iterator, Sequence
|
|
@@ -300,7 +300,7 @@ class SQLiteAggregateRecorder(SQLiteRecorder, AggregateRecorder):
|
|
|
300
300
|
return statements
|
|
301
301
|
|
|
302
302
|
def insert_events(
|
|
303
|
-
self, stored_events:
|
|
303
|
+
self, stored_events: Sequence[StoredEvent], **kwargs: Any
|
|
304
304
|
) -> Sequence[int] | None:
|
|
305
305
|
with self.datastore.transaction(commit=True) as c:
|
|
306
306
|
return self._insert_events(c, stored_events, **kwargs)
|
|
@@ -308,12 +308,16 @@ class SQLiteAggregateRecorder(SQLiteRecorder, AggregateRecorder):
|
|
|
308
308
|
def _insert_events(
|
|
309
309
|
self,
|
|
310
310
|
c: SQLiteCursor,
|
|
311
|
-
stored_events:
|
|
311
|
+
stored_events: Sequence[StoredEvent],
|
|
312
312
|
**_: Any,
|
|
313
313
|
) -> Sequence[int] | None:
|
|
314
314
|
params = [
|
|
315
315
|
(
|
|
316
|
-
|
|
316
|
+
(
|
|
317
|
+
s.originator_id.hex
|
|
318
|
+
if isinstance(s.originator_id, UUID)
|
|
319
|
+
else s.originator_id
|
|
320
|
+
),
|
|
317
321
|
s.originator_version,
|
|
318
322
|
s.topic,
|
|
319
323
|
s.state,
|
|
@@ -325,15 +329,17 @@ class SQLiteAggregateRecorder(SQLiteRecorder, AggregateRecorder):
|
|
|
325
329
|
|
|
326
330
|
def select_events(
|
|
327
331
|
self,
|
|
328
|
-
originator_id: UUID,
|
|
332
|
+
originator_id: UUID | str,
|
|
329
333
|
*,
|
|
330
334
|
gt: int | None = None,
|
|
331
335
|
lte: int | None = None,
|
|
332
336
|
desc: bool = False,
|
|
333
337
|
limit: int | None = None,
|
|
334
|
-
) ->
|
|
338
|
+
) -> Sequence[StoredEvent]:
|
|
335
339
|
statement = self.select_events_statement
|
|
336
|
-
params: list[Any] = [
|
|
340
|
+
params: list[Any] = [
|
|
341
|
+
originator_id.hex if isinstance(originator_id, UUID) else originator_id
|
|
342
|
+
]
|
|
337
343
|
if gt is not None:
|
|
338
344
|
statement += "AND originator_version>? "
|
|
339
345
|
params.append(gt)
|
|
@@ -352,7 +358,7 @@ class SQLiteAggregateRecorder(SQLiteRecorder, AggregateRecorder):
|
|
|
352
358
|
c.execute(statement, params)
|
|
353
359
|
return [
|
|
354
360
|
StoredEvent(
|
|
355
|
-
originator_id=
|
|
361
|
+
originator_id=row["originator_id"],
|
|
356
362
|
originator_version=row["originator_version"],
|
|
357
363
|
topic=row["topic"],
|
|
358
364
|
state=row["state"],
|
|
@@ -391,18 +397,22 @@ class SQLiteApplicationRecorder(
|
|
|
391
397
|
def _insert_events(
|
|
392
398
|
self,
|
|
393
399
|
c: SQLiteCursor,
|
|
394
|
-
stored_events:
|
|
400
|
+
stored_events: Sequence[StoredEvent],
|
|
395
401
|
**_: Any,
|
|
396
402
|
) -> Sequence[int] | None:
|
|
397
403
|
returning = []
|
|
398
|
-
for
|
|
404
|
+
for s in stored_events:
|
|
399
405
|
c.execute(
|
|
400
406
|
self.insert_events_statement,
|
|
401
407
|
(
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
408
|
+
(
|
|
409
|
+
s.originator_id.hex
|
|
410
|
+
if isinstance(s.originator_id, UUID)
|
|
411
|
+
else s.originator_id
|
|
412
|
+
),
|
|
413
|
+
s.originator_version,
|
|
414
|
+
s.topic,
|
|
415
|
+
s.state,
|
|
406
416
|
),
|
|
407
417
|
)
|
|
408
418
|
returning.append(c.lastrowid)
|
|
@@ -416,7 +426,7 @@ class SQLiteApplicationRecorder(
|
|
|
416
426
|
topics: Sequence[str] = (),
|
|
417
427
|
*,
|
|
418
428
|
inclusive_of_start: bool = True,
|
|
419
|
-
) ->
|
|
429
|
+
) -> Sequence[Notification]:
|
|
420
430
|
"""Returns a list of event notifications
|
|
421
431
|
from 'start', limited by 'limit'.
|
|
422
432
|
"""
|
|
@@ -457,7 +467,7 @@ class SQLiteApplicationRecorder(
|
|
|
457
467
|
return [
|
|
458
468
|
Notification(
|
|
459
469
|
id=row["rowid"],
|
|
460
|
-
originator_id=
|
|
470
|
+
originator_id=row["originator_id"],
|
|
461
471
|
originator_version=row["originator_version"],
|
|
462
472
|
topic=row["topic"],
|
|
463
473
|
state=row["state"],
|
|
@@ -650,7 +660,7 @@ class SQLiteProcessRecorder(
|
|
|
650
660
|
def _insert_events(
|
|
651
661
|
self,
|
|
652
662
|
c: SQLiteCursor,
|
|
653
|
-
stored_events:
|
|
663
|
+
stored_events: Sequence[StoredEvent],
|
|
654
664
|
**kwargs: Any,
|
|
655
665
|
) -> Sequence[int] | None:
|
|
656
666
|
returning = super()._insert_events(c, stored_events, **kwargs)
|
|
@@ -671,7 +681,7 @@ class SQLiteFactory(InfrastructureFactory[SQLiteTrackingRecorder]):
|
|
|
671
681
|
tracking_recorder_class = SQLiteTrackingRecorder
|
|
672
682
|
process_recorder_class = SQLiteProcessRecorder
|
|
673
683
|
|
|
674
|
-
def __init__(self, env: Environment):
|
|
684
|
+
def __init__(self, env: Environment | EnvType | None):
|
|
675
685
|
super().__init__(env)
|
|
676
686
|
db_name = self.env.get(self.SQLITE_DBNAME)
|
|
677
687
|
if not db_name:
|